CFBundle_Resources.c
1 /* CFBundle_Resources.c 2 Copyright (c) 1999-2019, Apple Inc. and the Swift project authors 3 4 Portions Copyright (c) 2014-2019, Apple Inc. and the Swift project authors 5 Licensed under Apache License v2.0 with Runtime Library Exception 6 See http://swift.org/LICENSE.txt for license information 7 See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors 8 Responsibility: Tony Parker 9 */ 10 11 #include "CFBundle_Internal.h" 12 #include "CFBundle_SplitFileName.h" 13 #include <CoreFoundation/CFURLAccess.h> 14 #include <CoreFoundation/CFPropertyList.h> 15 #include <CoreFoundation/CFByteOrder.h> 16 #include <CoreFoundation/CFNumber.h> 17 #include <CoreFoundation/CFLocale.h> 18 #include <CoreFoundation/CFPreferences.h> 19 #include <string.h> 20 #include "CFInternal.h" 21 #include <CoreFoundation/CFPriv.h> 22 #include <sys/stat.h> 23 #include <fcntl.h> 24 #include <stdio.h> 25 #include <ctype.h> 26 #include <errno.h> 27 #include <sys/types.h> 28 29 #if (!TARGET_OS_MAC && !TARGET_OS_BSD) || defined(__OpenBSD__) 30 #define strnstr(haystack, needle, size) strstr(haystack, needle) 31 #endif 32 33 #if TARGET_OS_MAC || TARGET_OS_LINUX || TARGET_OS_BSD 34 #include <unistd.h> 35 #if TARGET_OS_MAC || TARGET_OS_BSD 36 #include <sys/sysctl.h> 37 #endif 38 #include <sys/stat.h> 39 #include <dirent.h> 40 #endif 41 42 #if TARGET_OS_WIN32 43 #include <io.h> 44 #include <fcntl.h> 45 #include <sys/stat.h> 46 #include <errno.h> 47 #include <winioctl.h> 48 49 #define close _close 50 #define write _write 51 #define read _read 52 #define open _NS_open 53 #define stat _NS_stat 54 #define fstat _fstat 55 #define mkdir(a,b) _NS_mkdir(a) 56 #define rmdir _NS_rmdir 57 #define unlink _NS_unlink 58 59 #endif 60 61 #pragma mark - 62 #pragma mark Directory Contents and Caches 63 64 // These are here for compatibility, but they do nothing anymore 65 CF_EXPORT void _CFBundleFlushCachesForURL(CFURLRef url) { } 66 CF_EXPORT void _CFBundleFlushCaches(void) { } 67 68 CF_PRIVATE void _CFBundleFlushQueryTableCache(CFBundleRef bundle) { 69 __CFLock(&bundle->_queryLock); 70 if (bundle->_queryTable) { 71 CFDictionaryRemoveAllValues(bundle->_queryTable); 72 } 73 __CFUnlock(&bundle->_queryLock); 74 } 75 76 #pragma mark - 77 #pragma mark Resource URL Lookup 78 79 static Boolean _CFIsResourceCommon(char *path, Boolean *isDir) { 80 Boolean exists; 81 SInt32 mode; 82 if (_CFGetPathProperties(kCFAllocatorSystemDefault, path, &exists, &mode, NULL, NULL, NULL, NULL) == 0) { 83 if (isDir) *isDir = ((exists && ((mode & S_IFMT) == S_IFDIR)) ? true : false); 84 return (exists && (mode & 0444)); 85 } 86 return false; 87 } 88 89 CF_PRIVATE Boolean _CFIsResourceAtURL(CFURLRef url, Boolean *isDir) { 90 char path[CFMaxPathSize]; 91 if (!CFURLGetFileSystemRepresentation(url, true, (uint8_t *)path, CFMaxPathLength)) return false; 92 93 return _CFIsResourceCommon(path, isDir); 94 } 95 96 CF_PRIVATE Boolean _CFIsResourceAtPath(CFStringRef path, Boolean *isDir) { 97 char pathBuf[CFMaxPathSize]; 98 if (!CFStringGetFileSystemRepresentation(path, pathBuf, CFMaxPathSize)) return false; 99 100 return _CFIsResourceCommon(pathBuf, isDir); 101 } 102 103 104 static CFStringRef _CFBundleGetResourceDirForVersion(_CFBundleVersion version) { 105 if (_CFBundleVersionOldStyleSupportFiles == version) { 106 return _CFBundleSupportFilesDirectoryName1WithResources; 107 } else if (_CFBundleVersionContentsResources == version) { 108 return _CFBundleSupportFilesDirectoryName2WithResources; 109 } else if (_CFBundleVersionWrappedContentsResources == version) { 110 return _CFBundleWrappedSupportFilesDirectoryName2WithResources; 111 } else if (_CFBundleVersionWrappedFlat == version) { 112 return _CFBundleWrapperLinkName; 113 } else if (_CFBundleVersionOldStyleResources == version) { 114 return _CFBundleResourcesDirectoryName; 115 } 116 return CFSTR(""); 117 } 118 119 CF_EXPORT CFURLRef CFBundleCopyResourceURL(CFBundleRef bundle, CFStringRef resourceName, CFStringRef resourceType, CFStringRef subDirName) { 120 if (!bundle) return NULL; 121 CFURLRef result = (CFURLRef) _CFBundleCopyFindResources(bundle, NULL, NULL, resourceName, resourceType, subDirName, NULL, false, false, NULL); 122 return result; 123 } 124 125 CF_EXPORT CFArrayRef CFBundleCopyResourceURLsOfType(CFBundleRef bundle, CFStringRef resourceType, CFStringRef subDirName) { 126 if (!bundle) return CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks); 127 CFArrayRef result = (CFArrayRef) _CFBundleCopyFindResources(bundle, NULL, NULL, NULL, resourceType, subDirName, NULL, true, false, NULL); 128 return result; 129 } 130 131 CF_EXPORT CFURLRef _CFBundleCopyResourceURLForLanguage(CFBundleRef bundle, CFStringRef resourceName, CFStringRef resourceType, CFStringRef subDirName, CFStringRef language) { 132 return CFBundleCopyResourceURLForLocalization(bundle, resourceName, resourceType, subDirName, language); 133 } 134 135 CF_EXPORT CFURLRef CFBundleCopyResourceURLForLocalization(CFBundleRef bundle, CFStringRef resourceName, CFStringRef resourceType, CFStringRef subDirName, CFStringRef localizationName) { 136 if (!bundle) return NULL; 137 CFURLRef result = (CFURLRef) _CFBundleCopyFindResources(bundle, NULL, NULL, resourceName, resourceType, subDirName, localizationName, false, true, NULL); 138 return result; 139 } 140 141 CF_EXPORT CFArrayRef _CFBundleCopyResourceURLsOfTypeForLanguage(CFBundleRef bundle, CFStringRef resourceType, CFStringRef subDirName, CFStringRef language) { 142 return CFBundleCopyResourceURLsOfTypeForLocalization(bundle, resourceType, subDirName, language); 143 } 144 145 CF_EXPORT CFArrayRef CFBundleCopyResourceURLsOfTypeForLocalization(CFBundleRef bundle, CFStringRef resourceType, CFStringRef subDirName, CFStringRef localizationName) { 146 if (!bundle) return CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks); 147 CFArrayRef result = (CFArrayRef) _CFBundleCopyFindResources(bundle, NULL, NULL, NULL, resourceType, subDirName, localizationName, true, true, NULL); 148 return result; 149 } 150 151 CF_EXPORT CFURLRef CFBundleCopyResourceURLInDirectory(CFURLRef bundleURL, CFStringRef resourceName, CFStringRef resourceType, CFStringRef subDirName) { 152 CFURLRef result = NULL; 153 unsigned char buff[CFMaxPathSize]; 154 CFURLRef newURL = NULL; 155 156 if (!CFURLGetFileSystemRepresentation(bundleURL, true, buff, CFMaxPathSize)) return NULL; 157 158 newURL = CFURLCreateFromFileSystemRepresentation(kCFAllocatorSystemDefault, buff, strlen((char *)buff), true); 159 if (!newURL) newURL = (CFURLRef)CFRetain(bundleURL); 160 if (_CFBundleCouldBeBundle(newURL)) { 161 result = (CFURLRef) _CFBundleCopyFindResources(NULL, bundleURL, NULL, resourceName, resourceType, subDirName, NULL, false, false, NULL); 162 } 163 if (newURL) CFRelease(newURL); 164 return result; 165 } 166 167 CF_EXPORT CFArrayRef CFBundleCopyResourceURLsOfTypeInDirectory(CFURLRef bundleURL, CFStringRef resourceType, CFStringRef subDirName) { 168 CFArrayRef array = NULL; 169 unsigned char buff[CFMaxPathSize]; 170 CFURLRef newURL = NULL; 171 172 if (!CFURLGetFileSystemRepresentation(bundleURL, true, buff, CFMaxPathSize)) return NULL; 173 174 newURL = CFURLCreateFromFileSystemRepresentation(kCFAllocatorSystemDefault, buff, strlen((char *)buff), true); 175 if (!newURL) newURL = (CFURLRef)CFRetain(bundleURL); 176 if (_CFBundleCouldBeBundle(newURL)) { 177 array = (CFArrayRef) _CFBundleCopyFindResources(NULL, bundleURL, NULL, NULL, resourceType, subDirName, NULL, true, false, NULL); 178 } 179 if (newURL) CFRelease(newURL); 180 return array; 181 } 182 183 #pragma mark - 184 185 CF_INLINE Boolean _CFBundleURLHasSubDir(CFURLRef url, CFStringRef subDirName) { 186 Boolean isDir = false, result = false; 187 CFURLRef dirURL = CFURLCreateWithString(kCFAllocatorSystemDefault, subDirName, url); 188 if (dirURL) { 189 if (_CFIsResourceAtURL(dirURL, &isDir) && isDir) result = true; 190 CFRelease(dirURL); 191 } 192 return result; 193 } 194 195 #if TARGET_OS_WIN32 196 typedef signed long long ssize_t; 197 static ssize_t readlink(const char * restrict pathname, 198 char * restrict buffer, size_t bufsiz) { 199 ssize_t result = -1; 200 201 WIN32_FILE_ATTRIBUTE_DATA fsa; 202 HANDLE hFile = INVALID_HANDLE_VALUE; 203 REPARSE_DATA_BUFFER *pBuffer; 204 CHAR bBuffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; 205 DWORD dwCount; 206 size_t length; 207 208 if (!GetFileAttributesExA(pathname, GetFileExInfoStandard, &fsa)) 209 goto out; 210 211 if (~fsa.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { 212 result = strncpy(buffer, pathname, bufsiz); 213 goto out; 214 } 215 216 hFile = CreateFileA(pathname, GENERIC_READ, 217 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 218 NULL, OPEN_EXISTING, 219 FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, 220 NULL); 221 if (hFile == INVALID_HANDLE_VALUE) 222 goto out; 223 224 if (!DeviceIoControl(hFile, FSCTL_GET_REPARSE_POINT, NULL, 0, bBuffer, 225 sizeof(bBuffer), &dwCount, NULL)) 226 goto out; 227 228 if (dwCount >= sizeof(bBuffer)) 229 goto out; 230 231 pBuffer = (REPARSE_DATA_BUFFER *)bBuffer; 232 switch (pBuffer->ReparseTag) { 233 case IO_REPARSE_TAG_SYMLINK: 234 result = strncpy(buffer, pBuffer->GenericReparseBuffer.DataBuffer, bufsiz); 235 buffer[min(pBuffer->ReparseDataLength, result)] = '\0'; 236 default: 237 break; 238 } 239 240 out: 241 CloseHandle(hFile); 242 return result; 243 } 244 #endif 245 246 CF_PRIVATE _CFBundleVersion _CFBundleGetBundleVersionForURL(CFURLRef url) { 247 // check for existence of "Resources" or "Contents" or "Support Files" 248 // but check for the most likely one first 249 // version 0: old-style "Resources" bundles 250 // version 1: obsolete "Support Files" bundles 251 // version 2: modern "Contents" bundles 252 // version 3: none of the above (see below) (flat) 253 // version 4: not a bundle (for main bundle only) 254 // version 12: wrapper bundle of "Contents" bundle 255 // version 13: wrapper bundle of "Flat' bundle 256 257 CFURLRef bundleAbsoluteURL = CFURLCopyAbsoluteURL(url); 258 CFStringRef bundlePath = CFURLCopyFileSystemPath(bundleAbsoluteURL, PLATFORM_PATH_STYLE); 259 260 Boolean hasFrameworkSuffix = CFStringHasSuffix(CFURLGetString(url), CFSTR(".framework/")); 261 #if TARGET_OS_WIN32 262 hasFrameworkSuffix = hasFrameworkSuffix || CFStringHasSuffix(CFURLGetString(url), CFSTR(".framework\\")); 263 #endif 264 265 /* 266 #define _CFBundleSupportFilesDirectoryName1 CFSTR("Support Files") 267 #define _CFBundleSupportFilesDirectoryName2 CFSTR("Contents") 268 #define _CFBundleResourcesDirectoryName CFSTR("Resources") 269 #define _CFBundleExecutablesDirectoryName CFSTR("Executables") 270 #define _CFBundleNonLocalizedResourcesDirectoryName CFSTR("Non-localized Resources") 271 */ 272 __block _CFBundleVersion localVersion = _CFBundleVersionFlat; 273 CFIndex resourcesDirectoryLength = CFStringGetLength(_CFBundleResourcesDirectoryName); 274 CFIndex contentsDirectoryLength = CFStringGetLength(_CFBundleSupportFilesDirectoryName2); 275 CFIndex supportFilesDirectoryLength = CFStringGetLength(_CFBundleSupportFilesDirectoryName1); 276 CFIndex wrapperLinkLength = CFStringGetLength(_CFBundleWrapperLinkName); 277 CFIndex wrapperDirLength = CFStringGetLength(_CFBundleWrapperDirectoryName); 278 279 __block Boolean foundResources = false; 280 __block Boolean foundSupportFiles2 = false; 281 __block Boolean foundSupportFiles1 = false; 282 __block Boolean foundAppWrapperLink = false; 283 __block Boolean foundAppWrapperDirectory = false; 284 __block Boolean foundUnknown = false; 285 286 _CFIterateDirectory(bundlePath, false, NULL, ^Boolean (CFStringRef fileName, CFStringRef fileNameWithPrefix, uint8_t fileType) { 287 // We're looking for a few different names, and also some info on if it's a directory or not. 288 // We don't stop looking once we find one of the names. Otherwise we could run into the situation where we have both "Contents" and "Resources" in a framework, and we see Contents first but Resources is more important. 289 if (fileType == DT_DIR || fileType == DT_LNK) { 290 CFIndex fileNameLen = CFStringGetLength(fileName); 291 if (fileNameLen == resourcesDirectoryLength && CFStringCompareWithOptions(fileName, _CFBundleResourcesDirectoryName, CFRangeMake(0, resourcesDirectoryLength), kCFCompareCaseInsensitive) == kCFCompareEqualTo) { 292 foundResources = true; 293 } else if (fileNameLen == contentsDirectoryLength && CFStringCompareWithOptions(fileName, _CFBundleSupportFilesDirectoryName2, CFRangeMake(0, contentsDirectoryLength), kCFCompareCaseInsensitive) == kCFCompareEqualTo) { 294 foundSupportFiles2 = true; 295 } else if (fileNameLen == supportFilesDirectoryLength && CFStringCompareWithOptions(fileName, _CFBundleSupportFilesDirectoryName1, CFRangeMake(0, supportFilesDirectoryLength), kCFCompareCaseInsensitive) == kCFCompareEqualTo) { 296 foundSupportFiles1 = true; 297 } else if (fileNameLen == wrapperDirLength && CFStringCompareWithOptions(fileName, _CFBundleWrapperDirectoryName, CFRangeMake(0, wrapperDirLength), kCFCompareEqualTo) == kCFCompareEqualTo) { 298 foundAppWrapperDirectory = true; 299 } else if (fileType == DT_LNK && fileNameLen == wrapperLinkLength && CFStringCompareWithOptions(fileName, _CFBundleWrapperLinkName, CFRangeMake(0, wrapperLinkLength), kCFCompareEqualTo) == kCFCompareEqualTo) { 300 foundAppWrapperLink = true; 301 } 302 } else if (fileType == DT_UNKNOWN) { 303 // We'll have to do a more expensive check later; readdir couldn't tell us what the kind of a file was. This may mean that we are looking on a network directory. 304 foundUnknown = true; 305 } 306 return true; 307 }); 308 309 // If we are on a network mount (or FAT volume), we need to do an additional check to look for the symlink and directory for wrapped bundles. readdir will give us DT_UNKNOWN. 310 if (foundUnknown && localVersion == _CFBundleVersionFlat) { 311 // Look for wrapper directory 312 if (_CFBundleURLHasSubDir(url, _CFBundleWrapperDirectoryName)) { 313 foundAppWrapperDirectory = true; 314 315 // Look for wrapper link. Just verify something is there. We will verify it's linkiness later. 316 CFURLRef linkURL = CFURLCreateWithString(kCFAllocatorSystemDefault, _CFBundleWrapperLinkName, url); 317 Boolean isDir = false; 318 if (_CFIsResourceAtURL(linkURL, &isDir) && isDir) foundAppWrapperLink = true; 319 CFRelease(linkURL); 320 321 if (foundAppWrapperDirectory && foundAppWrapperLink) { 322 // Reset the unknown flag 323 foundUnknown = false; 324 } 325 } 326 } 327 328 if (foundAppWrapperDirectory && foundAppWrapperLink) { 329 // Default answer is flat until proven otherwise 330 localVersion = _CFBundleVersionFlat; 331 332 // Descend into the wrapper to find out what version it is 333 CFURLRef linkURL = CFURLCreateCopyAppendingPathComponent(kCFAllocatorSystemDefault, bundleAbsoluteURL, _CFBundleWrapperLinkName, true); 334 CFStringRef linkPath = CFURLCopyFileSystemPath(linkURL, PLATFORM_PATH_STYLE); 335 CFRelease(linkURL); 336 337 __block Boolean foundWrappedSupportFiles2 = false; 338 _CFIterateDirectory(linkPath, false, NULL, ^Boolean (CFStringRef fileName, CFStringRef fileNameWithPrefix, uint8_t fileType) { 339 // Only contents and flat directories are supported as wrapped bundles 340 if (fileType == DT_DIR || fileType == DT_LNK) { 341 CFIndex fileNameLen = CFStringGetLength(fileName); 342 if (fileNameLen == contentsDirectoryLength && CFStringCompareWithOptions(fileName, _CFBundleSupportFilesDirectoryName2, CFRangeMake(0, contentsDirectoryLength), kCFCompareCaseInsensitive) == kCFCompareEqualTo) { 343 foundWrappedSupportFiles2 = true; 344 } 345 } 346 return true; 347 }); 348 349 350 351 // 1. extension of bundle must match pointed-to 352 Boolean extensionCheckOk = false; 353 Boolean subdirectoryCheckOk = false; 354 355 char linkPathCString[CFMaxPathSize]; 356 char linkContentsCString[CFMaxPathSize]; 357 char bundlePathCString[CFMaxPathSize]; 358 359 if (CFStringGetFileSystemRepresentation(linkPath, linkPathCString, PATH_MAX) && 360 CFStringGetFileSystemRepresentation(bundlePath, bundlePathCString, PATH_MAX)) { 361 // Leave room for a null terminator 362 ssize_t len = readlink(linkPathCString, linkContentsCString, CFMaxPathLength); 363 // Make sure this is not an absolute link but a relative one 364 if (len < 2 || (len > 1 && linkContentsCString[0] == '/')) { 365 os_log_error(_CFBundleResourceLogger(), "`WrappedBundle` link too short or pointed outside bundle at %{public}@", url); 366 } else { 367 // readlink does not null terminate, so we manually do it here 368 // CFStringGetFileSystemRepresentation does null terminate 369 linkContentsCString[len] = 0; 370 371 const char *extensionOfWrapped = NULL; 372 const char *extensionOfWrapper = NULL; 373 374 const char *lastPeriodInWrapped = strrchr(linkContentsCString, '.'); 375 if (lastPeriodInWrapped) { 376 extensionOfWrapped = lastPeriodInWrapped + 1; // advance past the . 377 } 378 379 const char *lastPeriodInWrapper = strrchr(bundlePathCString, '.'); 380 if (lastPeriodInWrapper) { 381 extensionOfWrapper = lastPeriodInWrapper + 1; // advance past the . 382 } 383 384 if (extensionOfWrapper && extensionOfWrapped) { 385 if (strcmp(extensionOfWrapped, extensionOfWrapper) == 0) { 386 extensionCheckOk = true; 387 } else { 388 os_log_error(_CFBundleResourceLogger(), "Extensions of wrapped bundles did not match at %{public}@", url); 389 } 390 } else if (!extensionOfWrapper && !extensionOfWrapped) { 391 // If they both have no extensions, that is allowed 392 extensionCheckOk = true; 393 } else { 394 // One doesn't have an extension 395 os_log_error(_CFBundleResourceLogger(), "Extensions of wrapped bundles did not match (one missing) at %{public}@", url); 396 } 397 398 // 2. pointed-to must not traverse outside bundle 399 // We check this by making sure that the path of the wrapper bundle is found at the start of the resolved symlink of the wrapped bundle. Also check for links to the same directory. 400 #if TARGET_OS_WIN32 401 int resolvedWrappedBundleFd = _open(linkPathCString, O_RDONLY); 402 int resolvedBundlePathFd = _open(bundlePathCString, O_RDONLY); 403 #else 404 int resolvedWrappedBundleFd = open(linkPathCString, O_RDONLY); 405 int resolvedBundlePathFd = open(bundlePathCString, O_RDONLY); 406 #endif 407 408 if (resolvedWrappedBundleFd > 0 && resolvedBundlePathFd > 0) { 409 char resolvedWrappedBundlePath[PATH_MAX]; 410 char resolvedBundlePath[PATH_MAX]; 411 412 // Get the path for the wrapped bundle and the wrapper bundle here 413 if (_CFGetPathFromFileDescriptor(resolvedWrappedBundleFd, resolvedWrappedBundlePath) && 414 _CFGetPathFromFileDescriptor(resolvedBundlePathFd, resolvedBundlePath) && 415 strncmp(resolvedWrappedBundlePath, resolvedBundlePath, PATH_MAX) != 0 && 416 strnstr(resolvedWrappedBundlePath, resolvedBundlePath, PATH_MAX) == resolvedWrappedBundlePath) 417 { 418 subdirectoryCheckOk = true; 419 } 420 421 } 422 423 if (resolvedWrappedBundleFd > 0) close(resolvedWrappedBundleFd); 424 if (resolvedBundlePathFd > 0) close(resolvedBundlePathFd); 425 426 if (!subdirectoryCheckOk) { 427 os_log_error(_CFBundleResourceLogger(), "`WrappedBundle` link invalid or pointed outside bundle at %{public}@", url); 428 } 429 } 430 } 431 432 CFRelease(linkPath); 433 434 if (extensionCheckOk && subdirectoryCheckOk) { 435 if (foundWrappedSupportFiles2) { 436 localVersion = _CFBundleVersionWrappedContentsResources; 437 } else { 438 localVersion = _CFBundleVersionWrappedFlat; 439 } 440 } 441 442 } else if (hasFrameworkSuffix) { 443 // The order of these if statements is important - the Resources directory presence takes precedence over Contents, and so forth. The order for frameworks is different than other bundles for compatibility reasons. 444 if (foundResources) { 445 localVersion = _CFBundleVersionOldStyleResources; 446 } else if (foundSupportFiles2) { 447 localVersion = _CFBundleVersionContentsResources; 448 } else if (foundSupportFiles1) { 449 localVersion = _CFBundleVersionOldStyleSupportFiles; 450 } 451 } else { 452 // The order of these if statements is important - the Resources directory presence takes precedence over Contents, and so forth. 453 if (foundSupportFiles2) { 454 localVersion = _CFBundleVersionContentsResources; 455 } else if (foundResources) { 456 localVersion = _CFBundleVersionOldStyleResources; 457 } else if (foundSupportFiles1) { 458 localVersion = _CFBundleVersionOldStyleSupportFiles; 459 } 460 } 461 462 #if TARGET_OS_OSX || TARGET_OS_WIN32 463 // Do a more substantial check for the subdirectories that make up version 0/1/2 bundles. These are sometimes symlinks (like in Frameworks) and they would have been missed by our check above. 464 // n.b. that the readdir above may return DT_UNKNOWN, for example, when the directory is on a network mount. 465 if (foundUnknown && localVersion == _CFBundleVersionFlat) { 466 if (hasFrameworkSuffix) { 467 if (_CFBundleURLHasSubDir(url, _CFBundleResourcesURLFromBase0)) localVersion = _CFBundleVersionOldStyleResources; 468 else if (_CFBundleURLHasSubDir(url, _CFBundleSupportFilesURLFromBase2)) localVersion = _CFBundleVersionContentsResources; 469 else if (_CFBundleURLHasSubDir(url, _CFBundleSupportFilesURLFromBase1)) localVersion = _CFBundleVersionOldStyleSupportFiles; 470 } else { 471 if (_CFBundleURLHasSubDir(url, _CFBundleSupportFilesURLFromBase2)) localVersion = _CFBundleVersionContentsResources; 472 else if (_CFBundleURLHasSubDir(url, _CFBundleResourcesURLFromBase0)) localVersion = _CFBundleVersionOldStyleResources; 473 else if (_CFBundleURLHasSubDir(url, _CFBundleSupportFilesURLFromBase1)) localVersion = _CFBundleVersionOldStyleSupportFiles; 474 } 475 } 476 #endif 477 478 CFRelease(bundleAbsoluteURL); 479 CFRelease(bundlePath); 480 return localVersion; 481 } 482 483 #pragma mark - 484 #pragma mark Platforms 485 486 CF_EXPORT CFArrayRef _CFBundleGetSupportedPlatforms(CFBundleRef bundle) { 487 // This function is obsolete 488 return NULL; 489 } 490 491 CF_EXPORT CFStringRef _CFBundleGetCurrentPlatform(void) { 492 #if TARGET_OS_OSX 493 return CFSTR("MacOS"); 494 #elif TARGET_OS_IPHONE 495 return CFSTR("iPhoneOS"); 496 #elif TARGET_OS_WIN32 497 return CFSTR("Windows"); 498 #elif DEPLOYMENT_TARGET_SOLARIS 499 return CFSTR("Solaris"); 500 #elif DEPLOYMENT_TARGET_HPUX 501 return CFSTR("HPUX"); 502 #elif TARGET_OS_LINUX 503 #if TARGET_OS_CYGWIN 504 return CFSTR("Cygwin"); 505 #else 506 return CFSTR("Linux"); 507 #endif 508 #elif TARGET_OS_BSD 509 return CFSTR("FreeBSD"); 510 #else 511 #error Unknown or unspecified DEPLOYMENT_TARGET 512 #endif 513 } 514 515 CF_PRIVATE CFStringRef _CFBundleGetPlatformExecutablesSubdirectoryName(void) { 516 #if TARGET_OS_MAC 517 return CFSTR("MacOS"); 518 #elif TARGET_OS_WIN32 519 return CFSTR("Windows"); 520 #elif DEPLOYMENT_TARGET_SOLARIS 521 return CFSTR("Solaris"); 522 #elif DEPLOYMENT_TARGET_HPUX 523 return CFSTR("HPUX"); 524 #elif TARGET_OS_LINUX 525 #if TARGET_OS_CYGWIN 526 return CFSTR("Cygwin"); 527 #else 528 return CFSTR("Linux"); 529 #endif 530 #elif TARGET_OS_BSD 531 return CFSTR("FreeBSD"); 532 #else 533 #error Unknown or unspecified DEPLOYMENT_TARGET 534 #endif 535 } 536 537 CFArrayRef CFBundleCopyExecutableArchitecturesForURL(CFURLRef url) { 538 CFArrayRef result = NULL; 539 CFBundleRef bundle = CFBundleCreate(kCFAllocatorSystemDefault, url); 540 if (bundle) { 541 result = CFBundleCopyExecutableArchitectures(bundle); 542 CFRelease(bundle); 543 } else { 544 result = _CFBundleCopyArchitecturesForExecutable(url); 545 } 546 return result; 547 } 548 549 #pragma mark - 550 #pragma mark Resource Lookup - Query Table 551 552 static void _CFBundleAddValueForType(CFStringRef type, CFMutableDictionaryRef queryTable, CFMutableDictionaryRef typeDir, CFTypeRef value, CFMutableDictionaryRef addedTypes, Boolean firstLproj) { 553 CFMutableArrayRef tFiles = (CFMutableArrayRef) CFDictionaryGetValue(typeDir, type); 554 if (!tFiles) { 555 CFStringRef key = CFStringCreateWithFormat(kCFAllocatorSystemDefault, NULL, CFSTR("%@.%@"), _CFBundleTypeIndicator, type); 556 tFiles = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks); 557 CFDictionarySetValue(queryTable, key, tFiles); 558 CFDictionarySetValue(typeDir, type, tFiles); 559 CFRelease(tFiles); 560 CFRelease(key); 561 } 562 if (!addedTypes) { 563 CFArrayAppendValue(tFiles, value); 564 } else if (firstLproj) { 565 CFDictionarySetValue(addedTypes, type, type); 566 CFArrayAppendValue(tFiles, value); 567 } else if (!(CFDictionaryGetValue(addedTypes, type))) { 568 CFArrayAppendValue(tFiles, value); 569 } 570 } 571 572 573 static _CFBundleFileVersion _CFBundleCheckFileProductAndPlatform(CFStringRef file, CFRange searchRange, CFStringRef product, CFStringRef platform) 574 { 575 _CFBundleFileVersion version; 576 Boolean foundprod, foundplat; 577 foundplat = foundprod = NO; 578 Boolean wrong = false; 579 580 if (CFStringFindWithOptions(file, CFSTR("~"), searchRange, 0, NULL)) { 581 if (CFStringGetLength(product) != 1) { 582 // todo: really, search the same range again? 583 if (CFStringFindWithOptions(file, product, searchRange, 0, NULL)) { 584 foundprod = YES; 585 } 586 } 587 if (!foundprod) { 588 wrong = _CFBundleSupportedProductName(file, searchRange); 589 } 590 } 591 592 if (!wrong && CFStringFindWithOptions(file, CFSTR("-"), searchRange, 0, NULL)) { 593 if (CFStringFindWithOptions(file, platform, searchRange, 0, NULL)) { 594 foundplat = YES; 595 } 596 if (!foundplat) { 597 wrong = _CFBundleSupportedPlatformName(file, searchRange); 598 } 599 } 600 601 if (wrong) { 602 version = _CFBundleFileVersionUnmatched; 603 } else if (foundplat && foundprod) { 604 version = _CFBundleFileVersionWithProductWithPlatform; 605 } else if (foundplat) { 606 version = _CFBundleFileVersionNoProductWithPlatform; 607 } else if (foundprod) { 608 version = _CFBundleFileVersionWithProductNoPlatform; 609 } else { 610 version = _CFBundleFileVersionNoProductNoPlatform; 611 } 612 return version; 613 } 614 615 static Boolean _CFBundleReadDirectory(CFStringRef pathOfDir, CFStringRef subdirectory, CFMutableArrayRef allFiles, Boolean hasFileAdded, CFMutableDictionaryRef queryTable, CFMutableDictionaryRef typeDir, CFMutableDictionaryRef addedTypes, Boolean firstLproj, CFStringRef lprojName) { 616 617 CFStringRef product = _CFBundleGetProductNameSuffix(); 618 CFStringRef platform = _CFBundleGetPlatformNameSuffix(); 619 620 CFArrayRef stuffToPrefix = NULL; 621 if (lprojName && subdirectory) { 622 CFTypeRef thingsInTheArray[2] = {lprojName, subdirectory}; 623 stuffToPrefix = CFArrayCreate(kCFAllocatorSystemDefault, thingsInTheArray, 2, &kCFTypeArrayCallBacks); 624 } else if (lprojName) { 625 stuffToPrefix = CFArrayCreate(kCFAllocatorSystemDefault, (const void **)&lprojName, 1, &kCFTypeArrayCallBacks); 626 } else if (subdirectory) { 627 stuffToPrefix = CFArrayCreate(kCFAllocatorSystemDefault, (const void **)&subdirectory, 1, &kCFTypeArrayCallBacks); 628 } 629 630 Boolean searchForFallbackProduct = false; 631 632 // If this file is a directory, the path needs to include a trailing slash so we can later create the right kind of CFURL object 633 _CFIterateDirectory(pathOfDir, true, stuffToPrefix, ^Boolean(CFStringRef fileName, CFStringRef pathToFile, uint8_t fileType) { 634 CFStringRef startType = NULL, endType = NULL, noProductOrPlatform = NULL; 635 _CFBundleFileVersion fileVersion; 636 _CFBundleSplitFileName(fileName, &noProductOrPlatform, &endType, &startType, product, platform, searchForFallbackProduct, &fileVersion); 637 638 // put it into all file array 639 if (!hasFileAdded) { 640 CFArrayAppendValue(allFiles, pathToFile); 641 } 642 643 if (startType) { 644 _CFBundleAddValueForType(startType, queryTable, typeDir, pathToFile, addedTypes, firstLproj); 645 } 646 647 if (endType) { 648 _CFBundleAddValueForType(endType, queryTable, typeDir, pathToFile, addedTypes, firstLproj); 649 } 650 651 if (fileVersion == _CFBundleFileVersionNoProductNoPlatform || fileVersion == _CFBundleFileVersionUnmatched) { 652 // No product/no platform, or unmatched files get added directly to the query table. 653 CFStringRef prevPath = (CFStringRef)CFDictionaryGetValue(queryTable, fileName); 654 if (!prevPath) { 655 CFDictionarySetValue(queryTable, fileName, pathToFile); 656 } 657 } else { 658 // If the file has a product or platform extension, we add the full name to the query table so that it may be found using that name. But only if it doesn't already exist. 659 CFStringRef prevPath = (CFStringRef)CFDictionaryGetValue(queryTable, fileName); 660 if (!prevPath) { 661 CFDictionarySetValue(queryTable, fileName, pathToFile); 662 } 663 664 // Then we add the more specific name as well, replacing the existing one if this is a more specific version. 665 if (noProductOrPlatform) { 666 // add the path of the key into the query table 667 prevPath = (CFStringRef) CFDictionaryGetValue(queryTable, noProductOrPlatform); 668 if (!prevPath) { 669 CFDictionarySetValue(queryTable, noProductOrPlatform, pathToFile); 670 } else { 671 if (!lprojName || CFStringHasPrefix(prevPath, lprojName)) { 672 // we need to know the version of existing path to see if we can replace it by the current path 673 CFRange searchRange; 674 if (lprojName) { 675 searchRange.location = CFStringGetLength(lprojName); 676 searchRange.length = CFStringGetLength(prevPath) - searchRange.location; 677 } else { 678 searchRange.location = 0; 679 searchRange.length = CFStringGetLength(prevPath); 680 } 681 _CFBundleFileVersion prevFileVersion = _CFBundleCheckFileProductAndPlatform(prevPath, searchRange, product, platform); 682 switch (prevFileVersion) { 683 case _CFBundleFileVersionNoProductNoPlatform: 684 CFDictionarySetValue(queryTable, noProductOrPlatform, pathToFile); 685 break; 686 case _CFBundleFileVersionWithProductNoPlatform: 687 if (fileVersion == _CFBundleFileVersionWithProductWithPlatform) CFDictionarySetValue(queryTable, noProductOrPlatform, pathToFile); 688 break; 689 case _CFBundleFileVersionNoProductWithPlatform: 690 CFDictionarySetValue(queryTable, noProductOrPlatform, pathToFile); 691 break; 692 default: 693 break; 694 } 695 } 696 } 697 } 698 } 699 700 if (startType) CFRelease(startType); 701 if (endType) CFRelease(endType); 702 if (noProductOrPlatform) CFRelease(noProductOrPlatform); 703 704 return true; 705 }); 706 707 if (stuffToPrefix) CFRelease(stuffToPrefix); 708 709 return true; 710 } 711 712 713 static CFDictionaryRef _createQueryTableAtPath(CFStringRef inPath, CFArrayRef languages, CFStringRef resourcesDirectory, CFStringRef subdirectory) 714 { 715 716 CFMutableDictionaryRef queryTable = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 717 CFMutableArrayRef allFiles = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks); 718 CFMutableDictionaryRef typeDir = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 719 720 CFMutableStringRef path = CFStringCreateMutableCopy(kCFAllocatorSystemDefault, 0, inPath); 721 722 if (resourcesDirectory) { 723 _CFAppendPathComponent2(path, resourcesDirectory); 724 } 725 726 // Record the length of the base path, so we can strip off the stuff we'll be appending later 727 CFIndex basePathLen = CFStringGetLength(path); 728 729 if (subdirectory) { 730 _CFAppendPathComponent2(path, subdirectory); 731 } 732 // read the content in sub dir and put them into query table 733 _CFBundleReadDirectory(path, subdirectory, allFiles, false, queryTable, typeDir, NULL, false, NULL); 734 CFStringDelete(path, CFRangeMake(basePathLen, CFStringGetLength(path) - basePathLen)); // Strip the string back to the base path 735 736 CFIndex numOfAllFiles = CFArrayGetCount(allFiles); 737 738 CFIndex numLprojs = languages ? CFArrayGetCount(languages) : 0; 739 CFMutableDictionaryRef addedTypes = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 740 741 Boolean hasFileAdded = false; 742 Boolean firstLproj = true; 743 744 // First, search lproj for user's chosen language 745 if (numLprojs >= 1) { 746 CFStringRef lprojTarget = (CFStringRef)CFArrayGetValueAtIndex(languages, 0); 747 CFMutableStringRef lprojTargetWithLproj = CFStringCreateMutableCopy(kCFAllocatorSystemDefault, CFStringGetLength(lprojTarget) + 5, lprojTarget); 748 CFStringAppend(lprojTargetWithLproj, _CFBundleLprojExtensionWithDot); 749 _CFAppendPathComponent2(path, lprojTarget); 750 _CFAppendPathExtension2(path, _CFBundleLprojExtension); 751 if (subdirectory) { 752 _CFAppendPathComponent2(path, subdirectory); 753 } 754 _CFBundleReadDirectory(path, subdirectory, allFiles, hasFileAdded, queryTable, typeDir, addedTypes, firstLproj, lprojTargetWithLproj); 755 CFRelease(lprojTargetWithLproj); 756 CFStringDelete(path, CFRangeMake(basePathLen, CFStringGetLength(path) - basePathLen)); // Strip the string back to the base path 757 758 if (!hasFileAdded && numOfAllFiles < CFArrayGetCount(allFiles)) { 759 hasFileAdded = true; 760 } 761 firstLproj = false; 762 } 763 764 // Next, search Base.lproj folder 765 _CFAppendPathComponent2(path, _CFBundleBaseDirectory); 766 _CFAppendPathExtension2(path, _CFBundleLprojExtension); 767 if (subdirectory) { 768 _CFAppendPathComponent2(path, subdirectory); 769 } 770 _CFBundleReadDirectory(path, subdirectory, allFiles, hasFileAdded, queryTable, typeDir, addedTypes, YES, _CFBundleBaseDirectoryWithLproj); 771 CFStringDelete(path, CFRangeMake(basePathLen, CFStringGetLength(path) - basePathLen)); // Strip the string back to the base path 772 773 if (!hasFileAdded && numOfAllFiles < CFArrayGetCount(allFiles)) { 774 hasFileAdded = true; 775 } 776 777 // Finally, search remaining languages (development language first) 778 if (numLprojs >= 2) { 779 // for each lproj we are interested in, read the content and put them into query table 780 for (CFIndex i = 1; i < CFArrayGetCount(languages); i++) { 781 CFStringRef lprojTarget = (CFStringRef) CFArrayGetValueAtIndex(languages, i); 782 CFMutableStringRef lprojTargetWithLproj = CFStringCreateMutableCopy(kCFAllocatorSystemDefault, CFStringGetLength(lprojTarget) + 5, lprojTarget); 783 CFStringAppend(lprojTargetWithLproj, _CFBundleLprojExtensionWithDot); 784 _CFAppendPathComponent2(path, lprojTarget); 785 _CFAppendPathExtension2(path, _CFBundleLprojExtension); 786 if (subdirectory) { 787 _CFAppendPathComponent2(path, subdirectory); 788 } 789 _CFBundleReadDirectory(path, subdirectory, allFiles, hasFileAdded, queryTable, typeDir, addedTypes, false, lprojTargetWithLproj); 790 CFRelease(lprojTargetWithLproj); 791 CFStringDelete(path, CFRangeMake(basePathLen, CFStringGetLength(path) - basePathLen)); // Strip the string back to the base path 792 793 if (!hasFileAdded && numOfAllFiles < CFArrayGetCount(allFiles)) { 794 hasFileAdded = true; 795 } 796 } 797 } 798 799 CFRelease(addedTypes); 800 CFRelease(path); 801 802 // put the array of all files in sub dir to the query table 803 if (CFArrayGetCount(allFiles) > 0) { 804 CFDictionarySetValue(queryTable, _CFBundleAllFiles, allFiles); 805 } 806 807 CFRelease(allFiles); 808 CFRelease(typeDir); 809 810 return queryTable; 811 } 812 813 // caller need to release the table 814 static CFDictionaryRef _copyQueryTable(CFBundleRef bundle, CFURLRef bundleURL, CFArrayRef languages, CFStringRef resourcesDirectory, CFStringRef subdirectory) 815 { 816 CFDictionaryRef subTable = NULL; 817 818 if (bundle && !languages) { 819 languages = _CFBundleCopyLanguageSearchListInBundle(bundle); 820 } else if (languages) { 821 CFRetain(languages); 822 } 823 824 if (bundle) { 825 CFMutableStringRef argDirStr = NULL; 826 if (subdirectory) { 827 argDirStr = CFStringCreateMutableCopy(kCFAllocatorDefault, 0, resourcesDirectory); 828 _CFAppendPathComponent2(argDirStr, subdirectory); 829 } else { 830 argDirStr = (CFMutableStringRef)CFRetain(resourcesDirectory); 831 } 832 833 __CFLock(&bundle->_queryLock); 834 835 // Check if the query table for the given sub dir has been created. The query table itself is initialized lazily. 836 if (!bundle->_queryTable) { 837 bundle->_queryTable = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 838 } else { 839 subTable = (CFDictionaryRef) CFDictionaryGetValue(bundle->_queryTable, argDirStr); 840 } 841 842 if (!subTable) { 843 // create the query table for the given sub dir 844 subTable = _createQueryTableAtPath(bundle->_bundleBasePath, languages, resourcesDirectory, subdirectory); 845 846 CFDictionarySetValue(bundle->_queryTable, argDirStr, subTable); 847 } else { 848 CFRetain(subTable); 849 } 850 __CFUnlock(&bundle->_queryLock); 851 CFRelease(argDirStr); 852 } else { 853 CFURLRef url = CFURLCopyAbsoluteURL(bundleURL); 854 CFStringRef bundlePath = CFURLCopyFileSystemPath(url, PLATFORM_PATH_STYLE); 855 CFRelease(url); 856 subTable = _createQueryTableAtPath(bundlePath, languages, resourcesDirectory, subdirectory); 857 CFRelease(bundlePath); 858 } 859 860 if (languages) CFRelease(languages); 861 862 // Callers assume return value is non-null 863 if (!subTable) subTable = CFDictionaryCreate(kCFAllocatorSystemDefault, NULL, NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 864 return subTable; 865 } 866 867 static CFURLRef _CFBundleCreateRelativeURLFromBaseAndPath(CFStringRef path, CFURLRef base, UniChar slash, CFStringRef slashStr) 868 { 869 CFURLRef url = NULL; 870 CFRange resultRange; 871 Boolean needToRelease = false; 872 if (CFStringFindWithOptions(path, slashStr, CFRangeMake(0, CFStringGetLength(path)-1), kCFCompareBackwards, &resultRange)) { 873 CFStringRef subPathCom = CFStringCreateWithSubstring(kCFAllocatorSystemDefault, path, CFRangeMake(0, resultRange.location)); 874 base = CFURLCreateCopyAppendingPathComponent(kCFAllocatorSystemDefault, base, subPathCom, YES); 875 path = CFStringCreateWithSubstring(kCFAllocatorSystemDefault, path, CFRangeMake(resultRange.location+1, CFStringGetLength(path)-resultRange.location-1)); 876 CFRelease(subPathCom); 877 needToRelease = true; 878 } 879 if (CFStringGetCharacterAtIndex(path, CFStringGetLength(path)-1) == slash) { 880 url = (CFURLRef)CFURLCreateWithFileSystemPathRelativeToBase(kCFAllocatorSystemDefault, path, PLATFORM_PATH_STYLE, true, base); 881 } else { 882 url = (CFURLRef)CFURLCreateWithFileSystemPathRelativeToBase(kCFAllocatorSystemDefault, path, PLATFORM_PATH_STYLE, false, base); 883 } 884 if (needToRelease) { 885 CFRelease(base); 886 CFRelease(path); 887 } 888 return url; 889 } 890 891 static void _CFBundleFindResourcesWithPredicate(CFMutableArrayRef interResult, CFDictionaryRef queryTable, Boolean (^predicate)(CFStringRef filename, Boolean *stop), Boolean *stop) 892 { 893 CFIndex dictSize = CFDictionaryGetCount(queryTable); 894 if (dictSize == 0) { 895 return; 896 } 897 CFTypeRef *keys = (CFTypeRef *)malloc(sizeof(CFTypeRef) * dictSize); 898 CFTypeRef *values = (CFTypeRef *)malloc(sizeof(CFTypeRef) * dictSize); 899 if (!keys || !values) { 900 if (keys) free(keys); 901 if (values) free(values); 902 return; 903 } 904 905 CFDictionaryGetKeysAndValues(queryTable, keys, values); 906 for (CFIndex i = 0; i < dictSize; i++) { 907 if (predicate((CFStringRef)keys[i], stop)) { 908 if (CFGetTypeID(values[i]) == CFStringGetTypeID()) { 909 CFArrayAppendValue(interResult, values[i]); 910 } else { 911 CFArrayAppendArray(interResult, (CFArrayRef)values[i], CFRangeMake(0, CFArrayGetCount((CFArrayRef)values[i]))); 912 } 913 } 914 915 if (*stop) break; 916 } 917 918 free(keys); 919 free(values); 920 } 921 922 static CFTypeRef _copyResourceURLsFromBundle(CFBundleRef bundle, CFURLRef bundleURL, CFArrayRef bundleURLLanguages, CFStringRef resourcesDirectory, CFStringRef subDir, CFStringRef key, CFStringRef lproj, Boolean returnArray, Boolean localized, _CFBundleVersion bundleVersion, Boolean (^predicate)(CFStringRef filename, Boolean *stop)) 923 { 924 Boolean stop = false; // for predicate 925 CFMutableArrayRef interResult = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks); 926 927 // Be sure that subTable lives as long as value does 928 CFTypeRef value = NULL; 929 CFDictionaryRef subTable = NULL; 930 931 CFMutableStringRef path = CFStringCreateMutableCopy(kCFAllocatorDefault, 0, resourcesDirectory); 932 if (_CFBundleVersionOldStyleSupportFiles == bundleVersion) { 933 CFIndex savedPathLength = CFStringGetLength(path); 934 // add the non-localized resource dir 935 _CFAppendPathComponent2(path, _CFBundleNonLocalizedResourcesDirectoryName); 936 subTable = _copyQueryTable(bundle, bundleURL, bundleURLLanguages, path, subDir); 937 if (predicate) { 938 _CFBundleFindResourcesWithPredicate(interResult, subTable, predicate, &stop); 939 } else { 940 value = CFDictionaryGetValue(subTable, key); 941 } 942 CFStringDelete(path, CFRangeMake(savedPathLength, CFStringGetLength(path) - savedPathLength)); // Strip the string back to the base path 943 } 944 945 if (!value && !stop) { 946 if (subTable) CFRelease(subTable); 947 subTable = _copyQueryTable(bundle, bundleURL, bundleURLLanguages, path, subDir); 948 if (predicate) { 949 _CFBundleFindResourcesWithPredicate(interResult, subTable, predicate, &stop); 950 } else { 951 // get the path or paths for the given key 952 value = CFDictionaryGetValue(subTable, key); 953 } 954 } 955 956 Boolean checkSubDir = subDir && CFStringGetLength(subDir) > 0; 957 Boolean checkLproj = lproj && CFStringGetLength(lproj) > 0; 958 959 // if localization is needed, we filter out the paths for the localization and put the valid ones in the interResult 960 if (localized && value) { 961 962 if (CFGetTypeID(value) == CFStringGetTypeID()){ 963 // We had one result, but since we are going to do a search in a different localization, we will convert the one result into an array of results. 964 value = CFArrayCreate(kCFAllocatorSystemDefault, (const void **)&value, 1, &kCFTypeArrayCallBacks); 965 } else { 966 CFRetain(value); 967 } 968 969 CFRange resultRange, searchRange; 970 CFIndex pathValueLen; 971 CFIndex limit = returnArray ? CFArrayGetCount((CFArrayRef)value) : 1; 972 searchRange.location = 0; 973 for (CFIndex i = 0; i < limit; i++) { 974 CFStringRef pathValue = (CFStringRef) CFArrayGetValueAtIndex((CFArrayRef)value, i); 975 pathValueLen = CFStringGetLength(pathValue); 976 searchRange.length = pathValueLen; 977 978 // if we have subdir, we find the subdir and see if it is after the base path (bundle path + res dir) 979 Boolean searchForLocalization = false; 980 if (checkSubDir) { 981 // Does the subDir appear in the path, and if so, is it not at the start of the string 982 if (CFStringFindWithOptions(pathValue, subDir, searchRange, 0, &resultRange) && resultRange.location != searchRange.location) { 983 searchForLocalization = true; 984 } 985 } else if (!checkSubDir && searchRange.length != 0) { 986 if (CFStringFindWithOptions(pathValue, _CFBundleLprojExtensionWithDot, searchRange, 0, &resultRange) && resultRange.location + 7 < pathValueLen) { 987 searchForLocalization = true; 988 } 989 } 990 991 if (searchForLocalization) { 992 if (!(lproj && CFStringGetLength(lproj) > 0)) { 993 break; 994 } 995 996 if (!(CFStringFindWithOptions(pathValue, lproj, searchRange, kCFCompareAnchored, &resultRange) && CFStringFindWithOptions(pathValue, CFSTR("."), CFRangeMake(resultRange.location + resultRange.length, 1), 0, &resultRange))) { 997 break; 998 } 999 checkLproj = false; 1000 } 1001 1002 CFArrayAppendValue(interResult, pathValue); 1003 } 1004 1005 CFRelease(value); 1006 1007 if (!returnArray && CFArrayGetCount(interResult) != 0) { 1008 checkLproj = false; 1009 } 1010 } else if (value) { 1011 if (CFGetTypeID(value) == CFArrayGetTypeID()) { 1012 CFArrayAppendArray(interResult, (CFArrayRef)value, CFRangeMake(0, CFArrayGetCount((CFArrayRef)value))); 1013 } else { 1014 CFArrayAppendValue(interResult, value); 1015 } 1016 } 1017 1018 value = NULL; 1019 CFRelease(subTable); 1020 1021 // we fetch the result for a given lproj and join them with the nonlocalized result fetched above 1022 if (checkLproj) { 1023 CFMutableStringRef lprojSubdirName = CFStringCreateMutableCopy(kCFAllocatorDefault, 0, lproj); 1024 _CFAppendPathExtension2(lprojSubdirName, _CFBundleLprojExtension); 1025 if (subDir && CFStringGetLength(subDir) > 0) { 1026 _CFAppendPathComponent2(lprojSubdirName, subDir); 1027 } 1028 subTable = _copyQueryTable(bundle, bundleURL, bundleURLLanguages, path, lprojSubdirName); 1029 CFRelease(lprojSubdirName); 1030 value = CFDictionaryGetValue(subTable, key); 1031 1032 if (value) { 1033 if (CFGetTypeID(value) == CFStringGetTypeID()) { 1034 CFArrayAppendValue(interResult, value); 1035 } else { 1036 CFArrayAppendArray(interResult, (CFArrayRef)value, CFRangeMake(0, CFArrayGetCount((CFArrayRef)value))); 1037 } 1038 } 1039 1040 CFRelease(subTable); 1041 } 1042 1043 // after getting paths, we create urls from the paths 1044 CFTypeRef result = NULL; 1045 if (CFArrayGetCount(interResult) > 0) { 1046 UniChar slash = _CFGetSlash(); 1047 CFMutableStringRef urlStr = NULL; 1048 if (bundle) { 1049 urlStr = CFStringCreateMutableCopy(kCFAllocatorSystemDefault, 0, bundle->_bundleBasePath); 1050 } else { 1051 CFURLRef url = CFURLCopyAbsoluteURL(bundleURL); 1052 CFStringRef bundlePath = CFURLCopyFileSystemPath(url, PLATFORM_PATH_STYLE); 1053 urlStr = CFStringCreateMutableCopy(kCFAllocatorSystemDefault, 0, bundlePath); 1054 CFRelease(url); 1055 CFRelease(bundlePath); 1056 } 1057 1058 if (resourcesDirectory && CFStringGetLength(resourcesDirectory)) { 1059 _CFAppendPathComponent2(urlStr, resourcesDirectory); 1060 } 1061 1062 _CFAppendTrailingPathSlash2(urlStr); 1063 1064 if (!returnArray) { 1065 Boolean isOnlyTypeOrAllFiles = CFStringHasPrefix(key, _CFBundleTypeIndicator); 1066 isOnlyTypeOrAllFiles |= CFStringHasPrefix(key, _CFBundleAllFiles); 1067 1068 // Only one result was requested. Just take the first one (index 0). 1069 CFStringRef resultPath = (CFStringRef)CFArrayGetValueAtIndex((CFArrayRef)interResult, 0); 1070 1071 if (!isOnlyTypeOrAllFiles) { 1072 CFStringAppend(urlStr, resultPath); 1073 if (CFStringGetCharacterAtIndex(resultPath, CFStringGetLength(resultPath)-1) == slash) { 1074 result = (CFURLRef)CFURLCreateWithFileSystemPath(kCFAllocatorSystemDefault, urlStr, PLATFORM_PATH_STYLE, true); 1075 } else { 1076 result = (CFURLRef)CFURLCreateWithFileSystemPath(kCFAllocatorSystemDefault, urlStr, PLATFORM_PATH_STYLE, false); 1077 } 1078 } else { 1079 // need to create relative URLs for binary compatibility issues 1080 CFURLRef base = CFURLCreateWithFileSystemPath(kCFAllocatorSystemDefault, urlStr, PLATFORM_PATH_STYLE, true); 1081 result = (CFURLRef)_CFBundleCreateRelativeURLFromBaseAndPath(resultPath, base, slash, _CFGetSlashStr()); 1082 CFRelease(base); 1083 } 1084 } else { 1085 // need to create relative URLs for binary compatibility issues 1086 CFIndex numOfPaths = CFArrayGetCount((CFArrayRef)interResult); 1087 CFURLRef base = CFURLCreateWithFileSystemPath(kCFAllocatorSystemDefault, urlStr, PLATFORM_PATH_STYLE, true); 1088 CFMutableArrayRef urls = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks); 1089 for (CFIndex i = 0; i < numOfPaths; i++) { 1090 CFStringRef path = (CFStringRef)CFArrayGetValueAtIndex((CFArrayRef)interResult, i); 1091 CFURLRef url = _CFBundleCreateRelativeURLFromBaseAndPath(path, base, slash, _CFGetSlashStr()); 1092 CFArrayAppendValue(urls, url); 1093 CFRelease(url); 1094 } 1095 result = urls; 1096 CFRelease(base); 1097 } 1098 CFRelease(urlStr); 1099 } else if (returnArray) { 1100 result = CFRetain(interResult); 1101 } 1102 if (path) CFRelease(path); 1103 CFRelease(interResult); 1104 return result; 1105 } 1106 1107 #pragma mark - 1108 1109 1110 // This is the main entry point for all resource lookup. 1111 // Research shows that by far the most common scenario is to pass in a bundle object, a resource name, and a resource type, using the default localization. 1112 // It is probably the case that more than a few resources will be looked up, making the cost of a readdir less than repeated stats. But it is a relative waste of memory to create strings for every file name in the bundle, especially since those are not what are returned to the caller (URLs are). So, an idea: cache the existence of the most common file names (Info.plist, en.lproj, etc) instead of creating entries for them. If other resources are requested, then go ahead and do the readdir and cache the rest of the file names. 1113 // Another idea: if you want caching, you should create a bundle object. Otherwise we'll happily readdir each time. 1114 CF_EXPORT CFTypeRef _Nullable _CFBundleCopyFindResources(CFBundleRef _Nullable bundle, CFURLRef _Nullable bundleURL, CFArrayRef _Nullable _unused_pass_null_, CFStringRef _Nullable resourceName, CFStringRef _Nullable resourceType, CFStringRef _Nullable subPath, CFStringRef _Nullable lproj, Boolean returnArray, Boolean localized, Boolean (^_Nullable predicate)(CFStringRef filename, Boolean *stop)) 1115 { 1116 CF_ASSERT_TYPE_OR_NULL(_kCFRuntimeIDCFBundle, bundle); 1117 CFTypeRef returnValue = NULL; 1118 1119 if ( 1120 subPath) { 1121 int depthLevel = 0; 1122 CFArrayRef subPathComponents = CFStringCreateArrayBySeparatingStrings(kCFAllocatorSystemDefault, subPath, CFSTR("/")); 1123 CFIndex subPathComponentsCount = CFArrayGetCount(subPathComponents); 1124 1125 for (int i = 0; i < subPathComponentsCount; i++) { 1126 CFStringRef comp = CFArrayGetValueAtIndex(subPathComponents, i); 1127 1128 if (i == 0 && (CFStringCompare(comp, CFSTR(""), 0) == kCFCompareEqualTo)) { 1129 continue; 1130 } 1131 1132 if (CFStringCompare(comp, CFSTR("."), 0) == kCFCompareEqualTo) { 1133 continue; 1134 } 1135 1136 if (CFStringCompare(comp, CFSTR(".."), 0) == kCFCompareEqualTo) { 1137 depthLevel--; 1138 } else { 1139 depthLevel++; 1140 } 1141 1142 if(depthLevel < 0) { 1143 break; 1144 } 1145 } 1146 CFRelease(subPathComponents); 1147 1148 if (depthLevel < 0) { 1149 if (returnArray) { 1150 returnValue = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks); 1151 } 1152 return returnValue; 1153 } 1154 } 1155 1156 // Don't use any path info passed into the resource name 1157 CFStringRef realResourceName = NULL; 1158 CFStringRef subPathFromResourceName = NULL; 1159 1160 if (resourceName) { 1161 CFIndex slashLocation = -1; 1162 realResourceName = _CFCreateLastPathComponent(kCFAllocatorSystemDefault, resourceName, &slashLocation); 1163 if (slashLocation > 0) { 1164 // do not include the / 1165 subPathFromResourceName = CFStringCreateWithSubstring(kCFAllocatorSystemDefault, resourceName, CFRangeMake(0, slashLocation)); 1166 } 1167 1168 if (slashLocation > 0 && CFStringGetLength(realResourceName) == 0 && slashLocation == CFStringGetLength(resourceName) - 1) { 1169 // Did we have a name with just a single / at the end? Taking the lastPathComponent will end up with an empty resource name, which is probably not what was expected. 1170 // Reset the name to be just the directory name. 1171 CFRelease(realResourceName); 1172 realResourceName = CFStringCreateCopy(kCFAllocatorSystemDefault, subPathFromResourceName); 1173 } 1174 1175 // Normalize the resource name by converting it to file system representation. Otherwise when we look for the key in our tables, it will not match. 1176 // TODO: remove this in some way to avoid the malloc? 1177 char buff[CFMaxPathSize]; 1178 if (CFStringGetFileSystemRepresentation(realResourceName, buff, CFMaxPathSize)) { 1179 CFRelease(realResourceName); 1180 realResourceName = CFStringCreateWithFileSystemRepresentation(kCFAllocatorSystemDefault, buff); 1181 } 1182 } 1183 1184 CFMutableStringRef key = NULL; 1185 const static UniChar extensionSep = '.'; 1186 1187 if (realResourceName && CFStringGetLength(realResourceName) > 0 && resourceType && CFStringGetLength(resourceType) > 0) { 1188 // Testing shows that using a mutable string here is significantly faster than using the format functions. 1189 key = CFStringCreateMutableCopy(kCFAllocatorSystemDefault, 0, realResourceName); 1190 // Don't re-append a . if the resource name already has one 1191 if (CFStringGetCharacterAtIndex(resourceType, 0) != '.') CFStringAppendCharacters(key, &extensionSep, 1); 1192 CFStringAppend(key, resourceType); 1193 } else if (realResourceName && CFStringGetLength(realResourceName) > 0) { 1194 key = (CFMutableStringRef)CFRetain(realResourceName); 1195 } else if (resourceType && CFStringGetLength(resourceType) > 0) { 1196 key = CFStringCreateMutableCopy(kCFAllocatorSystemDefault, 0, _CFBundleTypeIndicator); 1197 // Don't re-append a . if the resource name already has one 1198 if (CFStringGetCharacterAtIndex(resourceType, 0) != '.') CFStringAppendCharacters(key, &extensionSep, 1); 1199 CFStringAppend(key, resourceType); 1200 } else { 1201 key = (CFMutableStringRef)CFRetain(_CFBundleAllFiles); 1202 } 1203 1204 CFStringRef realSubdirectory = NULL; 1205 1206 bool hasSubPath = subPath && CFStringGetLength(subPath); 1207 bool hasSubPathFromResourceName = subPathFromResourceName && CFStringGetLength(subPathFromResourceName); 1208 1209 if (hasSubPath && !hasSubPathFromResourceName) { 1210 realSubdirectory = (CFStringRef)CFRetain(subPath); 1211 } else if (!hasSubPath && hasSubPathFromResourceName) { 1212 realSubdirectory = (CFStringRef)CFRetain(subPathFromResourceName); 1213 } else if (hasSubPath && hasSubPathFromResourceName) { 1214 // Multiple sub paths - we'll have to concatenate 1215 realSubdirectory = CFStringCreateMutableCopy(kCFAllocatorSystemDefault, 0, subPath); 1216 _CFAppendPathComponent2((CFMutableStringRef)realSubdirectory, subPathFromResourceName); 1217 } 1218 1219 _CFBundleVersion bundleVersion = bundle ? _CFBundleLayoutVersion(bundle) : _CFBundleVersionOldStyleResources; 1220 CFArrayRef bundleURLLanguages = NULL; 1221 if (bundleURL) { 1222 bundleURLLanguages = _CFBundleCopyLanguageSearchListInDirectory(bundleURL, &bundleVersion); 1223 } 1224 1225 CFStringRef resDir = _CFBundleGetResourceDirForVersion(bundleVersion); 1226 1227 // if returnArray is true then this function will always return a CFArrayRef, even if it's empty 1228 returnValue = _copyResourceURLsFromBundle(bundle, bundleURL, bundleURLLanguages, resDir, realSubdirectory, key, lproj, returnArray, localized, bundleVersion, predicate); 1229 1230 // This is a rarely taken path to add additional resources for old-style bundles and a special case (Spotlight) 1231 if ((!returnValue || (CFGetTypeID(returnValue) == CFArrayGetTypeID() && CFArrayGetCount((CFArrayRef)returnValue) == 0)) && (_CFBundleVersionOldStyleResources == bundleVersion || _CFBundleVersionContentsResources == bundleVersion)) { 1232 CFStringRef bundlePath = NULL; 1233 if (bundle) { 1234 bundlePath = bundle->_bundleBasePath; 1235 CFRetain(bundlePath); 1236 } else { 1237 CFURLRef absoluteURL = CFURLCopyAbsoluteURL(bundleURL); 1238 bundlePath = CFURLCopyFileSystemPath(absoluteURL, PLATFORM_PATH_STYLE); 1239 CFRelease(absoluteURL); 1240 } 1241 if ((_CFBundleVersionOldStyleResources == bundleVersion) || CFEqual(CFSTR("/Library/Spotlight"), bundlePath)){ 1242 if (returnValue) CFRelease(returnValue); 1243 if ((bundleVersion == _CFBundleVersionOldStyleResources && realSubdirectory && CFEqual(realSubdirectory, CFSTR("Resources"))) || (bundleVersion == _CFBundleVersionContentsResources && realSubdirectory && CFEqual(realSubdirectory, CFSTR("Contents/Resources")))) { 1244 if (realSubdirectory) CFRelease(realSubdirectory); 1245 realSubdirectory = CFSTR(""); 1246 } else if (bundleVersion == _CFBundleVersionOldStyleResources && realSubdirectory && CFStringGetLength(realSubdirectory) > 10 && CFStringHasPrefix(realSubdirectory, CFSTR("Resources/"))) { 1247 CFStringRef tmpRealSubdirectory = CFStringCreateWithSubstring(kCFAllocatorSystemDefault, realSubdirectory, CFRangeMake(10, CFStringGetLength(realSubdirectory) - 10)); 1248 if (realSubdirectory) CFRelease(realSubdirectory); 1249 realSubdirectory = tmpRealSubdirectory; 1250 } else if (bundleVersion == _CFBundleVersionContentsResources && realSubdirectory && CFStringGetLength(realSubdirectory) > 19 && CFStringHasPrefix(realSubdirectory, CFSTR("Contents/Resources/"))) { 1251 CFStringRef tmpRealSubdirectory = CFStringCreateWithSubstring(kCFAllocatorSystemDefault, realSubdirectory, CFRangeMake(19, CFStringGetLength(realSubdirectory) - 19)); 1252 if (realSubdirectory) CFRelease(realSubdirectory); 1253 realSubdirectory = tmpRealSubdirectory; 1254 } else { 1255 // Assume no resources directory 1256 resDir = CFSTR(""); 1257 } 1258 returnValue = _copyResourceURLsFromBundle(bundle, bundleURL, bundleURLLanguages, resDir, realSubdirectory, key, lproj, returnArray, localized, bundleVersion, predicate); 1259 } 1260 CFRelease(bundlePath); 1261 } 1262 1263 // On-demand resource lookup 1264 // Each bundle can have a list of other CFBundleRef objects that it will do resource lookup in. That means this function is invoked recursively. It should really only be the case that there is one level of recursion, but we do nothing to enforce that. 1265 // We lock around the entire lookup. 1266 if (bundle) { 1267 __CFLock(&bundle->_additionalResourceLock); 1268 if (bundle->_additionalResourceBundles) { 1269 // If we haven't found a value in the main bundle, or if we're looking for an array of values, then look up resources in all additional resource bundles and join them with our result 1270 if (!returnValue || returnArray) { 1271 CFTypeRef *keys; 1272 CFTypeRef *values; 1273 CFIndex count = CFDictionaryGetCount(bundle->_additionalResourceBundles); 1274 keys = malloc(count * sizeof(CFTypeRef)); 1275 values = malloc(count * sizeof(CFTypeRef)); 1276 CFDictionaryGetKeysAndValues(bundle->_additionalResourceBundles, keys, values); 1277 CFMutableArrayRef combinedResultFromOtherBundles = NULL; 1278 1279 for (CFIndex i = 0; i < count; i++) { 1280 CFTypeRef returnValueFromOtherBundle = _CFBundleCopyFindResources((CFBundleRef)(values[i]), NULL, NULL, resourceName, resourceType, subPath, lproj, returnArray, localized, predicate); 1281 if (returnValueFromOtherBundle) { 1282 if (returnArray) { 1283 // Append to our existing array. We'll replace the result after we're done looping. 1284 if (!combinedResultFromOtherBundles) { 1285 // Create the new result by copying returnValue 1286 combinedResultFromOtherBundles = CFArrayCreateMutableCopy(kCFAllocatorSystemDefault, 0, returnValue); 1287 } 1288 1289 CFArrayAppendArray(combinedResultFromOtherBundles, returnValueFromOtherBundle, CFRangeMake(0, CFArrayGetCount(returnValueFromOtherBundle))); 1290 CFRelease(returnValueFromOtherBundle); 1291 } else { 1292 // Set our return value and break out of loop - we only need one result. combinedResultFromOtherBundles should not be touched at this point. 1293 // Don't release the result here, it is the output of this function 1294 returnValue = returnValueFromOtherBundle; 1295 break; 1296 } 1297 } 1298 } 1299 1300 // If we were building up a newResult array, replace the returnValue with it 1301 if (combinedResultFromOtherBundles) { 1302 if (returnValue) { CFRelease(returnValue); } 1303 returnValue = combinedResultFromOtherBundles; 1304 } 1305 1306 free(keys); 1307 free(values); 1308 } 1309 } 1310 __CFUnlock(&bundle->_additionalResourceLock); 1311 } 1312 1313 if (realResourceName) CFRelease(realResourceName); 1314 if (realSubdirectory) CFRelease(realSubdirectory); 1315 if (subPathFromResourceName) CFRelease(subPathFromResourceName); 1316 if (bundleURLLanguages) CFRelease(bundleURLLanguages); 1317 CFRelease(key); 1318 1319 1320 return returnValue; 1321 } 1322 1323 // Note: content must be on disk and pinned before invoking this method 1324 CF_EXPORT Boolean _CFBundleAddResourceURL(CFBundleRef bundle, CFURLRef url) { 1325 CFBundleRef resourceBundle = CFBundleCreate(kCFAllocatorSystemDefault, url); 1326 if (!resourceBundle) return false; 1327 1328 if (resourceBundle == bundle) { 1329 // this shouldn't happen 1330 HALT; 1331 } 1332 1333 __CFLock(&bundle->_additionalResourceLock); 1334 if (!bundle->_additionalResourceBundles) { 1335 bundle->_additionalResourceBundles = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 1336 } 1337 CFDictionarySetValue(bundle->_additionalResourceBundles, url, resourceBundle); 1338 __CFUnlock(&bundle->_additionalResourceLock); 1339 CFRelease(resourceBundle); 1340 return true; 1341 } 1342 1343 // Note: Content must not be unpinned until this method returns 1344 CF_EXPORT Boolean _CFBundleRemoveResourceURL(CFBundleRef bundle, CFURLRef url) { 1345 Boolean result = false; 1346 __CFLock(&bundle->_additionalResourceLock); 1347 if (bundle->_additionalResourceBundles) { 1348 CFDictionaryRemoveValue(bundle->_additionalResourceBundles, url); 1349 result = true; 1350 } 1351 __CFUnlock(&bundle->_additionalResourceLock); 1352 return result; 1353 }