/ CoreFoundation / PlugIn.subproj / CFBundle_Resources.c
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  }