- Added OnAmbientPropertyChange implementation.
[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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  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 #include "action.h"
40
41 WINE_DEFAULT_DEBUG_CHANNEL(msi);
42
43
44 static DWORD deformat_string_internal(MSIPACKAGE *package, LPCWSTR ptr, 
45                                      WCHAR** data, DWORD len, MSIRECORD* record,
46                                      BOOL* in_group);
47
48
49 static LPWSTR build_default_format(MSIRECORD* record)
50 {
51     int i;  
52     int count;
53     LPWSTR rc;
54     static const WCHAR fmt[] = {'%','i',':',' ','[','%','i',']',' ',0};
55     WCHAR buf[11];
56
57     count = MSI_RecordGetFieldCount(record);
58
59     rc = HeapAlloc(GetProcessHeap(),0,(11*count)*sizeof(WCHAR));
60     rc[0] = 0;
61     for (i = 1; i <= count; i++)
62     {
63         sprintfW(buf,fmt,i,i); 
64         strcatW(rc,buf);
65     }
66     return rc;
67 }
68
69 static const WCHAR* scanW(LPCWSTR buf, WCHAR token, DWORD len)
70 {
71     DWORD i;
72     for (i = 0; i < len; i++)
73         if (buf[i] == token)
74             return &buf[i];
75     return NULL;
76 }
77
78 /* break out helper functions for deformating */
79 static LPWSTR deformat_component(MSIPACKAGE* package, LPCWSTR key, DWORD* sz)
80 {
81     LPWSTR value = NULL;
82     MSICOMPONENT *comp;
83
84     *sz = 0;
85     if (!package)
86         return NULL;
87
88     ERR("POORLY HANDLED DEFORMAT.. [$componentkey] \n");
89     comp = get_loaded_component(package,key);
90     if (comp)
91     {
92         value = resolve_folder(package, comp->Directory, FALSE, FALSE, NULL);
93         *sz = (strlenW(value)) * sizeof(WCHAR);
94     }
95
96     return value;
97 }
98
99 static LPWSTR deformat_file(MSIPACKAGE* package, LPCWSTR key, DWORD* sz, 
100                             BOOL shortname)
101 {
102     LPWSTR value = NULL;
103     INT index;
104
105     *sz = 0;
106
107     if (!package)
108         return NULL;
109
110     index = get_loaded_file(package,key);
111     if (index >=0)
112     {
113         if (!shortname)
114         {
115             value = strdupW(package->files[index].TargetPath);
116             *sz = (strlenW(value)) * sizeof(WCHAR);
117         }
118         else
119         {
120             DWORD size = 0;
121             size = GetShortPathNameW(package->files[index].TargetPath, NULL, 0);
122
123             if (size > 0)
124             {
125                 *sz = (size-1) * sizeof (WCHAR);
126                 size ++;
127                 value = HeapAlloc(GetProcessHeap(),0,size * sizeof(WCHAR));
128                 GetShortPathNameW(package->files[index].TargetPath, value, 
129                                   size);
130             }
131             else
132             {
133                 ERR("Unable to get ShortPath size (%s)\n", 
134                                 debugstr_w(package->files[index].TargetPath));
135                 value = NULL;
136                 *sz = 0;
137             }
138         }
139     }
140
141     return value;
142 }
143
144 static LPWSTR deformat_environment(MSIPACKAGE* package, LPCWSTR key, 
145                                    DWORD* chunk)
146 {
147     LPWSTR value = NULL;
148     DWORD sz;
149
150     sz  = GetEnvironmentVariableW(key,NULL,0);
151     if (sz > 0)
152     {
153         sz++;
154         value = HeapAlloc(GetProcessHeap(),0,sz * sizeof(WCHAR));
155         GetEnvironmentVariableW(&key[1],value,sz);
156         *chunk = (strlenW(value)) * sizeof(WCHAR);
157     }
158     else
159     {
160         ERR("Unknown environment variable %s\n", debugstr_w(key));
161         *chunk = 0;
162         value = NULL;
163     }
164     return value;
165 }
166
167  
168 static LPWSTR deformat_NULL(DWORD* chunk)
169 {
170     LPWSTR value;
171
172     value = HeapAlloc(GetProcessHeap(),0,sizeof(WCHAR)*2);
173     value[0] =  0;
174     *chunk = sizeof(WCHAR);
175     return value;
176 }
177
178 static LPWSTR deformat_escape(LPCWSTR key, DWORD* chunk)
179 {
180     LPWSTR value;
181
182     value = HeapAlloc(GetProcessHeap(),0,sizeof(WCHAR)*2);
183     value[0] =  key[0];
184     *chunk = sizeof(WCHAR);
185
186     return value;
187 }
188
189
190 static BOOL is_key_number(LPCWSTR key)
191 {
192     INT index = 0;
193     if (key[0] == 0)
194         return FALSE;
195
196     while (isdigitW(key[index])) index++;
197     if (key[index] == 0)
198         return TRUE;
199     else
200         return FALSE;
201 }
202
203 static LPWSTR deformat_index(MSIRECORD* record, LPCWSTR key, DWORD* chunk )
204 {
205     INT index;
206     LPWSTR value; 
207
208     index = atoiW(key);
209     TRACE("record index %i\n",index);
210     value = load_dynamic_stringW(record,index);
211     if (value)
212         *chunk = strlenW(value) * sizeof(WCHAR);
213     else
214     {
215         value = NULL;
216         *chunk = 0;
217     }
218     return value;
219 }
220
221 static LPWSTR deformat_property(MSIPACKAGE* package, LPCWSTR key, DWORD* chunk)
222 {
223     UINT rc;
224     LPWSTR value;
225
226     if (!package)
227         return NULL;
228
229     value = load_dynamic_property(package,key, &rc);
230
231     if (rc == ERROR_SUCCESS)
232         *chunk = (strlenW(value)) * sizeof(WCHAR);
233
234     return value;
235 }
236
237 /*
238  * Groups cannot be nested. They are just treated as from { to next } 
239  */
240 static BOOL find_next_group(LPCWSTR source, DWORD len_remaining,
241                                     LPWSTR *group, LPCWSTR *mark, 
242                                     LPCWSTR* mark2)
243 {
244     int i;
245     BOOL found = FALSE;
246
247     *mark = scanW(source,'{',len_remaining);
248     if (!*mark)
249         return FALSE;
250
251     for (i = 1; (*mark - source) + i < len_remaining; i++)
252     {
253         if ((*mark)[i] == '}')
254         {
255             found = TRUE;
256             break;
257         }
258     }
259     if (! found)
260         return FALSE;
261
262     *mark2 = &(*mark)[i]; 
263
264     i = *mark2 - *mark;
265     *group = HeapAlloc(GetProcessHeap(),0,i*sizeof(WCHAR));
266
267     i -= 1;
268     memcpy(*group,&(*mark)[1],i*sizeof(WCHAR));
269     (*group)[i] = 0;
270
271     TRACE("Found group %s\n",debugstr_w(*group));
272     return TRUE;
273 }
274
275
276 static BOOL find_next_outermost_key(LPCWSTR source, DWORD len_remaining,
277                                     LPWSTR *key, LPCWSTR *mark, LPCWSTR* mark2, 
278                                     BOOL *nested)
279 {
280     INT count = 0;
281     INT total_count = 0;
282     int i;
283
284     *mark = scanW(source,'[',len_remaining);
285     if (!*mark)
286         return FALSE;
287
288     count = 1;
289     total_count = 1;
290     *nested = FALSE;
291     for (i = 1; (*mark - source) + i < len_remaining && count > 0; i++)
292     {
293         if ((*mark)[i] == '[' && (*mark)[i-1] != '\\')
294         {
295             count ++;
296             total_count ++;
297             *nested = TRUE;
298         }
299         else if ((*mark)[i] == ']' && (*mark)[i-1] != '\\')
300         {
301             count --;
302         }
303     }
304
305     if (count > 0)
306         return FALSE;
307
308     *mark2 = &(*mark)[i-1]; 
309
310     i = *mark2 - *mark;
311     *key = HeapAlloc(GetProcessHeap(),0,i*sizeof(WCHAR));
312     /* do not have the [] in the key */
313     i -= 1;
314     memcpy(*key,&(*mark)[1],i*sizeof(WCHAR));
315     (*key)[i] = 0;
316
317     TRACE("Found Key %s\n",debugstr_w(*key));
318     return TRUE;
319 }
320
321 static LPWSTR deformat_group(MSIPACKAGE* package, LPWSTR group, DWORD len, 
322                       MSIRECORD* record, DWORD* size)
323 {
324     LPWSTR value;
325     LPCWSTR mark, mark2;
326     LPWSTR key;
327     BOOL nested;
328     INT failcount;
329     static const WCHAR fmt[] = {'{','%','s','}',0};
330     UINT sz;
331
332     if (!group || group[0] == 0) 
333     {
334         *size = 0;
335         return NULL;
336     }
337     /* if no [] then group is returned as is */
338
339      if (!find_next_outermost_key(group, len, &key, &mark, &mark2, &nested))
340      {
341          *size = (len+2)*sizeof(WCHAR);
342          value = HeapAlloc(GetProcessHeap(),0,*size);
343          sprintfW(value,fmt,group);
344          /* do not return size of the null at the end */
345          *size = (len+1)*sizeof(WCHAR);
346          return value;
347      }
348
349      HeapFree(GetProcessHeap(),0,key);
350      failcount = 0;
351      sz = deformat_string_internal(package, group, &value, strlenW(group),
352                                      record, &failcount);
353      if (failcount==0)
354      {
355         *size = sz * sizeof(WCHAR);
356         return value;
357      }
358      else if (failcount < 0)
359      {
360          LPWSTR v2;
361
362          v2 = HeapAlloc(GetProcessHeap(),0,(sz+2)*sizeof(WCHAR));
363          v2[0] = '{';
364          memcpy(&v2[1],value,sz*sizeof(WCHAR));
365          v2[sz+1]='}';
366          HeapFree(GetProcessHeap(),0,value);
367
368          *size = (sz+2)*sizeof(WCHAR);
369          return v2;
370      }
371      else
372      {
373          *size = 0;
374          return NULL;
375      }
376 }
377
378
379 /*
380  * len is in WCHARs
381  * return is also in WCHARs
382  */
383 static DWORD deformat_string_internal(MSIPACKAGE *package, LPCWSTR ptr, 
384                                      WCHAR** data, DWORD len, MSIRECORD* record,
385                                      INT* failcount)
386 {
387     LPCWSTR mark = NULL;
388     LPCWSTR mark2 = NULL;
389     DWORD size=0;
390     DWORD chunk=0;
391     LPWSTR key;
392     LPWSTR value = NULL;
393     DWORD sz;
394     LPBYTE newdata = NULL;
395     const WCHAR* progress = NULL;
396     BOOL nested;
397
398     if (ptr==NULL)
399     {
400         TRACE("Deformatting NULL string\n");
401         *data = NULL;
402         return 0;
403     }
404
405     TRACE("Starting with %s\n",debugstr_wn(ptr,len));
406
407     /* scan for special characters... fast exit */
408     if ((!scanW(ptr,'[',len) || (scanW(ptr,'[',len) && !scanW(ptr,']',len))) && 
409         (scanW(ptr,'{',len) && !scanW(ptr,'}',len)))
410     {
411         /* not formatted */
412         *data = HeapAlloc(GetProcessHeap(),0,(len*sizeof(WCHAR)));
413         memcpy(*data,ptr,len*sizeof(WCHAR));
414         TRACE("Returning %s\n",debugstr_wn(*data,len));
415         return len;
416     }
417   
418     progress = ptr;
419
420     while (progress - ptr < len)
421     {
422         /* seek out first group if existing */
423         if (find_next_group(progress, len - (progress - ptr), &key,
424                                 &mark, &mark2))
425         {
426             value = deformat_group(package, key, strlenW(key)+1, record, 
427                             &chunk);
428             key = NULL;
429             nested = FALSE;
430         }
431         /* formatted string located */
432         else if (!find_next_outermost_key(progress, len - (progress - ptr), 
433                                 &key, &mark, &mark2, &nested))
434         {
435             LPBYTE nd2;
436
437             TRACE("after value %s \n",debugstr_wn((LPWSTR)newdata,
438                                     size/sizeof(WCHAR)));
439             chunk = (len - (progress - ptr)) * sizeof(WCHAR);
440             TRACE("after chunk is %li + %li\n",size,chunk);
441             if (size)
442                 nd2 = HeapReAlloc(GetProcessHeap(),0,newdata,(size+chunk));
443             else
444                 nd2 = HeapAlloc(GetProcessHeap(),0,chunk);
445
446             newdata = nd2;
447             memcpy(&newdata[size],progress,chunk);
448             size+=chunk;
449             break;
450         }
451
452         if (mark != progress)
453         {
454             LPBYTE tgt;
455             DWORD old_size = size;
456             INT cnt = (mark - progress);
457             TRACE("%i  (%i) characters before marker\n",cnt,(mark-progress));
458             size += cnt * sizeof(WCHAR);
459             if (!old_size)
460                 tgt = HeapAlloc(GetProcessHeap(),0,size);
461             else
462                 tgt = HeapReAlloc(GetProcessHeap(),0,newdata,size);
463             newdata  = tgt;
464             memcpy(&newdata[old_size],progress,(cnt * sizeof(WCHAR)));  
465         }
466
467         progress = mark;
468
469         if (nested)
470         {
471             TRACE("Nested key... %s\n",debugstr_w(key));
472             deformat_string_internal(package, key, &value, strlenW(key)+1,
473                                      record, failcount);
474
475             HeapFree(GetProcessHeap(),0,key);
476             key = value;
477         }
478
479         TRACE("Current %s .. %s\n",debugstr_wn((LPWSTR)newdata, 
480                                 size/sizeof(WCHAR)),debugstr_w(key));
481
482         if (!package)
483         {
484             /* only deformat number indexs */
485             if (key && is_key_number(key))
486             {
487                 value = deformat_index(record,key,&chunk);  
488                 if (!chunk && failcount && *failcount >= 0)
489                     (*failcount)++;
490             }
491             else
492             {
493                 if (failcount)
494                     *failcount = -1;
495                 if(key)
496                 {
497                     DWORD keylen = strlenW(key);
498                     chunk = (keylen + 2)*sizeof(WCHAR);
499                     value = HeapAlloc(GetProcessHeap(),0,chunk);
500                     value[0] = '[';
501                     memcpy(&value[1],key,keylen*sizeof(WCHAR));
502                     value[1+keylen] = ']';
503                 }
504             }
505         }
506         else
507         {
508             sz = 0;
509             if (key) switch (key[0])
510             {
511                 case '~':
512                     value = deformat_NULL(&chunk);
513                 break;
514                 case '$':
515                     value = deformat_component(package,&key[1],&chunk);
516                 break;
517                 case '#':
518                     value = deformat_file(package,&key[1], &chunk, FALSE);
519                 break;
520                 case '!': /* should be short path */
521                     value = deformat_file(package,&key[1], &chunk, TRUE);
522                 break;
523                 case '\\':
524                     value = deformat_escape(&key[1],&chunk);
525                 break;
526                 case '%':
527                     value = deformat_environment(package,&key[1],&chunk);
528                 break;
529                 default:
530                     /* index keys cannot be nested */
531                     if (is_key_number(key))
532                         if (!nested)
533                             value = deformat_index(record,key,&chunk);
534                         else
535                         {
536                             static const WCHAR fmt[] = {'[','%','s',']',0};
537                             value = HeapAlloc(GetProcessHeap(),0,10);
538                             sprintfW(value,fmt,key);
539                             chunk = strlenW(value)*sizeof(WCHAR);
540                         }
541                     else
542                         value = deformat_property(package,key,&chunk);
543                 break;      
544             }
545         }
546
547         HeapFree(GetProcessHeap(),0,key);
548
549         if (value!=NULL)
550         {
551             LPBYTE nd2;
552             TRACE("value %s, chunk %li size %li\n",debugstr_w((LPWSTR)value),
553                     chunk, size);
554             if (size)
555                 nd2= HeapReAlloc(GetProcessHeap(),0,newdata,(size + chunk));
556             else
557                 nd2= HeapAlloc(GetProcessHeap(),0,chunk);
558             newdata = nd2;
559             memcpy(&newdata[size],value,chunk);
560             size+=chunk;   
561             HeapFree(GetProcessHeap(),0,value);
562         }
563         else if (failcount && *failcount >=0 )
564             (*failcount)++;
565
566         progress = mark2+1;
567     }
568
569     TRACE("after everything %s\n",debugstr_wn((LPWSTR)newdata, 
570                             size/sizeof(WCHAR)));
571
572     *data = (LPWSTR)newdata;
573     return size / sizeof(WCHAR);
574 }
575
576
577 UINT MSI_FormatRecordW( MSIPACKAGE* package, MSIRECORD* record, LPWSTR buffer,
578                         DWORD *size )
579 {
580     LPWSTR deformated;
581     LPWSTR rec;
582     DWORD len;
583     UINT rc = ERROR_INVALID_PARAMETER;
584
585     TRACE("%p %p %p %li\n",package, record ,buffer, *size);
586
587     rec = load_dynamic_stringW(record,0);
588     if (!rec)
589         rec = build_default_format(record);
590
591     TRACE("(%s)\n",debugstr_w(rec));
592
593     len = deformat_string_internal(package,rec,&deformated,strlenW(rec),
594                                    record, NULL);
595
596     if (buffer)
597     {
598         if (*size>len)
599         {
600             memcpy(buffer,deformated,len*sizeof(WCHAR));
601             rc = ERROR_SUCCESS;
602             buffer[len] = 0;
603         }
604         else
605         {
606             if (*size > 0)
607             {
608                 memcpy(buffer,deformated,(*size)*sizeof(WCHAR));
609                 buffer[(*size)-1] = 0;
610             }
611             rc = ERROR_MORE_DATA;
612         }
613     }
614     else
615         rc = ERROR_SUCCESS;
616
617     *size = len;
618
619     HeapFree(GetProcessHeap(),0,rec);
620     HeapFree(GetProcessHeap(),0,deformated);
621     return rc;
622 }
623
624 UINT MSI_FormatRecordA( MSIPACKAGE* package, MSIRECORD* record, LPSTR buffer,
625                         DWORD *size )
626 {
627     LPWSTR deformated;
628     LPWSTR rec;
629     DWORD len,lenA;
630     UINT rc = ERROR_INVALID_PARAMETER;
631
632     TRACE("%p %p %p %li\n",package, record ,buffer, *size);
633
634     rec = load_dynamic_stringW(record,0);
635     if (!rec)
636         rec = build_default_format(record);
637
638     TRACE("(%s)\n",debugstr_w(rec));
639
640     len = deformat_string_internal(package,rec,&deformated,strlenW(rec),
641                                    record, NULL);
642     lenA = WideCharToMultiByte(CP_ACP,0,deformated,len,NULL,0,NULL,NULL);
643
644     if (buffer)
645     {
646         WideCharToMultiByte(CP_ACP,0,deformated,len,buffer,*size,NULL, NULL);
647         if (*size>lenA)
648         {
649             rc = ERROR_SUCCESS;
650             buffer[lenA] = 0;
651         }
652         else
653         {
654             rc = ERROR_MORE_DATA;
655             buffer[(*size)-1] = 0;    
656         }
657     }
658     else
659         rc = ERROR_SUCCESS;
660
661     *size = lenA;
662
663     HeapFree(GetProcessHeap(),0,rec);
664     HeapFree(GetProcessHeap(),0,deformated);
665     return rc;
666 }
667
668
669 UINT WINAPI MsiFormatRecordW( MSIHANDLE hInstall, MSIHANDLE hRecord, 
670                               LPWSTR szResult, DWORD *sz )
671 {
672     UINT r = ERROR_INVALID_HANDLE;
673     MSIPACKAGE *package;
674     MSIRECORD *record;
675
676     TRACE("%ld %ld %p %p\n", hInstall, hRecord, szResult, sz);
677
678     record = msihandle2msiinfo( hRecord, MSIHANDLETYPE_RECORD );
679
680     if (!record)
681         return ERROR_INVALID_HANDLE;
682     if (!sz)
683     {
684         msiobj_release( &record->hdr );
685         if (szResult)
686             return ERROR_INVALID_PARAMETER;
687         else
688             return ERROR_SUCCESS;
689     }
690
691     package = msihandle2msiinfo( hInstall, MSIHANDLETYPE_PACKAGE );
692
693     r = MSI_FormatRecordW( package, record, szResult, sz );
694     msiobj_release( &record->hdr );
695     if (package)
696         msiobj_release( &package->hdr );
697     return r;
698 }
699
700 UINT WINAPI MsiFormatRecordA( MSIHANDLE hInstall, MSIHANDLE hRecord,
701                               LPSTR szResult, DWORD *sz )
702 {
703     UINT r = ERROR_INVALID_HANDLE;
704     MSIPACKAGE *package = NULL;
705     MSIRECORD *record = NULL;
706
707     TRACE("%ld %ld %p %p\n", hInstall, hRecord, szResult, sz);
708
709     record = msihandle2msiinfo( hRecord, MSIHANDLETYPE_RECORD );
710
711     if (!record)
712         return ERROR_INVALID_HANDLE;
713     if (!sz)
714     {
715         msiobj_release( &record->hdr );
716         if (szResult)
717             return ERROR_INVALID_PARAMETER;
718         else
719             return ERROR_SUCCESS;
720     }
721
722     package = msihandle2msiinfo( hInstall, MSIHANDLETYPE_PACKAGE );
723
724     r = MSI_FormatRecordA( package, record, szResult, sz );
725     msiobj_release( &record->hdr );
726     if (package)
727         msiobj_release( &package->hdr );
728     return r;
729 }