1 /* 2 * String Table Functions 3 * 4 * Copyright 2002-2004, Mike McCormack for CodeWeavers 5 * Copyright 2007 Robert Shearman for CodeWeavers 6 * Copyright 2010 Hans Leidekker for CodeWeavers 7 * 8 * This library is free software; you can redistribute it and/or 9 * modify it under the terms of the GNU Lesser General Public 10 * License as published by the Free Software Foundation; either 11 * version 2.1 of the License, or (at your option) any later version. 12 * 13 * This library is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 * Lesser General Public License for more details. 17 * 18 * You should have received a copy of the GNU Lesser General Public 19 * License along with this library; if not, write to the Free Software 20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA 21 */ 22 23 #define COBJMACROS 24 25 #include <stdarg.h> 26 #include <assert.h> 27 28 #include "windef.h" 29 #include "winbase.h" 30 #include "winerror.h" 31 #include "wine/debug.h" 32 #include "msi.h" 33 #include "msiquery.h" 34 #include "objbase.h" 35 #include "objidl.h" 36 #include "msipriv.h" 37 #include "winnls.h" 38 39 #include "query.h" 40 41 WINE_DEFAULT_DEBUG_CHANNEL(msidb); 42 43 struct msistring 44 { 45 USHORT persistent_refcount; 46 USHORT nonpersistent_refcount; 47 WCHAR *data; 48 int len; 49 }; 50 51 struct string_table 52 { 53 UINT maxcount; /* the number of strings */ 54 UINT freeslot; 55 UINT codepage; 56 UINT sortcount; 57 struct msistring *strings; /* an array of strings */ 58 UINT *sorted; /* index */ 59 }; 60 61 static BOOL validate_codepage( UINT codepage ) 62 { 63 if (codepage != CP_ACP && !IsValidCodePage( codepage )) 64 { 65 WARN("invalid codepage %u\n", codepage); 66 return FALSE; 67 } 68 return TRUE; 69 } 70 71 static string_table *init_stringtable( int entries, UINT codepage ) 72 { 73 string_table *st; 74 75 if (!validate_codepage( codepage )) 76 return NULL; 77 78 st = msi_alloc( sizeof (string_table) ); 79 if( !st ) 80 return NULL; 81 if( entries < 1 ) 82 entries = 1; 83 84 st->strings = msi_alloc_zero( sizeof(struct msistring) * entries ); 85 if( !st->strings ) 86 { 87 msi_free( st ); 88 return NULL; 89 } 90 91 st->sorted = msi_alloc( sizeof (UINT) * entries ); 92 if( !st->sorted ) 93 { 94 msi_free( st->strings ); 95 msi_free( st ); 96 return NULL; 97 } 98 99 st->maxcount = entries; 100 st->freeslot = 1; 101 st->codepage = codepage; 102 st->sortcount = 0; 103 104 return st; 105 } 106 107 VOID msi_destroy_stringtable( string_table *st ) 108 { 109 UINT i; 110 111 for( i=0; i<st->maxcount; i++ ) 112 { 113 if( st->strings[i].persistent_refcount || 114 st->strings[i].nonpersistent_refcount ) 115 msi_free( st->strings[i].data ); 116 } 117 msi_free( st->strings ); 118 msi_free( st->sorted ); 119 msi_free( st ); 120 } 121 122 static int st_find_free_entry( string_table *st ) 123 { 124 UINT i, sz, *s; 125 struct msistring *p; 126 127 TRACE("%p\n", st); 128 129 if( st->freeslot ) 130 { 131 for( i = st->freeslot; i < st->maxcount; i++ ) 132 if( !st->strings[i].persistent_refcount && 133 !st->strings[i].nonpersistent_refcount ) 134 return i; 135 } 136 for( i = 1; i < st->maxcount; i++ ) 137 if( !st->strings[i].persistent_refcount && 138 !st->strings[i].nonpersistent_refcount ) 139 return i; 140 141 /* dynamically resize */ 142 sz = st->maxcount + 1 + st->maxcount / 2; 143 if (!(p = msi_realloc( st->strings, sz * sizeof(*p) ))) return -1; 144 memset( p + st->maxcount, 0, (sz - st->maxcount) * sizeof(*p) ); 145 146 if (!(s = msi_realloc( st->sorted, sz * sizeof(*s) ))) 147 { 148 msi_free( p ); 149 return -1; 150 } 151 152 st->strings = p; 153 st->sorted = s; 154 155 st->freeslot = st->maxcount; 156 st->maxcount = sz; 157 if( st->strings[st->freeslot].persistent_refcount || 158 st->strings[st->freeslot].nonpersistent_refcount ) 159 ERR("oops. expected freeslot to be free...\n"); 160 return st->freeslot; 161 } 162 163 static inline int cmp_string( const WCHAR *str1, int len1, const WCHAR *str2, int len2 ) 164 { 165 if (len1 < len2) return -1; 166 else if (len1 > len2) return 1; 167 while (len1) 168 { 169 if (*str1 == *str2) { str1++; str2++; } 170 else return *str1 - *str2; 171 len1--; 172 } 173 return 0; 174 } 175 176 static int find_insert_index( const string_table *st, UINT string_id ) 177 { 178 int i, c, low = 0, high = st->sortcount - 1; 179 180 while (low <= high) 181 { 182 i = (low + high) / 2; 183 c = cmp_string( st->strings[string_id].data, st->strings[string_id].len, 184 st->strings[st->sorted[i]].data, st->strings[st->sorted[i]].len ); 185 if (c < 0) 186 high = i - 1; 187 else if (c > 0) 188 low = i + 1; 189 else 190 return -1; /* already exists */ 191 } 192 return high + 1; 193 } 194 195 static void insert_string_sorted( string_table *st, UINT string_id ) 196 { 197 int i; 198 199 i = find_insert_index( st, string_id ); 200 if (i == -1) 201 return; 202 203 memmove( &st->sorted[i] + 1, &st->sorted[i], (st->sortcount - i) * sizeof(UINT) ); 204 st->sorted[i] = string_id; 205 st->sortcount++; 206 } 207 208 static void set_st_entry( string_table *st, UINT n, WCHAR *str, int len, USHORT refcount, 209 BOOL persistent ) 210 { 211 if (persistent) 212 { 213 st->strings[n].persistent_refcount = refcount; 214 st->strings[n].nonpersistent_refcount = 0; 215 } 216 else 217 { 218 st->strings[n].persistent_refcount = 0; 219 st->strings[n].nonpersistent_refcount = refcount; 220 } 221 222 st->strings[n].data = str; 223 st->strings[n].len = len; 224 225 insert_string_sorted( st, n ); 226 227 if( n < st->maxcount ) 228 st->freeslot = n + 1; 229 } 230 231 static UINT string2id( const string_table *st, const char *buffer, UINT *id ) 232 { 233 int sz; 234 UINT r = ERROR_INVALID_PARAMETER; 235 LPWSTR str; 236 237 TRACE("Finding string %s in string table\n", debugstr_a(buffer) ); 238 239 if( buffer[0] == 0 ) 240 { 241 *id = 0; 242 return ERROR_SUCCESS; 243 } 244 245 if (!(sz = MultiByteToWideChar( st->codepage, 0, buffer, -1, NULL, 0 ))) 246 return r; 247 str = msi_alloc( sz*sizeof(WCHAR) ); 248 if( !str ) 249 return ERROR_NOT_ENOUGH_MEMORY; 250 MultiByteToWideChar( st->codepage, 0, buffer, -1, str, sz ); 251 252 r = msi_string2id( st, str, sz - 1, id ); 253 msi_free( str ); 254 return r; 255 } 256 257 static int add_string( string_table *st, UINT n, const char *data, UINT len, USHORT refcount, BOOL persistent ) 258 { 259 LPWSTR str; 260 int sz; 261 262 if( !data || !len ) 263 return 0; 264 if( n > 0 ) 265 { 266 if( st->strings[n].persistent_refcount || 267 st->strings[n].nonpersistent_refcount ) 268 return -1; 269 } 270 else 271 { 272 if (string2id( st, data, &n ) == ERROR_SUCCESS) 273 { 274 if (persistent) 275 st->strings[n].persistent_refcount += refcount; 276 else 277 st->strings[n].nonpersistent_refcount += refcount; 278 return n; 279 } 280 n = st_find_free_entry( st ); 281 if( n == -1 ) 282 return -1; 283 } 284 285 if( n < 1 ) 286 { 287 ERR("invalid index adding %s (%d)\n", debugstr_a( data ), n ); 288 return -1; 289 } 290 291 /* allocate a new string */ 292 sz = MultiByteToWideChar( st->codepage, 0, data, len, NULL, 0 ); 293 str = msi_alloc( (sz+1)*sizeof(WCHAR) ); 294 if( !str ) 295 return -1; 296 MultiByteToWideChar( st->codepage, 0, data, len, str, sz ); 297 str[sz] = 0; 298 299 set_st_entry( st, n, str, sz, refcount, persistent ); 300 return n; 301 } 302 303 int msi_add_string( string_table *st, const WCHAR *data, int len, BOOL persistent ) 304 { 305 UINT n; 306 LPWSTR str; 307 308 if( !data ) 309 return 0; 310 311 if (len < 0) len = lstrlenW( data ); 312 313 if( !data[0] && !len ) 314 return 0; 315 316 if (msi_string2id( st, data, len, &n) == ERROR_SUCCESS ) 317 { 318 if (persistent) 319 st->strings[n].persistent_refcount++; 320 else 321 st->strings[n].nonpersistent_refcount++; 322 return n; 323 } 324 325 n = st_find_free_entry( st ); 326 if( n == -1 ) 327 return -1; 328 329 /* allocate a new string */ 330 TRACE( "%s, n = %d len = %d\n", debugstr_wn(data, len), n, len ); 331 332 str = msi_alloc( (len+1)*sizeof(WCHAR) ); 333 if( !str ) 334 return -1; 335 memcpy( str, data, len*sizeof(WCHAR) ); 336 str[len] = 0; 337 338 set_st_entry( st, n, str, len, 1, persistent ); 339 return n; 340 } 341 342 /* find the string identified by an id - return null if there's none */ 343 const WCHAR *msi_string_lookup( const string_table *st, UINT id, int *len ) 344 { 345 if( id == 0 ) 346 { 347 if (len) *len = 0; 348 return L""; 349 } 350 if( id >= st->maxcount ) 351 return NULL; 352 353 if( id && !st->strings[id].persistent_refcount && !st->strings[id].nonpersistent_refcount) 354 return NULL; 355 356 if (len) *len = st->strings[id].len; 357 358 return st->strings[id].data; 359 } 360 361 /* 362 * id2string 363 * 364 * [in] st - pointer to the string table 365 * [in] id - id of the string to retrieve 366 * [out] buffer - destination of the UTF8 string 367 * [in/out] sz - number of bytes available in the buffer on input 368 * number of bytes used on output 369 * 370 * Returned string is not nul terminated. 371 */ 372 static UINT id2string( const string_table *st, UINT id, char *buffer, UINT *sz ) 373 { 374 int len, lenW; 375 const WCHAR *str; 376 377 TRACE("Finding string %d of %d\n", id, st->maxcount); 378 379 str = msi_string_lookup( st, id, &lenW ); 380 if( !str ) 381 return ERROR_FUNCTION_FAILED; 382 383 len = WideCharToMultiByte( st->codepage, 0, str, lenW, NULL, 0, NULL, NULL ); 384 if( *sz < len ) 385 { 386 *sz = len; 387 return ERROR_MORE_DATA; 388 } 389 *sz = WideCharToMultiByte( st->codepage, 0, str, lenW, buffer, *sz, NULL, NULL ); 390 return ERROR_SUCCESS; 391 } 392 393 /* 394 * msi_string2id 395 * 396 * [in] st - pointer to the string table 397 * [in] str - string to find in the string table 398 * [out] id - id of the string, if found 399 */ 400 UINT msi_string2id( const string_table *st, const WCHAR *str, int len, UINT *id ) 401 { 402 int i, c, low = 0, high = st->sortcount - 1; 403 404 if (len < 0) len = lstrlenW( str ); 405 406 while (low <= high) 407 { 408 i = (low + high) / 2; 409 c = cmp_string( str, len, st->strings[st->sorted[i]].data, st->strings[st->sorted[i]].len ); 410 411 if (c < 0) 412 high = i - 1; 413 else if (c > 0) 414 low = i + 1; 415 else 416 { 417 *id = st->sorted[i]; 418 return ERROR_SUCCESS; 419 } 420 } 421 return ERROR_INVALID_PARAMETER; 422 } 423 424 static void string_totalsize( const string_table *st, UINT *datasize, UINT *poolsize ) 425 { 426 UINT i, len, holesize; 427 428 if( st->strings[0].data || st->strings[0].persistent_refcount || st->strings[0].nonpersistent_refcount) 429 ERR("oops. element 0 has a string\n"); 430 431 *poolsize = 4; 432 *datasize = 0; 433 holesize = 0; 434 for( i=1; i<st->maxcount; i++ ) 435 { 436 if( !st->strings[i].persistent_refcount ) 437 { 438 TRACE("[%u] nonpersistent = %s\n", i, debugstr_wn(st->strings[i].data, st->strings[i].len)); 439 (*poolsize) += 4; 440 } 441 else if( st->strings[i].data ) 442 { 443 TRACE("[%u] = %s\n", i, debugstr_wn(st->strings[i].data, st->strings[i].len)); 444 len = WideCharToMultiByte( st->codepage, 0, st->strings[i].data, st->strings[i].len + 1, 445 NULL, 0, NULL, NULL); 446 if( len ) 447 len--; 448 (*datasize) += len; 449 if (len>0xffff) 450 (*poolsize) += 4; 451 (*poolsize) += holesize + 4; 452 holesize = 0; 453 } 454 else 455 holesize += 4; 456 } 457 TRACE("data %u pool %u codepage %x\n", *datasize, *poolsize, st->codepage ); 458 } 459 460 HRESULT msi_init_string_table( IStorage *stg ) 461 { 462 USHORT zero[2] = { 0, 0 }; 463 UINT ret; 464 465 /* create the StringPool stream... add the zero string to it*/ 466 ret = write_stream_data(stg, L"_StringPool", zero, sizeof zero, TRUE); 467 if (ret != ERROR_SUCCESS) 468 return E_FAIL; 469 470 /* create the StringData stream... make it zero length */ 471 ret = write_stream_data(stg, L"_StringData", NULL, 0, TRUE); 472 if (ret != ERROR_SUCCESS) 473 return E_FAIL; 474 475 return S_OK; 476 } 477 478 string_table *msi_load_string_table( IStorage *stg, UINT *bytes_per_strref ) 479 { 480 string_table *st = NULL; 481 CHAR *data = NULL; 482 USHORT *pool = NULL; 483 UINT r, datasize = 0, poolsize = 0, codepage; 484 DWORD i, count, offset, len, n, refs; 485 486 r = read_stream_data( stg, L"_StringPool", TRUE, (BYTE **)&pool, &poolsize ); 487 if( r != ERROR_SUCCESS) 488 goto end; 489 r = read_stream_data( stg, L"_StringData", TRUE, (BYTE **)&data, &datasize ); 490 if( r != ERROR_SUCCESS) 491 goto end; 492 493 if ( (poolsize > 4) && (pool[1] & 0x8000) ) 494 *bytes_per_strref = LONG_STR_BYTES; 495 else 496 *bytes_per_strref = sizeof(USHORT); 497 498 count = poolsize/4; 499 if( poolsize > 4 ) 500 codepage = pool[0] | ( (pool[1] & ~0x8000) << 16 ); 501 else 502 codepage = CP_ACP; 503 st = init_stringtable( count, codepage ); 504 if (!st) 505 goto end; 506 507 offset = 0; 508 n = 1; 509 i = 1; 510 while( i<count ) 511 { 512 /* the string reference count is always the second word */ 513 refs = pool[i*2+1]; 514 515 /* empty entries have two zeros, still have a string id */ 516 if (pool[i*2] == 0 && refs == 0) 517 { 518 i++; 519 n++; 520 continue; 521 } 522 523 /* 524 * If a string is over 64k, the previous string entry is made null 525 * and its the high word of the length is inserted in the null string's 526 * reference count field. 527 */ 528 if( pool[i*2] == 0) 529 { 530 len = (pool[i*2+3] << 16) + pool[i*2+2]; 531 i += 2; 532 } 533 else 534 { 535 len = pool[i*2]; 536 i += 1; 537 } 538 539 if ( (offset + len) > datasize ) 540 { 541 ERR("string table corrupt?\n"); 542 break; 543 } 544 545 r = add_string( st, n, data+offset, len, refs, TRUE ); 546 if( r != n ) 547 ERR( "Failed to add string %lu\n", n ); 548 n++; 549 offset += len; 550 } 551 552 if ( datasize != offset ) 553 ERR( "string table load failed! (%u != %lu), please report\n", datasize, offset ); 554 555 TRACE( "loaded %lu strings\n", count ); 556 557 end: 558 msi_free( pool ); 559 msi_free( data ); 560 561 return st; 562 } 563 564 UINT msi_save_string_table( const string_table *st, IStorage *storage, UINT *bytes_per_strref ) 565 { 566 UINT i, datasize = 0, poolsize = 0, sz, used, r, codepage, n; 567 UINT ret = ERROR_FUNCTION_FAILED; 568 CHAR *data = NULL; 569 USHORT *pool = NULL; 570 571 TRACE("\n"); 572 573 /* construct the new table in memory first */ 574 string_totalsize( st, &datasize, &poolsize ); 575 576 TRACE("%u %u %u\n", st->maxcount, datasize, poolsize ); 577 578 pool = msi_alloc( poolsize ); 579 if( ! pool ) 580 { 581 WARN("Failed to alloc pool %d bytes\n", poolsize ); 582 goto err; 583 } 584 data = msi_alloc( datasize ); 585 if( ! data ) 586 { 587 WARN("Failed to alloc data %d bytes\n", datasize ); 588 goto err; 589 } 590 591 used = 0; 592 codepage = st->codepage; 593 pool[0] = codepage & 0xffff; 594 pool[1] = codepage >> 16; 595 if (st->maxcount > 0xffff) 596 { 597 pool[1] |= 0x8000; 598 *bytes_per_strref = LONG_STR_BYTES; 599 } 600 else 601 *bytes_per_strref = sizeof(USHORT); 602 603 n = 1; 604 for( i=1; i<st->maxcount; i++ ) 605 { 606 if( !st->strings[i].persistent_refcount ) 607 { 608 pool[ n*2 ] = 0; 609 pool[ n*2 + 1] = 0; 610 n++; 611 continue; 612 } 613 614 sz = datasize - used; 615 r = id2string( st, i, data+used, &sz ); 616 if( r != ERROR_SUCCESS ) 617 { 618 ERR("failed to fetch string\n"); 619 sz = 0; 620 } 621 622 if (sz) 623 pool[ n*2 + 1 ] = st->strings[i].persistent_refcount; 624 else 625 pool[ n*2 + 1 ] = 0; 626 if (sz < 0x10000) 627 { 628 pool[ n*2 ] = sz; 629 n++; 630 } 631 else 632 { 633 pool[ n*2 ] = 0; 634 pool[ n*2 + 2 ] = sz&0xffff; 635 pool[ n*2 + 3 ] = (sz>>16); 636 n += 2; 637 } 638 used += sz; 639 if( used > datasize ) 640 { 641 ERR("oops overran %d >= %d\n", used, datasize); 642 goto err; 643 } 644 } 645 646 if( used != datasize ) 647 { 648 ERR("oops used %d != datasize %d\n", used, datasize); 649 goto err; 650 } 651 652 /* write the streams */ 653 r = write_stream_data( storage, L"_StringData", data, datasize, TRUE ); 654 TRACE("Wrote StringData r=%08x\n", r); 655 if( r ) 656 goto err; 657 r = write_stream_data( storage, L"_StringPool", pool, poolsize, TRUE ); 658 TRACE("Wrote StringPool r=%08x\n", r); 659 if( r ) 660 goto err; 661 662 ret = ERROR_SUCCESS; 663 664 err: 665 msi_free( data ); 666 msi_free( pool ); 667 668 return ret; 669 } 670 671 UINT msi_get_string_table_codepage( const string_table *st ) 672 { 673 return st->codepage; 674 } 675 676 UINT msi_set_string_table_codepage( string_table *st, UINT codepage ) 677 { 678 if (validate_codepage( codepage )) 679 { 680 st->codepage = codepage; 681 return ERROR_SUCCESS; 682 } 683 return ERROR_FUNCTION_FAILED; 684 } 685