dc27c787f12402ad349161376b3bfac0443d247e
[reactos.git] / reactos / dll / win32 / msi / appsearch.c
1 /*
2 * Implementation of the AppSearch action of the Microsoft Installer (msi.dll)
3 *
4 * Copyright 2005 Juan Lang
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 */
20 #define WIN32_NO_STATUS
21 #define _INC_WINDOWS
22 #define COM_NO_WINDOWS_H
23
24 #include <stdarg.h>
25
26 #define COBJMACROS
27
28 #include <windef.h>
29 #include <winbase.h>
30 #include <winreg.h>
31 //#include "msi.h"
32 //#include "msiquery.h"
33 //#include "msidefs.h"
34 //#include "winver.h"
35 #include <shlwapi.h>
36 #include <wine/unicode.h>
37 #include <wine/debug.h>
38 #include "msipriv.h"
39
40 WINE_DEFAULT_DEBUG_CHANNEL(msi);
41
42 typedef struct tagMSISIGNATURE
43 {
44 LPCWSTR Name; /* NOT owned by this structure */
45 LPWSTR File;
46 DWORD MinVersionMS;
47 DWORD MinVersionLS;
48 DWORD MaxVersionMS;
49 DWORD MaxVersionLS;
50 DWORD MinSize;
51 DWORD MaxSize;
52 FILETIME MinTime;
53 FILETIME MaxTime;
54 LPWSTR Languages;
55 }MSISIGNATURE;
56
57 void msi_parse_version_string(LPCWSTR verStr, PDWORD ms, PDWORD ls)
58 {
59 const WCHAR *ptr;
60 int x1 = 0, x2 = 0, x3 = 0, x4 = 0;
61
62 x1 = atoiW(verStr);
63 ptr = strchrW(verStr, '.');
64 if (ptr)
65 {
66 x2 = atoiW(ptr + 1);
67 ptr = strchrW(ptr + 1, '.');
68 }
69 if (ptr)
70 {
71 x3 = atoiW(ptr + 1);
72 ptr = strchrW(ptr + 1, '.');
73 }
74 if (ptr)
75 x4 = atoiW(ptr + 1);
76 /* FIXME: byte-order dependent? */
77 *ms = x1 << 16 | x2;
78 if (ls) *ls = x3 << 16 | x4;
79 }
80
81 /* Fills in sig with the values from the Signature table, where name is the
82 * signature to find. Upon return, sig->File will be NULL if the record is not
83 * found, and not NULL if it is found.
84 * Warning: clears all fields in sig!
85 * Returns ERROR_SUCCESS upon success (where not finding the record counts as
86 * success), something else on error.
87 */
88 static UINT ACTION_AppSearchGetSignature(MSIPACKAGE *package, MSISIGNATURE *sig, LPCWSTR name)
89 {
90 static const WCHAR query[] = {
91 's','e','l','e','c','t',' ','*',' ',
92 'f','r','o','m',' ',
93 'S','i','g','n','a','t','u','r','e',' ',
94 'w','h','e','r','e',' ','S','i','g','n','a','t','u','r','e',' ','=',' ',
95 '\'','%','s','\'',0};
96 LPWSTR minVersion, maxVersion, p;
97 MSIRECORD *row;
98 DWORD time;
99
100 TRACE("package %p, sig %p\n", package, sig);
101
102 memset(sig, 0, sizeof(*sig));
103 sig->Name = name;
104 row = MSI_QueryGetRecord( package->db, query, name );
105 if (!row)
106 {
107 TRACE("failed to query signature for %s\n", debugstr_w(name));
108 return ERROR_SUCCESS;
109 }
110
111 /* get properties */
112 sig->File = msi_dup_record_field(row,2);
113 if ((p = strchrW(sig->File, '|')))
114 {
115 p++;
116 memmove(sig->File, p, (strlenW(p) + 1) * sizeof(WCHAR));
117 }
118
119 minVersion = msi_dup_record_field(row,3);
120 if (minVersion)
121 {
122 msi_parse_version_string( minVersion, &sig->MinVersionMS, &sig->MinVersionLS );
123 msi_free( minVersion );
124 }
125 maxVersion = msi_dup_record_field(row,4);
126 if (maxVersion)
127 {
128 msi_parse_version_string( maxVersion, &sig->MaxVersionMS, &sig->MaxVersionLS );
129 msi_free( maxVersion );
130 }
131 sig->MinSize = MSI_RecordGetInteger(row,5);
132 if (sig->MinSize == MSI_NULL_INTEGER)
133 sig->MinSize = 0;
134 sig->MaxSize = MSI_RecordGetInteger(row,6);
135 if (sig->MaxSize == MSI_NULL_INTEGER)
136 sig->MaxSize = 0;
137 sig->Languages = msi_dup_record_field(row,9);
138 time = MSI_RecordGetInteger(row,7);
139 if (time != MSI_NULL_INTEGER)
140 DosDateTimeToFileTime(HIWORD(time), LOWORD(time), &sig->MinTime);
141 time = MSI_RecordGetInteger(row,8);
142 if (time != MSI_NULL_INTEGER)
143 DosDateTimeToFileTime(HIWORD(time), LOWORD(time), &sig->MaxTime);
144
145 TRACE("Found file name %s for Signature_ %s;\n",
146 debugstr_w(sig->File), debugstr_w(name));
147 TRACE("MinVersion is %d.%d.%d.%d\n", HIWORD(sig->MinVersionMS),
148 LOWORD(sig->MinVersionMS), HIWORD(sig->MinVersionLS),
149 LOWORD(sig->MinVersionLS));
150 TRACE("MaxVersion is %d.%d.%d.%d\n", HIWORD(sig->MaxVersionMS),
151 LOWORD(sig->MaxVersionMS), HIWORD(sig->MaxVersionLS),
152 LOWORD(sig->MaxVersionLS));
153 TRACE("MinSize is %d, MaxSize is %d;\n", sig->MinSize, sig->MaxSize);
154 TRACE("Languages is %s\n", debugstr_w(sig->Languages));
155
156 msiobj_release( &row->hdr );
157
158 return ERROR_SUCCESS;
159 }
160
161 /* Frees any memory allocated in sig */
162 static void ACTION_FreeSignature(MSISIGNATURE *sig)
163 {
164 msi_free(sig->File);
165 msi_free(sig->Languages);
166 }
167
168 static LPWSTR app_search_file(LPWSTR path, MSISIGNATURE *sig)
169 {
170 VS_FIXEDFILEINFO *info;
171 DWORD attr, handle, size;
172 LPWSTR val = NULL;
173 LPBYTE buffer;
174
175 if (!sig->File)
176 {
177 PathRemoveFileSpecW(path);
178 PathAddBackslashW(path);
179
180 attr = GetFileAttributesW(path);
181 if (attr != INVALID_FILE_ATTRIBUTES &&
182 (attr & FILE_ATTRIBUTE_DIRECTORY))
183 return strdupW(path);
184
185 return NULL;
186 }
187
188 attr = GetFileAttributesW(path);
189 if (attr == INVALID_FILE_ATTRIBUTES ||
190 (attr & FILE_ATTRIBUTE_DIRECTORY))
191 return NULL;
192
193 size = GetFileVersionInfoSizeW(path, &handle);
194 if (!size)
195 return strdupW(path);
196
197 buffer = msi_alloc(size);
198 if (!buffer)
199 return NULL;
200
201 if (!GetFileVersionInfoW(path, 0, size, buffer))
202 goto done;
203
204 if (!VerQueryValueW(buffer, szBackSlash, (LPVOID)&info, &size) || !info)
205 goto done;
206
207 if (sig->MinVersionLS || sig->MinVersionMS)
208 {
209 if (info->dwFileVersionMS < sig->MinVersionMS)
210 goto done;
211
212 if (info->dwFileVersionMS == sig->MinVersionMS &&
213 info->dwFileVersionLS < sig->MinVersionLS)
214 goto done;
215 }
216
217 if (sig->MaxVersionLS || sig->MaxVersionMS)
218 {
219 if (info->dwFileVersionMS > sig->MaxVersionMS)
220 goto done;
221
222 if (info->dwFileVersionMS == sig->MaxVersionMS &&
223 info->dwFileVersionLS > sig->MaxVersionLS)
224 goto done;
225 }
226
227 val = strdupW(path);
228
229 done:
230 msi_free(buffer);
231 return val;
232 }
233
234 static UINT ACTION_AppSearchComponents(MSIPACKAGE *package, LPWSTR *appValue, MSISIGNATURE *sig)
235 {
236 static const WCHAR query[] = {
237 'S','E','L','E','C','T',' ','*',' ',
238 'F','R','O','M',' ',
239 '`','C','o','m','p','L','o','c','a','t','o','r','`',' ',
240 'W','H','E','R','E',' ','`','S','i','g','n','a','t','u','r','e','_','`',' ','=',' ',
241 '\'','%','s','\'',0};
242 static const WCHAR sigquery[] = {
243 'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
244 '`','S','i','g','n','a','t','u','r','e','`',' ',
245 'W','H','E','R','E',' ','`','S','i','g','n','a','t','u','r','e','`',' ','=',' ',
246 '\'','%','s','\'',0};
247
248 MSIRECORD *row, *rec;
249 LPCWSTR signature, guid;
250 BOOL sigpresent = TRUE;
251 BOOL isdir;
252 UINT type;
253 WCHAR path[MAX_PATH];
254 DWORD size = MAX_PATH;
255 LPWSTR ptr;
256 DWORD attr;
257
258 TRACE("%s\n", debugstr_w(sig->Name));
259
260 *appValue = NULL;
261
262 row = MSI_QueryGetRecord(package->db, query, sig->Name);
263 if (!row)
264 {
265 TRACE("failed to query CompLocator for %s\n", debugstr_w(sig->Name));
266 return ERROR_SUCCESS;
267 }
268
269 signature = MSI_RecordGetString(row, 1);
270 guid = MSI_RecordGetString(row, 2);
271 type = MSI_RecordGetInteger(row, 3);
272
273 rec = MSI_QueryGetRecord(package->db, sigquery, signature);
274 if (!rec)
275 sigpresent = FALSE;
276
277 *path = '\0';
278 MsiLocateComponentW(guid, path, &size);
279 if (!*path)
280 goto done;
281
282 attr = GetFileAttributesW(path);
283 if (attr == INVALID_FILE_ATTRIBUTES)
284 goto done;
285
286 isdir = (attr & FILE_ATTRIBUTE_DIRECTORY);
287
288 if (type != msidbLocatorTypeDirectory && sigpresent && !isdir)
289 {
290 *appValue = app_search_file(path, sig);
291 }
292 else if (!sigpresent && (type != msidbLocatorTypeDirectory || isdir))
293 {
294 if (type == msidbLocatorTypeFileName)
295 {
296 ptr = strrchrW(path, '\\');
297 *(ptr + 1) = '\0';
298 }
299 else
300 PathAddBackslashW(path);
301
302 *appValue = strdupW(path);
303 }
304 else if (sigpresent)
305 {
306 PathAddBackslashW(path);
307 lstrcatW(path, MSI_RecordGetString(rec, 2));
308
309 attr = GetFileAttributesW(path);
310 if (attr != INVALID_FILE_ATTRIBUTES &&
311 !(attr & FILE_ATTRIBUTE_DIRECTORY))
312 *appValue = strdupW(path);
313 }
314
315 done:
316 if (rec) msiobj_release(&rec->hdr);
317 msiobj_release(&row->hdr);
318 return ERROR_SUCCESS;
319 }
320
321 static void ACTION_ConvertRegValue(DWORD regType, const BYTE *value, DWORD sz,
322 LPWSTR *appValue)
323 {
324 static const WCHAR dwordFmt[] = { '#','%','d','\0' };
325 static const WCHAR binPre[] = { '#','x','\0' };
326 static const WCHAR binFmt[] = { '%','0','2','X','\0' };
327 LPWSTR ptr;
328 DWORD i;
329
330 switch (regType)
331 {
332 case REG_SZ:
333 if (*(LPCWSTR)value == '#')
334 {
335 /* escape leading pound with another */
336 *appValue = msi_alloc(sz + sizeof(WCHAR));
337 (*appValue)[0] = '#';
338 strcpyW(*appValue + 1, (LPCWSTR)value);
339 }
340 else
341 {
342 *appValue = msi_alloc(sz);
343 strcpyW(*appValue, (LPCWSTR)value);
344 }
345 break;
346 case REG_DWORD:
347 /* 7 chars for digits, 1 for NULL, 1 for #, and 1 for sign
348 * char if needed
349 */
350 *appValue = msi_alloc(10 * sizeof(WCHAR));
351 sprintfW(*appValue, dwordFmt, *(const DWORD *)value);
352 break;
353 case REG_EXPAND_SZ:
354 sz = ExpandEnvironmentStringsW((LPCWSTR)value, NULL, 0);
355 *appValue = msi_alloc(sz * sizeof(WCHAR));
356 ExpandEnvironmentStringsW((LPCWSTR)value, *appValue, sz);
357 break;
358 case REG_BINARY:
359 /* #x<nibbles>\0 */
360 *appValue = msi_alloc((sz * 2 + 3) * sizeof(WCHAR));
361 lstrcpyW(*appValue, binPre);
362 ptr = *appValue + lstrlenW(binPre);
363 for (i = 0; i < sz; i++, ptr += 2)
364 sprintfW(ptr, binFmt, value[i]);
365 break;
366 default:
367 WARN("unimplemented for values of type %d\n", regType);
368 *appValue = NULL;
369 }
370 }
371
372 static UINT ACTION_SearchDirectory(MSIPACKAGE *package, MSISIGNATURE *sig,
373 LPCWSTR path, int depth, LPWSTR *appValue);
374
375 static UINT ACTION_AppSearchReg(MSIPACKAGE *package, LPWSTR *appValue, MSISIGNATURE *sig)
376 {
377 static const WCHAR query[] = {
378 'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
379 'R','e','g','L','o','c','a','t','o','r',' ','W','H','E','R','E',' ',
380 'S','i','g','n','a','t','u','r','e','_',' ','=',' ', '\'','%','s','\'',0};
381 const WCHAR *keyPath, *valueName;
382 WCHAR *deformatted = NULL, *ptr = NULL, *end;
383 int root, type;
384 HKEY rootKey, key = NULL;
385 DWORD sz = 0, regType;
386 LPBYTE value = NULL;
387 MSIRECORD *row;
388 UINT rc;
389
390 TRACE("%s\n", debugstr_w(sig->Name));
391
392 *appValue = NULL;
393
394 row = MSI_QueryGetRecord( package->db, query, sig->Name );
395 if (!row)
396 {
397 TRACE("failed to query RegLocator for %s\n", debugstr_w(sig->Name));
398 return ERROR_SUCCESS;
399 }
400
401 root = MSI_RecordGetInteger(row, 2);
402 keyPath = MSI_RecordGetString(row, 3);
403 valueName = MSI_RecordGetString(row, 4);
404 type = MSI_RecordGetInteger(row, 5);
405
406 deformat_string(package, keyPath, &deformatted);
407
408 switch (root)
409 {
410 case msidbRegistryRootClassesRoot:
411 rootKey = HKEY_CLASSES_ROOT;
412 break;
413 case msidbRegistryRootCurrentUser:
414 rootKey = HKEY_CURRENT_USER;
415 break;
416 case msidbRegistryRootLocalMachine:
417 rootKey = HKEY_LOCAL_MACHINE;
418 break;
419 case msidbRegistryRootUsers:
420 rootKey = HKEY_USERS;
421 break;
422 default:
423 WARN("Unknown root key %d\n", root);
424 goto end;
425 }
426
427 rc = RegOpenKeyW(rootKey, deformatted, &key);
428 if (rc)
429 {
430 TRACE("RegOpenKeyW returned %d\n", rc);
431 goto end;
432 }
433
434 msi_free(deformatted);
435 deformat_string(package, valueName, &deformatted);
436
437 rc = RegQueryValueExW(key, deformatted, NULL, NULL, NULL, &sz);
438 if (rc)
439 {
440 TRACE("RegQueryValueExW returned %d\n", rc);
441 goto end;
442 }
443 /* FIXME: sanity-check sz before allocating (is there an upper-limit
444 * on the value of a property?)
445 */
446 value = msi_alloc( sz );
447 rc = RegQueryValueExW(key, deformatted, NULL, &regType, value, &sz);
448 if (rc)
449 {
450 TRACE("RegQueryValueExW returned %d\n", rc);
451 goto end;
452 }
453
454 /* bail out if the registry key is empty */
455 if (sz == 0)
456 goto end;
457
458 if ((regType == REG_SZ || regType == REG_EXPAND_SZ) &&
459 (ptr = strchrW((LPWSTR)value, '"')) && (end = strchrW(++ptr, '"')))
460 *end = '\0';
461 else
462 ptr = (LPWSTR)value;
463
464 switch (type & 0x0f)
465 {
466 case msidbLocatorTypeDirectory:
467 ACTION_SearchDirectory(package, sig, ptr, 0, appValue);
468 break;
469 case msidbLocatorTypeFileName:
470 *appValue = app_search_file(ptr, sig);
471 break;
472 case msidbLocatorTypeRawValue:
473 ACTION_ConvertRegValue(regType, value, sz, appValue);
474 break;
475 default:
476 FIXME("unimplemented for type %d (key path %s, value %s)\n",
477 type, debugstr_w(keyPath), debugstr_w(valueName));
478 }
479 end:
480 msi_free( value );
481 RegCloseKey( key );
482 msi_free( deformatted );
483
484 msiobj_release(&row->hdr);
485 return ERROR_SUCCESS;
486 }
487
488 static LPWSTR get_ini_field(LPWSTR buf, int field)
489 {
490 LPWSTR beg, end;
491 int i = 1;
492
493 if (field == 0)
494 return strdupW(buf);
495
496 beg = buf;
497 while ((end = strchrW(beg, ',')) && i < field)
498 {
499 beg = end + 1;
500 while (*beg && *beg == ' ')
501 beg++;
502
503 i++;
504 }
505
506 end = strchrW(beg, ',');
507 if (!end)
508 end = beg + lstrlenW(beg);
509
510 *end = '\0';
511 return strdupW(beg);
512 }
513
514 static UINT ACTION_AppSearchIni(MSIPACKAGE *package, LPWSTR *appValue,
515 MSISIGNATURE *sig)
516 {
517 static const WCHAR query[] = {
518 's','e','l','e','c','t',' ','*',' ',
519 'f','r','o','m',' ',
520 'I','n','i','L','o','c','a','t','o','r',' ',
521 'w','h','e','r','e',' ',
522 'S','i','g','n','a','t','u','r','e','_',' ','=',' ','\'','%','s','\'',0};
523 MSIRECORD *row;
524 LPWSTR fileName, section, key;
525 int field, type;
526 WCHAR buf[MAX_PATH];
527
528 TRACE("%s\n", debugstr_w(sig->Name));
529
530 *appValue = NULL;
531
532 row = MSI_QueryGetRecord( package->db, query, sig->Name );
533 if (!row)
534 {
535 TRACE("failed to query IniLocator for %s\n", debugstr_w(sig->Name));
536 return ERROR_SUCCESS;
537 }
538
539 fileName = msi_dup_record_field(row, 2);
540 section = msi_dup_record_field(row, 3);
541 key = msi_dup_record_field(row, 4);
542 field = MSI_RecordGetInteger(row, 5);
543 type = MSI_RecordGetInteger(row, 6);
544 if (field == MSI_NULL_INTEGER)
545 field = 0;
546 if (type == MSI_NULL_INTEGER)
547 type = 0;
548
549 GetPrivateProfileStringW(section, key, NULL, buf, MAX_PATH, fileName);
550 if (buf[0])
551 {
552 switch (type & 0x0f)
553 {
554 case msidbLocatorTypeDirectory:
555 ACTION_SearchDirectory(package, sig, buf, 0, appValue);
556 break;
557 case msidbLocatorTypeFileName:
558 *appValue = app_search_file(buf, sig);
559 break;
560 case msidbLocatorTypeRawValue:
561 *appValue = get_ini_field(buf, field);
562 break;
563 }
564 }
565
566 msi_free(fileName);
567 msi_free(section);
568 msi_free(key);
569
570 msiobj_release(&row->hdr);
571
572 return ERROR_SUCCESS;
573 }
574
575 /* Expands the value in src into a path without property names and only
576 * containing long path names into dst. Replaces at most len characters of dst,
577 * and always NULL-terminates dst if dst is not NULL and len >= 1.
578 * May modify src.
579 * Assumes src and dst are non-overlapping.
580 * FIXME: return code probably needed:
581 * - what does AppSearch return if the table values are invalid?
582 * - what if dst is too small?
583 */
584 static void ACTION_ExpandAnyPath(MSIPACKAGE *package, WCHAR *src, WCHAR *dst,
585 size_t len)
586 {
587 WCHAR *ptr, *deformatted;
588
589 if (!src || !dst || !len)
590 {
591 if (dst) *dst = '\0';
592 return;
593 }
594
595 dst[0] = '\0';
596
597 /* Ignore the short portion of the path */
598 if ((ptr = strchrW(src, '|')))
599 ptr++;
600 else
601 ptr = src;
602
603 deformat_string(package, ptr, &deformatted);
604 if (!deformatted || strlenW(deformatted) > len - 1)
605 {
606 msi_free(deformatted);
607 return;
608 }
609
610 lstrcpyW(dst, deformatted);
611 dst[lstrlenW(deformatted)] = '\0';
612 msi_free(deformatted);
613 }
614
615 static LANGID *parse_languages( const WCHAR *languages, DWORD *num_ids )
616 {
617 UINT i, count = 1;
618 WCHAR *str = strdupW( languages ), *p, *q;
619 LANGID *ret;
620
621 if (!str) return NULL;
622 for (p = q = str; (q = strchrW( q, ',' )); q++) count++;
623
624 if (!(ret = msi_alloc( count * sizeof(LANGID) )))
625 {
626 msi_free( str );
627 return NULL;
628 }
629 i = 0;
630 while (*p)
631 {
632 q = strchrW( p, ',' );
633 if (q) *q = 0;
634 ret[i] = atoiW( p );
635 if (!q) break;
636 p = q + 1;
637 i++;
638 }
639 msi_free( str );
640 *num_ids = count;
641 return ret;
642 }
643
644 static BOOL match_languages( const void *version, const WCHAR *languages )
645 {
646 struct lang
647 {
648 USHORT id;
649 USHORT codepage;
650 } *lang;
651 DWORD len, num_ids, i, j;
652 BOOL found = FALSE;
653 LANGID *ids;
654
655 if (!languages || !languages[0]) return TRUE;
656 if (!VerQueryValueW( version, szLangResource, (void **)&lang, &len )) return FALSE;
657 if (!(ids = parse_languages( languages, &num_ids ))) return FALSE;
658
659 for (i = 0; i < num_ids; i++)
660 {
661 found = FALSE;
662 for (j = 0; j < len / sizeof(struct lang); j++)
663 {
664 if (!ids[i] || ids[i] == lang[j].id) found = TRUE;
665 }
666 if (!found) goto done;
667 }
668
669 done:
670 msi_free( ids );
671 return found;
672 }
673
674 /* Sets *matches to whether the file (whose path is filePath) matches the
675 * versions set in sig.
676 * Return ERROR_SUCCESS in case of success (whether or not the file matches),
677 * something else if an install-halting error occurs.
678 */
679 static UINT ACTION_FileVersionMatches(const MSISIGNATURE *sig, LPCWSTR filePath,
680 BOOL *matches)
681 {
682 UINT len;
683 void *version;
684 VS_FIXEDFILEINFO *info = NULL;
685 DWORD zero, size = GetFileVersionInfoSizeW( filePath, &zero );
686
687 *matches = FALSE;
688
689 if (!size) return ERROR_SUCCESS;
690 if (!(version = msi_alloc( size ))) return ERROR_OUTOFMEMORY;
691
692 if (GetFileVersionInfoW( filePath, 0, size, version ))
693 VerQueryValueW( version, szBackSlash, (void **)&info, &len );
694
695 if (info)
696 {
697 TRACE("comparing file version %d.%d.%d.%d:\n",
698 HIWORD(info->dwFileVersionMS),
699 LOWORD(info->dwFileVersionMS),
700 HIWORD(info->dwFileVersionLS),
701 LOWORD(info->dwFileVersionLS));
702 if (info->dwFileVersionMS < sig->MinVersionMS
703 || (info->dwFileVersionMS == sig->MinVersionMS &&
704 info->dwFileVersionLS < sig->MinVersionLS))
705 {
706 TRACE("less than minimum version %d.%d.%d.%d\n",
707 HIWORD(sig->MinVersionMS),
708 LOWORD(sig->MinVersionMS),
709 HIWORD(sig->MinVersionLS),
710 LOWORD(sig->MinVersionLS));
711 }
712 else if ((sig->MaxVersionMS || sig->MaxVersionLS) &&
713 (info->dwFileVersionMS > sig->MaxVersionMS ||
714 (info->dwFileVersionMS == sig->MaxVersionMS &&
715 info->dwFileVersionLS > sig->MaxVersionLS)))
716 {
717 TRACE("greater than maximum version %d.%d.%d.%d\n",
718 HIWORD(sig->MaxVersionMS),
719 LOWORD(sig->MaxVersionMS),
720 HIWORD(sig->MaxVersionLS),
721 LOWORD(sig->MaxVersionLS));
722 }
723 else if (!match_languages( version, sig->Languages ))
724 {
725 TRACE("languages %s not supported\n", debugstr_w( sig->Languages ));
726 }
727 else *matches = TRUE;
728 }
729 msi_free( version );
730 return ERROR_SUCCESS;
731 }
732
733 /* Sets *matches to whether the file in findData matches that in sig.
734 * fullFilePath is assumed to be the full path of the file specified in
735 * findData, which may be necessary to compare the version.
736 * Return ERROR_SUCCESS in case of success (whether or not the file matches),
737 * something else if an install-halting error occurs.
738 */
739 static UINT ACTION_FileMatchesSig(const MSISIGNATURE *sig,
740 const WIN32_FIND_DATAW *findData, LPCWSTR fullFilePath, BOOL *matches)
741 {
742 UINT rc = ERROR_SUCCESS;
743
744 *matches = TRUE;
745 /* assumes the caller has already ensured the filenames match, so check
746 * the other fields..
747 */
748 if (sig->MinTime.dwLowDateTime || sig->MinTime.dwHighDateTime)
749 {
750 if (findData->ftCreationTime.dwHighDateTime <
751 sig->MinTime.dwHighDateTime ||
752 (findData->ftCreationTime.dwHighDateTime == sig->MinTime.dwHighDateTime
753 && findData->ftCreationTime.dwLowDateTime <
754 sig->MinTime.dwLowDateTime))
755 *matches = FALSE;
756 }
757 if (*matches && (sig->MaxTime.dwLowDateTime || sig->MaxTime.dwHighDateTime))
758 {
759 if (findData->ftCreationTime.dwHighDateTime >
760 sig->MaxTime.dwHighDateTime ||
761 (findData->ftCreationTime.dwHighDateTime == sig->MaxTime.dwHighDateTime
762 && findData->ftCreationTime.dwLowDateTime >
763 sig->MaxTime.dwLowDateTime))
764 *matches = FALSE;
765 }
766 if (*matches && sig->MinSize && findData->nFileSizeLow < sig->MinSize)
767 *matches = FALSE;
768 if (*matches && sig->MaxSize && findData->nFileSizeLow > sig->MaxSize)
769 *matches = FALSE;
770 if (*matches && (sig->MinVersionMS || sig->MinVersionLS ||
771 sig->MaxVersionMS || sig->MaxVersionLS))
772 rc = ACTION_FileVersionMatches(sig, fullFilePath, matches);
773 return rc;
774 }
775
776 /* Recursively searches the directory dir for files that match the signature
777 * sig, up to (depth + 1) levels deep. That is, if depth is 0, it searches dir
778 * (and only dir). If depth is 1, searches dir and its immediate
779 * subdirectories.
780 * Assumes sig->File is not NULL.
781 * Returns ERROR_SUCCESS on success (which may include non-critical errors),
782 * something else on failures which should halt the install.
783 */
784 static UINT ACTION_RecurseSearchDirectory(MSIPACKAGE *package, LPWSTR *appValue,
785 MSISIGNATURE *sig, LPCWSTR dir, int depth)
786 {
787 HANDLE hFind;
788 WIN32_FIND_DATAW findData;
789 UINT rc = ERROR_SUCCESS;
790 size_t dirLen = lstrlenW(dir), fileLen = lstrlenW(sig->File);
791 WCHAR subpath[MAX_PATH];
792 WCHAR *buf;
793 DWORD len;
794
795 static const WCHAR starDotStarW[] = { '*','.','*',0 };
796
797 TRACE("Searching directory %s for file %s, depth %d\n", debugstr_w(dir),
798 debugstr_w(sig->File), depth);
799
800 if (depth < 0)
801 return ERROR_SUCCESS;
802
803 *appValue = NULL;
804 /* We need the buffer in both paths below, so go ahead and allocate it
805 * here. Add two because we might need to add a backslash if the dir name
806 * isn't backslash-terminated.
807 */
808 len = dirLen + max(fileLen, strlenW(starDotStarW)) + 2;
809 buf = msi_alloc(len * sizeof(WCHAR));
810 if (!buf)
811 return ERROR_OUTOFMEMORY;
812
813 lstrcpyW(buf, dir);
814 PathAddBackslashW(buf);
815 lstrcatW(buf, sig->File);
816
817 hFind = FindFirstFileW(buf, &findData);
818 if (hFind != INVALID_HANDLE_VALUE)
819 {
820 if (!(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
821 {
822 BOOL matches;
823
824 rc = ACTION_FileMatchesSig(sig, &findData, buf, &matches);
825 if (rc == ERROR_SUCCESS && matches)
826 {
827 TRACE("found file, returning %s\n", debugstr_w(buf));
828 *appValue = buf;
829 }
830 }
831 FindClose(hFind);
832 }
833
834 if (rc == ERROR_SUCCESS && !*appValue)
835 {
836 lstrcpyW(buf, dir);
837 PathAddBackslashW(buf);
838 lstrcatW(buf, starDotStarW);
839
840 hFind = FindFirstFileW(buf, &findData);
841 if (hFind != INVALID_HANDLE_VALUE)
842 {
843 if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY &&
844 strcmpW( findData.cFileName, szDot ) &&
845 strcmpW( findData.cFileName, szDotDot ))
846 {
847 lstrcpyW(subpath, dir);
848 PathAppendW(subpath, findData.cFileName);
849 rc = ACTION_RecurseSearchDirectory(package, appValue, sig,
850 subpath, depth - 1);
851 }
852
853 while (rc == ERROR_SUCCESS && !*appValue &&
854 FindNextFileW(hFind, &findData) != 0)
855 {
856 if (!strcmpW( findData.cFileName, szDot ) ||
857 !strcmpW( findData.cFileName, szDotDot ))
858 continue;
859
860 lstrcpyW(subpath, dir);
861 PathAppendW(subpath, findData.cFileName);
862 if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
863 rc = ACTION_RecurseSearchDirectory(package, appValue,
864 sig, subpath, depth - 1);
865 }
866
867 FindClose(hFind);
868 }
869 }
870
871 if (*appValue != buf)
872 msi_free(buf);
873
874 return rc;
875 }
876
877 static UINT ACTION_CheckDirectory(MSIPACKAGE *package, LPCWSTR dir,
878 LPWSTR *appValue)
879 {
880 DWORD attr = GetFileAttributesW(dir);
881
882 if (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY))
883 {
884 TRACE("directory exists, returning %s\n", debugstr_w(dir));
885 *appValue = strdupW(dir);
886 }
887
888 return ERROR_SUCCESS;
889 }
890
891 static BOOL ACTION_IsFullPath(LPCWSTR path)
892 {
893 WCHAR first = toupperW(path[0]);
894 BOOL ret;
895
896 if (first >= 'A' && first <= 'Z' && path[1] == ':')
897 ret = TRUE;
898 else if (path[0] == '\\' && path[1] == '\\')
899 ret = TRUE;
900 else
901 ret = FALSE;
902 return ret;
903 }
904
905 static UINT ACTION_SearchDirectory(MSIPACKAGE *package, MSISIGNATURE *sig,
906 LPCWSTR path, int depth, LPWSTR *appValue)
907 {
908 UINT rc;
909 DWORD attr;
910 LPWSTR val = NULL;
911
912 TRACE("%p, %p, %s, %d, %p\n", package, sig, debugstr_w(path), depth,
913 appValue);
914
915 if (ACTION_IsFullPath(path))
916 {
917 if (sig->File)
918 rc = ACTION_RecurseSearchDirectory(package, &val, sig, path, depth);
919 else
920 {
921 /* Recursively searching a directory makes no sense when the
922 * directory to search is the thing you're trying to find.
923 */
924 rc = ACTION_CheckDirectory(package, path, &val);
925 }
926 }
927 else
928 {
929 WCHAR pathWithDrive[MAX_PATH] = { 'C',':','\\',0 };
930 DWORD drives = GetLogicalDrives();
931 int i;
932
933 rc = ERROR_SUCCESS;
934 for (i = 0; rc == ERROR_SUCCESS && !val && i < 26; i++)
935 {
936 if (!(drives & (1 << i)))
937 continue;
938
939 pathWithDrive[0] = 'A' + i;
940 if (GetDriveTypeW(pathWithDrive) != DRIVE_FIXED)
941 continue;
942
943 lstrcpynW(pathWithDrive + 3, path,
944 sizeof(pathWithDrive) / sizeof(pathWithDrive[0]) - 3);
945
946 if (sig->File)
947 rc = ACTION_RecurseSearchDirectory(package, &val, sig,
948 pathWithDrive, depth);
949 else
950 rc = ACTION_CheckDirectory(package, pathWithDrive, &val);
951 }
952 }
953
954 attr = GetFileAttributesW(val);
955 if (attr != INVALID_FILE_ATTRIBUTES &&
956 (attr & FILE_ATTRIBUTE_DIRECTORY) &&
957 val && val[lstrlenW(val) - 1] != '\\')
958 {
959 val = msi_realloc(val, (lstrlenW(val) + 2) * sizeof(WCHAR));
960 if (!val)
961 rc = ERROR_OUTOFMEMORY;
962 else
963 PathAddBackslashW(val);
964 }
965
966 *appValue = val;
967
968 TRACE("returning %d\n", rc);
969 return rc;
970 }
971
972 static UINT ACTION_AppSearchSigName(MSIPACKAGE *package, LPCWSTR sigName,
973 MSISIGNATURE *sig, LPWSTR *appValue);
974
975 static UINT ACTION_AppSearchDr(MSIPACKAGE *package, LPWSTR *appValue, MSISIGNATURE *sig)
976 {
977 static const WCHAR query[] = {
978 's','e','l','e','c','t',' ','*',' ',
979 'f','r','o','m',' ',
980 'D','r','L','o','c','a','t','o','r',' ',
981 'w','h','e','r','e',' ',
982 'S','i','g','n','a','t','u','r','e','_',' ','=',' ', '\'','%','s','\'',0};
983 LPWSTR parent = NULL;
984 LPCWSTR parentName;
985 WCHAR path[MAX_PATH];
986 WCHAR expanded[MAX_PATH];
987 MSIRECORD *row;
988 int depth;
989 DWORD sz, attr;
990 UINT rc;
991
992 TRACE("%s\n", debugstr_w(sig->Name));
993
994 *appValue = NULL;
995
996 row = MSI_QueryGetRecord( package->db, query, sig->Name );
997 if (!row)
998 {
999 TRACE("failed to query DrLocator for %s\n", debugstr_w(sig->Name));
1000 return ERROR_SUCCESS;
1001 }
1002
1003 /* check whether parent is set */
1004 parentName = MSI_RecordGetString(row, 2);
1005 if (parentName)
1006 {
1007 MSISIGNATURE parentSig;
1008
1009 ACTION_AppSearchSigName(package, parentName, &parentSig, &parent);
1010 ACTION_FreeSignature(&parentSig);
1011 if (!parent)
1012 {
1013 msiobj_release(&row->hdr);
1014 return ERROR_SUCCESS;
1015 }
1016 }
1017
1018 sz = MAX_PATH;
1019 MSI_RecordGetStringW(row, 3, path, &sz);
1020
1021 if (MSI_RecordIsNull(row,4))
1022 depth = 0;
1023 else
1024 depth = MSI_RecordGetInteger(row,4);
1025
1026 if (sz)
1027 ACTION_ExpandAnyPath(package, path, expanded, MAX_PATH);
1028 else
1029 strcpyW(expanded, path);
1030
1031 if (parent)
1032 {
1033 attr = GetFileAttributesW(parent);
1034 if (attr != INVALID_FILE_ATTRIBUTES &&
1035 !(attr & FILE_ATTRIBUTE_DIRECTORY))
1036 {
1037 PathRemoveFileSpecW(parent);
1038 PathAddBackslashW(parent);
1039 }
1040
1041 strcpyW(path, parent);
1042 strcatW(path, expanded);
1043 }
1044 else if (sz)
1045 strcpyW(path, expanded);
1046
1047 PathAddBackslashW(path);
1048
1049 rc = ACTION_SearchDirectory(package, sig, path, depth, appValue);
1050
1051 msi_free(parent);
1052 msiobj_release(&row->hdr);
1053
1054 TRACE("returning %d\n", rc);
1055 return rc;
1056 }
1057
1058 static UINT ACTION_AppSearchSigName(MSIPACKAGE *package, LPCWSTR sigName,
1059 MSISIGNATURE *sig, LPWSTR *appValue)
1060 {
1061 UINT rc;
1062
1063 *appValue = NULL;
1064 rc = ACTION_AppSearchGetSignature(package, sig, sigName);
1065 if (rc == ERROR_SUCCESS)
1066 {
1067 rc = ACTION_AppSearchComponents(package, appValue, sig);
1068 if (rc == ERROR_SUCCESS && !*appValue)
1069 {
1070 rc = ACTION_AppSearchReg(package, appValue, sig);
1071 if (rc == ERROR_SUCCESS && !*appValue)
1072 {
1073 rc = ACTION_AppSearchIni(package, appValue, sig);
1074 if (rc == ERROR_SUCCESS && !*appValue)
1075 rc = ACTION_AppSearchDr(package, appValue, sig);
1076 }
1077 }
1078 }
1079 return rc;
1080 }
1081
1082 static UINT iterate_appsearch(MSIRECORD *row, LPVOID param)
1083 {
1084 MSIPACKAGE *package = param;
1085 LPCWSTR propName, sigName;
1086 LPWSTR value = NULL;
1087 MSISIGNATURE sig;
1088 MSIRECORD *uirow;
1089 UINT r;
1090
1091 /* get property and signature */
1092 propName = MSI_RecordGetString(row, 1);
1093 sigName = MSI_RecordGetString(row, 2);
1094
1095 TRACE("%s %s\n", debugstr_w(propName), debugstr_w(sigName));
1096
1097 r = ACTION_AppSearchSigName(package, sigName, &sig, &value);
1098 if (value)
1099 {
1100 r = msi_set_property( package->db, propName, value, -1 );
1101 if (r == ERROR_SUCCESS && !strcmpW( propName, szSourceDir ))
1102 msi_reset_folders( package, TRUE );
1103
1104 msi_free(value);
1105 }
1106 ACTION_FreeSignature(&sig);
1107
1108 uirow = MSI_CreateRecord( 2 );
1109 MSI_RecordSetStringW( uirow, 1, propName );
1110 MSI_RecordSetStringW( uirow, 2, sigName );
1111 msi_ui_actiondata( package, szAppSearch, uirow );
1112 msiobj_release( &uirow->hdr );
1113
1114 return r;
1115 }
1116
1117 UINT ACTION_AppSearch(MSIPACKAGE *package)
1118 {
1119 static const WCHAR query[] = {
1120 'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
1121 'A','p','p','S','e','a','r','c','h',0};
1122 MSIQUERY *view;
1123 UINT r;
1124
1125 if (msi_action_is_unique(package, szAppSearch))
1126 {
1127 TRACE("Skipping AppSearch action: already done in UI sequence\n");
1128 return ERROR_SUCCESS;
1129 }
1130 else
1131 msi_register_unique_action(package, szAppSearch);
1132
1133 r = MSI_OpenQuery( package->db, &view, query );
1134 if (r != ERROR_SUCCESS)
1135 return ERROR_SUCCESS;
1136
1137 r = MSI_IterateRecords( view, NULL, iterate_appsearch, package );
1138 msiobj_release( &view->hdr );
1139 return r;
1140 }
1141
1142 static UINT ITERATE_CCPSearch(MSIRECORD *row, LPVOID param)
1143 {
1144 MSIPACKAGE *package = param;
1145 LPCWSTR signature;
1146 LPWSTR value = NULL;
1147 MSISIGNATURE sig;
1148 UINT r = ERROR_SUCCESS;
1149
1150 static const WCHAR success[] = {'C','C','P','_','S','u','c','c','e','s','s',0};
1151
1152 signature = MSI_RecordGetString(row, 1);
1153
1154 TRACE("%s\n", debugstr_w(signature));
1155
1156 ACTION_AppSearchSigName(package, signature, &sig, &value);
1157 if (value)
1158 {
1159 TRACE("Found signature %s\n", debugstr_w(signature));
1160 msi_set_property( package->db, success, szOne, -1 );
1161 msi_free(value);
1162 r = ERROR_NO_MORE_ITEMS;
1163 }
1164
1165 ACTION_FreeSignature(&sig);
1166
1167 return r;
1168 }
1169
1170 UINT ACTION_CCPSearch(MSIPACKAGE *package)
1171 {
1172 static const WCHAR query[] = {
1173 'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
1174 'C','C','P','S','e','a','r','c','h',0};
1175 MSIQUERY *view;
1176 UINT r;
1177
1178 if (msi_action_is_unique(package, szCCPSearch))
1179 {
1180 TRACE("Skipping AppSearch action: already done in UI sequence\n");
1181 return ERROR_SUCCESS;
1182 }
1183 else
1184 msi_register_unique_action(package, szCCPSearch);
1185
1186 r = MSI_OpenQuery(package->db, &view, query);
1187 if (r != ERROR_SUCCESS)
1188 return ERROR_SUCCESS;
1189
1190 r = MSI_IterateRecords(view, NULL, ITERATE_CCPSearch, package);
1191 msiobj_release(&view->hdr);
1192 return r;
1193 }