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