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