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 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, 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 */ 151 static void free_signature( struct signature *sig ) 152 { 153 free(sig->File); 154 free(sig->Languages); 155 } 156 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 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 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 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, ®Type, 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 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 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 */ 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 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 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 */ 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 */ 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 */ 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 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 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, 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 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 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 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 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 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 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