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