xref: /reactos/dll/win32/msi/appsearch.c (revision f4be6dc3)
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 struct signature
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 };
51 
msi_parse_version_string(LPCWSTR verStr,PDWORD ms,PDWORD ls)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  */
get_signature(MSIPACKAGE * package,struct signature * sig,const WCHAR * name)83 static UINT get_signature( MSIPACKAGE *package, struct signature *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         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         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 */
free_signature(struct signature * sig)151 static void free_signature( struct signature *sig )
152 {
153     free(sig->File);
154     free(sig->Languages);
155 }
156 
search_file(MSIPACKAGE * package,WCHAR * path,struct signature * sig)157 static WCHAR *search_file( MSIPACKAGE *package, WCHAR *path, struct signature *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 wcsdup(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 wcsdup(path);
184 
185     buffer = malloc(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 = wcsdup(path);
217 
218 done:
219     free(buffer);
220     return val;
221 }
222 
search_components(MSIPACKAGE * package,WCHAR ** appValue,struct signature * sig)223 static UINT search_components( MSIPACKAGE *package, WCHAR **appValue, struct signature *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 = wcsdup(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 = wcsdup(path);
289     }
290 
291 done:
292     if (rec) msiobj_release(&rec->hdr);
293     msiobj_release(&row->hdr);
294     return ERROR_SUCCESS;
295 }
296 
convert_reg_value(DWORD regType,const BYTE * value,DWORD sz,WCHAR ** appValue)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 = malloc(sz + sizeof(WCHAR));
309                 (*appValue)[0] = '#';
310                 lstrcpyW(*appValue + 1, (LPCWSTR)value);
311             }
312             else
313             {
314                 *appValue = malloc(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 = malloc(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 = malloc(sz * sizeof(WCHAR));
328             ExpandEnvironmentStringsW((LPCWSTR)value, *appValue, sz);
329             break;
330         case REG_BINARY:
331             /* #x<nibbles>\0 */
332             *appValue = malloc((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 *, struct signature *, const WCHAR *, int, WCHAR ** );
345 
search_reg(MSIPACKAGE * package,WCHAR ** appValue,struct signature * sig)346 static UINT search_reg( MSIPACKAGE *package, WCHAR **appValue, struct signature *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     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 = malloc(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             WCHAR *buf = malloc(sz * sizeof(WCHAR));
435             ExpandEnvironmentStringsW((LPCWSTR)value, buf, sz);
436             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     free( value );
464     RegCloseKey( key );
465     free( deformatted );
466 
467     msiobj_release(&row->hdr);
468     return ERROR_SUCCESS;
469 }
470 
get_ini_field(LPWSTR buf,int field)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 wcsdup(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 wcsdup(beg);
495 }
496 
search_ini(MSIPACKAGE * package,WCHAR ** appValue,struct signature * sig)497 static UINT search_ini( MSIPACKAGE *package, WCHAR **appValue, struct signature *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     free(fileName);
543     free(section);
544     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  */
expand_any_path(MSIPACKAGE * package,WCHAR * src,WCHAR * dst,size_t len)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         free(deformatted);
582         return;
583     }
584 
585     lstrcpyW(dst, deformatted);
586     dst[lstrlenW(deformatted)] = '\0';
587     free(deformatted);
588 }
589 
parse_languages(const WCHAR * languages,DWORD * num_ids)590 static LANGID *parse_languages( const WCHAR *languages, DWORD *num_ids )
591 {
592     UINT i, count = 1;
593     WCHAR *str = wcsdup( 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 = malloc( count * sizeof(LANGID) )))
600     {
601         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     free( str );
615     *num_ids = count;
616     return ret;
617 }
618 
match_languages(const void * version,const WCHAR * languages)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     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  */
file_version_matches(MSIPACKAGE * package,const struct signature * sig,const WCHAR * filePath,BOOL * matches)655 static UINT file_version_matches( MSIPACKAGE *package, const struct signature *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 = malloc( 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     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  */
file_matches_sig(MSIPACKAGE * package,const struct signature * sig,const WIN32_FIND_DATAW * findData,const WCHAR * fullFilePath,BOOL * matches)715 static UINT file_matches_sig( MSIPACKAGE *package, const struct signature *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  */
recurse_search_directory(MSIPACKAGE * package,WCHAR ** appValue,struct signature * sig,const WCHAR * dir,int depth)760 static UINT recurse_search_directory( MSIPACKAGE *package, WCHAR **appValue, struct signature *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 = malloc(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         free(buf);
843 
844     return rc;
845 }
846 
check_directory(MSIPACKAGE * package,const WCHAR * dir,WCHAR ** appValue)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 = wcsdup(dir);
855     }
856 
857     return ERROR_SUCCESS;
858 }
859 
is_full_path(const WCHAR * path)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 
search_directory(MSIPACKAGE * package,struct signature * sig,const WCHAR * path,int depth,WCHAR ** appValue)874 static UINT search_directory( MSIPACKAGE *package, struct signature *sig, const WCHAR *path, int depth, WCHAR **appValue )
875 {
876     UINT rc;
877     DWORD attr;
878     WCHAR *val = NULL, *new_val;
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         new_val = realloc(val, (wcslen(val) + 2) * sizeof(WCHAR));
924         if (!new_val)
925         {
926             free(val);
927             val = NULL;
928             rc = ERROR_OUTOFMEMORY;
929         }
930         else
931         {
932             val = new_val;
933             PathAddBackslashW(val);
934         }
935     }
936 
937     *appValue = val;
938 
939     TRACE("returning %d\n", rc);
940     return rc;
941 }
942 
943 static UINT search_sig_name( MSIPACKAGE *, const WCHAR *, struct signature *, WCHAR ** );
944 
search_dr(MSIPACKAGE * package,WCHAR ** appValue,struct signature * sig)945 static UINT search_dr( MSIPACKAGE *package, WCHAR **appValue, struct signature *sig )
946 {
947     LPWSTR parent = NULL;
948     LPCWSTR parentName;
949     WCHAR path[MAX_PATH];
950     WCHAR expanded[MAX_PATH];
951     MSIRECORD *row;
952     int depth;
953     DWORD sz, attr;
954     UINT rc;
955 
956     TRACE("%s\n", debugstr_w(sig->Name));
957 
958     *appValue = NULL;
959 
960     row = MSI_QueryGetRecord( package->db, L"SELECT * FROM `DrLocator` WHERE `Signature_` = '%s'", sig->Name );
961     if (!row)
962     {
963         TRACE("failed to query DrLocator for %s\n", debugstr_w(sig->Name));
964         return ERROR_SUCCESS;
965     }
966 
967     /* check whether parent is set */
968     parentName = MSI_RecordGetString(row, 2);
969     if (parentName)
970     {
971         struct signature parentSig;
972 
973         search_sig_name( package, parentName, &parentSig, &parent );
974         free_signature( &parentSig );
975         if (!parent)
976         {
977             msiobj_release(&row->hdr);
978             return ERROR_SUCCESS;
979         }
980     }
981 
982     sz = MAX_PATH;
983     MSI_RecordGetStringW(row, 3, path, &sz);
984 
985     if (MSI_RecordIsNull(row,4))
986         depth = 0;
987     else
988         depth = MSI_RecordGetInteger(row,4);
989 
990     if (sz)
991         expand_any_path( package, path, expanded, MAX_PATH );
992     else
993         lstrcpyW(expanded, path);
994 
995     if (parent)
996     {
997         attr = msi_get_file_attributes( package, parent );
998         if (attr != INVALID_FILE_ATTRIBUTES &&
999             !(attr & FILE_ATTRIBUTE_DIRECTORY))
1000         {
1001             PathRemoveFileSpecW(parent);
1002             PathAddBackslashW(parent);
1003         }
1004 
1005         lstrcpyW(path, parent);
1006         lstrcatW(path, expanded);
1007     }
1008     else if (sz) lstrcpyW(path, expanded);
1009 
1010     PathAddBackslashW(path);
1011 
1012     rc = search_directory( package, sig, path, depth, appValue );
1013 
1014     free(parent);
1015     msiobj_release(&row->hdr);
1016     TRACE("returning %d\n", rc);
1017     return rc;
1018 }
1019 
search_sig_name(MSIPACKAGE * package,const WCHAR * sigName,struct signature * sig,WCHAR ** appValue)1020 static UINT search_sig_name( MSIPACKAGE *package, const WCHAR *sigName, struct signature *sig, WCHAR **appValue )
1021 {
1022     UINT rc;
1023 
1024     *appValue = NULL;
1025     rc = get_signature( package, sig, sigName );
1026     if (rc == ERROR_SUCCESS)
1027     {
1028         rc = search_components( package, appValue, sig );
1029         if (rc == ERROR_SUCCESS && !*appValue)
1030         {
1031             rc = search_reg( package, appValue, sig );
1032             if (rc == ERROR_SUCCESS && !*appValue)
1033             {
1034                 rc = search_ini( package, appValue, sig );
1035                 if (rc == ERROR_SUCCESS && !*appValue)
1036                     rc = search_dr( package, appValue, sig );
1037             }
1038         }
1039     }
1040     return rc;
1041 }
1042 
ITERATE_AppSearch(MSIRECORD * row,LPVOID param)1043 static UINT ITERATE_AppSearch(MSIRECORD *row, LPVOID param)
1044 {
1045     MSIPACKAGE *package = param;
1046     LPCWSTR propName, sigName;
1047     LPWSTR value = NULL;
1048     struct signature sig;
1049     MSIRECORD *uirow;
1050     UINT r;
1051 
1052     /* get property and signature */
1053     propName = MSI_RecordGetString(row, 1);
1054     sigName = MSI_RecordGetString(row, 2);
1055 
1056     TRACE("%s %s\n", debugstr_w(propName), debugstr_w(sigName));
1057 
1058     r = search_sig_name( package, sigName, &sig, &value );
1059     if (value)
1060     {
1061         r = msi_set_property( package->db, propName, value, -1 );
1062         if (r == ERROR_SUCCESS && !wcscmp( propName, L"SourceDir" ))
1063             msi_reset_source_folders( package );
1064 
1065         free(value);
1066     }
1067     free_signature( &sig );
1068 
1069     uirow = MSI_CreateRecord( 2 );
1070     MSI_RecordSetStringW( uirow, 1, propName );
1071     MSI_RecordSetStringW( uirow, 2, sigName );
1072     MSI_ProcessMessage(package, INSTALLMESSAGE_ACTIONDATA, uirow);
1073     msiobj_release( &uirow->hdr );
1074 
1075     return r;
1076 }
1077 
ACTION_AppSearch(MSIPACKAGE * package)1078 UINT ACTION_AppSearch(MSIPACKAGE *package)
1079 {
1080     MSIQUERY *view;
1081     UINT r;
1082 
1083     if (msi_action_is_unique(package, L"AppSearch"))
1084     {
1085         TRACE("Skipping AppSearch action: already done in UI sequence\n");
1086         return ERROR_SUCCESS;
1087     }
1088     else
1089         msi_register_unique_action(package, L"AppSearch");
1090 
1091     r = MSI_OpenQuery( package->db, &view, L"SELECT * FROM `AppSearch`" );
1092     if (r != ERROR_SUCCESS)
1093         return ERROR_SUCCESS;
1094 
1095     r = MSI_IterateRecords( view, NULL, ITERATE_AppSearch, package );
1096     msiobj_release( &view->hdr );
1097     return r;
1098 }
1099 
ITERATE_CCPSearch(MSIRECORD * row,LPVOID param)1100 static UINT ITERATE_CCPSearch(MSIRECORD *row, LPVOID param)
1101 {
1102     MSIPACKAGE *package = param;
1103     LPCWSTR signature;
1104     LPWSTR value = NULL;
1105     struct signature sig;
1106     UINT r = ERROR_SUCCESS;
1107 
1108     signature = MSI_RecordGetString(row, 1);
1109 
1110     TRACE("%s\n", debugstr_w(signature));
1111 
1112     search_sig_name( package, signature, &sig, &value );
1113     if (value)
1114     {
1115         TRACE("Found signature %s\n", debugstr_w(signature));
1116         msi_set_property( package->db, L"CCP_Success", L"1", -1 );
1117         free(value);
1118         r = ERROR_NO_MORE_ITEMS;
1119     }
1120 
1121     free_signature(&sig);
1122     return r;
1123 }
1124 
ACTION_CCPSearch(MSIPACKAGE * package)1125 UINT ACTION_CCPSearch(MSIPACKAGE *package)
1126 {
1127     MSIQUERY *view;
1128     UINT r;
1129 
1130     if (msi_action_is_unique(package, L"CCPSearch"))
1131     {
1132         TRACE("Skipping AppSearch action: already done in UI sequence\n");
1133         return ERROR_SUCCESS;
1134     }
1135     else
1136         msi_register_unique_action(package, L"CCPSearch");
1137 
1138     r = MSI_OpenQuery(package->db, &view, L"SELECT * FROM `CCPSearch`");
1139     if (r != ERROR_SUCCESS)
1140         return ERROR_SUCCESS;
1141 
1142     r = MSI_IterateRecords(view, NULL, ITERATE_CCPSearch, package);
1143     msiobj_release(&view->hdr);
1144     return r;
1145 }
1146