msi: Move some registry helper functions to registry.c.
[wine] / dlls / msi / string.c
1 /*
2  * Implementation of the Microsoft Installer (msi.dll)
3  *
4  * Copyright 2002-2004, Mike McCormack for CodeWeavers
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20
21 #include <stdarg.h>
22 #include <assert.h>
23
24 #include "windef.h"
25 #include "winbase.h"
26 #include "winerror.h"
27 #include "wine/debug.h"
28 #include "wine/unicode.h"
29 #include "msi.h"
30 #include "msiquery.h"
31 #include "objbase.h"
32 #include "objidl.h"
33 #include "msipriv.h"
34 #include "winnls.h"
35
36 #include "query.h"
37
38 WINE_DEFAULT_DEBUG_CHANNEL(msidb);
39
40 #define HASH_SIZE 67
41
42 typedef struct _msistring
43 {
44     int hash_next;
45     UINT refcount;
46     LPWSTR str;
47 } msistring;
48
49 struct string_table
50 {
51     UINT maxcount;         /* the number of strings */
52     UINT freeslot;
53     UINT codepage;
54     int hash[HASH_SIZE];
55     msistring *strings; /* an array of strings (in the tree) */
56 };
57
58 static UINT msistring_makehash( const WCHAR *str )
59 {
60     UINT hash = 0;
61
62     if (str==NULL)
63         return hash;
64
65     while( *str )
66     {
67         hash ^= *str++;
68         hash *= 53;
69         hash = (hash<<5) | (hash>>27);
70     }
71     return hash % HASH_SIZE;
72 }
73
74 string_table *msi_init_stringtable( int entries, UINT codepage )
75 {
76     string_table *st;
77     int i;
78
79     st = msi_alloc( sizeof (string_table) );
80     if( !st )
81         return NULL;    
82     if( entries < 1 )
83         entries = 1;
84     st->strings = msi_alloc_zero( sizeof (msistring) * entries );
85     if( !st->strings )
86     {
87         msi_free( st );
88         return NULL;    
89     }
90     st->maxcount = entries;
91     st->freeslot = 1;
92     st->codepage = codepage;
93
94     for( i=0; i<HASH_SIZE; i++ )
95         st->hash[i] = -1;
96
97     return st;
98 }
99
100 VOID msi_destroy_stringtable( string_table *st )
101 {
102     UINT i;
103
104     for( i=0; i<st->maxcount; i++ )
105     {
106         if( st->strings[i].refcount )
107             msi_free( st->strings[i].str );
108     }
109     msi_free( st->strings );
110     msi_free( st );
111 }
112
113 static int st_find_free_entry( string_table *st )
114 {
115     UINT i, sz;
116     msistring *p;
117
118     TRACE("%p\n", st);
119
120     if( st->freeslot )
121     {
122         for( i = st->freeslot; i < st->maxcount; i++ )
123             if( !st->strings[i].refcount )
124                 return i;
125     }
126     for( i = 1; i < st->maxcount; i++ )
127         if( !st->strings[i].refcount )
128             return i;
129
130     /* dynamically resize */
131     sz = st->maxcount + 1 + st->maxcount/2;
132     p = msi_realloc_zero( st->strings, sz*sizeof(msistring) );
133     if( !p )
134         return -1;
135     st->strings = p;
136     st->freeslot = st->maxcount;
137     st->maxcount = sz;
138     if( st->strings[st->freeslot].refcount )
139         ERR("oops. expected freeslot to be free...\n");
140     return st->freeslot;
141 }
142
143 static void set_st_entry( string_table *st, UINT n, LPWSTR str )
144 {
145     UINT hash = msistring_makehash( str );
146
147     st->strings[n].refcount = 1;
148     st->strings[n].str = str;
149
150     st->strings[n].hash_next = st->hash[hash];
151     st->hash[hash] = n;
152
153     if( n < st->maxcount )
154         st->freeslot = n + 1;
155 }
156
157 int msi_addstring( string_table *st, UINT n, const CHAR *data, int len, UINT refcount )
158 {
159     LPWSTR str;
160     int sz;
161
162     if( !data )
163         return 0;
164     if( !data[0] )
165         return 0;
166     if( n > 0 )
167     {
168         if( st->strings[n].refcount )
169             return -1;
170     }
171     else
172     {
173         if( ERROR_SUCCESS == msi_string2idA( st, data, &n ) )
174         {
175             st->strings[n].refcount++;
176             return n;
177         }
178         n = st_find_free_entry( st );
179         if( n < 0 )
180             return -1;
181     }
182
183     if( n < 1 )
184     {
185         ERR("invalid index adding %s (%d)\n", debugstr_a( data ), n );
186         return -1;
187     }
188
189     /* allocate a new string */
190     if( len < 0 )
191         len = strlen(data);
192     sz = MultiByteToWideChar( st->codepage, 0, data, len, NULL, 0 );
193     str = msi_alloc( (sz+1)*sizeof(WCHAR) );
194     if( !str )
195         return -1;
196     MultiByteToWideChar( st->codepage, 0, data, len, str, sz );
197     str[sz] = 0;
198
199     set_st_entry( st, n, str );
200
201     return n;
202 }
203
204 int msi_addstringW( string_table *st, UINT n, const WCHAR *data, int len, UINT refcount )
205 {
206     LPWSTR str;
207
208     /* TRACE("[%2d] = %s\n", string_no, debugstr_an(data,len) ); */
209
210     if( !data )
211         return 0;
212     if( !data[0] )
213         return 0;
214     if( n > 0 )
215     {
216         if( st->strings[n].refcount )
217             return -1;
218     }
219     else
220     {
221         if( ERROR_SUCCESS == msi_string2idW( st, data, &n ) )
222         {
223             st->strings[n].refcount++;
224             return n;
225         }
226         n = st_find_free_entry( st );
227         if( n < 0 )
228             return -1;
229     }
230
231     if( n < 1 )
232     {
233         ERR("invalid index adding %s (%d)\n", debugstr_w( data ), n );
234         return -1;
235     }
236
237     /* allocate a new string */
238     if(len<0)
239         len = strlenW(data);
240     TRACE("%s, n = %d len = %d\n", debugstr_w(data), n, len );
241
242     str = msi_alloc( (len+1)*sizeof(WCHAR) );
243     if( !str )
244         return -1;
245     TRACE("%d\n",__LINE__);
246     memcpy( str, data, len*sizeof(WCHAR) );
247     str[len] = 0;
248
249     set_st_entry( st, n, str );
250
251     return n;
252 }
253
254 /* find the string identified by an id - return null if there's none */
255 const WCHAR *msi_string_lookup_id( string_table *st, UINT id )
256 {
257     static const WCHAR zero[] = { 0 };
258     if( id == 0 )
259         return zero;
260
261     if( id >= st->maxcount )
262         return NULL;
263
264     if( id && !st->strings[id].refcount )
265         return NULL;
266
267     return st->strings[id].str;
268 }
269
270 /*
271  *  msi_id2stringW
272  *
273  *  [in] st         - pointer to the string table
274  *  [in] id  - id of the string to retrieve
275  *  [out] buffer    - destination of the string
276  *  [in/out] sz     - number of bytes available in the buffer on input
277  *                    number of bytes used on output
278  *
279  *   The size includes the terminating nul character.  Short buffers
280  *  will be filled, but not nul terminated.
281  */
282 UINT msi_id2stringW( string_table *st, UINT id, LPWSTR buffer, UINT *sz )
283 {
284     UINT len;
285     const WCHAR *str;
286
287     TRACE("Finding string %d of %d\n", id, st->maxcount);
288
289     str = msi_string_lookup_id( st, id );
290     if( !str )
291         return ERROR_FUNCTION_FAILED;
292
293     len = strlenW( str ) + 1;
294
295     if( !buffer )
296     {
297         *sz = len;
298         return ERROR_SUCCESS;
299     }
300
301     if( *sz < len )
302         *sz = len;
303     memcpy( buffer, str, (*sz)*sizeof(WCHAR) ); 
304     *sz = len;
305
306     return ERROR_SUCCESS;
307 }
308
309 /*
310  *  msi_id2stringA
311  *
312  *  [in] st         - pointer to the string table
313  *  [in] id         - id of the string to retrieve
314  *  [out] buffer    - destination of the UTF8 string
315  *  [in/out] sz     - number of bytes available in the buffer on input
316  *                    number of bytes used on output
317  *
318  *   The size includes the terminating nul character.  Short buffers
319  *  will be filled, but not nul terminated.
320  */
321 UINT msi_id2stringA( string_table *st, UINT id, LPSTR buffer, UINT *sz )
322 {
323     UINT len;
324     const WCHAR *str;
325     int n;
326
327     TRACE("Finding string %d of %d\n", id, st->maxcount);
328
329     str = msi_string_lookup_id( st, id );
330     if( !str )
331         return ERROR_FUNCTION_FAILED;
332
333     len = WideCharToMultiByte( st->codepage, 0, str, -1, NULL, 0, NULL, NULL );
334
335     if( !buffer )
336     {
337         *sz = len;
338         return ERROR_SUCCESS;
339     }
340
341     if( len > *sz )
342     {
343         n = strlenW( str ) + 1;
344         while( n && (len > *sz) )
345             len = WideCharToMultiByte( st->codepage, 0, 
346                            str, --n, NULL, 0, NULL, NULL );
347     }
348     else
349         n = -1;
350
351     *sz = WideCharToMultiByte( st->codepage, 0, str, n, buffer, len, NULL, NULL );
352
353     return ERROR_SUCCESS;
354 }
355
356 /*
357  *  msi_string2idW
358  *
359  *  [in] st         - pointer to the string table
360  *  [in] str        - string to find in the string table
361  *  [out] id        - id of the string, if found
362  */
363 UINT msi_string2idW( string_table *st, LPCWSTR str, UINT *id )
364 {
365     UINT n, hash = msistring_makehash( str );
366     msistring *se = st->strings;
367
368     for (n = st->hash[hash]; n != -1; n = st->strings[n].hash_next )
369     {
370         if ((str == se[n].str) || !lstrcmpW(str, se[n].str))
371         {
372             *id = n;
373             return ERROR_SUCCESS;
374         }
375     }
376
377     return ERROR_INVALID_PARAMETER;
378 }
379
380 UINT msi_string2idA( string_table *st, LPCSTR buffer, UINT *id )
381 {
382     DWORD sz;
383     UINT r = ERROR_INVALID_PARAMETER;
384     LPWSTR str;
385
386     TRACE("Finding string %s in string table\n", debugstr_a(buffer) );
387
388     if( buffer[0] == 0 )
389     {
390         *id = 0;
391         return ERROR_SUCCESS;
392     }
393
394     sz = MultiByteToWideChar( st->codepage, 0, buffer, -1, NULL, 0 );
395     if( sz <= 0 )
396         return r;
397     str = msi_alloc( sz*sizeof(WCHAR) );
398     if( !str )
399         return ERROR_NOT_ENOUGH_MEMORY;
400     MultiByteToWideChar( st->codepage, 0, buffer, -1, str, sz );
401
402     r = msi_string2idW( st, str, id );
403     msi_free( str );
404
405     return r;
406 }
407
408 UINT msi_strcmp( string_table *st, UINT lval, UINT rval, UINT *res )
409 {
410     const WCHAR *l_str, *r_str;
411
412     l_str = msi_string_lookup_id( st, lval );
413     if( !l_str )
414         return ERROR_INVALID_PARAMETER;
415     
416     r_str = msi_string_lookup_id( st, rval );
417     if( !r_str )
418         return ERROR_INVALID_PARAMETER;
419
420     /* does this do the right thing for all UTF-8 strings? */
421     *res = strcmpW( l_str, r_str );
422
423     return ERROR_SUCCESS;
424 }
425
426 UINT msi_string_count( string_table *st )
427 {
428     return st->maxcount;
429 }
430
431 UINT msi_id_refcount( string_table *st, UINT i )
432 {
433     if( i >= st->maxcount )
434         return 0;
435     return st->strings[i].refcount;
436 }
437
438 UINT msi_string_totalsize( string_table *st, UINT *total )
439 {
440     UINT size = 0, i, len;
441
442     if( st->strings[0].str || st->strings[0].refcount )
443         ERR("oops. element 0 has a string\n");
444     *total = 0;
445     for( i=1; i<st->maxcount; i++ )
446     {
447         if( st->strings[i].str )
448         {
449             TRACE("[%u] = %s\n", i, debugstr_w(st->strings[i].str));
450             len = WideCharToMultiByte( st->codepage, 0,
451                      st->strings[i].str, -1, NULL, 0, NULL, NULL);
452             if( len )
453                 len--;
454             size += len;
455             *total = (i+1);
456         }
457     }
458     TRACE("%u/%u strings %u bytes codepage %x\n", *total, st->maxcount, size, st->codepage );
459     return size;
460 }
461
462 UINT msi_string_get_codepage( string_table *st )
463 {
464     return st->codepage;
465 }