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