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