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