2 * Copyright (c) 2014 Apple Inc. All rights reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
25 // CFBundle_InfoPlist.c
28 // Created by Tony Parker on 5/30/12.
32 #include <CoreFoundation/CFBundle.h>
33 #include <CoreFoundation/CFNumber.h>
34 #include "CFBundle_Internal.h"
35 #include "CFByteOrder.h"
36 #include "CFURLAccess.h"
38 #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_LINUX || DEPLOYMENT_TARGET_EMBEDDED_MINI
40 #include <sys/sysctl.h>
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__
46 #define _CFBundleNumberOfPlatforms 7
47 static CFStringRef _CFBundleSupportedPlatforms
[_CFBundleNumberOfPlatforms
] = { NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
};
48 static const char *_CFBundleSupportedPlatformStrings
[_CFBundleNumberOfPlatforms
] = { "iphoneos", "macos", "windows", "linux", "freebsd", "solaris", "hpux" };
50 #define _CFBundleNumberOfProducts 3
51 static CFStringRef _CFBundleSupportedProducts
[_CFBundleNumberOfProducts
] = { NULL
, NULL
, NULL
};
52 static const char *_CFBundleSupportedProductStrings
[_CFBundleNumberOfProducts
] = { "iphone", "ipod", "ipad" };
54 #define _CFBundleNumberOfiPhoneOSPlatformProducts 3
55 static CFStringRef _CFBundleSupportediPhoneOSPlatformProducts
[_CFBundleNumberOfiPhoneOSPlatformProducts
] = { NULL
, NULL
, NULL
};
56 static const char *_CFBundleSupportediPhoneOSPlatformProductStrings
[_CFBundleNumberOfiPhoneOSPlatformProducts
] = { "iphone", "ipod", "ipad" };
58 CF_PRIVATE
void _CFBundleResourcesInitialize() {
59 for (unsigned int i
= 0; i
< _CFBundleNumberOfPlatforms
; i
++) _CFBundleSupportedPlatforms
[i
] = CFStringCreateWithCString(kCFAllocatorSystemDefault
, _CFBundleSupportedPlatformStrings
[i
], kCFStringEncodingUTF8
);
61 for (unsigned int i
= 0; i
< _CFBundleNumberOfProducts
; i
++) _CFBundleSupportedProducts
[i
] = CFStringCreateWithCString(kCFAllocatorSystemDefault
, _CFBundleSupportedProductStrings
[i
], kCFStringEncodingUTF8
);
63 for (unsigned int i
= 0; i
< _CFBundleNumberOfiPhoneOSPlatformProducts
; i
++) _CFBundleSupportediPhoneOSPlatformProducts
[i
] = CFStringCreateWithCString(kCFAllocatorSystemDefault
, _CFBundleSupportediPhoneOSPlatformProductStrings
[i
], kCFStringEncodingUTF8
);
68 #if DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
69 // On iOS, we only support one platform
70 #define _CFBundleNumberOfPlatforms 1
71 static CFStringRef _CFBundleSupportedPlatforms
[_CFBundleNumberOfPlatforms
] = { CFSTR("iphoneos") };
73 // On other platforms, we support the following platforms
74 #define _CFBundleNumberOfPlatforms 7
75 static CFStringRef _CFBundleSupportedPlatforms
[_CFBundleNumberOfPlatforms
] = { CFSTR("iphoneos"), CFSTR("macos"), CFSTR("windows"), CFSTR("linux"), CFSTR("freebsd"), CFSTR("solaris"), CFSTR("hpux") };
78 #define _CFBundleNumberOfProducts 3
79 static CFStringRef _CFBundleSupportedProducts
[_CFBundleNumberOfProducts
] = { CFSTR("iphone"), CFSTR("ipod"), CFSTR("ipad") };
81 #define _CFBundleNumberOfiPhoneOSPlatformProducts 3
82 static CFStringRef _CFBundleSupportediPhoneOSPlatformProducts
[_CFBundleNumberOfiPhoneOSPlatformProducts
] = { CFSTR("iphone"), CFSTR("ipod"), CFSTR("ipad") };
84 CF_PRIVATE
void _CFBundleResourcesInitialize() { }
88 #pragma mark Product and Platform Getters - Exported
90 static CFStringRef _cfBundlePlatform
= NULL
;
91 CF_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.
102 CF_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");
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");
121 const char *env
= __CFgetenv("IPHONE_SIMULATOR_DEVICE");
123 if (0 == strcmp(env
, "iPhone")) {
124 _cfBundlePlatform
= CFSTR("iphone");
125 } else if (0 == strcmp(env
, "iPad")) {
126 _cfBundlePlatform
= CFSTR("ipad");
128 // fallback, unrecognized IPHONE_SIMULATOR_DEVICE
131 // fallback, unrecognized hw.machine and no IPHONE_SIMULATOR_DEVICE
136 if (!_cfBundlePlatform
) _cfBundlePlatform
= CFSTR("iphone"); // fallback
138 return _cfBundlePlatform
;
143 // All new-style bundles will have these extensions.
144 CF_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
;
160 #error Unknown or unspecified DEPLOYMENT_TARGET
164 CF_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
172 #error Unknown or unspecified DEPLOYMENT_TARGET
177 #pragma mark Product and Platform Suffix Processing - Internal
179 // TODO: Merge with below function, they do the same thing
180 static Boolean
_isValidPlatformSuffix(CFStringRef suffix
) {
181 for (CFIndex idx
= 0; idx
< _CFBundleNumberOfPlatforms
; idx
++) {
182 if (CFEqual(suffix
, _CFBundleSupportedPlatforms
[idx
])) return true;
187 // Returns true if the searchRange of the fileName is equal to a valid platform name (e.g., macos, iphoneos)
188 CF_PRIVATE Boolean
_CFBundleSupportedPlatformName(CFStringRef fileName
, CFRange searchRange
) {
189 for (CFIndex i
= 0; i
< _CFBundleNumberOfPlatforms
; i
++) {
190 if (CFStringFindWithOptions(fileName
, _CFBundleSupportedPlatforms
[i
], searchRange
, kCFCompareAnchored
, NULL
)) {
197 // TODO: Merge with below function, they do the same thing
198 static Boolean
_isValidProductSuffix(CFStringRef suffix
) {
199 for (CFIndex idx
= 0; idx
< _CFBundleNumberOfProducts
; idx
++) {
200 if (CFEqual(suffix
, _CFBundleSupportedProducts
[idx
])) return true;
205 // Returns true if the searchRange of the fileName is equal to a a valid product name (e.g., ipod, ipad)
206 CF_PRIVATE Boolean
_CFBundleSupportedProductName(CFStringRef fileName
, CFRange searchRange
) {
207 for (CFIndex i
= 0; i
< _CFBundleNumberOfProducts
; i
++) {
208 if (CFStringFindWithOptions(fileName
, _CFBundleSupportedProducts
[i
], searchRange
, kCFCompareAnchored
, NULL
)) {
215 static Boolean
_isValidiPhoneOSPlatformProductSuffix(CFStringRef suffix
) {
216 for (CFIndex idx
= 0; idx
< _CFBundleNumberOfiPhoneOSPlatformProducts
; idx
++) {
217 if (CFEqual(suffix
, _CFBundleSupportediPhoneOSPlatformProducts
[idx
])) return true;
222 static Boolean
_isValidPlatformAndProductSuffixPair(CFStringRef platform
, CFStringRef product
) {
223 if (!platform
&& !product
) return true;
225 return _isValidProductSuffix(product
);
228 return _isValidPlatformSuffix(platform
);
230 if (CFEqual(platform
, _CFBundleiPhoneOSPlatformName
)) {
231 return _isValidiPhoneOSPlatformProductSuffix(product
);
236 static Boolean
_isBlacklistedKey(CFStringRef keyName
) {
237 #if __CONSTANT_STRINGS__
238 #define _CFBundleNumberOfBlacklistedInfoDictionaryKeys 2
239 static const CFStringRef _CFBundleBlacklistedInfoDictionaryKeys
[_CFBundleNumberOfBlacklistedInfoDictionaryKeys
] = { CFSTR("CFBundleExecutable"), CFSTR("CFBundleIdentifier") };
241 for (CFIndex idx
= 0; idx
< _CFBundleNumberOfBlacklistedInfoDictionaryKeys
; idx
++) {
242 if (CFEqual(keyName
, _CFBundleBlacklistedInfoDictionaryKeys
[idx
])) return true;
248 static Boolean
_isOverrideKey(CFStringRef fullKey
, CFStringRef
*outBaseKey
, CFStringRef
*outPlatformSuffix
, CFStringRef
*outProductSuffix
) {
252 if (outPlatformSuffix
) {
253 *outPlatformSuffix
= NULL
;
255 if (outProductSuffix
) {
256 *outProductSuffix
= NULL
;
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;
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
;
274 if (tildeRange
.location
!= kCFNotFound
) {
275 productRange
.location
= tildeRange
.location
+ tildeRange
.length
;
276 productRange
.length
= strLen
- productRange
.location
;
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;
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
);
288 *outBaseKey
= CFStringCreateWithSubstring(kCFAllocatorSystemDefault
, fullKey
, baseKeyRange
);
290 if (outPlatformSuffix
) {
291 *outPlatformSuffix
= platform
;
293 if (platform
&& !(0)) CFRelease(platform
);
295 if (outProductSuffix
) {
296 *outProductSuffix
= product
;
298 if (product
&& !(0)) CFRelease(product
);
301 if (platform
&& !(0)) CFRelease(platform
);
302 if (product
&& !(0)) CFRelease(product
);
307 static Boolean
_isCurrentPlatformAndProduct(CFStringRef platform
, CFStringRef product
) {
308 if (!platform
&& !product
) return true;
310 return CFEqual(_CFGetProductName(), product
);
313 return CFEqual(_CFGetPlatformName(), platform
);
316 return CFEqual(_CFGetProductName(), product
) && CFEqual(_CFGetPlatformName(), platform
);
319 static 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());
325 CFIndex count
= CFDictionaryGetCount(dict
);
328 CFTypeRef
*keys
= (CFTypeRef
*)CFAllocatorAllocate(kCFAllocatorSystemDefault
, 2 * count
* sizeof(CFTypeRef
), 0);
329 CFTypeRef
*values
= &(keys
[count
]);
331 CFDictionaryGetKeysAndValues(dict
, keys
, values
);
332 for (CFIndex idx
= 0; idx
< count
; idx
++) {
333 if (CFEqual(keys
[idx
], keyNameWithBoth
)) {
334 CFArrayAppendValue(overrides
, keys
[idx
]);
338 for (CFIndex idx
= 0; idx
< count
; idx
++) {
339 if (CFEqual(keys
[idx
], keyNameWithProduct
)) {
340 CFArrayAppendValue(overrides
, keys
[idx
]);
344 for (CFIndex idx
= 0; idx
< count
; idx
++) {
345 if (CFEqual(keys
[idx
], keyNameWithPlatform
)) {
346 CFArrayAppendValue(overrides
, keys
[idx
]);
350 for (CFIndex idx
= 0; idx
< count
; idx
++) {
351 if (CFEqual(keys
[idx
], keyName
)) {
352 CFArrayAppendValue(overrides
, keys
[idx
]);
357 CFAllocatorDeallocate(kCFAllocatorSystemDefault
, keys
);
360 CFRelease(keyNameWithProduct
);
361 CFRelease(keyNameWithPlatform
);
362 CFRelease(keyNameWithBoth
);
367 CF_PRIVATE
void _CFBundleInfoPlistProcessInfoDictionary(CFMutableDictionaryRef dict
) {
368 CFIndex count
= CFDictionaryGetCount(dict
);
371 CFTypeRef
*keys
= (CFTypeRef
*)CFAllocatorAllocate(kCFAllocatorSystemDefault
, 2 * count
* sizeof(CFTypeRef
), 0);
372 CFTypeRef
*values
= &(keys
[count
]);
373 CFMutableArrayRef guard
= CFArrayCreateMutable(kCFAllocatorSystemDefault
, 0, &kCFTypeArrayCallBacks
);
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
);
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
);
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
));
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
);
398 CFDictionaryRemoveValue(dict
, keys
[idx
]);
402 if (keyPlatformSuffix
) CFRelease(keyPlatformSuffix
);
403 if (keyProductSuffix
) CFRelease(keyProductSuffix
);
405 if (keysForBaseKey
) CFRelease(keysForBaseKey
);
409 CFAllocatorDeallocate(kCFAllocatorSystemDefault
, keys
);
415 #pragma mark Info Plist Functions
417 CF_PRIVATE CFDictionaryRef
_CFBundleCopyInfoDictionaryInDirectory(CFAllocatorRef alloc
, CFURLRef url
, uint8_t *version
) {
418 CFDictionaryRef dict
= NULL
;
419 unsigned char buff
[CFMaxPathSize
];
420 uint8_t localVersion
= 0;
422 if (CFURLGetFileSystemRepresentation(url
, true, buff
, CFMaxPathSize
)) {
423 CFURLRef newURL
= CFURLCreateFromFileSystemRepresentation(kCFAllocatorSystemDefault
, buff
, strlen((char *)buff
), true);
424 if (!newURL
) newURL
= (CFURLRef
)CFRetain(url
);
426 localVersion
= _CFBundleGetBundleVersionForURL(newURL
);
428 dict
= _CFBundleCopyInfoDictionaryInDirectoryWithVersion(alloc
, newURL
, localVersion
);
431 if (version
) *version
= localVersion
;
435 CF_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
;
439 CFDictionaryRef result
= NULL
;
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
;
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
463 if (!(CFStringHasSuffix(path
, _CFBundleSupportFilesDirectoryName1
) || CFStringHasSuffix(path
, _CFBundleSupportFilesDirectoryName2
) || CFStringHasSuffix(path
, _CFBundleResourcesDirectoryName
))) {
464 directoryURL
= (CFURLRef
)CFRetain(url
);
465 platformInfoURLFromBase
= _CFBundlePlatformInfoURLFromBase3
;
466 infoURLFromBase
= _CFBundleInfoURLFromBase3
;
472 CFURLRef absoluteURL
;
474 absoluteURL
= CFURLCopyAbsoluteURL(directoryURL
);
475 CFStringRef directoryPath
= CFURLCopyFileSystemPath(absoluteURL
, PLATFORM_PATH_STYLE
);
476 CFRelease(absoluteURL
);
478 __block CFURLRef infoPlistURL
= NULL
;
479 __block CFURLRef platformInfoPlistURL
= NULL
;
481 CFIndex infoPlistLength
= CFStringGetLength(_CFBundleInfoPlistName
);
482 CFIndex platformInfoPlistLength
= CFStringGetLength(_CFBundlePlatformInfoPlistName
);
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
);
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
);
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;
504 if (infoPlistURL
) return false;
510 CFRelease(directoryPath
);
511 CFRelease(directoryURL
);
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
;
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
;
533 CFErrorRef error
= NULL
;
534 result
= (CFDictionaryRef
)CFPropertyListCreateWithData(alloc
, infoData
, kCFPropertyListMutableContainers
, NULL
, &error
);
536 if (CFDictionaryGetTypeID() == CFGetTypeID(result
)) {
537 CFDictionarySetValue((CFMutableDictionaryRef
)result
, _kCFBundleInfoPlistURLKey
, finalInfoPlistURL
);
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
);
550 result
= CFDictionaryCreateMutable(alloc
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
551 CFDictionarySetValue((CFMutableDictionaryRef
)result
, _kCFBundleRawInfoPlistURLKey
, finalInfoPlistURL
);
557 if (platformInfoPlistURL
) CFRelease(platformInfoPlistURL
);
558 if (infoPlistURL
) CFRelease(infoPlistURL
);
562 result
= CFDictionaryCreateMutable(alloc
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
565 // process ~ipad, ~iphone, etc.
566 _CFBundleInfoPlistProcessInfoDictionary((CFMutableDictionaryRef
)result
);
571 CF_EXPORT CFDictionaryRef
CFBundleCopyInfoDictionaryForURL(CFURLRef url
) {
572 CFDictionaryRef result
= NULL
;
573 Boolean isDir
= false;
574 if (_CFIsResourceAtURL(url
, &isDir
)) {
576 result
= _CFBundleCopyInfoDictionaryInDirectory(kCFAllocatorSystemDefault
, url
, NULL
);
578 result
= _CFBundleCopyInfoDictionaryInExecutable(url
);
581 if (result
&& (0)) CFRetain(result
); // conditionally put on a retain for a Copy function
585 static Boolean
_CFBundleGetPackageInfoInDirectoryWithInfoDictionary(CFAllocatorRef alloc
, CFURLRef url
, CFDictionaryRef infoDict
, UInt32
*packageType
, UInt32
*packageCreator
) {
586 Boolean retVal
= false, hasType
= false, hasCreator
= false, releaseInfoDict
= false;
588 CFDataRef pkgInfoData
= NULL
;
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
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
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
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.
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;
625 if (pkgInfoData
) CFRelease(pkgInfoData
);
628 infoDict
= _CFBundleCopyInfoDictionaryInDirectory(kCFAllocatorSystemDefault
, url
, NULL
);
629 releaseInfoDict
= true;
632 CFStringRef typeString
= (CFStringRef
)CFDictionaryGetValue(infoDict
, _kCFBundlePackageTypeKey
), creatorString
= (CFStringRef
)CFDictionaryGetValue(infoDict
, _kCFBundleSignatureKey
);
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;
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;
643 if (releaseInfoDict
&& !(0)) CFRelease(infoDict
);
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
) {
652 UniChar buff
[CFMaxPathSize
];
653 CFIndex strLen
, startOfExtension
;
654 CFURLRef absoluteURL
;
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
);
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
)) {
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
)) {
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'
681 // Default to BNDL for generic bundle
682 *packageType
= 0x424e444c; // 'BNDL'
691 CF_EXPORT Boolean
_CFBundleGetPackageInfoInDirectory(CFAllocatorRef alloc
, CFURLRef url
, UInt32
*packageType
, UInt32
*packageCreator
) {
692 return _CFBundleGetPackageInfoInDirectoryWithInfoDictionary(alloc
, url
, NULL
, packageType
, packageCreator
);
695 CF_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; // '????'
701 if (bundleURL
) CFRelease(bundleURL
);
704 CF_EXPORT Boolean
CFBundleGetPackageInfoInDirectory(CFURLRef url
, UInt32
*packageType
, UInt32
*packageCreator
) {
705 return _CFBundleGetPackageInfoInDirectory(kCFAllocatorSystemDefault
, url
, packageType
, packageCreator
);
708 CFDictionaryRef
CFBundleCopyInfoDictionaryInDirectory(CFURLRef url
) {
709 CFDictionaryRef dict
= _CFBundleCopyInfoDictionaryInDirectory(kCFAllocatorSystemDefault
, url
, NULL
);
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
715 static void _CFBundleInfoPlistFixupInfoDictionary(CFBundleRef bundle
, CFMutableDictionaryRef infoDict
) {
717 CFTypeRef unknownVersionValue
= CFDictionaryGetValue(infoDict
, _kCFBundleNumericVersionKey
);
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
);
727 versNum
= CFNumberCreate(CFGetAllocator(bundle
), kCFNumberSInt32Type
, &vers
);
728 CFDictionarySetValue(infoDict
, _kCFBundleNumericVersionKey
, versNum
);
730 } else if (CFGetTypeID(unknownVersionValue
) == CFNumberGetTypeID()) {
731 // Nothing to do here
733 CFDictionaryRemoveValue((CFMutableDictionaryRef
)infoDict
, _kCFBundleNumericVersionKey
);
738 CFDictionaryRef
CFBundleGetInfoDictionary(CFBundleRef bundle
) {
739 __CFSpinLock(&bundle
->_lock
);
740 if (!bundle
->_infoDict
) {
741 bundle
->_infoDict
= _CFBundleCopyInfoDictionaryInDirectoryWithVersion(kCFAllocatorSystemDefault
, bundle
->_url
, bundle
->_version
);
743 // Add or fixup any keys that will be expected later
744 if (bundle
->_infoDict
) _CFBundleInfoPlistFixupInfoDictionary(bundle
, (CFMutableDictionaryRef
)bundle
->_infoDict
);
746 __CFSpinUnlock(&bundle
->_lock
);
748 return bundle
->_infoDict
;
751 CFDictionaryRef
_CFBundleGetLocalInfoDictionary(CFBundleRef bundle
) {
752 return CFBundleGetLocalInfoDictionary(bundle
);
755 CFDictionaryRef
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
);
766 CFStringRef errStr
= NULL
;
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
;
779 #pragma GCC diagnostic pop
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
;
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
;
794 __CFSpinUnlock(&bundle
->_lock
);
796 return localInfoDict
;
799 CFPropertyListRef
_CFBundleGetValueForInfoKey(CFBundleRef bundle
, CFStringRef key
) {
800 return (CFPropertyListRef
)CFBundleGetValueForInfoDictionaryKey(bundle
, key
);
803 CFTypeRef
CFBundleGetValueForInfoDictionaryKey(CFBundleRef bundle
, CFStringRef key
) {
804 // Look in InfoPlist.strings first. Then look in Info.plist
805 CFTypeRef result
= NULL
;
807 CFDictionaryRef dict
= CFBundleGetLocalInfoDictionary(bundle
);
808 if (dict
) result
= CFDictionaryGetValue(dict
, key
);
810 dict
= CFBundleGetInfoDictionary(bundle
);
811 if (dict
) result
= CFDictionaryGetValue(dict
, key
);
817 CFStringRef
CFBundleGetIdentifier(CFBundleRef bundle
) {
818 CFStringRef bundleID
= NULL
;
819 CFDictionaryRef infoDict
= CFBundleGetInfoDictionary(bundle
);
820 if (infoDict
) bundleID
= (CFStringRef
)CFDictionaryGetValue(infoDict
, kCFBundleIdentifierKey
);