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