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