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