1/* 2 * Copyright (c) 2014 Apple Inc. All rights reserved. 3 * 4 * @APPLE_LICENSE_HEADER_START@ 5 * 6 * This file contains Original Code and/or Modifications of Original Code 7 * as defined in and that are subject to the Apple Public Source License 8 * Version 2.0 (the 'License'). You may not use this file except in 9 * compliance with the License. Please obtain a copy of the License at 10 * http://www.opensource.apple.com/apsl/ and read it before using this 11 * file. 12 * 13 * The Original Code and all software distributed under the License are 14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 18 * Please see the License for the specific language governing rights and 19 * limitations under the License. 20 * 21 * @APPLE_LICENSE_HEADER_END@ 22 */ 23 24// 25// CFBundle_InfoPlist.c 26// CoreFoundation 27// 28// Created by Tony Parker on 5/30/12. 29// 30// 31 32#include <CoreFoundation/CFBundle.h> 33#include <CoreFoundation/CFNumber.h> 34#include "CFBundle_Internal.h" 35#include "CFByteOrder.h" 36#include "CFURLAccess.h" 37 38#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_LINUX || DEPLOYMENT_TARGET_EMBEDDED_MINI 39#include <dirent.h> 40#include <sys/sysctl.h> 41#endif 42 43// The following strings are initialized 'later' (i.e., not at static initialization time) because static init time is too early for CFSTR to work, on platforms without constant CF strings 44#if !__CONSTANT_STRINGS__ 45 46#define _CFBundleNumberOfPlatforms 7 47static CFStringRef _CFBundleSupportedPlatforms[_CFBundleNumberOfPlatforms] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL }; 48static const char *_CFBundleSupportedPlatformStrings[_CFBundleNumberOfPlatforms] = { "iphoneos", "macos", "windows", "linux", "freebsd", "solaris", "hpux" }; 49 50#define _CFBundleNumberOfProducts 3 51static CFStringRef _CFBundleSupportedProducts[_CFBundleNumberOfProducts] = { NULL, NULL, NULL }; 52static const char *_CFBundleSupportedProductStrings[_CFBundleNumberOfProducts] = { "iphone", "ipod", "ipad" }; 53 54#define _CFBundleNumberOfiPhoneOSPlatformProducts 3 55static CFStringRef _CFBundleSupportediPhoneOSPlatformProducts[_CFBundleNumberOfiPhoneOSPlatformProducts] = { NULL, NULL, NULL }; 56static const char *_CFBundleSupportediPhoneOSPlatformProductStrings[_CFBundleNumberOfiPhoneOSPlatformProducts] = { "iphone", "ipod", "ipad" }; 57 58CF_PRIVATE void _CFBundleResourcesInitialize() { 59 for (unsigned int i = 0; i < _CFBundleNumberOfPlatforms; i++) _CFBundleSupportedPlatforms[i] = CFStringCreateWithCString(kCFAllocatorSystemDefault, _CFBundleSupportedPlatformStrings[i], kCFStringEncodingUTF8); 60 61 for (unsigned int i = 0; i < _CFBundleNumberOfProducts; i++) _CFBundleSupportedProducts[i] = CFStringCreateWithCString(kCFAllocatorSystemDefault, _CFBundleSupportedProductStrings[i], kCFStringEncodingUTF8); 62 63 for (unsigned int i = 0; i < _CFBundleNumberOfiPhoneOSPlatformProducts; i++) _CFBundleSupportediPhoneOSPlatformProducts[i] = CFStringCreateWithCString(kCFAllocatorSystemDefault, _CFBundleSupportediPhoneOSPlatformProductStrings[i], kCFStringEncodingUTF8); 64} 65 66#else 67 68#if DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI 69// On iOS, we only support one platform 70#define _CFBundleNumberOfPlatforms 1 71static CFStringRef _CFBundleSupportedPlatforms[_CFBundleNumberOfPlatforms] = { CFSTR("iphoneos") }; 72#else 73// On other platforms, we support the following platforms 74#define _CFBundleNumberOfPlatforms 7 75static CFStringRef _CFBundleSupportedPlatforms[_CFBundleNumberOfPlatforms] = { CFSTR("iphoneos"), CFSTR("macos"), CFSTR("windows"), CFSTR("linux"), CFSTR("freebsd"), CFSTR("solaris"), CFSTR("hpux") }; 76#endif 77 78#define _CFBundleNumberOfProducts 3 79static CFStringRef _CFBundleSupportedProducts[_CFBundleNumberOfProducts] = { CFSTR("iphone"), CFSTR("ipod"), CFSTR("ipad") }; 80 81#define _CFBundleNumberOfiPhoneOSPlatformProducts 3 82static CFStringRef _CFBundleSupportediPhoneOSPlatformProducts[_CFBundleNumberOfiPhoneOSPlatformProducts] = { CFSTR("iphone"), CFSTR("ipod"), CFSTR("ipad") }; 83 84CF_PRIVATE void _CFBundleResourcesInitialize() { } 85#endif 86 87#pragma mark - 88#pragma mark Product and Platform Getters - Exported 89 90static CFStringRef _cfBundlePlatform = NULL; 91CF_EXPORT void _CFSetProductName(CFStringRef str) { 92 // TODO: This should be removed. The "CLASSIC" check below removes the need to set the product name manually. 93 if (str) CFRetain(str); 94 _cfBundlePlatform = str; 95 // Note that the previous value is leaked, which is fine normally 96 // because the initial values would tend to be the constant strings 97 // below. That is required for thread-safety value due to the Get 98 // function [not being Copy]. It is also fine because people 99 // shouldn't be screwing around with this value casually. 100} 101 102CF_EXPORT CFStringRef _CFGetProductName(void) { 103#if DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI 104 if (!_cfBundlePlatform) { 105 const char *isClassic = __CFgetenv("CLASSIC"); 106 if (isClassic && strnlen(isClassic, 1) >= 1 && isClassic[0] == '1') { 107 _cfBundlePlatform = CFSTR("iphone"); 108 } else { 109 char buffer[256]; 110 memset(buffer, 0, sizeof(buffer)); 111 size_t buflen = sizeof(buffer); 112 int ret = sysctlbyname("hw.machine", buffer, &buflen, NULL, 0); 113 if (0 == ret || (-1 == ret && ENOMEM == errno)) { 114 if (6 <= buflen && 0 == memcmp(buffer, "iPhone", 6)) { 115 _cfBundlePlatform = CFSTR("iphone"); 116 } else if (4 <= buflen && 0 == memcmp(buffer, "iPod", 4)) { 117 _cfBundlePlatform = CFSTR("ipod"); 118 } else if (4 <= buflen && 0 == memcmp(buffer, "iPad", 4)) { 119 _cfBundlePlatform = CFSTR("ipad"); 120 } else { 121 const char *env = __CFgetenv("IPHONE_SIMULATOR_DEVICE"); 122 if (env) { 123 if (0 == strcmp(env, "iPhone")) { 124 _cfBundlePlatform = CFSTR("iphone"); 125 } else if (0 == strcmp(env, "iPad")) { 126 _cfBundlePlatform = CFSTR("ipad"); 127 } else { 128 // fallback, unrecognized IPHONE_SIMULATOR_DEVICE 129 } 130 } else { 131 // fallback, unrecognized hw.machine and no IPHONE_SIMULATOR_DEVICE 132 } 133 } 134 } 135 } 136 if (!_cfBundlePlatform) _cfBundlePlatform = CFSTR("iphone"); // fallback 137 } 138 return _cfBundlePlatform; 139#endif 140 return CFSTR(""); 141} 142 143// All new-style bundles will have these extensions. 144CF_EXPORT CFStringRef _CFGetPlatformName(void) { 145#if DEPLOYMENT_TARGET_MACOSX 146 return _CFBundleMacOSXPlatformName; 147#elif DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI 148 return _CFBundleiPhoneOSPlatformName; 149#elif DEPLOYMENT_TARGET_WINDOWS 150 return _CFBundleWindowsPlatformName; 151#elif DEPLOYMENT_TARGET_SOLARIS 152 return _CFBundleSolarisPlatformName; 153#elif DEPLOYMENT_TARGET_HPUX 154 return _CFBundleHPUXPlatformName; 155#elif DEPLOYMENT_TARGET_LINUX 156 return _CFBundleLinuxPlatformName; 157#elif DEPLOYMENT_TARGET_FREEBSD 158 return _CFBundleFreeBSDPlatformName; 159#else 160#error Unknown or unspecified DEPLOYMENT_TARGET 161#endif 162} 163 164CF_EXPORT CFStringRef _CFGetAlternatePlatformName(void) { 165#if DEPLOYMENT_TARGET_MACOSX 166 return _CFBundleAlternateMacOSXPlatformName; 167#elif DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI 168 return _CFBundleMacOSXPlatformName; 169#elif DEPLOYMENT_TARGET_WINDOWS 170 return CFSTR(""); 171#else 172#error Unknown or unspecified DEPLOYMENT_TARGET 173#endif 174} 175 176#pragma mark - 177#pragma mark Product and Platform Suffix Processing - Internal 178 179// TODO: Merge with below function, they do the same thing 180static Boolean _isValidPlatformSuffix(CFStringRef suffix) { 181 for (CFIndex idx = 0; idx < _CFBundleNumberOfPlatforms; idx++) { 182 if (CFEqual(suffix, _CFBundleSupportedPlatforms[idx])) return true; 183 } 184 return false; 185} 186 187// Returns true if the searchRange of the fileName is equal to a valid platform name (e.g., macos, iphoneos) 188CF_PRIVATE Boolean _CFBundleSupportedPlatformName(CFStringRef fileName, CFRange searchRange) { 189 for (CFIndex i = 0; i < _CFBundleNumberOfPlatforms; i++) { 190 if (CFStringFindWithOptions(fileName, _CFBundleSupportedPlatforms[i], searchRange, kCFCompareAnchored, NULL)) { 191 return true; 192 } 193 } 194 return false; 195} 196 197// TODO: Merge with below function, they do the same thing 198static Boolean _isValidProductSuffix(CFStringRef suffix) { 199 for (CFIndex idx = 0; idx < _CFBundleNumberOfProducts; idx++) { 200 if (CFEqual(suffix, _CFBundleSupportedProducts[idx])) return true; 201 } 202 return false; 203} 204 205// Returns true if the searchRange of the fileName is equal to a a valid product name (e.g., ipod, ipad) 206CF_PRIVATE Boolean _CFBundleSupportedProductName(CFStringRef fileName, CFRange searchRange) { 207 for (CFIndex i = 0; i < _CFBundleNumberOfProducts; i++) { 208 if (CFStringFindWithOptions(fileName, _CFBundleSupportedProducts[i], searchRange, kCFCompareAnchored, NULL)) { 209 return true; 210 } 211 } 212 return false; 213} 214 215static Boolean _isValidiPhoneOSPlatformProductSuffix(CFStringRef suffix) { 216 for (CFIndex idx = 0; idx < _CFBundleNumberOfiPhoneOSPlatformProducts; idx++) { 217 if (CFEqual(suffix, _CFBundleSupportediPhoneOSPlatformProducts[idx])) return true; 218 } 219 return false; 220} 221 222static Boolean _isValidPlatformAndProductSuffixPair(CFStringRef platform, CFStringRef product) { 223 if (!platform && !product) return true; 224 if (!platform) { 225 return _isValidProductSuffix(product); 226 } 227 if (!product) { 228 return _isValidPlatformSuffix(platform); 229 } 230 if (CFEqual(platform, _CFBundleiPhoneOSPlatformName)) { 231 return _isValidiPhoneOSPlatformProductSuffix(product); 232 } 233 return false; 234} 235 236static Boolean _isBlacklistedKey(CFStringRef keyName) { 237#if __CONSTANT_STRINGS__ 238#define _CFBundleNumberOfBlacklistedInfoDictionaryKeys 2 239 static const CFStringRef _CFBundleBlacklistedInfoDictionaryKeys[_CFBundleNumberOfBlacklistedInfoDictionaryKeys] = { CFSTR("CFBundleExecutable"), CFSTR("CFBundleIdentifier") }; 240 241 for (CFIndex idx = 0; idx < _CFBundleNumberOfBlacklistedInfoDictionaryKeys; idx++) { 242 if (CFEqual(keyName, _CFBundleBlacklistedInfoDictionaryKeys[idx])) return true; 243 } 244#endif 245 return false; 246} 247 248static Boolean _isOverrideKey(CFStringRef fullKey, CFStringRef *outBaseKey, CFStringRef *outPlatformSuffix, CFStringRef *outProductSuffix) { 249 if (outBaseKey) { 250 *outBaseKey = NULL; 251 } 252 if (outPlatformSuffix) { 253 *outPlatformSuffix = NULL; 254 } 255 if (outProductSuffix) { 256 *outProductSuffix = NULL; 257 } 258 if (!fullKey) 259 return false; 260 CFRange minusRange = CFStringFind(fullKey, CFSTR("-"), kCFCompareBackwards); 261 CFRange tildeRange = CFStringFind(fullKey, CFSTR("~"), kCFCompareBackwards); 262 if (minusRange.location == kCFNotFound && tildeRange.location == kCFNotFound) return false; 263 // minus must come before tilde if both are present 264 if (minusRange.location != kCFNotFound && tildeRange.location != kCFNotFound && tildeRange.location <= minusRange.location) return false; 265 266 CFIndex strLen = CFStringGetLength(fullKey); 267 CFRange baseKeyRange = (minusRange.location != kCFNotFound) ? CFRangeMake(0, minusRange.location) : CFRangeMake(0, tildeRange.location); 268 CFRange platformRange = CFRangeMake(kCFNotFound, 0); 269 CFRange productRange = CFRangeMake(kCFNotFound, 0); 270 if (minusRange.location != kCFNotFound) { 271 platformRange.location = minusRange.location + minusRange.length; 272 platformRange.length = ((tildeRange.location != kCFNotFound) ? tildeRange.location : strLen) - platformRange.location; 273 } 274 if (tildeRange.location != kCFNotFound) { 275 productRange.location = tildeRange.location + tildeRange.length; 276 productRange.length = strLen - productRange.location; 277 } 278 if (baseKeyRange.length < 1) return false; 279 if (platformRange.location != kCFNotFound && platformRange.length < 1) return false; 280 if (productRange.location != kCFNotFound && productRange.length < 1) return false; 281 282 CFStringRef platform = (platformRange.location != kCFNotFound) ? CFStringCreateWithSubstring(kCFAllocatorSystemDefault, fullKey, platformRange) : NULL; 283 CFStringRef product = (productRange.location != kCFNotFound) ? CFStringCreateWithSubstring(kCFAllocatorSystemDefault, fullKey, productRange) : NULL; 284 Boolean result = _isValidPlatformAndProductSuffixPair(platform, product); 285 286 if (result) { 287 if (outBaseKey) { 288 *outBaseKey = CFStringCreateWithSubstring(kCFAllocatorSystemDefault, fullKey, baseKeyRange); 289 } 290 if (outPlatformSuffix) { 291 *outPlatformSuffix = platform; 292 } else { 293 if (platform && !(0)) CFRelease(platform); 294 } 295 if (outProductSuffix) { 296 *outProductSuffix = product; 297 } else { 298 if (product && !(0)) CFRelease(product); 299 } 300 } else { 301 if (platform && !(0)) CFRelease(platform); 302 if (product && !(0)) CFRelease(product); 303 } 304 return result; 305} 306 307static Boolean _isCurrentPlatformAndProduct(CFStringRef platform, CFStringRef product) { 308 if (!platform && !product) return true; 309 if (!platform) { 310 return CFEqual(_CFGetProductName(), product); 311 } 312 if (!product) { 313 return CFEqual(_CFGetPlatformName(), platform); 314 } 315 316 return CFEqual(_CFGetProductName(), product) && CFEqual(_CFGetPlatformName(), platform); 317} 318 319static CFArrayRef _CopySortedOverridesForBaseKey(CFStringRef keyName, CFDictionaryRef dict) { 320 CFMutableArrayRef overrides = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks); 321 CFStringRef keyNameWithBoth = CFStringCreateWithFormat(kCFAllocatorSystemDefault, NULL, CFSTR("%@-%@~%@"), keyName, _CFGetPlatformName(), _CFGetProductName()); 322 CFStringRef keyNameWithProduct = CFStringCreateWithFormat(kCFAllocatorSystemDefault, NULL, CFSTR("%@~%@"), keyName, _CFGetProductName()); 323 CFStringRef keyNameWithPlatform = CFStringCreateWithFormat(kCFAllocatorSystemDefault, NULL, CFSTR("%@-%@"), keyName, _CFGetPlatformName()); 324 325 CFIndex count = CFDictionaryGetCount(dict); 326 327 if (count > 0) { 328 CFTypeRef *keys = (CFTypeRef *)CFAllocatorAllocate(kCFAllocatorSystemDefault, 2 * count * sizeof(CFTypeRef), 0); 329 CFTypeRef *values = &(keys[count]); 330 331 CFDictionaryGetKeysAndValues(dict, keys, values); 332 for (CFIndex idx = 0; idx < count; idx++) { 333 if (CFEqual(keys[idx], keyNameWithBoth)) { 334 CFArrayAppendValue(overrides, keys[idx]); 335 break; 336 } 337 } 338 for (CFIndex idx = 0; idx < count; idx++) { 339 if (CFEqual(keys[idx], keyNameWithProduct)) { 340 CFArrayAppendValue(overrides, keys[idx]); 341 break; 342 } 343 } 344 for (CFIndex idx = 0; idx < count; idx++) { 345 if (CFEqual(keys[idx], keyNameWithPlatform)) { 346 CFArrayAppendValue(overrides, keys[idx]); 347 break; 348 } 349 } 350 for (CFIndex idx = 0; idx < count; idx++) { 351 if (CFEqual(keys[idx], keyName)) { 352 CFArrayAppendValue(overrides, keys[idx]); 353 break; 354 } 355 } 356 357 CFAllocatorDeallocate(kCFAllocatorSystemDefault, keys); 358 } 359 360 CFRelease(keyNameWithProduct); 361 CFRelease(keyNameWithPlatform); 362 CFRelease(keyNameWithBoth); 363 364 return overrides; 365} 366 367CF_PRIVATE void _CFBundleInfoPlistProcessInfoDictionary(CFMutableDictionaryRef dict) { 368 CFIndex count = CFDictionaryGetCount(dict); 369 370 if (count > 0) { 371 CFTypeRef *keys = (CFTypeRef *)CFAllocatorAllocate(kCFAllocatorSystemDefault, 2 * count * sizeof(CFTypeRef), 0); 372 CFTypeRef *values = &(keys[count]); 373 CFMutableArrayRef guard = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks); 374 375 CFDictionaryGetKeysAndValues(dict, keys, values); 376 for (CFIndex idx = 0; idx < count; idx++) { 377 CFStringRef keyPlatformSuffix, keyProductSuffix, keyName; 378 if (_isOverrideKey((CFStringRef)keys[idx], &keyName, &keyPlatformSuffix, &keyProductSuffix)) { 379 CFArrayRef keysForBaseKey = NULL; 380 if (_isCurrentPlatformAndProduct(keyPlatformSuffix, keyProductSuffix) && !_isBlacklistedKey(keyName) && CFDictionaryContainsKey(dict, keys[idx])) { 381 keysForBaseKey = _CopySortedOverridesForBaseKey(keyName, dict); 382 CFIndex keysForBaseKeyCount = CFArrayGetCount(keysForBaseKey); 383 384 //make sure the other keys for this base key don't get released out from under us until we're done 385 CFArrayAppendValue(guard, keysForBaseKey); 386 387 //the winner for this base key will be sorted to the front, do the override with it 388 CFTypeRef highestPriorityKey = CFArrayGetValueAtIndex(keysForBaseKey, 0); 389 CFDictionarySetValue(dict, keyName, CFDictionaryGetValue(dict, highestPriorityKey)); 390 391 //remove everything except the now-overridden key; this will cause them to fail the CFDictionaryContainsKey(dict, keys[idx]) check in the enclosing if() and not be reprocessed 392 for (CFIndex presentKeysIdx = 0; presentKeysIdx < keysForBaseKeyCount; presentKeysIdx++) { 393 CFStringRef currentKey = (CFStringRef)CFArrayGetValueAtIndex(keysForBaseKey, presentKeysIdx); 394 if (!CFEqual(currentKey, keyName)) 395 CFDictionaryRemoveValue(dict, currentKey); 396 } 397 } else { 398 CFDictionaryRemoveValue(dict, keys[idx]); 399 } 400 401 402 if (keyPlatformSuffix) CFRelease(keyPlatformSuffix); 403 if (keyProductSuffix) CFRelease(keyProductSuffix); 404 CFRelease(keyName); 405 if (keysForBaseKey) CFRelease(keysForBaseKey); 406 } 407 } 408 409 CFAllocatorDeallocate(kCFAllocatorSystemDefault, keys); 410 CFRelease(guard); 411 } 412} 413 414#pragma mark - 415#pragma mark Info Plist Functions 416 417CF_PRIVATE CFDictionaryRef _CFBundleCopyInfoDictionaryInDirectory(CFAllocatorRef alloc, CFURLRef url, uint8_t *version) { 418 CFDictionaryRef dict = NULL; 419 unsigned char buff[CFMaxPathSize]; 420 uint8_t localVersion = 0; 421 422 if (CFURLGetFileSystemRepresentation(url, true, buff, CFMaxPathSize)) { 423 CFURLRef newURL = CFURLCreateFromFileSystemRepresentation(kCFAllocatorSystemDefault, buff, strlen((char *)buff), true); 424 if (!newURL) newURL = (CFURLRef)CFRetain(url); 425 426 localVersion = _CFBundleGetBundleVersionForURL(newURL); 427 428 dict = _CFBundleCopyInfoDictionaryInDirectoryWithVersion(alloc, newURL, localVersion); 429 CFRelease(newURL); 430 } 431 if (version) *version = localVersion; 432 return dict; 433} 434 435CF_PRIVATE CFDictionaryRef _CFBundleCopyInfoDictionaryInDirectoryWithVersion(CFAllocatorRef alloc, CFURLRef url, uint8_t version) { 436 // We only return NULL for a bad URL, otherwise we create a dummy dictionary 437 if (!url) return NULL; 438 439 CFDictionaryRef result = NULL; 440 441 // We're going to search for two files here - Info.plist and Info-macos.plist (platform specific). The platform-specific one takes precedence. 442 // First, construct the URL to the directory we'll search by using the passed in URL as a base 443 CFStringRef platformInfoURLFromBase = _CFBundlePlatformInfoURLFromBase0; 444 CFStringRef infoURLFromBase = _CFBundleInfoURLFromBase0; 445 CFURLRef directoryURL = NULL; 446 447 if (0 == version) { 448 directoryURL = CFURLCreateWithString(kCFAllocatorSystemDefault, _CFBundleResourcesURLFromBase0, url); 449 platformInfoURLFromBase = _CFBundlePlatformInfoURLFromBase0; 450 infoURLFromBase = _CFBundleInfoURLFromBase0; 451 } else if (1 == version) { 452 directoryURL = CFURLCreateWithString(kCFAllocatorSystemDefault, _CFBundleSupportFilesURLFromBase1, url); 453 platformInfoURLFromBase = _CFBundlePlatformInfoURLFromBase1; 454 infoURLFromBase = _CFBundleInfoURLFromBase1; 455 } else if (2 == version) { 456 directoryURL = CFURLCreateWithString(kCFAllocatorSystemDefault, _CFBundleSupportFilesURLFromBase2, url); 457 platformInfoURLFromBase = _CFBundlePlatformInfoURLFromBase2; 458 infoURLFromBase = _CFBundleInfoURLFromBase2; 459 } else if (3 == version) { 460 CFStringRef path = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle); 461 // this test is necessary to exclude the case where a bundle is spuriously created from the innards of another bundle 462 if (path) { 463 if (!(CFStringHasSuffix(path, _CFBundleSupportFilesDirectoryName1) || CFStringHasSuffix(path, _CFBundleSupportFilesDirectoryName2) || CFStringHasSuffix(path, _CFBundleResourcesDirectoryName))) { 464 directoryURL = (CFURLRef)CFRetain(url); 465 platformInfoURLFromBase = _CFBundlePlatformInfoURLFromBase3; 466 infoURLFromBase = _CFBundleInfoURLFromBase3; 467 } 468 CFRelease(path); 469 } 470 } 471 472 CFURLRef absoluteURL; 473 if (directoryURL) { 474 absoluteURL = CFURLCopyAbsoluteURL(directoryURL); 475 CFStringRef directoryPath = CFURLCopyFileSystemPath(absoluteURL, PLATFORM_PATH_STYLE); 476 CFRelease(absoluteURL); 477 478 __block CFURLRef infoPlistURL = NULL; 479 __block CFURLRef platformInfoPlistURL = NULL; 480 481 CFIndex infoPlistLength = CFStringGetLength(_CFBundleInfoPlistName); 482 CFIndex platformInfoPlistLength = CFStringGetLength(_CFBundlePlatformInfoPlistName); 483 484 // Look inside this directory for the platform-specific and global Info.plist 485 // For compatability 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. 486 _CFIterateDirectory(directoryPath, ^Boolean(CFStringRef fileName, uint8_t fileType) { 487 // Only do the platform check on platforms where the string is different than the normal one 488 if (_CFBundlePlatformInfoPlistName != _CFBundleInfoPlistName) { 489 if (!platformInfoPlistURL && CFStringGetLength(fileName) == platformInfoPlistLength && CFStringCompareWithOptions(fileName, _CFBundlePlatformInfoPlistName, CFRangeMake(0, platformInfoPlistLength), kCFCompareCaseInsensitive | kCFCompareAnchored) == kCFCompareEqualTo) { 490 // Make a URL out of this file 491 platformInfoPlistURL = CFURLCreateWithString(kCFAllocatorSystemDefault, platformInfoURLFromBase, url); 492 } 493 } 494 495 if (!infoPlistURL && CFStringGetLength(fileName) == infoPlistLength && CFStringCompareWithOptions(fileName, _CFBundleInfoPlistName, CFRangeMake(0, infoPlistLength), kCFCompareCaseInsensitive | kCFCompareAnchored) == kCFCompareEqualTo) { 496 // Make a URL out of this file 497 infoPlistURL = CFURLCreateWithString(kCFAllocatorSystemDefault, infoURLFromBase, url); 498 } 499 500 // If by some chance we have both URLs, just bail early (or just the infoPlistURL on platforms that have no platform-specific name) 501 if (_CFBundlePlatformInfoPlistName != _CFBundleInfoPlistName) { 502 if (infoPlistURL && platformInfoPlistURL) return false; 503 } else { 504 if (infoPlistURL) return false; 505 } 506 507 return true; 508 }); 509 510 CFRelease(directoryPath); 511 CFRelease(directoryURL); 512 513 // Attempt to read in the data from the Info.plist we found - first the platform-specific one. 514 CFDataRef infoData = NULL; 515 CFURLRef finalInfoPlistURL = NULL; 516 if (platformInfoPlistURL) { 517#pragma GCC diagnostic push 518#pragma GCC diagnostic ignored "-Wdeprecated" 519 CFURLCreateDataAndPropertiesFromResource(kCFAllocatorSystemDefault, platformInfoPlistURL, &infoData, NULL, NULL, NULL); 520#pragma GCC diagnostic pop 521 if (infoData) finalInfoPlistURL = platformInfoPlistURL; 522 } 523 524 if (!infoData && infoPlistURL) { 525#pragma GCC diagnostic push 526#pragma GCC diagnostic ignored "-Wdeprecated" 527 CFURLCreateDataAndPropertiesFromResource(kCFAllocatorSystemDefault, infoPlistURL, &infoData, NULL, NULL, NULL); 528#pragma GCC diagnostic pop 529 if (infoData) finalInfoPlistURL = infoPlistURL; 530 } 531 532 if (infoData) { 533 CFErrorRef error = NULL; 534 result = (CFDictionaryRef)CFPropertyListCreateWithData(alloc, infoData, kCFPropertyListMutableContainers, NULL, &error); 535 if (result) { 536 if (CFDictionaryGetTypeID() == CFGetTypeID(result)) { 537 CFDictionarySetValue((CFMutableDictionaryRef)result, _kCFBundleInfoPlistURLKey, finalInfoPlistURL); 538 } else { 539 CFRelease(result); 540 result = NULL; 541 } 542 } else if (error) { 543 CFDictionaryRef userInfo = CFErrorCopyUserInfo(error); 544 CFLog(kCFLogLevelError, CFSTR("There was an error parsing the Info.plist for the bundle at URL %@\n %@\n %@"), infoPlistURL, error, userInfo); 545 if (userInfo) CFRelease(userInfo); 546 CFRelease(error); 547 } 548 549 if (!result) { 550 result = CFDictionaryCreateMutable(alloc, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 551 CFDictionarySetValue((CFMutableDictionaryRef)result, _kCFBundleRawInfoPlistURLKey, finalInfoPlistURL); 552 } 553 554 CFRelease(infoData); 555 } 556 557 if (platformInfoPlistURL) CFRelease(platformInfoPlistURL); 558 if (infoPlistURL) CFRelease(infoPlistURL); 559 } 560 561 if (!result) { 562 result = CFDictionaryCreateMutable(alloc, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 563 } 564 565 // process ~ipad, ~iphone, etc. 566 _CFBundleInfoPlistProcessInfoDictionary((CFMutableDictionaryRef)result); 567 568 return result; 569} 570 571CF_EXPORT CFDictionaryRef CFBundleCopyInfoDictionaryForURL(CFURLRef url) { 572 CFDictionaryRef result = NULL; 573 Boolean isDir = false; 574 if (_CFIsResourceAtURL(url, &isDir)) { 575 if (isDir) { 576 result = _CFBundleCopyInfoDictionaryInDirectory(kCFAllocatorSystemDefault, url, NULL); 577 } else { 578 result = _CFBundleCopyInfoDictionaryInExecutable(url); 579 } 580 } 581 if (result && (0)) CFRetain(result); // conditionally put on a retain for a Copy function 582 return result; 583} 584 585static Boolean _CFBundleGetPackageInfoInDirectoryWithInfoDictionary(CFAllocatorRef alloc, CFURLRef url, CFDictionaryRef infoDict, UInt32 *packageType, UInt32 *packageCreator) { 586 Boolean retVal = false, hasType = false, hasCreator = false, releaseInfoDict = false; 587 CFURLRef tempURL; 588 CFDataRef pkgInfoData = NULL; 589 590 // Check for a "real" new bundle 591 tempURL = CFURLCreateWithString(kCFAllocatorSystemDefault, _CFBundlePkgInfoURLFromBase2, url); 592#pragma GCC diagnostic push 593#pragma GCC diagnostic ignored "-Wdeprecated" 594 CFURLCreateDataAndPropertiesFromResource(kCFAllocatorSystemDefault, tempURL, &pkgInfoData, NULL, NULL, NULL); 595#pragma GCC diagnostic pop 596 CFRelease(tempURL); 597 if (!pkgInfoData) { 598 tempURL = CFURLCreateWithString(kCFAllocatorSystemDefault, _CFBundlePkgInfoURLFromBase1, url); 599#pragma GCC diagnostic push 600#pragma GCC diagnostic ignored "-Wdeprecated" 601 CFURLCreateDataAndPropertiesFromResource(kCFAllocatorSystemDefault, tempURL, &pkgInfoData, NULL, NULL, NULL); 602#pragma GCC diagnostic pop 603 CFRelease(tempURL); 604 } 605 if (!pkgInfoData) { 606 // Check for a "pseudo" new bundle 607 tempURL = CFURLCreateWithString(kCFAllocatorSystemDefault, _CFBundlePseudoPkgInfoURLFromBase, url); 608#pragma GCC diagnostic push 609#pragma GCC diagnostic ignored "-Wdeprecated" 610 CFURLCreateDataAndPropertiesFromResource(kCFAllocatorSystemDefault, tempURL, &pkgInfoData, NULL, NULL, NULL); 611#pragma GCC diagnostic pop 612 CFRelease(tempURL); 613 } 614 615 // 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. 616 // 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. 617 // 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. 618 619 if (pkgInfoData && CFDataGetLength(pkgInfoData) >= (int)(sizeof(UInt32) * 2)) { 620 UInt32 *pkgInfo = (UInt32 *)CFDataGetBytePtr(pkgInfoData); 621 if (packageType) *packageType = CFSwapInt32BigToHost(pkgInfo[0]); 622 if (packageCreator) *packageCreator = CFSwapInt32BigToHost(pkgInfo[1]); 623 retVal = hasType = hasCreator = true; 624 } 625 if (pkgInfoData) CFRelease(pkgInfoData); 626 if (!retVal) { 627 if (!infoDict) { 628 infoDict = _CFBundleCopyInfoDictionaryInDirectory(kCFAllocatorSystemDefault, url, NULL); 629 releaseInfoDict = true; 630 } 631 if (infoDict) { 632 CFStringRef typeString = (CFStringRef)CFDictionaryGetValue(infoDict, _kCFBundlePackageTypeKey), creatorString = (CFStringRef)CFDictionaryGetValue(infoDict, _kCFBundleSignatureKey); 633 UInt32 tmp; 634 CFIndex usedBufLen = 0; 635 if (typeString && CFGetTypeID(typeString) == CFStringGetTypeID() && CFStringGetLength(typeString) == 4 && 4 == CFStringGetBytes(typeString, CFRangeMake(0, 4), kCFStringEncodingMacRoman, 0, false, (UInt8 *)&tmp, 4, &usedBufLen) && 4 == usedBufLen) { 636 if (packageType) *packageType = CFSwapInt32BigToHost(tmp); 637 retVal = hasType = true; 638 } 639 if (creatorString && CFGetTypeID(creatorString) == CFStringGetTypeID() && CFStringGetLength(creatorString) == 4 && 4 == CFStringGetBytes(creatorString, CFRangeMake(0, 4), kCFStringEncodingMacRoman, 0, false, (UInt8 *)&tmp, 4, &usedBufLen) && 4 == usedBufLen) { 640 if (packageCreator) *packageCreator = CFSwapInt32BigToHost(tmp); 641 retVal = hasCreator = true; 642 } 643 if (releaseInfoDict && !(0)) CFRelease(infoDict); 644 } 645 } 646 if (!hasType || !hasCreator) { 647 // If this looks like a bundle then manufacture the type and creator. 648 if (retVal || _CFBundleURLLooksLikeBundle(url)) { 649 if (packageCreator && !hasCreator) *packageCreator = 0x3f3f3f3f; // '????' 650 if (packageType && !hasType) { 651 CFStringRef urlStr; 652 UniChar buff[CFMaxPathSize]; 653 CFIndex strLen, startOfExtension; 654 CFURLRef absoluteURL; 655 656 // Detect "app", "debug", "profile", or "framework" extensions 657 absoluteURL = CFURLCopyAbsoluteURL(url); 658 urlStr = CFURLCopyFileSystemPath(absoluteURL, PLATFORM_PATH_STYLE); 659 CFRelease(absoluteURL); 660 strLen = CFStringGetLength(urlStr); 661 if (strLen > CFMaxPathSize) strLen = CFMaxPathSize; 662 CFStringGetCharacters(urlStr, CFRangeMake(0, strLen), buff); 663 CFRelease(urlStr); 664 startOfExtension = _CFStartOfPathExtension(buff, strLen); 665 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)) { 666 // This is an app 667 *packageType = 0x4150504c; // 'APPL' 668 } 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)) { 669 // This is an app (debug version) 670 *packageType = 0x4150504c; // 'APPL' 671 } 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)) { 672 // This is an app (profile version) 673 *packageType = 0x4150504c; // 'APPL' 674 } 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)) { 675 // This is a service 676 *packageType = 0x4150504c; // 'APPL' 677 } 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)) { 678 // This is a framework 679 *packageType = 0x464d574b; // 'FMWK' 680 } else { 681 // Default to BNDL for generic bundle 682 *packageType = 0x424e444c; // 'BNDL' 683 } 684 } 685 retVal = true; 686 } 687 } 688 return retVal; 689} 690 691CF_EXPORT Boolean _CFBundleGetPackageInfoInDirectory(CFAllocatorRef alloc, CFURLRef url, UInt32 *packageType, UInt32 *packageCreator) { 692 return _CFBundleGetPackageInfoInDirectoryWithInfoDictionary(alloc, url, NULL, packageType, packageCreator); 693} 694 695CF_EXPORT void CFBundleGetPackageInfo(CFBundleRef bundle, UInt32 *packageType, UInt32 *packageCreator) { 696 CFURLRef bundleURL = CFBundleCopyBundleURL(bundle); 697 if (!_CFBundleGetPackageInfoInDirectoryWithInfoDictionary(kCFAllocatorSystemDefault, bundleURL, CFBundleGetInfoDictionary(bundle), packageType, packageCreator)) { 698 if (packageType) *packageType = 0x424e444c; // 'BNDL' 699 if (packageCreator) *packageCreator = 0x3f3f3f3f; // '????' 700 } 701 if (bundleURL) CFRelease(bundleURL); 702} 703 704CF_EXPORT Boolean CFBundleGetPackageInfoInDirectory(CFURLRef url, UInt32 *packageType, UInt32 *packageCreator) { 705 return _CFBundleGetPackageInfoInDirectory(kCFAllocatorSystemDefault, url, packageType, packageCreator); 706} 707 708CFDictionaryRef CFBundleCopyInfoDictionaryInDirectory(CFURLRef url) { 709 CFDictionaryRef dict = _CFBundleCopyInfoDictionaryInDirectory(kCFAllocatorSystemDefault, url, NULL); 710 return dict; 711} 712 713// 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. 714// Call with bundle lock 715static void _CFBundleInfoPlistFixupInfoDictionary(CFBundleRef bundle, CFMutableDictionaryRef infoDict) { 716 // Version number 717 CFTypeRef unknownVersionValue = CFDictionaryGetValue(infoDict, _kCFBundleNumericVersionKey); 718 CFNumberRef versNum; 719 UInt32 vers = 0; 720 721 if (!unknownVersionValue) unknownVersionValue = CFDictionaryGetValue(infoDict, kCFBundleVersionKey); 722 if (unknownVersionValue) { 723 if (CFGetTypeID(unknownVersionValue) == CFStringGetTypeID()) { 724 // Convert a string version number into a numeric one. 725 vers = _CFVersionNumberFromString((CFStringRef)unknownVersionValue); 726 727 versNum = CFNumberCreate(CFGetAllocator(bundle), kCFNumberSInt32Type, &vers); 728 CFDictionarySetValue(infoDict, _kCFBundleNumericVersionKey, versNum); 729 CFRelease(versNum); 730 } else if (CFGetTypeID(unknownVersionValue) == CFNumberGetTypeID()) { 731 // Nothing to do here 732 } else { 733 CFDictionaryRemoveValue((CFMutableDictionaryRef)infoDict, _kCFBundleNumericVersionKey); 734 } 735 } 736} 737 738CFDictionaryRef CFBundleGetInfoDictionary(CFBundleRef bundle) { 739 __CFSpinLock(&bundle->_lock); 740 if (!bundle->_infoDict) { 741 bundle->_infoDict = _CFBundleCopyInfoDictionaryInDirectoryWithVersion(kCFAllocatorSystemDefault, bundle->_url, bundle->_version); 742 743 // Add or fixup any keys that will be expected later 744 if (bundle->_infoDict) _CFBundleInfoPlistFixupInfoDictionary(bundle, (CFMutableDictionaryRef)bundle->_infoDict); 745 } 746 __CFSpinUnlock(&bundle->_lock); 747 748 return bundle->_infoDict; 749} 750 751CFDictionaryRef _CFBundleGetLocalInfoDictionary(CFBundleRef bundle) { 752 return CFBundleGetLocalInfoDictionary(bundle); 753} 754 755CFDictionaryRef CFBundleGetLocalInfoDictionary(CFBundleRef bundle) { 756 CFDictionaryRef localInfoDict = NULL; 757 __CFSpinLock(&bundle->_lock); 758 localInfoDict = bundle->_localInfoDict; 759 if (!localInfoDict) { 760 // 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. 761 __CFSpinUnlock(&bundle->_lock); 762 CFURLRef url = CFBundleCopyResourceURL(bundle, _CFBundleLocalInfoName, _CFBundleStringTableType, NULL); 763 if (url) { 764 CFDataRef data; 765 SInt32 errCode; 766 CFStringRef errStr = NULL; 767 768#pragma GCC diagnostic push 769#pragma GCC diagnostic ignored "-Wdeprecated" 770 if (CFURLCreateDataAndPropertiesFromResource(kCFAllocatorSystemDefault, url, &data, NULL, NULL, &errCode)) { 771 localInfoDict = (CFDictionaryRef)CFPropertyListCreateFromXMLData(kCFAllocatorSystemDefault, data, kCFPropertyListMutableContainers, &errStr); 772 if (errStr) CFRelease(errStr); 773 if (localInfoDict && CFDictionaryGetTypeID() != CFGetTypeID(localInfoDict)) { 774 CFRelease(localInfoDict); 775 localInfoDict = NULL; 776 } 777 CFRelease(data); 778 } 779#pragma GCC diagnostic pop 780 CFRelease(url); 781 } 782 if (localInfoDict) _CFBundleInfoPlistProcessInfoDictionary((CFMutableDictionaryRef)localInfoDict); 783 // remain locked here until we exit the if statement. 784 __CFSpinLock(&bundle->_lock); 785 if (!bundle->_localInfoDict) { 786 // Still have no info dictionary, so set it 787 bundle->_localInfoDict = localInfoDict; 788 } else { 789 // Oops, some other thread created an info dictionary too. We'll just release this one and use that one. 790 if (localInfoDict) CFRelease(localInfoDict); 791 localInfoDict = bundle->_localInfoDict; 792 } 793 } 794 __CFSpinUnlock(&bundle->_lock); 795 796 return localInfoDict; 797} 798 799CFPropertyListRef _CFBundleGetValueForInfoKey(CFBundleRef bundle, CFStringRef key) { 800 return (CFPropertyListRef)CFBundleGetValueForInfoDictionaryKey(bundle, key); 801} 802 803CFTypeRef CFBundleGetValueForInfoDictionaryKey(CFBundleRef bundle, CFStringRef key) { 804 // Look in InfoPlist.strings first. Then look in Info.plist 805 CFTypeRef result = NULL; 806 if (bundle && key) { 807 CFDictionaryRef dict = CFBundleGetLocalInfoDictionary(bundle); 808 if (dict) result = CFDictionaryGetValue(dict, key); 809 if (!result) { 810 dict = CFBundleGetInfoDictionary(bundle); 811 if (dict) result = CFDictionaryGetValue(dict, key); 812 } 813 } 814 return result; 815} 816 817CFStringRef CFBundleGetIdentifier(CFBundleRef bundle) { 818 CFStringRef bundleID = NULL; 819 CFDictionaryRef infoDict = CFBundleGetInfoDictionary(bundle); 820 if (infoDict) bundleID = (CFStringRef)CFDictionaryGetValue(infoDict, kCFBundleIdentifierKey); 821 return bundleID; 822} 823