/ CoreFoundation / PlugIn.subproj / CFBundle_InfoPlist.c
CFBundle_InfoPlist.c
   1  /*      CFBundle_InfoPlist.c
   2  	Copyright (c) 2012-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 <CoreFoundation/CFBundle.h>
  12  #include <CoreFoundation/CFNumber.h>
  13  #include <CoreFoundation/CFError_Private.h>
  14  #include "CFBundle_Internal.h"
  15  #include <CoreFoundation/CFByteOrder.h>
  16  #include <CoreFoundation/CFURLAccess.h>
  17  
  18  #if (TARGET_OS_MAC || TARGET_OS_LINUX || TARGET_OS_BSD) && !TARGET_OS_CYGWIN
  19  #include <dirent.h>
  20  #if TARGET_OS_MAC || TARGET_OS_BSD
  21  #include <sys/sysctl.h>
  22  #endif
  23  #include <sys/mman.h>
  24  #endif
  25  
  26  
  27  #pragma mark -
  28  #pragma mark Product and Platform Getters - Exported
  29  
  30  CF_EXPORT void _CFSetProductName(CFStringRef str) {
  31      // Obsolete, does nothing
  32  }
  33  
  34  CF_EXPORT CFStringRef _CFGetProductName(void) {
  35      static CFStringRef _cfBundlePlatform = NULL;
  36      static dispatch_once_t onceToken;
  37      dispatch_once(&onceToken, ^{
  38  #if TARGET_OS_MAC
  39          // We only honor the classic suffix if it is one of two preset values. Otherwise we fall back to the result of sysctlbyname.
  40          const char *classicSuffix = __CFgetenv("CLASSIC_SUFFIX");
  41          if (classicSuffix && strncmp(classicSuffix, "iphone", strlen("iphone")) == 0) {
  42              os_log_debug(_CFBundleResourceLogger(), "Using ~iphone resources (classic)");
  43              _cfBundlePlatform = _CFBundleiPhoneDeviceName;
  44          } else if (classicSuffix && strncmp(classicSuffix, "ipad", strlen("ipad")) == 0) {
  45              os_log_debug(_CFBundleResourceLogger(), "Using ~ipad resources (classic)");
  46              _cfBundlePlatform = _CFBundleiPadDeviceName;
  47          } else {
  48  #if TARGET_OS_OSX
  49              // Do not check the sysctl on macOS
  50              _cfBundlePlatform = CFSTR("");
  51  #else
  52              char buffer[256];
  53              memset(buffer, 0, sizeof(buffer));
  54              size_t buflen = sizeof(buffer);
  55              int ret = sysctlbyname("hw.machine", buffer, &buflen, NULL, 0);
  56              if (0 == ret || (-1 == ret && ENOMEM == errno)) {
  57  #if TARGET_OS_IOS
  58                  if (6 <= buflen && 0 == memcmp(buffer, "iPhone", 6)) {
  59                      _cfBundlePlatform = _CFBundleiPhoneDeviceName;
  60                  } else
  61                      if (4 <= buflen && 0 == memcmp(buffer, "iPod", 4)) {
  62                          _cfBundlePlatform = _CFBundleiPodDeviceName;
  63                      } else
  64                          if (4 <= buflen && 0 == memcmp(buffer, "iPad", 4)) {
  65                              _cfBundlePlatform = _CFBundleiPadDeviceName;
  66                          }
  67  #elif TARGET_OS_WATCH
  68                  if (5 <= buflen && 0 == memcmp(buffer, "Watch", 5)) {
  69                      _cfBundlePlatform = _CFBundleAppleWatchDeviceName;
  70                  }
  71  #elif TARGET_OS_TV
  72                  if (7 <= buflen && 0 == memcmp(buffer, "AppleTV", 7)) {
  73                      _cfBundlePlatform = _CFBundleAppleTVDeviceName;
  74                  }
  75  #else
  76                  // Fallback path for other TARGET_OS_IPHONE child macros we don't know or care about
  77                  if (false) { }
  78  #endif
  79                  else {
  80                      const char *env = __CFgetenv("SIMULATOR_LEGACY_ASSET_SUFFIX");
  81                      if (env) {
  82                          if (0 == strcmp(env, "iphone")) {
  83                              _cfBundlePlatform = _CFBundleiPhoneDeviceName;
  84                          } else if (0 == strcmp(env, "ipad")) {
  85                              _cfBundlePlatform = _CFBundleiPadDeviceName;
  86                          } else {
  87                              // fallback, unrecognized SIMULATOR_LEGACY_ASSET_SUFFIX
  88                          }
  89                      } else {
  90                          // fallback, unrecognized hw.machine and no SIMULATOR_LEGACY_ASSET_SUFFIX
  91                      }
  92                  }
  93              }
  94  #endif // TARGET_OS_OSX
  95              
  96              os_log_debug(_CFBundleResourceLogger(), "Using ~%@ resources", _cfBundlePlatform);
  97          }
  98  #endif // TARGET_OS_MAC
  99  
 100          // This used to fall back to "iphone" on all unknown TARGET_OS_IPHONE platforms, but since that macro covers a wide swath of platforms, it now falls back to an empty string.
 101          if (!_cfBundlePlatform) {
 102              os_log_debug(_CFBundleResourceLogger(), "Using ~ resources");
 103              _cfBundlePlatform = CFSTR(""); // fallback
 104          }
 105      });
 106      
 107      return _cfBundlePlatform;
 108  }
 109  
 110  CF_PRIVATE CFStringRef _CFBundleGetProductNameSuffix(void) {
 111      static CFStringRef _cfBundlePlatformSuffix = NULL;
 112      static dispatch_once_t onceToken;
 113      dispatch_once(&onceToken, ^{
 114          CFStringRef productName = _CFGetProductName();
 115          if (CFEqual(productName, _CFBundleiPodDeviceName)) {
 116              productName = _CFBundleiPhoneDeviceName;
 117          }
 118          _cfBundlePlatformSuffix = CFStringCreateWithFormat(kCFAllocatorSystemDefault, NULL, CFSTR("~%@"), productName);
 119      });
 120      return _cfBundlePlatformSuffix;
 121  }
 122  
 123  CF_PRIVATE CFStringRef _CFBundleGetPlatformNameSuffix(void) {
 124  #if TARGET_OS_OSX
 125      return _CFBundleMacOSXPlatformNameSuffix;
 126  #elif TARGET_OS_IOS
 127      return _CFBundleiPhoneOSPlatformNameSuffix;
 128  #elif TARGET_OS_WATCH
 129      return _CFBundleWatchOSPlatformNameSuffix;
 130  #elif TARGET_OS_TV
 131      return _CFBundletvOSPlatformNameSuffix;
 132  #elif TARGET_OS_IPHONE
 133      // Fallback path for other TARGET_OS_IPHONE targets we do not know about
 134      return CFSTR("");
 135  #elif TARGET_OS_WIN32
 136      return _CFBundleWindowsPlatformNameSuffix;
 137  #elif DEPLOYMENT_TARGET_SOLARIS
 138      return _CFBundleSolarisPlatformNameSuffix;
 139  #elif DEPLOYMENT_TARGET_HPUX
 140      return _CFBundleHPUXPlatformNameSuffix;
 141  #elif TARGET_OS_LINUX
 142      return _CFBundleLinuxPlatformNameSuffix;
 143  #elif TARGET_OS_BSD
 144      return _CFBundleFreeBSDPlatformNameSuffix;
 145  #else
 146  #error Unknown or unspecified DEPLOYMENT_TARGET
 147  #endif
 148  }
 149  
 150  // All new-style bundles will have these extensions.
 151  CF_EXPORT CFStringRef _CFGetPlatformName(void) {
 152  #if TARGET_OS_OSX
 153      return _CFBundleMacOSXPlatformName;
 154  #elif TARGET_OS_IOS
 155      return _CFBundleiPhoneOSPlatformName;
 156  #elif TARGET_OS_WATCH
 157      return _CFBundleWatchOSPlatformName;
 158  #elif TARGET_OS_TV
 159      return _CFBundletvOSPlatformName;
 160  #elif TARGET_OS_IPHONE
 161      // Fallback path for other TARGET_OS_IPHONE targets we do not know about
 162      return CFSTR("");
 163  #elif TARGET_OS_WIN32
 164      return _CFBundleWindowsPlatformName;
 165  #elif DEPLOYMENT_TARGET_SOLARIS
 166      return _CFBundleSolarisPlatformName;
 167  #elif DEPLOYMENT_TARGET_HPUX
 168      return _CFBundleHPUXPlatformName;
 169  #elif TARGET_OS_LINUX
 170  #if TARGET_OS_CYGWIN
 171      return _CFBundleCygwinPlatformName;
 172  #else
 173      return _CFBundleLinuxPlatformName;
 174  #endif
 175  #elif TARGET_OS_BSD
 176      return _CFBundleFreeBSDPlatformName;
 177  #else
 178  #error Unknown or unspecified DEPLOYMENT_TARGET
 179  #endif
 180  }
 181  
 182  CF_EXPORT CFStringRef _CFGetAlternatePlatformName(void) {
 183  #if TARGET_OS_OSX
 184      return _CFBundleAlternateMacOSXPlatformName;
 185  #elif TARGET_OS_IPHONE
 186      return _CFBundleMacOSXPlatformName;
 187  #elif TARGET_OS_WIN32
 188      return CFSTR("");
 189  #elif TARGET_OS_LINUX
 190  #if TARGET_OS_CYGWIN
 191      return CFSTR("Cygwin");
 192  #else
 193      return CFSTR("Linux");
 194  #endif
 195  #elif TARGET_OS_BSD
 196      return CFSTR("FreeBSD");
 197  #else
 198  #error Unknown or unspecified DEPLOYMENT_TARGET
 199  #endif
 200  }
 201  
 202  #pragma mark -
 203  #pragma mark Product and Platform Suffix Processing - Internal
 204  
 205  // Returns true if the searchRange of the fileName is equal to a valid platform name (e.g., macos, iphoneos).
 206  CF_PRIVATE Boolean _CFBundleSupportedPlatformName(CFStringRef fileName, CFRange searchRange) {
 207  #if TARGET_OS_IOS
 208      return CFStringFindWithOptions(fileName, _CFBundleiPhoneOSPlatformName, searchRange, kCFCompareAnchored, NULL);
 209  #elif TARGET_OS_WATCH
 210      return CFStringFindWithOptions(fileName, _CFBundleWatchOSPlatformName   , searchRange, kCFCompareAnchored, NULL);
 211  #elif TARGET_OS_TV
 212      return CFStringFindWithOptions(fileName, _CFBundletvOSPlatformName, searchRange, kCFCompareAnchored, NULL);
 213  #elif TARGET_OS_OSX
 214      return CFStringFindWithOptions(fileName, _CFBundleMacOSXPlatformName, searchRange, kCFCompareAnchored, NULL);
 215  #else
 216      // This OS supports no platform suffixes
 217      return false;
 218  #endif
 219  }
 220  
 221  // Returns true if the searchRange of the fileName is equal to a a valid product name (e.g., ipod, ipad)
 222  CF_PRIVATE Boolean _CFBundleSupportedProductName(CFStringRef fileName, CFRange searchRange) {
 223  #if TARGET_OS_IOS
 224  #define _CFBundleNumberOfPlatforms 3
 225      static const CFIndex numberOfPlatforms = 3;
 226      static const CFStringRef platforms[numberOfPlatforms] = { CFSTR("iphone"), CFSTR("ipad"), CFSTR("ipod") };
 227      for (CFIndex i = 0; i < numberOfPlatforms; i++) {
 228          if (CFStringFindWithOptions(fileName, platforms[i], searchRange, kCFCompareAnchored, NULL)) {
 229              return true;
 230          }
 231      }
 232      return false;
 233  #elif TARGET_OS_WATCH
 234      return CFStringFindWithOptions(fileName, CFSTR("applewatch"), searchRange, kCFCompareAnchored, NULL);
 235  #elif TARGET_OS_TV
 236      return CFStringFindWithOptions(fileName, CFSTR("appletv"), searchRange, kCFCompareAnchored, NULL);
 237  #elif TARGET_OS_OSX
 238      // MacOS uses an empty string for a product name. We do not distinguish at this time between kinds of Mac products
 239      return false;
 240  #else
 241      // This OS supports no product suffixes
 242      return false;
 243  #endif
 244  }
 245  
 246  static Boolean _isBlacklistedKey(CFStringRef keyName) {
 247  #if __CONSTANT_STRINGS__
 248  #define _CFBundleNumberOfBlacklistedInfoDictionaryKeys 2
 249      static const CFStringRef _CFBundleBlacklistedInfoDictionaryKeys[_CFBundleNumberOfBlacklistedInfoDictionaryKeys] = { CFSTR("CFBundleExecutable"), CFSTR("CFBundleIdentifier") };
 250      
 251      for (CFIndex idx = 0; idx < _CFBundleNumberOfBlacklistedInfoDictionaryKeys; idx++) {
 252          if (CFEqual(keyName, _CFBundleBlacklistedInfoDictionaryKeys[idx])) return true;
 253      }
 254  #endif
 255      return false;
 256  }
 257  
 258  static Boolean _isPlatformAndProductKey(CFStringRef fullKey, Boolean const useFallbackKey, CFStringRef *outBaseKey, CFStringRef *outPlatformSuffix, CFStringRef *outProductSuffix) {
 259      if (outBaseKey) {
 260          *outBaseKey = NULL;
 261      }
 262      if (outPlatformSuffix) {
 263          *outPlatformSuffix = NULL;
 264      }
 265      if (outProductSuffix) {
 266          *outProductSuffix = NULL;
 267      }
 268      if (!fullKey) return false;
 269      CFRange minusRange = CFStringFind(fullKey, CFSTR("-"), kCFCompareBackwards);
 270      CFRange tildeRange = CFStringFind(fullKey, CFSTR("~"), kCFCompareBackwards);
 271      if (minusRange.location == kCFNotFound && tildeRange.location == kCFNotFound) return false;
 272      // minus must come before tilde if both are present
 273      if (minusRange.location != kCFNotFound && tildeRange.location != kCFNotFound && tildeRange.location <= minusRange.location) return false;
 274      
 275      CFIndex strLen = CFStringGetLength(fullKey);
 276      CFRange baseKeyRange = (minusRange.location != kCFNotFound) ? CFRangeMake(0, minusRange.location) : CFRangeMake(0, tildeRange.location);
 277      CFRange platformRange = CFRangeMake(kCFNotFound, 0);
 278      CFRange productRange = CFRangeMake(kCFNotFound, 0);
 279      if (minusRange.location != kCFNotFound) {
 280          platformRange.location = minusRange.location + minusRange.length;
 281          platformRange.length = ((tildeRange.location != kCFNotFound) ? tildeRange.location : strLen) - platformRange.location;
 282      }
 283      if (tildeRange.location != kCFNotFound) {
 284          productRange.location = tildeRange.location + tildeRange.length;
 285          productRange.length = strLen - productRange.location;
 286      }
 287      if (baseKeyRange.length < 1) return false;
 288      if (platformRange.location != kCFNotFound && platformRange.length < 1) return false;
 289      if (productRange.location != kCFNotFound && productRange.length < 1) return false;
 290      
 291      Boolean isValidPlatformAndProduct = true;
 292      if (platformRange.location == kCFNotFound && productRange.location != kCFNotFound) {
 293          // With no platform, only check the product
 294          isValidPlatformAndProduct = _CFBundleSupportedProductName(fullKey, productRange);
 295      } else if (platformRange.location != kCFNotFound && productRange.location == kCFNotFound) {
 296          // With no product, check only the platform
 297          isValidPlatformAndProduct = _CFBundleSupportedPlatformName(fullKey, platformRange);
 298      } else {
 299          // Check both
 300          isValidPlatformAndProduct = _CFBundleSupportedProductName(fullKey, productRange) && _CFBundleSupportedPlatformName(fullKey, platformRange);
 301      }
 302      
 303  
 304      if (isValidPlatformAndProduct) {
 305          if (outBaseKey) {
 306              *outBaseKey = CFStringCreateWithSubstring(kCFAllocatorSystemDefault, fullKey, baseKeyRange);
 307          }
 308          if (outPlatformSuffix) {
 309              CFStringRef platform = (platformRange.location != kCFNotFound) ? CFStringCreateWithSubstring(kCFAllocatorSystemDefault, fullKey, platformRange) : NULL;
 310              *outPlatformSuffix = platform;
 311          }
 312          if (outProductSuffix) {
 313              CFStringRef product = (productRange.location != kCFNotFound) ? CFStringCreateWithSubstring(kCFAllocatorSystemDefault, fullKey, productRange) : NULL;
 314              *outProductSuffix = product;
 315          }
 316      }
 317      return isValidPlatformAndProduct;
 318  }
 319  
 320  
 321  static Boolean _isValidSpecialCase(CFStringRef specialCase) {
 322      // NOTE: Adding any special case to this check must be paired with adding the suffix in __addSuffixesToKeys
 323      return false;
 324  }
 325  
 326  // Special case keys replace base keys in Info.plist and InfoPlist.strings files. They take the form of KeyName#SpecialCase. The special cases are checked in _isValidSpecialCase. If this function returns true then the special case key exists and the replacement behavior should be triggered, according to whatever the criteria are.
 327  static Boolean _isSpecialCaseKey(CFStringRef fullKey, CFStringRef *outBaseKey, CFStringRef *outSpecialCase) {
 328      if (outBaseKey) {
 329          *outBaseKey = NULL;
 330      }
 331      if (outSpecialCase) {
 332          *outSpecialCase = NULL;
 333      }
 334      if (!fullKey) return false;
 335      
 336      CFRange hashRange = CFStringFind(fullKey, CFSTR("#"), kCFCompareBackwards);
 337      if (hashRange.location == kCFNotFound) return false;
 338      CFRange baseKeyRange = CFRangeMake(0, hashRange.location);
 339      if (baseKeyRange.length < 1) return false;
 340      CFIndex strLen = CFStringGetLength(fullKey);
 341      CFIndex specialCaseStart = hashRange.location + hashRange.length;
 342      CFRange specialCaseRange = CFRangeMake(specialCaseStart, strLen - specialCaseStart);
 343      CFStringRef specialCase = CFStringCreateWithSubstring(kCFAllocatorSystemDefault, fullKey, specialCaseRange);
 344      Boolean result = _isValidSpecialCase(specialCase);
 345      
 346      if (result) {
 347          if (outBaseKey) {
 348              *outBaseKey = CFStringCreateWithSubstring(kCFAllocatorSystemDefault, fullKey, baseKeyRange);
 349          }
 350          if (outSpecialCase) {
 351              *outSpecialCase = specialCase;
 352          } else if (specialCase) {
 353              CFRelease(specialCase);
 354          }
 355      } else if (specialCase) {
 356          CFRelease(specialCase);
 357      }
 358      return result;
 359  }
 360  
 361  static Boolean _isCurrentPlatformAndProduct(CFStringRef platform, CFStringRef product) {
 362      if (!platform && !product) return true;
 363      if (!platform) {
 364          return CFEqual(_CFGetProductName(), product);
 365      }
 366      if (!product) {
 367          return CFEqual(_CFGetPlatformName(), platform);
 368      }
 369      
 370      return CFEqual(_CFGetProductName(), product) && CFEqual(_CFGetPlatformName(), platform);
 371  }
 372  
 373  static CFArrayRef _CopySortedOverridesForBaseKey(CFStringRef keyName, CFDictionaryRef dict, Boolean const useFallbackKey) {
 374      CFMutableArrayRef overrides = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
 375      CFStringRef keyNameWithBoth = CFStringCreateWithFormat(kCFAllocatorSystemDefault, NULL, CFSTR("%@-%@~%@"), keyName, _CFGetPlatformName(), _CFGetProductName());
 376      CFStringRef keyNameWithProduct = CFStringCreateWithFormat(kCFAllocatorSystemDefault, NULL, CFSTR("%@~%@"), keyName, _CFGetProductName());
 377      CFStringRef keyNameWithPlatform = CFStringCreateWithFormat(kCFAllocatorSystemDefault, NULL, CFSTR("%@-%@"), keyName, _CFGetPlatformName());
 378      
 379      CFIndex count = CFDictionaryGetCount(dict);
 380      
 381      if (count > 0) {
 382          CFTypeRef *keys = (CFTypeRef *)CFAllocatorAllocate(kCFAllocatorSystemDefault, 2 * count * sizeof(CFTypeRef), 0);
 383          CFTypeRef *values = &(keys[count]);
 384          
 385          CFDictionaryGetKeysAndValues(dict, keys, values);
 386          for (CFIndex idx = 0; idx < count; idx++) {
 387              if (CFEqual(keys[idx], keyNameWithBoth)) {
 388                  CFArrayAppendValue(overrides, keys[idx]);
 389                  break;
 390              }
 391          }
 392          for (CFIndex idx = 0; idx < count; idx++) {
 393              if (CFEqual(keys[idx], keyNameWithProduct)) {
 394                  CFArrayAppendValue(overrides, keys[idx]);
 395                  break;
 396              }
 397          }
 398          for (CFIndex idx = 0; idx < count; idx++) {
 399              if (CFEqual(keys[idx], keyNameWithPlatform)) {
 400                  CFArrayAppendValue(overrides, keys[idx]);
 401                  break;
 402              }
 403          }
 404  
 405          for (CFIndex idx = 0; idx < count; idx++) {
 406              if (CFEqual(keys[idx], keyName)) {
 407                  CFArrayAppendValue(overrides, keys[idx]);
 408                  break;
 409              }
 410          }
 411          
 412          CFAllocatorDeallocate(kCFAllocatorSystemDefault, keys);
 413      }
 414      
 415      CFRelease(keyNameWithProduct);
 416      CFRelease(keyNameWithPlatform);
 417      CFRelease(keyNameWithBoth);
 418      
 419      return overrides;
 420  }
 421  
 422  CF_PRIVATE void _CFBundleInfoPlistProcessInfoDictionary(CFMutableDictionaryRef dict) {
 423      // Defensive programming
 424      if (!dict) return;
 425      
 426      CFIndex count = CFDictionaryGetCount(dict);
 427      
 428      if (count > 0) {
 429          CFTypeRef *keys = (CFTypeRef *)CFAllocatorAllocate(kCFAllocatorSystemDefault, 2 * count * sizeof(CFTypeRef), 0);
 430          CFTypeRef *values = &(keys[count]);
 431          CFMutableArrayRef guard = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
 432          
 433          CFDictionaryGetKeysAndValues(dict, keys, values);
 434          for (CFIndex idx = 0; idx < count; idx++) {
 435              CFStringRef keyPlatformSuffix, keyProductSuffix, keySpecialCaseSuffix, keyName;
 436              CFStringRef key = (CFStringRef)keys[idx];
 437  
 438              // Non-string keys in plists aren't valid so remove them
 439              // if we come across one
 440              if (CFGetTypeID(key) != _kCFRuntimeIDCFString) {
 441                  CFDictionaryRemoveValue(dict, key);
 442                  continue;
 443              }
 444  
 445              Boolean const useFallbackPlatformAndProductKey = false;
 446              if (_isSpecialCaseKey(key, &keyName, &keySpecialCaseSuffix)) {
 447                  // This special case key overrides the base value
 448                  CFDictionarySetValue(dict, keyName, CFDictionaryGetValue(dict, key));
 449                  
 450                  // Remove the special case key
 451                  CFDictionaryRemoveValue(dict, key);
 452                  
 453                  CFRelease(keyName);
 454                  if (keySpecialCaseSuffix) CFRelease(keySpecialCaseSuffix);
 455                  
 456              } else if (_isPlatformAndProductKey(key, useFallbackPlatformAndProductKey, &keyName, &keyPlatformSuffix, &keyProductSuffix)) {
 457                  CFArrayRef keysForBaseKey = NULL;
 458  
 459                  Boolean isSupportedPlatformAndProduct = _isCurrentPlatformAndProduct(keyPlatformSuffix, keyProductSuffix);
 460  
 461  
 462                  if (isSupportedPlatformAndProduct && !_isBlacklistedKey(keyName) && CFDictionaryContainsKey(dict, key)) {
 463                      keysForBaseKey = _CopySortedOverridesForBaseKey(keyName, dict, useFallbackPlatformAndProductKey);
 464                      CFIndex keysForBaseKeyCount = CFArrayGetCount(keysForBaseKey);
 465                      
 466                      //make sure the other keys for this base key don't get released out from under us until we're done
 467                      CFArrayAppendValue(guard, keysForBaseKey); 
 468                      
 469                      //the winner for this base key will be sorted to the front, do the override with it
 470                      CFTypeRef highestPriorityKey = CFArrayGetValueAtIndex(keysForBaseKey, 0);
 471                      CFDictionarySetValue(dict, keyName, CFDictionaryGetValue(dict, highestPriorityKey));
 472                      
 473                      //remove everything except the now-overridden key; this will cause them to fail the CFDictionaryContainsKey(dict, key) check in the enclosing if() and not be reprocessed
 474                      for (CFIndex presentKeysIdx = 0; presentKeysIdx < keysForBaseKeyCount; presentKeysIdx++) {
 475                          CFStringRef currentKey = (CFStringRef)CFArrayGetValueAtIndex(keysForBaseKey, presentKeysIdx);
 476                          if (!CFEqual(currentKey, keyName)) {
 477                              CFDictionaryRemoveValue(dict, currentKey);
 478                          }
 479                      }
 480                  } else {
 481                      CFDictionaryRemoveValue(dict, key);
 482                  }
 483                  
 484                  
 485                  if (keyPlatformSuffix) CFRelease(keyPlatformSuffix);
 486                  if (keyProductSuffix) CFRelease(keyProductSuffix);
 487                  CFRelease(keyName);
 488                  if (keysForBaseKey) CFRelease(keysForBaseKey);
 489              }
 490          }
 491          
 492          CFAllocatorDeallocate(kCFAllocatorSystemDefault, keys);
 493          CFRelease(guard);
 494      }
 495  }
 496  
 497  #pragma mark -
 498  
 499  #define DEVELOPMENT_STAGE 0x20
 500  #define ALPHA_STAGE 0x40
 501  #define BETA_STAGE 0x60
 502  #define RELEASE_STAGE 0x80
 503  
 504  #define MAX_VERS_LEN 10
 505  
 506  CF_INLINE Boolean _isDigit(UniChar aChar) {return ((aChar >= (UniChar)'0' && aChar <= (UniChar)'9') ? true : false);}
 507  
 508  static UInt32 _CFVersionNumberFromString(CFStringRef versStr) {
 509      // Parse version number from string.
 510      // String can begin with "." for major version number 0.  String can end at any point, but elements within the string cannot be skipped.
 511      UInt32 major1 = 0, major2 = 0, minor1 = 0, minor2 = 0, stage = RELEASE_STAGE, build = 0;
 512      UniChar versChars[MAX_VERS_LEN];
 513      UniChar *chars = NULL;
 514      CFIndex len;
 515      UInt32 theVers;
 516      Boolean digitsDone = false;
 517      
 518      if (!versStr) return 0;
 519      len = CFStringGetLength(versStr);
 520      if (len <= 0 || len > MAX_VERS_LEN) return 0;
 521      
 522      CFStringGetCharacters(versStr, CFRangeMake(0, len), versChars);
 523      chars = versChars;
 524      
 525      // Get major version number.
 526      major1 = major2 = 0;
 527      if (_isDigit(*chars)) {
 528          major2 = *chars - (UniChar)'0';
 529          chars++;
 530          len--;
 531          if (len > 0) {
 532              if (_isDigit(*chars)) {
 533                  major1 = major2;
 534                  major2 = *chars - (UniChar)'0';
 535                  chars++;
 536                  len--;
 537                  if (len > 0) {
 538                      if (*chars == (UniChar)'.') {
 539                          chars++;
 540                          len--;
 541                      } else {
 542                          digitsDone = true;
 543                      }
 544                  }
 545              } else if (*chars == (UniChar)'.') {
 546                  chars++;
 547                  len--;
 548              } else {
 549                  digitsDone = true;
 550              }
 551          }
 552      } else if (*chars == (UniChar)'.') {
 553          chars++;
 554          len--;
 555      } else {
 556          digitsDone = true;
 557      }
 558      
 559      // Now major1 and major2 contain first and second digit of the major version number as ints.
 560      // Now either len is 0 or chars points at the first char beyond the first decimal point.
 561      
 562      // Get the first minor version number.
 563      if (len > 0 && !digitsDone) {
 564          if (_isDigit(*chars)) {
 565              minor1 = *chars - (UniChar)'0';
 566              chars++;
 567              len--;
 568              if (len > 0) {
 569                  if (*chars == (UniChar)'.') {
 570                      chars++;
 571                      len--;
 572                  } else {
 573                      digitsDone = true;
 574                  }
 575              }
 576          } else {
 577              digitsDone = true;
 578          }
 579      }
 580      
 581      // Now minor1 contains the first minor version number as an int.
 582      // Now either len is 0 or chars points at the first char beyond the second decimal point.
 583      
 584      // Get the second minor version number.
 585      if (len > 0 && !digitsDone) {
 586          if (_isDigit(*chars)) {
 587              minor2 = *chars - (UniChar)'0';
 588              chars++;
 589              len--;
 590          } else {
 591              digitsDone = true;
 592          }
 593      }
 594      
 595      // Now minor2 contains the second minor version number as an int.
 596      // Now either len is 0 or chars points at the build stage letter.
 597      
 598      // Get the build stage letter.  We must find 'd', 'a', 'b', or 'f' next, if there is anything next.
 599      if (len > 0) {
 600          if (*chars == (UniChar)'d') {
 601              stage = DEVELOPMENT_STAGE;
 602          } else if (*chars == (UniChar)'a') {
 603              stage = ALPHA_STAGE;
 604          } else if (*chars == (UniChar)'b') {
 605              stage = BETA_STAGE;
 606          } else if (*chars == (UniChar)'f') {
 607              stage = RELEASE_STAGE;
 608          } else {
 609              return 0;
 610          }
 611          chars++;
 612          len--;
 613      }
 614      
 615      // Now stage contains the release stage.
 616      // Now either len is 0 or chars points at the build number.
 617      
 618      // Get the first digit of the build number.
 619      if (len > 0) {
 620          if (_isDigit(*chars)) {
 621              build = *chars - (UniChar)'0';
 622              chars++;
 623              len--;
 624          } else {
 625              return 0;
 626          }
 627      }
 628      // Get the second digit of the build number.
 629      if (len > 0) {
 630          if (_isDigit(*chars)) {
 631              build *= 10;
 632              build += *chars - (UniChar)'0';
 633              chars++;
 634              len--;
 635          } else {
 636              return 0;
 637          }
 638      }
 639      // Get the third digit of the build number.
 640      if (len > 0) {
 641          if (_isDigit(*chars)) {
 642              build *= 10;
 643              build += *chars - (UniChar)'0';
 644              chars++;
 645              len--;
 646          } else {
 647              return 0;
 648          }
 649      }
 650      
 651      // Range check the build number and make sure we exhausted the string.
 652      if (build > 0xFF || len > 0) return 0;
 653      
 654      // Build the number
 655      theVers = major1 << 28;
 656      theVers += major2 << 24;
 657      theVers += minor1 << 20;
 658      theVers += minor2 << 16;
 659      theVers += stage << 8;
 660      theVers += build;
 661      
 662      return theVers;
 663  }
 664  
 665  #pragma mark -
 666  #pragma mark Info Plist Functions
 667  
 668  // If infoPlistUrl is passed as non-null it will return retained as the out parameter; callers are responsible for releasing.
 669  static CFDictionaryRef _CFBundleCopyInfoDictionaryInDirectoryWithVersion(CFAllocatorRef alloc, CFURLRef url, CFURLRef * infoPlistUrl, _CFBundleVersion version) {
 670      // We only return NULL for a bad URL, otherwise we create a dummy dictionary
 671      if (!url) return NULL;
 672      
 673      CFDictionaryRef result = NULL;
 674      
 675      // We're going to search for two files here - Info.plist and Info-macos.plist (platform specific). The platform-specific one takes precedence.
 676      // First, construct the URL to the directory we'll search by using the passed in URL as a base
 677      CFStringRef platformInfoURLFromBase = _CFBundlePlatformInfoURLFromBase0;
 678      CFStringRef infoURLFromBase = _CFBundleInfoURLFromBase0;
 679      CFURLRef directoryURL = NULL;
 680      
 681      if (_CFBundleVersionOldStyleResources == version) {
 682          directoryURL = CFURLCreateWithString(kCFAllocatorSystemDefault, _CFBundleResourcesURLFromBase0, url);
 683          platformInfoURLFromBase = _CFBundlePlatformInfoURLFromBase0;
 684          infoURLFromBase = _CFBundleInfoURLFromBase0;
 685      } else if (_CFBundleVersionOldStyleSupportFiles == version) {
 686          directoryURL = CFURLCreateWithString(kCFAllocatorSystemDefault, _CFBundleSupportFilesURLFromBase1, url);
 687          platformInfoURLFromBase = _CFBundlePlatformInfoURLFromBase1;
 688          infoURLFromBase = _CFBundleInfoURLFromBase1;
 689      } else if (_CFBundleVersionContentsResources == version) {
 690          directoryURL = CFURLCreateWithString(kCFAllocatorSystemDefault, _CFBundleSupportFilesURLFromBase2, url);
 691          platformInfoURLFromBase = _CFBundlePlatformInfoURLFromBase2;
 692          infoURLFromBase = _CFBundleInfoURLFromBase2;
 693      } else if (_CFBundleVersionWrappedContentsResources == version) {
 694          directoryURL = CFURLCreateWithString(kCFAllocatorSystemDefault, _CFBundleWrappedSupportFilesURLFromBase2, url);
 695          platformInfoURLFromBase = _CFBundleWrappedPlatformInfoURLFromBase2;
 696          infoURLFromBase = _CFBundleWrappedInfoURLFromBase2;
 697      } else if (_CFBundleVersionWrappedFlat == version) {
 698          directoryURL = CFURLCreateWithString(kCFAllocatorSystemDefault, _CFBundleWrappedSupportFilesURLFromBase3, url);
 699          platformInfoURLFromBase = _CFBundleWrappedPlatformInfoURLFromBase3;
 700          infoURLFromBase = _CFBundleWrappedInfoURLFromBase3;
 701      } else if (_CFBundleVersionFlat == version) {
 702          CFStringRef path = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle);
 703          // this test is necessary to exclude the case where a bundle is spuriously created from the innards of another bundle
 704          if (path) {
 705              if (!(CFStringHasSuffix(path, _CFBundleSupportFilesDirectoryName1) || CFStringHasSuffix(path, _CFBundleSupportFilesDirectoryName2) || CFStringHasSuffix(path, _CFBundleResourcesDirectoryName))) {
 706                  directoryURL = (CFURLRef)CFRetain(url);
 707                  platformInfoURLFromBase = _CFBundlePlatformInfoURLFromBase3;
 708                  infoURLFromBase = _CFBundleInfoURLFromBase3;
 709              }
 710              CFRelease(path);
 711          }
 712      }
 713      
 714      CFURLRef absoluteURL;
 715      if (directoryURL) {
 716          absoluteURL = CFURLCopyAbsoluteURL(directoryURL);
 717          CFStringRef directoryPath = CFURLCopyFileSystemPath(absoluteURL, PLATFORM_PATH_STYLE);
 718          CFRelease(absoluteURL);
 719          
 720          __block CFURLRef localInfoPlistURL = NULL;
 721          __block CFURLRef platformInfoPlistURL = NULL;
 722          
 723          if (directoryPath) {
 724              CFIndex infoPlistLength = CFStringGetLength(_CFBundleInfoPlistName);
 725              CFIndex platformInfoPlistLength = CFStringGetLength(_CFBundlePlatformInfoPlistName);
 726              
 727              // Look inside this directory for the platform-specific and global Info.plist
 728              // For compatibility reasons, we support case-insensitive versions of Info.plist. That means that we must do a search of all the file names in the directory so we can compare. Otherwise, perhaps a couple of stats would be more efficient than the readdir.
 729              _CFIterateDirectory(directoryPath, false, NULL, ^Boolean(CFStringRef fileName, CFStringRef fileNameWithPrefix, uint8_t fileType) {
 730                  // Only do the platform check on platforms where the string is different than the normal one
 731                  if (_CFBundlePlatformInfoPlistName != _CFBundleInfoPlistName) {
 732                      if (!platformInfoPlistURL && CFStringGetLength(fileName) == platformInfoPlistLength && CFStringCompareWithOptions(fileName, _CFBundlePlatformInfoPlistName, CFRangeMake(0, platformInfoPlistLength), kCFCompareCaseInsensitive | kCFCompareAnchored) == kCFCompareEqualTo) {
 733                          // Make a URL out of this file
 734                          platformInfoPlistURL = CFURLCreateWithString(kCFAllocatorSystemDefault, platformInfoURLFromBase, url);
 735                      }
 736                  }
 737                  
 738                  if (!localInfoPlistURL && CFStringGetLength(fileName) == infoPlistLength && CFStringCompareWithOptions(fileName, _CFBundleInfoPlistName, CFRangeMake(0, infoPlistLength), kCFCompareCaseInsensitive | kCFCompareAnchored) == kCFCompareEqualTo) {
 739                      // Make a URL out of this file
 740                      localInfoPlistURL = CFURLCreateWithString(kCFAllocatorSystemDefault, infoURLFromBase, url);
 741                  }
 742                  
 743                  // If by some chance we have both URLs, just bail early (or just the localInfoPlistURL on platforms that have no platform-specific name)
 744                  if (_CFBundlePlatformInfoPlistName != _CFBundleInfoPlistName) {
 745                      if (localInfoPlistURL && platformInfoPlistURL) return false;
 746                  } else {
 747                      if (localInfoPlistURL) return false;
 748                  }
 749                  
 750                  return true;
 751              });
 752              
 753              CFRelease(directoryPath);
 754          }
 755          
 756          CFRelease(directoryURL);
 757          
 758          // Attempt to read in the data from the Info.plist we found - first the platform-specific one.
 759          CFDataRef infoData = NULL;
 760          CFURLRef finalInfoPlistURL = NULL;
 761          if (platformInfoPlistURL) {
 762  #pragma GCC diagnostic push
 763  #pragma GCC diagnostic ignored "-Wdeprecated"
 764              CFURLCreateDataAndPropertiesFromResource(kCFAllocatorSystemDefault, platformInfoPlistURL, &infoData, NULL, NULL, NULL);
 765  #pragma GCC diagnostic pop
 766              if (infoData) finalInfoPlistURL = platformInfoPlistURL;
 767          }
 768          
 769          if (!infoData && localInfoPlistURL) {
 770  #pragma GCC diagnostic push
 771  #pragma GCC diagnostic ignored "-Wdeprecated"
 772              CFURLCreateDataAndPropertiesFromResource(kCFAllocatorSystemDefault, localInfoPlistURL, &infoData, NULL, NULL, NULL);
 773  #pragma GCC diagnostic pop
 774              if (infoData) finalInfoPlistURL = localInfoPlistURL;
 775          }
 776          
 777          if (infoData) {
 778              CFErrorRef error = NULL;
 779              result = (CFDictionaryRef)CFPropertyListCreateWithData(alloc, infoData, kCFPropertyListMutableContainers, NULL, &error);
 780              if (result) {
 781                  if (CFDictionaryGetTypeID() != CFGetTypeID(result)) {
 782                      CFRelease(result);
 783                      result = NULL;
 784                  }
 785              } else if (error) {
 786                  // Avoid calling out from CFError (which can cause infinite recursion) by grabbing some of the vital info and printing it ourselves
 787                  CFStringRef domain = CFErrorGetDomain(error);
 788                  CFIndex code = CFErrorGetCode(error);
 789                   CFLog(kCFLogLevelError, CFSTR("There was an error parsing the Info.plist for the bundle at URL <%p>: %@ - %ld"), localInfoPlistURL, domain, code);
 790                  CFRelease(error);
 791              }
 792              
 793              if (!result) {
 794                  result = CFDictionaryCreateMutable(alloc, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
 795              }
 796              
 797              CFRelease(infoData);
 798          }
 799          
 800          if (infoPlistUrl && finalInfoPlistURL) {
 801              CFRetain(finalInfoPlistURL);
 802              *infoPlistUrl = finalInfoPlistURL;
 803          }
 804          
 805          if (platformInfoPlistURL) CFRelease(platformInfoPlistURL);
 806          if (localInfoPlistURL) CFRelease(localInfoPlistURL);
 807      }
 808      
 809      if (!result) {
 810          result = CFDictionaryCreateMutable(alloc, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
 811      }
 812      
 813      // process ~ipad, ~iphone, etc.
 814      _CFBundleInfoPlistProcessInfoDictionary((CFMutableDictionaryRef)result);
 815      
 816      return result;
 817  }
 818  
 819  CF_PRIVATE CFDictionaryRef _CFBundleCopyInfoDictionaryInDirectory(CFAllocatorRef alloc, CFURLRef url, _CFBundleVersion *version) {
 820      CFDictionaryRef dict = NULL;
 821      unsigned char buff[CFMaxPathSize];
 822      _CFBundleVersion localVersion = _CFBundleVersionOldStyleResources;
 823      
 824      if (CFURLGetFileSystemRepresentation(url, true, buff, CFMaxPathSize)) {
 825          CFURLRef newURL = CFURLCreateFromFileSystemRepresentation(kCFAllocatorSystemDefault, buff, strlen((char *)buff), true);
 826          if (!newURL) newURL = (CFURLRef)CFRetain(url);
 827          
 828          localVersion = _CFBundleGetBundleVersionForURL(newURL);
 829          
 830          dict = _CFBundleCopyInfoDictionaryInDirectoryWithVersion(alloc, newURL, NULL, localVersion);
 831          CFRelease(newURL);
 832      }
 833      if (version) *version = localVersion;
 834      return dict;
 835  }
 836  
 837  CF_EXPORT CFDictionaryRef CFBundleCopyInfoDictionaryForURL(CFURLRef url) {
 838      CFDictionaryRef result = NULL;
 839      Boolean isDir = false;
 840      if (_CFIsResourceAtURL(url, &isDir)) {
 841          if (isDir) {
 842              result = _CFBundleCopyInfoDictionaryInDirectory(kCFAllocatorSystemDefault, url, NULL);
 843          } else {
 844              result = _CFBundleCopyInfoDictionaryInExecutable(url);
 845          }
 846      }
 847      return result;
 848  }
 849  
 850  static Boolean _CFBundleGetPackageInfoInDirectoryWithInfoDictionary(CFAllocatorRef alloc, CFURLRef url, CFDictionaryRef infoDict, UInt32 *packageType, UInt32 *packageCreator) {
 851      Boolean retVal = false, hasType = false, hasCreator = false, releaseInfoDict = false;
 852      CFURLRef tempURL;
 853      CFDataRef pkgInfoData = NULL;
 854      
 855      // Check for a "real" new bundle
 856      tempURL = CFURLCreateWithString(kCFAllocatorSystemDefault, _CFBundlePkgInfoURLFromBase2, url);
 857  #pragma GCC diagnostic push
 858  #pragma GCC diagnostic ignored "-Wdeprecated"
 859      CFURLCreateDataAndPropertiesFromResource(kCFAllocatorSystemDefault, tempURL, &pkgInfoData, NULL, NULL, NULL);
 860  #pragma GCC diagnostic pop
 861      CFRelease(tempURL);
 862      if (!pkgInfoData) {
 863          tempURL = CFURLCreateWithString(kCFAllocatorSystemDefault, _CFBundlePkgInfoURLFromBase1, url);
 864  #pragma GCC diagnostic push
 865  #pragma GCC diagnostic ignored "-Wdeprecated"
 866          CFURLCreateDataAndPropertiesFromResource(kCFAllocatorSystemDefault, tempURL, &pkgInfoData, NULL, NULL, NULL);
 867  #pragma GCC diagnostic pop
 868          CFRelease(tempURL);
 869      }
 870      if (!pkgInfoData) {
 871          // Check for a "pseudo" new bundle
 872          tempURL = CFURLCreateWithString(kCFAllocatorSystemDefault, _CFBundlePseudoPkgInfoURLFromBase, url);
 873  #pragma GCC diagnostic push
 874  #pragma GCC diagnostic ignored "-Wdeprecated"
 875          CFURLCreateDataAndPropertiesFromResource(kCFAllocatorSystemDefault, tempURL, &pkgInfoData, NULL, NULL, NULL);
 876  #pragma GCC diagnostic pop
 877          CFRelease(tempURL);
 878      }
 879      
 880      // Now, either we have a pkgInfoData or not.  If not, then is it because this is a new bundle without one (do we allow this?), or is it dbecause it is an old bundle.
 881      // If we allow new bundles to not have a PkgInfo (because they already have the same data in the Info.plist), then we have to go read the info plist which makes failure expensive.
 882      // drd: So we assume that a new bundle _must_ have a PkgInfo if they have this data at all, otherwise we manufacture it from the extension.
 883      
 884      if (pkgInfoData && CFDataGetLength(pkgInfoData) >= (int)(sizeof(UInt32) * 2)) {
 885          UInt32 *pkgInfo = (UInt32 *)CFDataGetBytePtr(pkgInfoData);
 886          if (packageType) *packageType = CFSwapInt32BigToHost(pkgInfo[0]);
 887          if (packageCreator) *packageCreator = CFSwapInt32BigToHost(pkgInfo[1]);
 888          retVal = hasType = hasCreator = true;
 889      }
 890      if (pkgInfoData) CFRelease(pkgInfoData);
 891      if (!retVal) {
 892          if (!infoDict) {
 893              infoDict = _CFBundleCopyInfoDictionaryInDirectory(kCFAllocatorSystemDefault, url, NULL);
 894              releaseInfoDict = true;
 895          }
 896          if (infoDict) {
 897              CFStringRef typeString = (CFStringRef)CFDictionaryGetValue(infoDict, _kCFBundlePackageTypeKey), creatorString = (CFStringRef)CFDictionaryGetValue(infoDict, _kCFBundleSignatureKey);
 898              UInt32 tmp;
 899              CFIndex usedBufLen = 0;
 900              if (typeString && CFGetTypeID(typeString) == CFStringGetTypeID() && CFStringGetLength(typeString) == 4 && 4 == CFStringGetBytes(typeString, CFRangeMake(0, 4), kCFStringEncodingMacRoman, 0, false, (UInt8 *)&tmp, 4, &usedBufLen) && 4 == usedBufLen) {
 901                  if (packageType) *packageType = CFSwapInt32BigToHost(tmp);
 902                  retVal = hasType = true;
 903              }
 904              if (creatorString && CFGetTypeID(creatorString) == CFStringGetTypeID() && CFStringGetLength(creatorString) == 4 && 4 == CFStringGetBytes(creatorString, CFRangeMake(0, 4), kCFStringEncodingMacRoman, 0, false, (UInt8 *)&tmp, 4, &usedBufLen) && 4 == usedBufLen) {
 905                  if (packageCreator) *packageCreator = CFSwapInt32BigToHost(tmp);
 906                  retVal = hasCreator = true;
 907              }
 908              if (releaseInfoDict) CFRelease(infoDict);
 909          }
 910      }
 911      if (!hasType || !hasCreator) {
 912          // If this looks like a bundle then manufacture the type and creator.
 913          if (retVal || _CFBundleURLLooksLikeBundle(url)) {
 914              if (packageCreator && !hasCreator) *packageCreator = 0x3f3f3f3f;  // '????'
 915              if (packageType && !hasType) {
 916                  // Detect "app", "debug", "profile", or "framework" extensions
 917                  CFURLRef absoluteURL = CFURLCopyAbsoluteURL(url);
 918                  CFStringRef urlStr = CFURLCopyFileSystemPath(absoluteURL, PLATFORM_PATH_STYLE);
 919                  CFRelease(absoluteURL);
 920                  
 921                  if (urlStr) {
 922                      UniChar buff[CFMaxPathSize];
 923                      CFIndex strLen, startOfExtension;
 924                      
 925                      strLen = CFStringGetLength(urlStr);
 926                      if (strLen > CFMaxPathSize) strLen = CFMaxPathSize;
 927                      CFStringGetCharacters(urlStr, CFRangeMake(0, strLen), buff);
 928                      CFRelease(urlStr);
 929                      startOfExtension = _CFStartOfPathExtension(buff, strLen);
 930                      if ((strLen - startOfExtension == 4 || strLen - startOfExtension == 5) && buff[startOfExtension] == (UniChar)'.' && buff[startOfExtension+1] == (UniChar)'a' && buff[startOfExtension+2] == (UniChar)'p' && buff[startOfExtension+3] == (UniChar)'p' && (strLen - startOfExtension == 4 || buff[startOfExtension+4] == (UniChar)PATH_SEP)) {
 931                          // This is an app
 932                          *packageType = 0x4150504c;  // 'APPL'
 933                      } else if ((strLen - startOfExtension == 6 || strLen - startOfExtension == 7) && buff[startOfExtension] == (UniChar)'.' && buff[startOfExtension+1] == (UniChar)'d' && buff[startOfExtension+2] == (UniChar)'e' && buff[startOfExtension+3] == (UniChar)'b' && buff[startOfExtension+4] == (UniChar)'u' && buff[startOfExtension+5] == (UniChar)'g' && (strLen - startOfExtension == 6 || buff[startOfExtension+6] == (UniChar)PATH_SEP)) {
 934                          // This is an app (debug version)
 935                          *packageType = 0x4150504c;  // 'APPL'
 936                      } else if ((strLen - startOfExtension == 8 || strLen - startOfExtension == 9) && buff[startOfExtension] == (UniChar)'.' && buff[startOfExtension+1] == (UniChar)'p' && buff[startOfExtension+2] == (UniChar)'r' && buff[startOfExtension+3] == (UniChar)'o' && buff[startOfExtension+4] == (UniChar)'f' && buff[startOfExtension+5] == (UniChar)'i' && buff[startOfExtension+6] == (UniChar)'l' && buff[startOfExtension+7] == (UniChar)'e' && (strLen - startOfExtension == 8 || buff[startOfExtension+8] == (UniChar)PATH_SEP)) {
 937                          // This is an app (profile version)
 938                          *packageType = 0x4150504c;  // 'APPL'
 939                      } else if ((strLen - startOfExtension == 8 || strLen - startOfExtension == 9) && buff[startOfExtension] == (UniChar)'.' && buff[startOfExtension+1] == (UniChar)'s' && buff[startOfExtension+2] == (UniChar)'e' && buff[startOfExtension+3] == (UniChar)'r' && buff[startOfExtension+4] == (UniChar)'v' && buff[startOfExtension+5] == (UniChar)'i' && buff[startOfExtension+6] == (UniChar)'c' && buff[startOfExtension+7] == (UniChar)'e' && (strLen - startOfExtension == 8 || buff[startOfExtension+8] == (UniChar)PATH_SEP)) {
 940                          // This is a service
 941                          *packageType = 0x4150504c;  // 'APPL'
 942                      } else if ((strLen - startOfExtension == 10 || strLen - startOfExtension == 11) && buff[startOfExtension] == (UniChar)'.' && buff[startOfExtension+1] == (UniChar)'f' && buff[startOfExtension+2] == (UniChar)'r' && buff[startOfExtension+3] == (UniChar)'a' && buff[startOfExtension+4] == (UniChar)'m' && buff[startOfExtension+5] == (UniChar)'e' && buff[startOfExtension+6] == (UniChar)'w' && buff[startOfExtension+7] == (UniChar)'o' && buff[startOfExtension+8] == (UniChar)'r' && buff[startOfExtension+9] == (UniChar)'k' && (strLen - startOfExtension == 10 || buff[startOfExtension+10] == (UniChar)PATH_SEP)) {
 943                          // This is a framework
 944                          *packageType = 0x464d574b;  // 'FMWK'
 945                      } else {
 946                          // Default to BNDL for generic bundle
 947                          *packageType = 0x424e444c;  // 'BNDL'
 948                      }
 949                  } else {
 950                      // Default to BNDL for generic bundle
 951                      *packageType = 0x424e444c;  // 'BNDL'
 952                  }
 953              }
 954              retVal = true;
 955          }
 956      }
 957      return retVal;
 958  }
 959  
 960  CF_EXPORT Boolean _CFBundleGetPackageInfoInDirectory(CFAllocatorRef alloc, CFURLRef url, UInt32 *packageType, UInt32 *packageCreator) {
 961      return _CFBundleGetPackageInfoInDirectoryWithInfoDictionary(alloc, url, NULL, packageType, packageCreator);
 962  }
 963  
 964  CF_EXPORT void CFBundleGetPackageInfo(CFBundleRef bundle, UInt32 *packageType, UInt32 *packageCreator) {
 965      CFURLRef bundleURL = CFBundleCopyBundleURL(bundle);
 966      if (!_CFBundleGetPackageInfoInDirectoryWithInfoDictionary(kCFAllocatorSystemDefault, bundleURL, CFBundleGetInfoDictionary(bundle), packageType, packageCreator)) {
 967          if (packageType) *packageType = 0x424e444c;  // 'BNDL'
 968          if (packageCreator) *packageCreator = 0x3f3f3f3f;  // '????'
 969      }
 970      if (bundleURL) CFRelease(bundleURL);
 971  }
 972  
 973  CF_EXPORT Boolean CFBundleGetPackageInfoInDirectory(CFURLRef url, UInt32 *packageType, UInt32 *packageCreator) {
 974      return _CFBundleGetPackageInfoInDirectory(kCFAllocatorSystemDefault, url, packageType, packageCreator);
 975  }
 976  
 977  CFDictionaryRef CFBundleCopyInfoDictionaryInDirectory(CFURLRef url) {
 978      CFDictionaryRef dict = _CFBundleCopyInfoDictionaryInDirectory(kCFAllocatorSystemDefault, url, NULL);
 979      return dict;
 980  }
 981  
 982  // The Info.plist should NOT be mutated after being created. If there is any fixing up of the info dictionary to do, do it here.
 983  // Call with bundle lock
 984  static void _CFBundleInfoPlistFixupInfoDictionary(CFBundleRef bundle, CFMutableDictionaryRef infoDict) {
 985      // Version number
 986      CFTypeRef unknownVersionValue = CFDictionaryGetValue(infoDict, _kCFBundleNumericVersionKey);
 987      CFNumberRef versNum;
 988      UInt32 vers = 0;
 989      
 990      if (!unknownVersionValue) unknownVersionValue = CFDictionaryGetValue(infoDict, kCFBundleVersionKey);
 991      if (unknownVersionValue) {
 992          if (CFGetTypeID(unknownVersionValue) == CFStringGetTypeID()) {
 993              // Convert a string version number into a numeric one.
 994              vers = _CFVersionNumberFromString((CFStringRef)unknownVersionValue);
 995              
 996              versNum = CFNumberCreate(CFGetAllocator(bundle), kCFNumberSInt32Type, &vers);
 997              CFDictionarySetValue(infoDict, _kCFBundleNumericVersionKey, versNum);
 998              CFRelease(versNum);
 999          } else if (CFGetTypeID(unknownVersionValue) == CFNumberGetTypeID()) {
1000              // Nothing to do here
1001          } else {
1002              CFDictionaryRemoveValue((CFMutableDictionaryRef)infoDict, _kCFBundleNumericVersionKey);
1003          }
1004      }    
1005  }
1006  
1007  CF_PRIVATE void _CFBundleRefreshInfoDictionaryAlreadyLocked(CFBundleRef bundle) {
1008      if (!bundle->_infoDict) {
1009          CFURLRef infoPlistUrl = NULL;
1010          bundle->_infoDict = _CFBundleCopyInfoDictionaryInDirectoryWithVersion(kCFAllocatorSystemDefault, bundle->_url, &infoPlistUrl, bundle->_version);
1011          if (bundle->_infoPlistUrl) {
1012              CFRelease(bundle->_infoPlistUrl);
1013          }
1014          bundle->_infoPlistUrl = infoPlistUrl; // transfered as retained
1015  
1016          // Add or fixup any keys that will be expected later
1017          if (bundle->_infoDict) _CFBundleInfoPlistFixupInfoDictionary(bundle, (CFMutableDictionaryRef)bundle->_infoDict);
1018      }
1019  }
1020  
1021  CFDictionaryRef CFBundleGetInfoDictionary(CFBundleRef bundle) {
1022      CF_ASSERT_TYPE(_kCFRuntimeIDCFBundle, bundle);
1023      __CFLock(&bundle->_lock);
1024      _CFBundleRefreshInfoDictionaryAlreadyLocked(bundle);
1025      __CFUnlock(&bundle->_lock);
1026      return bundle->_infoDict;
1027  }
1028  
1029  CFDictionaryRef _CFBundleGetLocalInfoDictionary(CFBundleRef bundle) {
1030      return CFBundleGetLocalInfoDictionary(bundle);
1031  }
1032  
1033  CFDictionaryRef CFBundleGetLocalInfoDictionary(CFBundleRef bundle) {
1034      CF_ASSERT_TYPE(_kCFRuntimeIDCFBundle, bundle);
1035      CFDictionaryRef localInfoDict = NULL;
1036      __CFLock(&bundle->_lock);
1037      localInfoDict = bundle->_localInfoDict;    
1038      if (!localInfoDict) {
1039          // To avoid keeping the spin lock for too long, let go of it here while we create a new dictionary. We'll relock later to set the value. If it turns out that we have already created another local info dictionary in the meantime, then we'll take care of it then.
1040          __CFUnlock(&bundle->_lock);
1041          CFURLRef url = CFBundleCopyResourceURL(bundle, _CFBundleLocalInfoName, _CFBundleStringTableType, NULL);
1042          if (url) {
1043              CFDataRef data;
1044              SInt32 errCode;
1045              CFStringRef errStr = NULL;
1046              
1047  #pragma GCC diagnostic push
1048  #pragma GCC diagnostic ignored "-Wdeprecated"
1049              if (CFURLCreateDataAndPropertiesFromResource(kCFAllocatorSystemDefault, url, &data, NULL, NULL, &errCode)) {
1050                  localInfoDict = (CFDictionaryRef)CFPropertyListCreateFromXMLData(kCFAllocatorSystemDefault, data, kCFPropertyListMutableContainers, &errStr);
1051                  if (errStr) CFRelease(errStr);
1052                  if (localInfoDict && CFDictionaryGetTypeID() != CFGetTypeID(localInfoDict)) {
1053                      CFRelease(localInfoDict);
1054                      localInfoDict = NULL;
1055                  }
1056                  CFRelease(data);
1057              }
1058  #pragma GCC diagnostic pop
1059              CFRelease(url);
1060          }
1061          if (localInfoDict) _CFBundleInfoPlistProcessInfoDictionary((CFMutableDictionaryRef)localInfoDict);
1062          // remain locked here until we exit the if statement.
1063          __CFLock(&bundle->_lock);
1064          if (!bundle->_localInfoDict) {
1065              // Still have no info dictionary, so set it
1066              bundle->_localInfoDict = localInfoDict;
1067          } else {
1068              // Oops, some other thread created an info dictionary too. We'll just release this one and use that one.
1069              if (localInfoDict) CFRelease(localInfoDict);
1070              localInfoDict = bundle->_localInfoDict;
1071          }
1072      }
1073      __CFUnlock(&bundle->_lock);
1074  
1075      return localInfoDict;
1076  }
1077  
1078  CFPropertyListRef _CFBundleGetValueForInfoKey(CFBundleRef bundle, CFStringRef key) {
1079      return (CFPropertyListRef)CFBundleGetValueForInfoDictionaryKey(bundle, key);
1080  }
1081  
1082  CFTypeRef CFBundleGetValueForInfoDictionaryKey(CFBundleRef bundle, CFStringRef key) {
1083      // Look in InfoPlist.strings first.  Then look in Info.plist
1084      CFTypeRef result = NULL;
1085      if (bundle && key) {
1086          CFDictionaryRef dict = CFBundleGetLocalInfoDictionary(bundle);
1087          if (dict) result = CFDictionaryGetValue(dict, key);
1088          if (!result) {
1089              dict = CFBundleGetInfoDictionary(bundle);
1090              if (dict) result = CFDictionaryGetValue(dict, key);
1091          }
1092      }
1093      return result;
1094  }
1095  
1096  CFStringRef CFBundleGetIdentifier(CFBundleRef bundle) {
1097      CFStringRef bundleID = NULL;
1098      CFDictionaryRef infoDict = CFBundleGetInfoDictionary(bundle);
1099      if (infoDict) bundleID = (CFStringRef)CFDictionaryGetValue(infoDict, kCFBundleIdentifierKey);
1100      return bundleID;
1101  }
1102  
1103  static void __addSuffixesToKeys(const void *value, void *context) {
1104      CFMutableSetRef newKeys = (CFMutableSetRef)context;
1105      CFStringRef key = (CFStringRef)value;
1106      CFStringRef firstPartOfKey = NULL;
1107      CFStringRef restOfKey = NULL;
1108      
1109      // Find the first ':'
1110      CFRange range;
1111      Boolean success = CFStringFindWithOptions(key, CFSTR(":"), CFRangeMake(0, CFStringGetLength(key)), 0, &range);
1112      if (success) {
1113          firstPartOfKey = CFStringCreateWithSubstring(kCFAllocatorSystemDefault, key, CFRangeMake(0, range.location));
1114          restOfKey = CFStringCreateWithSubstring(kCFAllocatorSystemDefault, key, CFRangeMake(range.location + 1, CFStringGetLength(key) - range.location - 1));
1115      } else {
1116          firstPartOfKey = (CFStringRef)CFRetain(key);
1117      }
1118      
1119      // only apply product and platform to top-level key
1120      CFStringRef newKeyWithPlatform = CFStringCreateWithFormat(kCFAllocatorSystemDefault, NULL, CFSTR("%@-%@%@%@"), firstPartOfKey, _CFGetPlatformName(), restOfKey ? CFSTR(":") : CFSTR(""), restOfKey ? restOfKey : CFSTR(""));
1121      CFStringRef newKeyWithProduct = CFStringCreateWithFormat(kCFAllocatorSystemDefault, NULL, CFSTR("%@~%@%@%@"), firstPartOfKey, _CFGetProductName(), restOfKey ? CFSTR(":") : CFSTR(""), restOfKey ? restOfKey : CFSTR(""));
1122      CFStringRef newKeyWithProductAndPlatform = CFStringCreateWithFormat(kCFAllocatorSystemDefault, NULL, CFSTR("%@-%@~%@%@%@"), firstPartOfKey, _CFGetPlatformName(), _CFGetProductName(), restOfKey ? CFSTR(":") : CFSTR(""), restOfKey ? restOfKey : CFSTR(""));
1123      
1124      CFSetAddValue(newKeys, key);
1125      CFSetAddValue(newKeys, newKeyWithPlatform);
1126      CFSetAddValue(newKeys, newKeyWithProduct);
1127      CFSetAddValue(newKeys, newKeyWithProductAndPlatform);
1128      
1129      CFRelease(newKeyWithPlatform);
1130      CFRelease(newKeyWithProduct);
1131      CFRelease(newKeyWithProductAndPlatform);
1132  
1133      // Add special case keys
1134      CFStringRef overrideSpecialCase = CFStringCreateWithFormat(kCFAllocatorSystemDefault, NULL, CFSTR("%@#override%@%@"), firstPartOfKey, restOfKey ? CFSTR(":") : CFSTR(""), restOfKey ? restOfKey : CFSTR(""));
1135      CFSetAddValue(newKeys, overrideSpecialCase);
1136      CFRelease(overrideSpecialCase);
1137      
1138      if (firstPartOfKey) CFRelease(firstPartOfKey);
1139      if (restOfKey) CFRelease(restOfKey);
1140  }
1141  
1142  // from CFUtilities.c
1143  CF_PRIVATE Boolean _CFReadMappedFromFile(CFStringRef path, Boolean map, Boolean uncached, void **outBytes, CFIndex *outLength, CFErrorRef *errorPtr);
1144  
1145  // Ensure keyPaths are actually `CFString`
1146  static void __validPlistKeys(const void *value, void *context) {
1147      CFStringRef key = (CFStringRef)value;
1148      if (CFGetTypeID(key) != _kCFRuntimeIDCFString) {
1149          HALT_MSG("Property lists must have string keys!");
1150      }
1151  }
1152  
1153  // implementation of below functions - takes URL as parameter
1154  static CFPropertyListRef _CFBundleCreateFilteredInfoPlistWithURL(CFURLRef infoPlistURL, CFSetRef keyPaths, _CFBundleFilteredPlistOptions options) {
1155      CFPropertyListRef result = NULL;
1156      
1157      if (!infoPlistURL) return CFDictionaryCreate(kCFAllocatorSystemDefault, NULL, NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1158      
1159      CFURLRef absoluteURL = CFURLCopyAbsoluteURL(infoPlistURL);
1160      CFStringRef filePath = CFURLCopyFileSystemPath(absoluteURL, PLATFORM_PATH_STYLE);
1161      CFRelease(absoluteURL);
1162      
1163      if (!filePath) return CFDictionaryCreate(kCFAllocatorSystemDefault, NULL, NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1164      
1165      void *bytes = NULL;
1166      CFIndex length = 0;
1167  #if TARGET_OS_MAC
1168      Boolean mapped = options & _CFBundleFilteredPlistMemoryMapped ? true : false;
1169  #else
1170      Boolean mapped = false;
1171  #endif
1172      Boolean success = _CFReadMappedFromFile(filePath, mapped, false, &bytes, &length, NULL);
1173      CFRelease(filePath);
1174      if (!success) return CFDictionaryCreate(kCFAllocatorSystemDefault, NULL, NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1175      
1176      CFDataRef infoPlistData = CFDataCreateWithBytesNoCopy(kCFAllocatorSystemDefault, (const UInt8 *)bytes, length, kCFAllocatorNull);
1177  
1178      // Ensure `keyPaths` are all actually `CFString`s
1179      // if not `HALT` on first invalid keyPath
1180      CFSetApplyFunction(keyPaths, __validPlistKeys, NULL);
1181  
1182      // We need to include all possible variants of the platform/product combo as possible keys.
1183      CFMutableSetRef newKeyPaths = CFSetCreateMutable(kCFAllocatorSystemDefault, CFSetGetCount(keyPaths), &kCFTypeSetCallBacks);
1184      CFSetApplyFunction(keyPaths, __addSuffixesToKeys, newKeyPaths);
1185      
1186      success = _CFPropertyListCreateFiltered(kCFAllocatorSystemDefault, infoPlistData, kCFPropertyListMutableContainers, newKeyPaths, &result, NULL);
1187      
1188      if (!success || !result) {
1189          result = CFDictionaryCreate(kCFAllocatorSystemDefault, NULL, NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1190      } else if (CFDictionaryGetTypeID() == CFGetTypeID(result)) {
1191          _CFBundleInfoPlistProcessInfoDictionary((CFMutableDictionaryRef)result);
1192      } else {
1193          CFRelease(result);
1194          CFLog(kCFLogLevelError, CFSTR("A filtered Info.plist result was not a dictionary at URL %@ (for key paths %@)"), infoPlistURL, keyPaths);
1195          result = CFDictionaryCreate(kCFAllocatorSystemDefault, NULL, NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1196      }
1197      
1198      CFRelease(newKeyPaths);
1199      CFRelease(infoPlistData);
1200      if (mapped) {
1201  #if TARGET_OS_MAC
1202          munmap(bytes, length);
1203  #endif
1204      } else {
1205          free(bytes);
1206      }
1207      
1208      return result;
1209  }
1210  
1211  // Returns a subset of the bundle's property list, only including the keyPaths in the CFSet. If the top level object is not a dictionary, you will get back an empty dictionary as the result. If the Info.plist does not exist or could not be parsed, you will get back an empty dictionary.
1212  CF_EXPORT CFPropertyListRef _CFBundleCreateFilteredInfoPlist(CFBundleRef bundle, CFSetRef keyPaths, _CFBundleFilteredPlistOptions options) {
1213      CFURLRef infoPlistURL = _CFBundleCopyInfoPlistURL(bundle);
1214      CFPropertyListRef result = _CFBundleCreateFilteredInfoPlistWithURL(infoPlistURL, keyPaths, options);
1215      if (infoPlistURL) CFRelease(infoPlistURL);
1216      return result;
1217  }
1218  
1219  CF_EXPORT CFPropertyListRef _CFBundleCreateFilteredLocalizedInfoPlist(CFBundleRef bundle, CFSetRef keyPaths, CFStringRef localizationName, _CFBundleFilteredPlistOptions options) {
1220      CFURLRef infoPlistURL = CFBundleCopyResourceURLForLocalization(bundle, _CFBundleLocalInfoName, _CFBundleStringTableType, NULL, localizationName);
1221      CFPropertyListRef result = _CFBundleCreateFilteredInfoPlistWithURL(infoPlistURL, keyPaths, options);
1222      if (infoPlistURL) CFRelease(infoPlistURL);
1223      return result;
1224  }
1225  
1226  CF_EXPORT CFURLRef _CFBundleCopyInfoPlistURL(CFBundleRef bundle) {
1227      __CFLock(&bundle->_lock);
1228      CFURLRef url = bundle->_infoPlistUrl;
1229      CFURLRef result = (url ? (CFURLRef) CFRetain(url) : NULL);
1230      __CFUnlock(&bundle->_lock);
1231      return result;
1232  }