msi: Test MsiRecordGetString on an integer record fields with a NULL output buffer.
[wine] / dlls / msi / format.c
1 /*
2  * Implementation of the Microsoft Installer (msi.dll)
3  *
4  * Copyright 2005 Mike McCormack for CodeWeavers
5  * Copyright 2005 Aric Stewart for CodeWeavers
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20  */
21
22 /*
23 http://msdn.microsoft.com/library/default.asp?url=/library/en-us/msi/setup/msiformatrecord.asp 
24  */
25
26 #include <stdarg.h>
27 #include <stdio.h>
28
29 #define COBJMACROS
30
31 #include "windef.h"
32 #include "winbase.h"
33 #include "winerror.h"
34 #include "wine/debug.h"
35 #include "msi.h"
36 #include "msipriv.h"
37 #include "winnls.h"
38 #include "wine/unicode.h"
39
40 WINE_DEFAULT_DEBUG_CHANNEL(msi);
41
42
43 static DWORD deformat_string_internal(MSIPACKAGE *package, LPCWSTR ptr, 
44                                      WCHAR** data, DWORD len, MSIRECORD* record,
45                                      BOOL* in_group);
46
47
48 static LPWSTR build_default_format(MSIRECORD* record)
49 {
50     int i;  
51     int count;
52     LPWSTR rc, buf;
53     static const WCHAR fmt[] = {'%','i',':',' ','%','s',' ',0};
54     static const WCHAR fmt_null[] = {'%','i',':',' ',' ',0};
55     static const WCHAR fmt_index[] = {'%','i',0};
56     LPCWSTR str;
57     WCHAR index[10];
58     DWORD size, max_len, len;
59
60     count = MSI_RecordGetFieldCount(record);
61
62     max_len = MAX_PATH;
63     buf = msi_alloc((max_len + 1) * sizeof(WCHAR));
64
65     rc = NULL;
66     size = 1;
67     for (i = 1; i <= count; i++)
68     {
69         sprintfW(index,fmt_index,i);
70         str = MSI_RecordGetString(record, i);
71         len = (str) ? lstrlenW(str) : 0;
72         len += (sizeof(fmt_null) - 3) + lstrlenW(index);
73         size += len;
74
75         if (len > max_len)
76         {
77             max_len = len;
78             buf = msi_realloc(buf, (max_len + 1) * sizeof(WCHAR));
79             if (!buf) return NULL;
80         }
81
82         if (str)
83             sprintfW(buf,fmt,i,str);
84         else
85             sprintfW(buf,fmt_null,i);
86
87         if (!rc)
88         {
89             rc = msi_alloc(size * sizeof(WCHAR));
90             lstrcpyW(rc, buf);
91         }
92         else
93         {
94             rc = msi_realloc(rc, size * sizeof(WCHAR));
95             lstrcatW(rc, buf);
96         }
97     }
98     msi_free(buf);
99     return rc;
100 }
101
102 static const WCHAR* scanW(LPCWSTR buf, WCHAR token, DWORD len)
103 {
104     DWORD i;
105     for (i = 0; i < len; i++)
106         if (buf[i] == token)
107             return &buf[i];
108     return NULL;
109 }
110
111 /* break out helper functions for deformating */
112 static LPWSTR deformat_component(MSIPACKAGE* package, LPCWSTR key, DWORD* sz)
113 {
114     LPWSTR value = NULL;
115     MSICOMPONENT *comp;
116
117     *sz = 0;
118     if (!package)
119         return NULL;
120
121     FIXME("component key %s\n", debugstr_w(key));
122     comp = get_loaded_component(package,key);
123     if (comp)
124     {
125         value = resolve_folder(package, comp->Directory, FALSE, FALSE, NULL);
126         *sz = (strlenW(value)) * sizeof(WCHAR);
127     }
128
129     return value;
130 }
131
132 static LPWSTR deformat_file(MSIPACKAGE* package, LPCWSTR key, DWORD* sz, 
133                             BOOL shortname)
134 {
135     LPWSTR value = NULL;
136     MSIFILE *file;
137
138     *sz = 0;
139
140     if (!package)
141         return NULL;
142
143     file = get_loaded_file( package, key );
144     if (file)
145     {
146         if (!shortname)
147         {
148             value = strdupW( file->TargetPath );
149             *sz = (strlenW(value)) * sizeof(WCHAR);
150         }
151         else
152         {
153             DWORD size = 0;
154             size = GetShortPathNameW( file->TargetPath, NULL, 0 );
155
156             if (size > 0)
157             {
158                 *sz = (size-1) * sizeof (WCHAR);
159                 size ++;
160                 value = msi_alloc(size * sizeof(WCHAR));
161                 GetShortPathNameW( file->TargetPath, value, size );
162             }
163             else
164             {
165                 ERR("Unable to get ShortPath size (%s)\n",
166                     debugstr_w( file->TargetPath) );
167                 value = strdupW( file->TargetPath );
168                 *sz = (lstrlenW(value)) * sizeof(WCHAR);
169             }
170         }
171     }
172
173     return value;
174 }
175
176 static LPWSTR deformat_environment(MSIPACKAGE* package, LPCWSTR key, 
177                                    DWORD* chunk)
178 {
179     LPWSTR value = NULL;
180     DWORD sz;
181
182     sz  = GetEnvironmentVariableW(key,NULL,0);
183     if (sz > 0)
184     {
185         sz++;
186         value = msi_alloc(sz * sizeof(WCHAR));
187         GetEnvironmentVariableW(key,value,sz);
188         *chunk = (strlenW(value)) * sizeof(WCHAR);
189     }
190     else
191     {
192         ERR("Unknown environment variable %s\n", debugstr_w(key));
193         *chunk = 0;
194         value = NULL;
195     }
196     return value;
197 }
198
199  
200 static LPWSTR deformat_NULL(DWORD* chunk)
201 {
202     LPWSTR value;
203
204     value = msi_alloc(sizeof(WCHAR)*2);
205     value[0] =  0;
206     *chunk = sizeof(WCHAR);
207     return value;
208 }
209
210 static LPWSTR deformat_escape(LPCWSTR key, DWORD* chunk)
211 {
212     LPWSTR value;
213
214     value = msi_alloc(sizeof(WCHAR)*2);
215     value[0] =  key[0];
216     *chunk = sizeof(WCHAR);
217
218     return value;
219 }
220
221
222 static BOOL is_key_number(LPCWSTR key)
223 {
224     INT index = 0;
225     if (key[0] == 0)
226         return FALSE;
227
228     while (isdigitW(key[index])) index++;
229     if (key[index] == 0)
230         return TRUE;
231     else
232         return FALSE;
233 }
234
235 static LPWSTR deformat_index(MSIRECORD* record, LPCWSTR key, DWORD* chunk )
236 {
237     INT index;
238     LPWSTR value; 
239
240     index = atoiW(key);
241     TRACE("record index %i\n",index);
242     value = msi_dup_record_field(record,index);
243     if (value)
244         *chunk = strlenW(value) * sizeof(WCHAR);
245     else
246     {
247         value = NULL;
248         *chunk = 0;
249     }
250     return value;
251 }
252
253 static LPWSTR deformat_property(MSIPACKAGE* package, LPCWSTR key, DWORD* chunk)
254 {
255     LPWSTR value;
256
257     if (!package)
258         return NULL;
259
260     value = msi_dup_property( package, key );
261
262     if (value)
263         *chunk = (strlenW(value)) * sizeof(WCHAR);
264
265     return value;
266 }
267
268 /*
269  * Groups cannot be nested. They are just treated as from { to next } 
270  */
271 static BOOL find_next_group(LPCWSTR source, DWORD len_remaining,
272                                     LPWSTR *group, LPCWSTR *mark, 
273                                     LPCWSTR* mark2)
274 {
275     int i;
276     BOOL found = FALSE;
277
278     *mark = scanW(source,'{',len_remaining);
279     if (!*mark)
280         return FALSE;
281
282     for (i = 1; (*mark - source) + i < len_remaining; i++)
283     {
284         if ((*mark)[i] == '}')
285         {
286             found = TRUE;
287             break;
288         }
289     }
290     if (! found)
291         return FALSE;
292
293     *mark2 = &(*mark)[i]; 
294
295     i = *mark2 - *mark;
296     *group = msi_alloc(i*sizeof(WCHAR));
297
298     i -= 1;
299     memcpy(*group,&(*mark)[1],i*sizeof(WCHAR));
300     (*group)[i] = 0;
301
302     TRACE("Found group %s\n",debugstr_w(*group));
303     return TRUE;
304 }
305
306
307 static BOOL find_next_outermost_key(LPCWSTR source, DWORD len_remaining,
308                                     LPWSTR *key, LPCWSTR *mark, LPCWSTR* mark2, 
309                                     BOOL *nested)
310 {
311     INT count = 0;
312     INT total_count = 0;
313     int i;
314
315     *mark = scanW(source,'[',len_remaining);
316     if (!*mark)
317         return FALSE;
318
319     count = 1;
320     total_count = 1;
321     *nested = FALSE;
322     for (i = 1; (*mark - source) + i < len_remaining && count > 0; i++)
323     {
324         if ((*mark)[i] == '[' && (*mark)[i-1] != '\\')
325         {
326             count ++;
327             total_count ++;
328             *nested = TRUE;
329         }
330         else if ((*mark)[i] == ']' && (*mark)[i-1] != '\\')
331         {
332             count --;
333         }
334     }
335
336     if (count > 0)
337         return FALSE;
338
339     *mark2 = &(*mark)[i-1]; 
340
341     i = *mark2 - *mark;
342     *key = msi_alloc(i*sizeof(WCHAR));
343     /* do not have the [] in the key */
344     i -= 1;
345     memcpy(*key,&(*mark)[1],i*sizeof(WCHAR));
346     (*key)[i] = 0;
347
348     TRACE("Found Key %s\n",debugstr_w(*key));
349     return TRUE;
350 }
351
352 static LPWSTR deformat_group(MSIPACKAGE* package, LPWSTR group, DWORD len, 
353                       MSIRECORD* record, DWORD* size)
354 {
355     LPWSTR value = NULL;
356     LPCWSTR mark, mark2;
357     LPWSTR key;
358     BOOL nested;
359     INT failcount;
360     static const WCHAR fmt[] = {'{','%','s','}',0};
361     UINT sz;
362
363     if (!group || group[0] == 0) 
364     {
365         *size = 0;
366         return NULL;
367     }
368     /* if no [] then group is returned as is */
369
370      if (!find_next_outermost_key(group, len, &key, &mark, &mark2, &nested))
371      {
372          *size = (len+2)*sizeof(WCHAR);
373          value = msi_alloc(*size);
374          sprintfW(value,fmt,group);
375          /* do not return size of the null at the end */
376          *size = (len+1)*sizeof(WCHAR);
377          return value;
378      }
379
380      msi_free(key);
381      failcount = 0;
382      sz = deformat_string_internal(package, group, &value, strlenW(group),
383                                      record, &failcount);
384      if (failcount==0)
385      {
386         *size = sz * sizeof(WCHAR);
387         return value;
388      }
389      else if (failcount < 0)
390      {
391          LPWSTR v2;
392
393          v2 = msi_alloc((sz+2)*sizeof(WCHAR));
394          v2[0] = '{';
395          memcpy(&v2[1],value,sz*sizeof(WCHAR));
396          v2[sz+1]='}';
397          msi_free(value);
398
399          *size = (sz+2)*sizeof(WCHAR);
400          return v2;
401      }
402      else
403      {
404          msi_free(value);
405          *size = 0;
406          return NULL;
407      }
408 }
409
410
411 /*
412  * len is in WCHARs
413  * return is also in WCHARs
414  */
415 static DWORD deformat_string_internal(MSIPACKAGE *package, LPCWSTR ptr, 
416                                      WCHAR** data, DWORD len, MSIRECORD* record,
417                                      INT* failcount)
418 {
419     LPCWSTR mark = NULL;
420     LPCWSTR mark2 = NULL;
421     DWORD size=0;
422     DWORD chunk=0;
423     LPWSTR key;
424     LPWSTR value = NULL;
425     DWORD sz;
426     LPBYTE newdata = NULL;
427     const WCHAR* progress = NULL;
428     BOOL nested;
429
430     if (ptr==NULL)
431     {
432         TRACE("Deformatting NULL string\n");
433         *data = NULL;
434         return 0;
435     }
436
437     TRACE("Starting with %s\n",debugstr_wn(ptr,len));
438
439     /* scan for special characters... fast exit */
440     if ((!scanW(ptr,'[',len) || (scanW(ptr,'[',len) && !scanW(ptr,']',len))) && 
441         (scanW(ptr,'{',len) && !scanW(ptr,'}',len)))
442     {
443         /* not formatted */
444         *data = msi_alloc((len*sizeof(WCHAR)));
445         memcpy(*data,ptr,len*sizeof(WCHAR));
446         TRACE("Returning %s\n",debugstr_wn(*data,len));
447         return len;
448     }
449   
450     progress = ptr;
451
452     while (progress - ptr < len)
453     {
454         /* seek out first group if existing */
455         if (find_next_group(progress, len - (progress - ptr), &key,
456                                 &mark, &mark2))
457         {
458             value = deformat_group(package, key, strlenW(key)+1, record, 
459                             &chunk);
460             msi_free( key );
461             key = NULL;
462             nested = FALSE;
463         }
464         /* formatted string located */
465         else if (!find_next_outermost_key(progress, len - (progress - ptr), 
466                                 &key, &mark, &mark2, &nested))
467         {
468             LPBYTE nd2;
469
470             TRACE("after value %s\n", debugstr_wn((LPWSTR)newdata,
471                                     size/sizeof(WCHAR)));
472             chunk = (len - (progress - ptr)) * sizeof(WCHAR);
473             TRACE("after chunk is %i + %i\n",size,chunk);
474             if (size)
475                 nd2 = msi_realloc(newdata,(size+chunk));
476             else
477                 nd2 = msi_alloc(chunk);
478
479             newdata = nd2;
480             memcpy(&newdata[size],progress,chunk);
481             size+=chunk;
482             break;
483         }
484
485         if (mark != progress)
486         {
487             LPBYTE tgt;
488             DWORD old_size = size;
489             INT cnt = (mark - progress);
490             TRACE("%i  (%i) characters before marker\n",cnt,(mark-progress));
491             size += cnt * sizeof(WCHAR);
492             if (!old_size)
493                 tgt = msi_alloc(size);
494             else
495                 tgt = msi_realloc(newdata,size);
496             newdata  = tgt;
497             memcpy(&newdata[old_size],progress,(cnt * sizeof(WCHAR)));  
498         }
499
500         progress = mark;
501
502         if (nested)
503         {
504             TRACE("Nested key... %s\n",debugstr_w(key));
505             deformat_string_internal(package, key, &value, strlenW(key)+1,
506                                      record, failcount);
507
508             msi_free(key);
509             key = value;
510         }
511
512         TRACE("Current %s .. %s\n",debugstr_wn((LPWSTR)newdata, 
513                                 size/sizeof(WCHAR)),debugstr_w(key));
514
515         if (!package)
516         {
517             /* only deformat number indexs */
518             if (key && is_key_number(key))
519             {
520                 value = deformat_index(record,key,&chunk);  
521                 if (!chunk && failcount && *failcount >= 0)
522                     (*failcount)++;
523             }
524             else
525             {
526                 if (failcount)
527                     *failcount = -1;
528                 if(key)
529                 {
530                     DWORD keylen = strlenW(key);
531                     chunk = (keylen + 2)*sizeof(WCHAR);
532                     value = msi_alloc(chunk);
533                     value[0] = '[';
534                     memcpy(&value[1],key,keylen*sizeof(WCHAR));
535                     value[1+keylen] = ']';
536                 }
537             }
538         }
539         else
540         {
541             sz = 0;
542             if (key) switch (key[0])
543             {
544                 case '~':
545                     value = deformat_NULL(&chunk);
546                 break;
547                 case '$':
548                     value = deformat_component(package,&key[1],&chunk);
549                 break;
550                 case '#':
551                     value = deformat_file(package,&key[1], &chunk, FALSE);
552                 break;
553                 case '!': /* should be short path */
554                     value = deformat_file(package,&key[1], &chunk, TRUE);
555                 break;
556                 case '\\':
557                     value = deformat_escape(&key[1],&chunk);
558                 break;
559                 case '%':
560                     value = deformat_environment(package,&key[1],&chunk);
561                 break;
562                 default:
563                     /* index keys cannot be nested */
564                     if (is_key_number(key))
565                         if (!nested)
566                             value = deformat_index(record,key,&chunk);
567                         else
568                         {
569                             static const WCHAR fmt[] = {'[','%','s',']',0};
570                             value = msi_alloc(10);
571                             sprintfW(value,fmt,key);
572                             chunk = strlenW(value)*sizeof(WCHAR);
573                         }
574                     else
575                         value = deformat_property(package,key,&chunk);
576                 break;      
577             }
578         }
579
580         msi_free(key);
581
582         if (value!=NULL)
583         {
584             LPBYTE nd2;
585             TRACE("value %s, chunk %i size %i\n",debugstr_w((LPWSTR)value),
586                     chunk, size);
587             if (size)
588                 nd2= msi_realloc(newdata,(size + chunk));
589             else
590                 nd2= msi_alloc(chunk);
591             newdata = nd2;
592             memcpy(&newdata[size],value,chunk);
593             size+=chunk;   
594             msi_free(value);
595         }
596         else if (failcount && *failcount >=0 )
597             (*failcount)++;
598
599         progress = mark2+1;
600     }
601
602     TRACE("after everything %s\n",debugstr_wn((LPWSTR)newdata, 
603                             size/sizeof(WCHAR)));
604
605     *data = (LPWSTR)newdata;
606     return size / sizeof(WCHAR);
607 }
608
609
610 UINT MSI_FormatRecordW( MSIPACKAGE* package, MSIRECORD* record, LPWSTR buffer,
611                         DWORD *size )
612 {
613     LPWSTR deformated;
614     LPWSTR rec;
615     DWORD len;
616     UINT rc = ERROR_INVALID_PARAMETER;
617
618     TRACE("%p %p %p %i\n", package, record ,buffer, *size);
619
620     rec = msi_dup_record_field(record,0);
621     if (!rec)
622         rec = build_default_format(record);
623
624     TRACE("(%s)\n",debugstr_w(rec));
625
626     len = deformat_string_internal(package,rec,&deformated,strlenW(rec),
627                                    record, NULL);
628
629     if (buffer)
630     {
631         if (*size>len)
632         {
633             memcpy(buffer,deformated,len*sizeof(WCHAR));
634             rc = ERROR_SUCCESS;
635             buffer[len] = 0;
636         }
637         else
638         {
639             if (*size > 0)
640             {
641                 memcpy(buffer,deformated,(*size)*sizeof(WCHAR));
642                 buffer[(*size)-1] = 0;
643             }
644             rc = ERROR_MORE_DATA;
645         }
646     }
647     else
648         rc = ERROR_SUCCESS;
649
650     *size = len;
651
652     msi_free(rec);
653     msi_free(deformated);
654     return rc;
655 }
656
657 UINT MSI_FormatRecordA( MSIPACKAGE* package, MSIRECORD* record, LPSTR buffer,
658                         DWORD *size )
659 {
660     LPWSTR deformated;
661     LPWSTR rec;
662     DWORD len,lenA;
663     UINT rc = ERROR_INVALID_PARAMETER;
664
665     TRACE("%p %p %p %i\n", package, record ,buffer, *size);
666
667     rec = msi_dup_record_field(record,0);
668     if (!rec)
669         rec = build_default_format(record);
670
671     TRACE("(%s)\n",debugstr_w(rec));
672
673     len = deformat_string_internal(package,rec,&deformated,strlenW(rec),
674                                    record, NULL);
675     /* If len is zero then WideCharToMultiByte will return 0 indicating 
676      * failure, but that will do just as well since we are ignoring
677      * possible errors.
678      */
679     lenA = WideCharToMultiByte(CP_ACP,0,deformated,len,NULL,0,NULL,NULL);
680
681     if (buffer)
682     {
683         /* Ditto above */
684         WideCharToMultiByte(CP_ACP,0,deformated,len,buffer,*size,NULL, NULL);
685         if (*size>lenA)
686         {
687             rc = ERROR_SUCCESS;
688             buffer[lenA] = 0;
689         }
690         else
691         {
692             rc = ERROR_MORE_DATA;
693             if (*size)
694                 buffer[(*size)-1] = 0;
695         }
696     }
697     else
698         rc = ERROR_SUCCESS;
699
700     *size = lenA;
701
702     msi_free(rec);
703     msi_free(deformated);
704     return rc;
705 }
706
707
708 UINT WINAPI MsiFormatRecordW( MSIHANDLE hInstall, MSIHANDLE hRecord, 
709                               LPWSTR szResult, DWORD *sz )
710 {
711     UINT r = ERROR_INVALID_HANDLE;
712     MSIPACKAGE *package;
713     MSIRECORD *record;
714
715     TRACE("%ld %ld %p %p\n", hInstall, hRecord, szResult, sz);
716
717     record = msihandle2msiinfo( hRecord, MSIHANDLETYPE_RECORD );
718
719     if (!record)
720         return ERROR_INVALID_HANDLE;
721     if (!sz)
722     {
723         msiobj_release( &record->hdr );
724         if (szResult)
725             return ERROR_INVALID_PARAMETER;
726         else
727             return ERROR_SUCCESS;
728     }
729
730     package = msihandle2msiinfo( hInstall, MSIHANDLETYPE_PACKAGE );
731
732     r = MSI_FormatRecordW( package, record, szResult, sz );
733     msiobj_release( &record->hdr );
734     if (package)
735         msiobj_release( &package->hdr );
736     return r;
737 }
738
739 UINT WINAPI MsiFormatRecordA( MSIHANDLE hInstall, MSIHANDLE hRecord,
740                               LPSTR szResult, DWORD *sz )
741 {
742     UINT r = ERROR_INVALID_HANDLE;
743     MSIPACKAGE *package = NULL;
744     MSIRECORD *record = NULL;
745
746     TRACE("%ld %ld %p %p\n", hInstall, hRecord, szResult, sz);
747
748     record = msihandle2msiinfo( hRecord, MSIHANDLETYPE_RECORD );
749
750     if (!record)
751         return ERROR_INVALID_HANDLE;
752     if (!sz)
753     {
754         msiobj_release( &record->hdr );
755         if (szResult)
756             return ERROR_INVALID_PARAMETER;
757         else
758             return ERROR_SUCCESS;
759     }
760
761     package = msihandle2msiinfo( hInstall, MSIHANDLETYPE_PACKAGE );
762
763     r = MSI_FormatRecordA( package, record, szResult, sz );
764     msiobj_release( &record->hdr );
765     if (package)
766         msiobj_release( &package->hdr );
767     return r;
768 }