- Fix scaling when converting MF -> EMF.
[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 LPWSTR build_default_format(MSIRECORD* record)
54 {
55     int i;  
56     int count;
57     LPWSTR rc;
58     static const WCHAR fmt[] = {'%','i',':',' ','[','%','i',']',' ',0};
59     WCHAR buf[11];
60
61     count = MSI_RecordGetFieldCount(record);
62
63     rc = HeapAlloc(GetProcessHeap(),0,(11*count)*sizeof(WCHAR));
64     rc[0] = 0;
65     for (i = 1; i <= count; i++)
66     {
67         sprintfW(buf,fmt,i,i); 
68         strcatW(rc,buf);
69     }
70     return rc;
71 }
72
73 static const WCHAR* scanW(LPCWSTR buf, WCHAR token, DWORD len)
74 {
75     DWORD i;
76     for (i = 0; i < len; i++)
77         if (buf[i] == token)
78             return &buf[i];
79     return NULL;
80 }
81
82 /* break out helper functions for deformating */
83 static LPWSTR deformat_component(MSIPACKAGE* package, LPCWSTR key, DWORD* sz)
84 {
85     LPWSTR value = NULL;
86     INT index;
87
88     *sz = 0;
89     if (!package)
90         return NULL;
91
92     ERR("POORLY HANDLED DEFORMAT.. [$componentkey] \n");
93     index = get_loaded_component(package,key);
94     if (index >= 0)
95     {
96         value = resolve_folder(package, package->components[index].Directory, 
97                                        FALSE, FALSE, NULL);
98         *sz = (strlenW(value)) * sizeof(WCHAR);
99     }
100
101     return value;
102 }
103
104 static LPWSTR deformat_file(MSIPACKAGE* package, LPCWSTR key, DWORD* sz)
105 {
106     LPWSTR value = NULL;
107     INT index;
108
109     *sz = 0;
110
111     if (!package)
112         return NULL;
113
114     index = get_loaded_file(package,key);
115     if (index >=0)
116     {
117         value = strdupW(package->files[index].TargetPath);
118         *sz = (strlenW(value)) * sizeof(WCHAR);
119     }
120
121     return value;
122 }
123
124 static LPWSTR deformat_environment(MSIPACKAGE* package, LPCWSTR key, 
125                                    DWORD* chunk)
126 {
127     LPWSTR value = NULL;
128     DWORD sz;
129
130     sz  = GetEnvironmentVariableW(key,NULL,0);
131     if (sz > 0)
132     {
133         sz++;
134         value = HeapAlloc(GetProcessHeap(),0,sz * sizeof(WCHAR));
135         GetEnvironmentVariableW(&key[1],value,sz);
136         *chunk = (strlenW(value)) * sizeof(WCHAR);
137     }
138     else
139     {
140         ERR("Unknown environment variable %s\n", debugstr_w(key));
141         *chunk = 0;
142         value = NULL;
143     }
144     return value;
145 }
146
147  
148 static LPWSTR deformat_NULL(DWORD* chunk)
149 {
150     LPWSTR value;
151
152     value = HeapAlloc(GetProcessHeap(),0,sizeof(WCHAR)*2);
153     value[0] =  0;
154     *chunk = sizeof(WCHAR);
155     return value;
156 }
157
158 static LPWSTR deformat_escape(LPCWSTR key, DWORD* chunk)
159 {
160     LPWSTR value;
161
162     value = HeapAlloc(GetProcessHeap(),0,sizeof(WCHAR)*2);
163     value[0] =  key[0];
164     *chunk = sizeof(WCHAR);
165
166     return value;
167 }
168
169
170 static BOOL is_key_number(LPCWSTR key)
171 {
172     INT index = 0;
173     if (key[0] == 0)
174         return FALSE;
175
176     while (isdigitW(key[index])) index++;
177     if (key[index] == 0)
178         return TRUE;
179     else
180         return FALSE;
181 }
182
183 static LPWSTR deformat_index(MSIRECORD* record, LPCWSTR key, DWORD* chunk )
184 {
185     INT index;
186     LPWSTR value; 
187
188     index = atoiW(key);
189     TRACE("record index %i\n",index);
190     value = load_dynamic_stringW(record,index);
191     if (value)
192         *chunk = strlenW(value) * sizeof(WCHAR);
193     else
194     {
195         value = NULL;
196         *chunk = 0;
197     }
198     return value;
199 }
200
201 static LPWSTR deformat_property(MSIPACKAGE* package, LPCWSTR key, DWORD* chunk)
202 {
203     UINT rc;
204     LPWSTR value;
205
206     if (!package)
207         return NULL;
208
209     value = load_dynamic_property(package,key, &rc);
210
211     if (rc == ERROR_SUCCESS)
212         *chunk = (strlenW(value)) * sizeof(WCHAR);
213
214     return value;
215 }
216
217 static BOOL find_next_outermost_key(LPCWSTR source, DWORD len_remaining,
218                                     LPWSTR *key, LPCWSTR *mark, LPCWSTR* mark2, 
219                                     BOOL *nested)
220 {
221     INT count = 0;
222     INT total_count = 0;
223     int i;
224
225     *mark = scanW(source,'[',len_remaining);
226     if (!*mark)
227         return FALSE;
228
229     count = 1;
230     total_count = 1;
231     *nested = FALSE;
232     for (i = 1; (*mark - source) + i < len_remaining && count > 0; i++)
233     {
234         if ((*mark)[i] == '[' && (*mark)[i-1] != '\\')
235         {
236             count ++;
237             total_count ++;
238             *nested = TRUE;
239         }
240         else if ((*mark)[i] == ']' && (*mark)[i-1] != '\\')
241         {
242             count --;
243         }
244     }
245
246     if (count > 0)
247         return FALSE;
248
249     *mark2 = &(*mark)[i-1]; 
250
251     i = *mark2 - *mark;
252     *key = HeapAlloc(GetProcessHeap(),0,i*sizeof(WCHAR));
253     /* do not have the [] in the key */
254     i -= 1;
255     memcpy(*key,&(*mark)[1],i*sizeof(WCHAR));
256     (*key)[i] = 0;
257
258     TRACE("Found Key %s\n",debugstr_w(*key));
259     return TRUE;
260 }
261
262
263 /*
264  * len is in WCHARs
265  * return is also in WCHARs
266  */
267 static DWORD deformat_string_internal(MSIPACKAGE *package, LPCWSTR ptr, 
268                                      WCHAR** data, DWORD len, MSIRECORD* record)
269 {
270     LPCWSTR mark = NULL;
271     LPCWSTR mark2 = NULL;
272     DWORD size=0;
273     DWORD chunk=0;
274     LPWSTR key;
275     LPWSTR value = NULL;
276     DWORD sz;
277     LPBYTE newdata = NULL;
278     const WCHAR* progress = NULL;
279     BOOL nested;
280
281     if (ptr==NULL)
282     {
283         TRACE("Deformatting NULL string\n");
284         *data = NULL;
285         return 0;
286     }
287
288     TRACE("Starting with %s\n",debugstr_w(ptr));
289
290     /* scan for special characters... fast exit */
291     if (!scanW(ptr,'[',len) || (scanW(ptr,'[',len) && !scanW(ptr,']',len)))
292     {
293         /* not formatted */
294         *data = HeapAlloc(GetProcessHeap(),0,(len*sizeof(WCHAR)));
295         memcpy(*data,ptr,len*sizeof(WCHAR));
296         TRACE("Returning %s\n",debugstr_w(*data));
297         return len;
298     }
299   
300     progress = ptr;
301
302     while (progress - ptr < len)
303     {
304         /* formatted string located */
305         if (!find_next_outermost_key(progress, len - (progress - ptr), &key,
306                                      &mark, &mark2, &nested))
307         {
308             LPBYTE nd2;
309
310             TRACE("after value %s .. %s\n",debugstr_w((LPWSTR)newdata),
311                                        debugstr_w(mark));
312             chunk = (len - (progress - ptr)) * sizeof(WCHAR);
313             TRACE("after chunk is %li + %li\n",size,chunk);
314             if (size)
315                 nd2 = HeapReAlloc(GetProcessHeap(),0,newdata,(size+chunk));
316             else
317                 nd2 = HeapAlloc(GetProcessHeap(),0,chunk);
318
319             newdata = nd2;
320             memcpy(&newdata[size],progress,chunk);
321             size+=chunk;
322             break;
323         }
324
325         if (mark != progress)
326         {
327             LPBYTE tgt;
328             DWORD old_size = size;
329             INT cnt = (mark - progress);
330             TRACE("%i  (%i) characters before marker\n",cnt,(mark-progress));
331             size += cnt * sizeof(WCHAR);
332             if (!old_size)
333                 tgt = HeapAlloc(GetProcessHeap(),0,size);
334             else
335                 tgt = HeapReAlloc(GetProcessHeap(),0,newdata,size);
336             newdata  = tgt;
337             memcpy(&newdata[old_size],progress,(cnt * sizeof(WCHAR)));  
338         }
339
340         progress = mark;
341
342         if (nested)
343         {
344             TRACE("Nested key... %s\n",debugstr_w(key));
345             deformat_string_internal(package, key, &value, strlenW(key)+1,
346                                      record);
347
348             HeapFree(GetProcessHeap(),0,key);
349             key = value;
350         }
351
352         TRACE("Current %s .. %s\n",debugstr_w((LPWSTR)newdata),debugstr_w(key));
353
354         if (!package)
355         {
356             /* only deformat number indexs */
357             if (is_key_number(key))
358                 value = deformat_index(record,key,&chunk);  
359             else
360             {
361                 chunk = (strlenW(key) + 2)*sizeof(WCHAR);
362                 value = HeapAlloc(GetProcessHeap(),0,chunk);
363                 value[0] = '[';
364                 memcpy(&value[1],key,strlenW(key)*sizeof(WCHAR));
365                 value[strlenW(key)+1] = ']';
366             }
367         }
368         else
369         {
370             sz = 0;
371             switch (key[0])
372             {
373                 case '~':
374                     value = deformat_NULL(&chunk);
375                 break;
376                 case '$':
377                     value = deformat_component(package,&key[1],&chunk);
378                 break;
379                 case '#':
380                 case '!': /* should be short path */
381                     value = deformat_file(package,&key[1], &chunk);
382                 break;
383                 case '\\':
384                     value = deformat_escape(&key[1],&chunk);
385                 break;
386                 case '%':
387                     value = deformat_environment(package,&key[1],&chunk);
388                 break;
389                 default:
390                     if (is_key_number(key))
391                         value = deformat_index(record,key,&chunk);
392                     else
393                         value = deformat_property(package,key,&chunk);
394                 break;      
395             }
396         }
397
398         HeapFree(GetProcessHeap(),0,key);
399
400         if (value!=NULL)
401         {
402             LPBYTE nd2;
403             TRACE("value %s, chunk %li size %li\n",debugstr_w((LPWSTR)value),
404                     chunk, size);
405             if (size)
406                 nd2= HeapReAlloc(GetProcessHeap(),0,newdata,(size + chunk));
407             else
408                 nd2= HeapAlloc(GetProcessHeap(),0,chunk);
409             newdata = nd2;
410             memcpy(&newdata[size],value,chunk);
411             size+=chunk;   
412             HeapFree(GetProcessHeap(),0,value);
413         }
414
415         progress = mark2+1;
416     }
417
418     TRACE("after everything %s\n",debugstr_w((LPWSTR)newdata));
419
420     *data = (LPWSTR)newdata;
421     return size / sizeof(WCHAR);
422 }
423
424
425 UINT MSI_FormatRecordW( MSIPACKAGE* package, MSIRECORD* record, LPWSTR buffer,
426                         DWORD *size )
427 {
428     LPWSTR deformated;
429     LPWSTR rec;
430     DWORD len;
431     UINT rc = ERROR_INVALID_PARAMETER;
432
433     TRACE("%p %p %p %li\n",package, record ,buffer, *size);
434
435     rec = load_dynamic_stringW(record,0);
436     if (!rec)
437         rec = build_default_format(record);
438
439     TRACE("(%s)\n",debugstr_w(rec));
440
441     len = deformat_string_internal(package,rec,&deformated,strlenW(rec),
442                                    record);
443
444     if (buffer)
445     {
446         if (*size>len)
447         {
448             memcpy(buffer,deformated,len*sizeof(WCHAR));
449             rc = ERROR_SUCCESS;
450             buffer[len] = 0;
451         }
452         else
453         {
454             if (*size > 0)
455             {
456                 memcpy(buffer,deformated,(*size)*sizeof(WCHAR));
457                 buffer[(*size)-1] = 0;
458             }
459             rc = ERROR_MORE_DATA;
460         }
461     }
462     else
463         rc = ERROR_SUCCESS;
464
465     *size = len;
466
467     HeapFree(GetProcessHeap(),0,rec);
468     HeapFree(GetProcessHeap(),0,deformated);
469     return rc;
470 }
471
472 UINT MSI_FormatRecordA( MSIPACKAGE* package, MSIRECORD* record, LPSTR buffer,
473                         DWORD *size )
474 {
475     LPWSTR deformated;
476     LPWSTR rec;
477     DWORD len,lenA;
478     UINT rc = ERROR_INVALID_PARAMETER;
479
480     TRACE("%p %p %p %li\n",package, record ,buffer, *size);
481
482     rec = load_dynamic_stringW(record,0);
483     if (!rec)
484         rec = build_default_format(record);
485
486     TRACE("(%s)\n",debugstr_w(rec));
487
488     len = deformat_string_internal(package,rec,&deformated,strlenW(rec),
489                                    record);
490     lenA = WideCharToMultiByte(CP_ACP,0,deformated,len,NULL,0,NULL,NULL);
491
492     if (buffer)
493     {
494         WideCharToMultiByte(CP_ACP,0,deformated,len,buffer,*size,NULL, NULL);
495         if (*size>lenA)
496         {
497             rc = ERROR_SUCCESS;
498             buffer[lenA] = 0;
499         }
500         else
501         {
502             rc = ERROR_MORE_DATA;
503             buffer[(*size)-1] = 0;    
504         }
505     }
506     else
507         rc = ERROR_SUCCESS;
508
509     *size = lenA;
510
511     HeapFree(GetProcessHeap(),0,rec);
512     HeapFree(GetProcessHeap(),0,deformated);
513     return rc;
514 }
515
516
517 UINT WINAPI MsiFormatRecordW( MSIHANDLE hInstall, MSIHANDLE hRecord, 
518                               LPWSTR szResult, DWORD *sz )
519 {
520     UINT r = ERROR_INVALID_HANDLE;
521     MSIPACKAGE *package;
522     MSIRECORD *record;
523
524     TRACE("%ld %ld %p %p\n", hInstall, hRecord, szResult, sz);
525
526     record = msihandle2msiinfo( hRecord, MSIHANDLETYPE_RECORD );
527
528     if (!record)
529         return ERROR_INVALID_HANDLE;
530     if (!sz)
531     {
532         msiobj_release( &record->hdr );
533         if (szResult)
534             return ERROR_INVALID_PARAMETER;
535         else
536             return ERROR_SUCCESS;
537     }
538
539     package = msihandle2msiinfo( hInstall, MSIHANDLETYPE_PACKAGE );
540
541     r = MSI_FormatRecordW( package, record, szResult, sz );
542     msiobj_release( &record->hdr );
543     if (package)
544         msiobj_release( &package->hdr );
545     return r;
546 }
547
548 UINT WINAPI MsiFormatRecordA( MSIHANDLE hInstall, MSIHANDLE hRecord,
549                               LPSTR szResult, DWORD *sz )
550 {
551     UINT r = ERROR_INVALID_HANDLE;
552     MSIPACKAGE *package = NULL;
553     MSIRECORD *record = NULL;
554
555     TRACE("%ld %ld %p %p\n", hInstall, hRecord, szResult, sz);
556
557     record = msihandle2msiinfo( hRecord, MSIHANDLETYPE_RECORD );
558
559     if (!record)
560         return ERROR_INVALID_HANDLE;
561     if (!sz)
562     {
563         msiobj_release( &record->hdr );
564         if (szResult)
565             return ERROR_INVALID_PARAMETER;
566         else
567             return ERROR_SUCCESS;
568     }
569
570     package = msihandle2msiinfo( hInstall, MSIHANDLETYPE_PACKAGE );
571
572     r = MSI_FormatRecordA( package, record, szResult, sz );
573     msiobj_release( &record->hdr );
574     if (package)
575         msiobj_release( &package->hdr );
576     return r;
577 }