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