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