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