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