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/*      CFBundle_Resources.c
25        Copyright (c) 1999-2013, Apple Inc.  All rights reserved.
26        Responsibility: Tony Parker
27*/
28
29#include "CFBundle_Internal.h"
30#include <CoreFoundation/CFURLAccess.h>
31#include <CoreFoundation/CFPropertyList.h>
32#include <CoreFoundation/CFByteOrder.h>
33#include <CoreFoundation/CFNumber.h>
34#include <CoreFoundation/CFLocale.h>
35#include <CoreFoundation/CFPreferences.h>
36#include <string.h>
37#include "CFInternal.h"
38#include <CoreFoundation/CFPriv.h>
39#include <sys/stat.h>
40#include <fcntl.h>
41#include <stdio.h>
42#include <ctype.h>
43#include <errno.h>
44#include <sys/types.h>
45
46#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI || DEPLOYMENT_TARGET_LINUX
47#include <unistd.h>
48#include <sys/sysctl.h>
49#include <sys/stat.h>
50#include <dirent.h>
51#endif
52
53#if DEPLOYMENT_TARGET_MACOSX
54
55#endif
56
57#if DEPLOYMENT_TARGET_WINDOWS
58#include <io.h>
59#include <fcntl.h>
60#include <sys/stat.h>
61#include <errno.h>
62
63#define close _close
64#define write _write
65#define read _read
66#define open _NS_open
67#define stat _NS_stat
68#define fstat _fstat
69#define mkdir(a,b) _NS_mkdir(a)
70#define rmdir _NS_rmdir
71#define unlink _NS_unlink
72
73#endif
74
75#pragma mark -
76#pragma mark Directory Contents and Caches
77
78// These are here for compatibility, but they do nothing anymore
79CF_EXPORT void _CFBundleFlushCachesForURL(CFURLRef url) { }
80CF_EXPORT void _CFBundleFlushCaches(void) { }
81
82#pragma mark -
83#pragma mark Resource URL Lookup
84
85static inline Boolean _CFIsResourceCommon(char *path, Boolean *isDir) {
86    Boolean exists;
87    SInt32 mode;
88    if (_CFGetPathProperties(kCFAllocatorSystemDefault, path, &exists, &mode, NULL, NULL, NULL, NULL) == 0) {
89        if (isDir) *isDir = ((exists && ((mode & S_IFMT) == S_IFDIR)) ? true : false);
90        return (exists && (mode & 0444));
91    }
92    return false;
93}
94
95CF_PRIVATE Boolean _CFIsResourceAtURL(CFURLRef url, Boolean *isDir) {
96    char path[CFMaxPathSize];
97    if (!CFURLGetFileSystemRepresentation(url, true, (uint8_t *)path, CFMaxPathLength)) return false;
98
99    return _CFIsResourceCommon(path, isDir);
100}
101
102CF_PRIVATE Boolean _CFIsResourceAtPath(CFStringRef path, Boolean *isDir) {
103    char pathBuf[CFMaxPathSize];
104    if (!CFStringGetFileSystemRepresentation(path, pathBuf, CFMaxPathSize)) return false;
105
106    return _CFIsResourceCommon(pathBuf, isDir);
107}
108
109
110static CFStringRef _CFBundleGetResourceDirForVersion(uint8_t version) {
111    if (1 == version) {
112        return _CFBundleSupportFilesDirectoryName1WithResources;
113    } else if (2 == version) {
114        return _CFBundleSupportFilesDirectoryName2WithResources;
115    } else if (0 == version) {
116        return _CFBundleResourcesDirectoryName;
117    }
118    return CFSTR("");
119}
120
121CF_PRIVATE void _CFBundleAppendResourceDir(CFMutableStringRef path, uint8_t version) {
122    if (1 == version) {
123        // /path/to/bundle/Support Files/
124        CFStringAppend(path, _CFBundleSupportFilesDirectoryName1);
125        _CFAppendTrailingPathSlash2(path);
126    } else if (2 == version) {
127        // /path/to/bundle/Contents/
128        CFStringAppend(path, _CFBundleSupportFilesDirectoryName2);
129        _CFAppendTrailingPathSlash2(path);
130    }
131    if (0 == version || 1 == version || 2 == version) {
132        // /path/to/bundle/<above>/Resources
133        CFStringAppend(path, _CFBundleResourcesDirectoryName);
134    }
135}
136
137CF_EXPORT CFURLRef CFBundleCopyResourceURL(CFBundleRef bundle, CFStringRef resourceName, CFStringRef resourceType, CFStringRef subDirName) {
138    if (!bundle) return NULL;
139    CFURLRef result = (CFURLRef) _CFBundleCopyFindResources(bundle, NULL, NULL, resourceName, resourceType, subDirName, NULL, NO, NO, NULL);
140    return result;
141}
142
143CF_EXPORT CFArrayRef CFBundleCopyResourceURLsOfType(CFBundleRef bundle, CFStringRef resourceType, CFStringRef subDirName) {
144    if (!bundle) return CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
145    CFArrayRef result = (CFArrayRef) _CFBundleCopyFindResources(bundle, NULL, NULL, NULL, resourceType, subDirName, NULL, YES, NO, NULL);
146    return result;
147}
148
149CF_EXPORT CFURLRef _CFBundleCopyResourceURLForLanguage(CFBundleRef bundle, CFStringRef resourceName, CFStringRef resourceType, CFStringRef subDirName, CFStringRef language) {
150    return CFBundleCopyResourceURLForLocalization(bundle, resourceName, resourceType, subDirName, language);
151}
152
153CF_EXPORT CFURLRef CFBundleCopyResourceURLForLocalization(CFBundleRef bundle, CFStringRef resourceName, CFStringRef resourceType, CFStringRef subDirName, CFStringRef localizationName) {
154    if (!bundle) return NULL;
155    CFURLRef result = (CFURLRef) _CFBundleCopyFindResources(bundle, NULL, NULL, resourceName, resourceType, subDirName, localizationName, NO, YES, NULL);
156    return result;
157}
158
159CF_EXPORT CFArrayRef _CFBundleCopyResourceURLsOfTypeForLanguage(CFBundleRef bundle, CFStringRef resourceType, CFStringRef subDirName, CFStringRef language) {
160    return CFBundleCopyResourceURLsOfTypeForLocalization(bundle, resourceType, subDirName, language);
161}
162
163CF_EXPORT CFArrayRef CFBundleCopyResourceURLsOfTypeForLocalization(CFBundleRef bundle, CFStringRef resourceType, CFStringRef subDirName, CFStringRef localizationName) {
164    if (!bundle) return CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
165    CFArrayRef result = (CFArrayRef) _CFBundleCopyFindResources(bundle, NULL, NULL, NULL, resourceType, subDirName, localizationName, YES, YES, NULL);
166    return result;
167}
168
169CF_EXPORT CFURLRef CFBundleCopyResourceURLInDirectory(CFURLRef bundleURL, CFStringRef resourceName, CFStringRef resourceType, CFStringRef subDirName) {
170    CFURLRef result = NULL;
171    unsigned char buff[CFMaxPathSize];
172    CFURLRef newURL = NULL;
173
174    if (!CFURLGetFileSystemRepresentation(bundleURL, true, buff, CFMaxPathSize)) return NULL;
175
176    newURL = CFURLCreateFromFileSystemRepresentation(kCFAllocatorSystemDefault, buff, strlen((char *)buff), true);
177    if (!newURL) newURL = (CFURLRef)CFRetain(bundleURL);
178    if (_CFBundleCouldBeBundle(newURL)) {
179        result = (CFURLRef) _CFBundleCopyFindResources(NULL, bundleURL, NULL, resourceName, resourceType, subDirName, NULL, NO, NO, NULL);
180    }
181    if (newURL) CFRelease(newURL);
182    return result;
183}
184
185CF_EXPORT CFArrayRef CFBundleCopyResourceURLsOfTypeInDirectory(CFURLRef bundleURL, CFStringRef resourceType, CFStringRef subDirName) {
186    CFArrayRef array = NULL;
187    unsigned char buff[CFMaxPathSize];
188    CFURLRef newURL = NULL;
189
190    if (!CFURLGetFileSystemRepresentation(bundleURL, true, buff, CFMaxPathSize)) return NULL;
191
192    newURL = CFURLCreateFromFileSystemRepresentation(kCFAllocatorSystemDefault, buff, strlen((char *)buff), true);
193    if (!newURL) newURL = (CFURLRef)CFRetain(bundleURL);
194    if (_CFBundleCouldBeBundle(newURL)) {
195        array = (CFArrayRef) _CFBundleCopyFindResources(NULL, bundleURL, NULL, NULL, resourceType, subDirName, NULL, YES, NO, NULL);
196    }
197    if (newURL) CFRelease(newURL);
198    return array;
199}
200
201#pragma mark -
202#pragma mark Lanaguages and Locales
203
204// string, with groups of 6 characters being 1 element in the array of locale abbreviations
205const char * __CFBundleLocaleAbbreviationsArray =
206    "en_US\0"      "fr_FR\0"      "en_GB\0"      "de_DE\0"      "it_IT\0"      "nl_NL\0"      "nl_BE\0"      "sv_SE\0"
207    "es_ES\0"      "da_DK\0"      "pt_PT\0"      "fr_CA\0"      "nb_NO\0"      "he_IL\0"      "ja_JP\0"      "en_AU\0"
208    "ar\0\0\0\0"   "fi_FI\0"      "fr_CH\0"      "de_CH\0"      "el_GR\0"      "is_IS\0"      "mt_MT\0"      "el_CY\0"
209    "tr_TR\0"      "hr_HR\0"      "nl_NL\0"      "nl_BE\0"      "en_CA\0"      "en_CA\0"      "pt_PT\0"      "nb_NO\0"
210    "da_DK\0"      "hi_IN\0"      "ur_PK\0"      "tr_TR\0"      "it_CH\0"      "en\0\0\0\0"   "\0\0\0\0\0\0" "ro_RO\0"
211    "grc\0\0\0"    "lt_LT\0"      "pl_PL\0"      "hu_HU\0"      "et_EE\0"      "lv_LV\0"      "se\0\0\0\0"   "fo_FO\0"
212    "fa_IR\0"      "ru_RU\0"      "ga_IE\0"      "ko_KR\0"      "zh_CN\0"      "zh_TW\0"      "th_TH\0"      "\0\0\0\0\0\0"
213    "cs_CZ\0"      "sk_SK\0"      "\0\0\0\0\0\0" "hu_HU\0"      "bn\0\0\0\0"   "be_BY\0"      "uk_UA\0"      "\0\0\0\0\0\0"
214    "el_GR\0"      "sr_CS\0"      "sl_SI\0"      "mk_MK\0"      "hr_HR\0"      "\0\0\0\0\0\0" "de_DE\0"      "pt_BR\0"
215    "bg_BG\0"      "ca_ES\0"      "\0\0\0\0\0\0" "gd\0\0\0\0"   "gv\0\0\0\0"   "br\0\0\0\0"   "iu_CA\0"      "cy\0\0\0\0"
216    "en_CA\0"      "ga_IE\0"      "en_CA\0"      "dz_BT\0"      "hy_AM\0"      "ka_GE\0"      "es_XL\0"      "es_ES\0"
217    "to_TO\0"      "pl_PL\0"      "ca_ES\0"      "fr\0\0\0\0"   "de_AT\0"      "es_XL\0"      "gu_IN\0"      "pa\0\0\0\0"
218    "ur_IN\0"      "vi_VN\0"      "fr_BE\0"      "uz_UZ\0"      "en_SG\0"      "nn_NO\0"      "af_ZA\0"      "eo\0\0\0\0"
219    "mr_IN\0"      "bo\0\0\0\0"   "ne_NP\0"      "kl\0\0\0\0"   "en_IE\0";
220
221#define NUM_LOCALE_ABBREVIATIONS        109
222#define LOCALE_ABBREVIATION_LENGTH      6
223
224static const char * const __CFBundleLanguageNamesArray[] = {
225    "English",      "French",       "German",       "Italian",      "Dutch",        "Swedish",      "Spanish",      "Danish",
226    "Portuguese",   "Norwegian",    "Hebrew",       "Japanese",     "Arabic",       "Finnish",      "Greek",        "Icelandic",
227    "Maltese",      "Turkish",      "Croatian",     "Chinese",      "Urdu",         "Hindi",        "Thai",         "Korean",
228    "Lithuanian",   "Polish",       "Hungarian",    "Estonian",     "Latvian",      "Sami",         "Faroese",      "Farsi",
229    "Russian",      "Chinese",      "Dutch",        "Irish",        "Albanian",     "Romanian",     "Czech",        "Slovak",
230    "Slovenian",    "Yiddish",      "Serbian",      "Macedonian",   "Bulgarian",    "Ukrainian",    "Byelorussian", "Uzbek",
231    "Kazakh",       "Azerbaijani",  "Azerbaijani",  "Armenian",     "Georgian",     "Moldavian",    "Kirghiz",      "Tajiki",
232    "Turkmen",      "Mongolian",    "Mongolian",    "Pashto",       "Kurdish",      "Kashmiri",     "Sindhi",       "Tibetan",
233    "Nepali",       "Sanskrit",     "Marathi",      "Bengali",      "Assamese",     "Gujarati",     "Punjabi",      "Oriya",
234    "Malayalam",    "Kannada",      "Tamil",        "Telugu",       "Sinhalese",    "Burmese",      "Khmer",        "Lao",
235    "Vietnamese",   "Indonesian",   "Tagalog",      "Malay",        "Malay",        "Amharic",      "Tigrinya",     "Oromo",
236    "Somali",       "Swahili",      "Kinyarwanda",  "Rundi",        "Nyanja",       "Malagasy",     "Esperanto",    "",
237    "",             "",             "",             "",             "",             "",             "",             "",
238    "",             "",             "",             "",             "",             "",             "",             "",
239    "",             "",             "",             "",             "",             "",             "",             "",
240    "",             "",             "",             "",             "",             "",             "",             "",
241    "Welsh",        "Basque",       "Catalan",      "Latin",        "Quechua",      "Guarani",      "Aymara",       "Tatar",
242    "Uighur",       "Dzongkha",     "Javanese",     "Sundanese",    "Galician",     "Afrikaans",    "Breton",       "Inuktitut",
243    "Scottish",     "Manx",         "Irish",        "Tongan",       "Greek",        "Greenlandic",  "Azerbaijani",  "Nynorsk"
244};
245
246#define NUM_LANGUAGE_NAMES      152
247#define LANGUAGE_NAME_LENGTH    13
248
249// string, with groups of 3 characters being 1 element in the array of abbreviations
250const char * __CFBundleLanguageAbbreviationsArray =
251    "en\0"   "fr\0"   "de\0"   "it\0"   "nl\0"   "sv\0"   "es\0"   "da\0"
252    "pt\0"   "nb\0"   "he\0"   "ja\0"   "ar\0"   "fi\0"   "el\0"   "is\0"
253    "mt\0"   "tr\0"   "hr\0"   "zh\0"   "ur\0"   "hi\0"   "th\0"   "ko\0"
254    "lt\0"   "pl\0"   "hu\0"   "et\0"   "lv\0"   "se\0"   "fo\0"   "fa\0"
255    "ru\0"   "zh\0"   "nl\0"   "ga\0"   "sq\0"   "ro\0"   "cs\0"   "sk\0"
256    "sl\0"   "yi\0"   "sr\0"   "mk\0"   "bg\0"   "uk\0"   "be\0"   "uz\0"
257    "kk\0"   "az\0"   "az\0"   "hy\0"   "ka\0"   "mo\0"   "ky\0"   "tg\0"
258    "tk\0"   "mn\0"   "mn\0"   "ps\0"   "ku\0"   "ks\0"   "sd\0"   "bo\0"
259    "ne\0"   "sa\0"   "mr\0"   "bn\0"   "as\0"   "gu\0"   "pa\0"   "or\0"
260    "ml\0"   "kn\0"   "ta\0"   "te\0"   "si\0"   "my\0"   "km\0"   "lo\0"
261    "vi\0"   "id\0"   "tl\0"   "ms\0"   "ms\0"   "am\0"   "ti\0"   "om\0"
262    "so\0"   "sw\0"   "rw\0"   "rn\0"   "\0\0\0" "mg\0"   "eo\0"   "\0\0\0"
263    "\0\0\0" "\0\0\0" "\0\0\0" "\0\0\0" "\0\0\0" "\0\0\0" "\0\0\0" "\0\0\0"
264    "\0\0\0" "\0\0\0" "\0\0\0" "\0\0\0" "\0\0\0" "\0\0\0" "\0\0\0" "\0\0\0"
265    "\0\0\0" "\0\0\0" "\0\0\0" "\0\0\0" "\0\0\0" "\0\0\0" "\0\0\0" "\0\0\0"
266    "\0\0\0" "\0\0\0" "\0\0\0" "\0\0\0" "\0\0\0" "\0\0\0" "\0\0\0" "\0\0\0"
267    "cy\0"   "eu\0"   "ca\0"   "la\0"   "qu\0"   "gn\0"   "ay\0"   "tt\0"
268    "ug\0"   "dz\0"   "jv\0"   "su\0"   "gl\0"   "af\0"   "br\0"   "iu\0"
269    "gd\0"   "gv\0"   "ga\0"   "to\0"   "el\0"   "kl\0"   "az\0"   "nn\0";
270
271#define NUM_LANGUAGE_ABBREVIATIONS      152
272#define LANGUAGE_ABBREVIATION_LENGTH    3
273
274#if defined(__CONSTANT_CFSTRINGS__)
275
276// These are not necessarily common localizations per se, but localizations for which the full language name is still in common use.
277// These are used to provide a fast path for it (other localizations usually use the abbreviation, which is even faster).
278static CFStringRef const __CFBundleCommonLanguageNamesArray[] = {CFSTR("English"), CFSTR("French"), CFSTR("German"), CFSTR("Italian"), CFSTR("Dutch"), CFSTR("Spanish"), CFSTR("Japanese")};
279static CFStringRef const __CFBundleCommonLanguageAbbreviationsArray[] = {CFSTR("en"), CFSTR("fr"), CFSTR("de"), CFSTR("it"), CFSTR("nl"), CFSTR("es"), CFSTR("ja")};
280
281#define NUM_COMMON_LANGUAGE_NAMES 7
282
283#endif /* __CONSTANT_CFSTRINGS__ */
284
285static const SInt32 __CFBundleScriptCodesArray[] = {
286     0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  5,  1,  4,  0,  6,  0,
287     0,  0,  0,  2,  4,  9, 21,  3, 29, 29, 29, 29, 29,  0,  0,  4,
288     7, 25,  0,  0,  0,  0, 29, 29,  0,  5,  7,  7,  7,  7,  7,  7,
289     7,  7,  4, 24, 23,  7,  7,  7,  7, 27,  7,  4,  4,  4,  4, 26,
290     9,  9,  9, 13, 13, 11, 10, 12, 17, 16, 14, 15, 18, 19, 20, 22,
291    30,  0,  0,  0,  4, 28, 28, 28,  0,  0,  0,  0,  0,  0,  0,  0,
292     0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
293     0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
294     0,  0,  0,  0,  0,  0,  0,  7,  4, 26,  0,  0,  0,  0,  0, 28,
295     0,  0,  0,  0,  6,  0,  0,  0
296};
297
298static const CFStringEncoding __CFBundleStringEncodingsArray[] = {
299     0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  5,  1,  4,  0,  6, 37,
300     0, 35, 36,  2,  4,  9, 21,  3, 29, 29, 29, 29, 29,  0, 37, 0x8C,
301     7, 25,  0, 39,  0, 38, 29, 29, 36,  5,  7,  7,  7, 0x98,  7,  7,
302     7,  7,  4, 24, 23,  7,  7,  7,  7, 27,  7,  4,  4,  4,  4, 26,
303     9,  9,  9, 13, 13, 11, 10, 12, 17, 16, 14, 15, 18, 19, 20, 22,
304    30,  0,  0,  0,  4, 28, 28, 28,  0,  0,  0,  0,  0,  0,  0,  0,
305     0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
306     0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
307    39,  0,  0,  0,  0,  0,  0,  7,  4, 26,  0,  0,  0,  0, 39, 0xEC,
308    39, 39, 40,  0,  6,  0,  0,  0
309};
310
311static SInt32 _CFBundleGetLanguageCodeForLocalization(CFStringRef localizationName) {
312    SInt32 result = -1, i;
313    char buff[256];
314    CFIndex length = CFStringGetLength(localizationName);
315    if (length >= LANGUAGE_ABBREVIATION_LENGTH - 1 && length <= 255 && CFStringGetCString(localizationName, buff, 255, kCFStringEncodingASCII)) {
316        buff[255] = '\0';
317        for (i = 0; -1 == result && i < NUM_LANGUAGE_NAMES; i++) {
318            if (0 == strcmp(buff, __CFBundleLanguageNamesArray[i])) result = i;
319        }
320        if (0 == strcmp(buff, "zh_TW") || 0 == strcmp(buff, "zh-Hant")) result = 19; else if (0 == strcmp(buff, "zh_CN") || 0 == strcmp(buff, "zh-Hans")) result = 33; // hack for mixed-up Chinese language codes
321        if (-1 == result && (length == LANGUAGE_ABBREVIATION_LENGTH - 1 || !isalpha(buff[LANGUAGE_ABBREVIATION_LENGTH - 1]))) {
322            buff[LANGUAGE_ABBREVIATION_LENGTH - 1] = '\0';
323            if ('n' == buff[0] && 'o' == buff[1]) result = 9;  // hack for Norwegian
324            for (i = 0; -1 == result && i < NUM_LANGUAGE_ABBREVIATIONS * LANGUAGE_ABBREVIATION_LENGTH; i += LANGUAGE_ABBREVIATION_LENGTH) {
325                if (buff[0] == *(__CFBundleLanguageAbbreviationsArray + i + 0) && buff[1] == *(__CFBundleLanguageAbbreviationsArray + i + 1)) result = i / LANGUAGE_ABBREVIATION_LENGTH;
326            }
327        }
328    }
329    return result;
330}
331
332static CFStringRef _CFBundleCopyLanguageAbbreviationForLanguageCode(SInt32 languageCode) {
333    CFStringRef result = NULL;
334    if (0 <= languageCode && languageCode < NUM_LANGUAGE_ABBREVIATIONS) {
335        const char *languageAbbreviation = __CFBundleLanguageAbbreviationsArray + languageCode * LANGUAGE_ABBREVIATION_LENGTH;
336        if (languageAbbreviation && *languageAbbreviation != '\0') result = CFStringCreateWithCStringNoCopy(kCFAllocatorSystemDefault, languageAbbreviation, kCFStringEncodingASCII, kCFAllocatorNull);
337    }
338    return result;
339}
340
341CF_INLINE CFStringRef _CFBundleCopyLanguageNameForLanguageCode(SInt32 languageCode) {
342    CFStringRef result = NULL;
343    if (0 <= languageCode && languageCode < NUM_LANGUAGE_NAMES) {
344        const char *languageName = __CFBundleLanguageNamesArray[languageCode];
345        if (languageName && *languageName != '\0') result = CFStringCreateWithCStringNoCopy(kCFAllocatorSystemDefault, languageName, kCFStringEncodingASCII, kCFAllocatorNull);
346    }
347    return result;
348}
349
350CF_INLINE CFStringRef _CFBundleCopyLanguageAbbreviationForLocalization(CFStringRef localizationName) {
351    CFStringRef result = NULL;
352    SInt32 languageCode = _CFBundleGetLanguageCodeForLocalization(localizationName);
353    if (languageCode >= 0) {
354        result = _CFBundleCopyLanguageAbbreviationForLanguageCode(languageCode);
355    } else {
356        CFIndex length = CFStringGetLength(localizationName);
357        if (length == LANGUAGE_ABBREVIATION_LENGTH - 1 || (length > LANGUAGE_ABBREVIATION_LENGTH - 1 && CFStringGetCharacterAtIndex(localizationName, LANGUAGE_ABBREVIATION_LENGTH - 1) == '_')) {
358            result = CFStringCreateWithSubstring(kCFAllocatorSystemDefault, localizationName, CFRangeMake(0, LANGUAGE_ABBREVIATION_LENGTH - 1));
359        }
360    }
361    return result;
362}
363
364CF_INLINE CFStringRef _CFBundleCopyModifiedLocalization(CFStringRef localizationName) {
365    CFMutableStringRef result = NULL;
366    CFIndex length = CFStringGetLength(localizationName);
367    if (length >= 4) {
368        UniChar c = CFStringGetCharacterAtIndex(localizationName, 2);
369        if ('-' == c || '_' == c) {
370            result = CFStringCreateMutableCopy(kCFAllocatorSystemDefault, length, localizationName);
371            CFStringReplace(result, CFRangeMake(2, 1), ('-' == c) ? CFSTR("_") : CFSTR("-"));
372        }
373    }
374    return result;
375}
376
377CF_INLINE CFStringRef _CFBundleCopyLanguageNameForLocalization(CFStringRef localizationName) {
378    CFStringRef result = NULL;
379    SInt32 languageCode = _CFBundleGetLanguageCodeForLocalization(localizationName);
380    if (languageCode >= 0) {
381        result = _CFBundleCopyLanguageNameForLanguageCode(languageCode);
382    } else {
383        result = (CFStringRef)CFStringCreateCopy(kCFAllocatorSystemDefault, localizationName);
384    }
385    return result;
386}
387
388static SInt32 _CFBundleGetLanguageCodeForRegionCode(SInt32 regionCode) {
389    SInt32 result = -1, i;
390    if (52 == regionCode) {     // hack for mixed-up Chinese language codes
391        result = 33;
392    } else if (0 <= regionCode && regionCode < NUM_LOCALE_ABBREVIATIONS) {
393        const char *localeAbbreviation = __CFBundleLocaleAbbreviationsArray + regionCode * LOCALE_ABBREVIATION_LENGTH;
394        if (localeAbbreviation && *localeAbbreviation != '\0') {
395            for (i = 0; -1 == result && i < NUM_LANGUAGE_ABBREVIATIONS * LANGUAGE_ABBREVIATION_LENGTH; i += LANGUAGE_ABBREVIATION_LENGTH) {
396                if (localeAbbreviation[0] == *(__CFBundleLanguageAbbreviationsArray + i + 0) && localeAbbreviation[1] == *(__CFBundleLanguageAbbreviationsArray + i + 1)) result = i / LANGUAGE_ABBREVIATION_LENGTH;
397            }
398        }
399    }
400    return result;
401}
402
403static SInt32 _CFBundleGetRegionCodeForLanguageCode(SInt32 languageCode) {
404    SInt32 result = -1, i;
405    if (19 == languageCode) {   // hack for mixed-up Chinese language codes
406        result = 53;
407    } else if (0 <= languageCode && languageCode < NUM_LANGUAGE_ABBREVIATIONS) {
408        const char *languageAbbreviation = __CFBundleLanguageAbbreviationsArray + languageCode * LANGUAGE_ABBREVIATION_LENGTH;
409        if (languageAbbreviation && *languageAbbreviation != '\0') {
410            for (i = 0; -1 == result && i < NUM_LOCALE_ABBREVIATIONS * LOCALE_ABBREVIATION_LENGTH; i += LOCALE_ABBREVIATION_LENGTH) {
411                if (*(__CFBundleLocaleAbbreviationsArray + i + 0) == languageAbbreviation[0] && *(__CFBundleLocaleAbbreviationsArray + i + 1) == languageAbbreviation[1]) result = i / LOCALE_ABBREVIATION_LENGTH;
412            }
413        }
414    }
415    if (25 == result) result = 68;
416    if (28 == result) result = 82;
417    return result;
418}
419
420static SInt32 _CFBundleGetRegionCodeForLocalization(CFStringRef localizationName) {
421    SInt32 result = -1, i;
422    char buff[LOCALE_ABBREVIATION_LENGTH];
423    CFIndex length = CFStringGetLength(localizationName);
424    if (length >= LANGUAGE_ABBREVIATION_LENGTH - 1 && length <= LOCALE_ABBREVIATION_LENGTH - 1 && CFStringGetCString(localizationName, buff, LOCALE_ABBREVIATION_LENGTH, kCFStringEncodingASCII)) {
425        buff[LOCALE_ABBREVIATION_LENGTH - 1] = '\0';
426        for (i = 0; -1 == result && i < NUM_LOCALE_ABBREVIATIONS * LOCALE_ABBREVIATION_LENGTH; i += LOCALE_ABBREVIATION_LENGTH) {
427            if (0 == strcmp(buff, __CFBundleLocaleAbbreviationsArray + i)) result = i / LOCALE_ABBREVIATION_LENGTH;
428        }
429    }
430    if (25 == result) result = 68;
431    if (28 == result) result = 82;
432    if (37 == result) result = 0;
433    if (-1 == result) {
434        SInt32 languageCode = _CFBundleGetLanguageCodeForLocalization(localizationName);
435        result = _CFBundleGetRegionCodeForLanguageCode(languageCode);
436    }
437    return result;
438}
439
440CF_PRIVATE CFStringRef _CFBundleCopyLocaleAbbreviationForRegionCode(SInt32 regionCode) {
441    CFStringRef result = NULL;
442    if (0 <= regionCode && regionCode < NUM_LOCALE_ABBREVIATIONS) {
443        const char *localeAbbreviation = __CFBundleLocaleAbbreviationsArray + regionCode * LOCALE_ABBREVIATION_LENGTH;
444        if (localeAbbreviation && *localeAbbreviation != '\0') {
445            result = CFStringCreateWithCStringNoCopy(kCFAllocatorSystemDefault, localeAbbreviation, kCFStringEncodingASCII, kCFAllocatorNull);
446        }
447    }
448    return result;
449}
450
451CF_EXPORT Boolean CFBundleGetLocalizationInfoForLocalization(CFStringRef localizationName, SInt32 *languageCode, SInt32 *regionCode, SInt32 *scriptCode, CFStringEncoding *stringEncoding) {
452    Boolean retval = false;
453    SInt32 language = -1, region = -1, script = 0;
454    CFStringEncoding encoding = kCFStringEncodingMacRoman;
455    if (!localizationName) {
456        CFBundleRef mainBundle = CFBundleGetMainBundle();
457        CFArrayRef languages = NULL;
458        if (mainBundle) {
459            languages = _CFBundleGetLanguageSearchList(mainBundle);
460            if (languages) CFRetain(languages);
461        }
462        if (!languages) languages = _CFBundleCopyUserLanguages();
463        if (languages && CFArrayGetCount(languages) > 0) localizationName = (CFStringRef)CFArrayGetValueAtIndex(languages, 0);
464    }
465    if (localizationName) {
466        LangCode langCode = -1;
467        RegionCode regCode = -1;
468        ScriptCode scrCode = 0;
469        CFStringEncoding enc = kCFStringEncodingMacRoman;
470        retval = CFLocaleGetLanguageRegionEncodingForLocaleIdentifier(localizationName, &langCode, &regCode, &scrCode, &enc);
471        if (retval) {
472            language = langCode;
473            region = regCode;
474            script = scrCode;
475            encoding = enc;
476        }
477    }
478    if (!retval) {
479        if (localizationName) {
480            language = _CFBundleGetLanguageCodeForLocalization(localizationName);
481            region = _CFBundleGetRegionCodeForLocalization(localizationName);
482        } else {
483            _CFBundleGetLanguageAndRegionCodes(&language, &region);
484        }
485        if ((language < 0 || language > (int)(sizeof(__CFBundleScriptCodesArray)/sizeof(SInt32))) && region != -1) language = _CFBundleGetLanguageCodeForRegionCode(region);
486        if (region == -1 && language != -1) region = _CFBundleGetRegionCodeForLanguageCode(language);
487        if (language >= 0 && language < (int)(sizeof(__CFBundleScriptCodesArray)/sizeof(SInt32))) {
488            script = __CFBundleScriptCodesArray[language];
489        }
490        if (language >= 0 && language < (int)(sizeof(__CFBundleStringEncodingsArray)/sizeof(CFStringEncoding))) {
491            encoding = __CFBundleStringEncodingsArray[language];
492        }
493        retval = (language != -1 || region != -1);
494    }
495    if (languageCode) *languageCode = language;
496    if (regionCode) *regionCode = region;
497    if (scriptCode) *scriptCode = script;
498    if (stringEncoding) *stringEncoding = encoding;
499    return retval;
500}
501
502CFStringRef CFBundleCopyLocalizationForLocalizationInfo(SInt32 languageCode, SInt32 regionCode, SInt32 scriptCode, CFStringEncoding stringEncoding) {
503    CFStringRef localizationName = NULL;
504    if (!localizationName) localizationName = _CFBundleCopyLocaleAbbreviationForRegionCode(regionCode);
505#if DEPLOYMENT_TARGET_MACOSX
506    if (!localizationName && 0 <= languageCode && languageCode < SHRT_MAX) localizationName = CFLocaleCreateCanonicalLocaleIdentifierFromScriptManagerCodes(kCFAllocatorSystemDefault, (LangCode)languageCode, (RegionCode)-1);
507#endif
508    if (!localizationName) localizationName = _CFBundleCopyLanguageAbbreviationForLanguageCode(languageCode);
509    if (!localizationName) {
510        SInt32 language = -1, scriptLanguage = -1, encodingLanguage = -1;
511        unsigned int i;
512        for (i = 0; language == -1 && i < (sizeof(__CFBundleScriptCodesArray)/sizeof(SInt32)); i++) {
513            if (__CFBundleScriptCodesArray[i] == scriptCode && __CFBundleStringEncodingsArray[i] == stringEncoding) language = i;
514        }
515        for (i = 0; scriptLanguage == -1 && i < (sizeof(__CFBundleScriptCodesArray)/sizeof(SInt32)); i++) {
516            if (__CFBundleScriptCodesArray[i] == scriptCode) scriptLanguage = i;
517        }
518        for (i = 0; encodingLanguage == -1 && i < (sizeof(__CFBundleStringEncodingsArray)/sizeof(CFStringEncoding)); i++) {
519            if (__CFBundleStringEncodingsArray[i] == stringEncoding) encodingLanguage = i;
520        }
521        localizationName = _CFBundleCopyLanguageAbbreviationForLanguageCode(language);
522        if (!localizationName) localizationName = _CFBundleCopyLanguageAbbreviationForLanguageCode(encodingLanguage);
523        if (!localizationName) localizationName = _CFBundleCopyLanguageAbbreviationForLanguageCode(scriptLanguage);
524    }
525    return localizationName;
526}
527
528
529static Boolean CFBundleAllowMixedLocalizations(void) {
530    static Boolean allowMixed = false;
531    static dispatch_once_t once = 0;
532    dispatch_once(&once, ^{
533        CFBundleRef mainBundle = CFBundleGetMainBundle();
534        CFDictionaryRef infoDict = mainBundle ? CFBundleGetInfoDictionary(mainBundle) : NULL;
535        CFTypeRef allowMixedValue = infoDict ? CFDictionaryGetValue(infoDict, _kCFBundleAllowMixedLocalizationsKey) : NULL;
536        if (allowMixedValue) {
537            CFTypeID typeID = CFGetTypeID(allowMixedValue);
538            if (typeID == CFBooleanGetTypeID()) {
539                allowMixed = CFBooleanGetValue((CFBooleanRef)allowMixedValue);
540            } else if (typeID == CFStringGetTypeID()) {
541                allowMixed = (CFStringCompare((CFStringRef)allowMixedValue, CFSTR("true"), kCFCompareCaseInsensitive) == kCFCompareEqualTo || CFStringCompare((CFStringRef)allowMixedValue, CFSTR("YES"), kCFCompareCaseInsensitive) == kCFCompareEqualTo);
542            } else if (typeID == CFNumberGetTypeID()) {
543                SInt32 val = 0;
544                if (CFNumberGetValue((CFNumberRef)allowMixedValue, kCFNumberSInt32Type, &val)) allowMixed = (val != 0);
545            }
546        }
547    });
548    return allowMixed;
549}
550
551// Get a list of localizations for a particular resource directory URL. Uncached. Does not include any predefined localizations from an Info.plist.
552static CFArrayRef _CFBundleCopyURLLocalizations(CFAllocatorRef allocator, CFURLRef url) {
553    __block CFMutableArrayRef result = NULL;
554    CFURLRef absoluteURL = CFURLCopyAbsoluteURL(url);
555    CFStringRef directoryPath = CFURLCopyFileSystemPath(absoluteURL, PLATFORM_PATH_STYLE);
556    CFRelease(absoluteURL);
557
558    CFStringRef lproj = _CFBundleLprojExtensionWithDot;
559    CFIndex lprojLen = CFStringGetLength(lproj);
560
561    _CFIterateDirectory(directoryPath, ^Boolean(CFStringRef fileName, uint8_t fileType) {
562        // See if the fileName ends in .lproj
563        // The comparison starts at the end of the fileName, backed up by the length of .lproj
564        CFIndex fileNameLen = CFStringGetLength(fileName);
565        if (fileNameLen > lprojLen && CFStringCompareWithOptions(fileName, lproj, CFRangeMake(fileNameLen - lprojLen, lprojLen), 0) == kCFCompareEqualTo) {
566            // Chop off the .lproj part before creating a string
567            CFStringRef lprojDirectoryName = CFStringCreateWithSubstring(kCFAllocatorSystemDefault, fileName, CFRangeMake(0, fileNameLen - lprojLen));
568            if (!result) result = CFArrayCreateMutable(allocator, 0, &kCFTypeArrayCallBacks);
569            CFArrayAppendValue(result, lprojDirectoryName);
570            CFRelease(lprojDirectoryName);
571        }
572        return true;
573    });
574
575    CFRelease(directoryPath);
576    return (CFArrayRef)result;
577}
578
579static Boolean _CFBundleTryOnePreferredLprojNameInURL(CFAllocatorRef alloc, CFArrayRef localizations, CFStringRef curLangStr, CFMutableArrayRef lprojNames, Boolean fallBackToLanguage);
580
581CF_EXPORT CFArrayRef CFBundleCopyBundleLocalizations(CFBundleRef bundle) {
582    CFArrayRef result = NULL;
583
584    __CFSpinLock(&bundle->_lock);
585    if (bundle->_lookedForLocalizations) {
586        result = (CFArrayRef)CFRetain(bundle->_localizations);
587        __CFSpinUnlock(&bundle->_lock);
588        return result;
589    }
590    __CFSpinUnlock(&bundle->_lock);
591
592    CFDictionaryRef infoDict = CFBundleGetInfoDictionary(bundle);
593    if (infoDict) {
594        CFArrayRef predefinedLocalizations = (CFArrayRef)CFDictionaryGetValue(infoDict, kCFBundleLocalizationsKey);
595        if (predefinedLocalizations && CFGetTypeID(predefinedLocalizations) != CFArrayGetTypeID()) {
596            CFDictionaryRemoveValue((CFMutableDictionaryRef)infoDict, kCFBundleLocalizationsKey);
597        } else if (predefinedLocalizations) {
598            // <rdar://problem/14255685> Some people put bad things inside this array =(
599            CFMutableArrayRef realPredefinedLocalizations = CFArrayCreateMutable(CFGetAllocator(bundle), CFArrayGetCount(predefinedLocalizations), &kCFTypeArrayCallBacks);
600            for (CFIndex i = 0; i < CFArrayGetCount(predefinedLocalizations); i++) {
601                CFStringRef oneEntry = CFArrayGetValueAtIndex(predefinedLocalizations, i);
602                if (CFGetTypeID(oneEntry) == CFStringGetTypeID() && CFStringGetLength(oneEntry) > 0) {
603                    CFArrayAppendValue(realPredefinedLocalizations, oneEntry);
604                }
605            }
606            result = CFArrayCreateCopy(CFGetAllocator(bundle), realPredefinedLocalizations);
607            CFRelease(realPredefinedLocalizations);
608        }
609    }
610
611    CFURLRef resourcesURL = CFBundleCopyResourcesDirectoryURL(bundle);
612    if (resourcesURL) {
613        CFArrayRef lprojDirectoriesInResources = _CFBundleCopyURLLocalizations(CFGetAllocator(bundle), resourcesURL);
614        if (lprojDirectoriesInResources) {
615            if (result) {
616                // Append the lproj result to the predefined localization array
617                CFMutableArrayRef newResult = CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, result);
618                CFRelease(result);
619                CFArrayAppendArray(newResult, lprojDirectoriesInResources, CFRangeMake(0, CFArrayGetCount(lprojDirectoriesInResources)));
620                CFRelease(lprojDirectoriesInResources);
621                result = newResult;
622            } else {
623                result = lprojDirectoriesInResources;
624            }
625        }
626        CFRelease(resourcesURL);
627    }
628
629    CFStringRef developmentLocalization = CFBundleGetDevelopmentRegion(bundle);
630    if (result) {
631        if (developmentLocalization) {
632            CFRange entireRange = CFRangeMake(0, CFArrayGetCount(result));
633            if (CFArrayContainsValue(result, entireRange, _CFBundleBaseDirectory)) {
634                // Base.lproj contains localizations for the development region. Insert the devleopment region into the existing array if there isn't already a match so that resource lookup doesn't default to another language.
635                CFMutableArrayRef tempArray = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
636                if (tempArray) {
637                    if (!_CFBundleTryOnePreferredLprojNameInURL(kCFAllocatorSystemDefault, result, developmentLocalization, tempArray, false) && CFArrayGetCount(tempArray) == 0) {
638                        CFMutableArrayRef newResult = CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, result);
639                        CFRelease(result);
640                        CFArrayAppendValue(newResult, developmentLocalization);
641                        result = newResult;
642                    }
643                    CFRelease(tempArray);
644                }
645            }
646        }
647    } else {
648        if (developmentLocalization) {
649            result = CFArrayCreate(CFGetAllocator(bundle), (const void **)&developmentLocalization, 1, &kCFTypeArrayCallBacks);
650        } else {
651            result = CFArrayCreate(CFGetAllocator(bundle), NULL, 0, &kCFTypeArrayCallBacks);
652        }
653    }
654
655    // Cache the result.
656    __CFSpinLock(&bundle->_lock);
657    if (bundle->_localizations) CFRelease(bundle->_localizations);
658    bundle->_localizations = (CFArrayRef)CFRetain(result);
659    bundle->_lookedForLocalizations = true;
660    __CFSpinUnlock(&bundle->_lock);
661
662    return result;
663}
664
665CFArrayRef CFBundleCopyLocalizationsForURL(CFURLRef url) {
666    CFArrayRef result = NULL;
667    CFBundleRef bundle = CFBundleCreate(kCFAllocatorSystemDefault, url);
668    CFStringRef devLang = NULL;
669    if (bundle) {
670        result = CFBundleCopyBundleLocalizations(bundle);
671        CFRelease(bundle);
672    } else {
673        CFDictionaryRef infoDict = _CFBundleCopyInfoDictionaryInExecutable(url);
674        if (infoDict) {
675            CFArrayRef predefinedLocalizations = (CFArrayRef)CFDictionaryGetValue(infoDict, kCFBundleLocalizationsKey);
676            if (predefinedLocalizations && CFGetTypeID(predefinedLocalizations) == CFArrayGetTypeID()) result = (CFArrayRef)CFRetain(predefinedLocalizations);
677            if (!result) {
678                devLang = (CFStringRef)CFDictionaryGetValue(infoDict, kCFBundleDevelopmentRegionKey);
679                if (devLang && (CFGetTypeID(devLang) == CFStringGetTypeID() && CFStringGetLength(devLang) > 0)) result = CFArrayCreate(kCFAllocatorSystemDefault, (const void **)&devLang, 1, &kCFTypeArrayCallBacks);
680            }
681            CFRelease(infoDict);
682        }
683    }
684    return result;
685}
686
687extern void *__CFAppleLanguages;
688
689
690CF_PRIVATE CFArrayRef _CFBundleCopyUserLanguages() {
691    static CFArrayRef _CFBundleUserLanguages = NULL;
692    static dispatch_once_t once = 0;
693    dispatch_once(&once, ^{
694        CFArrayRef preferencesArray = NULL;
695        if (__CFAppleLanguages) {
696            CFDataRef data;
697            CFIndex length = strlen((const char *)__CFAppleLanguages);
698            if (length > 0) {
699                data = CFDataCreateWithBytesNoCopy(kCFAllocatorSystemDefault, (const UInt8 *)__CFAppleLanguages, length, kCFAllocatorNull);
700                if (data) {
701                    _CFBundleUserLanguages = (CFArrayRef)CFPropertyListCreateFromXMLData(kCFAllocatorSystemDefault, data, kCFPropertyListImmutable, NULL);
702                    CFRelease(data);
703                }
704            }
705        }
706        if (!_CFBundleUserLanguages && preferencesArray) _CFBundleUserLanguages = (CFArrayRef)CFRetain(preferencesArray);
707        Boolean useEnglishAsBackstop = true;
708        // could perhaps read out of LANG environment variable
709        if (useEnglishAsBackstop && !_CFBundleUserLanguages) {
710            CFStringRef english = CFSTR("en");
711            _CFBundleUserLanguages = CFArrayCreate(kCFAllocatorSystemDefault, (const void **)&english, 1, &kCFTypeArrayCallBacks);
712        }
713        if (_CFBundleUserLanguages && CFGetTypeID(_CFBundleUserLanguages) != CFArrayGetTypeID()) {
714            CFRelease(_CFBundleUserLanguages);
715            _CFBundleUserLanguages = NULL;
716        }
717        if (preferencesArray) CFRelease(preferencesArray);
718    });
719
720    if (_CFBundleUserLanguages) {
721        CFRetain(_CFBundleUserLanguages);
722        return _CFBundleUserLanguages;
723    } else {
724        return NULL;
725    }
726}
727
728CF_EXPORT void _CFBundleGetLanguageAndRegionCodes(SInt32 *languageCode, SInt32 *regionCode) {
729    // an attempt to answer the question, "what language are we running in?"
730    // note that the question cannot be answered fully since it may depend on the bundle
731    SInt32 language = -1, region = -1;
732    CFBundleRef mainBundle = CFBundleGetMainBundle();
733    CFArrayRef languages = NULL;
734    if (mainBundle) {
735        languages = _CFBundleGetLanguageSearchList(mainBundle);
736        if (languages) CFRetain(languages);
737    }
738    if (!languages) languages = _CFBundleCopyUserLanguages();
739    if (languages && CFArrayGetCount(languages) > 0) {
740        CFStringRef localizationName = (CFStringRef)CFArrayGetValueAtIndex(languages, 0);
741        Boolean retval = false;
742        LangCode langCode = -1;
743        RegionCode regCode = -1;
744        retval = CFLocaleGetLanguageRegionEncodingForLocaleIdentifier(localizationName, &langCode, &regCode, NULL, NULL);
745        if (retval) {
746            language = langCode;
747            region = regCode;
748        }
749        if (!retval) {
750            language = _CFBundleGetLanguageCodeForLocalization(localizationName);
751            region = _CFBundleGetRegionCodeForLocalization(localizationName);
752        }
753    } else {
754        language = 0;
755        region = 0;
756    }
757    if (language == -1 && region != -1) language = _CFBundleGetLanguageCodeForRegionCode(region);
758    if (region == -1 && language != -1) region = _CFBundleGetRegionCodeForLanguageCode(language);
759    if (languages) CFRelease(languages);
760    if (languageCode) *languageCode = language;
761    if (regionCode) *regionCode = region;
762}
763
764
765
766static Boolean _CFBundleTryOnePreferredLprojNameInArray(CFArrayRef array, CFStringRef curLangStr, CFMutableArrayRef lprojNames, Boolean fallBackToLanguage) {
767    CFRange range = CFRangeMake(0, CFArrayGetCount(array));
768    if (range.length == 0) return false;
769
770    Boolean foundOne = false, specifiesScript = false;
771    CFStringRef altLangStr = NULL, modifiedLangStr = NULL, languageAbbreviation = NULL, languageName = NULL, canonicalLanguageIdentifier = NULL, canonicalLanguageAbbreviation = NULL;
772    CFMutableDictionaryRef canonicalLanguageIdentifiers = NULL;
773
774    if (CFArrayContainsValue(array, range, curLangStr)) {
775        if (!CFArrayContainsValue(lprojNames, CFRangeMake(0, CFArrayGetCount(lprojNames)), curLangStr)) CFArrayAppendValue(lprojNames, curLangStr);
776        foundOne = true;
777        if (range.length == 1 || CFStringGetLength(curLangStr) <= 2) return foundOne;
778    }
779    if (range.length == 1 && CFArrayContainsValue(array, range, CFSTR("default"))) return foundOne;
780
781#if defined(__CONSTANT_CFSTRINGS__)
782    if (!altLangStr) {
783        CFIndex idx;
784        for (idx = 0; !altLangStr && idx < NUM_COMMON_LANGUAGE_NAMES; idx++) {
785            if (CFEqual(curLangStr, __CFBundleCommonLanguageAbbreviationsArray[idx])) altLangStr = __CFBundleCommonLanguageNamesArray[idx];
786            else if (CFEqual(curLangStr, __CFBundleCommonLanguageNamesArray[idx])) altLangStr = __CFBundleCommonLanguageAbbreviationsArray[idx];
787        }
788    }
789    if (foundOne && altLangStr) return foundOne;
790#endif /* __CONSTANT_CFSTRINGS__ */
791
792    if (altLangStr && CFArrayContainsValue(array, range, altLangStr)) {
793        if (!CFArrayContainsValue(lprojNames, CFRangeMake(0, CFArrayGetCount(lprojNames)), altLangStr)) CFArrayAppendValue(lprojNames, altLangStr);
794        foundOne = true;
795        return foundOne;
796    }
797
798    if (!altLangStr && (modifiedLangStr = _CFBundleCopyModifiedLocalization(curLangStr))) {
799        if (CFArrayContainsValue(array, range, modifiedLangStr)) {
800            if (!CFArrayContainsValue(lprojNames, CFRangeMake(0, CFArrayGetCount(lprojNames)), modifiedLangStr)) CFArrayAppendValue(lprojNames, modifiedLangStr);
801            foundOne = true;
802        }
803    }
804
805    if (!specifiesScript && (foundOne || fallBackToLanguage) && !altLangStr && (languageAbbreviation = _CFBundleCopyLanguageAbbreviationForLocalization(curLangStr)) && !CFEqual(curLangStr, languageAbbreviation)) {
806        if (CFArrayContainsValue(array, range, languageAbbreviation)) {
807            if (!CFArrayContainsValue(lprojNames, CFRangeMake(0, CFArrayGetCount(lprojNames)), languageAbbreviation)) CFArrayAppendValue(lprojNames, languageAbbreviation);
808            foundOne = true;
809        }
810    }
811    if (!specifiesScript && (foundOne || fallBackToLanguage) && !altLangStr && (languageName = _CFBundleCopyLanguageNameForLocalization(curLangStr)) && !CFEqual(curLangStr, languageName)) {
812        if (CFArrayContainsValue(array, range, languageName)) {
813            if (!CFArrayContainsValue(lprojNames, CFRangeMake(0, CFArrayGetCount(lprojNames)), languageName)) CFArrayAppendValue(lprojNames, languageName);
814            foundOne = true;
815        }
816    }
817    if (modifiedLangStr) CFRelease(modifiedLangStr);
818    if (languageAbbreviation) CFRelease(languageAbbreviation);
819    if (languageName) CFRelease(languageName);
820    if (canonicalLanguageIdentifier) CFRelease(canonicalLanguageIdentifier);
821    if (canonicalLanguageIdentifiers) CFRelease(canonicalLanguageIdentifiers);
822    if (canonicalLanguageAbbreviation) CFRelease(canonicalLanguageAbbreviation);
823    return foundOne;
824}
825
826// localizations array must include both predefined and actual lproj localizations
827static Boolean _CFBundleTryOnePreferredLprojNameInURL(CFAllocatorRef alloc, CFArrayRef localizations, CFStringRef curLangStr, CFMutableArrayRef lprojNames, Boolean fallBackToLanguage) {
828    CFStringRef altLangStr = NULL, modifiedLangStr = NULL, languageAbbreviation = NULL, languageName = NULL, canonicalLanguageIdentifier = NULL, canonicalLanguageAbbreviation = NULL;
829    CFMutableDictionaryRef canonicalLanguageIdentifiers = NULL;
830    Boolean foundOne = false, specifiesScript = false;
831
832    if (!localizations) return false;
833
834    CFRange localizationsRange = CFRangeMake(0, CFArrayGetCount(localizations));
835
836    // this use of contents is only checking for language strings - it could get the list of existing localizations and use that instead
837    if (CFArrayContainsValue(localizations, localizationsRange, curLangStr)) {
838        if (!CFArrayContainsValue(lprojNames, CFRangeMake(0, CFArrayGetCount(lprojNames)), curLangStr)) CFArrayAppendValue(lprojNames, curLangStr);
839        foundOne = true;
840        if (CFStringGetLength(curLangStr) <= 2) {
841            return foundOne;
842        }
843    }
844
845#if defined(__CONSTANT_CFSTRINGS__)
846    if (!altLangStr) {
847        CFIndex idx;
848        for (idx = 0; !altLangStr && idx < NUM_COMMON_LANGUAGE_NAMES; idx++) {
849            if (CFEqual(curLangStr, __CFBundleCommonLanguageAbbreviationsArray[idx])) altLangStr = __CFBundleCommonLanguageNamesArray[idx];
850            else if (CFEqual(curLangStr, __CFBundleCommonLanguageNamesArray[idx])) altLangStr = __CFBundleCommonLanguageAbbreviationsArray[idx];
851        }
852    }
853#endif /* __CONSTANT_CFSTRINGS__ */
854    if (foundOne && altLangStr) {
855        return foundOne;
856    }
857    if (altLangStr) {
858        if (CFArrayContainsValue(localizations, localizationsRange, altLangStr)) {
859            if (!CFArrayContainsValue(lprojNames, CFRangeMake(0, CFArrayGetCount(lprojNames)), altLangStr)) CFArrayAppendValue(lprojNames, altLangStr);
860            foundOne = true;
861            return foundOne;
862        }
863    }
864
865    if (!altLangStr && (modifiedLangStr = _CFBundleCopyModifiedLocalization(curLangStr))) {
866        if (CFArrayContainsValue(localizations, localizationsRange, modifiedLangStr)) {
867            if (!CFArrayContainsValue(lprojNames, CFRangeMake(0, CFArrayGetCount(lprojNames)), modifiedLangStr)) CFArrayAppendValue(lprojNames, modifiedLangStr);
868            foundOne = true;
869        }
870    }
871
872
873    if (!specifiesScript && (foundOne || fallBackToLanguage) && !altLangStr && (languageAbbreviation = _CFBundleCopyLanguageAbbreviationForLocalization(curLangStr)) && !CFEqual(curLangStr, languageAbbreviation)) {
874        if (CFArrayContainsValue(localizations, localizationsRange, languageAbbreviation)) {
875            if (!CFArrayContainsValue(lprojNames, CFRangeMake(0, CFArrayGetCount(lprojNames)), languageAbbreviation)) CFArrayAppendValue(lprojNames, languageAbbreviation);
876            foundOne = true;
877        }
878    }
879    if (!specifiesScript && (foundOne || fallBackToLanguage) && !altLangStr && (languageName = _CFBundleCopyLanguageNameForLocalization(curLangStr)) && !CFEqual(curLangStr, languageName)) {
880        if (CFArrayContainsValue(localizations, localizationsRange, languageName)) {
881            if (!CFArrayContainsValue(lprojNames, CFRangeMake(0, CFArrayGetCount(lprojNames)), languageName)) CFArrayAppendValue(lprojNames, languageName);
882            foundOne = true;
883        }
884    }
885    if (modifiedLangStr) CFRelease(modifiedLangStr);
886    if (languageAbbreviation) CFRelease(languageAbbreviation);
887    if (languageName) CFRelease(languageName);
888    if (canonicalLanguageIdentifier) CFRelease(canonicalLanguageIdentifier);
889    if (canonicalLanguageIdentifiers) CFRelease(canonicalLanguageIdentifiers);
890    if (canonicalLanguageAbbreviation) CFRelease(canonicalLanguageAbbreviation);
891    return foundOne;
892}
893
894static Boolean _CFBundleLocalizationsHaveCommonPrefix(CFStringRef loc1, CFStringRef loc2) {
895    Boolean result = false;
896    CFIndex length1 = CFStringGetLength(loc1), length2 = CFStringGetLength(loc2), idx;
897    if (length1 > 3 && length2 > 3) {
898        for (idx = 0; idx < length1 && idx < length2; idx++) {
899            UniChar c1 = CFStringGetCharacterAtIndex(loc1, idx), c2 = CFStringGetCharacterAtIndex(loc2, idx);
900            if (idx >= 2 && (c1 == '-' || c1 == '_') && (c2 == '-' || c2 == '_')) {
901                result = true;
902                break;
903            } else if (c1 != c2) {
904                break;
905            }
906        }
907    }
908    return result;
909}
910
911static void _CFBundleAddPreferredLprojNamesInDirectory(CFAllocatorRef alloc, CFURLRef bundleURL, CFArrayRef localizations, CFMutableArrayRef lprojNames, CFStringRef devLang) {
912    // This function will add zero, one or two elements to the lprojNames array.
913    // It examines the users preferred language list and the lproj directories inside the bundle directory.  It picks the lproj directory that is highest on the users list.
914    // The users list can contain region names (like "en_US" for US English).  In this case, if the region lproj exists, it will be added, and, if the region's associated language lproj exists that will be added.
915
916    Boolean foundOne = false;
917
918    // First check the main bundle.
919    if (!CFBundleAllowMixedLocalizations()) {
920        CFBundleRef mainBundle = CFBundleGetMainBundle();
921        if (mainBundle) {
922            CFURLRef mainBundleURL = CFBundleCopyBundleURL(mainBundle);
923            if (mainBundleURL) {
924                if (!CFEqual(bundleURL, mainBundleURL)) {
925                    // If there is a main bundle, and it isn't this one, try to use the language it prefers.
926                    CFArrayRef mainBundleLangs = _CFBundleGetLanguageSearchList(mainBundle);
927                    if (mainBundleLangs && (CFArrayGetCount(mainBundleLangs) > 0)) {
928                        CFStringRef curLangStr = (CFStringRef)CFArrayGetValueAtIndex(mainBundleLangs, 0);
929                        foundOne = _CFBundleTryOnePreferredLprojNameInURL(kCFAllocatorSystemDefault, localizations, curLangStr, lprojNames, true);
930                    }
931                }
932                CFRelease(mainBundleURL);
933            }
934        }
935    }
936
937    if (!foundOne) {
938        // If we didn't find the main bundle's preferred language, look at the users' prefs again and find the best one.
939        CFArrayRef userLanguages = _CFBundleCopyUserLanguages();
940        if (userLanguages) {
941            CFIndex count = CFArrayGetCount(userLanguages);
942            CFIndex idx, startIdx;
943            for (idx = 0, startIdx = -1; !foundOne && idx < count; idx++) {
944                CFStringRef curLangStr = (CFStringRef)CFArrayGetValueAtIndex(userLanguages, idx);
945                CFStringRef nextLangStr = (idx + 1 < count) ? (CFStringRef)CFArrayGetValueAtIndex(userLanguages, idx + 1) : NULL;
946                if (nextLangStr && _CFBundleLocalizationsHaveCommonPrefix(curLangStr, nextLangStr)) {
947                    foundOne = _CFBundleTryOnePreferredLprojNameInURL(kCFAllocatorSystemDefault, localizations, curLangStr, lprojNames, false);
948                    if (startIdx < 0) startIdx = idx;
949                } else if (startIdx >= 0 && startIdx <= idx) {
950                    foundOne = _CFBundleTryOnePreferredLprojNameInURL(kCFAllocatorSystemDefault, localizations, curLangStr, lprojNames, false);
951                    for (; !foundOne && startIdx <= idx; startIdx++) {
952                        curLangStr = (CFStringRef)CFArrayGetValueAtIndex(userLanguages, startIdx);
953                        foundOne = _CFBundleTryOnePreferredLprojNameInURL(kCFAllocatorSystemDefault, localizations, curLangStr, lprojNames, true);
954                    }
955                    startIdx = -1;
956                } else {
957                    foundOne = _CFBundleTryOnePreferredLprojNameInURL(kCFAllocatorSystemDefault, localizations, curLangStr, lprojNames, true);
958                    startIdx = -1;
959                }
960            }
961        }
962        // use development region and U.S. English as backstops
963        if (!foundOne && devLang) foundOne = _CFBundleTryOnePreferredLprojNameInURL(kCFAllocatorSystemDefault, localizations, devLang, lprojNames, true);
964        if (!foundOne) foundOne = _CFBundleTryOnePreferredLprojNameInURL(kCFAllocatorSystemDefault, localizations, CFSTR("en_US"), lprojNames, true);
965        if (userLanguages) CFRelease(userLanguages);
966    }
967}
968
969static CFArrayRef _CFBundleCopyLanguageSearchListInDirectory(CFAllocatorRef alloc, CFURLRef url, uint8_t *version) {
970    uint8_t localVersion = 0;
971    CFDictionaryRef infoDict = _CFBundleCopyInfoDictionaryInDirectory(alloc, url, &localVersion);
972
973    CFArrayRef predefinedLocalizations = NULL;
974    CFStringRef devLang = NULL;
975    if (infoDict) {
976        devLang = (CFStringRef)CFDictionaryGetValue(infoDict, kCFBundleDevelopmentRegionKey);
977        if (devLang && (CFGetTypeID(devLang) != CFStringGetTypeID() || CFStringGetLength(devLang) == 0)) devLang = NULL;
978
979        predefinedLocalizations = (CFArrayRef)CFDictionaryGetValue(infoDict, kCFBundleLocalizationsKey);
980        if (predefinedLocalizations && CFGetTypeID(predefinedLocalizations) != CFArrayGetTypeID()) {
981            predefinedLocalizations = NULL;
982            CFDictionaryRemoveValue((CFMutableDictionaryRef)infoDict, kCFBundleLocalizationsKey);
983        }
984    }
985
986    CFURLRef resourcesURL = _CFBundleCopyResourcesDirectoryURLInDirectory(url, localVersion);
987    CFArrayRef localizations = _CFBundleCopyURLLocalizations(alloc, resourcesURL);
988    CFRelease(resourcesURL);
989
990    if (predefinedLocalizations && localizations) {
991        CFMutableArrayRef newLocalizations = CFArrayCreateMutableCopy(alloc, 0, predefinedLocalizations);
992        CFArrayAppendArray(newLocalizations, localizations, CFRangeMake(0, CFArrayGetCount(localizations)));
993        CFRelease(localizations);
994        localizations = (CFArrayRef)newLocalizations;
995    } else if (predefinedLocalizations) {
996        localizations = (CFArrayRef)CFRetain(predefinedLocalizations);
997    } else if (!localizations) {
998        localizations = CFArrayCreate(alloc, NULL, 0, &kCFTypeArrayCallBacks);
999    }
1000
1001    CFMutableArrayRef langs = CFArrayCreateMutable(alloc, 0, &kCFTypeArrayCallBacks);
1002    _CFBundleAddPreferredLprojNamesInDirectory(alloc, url, localizations, langs, devLang);
1003    CFRelease(localizations);
1004
1005    if (devLang && CFArrayGetFirstIndexOfValue(langs, CFRangeMake(0, CFArrayGetCount(langs)), devLang) < 0) CFArrayAppendValue(langs, devLang);
1006
1007    // Total backstop behavior to avoid having an empty array.
1008    if (CFArrayGetCount(langs) == 0) CFArrayAppendValue(langs, CFSTR("en"));
1009
1010    if (infoDict) CFRelease(infoDict);
1011    if (version) *version = localVersion;
1012    return langs;
1013}
1014
1015static CFArrayRef _CFBundleCopyLocalizationsForPreferences(CFArrayRef locArray, CFArrayRef prefArray, Boolean considerMain) {
1016    CFMutableArrayRef lprojNames = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
1017    Boolean foundOne = false, releasePrefArray = false;
1018    CFIndex idx, count, startIdx;
1019
1020    if (considerMain && !CFBundleAllowMixedLocalizations()) {
1021        CFBundleRef mainBundle = CFBundleGetMainBundle();
1022        if (mainBundle) {
1023            // If there is a main bundle, try to use the language it prefers.
1024            CFArrayRef mainBundleLangs = _CFBundleGetLanguageSearchList(mainBundle);
1025            if (mainBundleLangs && (CFArrayGetCount(mainBundleLangs) > 0)) foundOne = _CFBundleTryOnePreferredLprojNameInArray(locArray, (CFStringRef)CFArrayGetValueAtIndex(mainBundleLangs, 0), lprojNames, true);
1026        }
1027    }
1028
1029    if (!foundOne) {
1030        CFStringRef curLangStr, nextLangStr;
1031        if (!prefArray) {
1032            prefArray = _CFBundleCopyUserLanguages();
1033            if (prefArray) releasePrefArray = true;
1034        }
1035        count = (prefArray ? CFArrayGetCount(prefArray) : 0);
1036        for (idx = 0, startIdx = -1; !foundOne && idx < count; idx++) {
1037            curLangStr = (CFStringRef)CFArrayGetValueAtIndex(prefArray, idx);
1038            nextLangStr = (idx + 1 < count) ? (CFStringRef)CFArrayGetValueAtIndex(prefArray, idx + 1) : NULL;
1039            if (nextLangStr && _CFBundleLocalizationsHaveCommonPrefix(curLangStr, nextLangStr)) {
1040                foundOne = _CFBundleTryOnePreferredLprojNameInArray(locArray, curLangStr, lprojNames, false);
1041                if (startIdx < 0) startIdx = idx;
1042            } else if (startIdx >= 0 && startIdx <= idx) {
1043                foundOne = _CFBundleTryOnePreferredLprojNameInArray(locArray, curLangStr, lprojNames, false);
1044                for (; !foundOne && startIdx <= idx; startIdx++) {
1045                    curLangStr = (CFStringRef)CFArrayGetValueAtIndex(prefArray, startIdx);
1046                    foundOne = _CFBundleTryOnePreferredLprojNameInArray(locArray, curLangStr, lprojNames, true);
1047                }
1048                startIdx = -1;
1049            } else {
1050                foundOne = _CFBundleTryOnePreferredLprojNameInArray(locArray, curLangStr, lprojNames, true);
1051                startIdx = -1;
1052            }
1053        }
1054        // use U.S. English as backstop
1055        if (!foundOne) foundOne = _CFBundleTryOnePreferredLprojNameInArray(locArray, CFSTR("en_US"), lprojNames, true);
1056        // use random entry as backstop
1057        if (!foundOne && CFArrayGetCount(locArray) > 0) foundOne = _CFBundleTryOnePreferredLprojNameInArray(locArray, (CFStringRef)CFArrayGetValueAtIndex(locArray, 0), lprojNames, true);
1058    }
1059    if (CFArrayGetCount(lprojNames) == 0) {
1060        // Total backstop behavior to avoid having an empty array.
1061        CFArrayAppendValue(lprojNames, CFSTR("en"));
1062    }
1063    if (releasePrefArray) {
1064        CFRelease(prefArray);
1065    }
1066    return lprojNames;
1067}
1068
1069CF_EXPORT CFArrayRef CFBundleCopyLocalizationsForPreferences(CFArrayRef locArray, CFArrayRef prefArray) {
1070    return _CFBundleCopyLocalizationsForPreferences(locArray, prefArray, false);
1071}
1072
1073CF_EXPORT CFArrayRef CFBundleCopyPreferredLocalizationsFromArray(CFArrayRef locArray) {
1074    return _CFBundleCopyLocalizationsForPreferences(locArray, NULL, true);
1075}
1076
1077static CFStringRef _defaultLocalization = NULL;
1078
1079CF_EXPORT void _CFBundleSetDefaultLocalization(CFStringRef localizationName) {
1080    CFStringRef newLocalization = localizationName ? (CFStringRef)CFStringCreateCopy(kCFAllocatorSystemDefault, localizationName) : NULL;
1081    if (_defaultLocalization) CFRelease(_defaultLocalization);
1082    _defaultLocalization = newLocalization;
1083}
1084
1085CF_EXPORT CFArrayRef _CFBundleGetLanguageSearchList(CFBundleRef bundle) {
1086    if (!bundle->_searchLanguages) {
1087        CFMutableArrayRef langs = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
1088        CFStringRef devLang = CFBundleGetDevelopmentRegion(bundle);
1089
1090#if DEPLOYMENT_TARGET_WINDOWS
1091        if (_defaultLocalization) CFArrayAppendValue(langs, _defaultLocalization);
1092#endif
1093        // includes predefined localizations
1094        CFArrayRef localizationsForBundle = CFBundleCopyBundleLocalizations(bundle);
1095
1096        _CFBundleAddPreferredLprojNamesInDirectory(CFGetAllocator(bundle), bundle->_url, localizationsForBundle, langs, devLang);
1097
1098        if (CFArrayGetCount(langs) == 0) {
1099            // If the user does not prefer any of our languages, and devLang is not present, try English
1100            _CFBundleAddPreferredLprojNamesInDirectory(CFGetAllocator(bundle), bundle->_url, localizationsForBundle, langs, CFSTR("en_US"));
1101        }
1102
1103        if (CFArrayGetCount(langs) == 0) {
1104            // if none of the preferred localizations are present, fall back on a random localization that is present
1105            if (localizationsForBundle && CFArrayGetCount(localizationsForBundle) > 0) {
1106                CFStringRef firstLocalization = (CFStringRef)CFArrayGetValueAtIndex(localizationsForBundle, 0);
1107                _CFBundleAddPreferredLprojNamesInDirectory(CFGetAllocator(bundle), bundle->_url, localizationsForBundle, langs, firstLocalization);
1108            }
1109        }
1110
1111        if (devLang && !CFArrayContainsValue(langs, CFRangeMake(0, CFArrayGetCount(langs)), devLang)) {
1112            // Make sure that devLang is on the list as a fallback for individual resources that are not present
1113            CFArrayAppendValue(langs, devLang);
1114        } else if (!devLang) {
1115            if (localizationsForBundle) {
1116                CFStringRef en_US = CFSTR("en_US"), en = CFSTR("en"), English = CFSTR("English");
1117                CFRange range = CFRangeMake(0, CFArrayGetCount(localizationsForBundle));
1118                if (CFArrayContainsValue(localizationsForBundle, range, en)) {
1119                    if (!CFArrayContainsValue(langs, CFRangeMake(0, CFArrayGetCount(langs)), en)) CFArrayAppendValue(langs, en);
1120                } else if (CFArrayContainsValue(localizationsForBundle, range, English)) {
1121                    if (!CFArrayContainsValue(langs, CFRangeMake(0, CFArrayGetCount(langs)), English)) CFArrayAppendValue(langs, English);
1122                } else if (CFArrayContainsValue(localizationsForBundle, range, en_US)) {
1123                    if (!CFArrayContainsValue(langs, CFRangeMake(0, CFArrayGetCount(langs)), en_US)) CFArrayAppendValue(langs, en_US);
1124                }
1125            }
1126        }
1127
1128        if (localizationsForBundle) CFRelease(localizationsForBundle);
1129
1130        if (CFArrayGetCount(langs) == 0) {
1131            // Total backstop behavior to avoid having an empty array.
1132            if (_defaultLocalization) {
1133                CFArrayAppendValue(langs, _defaultLocalization);
1134            } else {
1135                CFArrayAppendValue(langs, CFSTR("en"));
1136            }
1137        }
1138        if (!OSAtomicCompareAndSwapPtrBarrier(NULL, (void *)langs, (void * volatile *)&(bundle->_searchLanguages))) CFRelease(langs);
1139    }
1140    return bundle->_searchLanguages;
1141}
1142
1143#pragma mark -
1144
1145CF_EXPORT Boolean _CFBundleURLLooksLikeBundle(CFURLRef url) {
1146    Boolean result = false;
1147    CFBundleRef bundle = _CFBundleCreateIfLooksLikeBundle(kCFAllocatorSystemDefault, url);
1148    if (bundle) {
1149        result = true;
1150        CFRelease(bundle);
1151    }
1152    return result;
1153}
1154
1155#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_WINDOWS
1156// Note that subDirName is expected to be the string for a URL
1157CF_INLINE Boolean _CFBundleURLHasSubDir(CFURLRef url, CFStringRef subDirName) {
1158    Boolean isDir = false, result = false;
1159    CFURLRef dirURL = CFURLCreateWithString(kCFAllocatorSystemDefault, subDirName, url);
1160    if (dirURL) {
1161        if (_CFIsResourceAtURL(dirURL, &isDir) && isDir) result = true;
1162        CFRelease(dirURL);
1163    }
1164    return result;
1165}
1166#endif
1167
1168CF_PRIVATE uint8_t _CFBundleGetBundleVersionForURL(CFURLRef url) {
1169    // check for existence of "Resources" or "Contents" or "Support Files"
1170    // but check for the most likely one first
1171    // version 0:  old-style "Resources" bundles
1172    // version 1:  obsolete "Support Files" bundles
1173    // version 2:  modern "Contents" bundles
1174    // version 3:  none of the above (see below)
1175    // version 4:  not a bundle (for main bundle only)
1176
1177    CFURLRef absoluteURL = CFURLCopyAbsoluteURL(url);
1178    CFStringRef directoryPath = CFURLCopyFileSystemPath(absoluteURL, PLATFORM_PATH_STYLE);
1179    CFRelease(absoluteURL);
1180
1181    Boolean hasFrameworkSuffix = CFStringHasSuffix(CFURLGetString(url), CFSTR(".framework/"));
1182#if DEPLOYMENT_TARGET_WINDOWS
1183    hasFrameworkSuffix = hasFrameworkSuffix || CFStringHasSuffix(CFURLGetString(url), CFSTR(".framework\\"));
1184#endif
1185
1186    /*
1187     #define _CFBundleSupportFilesDirectoryName1 CFSTR("Support Files")
1188     #define _CFBundleSupportFilesDirectoryName2 CFSTR("Contents")
1189     #define _CFBundleResourcesDirectoryName CFSTR("Resources")
1190     #define _CFBundleExecutablesDirectoryName CFSTR("Executables")
1191     #define _CFBundleNonLocalizedResourcesDirectoryName CFSTR("Non-localized Resources")
1192    */
1193    __block uint8_t localVersion = 3;
1194    CFIndex resourcesDirectoryLength = CFStringGetLength(_CFBundleResourcesDirectoryName);
1195    CFIndex contentsDirectoryLength = CFStringGetLength(_CFBundleSupportFilesDirectoryName2);
1196    CFIndex supportFilesDirectoryLength = CFStringGetLength(_CFBundleSupportFilesDirectoryName1);
1197
1198    __block Boolean foundResources = false;
1199    __block Boolean foundSupportFiles2 = false;
1200    __block Boolean foundSupportFiles1 = false;
1201
1202    _CFIterateDirectory(directoryPath, ^Boolean (CFStringRef fileName, uint8_t fileType) {
1203        // We're looking for a few different names, and also some info on if it's a directory or not.
1204        // We don't stop looking once we find one of the names. Otherwise we could run into the situation where we have both "Contents" and "Resources" in a framework, and we see Contents first but Resources is more important.
1205        if (fileType == DT_DIR || fileType == DT_LNK) {
1206            CFIndex fileNameLen = CFStringGetLength(fileName);
1207            if (fileNameLen == resourcesDirectoryLength && CFStringCompareWithOptions(fileName, _CFBundleResourcesDirectoryName, CFRangeMake(0, resourcesDirectoryLength), kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
1208                foundResources = true;
1209            } else if (fileNameLen == contentsDirectoryLength && CFStringCompareWithOptions(fileName, _CFBundleSupportFilesDirectoryName2, CFRangeMake(0, contentsDirectoryLength), kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
1210                foundSupportFiles2 = true;
1211            } else if (fileNameLen == supportFilesDirectoryLength && CFStringCompareWithOptions(fileName, _CFBundleSupportFilesDirectoryName1, CFRangeMake(0, supportFilesDirectoryLength), kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
1212                foundSupportFiles1 = true;
1213            }
1214        }
1215        return true;
1216    });
1217
1218    // The order of these if statements is important - the Resources directory presence takes precedence over Contents, and so forth.
1219    if (hasFrameworkSuffix) {
1220        if (foundResources) {
1221            localVersion = 0;
1222        } else if (foundSupportFiles2) {
1223            localVersion = 2;
1224        } else if (foundSupportFiles1) {
1225            localVersion = 1;
1226        }
1227    } else {
1228        if (foundSupportFiles2) {
1229            localVersion = 2;
1230        } else if (foundResources) {
1231            localVersion = 0;
1232        } else if (foundSupportFiles1) {
1233            localVersion = 1;
1234        }
1235    }
1236
1237#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_WINDOWS
1238    // Do a more substantial check for the subdirectories that make up version 0/1/2 bundles. These are sometimes symlinks (like in Frameworks) and they would have been missed by our check above. Perhaps we can do a check for DT_LNK there as well, if it's sufficient instead of looking at the actual contents.
1239    if (localVersion == 3) {
1240        if (hasFrameworkSuffix) {
1241            if (_CFBundleURLHasSubDir(url, _CFBundleResourcesURLFromBase0)) localVersion = 0;
1242            else if (_CFBundleURLHasSubDir(url, _CFBundleSupportFilesURLFromBase2)) localVersion = 2;
1243            else if (_CFBundleURLHasSubDir(url, _CFBundleSupportFilesURLFromBase1)) localVersion = 1;
1244        } else {
1245            if (_CFBundleURLHasSubDir(url, _CFBundleSupportFilesURLFromBase2)) localVersion = 2;
1246            else if (_CFBundleURLHasSubDir(url, _CFBundleResourcesURLFromBase0)) localVersion = 0;
1247            else if (_CFBundleURLHasSubDir(url, _CFBundleSupportFilesURLFromBase1)) localVersion = 1;
1248        }
1249    }
1250#endif
1251
1252    CFRelease(directoryPath);
1253    return localVersion;
1254}
1255
1256#pragma mark -
1257#pragma mark Platforms
1258
1259static void _CFBundleCheckSupportedPlatform(CFMutableArrayRef mutableArray, UniChar *buff, CFIndex startLen, CFStringRef platformName, CFStringRef platformIdentifier) {
1260    CFIndex buffLen = startLen, platformLen = CFStringGetLength(platformName), extLen = CFStringGetLength(_CFBundleInfoExtension);
1261    CFMutableStringRef str;
1262    Boolean isDir;
1263    if (buffLen + platformLen + extLen < CFMaxPathSize) {
1264        CFStringGetCharacters(platformName, CFRangeMake(0, platformLen), buff + buffLen);
1265        buffLen += platformLen;
1266        buff[buffLen++] = (UniChar)'.';
1267        CFStringGetCharacters(_CFBundleInfoExtension, CFRangeMake(0, extLen), buff + buffLen);
1268        buffLen += extLen;
1269        str = CFStringCreateMutable(kCFAllocatorSystemDefault, 0);
1270        CFStringAppendCharacters(str, buff, buffLen);
1271        if (_CFIsResourceAtPath(str, &isDir) && !isDir && CFArrayGetFirstIndexOfValue(mutableArray, CFRangeMake(0, CFArrayGetCount(mutableArray)), platformIdentifier) < 0) CFArrayAppendValue(mutableArray, platformIdentifier);
1272        CFRelease(str);
1273    }
1274}
1275
1276CF_EXPORT CFArrayRef _CFBundleGetSupportedPlatforms(CFBundleRef bundle) {
1277    CFDictionaryRef infoDict = CFBundleGetInfoDictionary(bundle);
1278    CFArrayRef platformArray = infoDict ? (CFArrayRef)CFDictionaryGetValue(infoDict, _kCFBundleSupportedPlatformsKey) : NULL;
1279    if (platformArray && CFGetTypeID(platformArray) != CFArrayGetTypeID()) {
1280        platformArray = NULL;
1281        CFDictionaryRemoveValue((CFMutableDictionaryRef)infoDict, _kCFBundleSupportedPlatformsKey);
1282    }
1283    if (!platformArray) {
1284        CFURLRef infoPlistURL = infoDict ? (CFURLRef)CFDictionaryGetValue(infoDict, _kCFBundleInfoPlistURLKey) : NULL, absoluteURL;
1285        CFStringRef infoPlistPath;
1286        UniChar buff[CFMaxPathSize];
1287        CFIndex buffLen, infoLen = CFStringGetLength(_CFBundleInfoURLFromBaseNoExtension3), startLen, extLen = CFStringGetLength(_CFBundleInfoExtension);
1288        if (infoPlistURL) {
1289            CFMutableArrayRef mutableArray = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
1290            absoluteURL = CFURLCopyAbsoluteURL(infoPlistURL);
1291            infoPlistPath = CFURLCopyFileSystemPath(absoluteURL, PLATFORM_PATH_STYLE);
1292            CFRelease(absoluteURL);
1293            buffLen = CFStringGetLength(infoPlistPath);
1294            if (buffLen > CFMaxPathSize) buffLen = CFMaxPathSize;
1295            CFStringGetCharacters(infoPlistPath, CFRangeMake(0, buffLen), buff);
1296            CFRelease(infoPlistPath);
1297            if (buffLen > 0) {
1298                buffLen = _CFStartOfLastPathComponent(buff, buffLen);
1299                if (buffLen > 0 && buffLen + infoLen + extLen < CFMaxPathSize) {
1300                    CFStringGetCharacters(_CFBundleInfoURLFromBaseNoExtension3, CFRangeMake(0, infoLen), buff + buffLen);
1301                    buffLen += infoLen;
1302                    buff[buffLen++] = (UniChar)'-';
1303                    startLen = buffLen;
1304                    _CFBundleCheckSupportedPlatform(mutableArray, buff, startLen, CFSTR("macos"), CFSTR("MacOS"));
1305                    _CFBundleCheckSupportedPlatform(mutableArray, buff, startLen, CFSTR("macosx"), CFSTR("MacOS"));
1306                    _CFBundleCheckSupportedPlatform(mutableArray, buff, startLen, CFSTR("iphoneos"), CFSTR("iPhoneOS"));
1307                    _CFBundleCheckSupportedPlatform(mutableArray, buff, startLen, CFSTR("windows"), CFSTR("Windows"));
1308                }
1309            }
1310            if (CFArrayGetCount(mutableArray) > 0) {
1311                platformArray = (CFArrayRef)mutableArray;
1312                CFDictionarySetValue((CFMutableDictionaryRef)infoDict, _kCFBundleSupportedPlatformsKey, platformArray);
1313            }
1314            CFRelease(mutableArray);
1315        }
1316    }
1317    return platformArray;
1318}
1319
1320CF_EXPORT CFStringRef _CFBundleGetCurrentPlatform(void) {
1321#if DEPLOYMENT_TARGET_MACOSX
1322    return CFSTR("MacOS");
1323#elif DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
1324    return CFSTR("iPhoneOS");
1325#elif DEPLOYMENT_TARGET_WINDOWS
1326    return CFSTR("Windows");
1327#elif DEPLOYMENT_TARGET_SOLARIS
1328    return CFSTR("Solaris");
1329#elif DEPLOYMENT_TARGET_HPUX
1330    return CFSTR("HPUX");
1331#elif DEPLOYMENT_TARGET_LINUX
1332    return CFSTR("Linux");
1333#elif DEPLOYMENT_TARGET_FREEBSD
1334    return CFSTR("FreeBSD");
1335#else
1336#error Unknown or unspecified DEPLOYMENT_TARGET
1337#endif
1338}
1339
1340CF_PRIVATE CFStringRef _CFBundleGetPlatformExecutablesSubdirectoryName(void) {
1341#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
1342    return CFSTR("MacOS");
1343#elif DEPLOYMENT_TARGET_WINDOWS
1344    return CFSTR("Windows");
1345#elif DEPLOYMENT_TARGET_SOLARIS
1346    return CFSTR("Solaris");
1347#elif DEPLOYMENT_TARGET_HPUX
1348    return CFSTR("HPUX");
1349#elif DEPLOYMENT_TARGET_LINUX
1350    return CFSTR("Linux");
1351#elif DEPLOYMENT_TARGET_FREEBSD
1352    return CFSTR("FreeBSD");
1353#else
1354#error Unknown or unspecified DEPLOYMENT_TARGET
1355#endif
1356}
1357
1358CF_PRIVATE CFStringRef _CFBundleGetAlternatePlatformExecutablesSubdirectoryName(void) {
1359#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
1360    return CFSTR("Mac OS X");
1361#elif DEPLOYMENT_TARGET_WINDOWS
1362    return CFSTR("WinNT");
1363#elif DEPLOYMENT_TARGET_SOLARIS
1364    return CFSTR("Solaris");
1365#elif DEPLOYMENT_TARGET_HPUX
1366    return CFSTR("HP-UX");
1367#elif DEPLOYMENT_TARGET_LINUX
1368    return CFSTR("Linux");
1369#elif DEPLOYMENT_TARGET_FREEBSD
1370    return CFSTR("FreeBSD");
1371#else
1372#error Unknown or unspecified DEPLOYMENT_TARGET
1373#endif
1374}
1375
1376CF_PRIVATE CFStringRef _CFBundleGetOtherPlatformExecutablesSubdirectoryName(void) {
1377#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
1378    return CFSTR("MacOSClassic");
1379#elif DEPLOYMENT_TARGET_WINDOWS
1380    return CFSTR("Other");
1381#elif DEPLOYMENT_TARGET_HPUX
1382    return CFSTR("Other");
1383#elif DEPLOYMENT_TARGET_SOLARIS
1384    return CFSTR("Other");
1385#elif DEPLOYMENT_TARGET_LINUX
1386    return CFSTR("Other");
1387#elif DEPLOYMENT_TARGET_FREEBSD
1388    return CFSTR("Other");
1389#else
1390#error Unknown or unspecified DEPLOYMENT_TARGET
1391#endif
1392}
1393
1394CF_PRIVATE CFStringRef _CFBundleGetOtherAlternatePlatformExecutablesSubdirectoryName(void) {
1395#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
1396    return CFSTR("Mac OS 8");
1397#elif DEPLOYMENT_TARGET_WINDOWS
1398    return CFSTR("Other");
1399#elif DEPLOYMENT_TARGET_HPUX
1400    return CFSTR("Other");
1401#elif DEPLOYMENT_TARGET_SOLARIS
1402    return CFSTR("Other");
1403#elif DEPLOYMENT_TARGET_LINUX
1404    return CFSTR("Other");
1405#elif DEPLOYMENT_TARGET_FREEBSD
1406    return CFSTR("Other");
1407#else
1408#error Unknown or unspecified DEPLOYMENT_TARGET
1409#endif
1410}
1411
1412CFArrayRef CFBundleCopyExecutableArchitecturesForURL(CFURLRef url) {
1413    CFArrayRef result = NULL;
1414    CFBundleRef bundle = CFBundleCreate(kCFAllocatorSystemDefault, url);
1415    if (bundle) {
1416        result = CFBundleCopyExecutableArchitectures(bundle);
1417        CFRelease(bundle);
1418    } else {
1419        result = _CFBundleCopyArchitecturesForExecutable(url);
1420    }
1421    return result;
1422}
1423
1424#pragma mark -
1425#pragma mark Resource Lookup - Query Table
1426
1427static void _CFBundleAddValueForType(CFStringRef type, CFMutableDictionaryRef queryTable, CFMutableDictionaryRef typeDir, CFTypeRef value, CFMutableDictionaryRef addedTypes, Boolean firstLproj) {
1428    CFMutableArrayRef tFiles = (CFMutableArrayRef) CFDictionaryGetValue(typeDir, type);
1429    if (!tFiles) {
1430        CFStringRef key = CFStringCreateWithFormat(kCFAllocatorSystemDefault, NULL, CFSTR("%@.%@"), _CFBundleTypeIndicator, type);
1431        tFiles = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
1432        CFDictionarySetValue(queryTable, key, tFiles);
1433        CFDictionarySetValue(typeDir, type, tFiles);
1434        CFRelease(tFiles);
1435        CFRelease(key);
1436    }
1437    if (!addedTypes) {
1438        CFArrayAppendValue(tFiles, value);
1439    } else if (firstLproj) {
1440        CFDictionarySetValue(addedTypes, type, type);
1441        CFArrayAppendValue(tFiles, value);
1442    } else if (!(CFDictionaryGetValue(addedTypes, type))) {
1443        CFArrayAppendValue(tFiles, value);
1444    }
1445}
1446
1447CF_INLINE Boolean _CFBundleFindCharacterInStr(const UniChar *str, UniChar c, Boolean backward, CFIndex start, CFIndex length, CFRange *result){
1448    *result = CFRangeMake(kCFNotFound, 0);
1449    Boolean found = false;
1450    if (backward) {
1451        for (CFIndex i = start; i > start-length; i--) {
1452            if (c == str[i]) {
1453                result->location = i;
1454                found = true;
1455                break;
1456            }
1457        }
1458    } else {
1459        for (CFIndex i = start; i < start+length; i++) {
1460            if (c == str[i]) {
1461                result->location = i;
1462                found = true;
1463                break;
1464            }
1465        }
1466    }
1467    return found;
1468}
1469
1470typedef enum {
1471    _CFBundleFileVersionNoProductNoPlatform = 1,
1472    _CFBundleFileVersionWithProductNoPlatform,
1473    _CFBundleFileVersionNoProductWithPlatform,
1474    _CFBundleFileVersionWithProductWithPlatform,
1475    _CFBundleFileVersionUnmatched
1476} _CFBundleFileVersion;
1477
1478static _CFBundleFileVersion _CFBundleCheckFileProductAndPlatform(CFStringRef file, CFRange searchRange, CFStringRef product, CFStringRef platform)
1479{
1480    _CFBundleFileVersion version;
1481    Boolean foundprod, foundplat;
1482    foundplat = foundprod = NO;
1483    Boolean wrong = false;
1484
1485    if (CFStringFindWithOptions(file, CFSTR("~"), searchRange, 0, NULL)) {
1486        if (CFStringGetLength(product) != 1) {
1487            // todo: really, search the same range again?
1488            if (CFStringFindWithOptions(file, product, searchRange, kCFCompareEqualTo, NULL)) {
1489                foundprod = YES;
1490            }
1491        }
1492        if (!foundprod) {
1493            wrong = _CFBundleSupportedProductName(file, searchRange);
1494        }
1495    }
1496
1497    if (!wrong && CFStringFindWithOptions(file, CFSTR("-"), searchRange, 0, NULL)) {
1498        if (CFStringFindWithOptions(file, platform, searchRange, kCFCompareEqualTo, NULL)) {
1499            foundplat = YES;
1500        }
1501        if (!foundplat) {
1502            wrong = _CFBundleSupportedPlatformName(file, searchRange);
1503        }
1504    }
1505
1506    if (wrong) {
1507        version = _CFBundleFileVersionUnmatched;
1508    } else if (foundplat && foundprod) {
1509        version = _CFBundleFileVersionWithProductWithPlatform;
1510    } else if (foundplat) {
1511        version = _CFBundleFileVersionNoProductWithPlatform;
1512    } else if (foundprod) {
1513        version = _CFBundleFileVersionWithProductNoPlatform;
1514    } else {
1515        version = _CFBundleFileVersionNoProductNoPlatform;
1516    }
1517    return version;
1518}
1519
1520static _CFBundleFileVersion _CFBundleVersionForFileName(CFStringRef fileName, CFStringRef expectedProduct, CFStringRef expectedPlatform, CFRange *outProductRange, CFRange *outPlatformRange) {
1521    // Search for a product name, e.g.: foo~iphone.jpg or bar~ipad
1522    Boolean foundProduct = false;
1523    Boolean foundPlatform = false;
1524    CFIndex fileNameLen = CFStringGetLength(fileName);
1525    CFRange productRange;
1526    CFRange platformRange;
1527
1528    CFIndex dotLocation = fileNameLen;
1529    for (CFIndex i = fileNameLen - 1; i > 0; i--) {
1530        UniChar c = CFStringGetCharacterAtIndex(fileName, i);
1531        if (c == '.') {
1532            dotLocation = i;
1533        }
1534#if DEPLOYMENT_TARGET_EMBEDDED
1535        // Product names are only supported on iOS
1536        // ref docs here: "iOS Supports Device-Specific Resources" in "Resource Programming Guide"
1537        else if (c == '~' && !foundProduct) {
1538            productRange = CFRangeMake(i, dotLocation - i);
1539            foundProduct = (CFStringCompareWithOptions(fileName, expectedProduct, productRange, kCFCompareAnchored) == kCFCompareEqualTo);
1540            if (foundProduct && outProductRange) *outProductRange = productRange;
1541        }
1542#endif
1543        else if (c == '-') {
1544            if (foundProduct) {
1545                platformRange = CFRangeMake(i, productRange.location - i);
1546            } else {
1547                platformRange = CFRangeMake(i, dotLocation - i);
1548            }
1549            foundPlatform = (CFStringCompareWithOptions(fileName, expectedPlatform, platformRange, kCFCompareAnchored) == kCFCompareEqualTo);
1550            if (foundPlatform && outPlatformRange) *outPlatformRange = platformRange;
1551            break;
1552        }
1553    }
1554
1555    _CFBundleFileVersion version;
1556    if (foundPlatform && foundProduct) {
1557        version = _CFBundleFileVersionWithProductWithPlatform;
1558    } else if (foundPlatform) {
1559        version = _CFBundleFileVersionNoProductWithPlatform;
1560    } else if (foundProduct) {
1561        version = _CFBundleFileVersionWithProductNoPlatform;
1562    } else {
1563        version = _CFBundleFileVersionNoProductNoPlatform;
1564    }
1565    return version;
1566}
1567
1568// Splits up a string into its various parts. Note that the out-types must be released by the caller if they exist.
1569static void _CFBundleSplitFileName(CFStringRef fileName, CFStringRef *noProductOrPlatform, CFStringRef *endType, CFStringRef *startType, CFStringRef expectedProduct, CFStringRef expectedPlatform, _CFBundleFileVersion *version) {
1570    CFIndex fileNameLen = CFStringGetLength(fileName);
1571
1572    if (endType || startType) {
1573        // Search for the type from the end (type defined as everything after the last '.')
1574        // e.g., a file name like foo.jpg has a type of 'jpg'
1575        Boolean foundDot = false;
1576        uint16_t dotLocation = 0;
1577        for (CFIndex i = fileNameLen; i > 0; i--) {
1578            if (CFStringGetCharacterAtIndex(fileName, i - 1) == '.') {
1579                foundDot = true;
1580                dotLocation = i - 1;
1581                break;
1582            }
1583        }
1584
1585        if (foundDot && dotLocation != fileNameLen - 1) {
1586            if (endType) *endType = CFStringCreateWithSubstring(kCFAllocatorSystemDefault, fileName, CFRangeMake(dotLocation + 1, CFStringGetLength(fileName) - dotLocation - 1));
1587        }
1588
1589        // Search for the type from the beginning (type defined as everything after the first '.')
1590        // e.g., a file name like foo.jpg.gz has a type of 'jpg.gz'
1591        if (startType) {
1592            for (CFIndex i = 0; i < fileNameLen; i++) {
1593                if (CFStringGetCharacterAtIndex(fileName, i) == '.') {
1594                    // no need to create this again if it's the same as previous
1595                    if (i != dotLocation) {
1596                        *startType = CFStringCreateWithSubstring(kCFAllocatorSystemDefault, fileName, CFRangeMake(i + 1, CFStringGetLength(fileName) - i - 1));
1597                    }
1598                    break;
1599                }
1600            }
1601        }
1602    }
1603
1604    CFRange productRange, platformRange;
1605    *version = _CFBundleVersionForFileName(fileName, expectedProduct, expectedPlatform, &productRange, &platformRange);
1606
1607    Boolean foundPlatform = (*version == _CFBundleFileVersionNoProductWithPlatform || *version == _CFBundleFileVersionWithProductWithPlatform);
1608    Boolean foundProduct = (*version == _CFBundleFileVersionWithProductNoPlatform || *version == _CFBundleFileVersionWithProductWithPlatform);
1609    // Create a string that excludes both platform and product name
1610    // e.g., foo-iphone~iphoneos.jpg -> foo.jpg
1611    if (foundPlatform || foundProduct) {
1612        CFMutableStringRef fileNameScratch = CFStringCreateMutableCopy(kCFAllocatorSystemDefault, 0, fileName);
1613        CFIndex start, length = 0;
1614
1615        // Because the platform always comes first and is immediately followed by product if it exists, we'll use the platform start location as the start of our range to delete.
1616        if (foundPlatform) {
1617            start = platformRange.location;
1618        } else {
1619            start = productRange.location;
1620        }
1621
1622        if (foundPlatform && foundProduct) {
1623            length = platformRange.length + productRange.length;
1624        } else if (foundPlatform) {
1625            length = platformRange.length;
1626        } else if (foundProduct) {
1627            length = productRange.length;
1628        }
1629        CFStringDelete(fileNameScratch, CFRangeMake(start, length));
1630        *noProductOrPlatform = (CFStringRef)fileNameScratch;
1631    }
1632}
1633
1634static Boolean _CFBundleReadDirectory(CFStringRef pathOfDir, CFBundleRef bundle, CFStringRef subdirectory, CFMutableArrayRef allFiles, Boolean hasFileAdded, CFMutableStringRef type, CFMutableDictionaryRef queryTable, CFMutableDictionaryRef typeDir, CFMutableDictionaryRef addedTypes, Boolean firstLproj, CFStringRef product, CFStringRef platform, CFStringRef lprojName, Boolean appendLprojCharacters) {
1635
1636    Boolean result = true;
1637    CFMutableStringRef pathPrefix = NULL;
1638    if (lprojName) {
1639        pathPrefix = CFStringCreateMutableCopy(kCFAllocatorSystemDefault, 0, lprojName);
1640        if (appendLprojCharacters) _CFAppendPathExtension2(pathPrefix, _CFBundleLprojExtension);
1641        _CFAppendTrailingPathSlash2(pathPrefix);
1642    }
1643    if (subdirectory) {
1644        if (pathPrefix) {
1645            CFStringAppend(pathPrefix, subdirectory);
1646        } else {
1647            pathPrefix = CFStringCreateMutableCopy(kCFAllocatorSystemDefault, 0, subdirectory);
1648        }
1649        UniChar lastChar = CFStringGetCharacterAtIndex(subdirectory, CFStringGetLength(subdirectory)-1);
1650        if (lastChar != _CFGetSlash()) {
1651            _CFAppendTrailingPathSlash2(pathPrefix);
1652        }
1653    }
1654
1655    _CFIterateDirectory(pathOfDir, ^Boolean(CFStringRef fileName, uint8_t fileType) {
1656        CFStringRef startType = NULL, endType = NULL, noProductOrPlatform = NULL;
1657        _CFBundleFileVersion fileVersion;
1658        _CFBundleSplitFileName(fileName, &noProductOrPlatform, &endType, &startType, product, platform, &fileVersion);
1659
1660        CFStringRef pathToFile;
1661        if (pathPrefix && CFStringGetLength(pathPrefix) > 0) {
1662            CFMutableStringRef tmp = CFStringCreateMutableCopy(kCFAllocatorSystemDefault, 0, pathPrefix);
1663            CFStringAppend(tmp, fileName);
1664            pathToFile = (CFStringRef)tmp;
1665        } else {
1666            pathToFile = (CFStringRef)CFRetain(fileName);
1667        }
1668
1669        // If this file is a directory, the path needs to include a trailing slash so we can later create the right kind of CFURL object
1670        Boolean appendSlash = false;
1671        if (fileType == DT_DIR) {
1672            appendSlash = true;
1673        }
1674#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI || DEPLOYMENT_TARGET_LINUX || DEPLOYMENT_TARGET_FREEBSD
1675        else if (fileType == DT_UNKNOWN) {
1676            Boolean isDir = false;
1677            char subdirPath[CFMaxPathLength];
1678            struct stat statBuf;
1679            if (CFStringGetFileSystemRepresentation(pathOfDir, subdirPath, sizeof(subdirPath))) {
1680                strlcat(subdirPath, "/", sizeof(subdirPath));
1681                char fileNameBuf[CFMaxPathLength];
1682                if (CFStringGetFileSystemRepresentation(fileName, fileNameBuf, sizeof(fileNameBuf))) {
1683                    strlcat(subdirPath, fileNameBuf, sizeof(subdirPath));
1684                    if (stat(subdirPath, &statBuf) == 0) {
1685                        isDir = ((statBuf.st_mode & S_IFMT) == S_IFDIR);
1686                    }
1687                    if (isDir) {
1688                        appendSlash = true;
1689                    }
1690                }
1691            }
1692        }
1693#endif
1694        if (appendSlash) {
1695            // This is fairly inefficient
1696            CFMutableStringRef tmp = CFStringCreateMutableCopy(kCFAllocatorSystemDefault, 0, pathToFile);
1697            _CFAppendTrailingPathSlash2(tmp);
1698            CFRelease(pathToFile);
1699            pathToFile = (CFStringRef)tmp;
1700        }
1701
1702        // put it into all file array
1703        if (!hasFileAdded) {
1704            CFArrayAppendValue(allFiles, pathToFile);
1705        }
1706
1707        if (startType) {
1708            _CFBundleAddValueForType(startType, queryTable, typeDir, pathToFile, addedTypes, firstLproj);
1709        }
1710
1711        if (endType) {
1712            _CFBundleAddValueForType(endType, queryTable, typeDir, pathToFile, addedTypes, firstLproj);
1713        }
1714
1715        if (fileVersion == _CFBundleFileVersionNoProductNoPlatform || fileVersion == _CFBundleFileVersionUnmatched) {
1716            // No product/no platform, or unmatched files get added directly to the query table.
1717            CFStringRef prevPath = (CFStringRef)CFDictionaryGetValue(queryTable, fileName);
1718            if (!prevPath) {
1719                CFDictionarySetValue(queryTable, fileName, pathToFile);
1720            }
1721        } else {
1722            // If the file has a product or platform extension, we add the full name to the query table so that it may be found using that name. But only if it doesn't already exist.
1723            CFStringRef prevPath = (CFStringRef)CFDictionaryGetValue(queryTable, fileName);
1724            if (!prevPath) {
1725                CFDictionarySetValue(queryTable, fileName, pathToFile);
1726            }
1727
1728            // Then we add the more specific name as well, replacing the existing one if this is a more specific version.
1729            if (noProductOrPlatform) {
1730                // add the path of the key into the query table
1731                prevPath = (CFStringRef) CFDictionaryGetValue(queryTable, noProductOrPlatform);
1732                if (!prevPath) {
1733                    CFDictionarySetValue(queryTable, noProductOrPlatform, pathToFile);
1734                } else {
1735                    if (!lprojName || CFStringHasPrefix(prevPath, lprojName)) {
1736                        // we need to know the version of exisiting path to see if we can replace it by the current path
1737                        CFRange searchRange;
1738                        if (lprojName) {
1739                            searchRange.location = CFStringGetLength(lprojName);
1740                            searchRange.length = CFStringGetLength(prevPath) - searchRange.location;
1741                        } else {
1742                            searchRange.location = 0;
1743                            searchRange.length = CFStringGetLength(prevPath);
1744                        }
1745                        _CFBundleFileVersion prevFileVersion = _CFBundleCheckFileProductAndPlatform(prevPath, searchRange, product, platform);
1746                        switch (prevFileVersion) {
1747                            case _CFBundleFileVersionNoProductNoPlatform:
1748                                CFDictionarySetValue(queryTable, noProductOrPlatform, pathToFile);
1749                                break;
1750                            case _CFBundleFileVersionWithProductNoPlatform:
1751                                if (fileVersion == _CFBundleFileVersionWithProductWithPlatform) CFDictionarySetValue(queryTable, noProductOrPlatform, pathToFile);
1752                                break;
1753                            case _CFBundleFileVersionNoProductWithPlatform:
1754                                CFDictionarySetValue(queryTable, noProductOrPlatform, pathToFile);
1755                                break;
1756                            default:
1757                                break;
1758                        }
1759                    }
1760                }
1761            }
1762        }
1763
1764        if (pathToFile) CFRelease(pathToFile);
1765        if (startType) CFRelease(startType);
1766        if (endType) CFRelease(endType);
1767        if (noProductOrPlatform) CFRelease(noProductOrPlatform);
1768
1769        return true;
1770    });
1771
1772    if (pathPrefix) CFRelease(pathPrefix);
1773    return result;
1774}
1775
1776
1777static CFDictionaryRef _CFBundleCreateQueryTableAtPath(CFBundleRef bundle, CFURLRef bundleURL, CFArrayRef languages, CFStringRef resourcesDirectory, CFStringRef subdirectory)
1778{
1779
1780    CFMutableDictionaryRef queryTable = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1781    CFMutableArrayRef allFiles = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
1782    CFMutableDictionaryRef typeDir = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1783    CFMutableStringRef type = CFStringCreateMutableWithExternalCharactersNoCopy(kCFAllocatorSystemDefault, NULL, 0, 0, kCFAllocatorNull);
1784
1785    CFStringRef productName = _CFGetProductName();//CFSTR("iphone");
1786    CFStringRef platformName = _CFGetPlatformName();//CFSTR("iphoneos");
1787    if (CFEqual(productName, CFSTR("ipod"))) {
1788        productName = CFSTR("iphone");
1789    }
1790    CFStringRef product = CFStringCreateWithFormat(kCFAllocatorSystemDefault, NULL, CFSTR("~%@"), productName);
1791    CFStringRef platform = CFStringCreateWithFormat(kCFAllocatorSystemDefault, NULL, CFSTR("-%@"), platformName);
1792
1793    CFMutableStringRef path = NULL;
1794    if (bundle) {
1795        path = CFStringCreateMutableCopy(kCFAllocatorSystemDefault, 0, bundle->_bundleBasePath);
1796    } else {
1797        CFURLRef url = CFURLCopyAbsoluteURL(bundleURL);
1798        CFStringRef bundlePath = CFURLCopyFileSystemPath(url, PLATFORM_PATH_STYLE);
1799        CFRelease(url);
1800        path = CFStringCreateMutableCopy(kCFAllocatorSystemDefault, 0, bundlePath);
1801        CFRelease(bundlePath);
1802    }
1803
1804    if (resourcesDirectory) {
1805        _CFAppendPathComponent2(path, resourcesDirectory);
1806    }
1807
1808    // Record the length of the base path, so we can strip off the stuff we'll be appending later
1809    CFIndex basePathLen = CFStringGetLength(path);
1810
1811    if (subdirectory) {
1812        _CFAppendPathComponent2(path, subdirectory);
1813    }
1814    // read the content in sub dir and put them into query table
1815    _CFBundleReadDirectory(path, bundle, subdirectory, allFiles, false, type, queryTable, typeDir, NULL, false, product, platform, NULL, false);
1816    CFStringDelete(path, CFRangeMake(basePathLen, CFStringGetLength(path) - basePathLen));    // Strip the string back to the base path
1817
1818    CFIndex numOfAllFiles = CFArrayGetCount(allFiles);
1819
1820    if (bundle && !languages) {
1821        languages = _CFBundleGetLanguageSearchList(bundle);
1822    }
1823    CFIndex numLprojs = languages ? CFArrayGetCount(languages) : 0;
1824    CFMutableDictionaryRef addedTypes = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1825
1826    Boolean hasFileAdded = false;
1827    Boolean firstLproj = true;
1828
1829    // First, search lproj for user's chosen language
1830    if (numLprojs >= 1) {
1831        CFStringRef lprojTarget = (CFStringRef)CFArrayGetValueAtIndex(languages, 0);
1832        _CFAppendPathComponent2(path, lprojTarget);
1833        _CFAppendPathExtension2(path, _CFBundleLprojExtension);
1834        if (subdirectory) {
1835            _CFAppendPathComponent2(path, subdirectory);
1836        }
1837        _CFBundleReadDirectory(path, bundle, subdirectory, allFiles, hasFileAdded, type, queryTable, typeDir, addedTypes, firstLproj, product, platform, lprojTarget, true);
1838        CFStringDelete(path, CFRangeMake(basePathLen, CFStringGetLength(path) - basePathLen));         // Strip the string back to the base path
1839
1840        if (!hasFileAdded && numOfAllFiles < CFArrayGetCount(allFiles)) {
1841            hasFileAdded = true;
1842        }
1843        firstLproj = false;
1844    }
1845
1846    // Next, search Base.lproj folder
1847    _CFAppendPathComponent2(path, _CFBundleBaseDirectory);
1848    _CFAppendPathExtension2(path, _CFBundleLprojExtension);
1849    if (subdirectory) {
1850        _CFAppendPathComponent2(path, subdirectory);
1851    }
1852    _CFBundleReadDirectory(path, bundle, subdirectory, allFiles, hasFileAdded, type, queryTable, typeDir, addedTypes, YES, product, platform, _CFBundleBaseDirectory, true);
1853    CFStringDelete(path, CFRangeMake(basePathLen, CFStringGetLength(path) - basePathLen));    // Strip the string back to the base path
1854
1855    if (!hasFileAdded && numOfAllFiles < CFArrayGetCount(allFiles)) {
1856        hasFileAdded = true;
1857    }
1858
1859    // Finally, search remaining languages (development language first)
1860    if (numLprojs >= 2) {
1861        // for each lproj we are interested in, read the content and put them into query table
1862        for (CFIndex i = 1; i < CFArrayGetCount(languages); i++) {
1863            CFStringRef lprojTarget = (CFStringRef) CFArrayGetValueAtIndex(languages, i);
1864            _CFAppendPathComponent2(path, lprojTarget);
1865            _CFAppendPathExtension2(path, _CFBundleLprojExtension);
1866            if (subdirectory) {
1867                _CFAppendPathComponent2(path, subdirectory);
1868            }
1869            _CFBundleReadDirectory(path, bundle, subdirectory, allFiles, hasFileAdded, type, queryTable, typeDir, addedTypes, false, product, platform, lprojTarget, true);
1870            CFStringDelete(path, CFRangeMake(basePathLen, CFStringGetLength(path) - basePathLen));         // Strip the string back to the base path
1871
1872            if (!hasFileAdded && numOfAllFiles < CFArrayGetCount(allFiles)) {
1873                hasFileAdded = true;
1874            }
1875        }
1876    }
1877
1878    CFRelease(addedTypes);
1879    CFRelease(path);
1880
1881    // put the array of all files in sub dir to the query table
1882    if (CFArrayGetCount(allFiles) > 0) {
1883        CFDictionarySetValue(queryTable, _CFBundleAllFiles, allFiles);
1884    }
1885
1886    CFRelease(platform);
1887    CFRelease(product);
1888    CFRelease(allFiles);
1889    CFRelease(typeDir);
1890    CFRelease(type);
1891
1892
1893    return queryTable;
1894}
1895
1896// caller need to release the table
1897static CFDictionaryRef _CFBundleCopyQueryTable(CFBundleRef bundle, CFURLRef bundleURL, CFArrayRef languages, CFStringRef resourcesDirectory, CFStringRef subdirectory)
1898{
1899    CFDictionaryRef subTable = NULL;
1900
1901    // take the lock
1902    if (bundle) {
1903        CFMutableStringRef argDirStr = NULL;
1904        if (subdirectory) {
1905            argDirStr = CFStringCreateMutableCopy(kCFAllocatorDefault, 0, resourcesDirectory);
1906            _CFAppendPathComponent2(argDirStr, subdirectory);
1907        } else {
1908            argDirStr = (CFMutableStringRef)CFRetain(resourcesDirectory);
1909        }
1910
1911        __CFSpinLock(&bundle->_queryLock);
1912
1913        // check if the query table for the given sub dir has been created
1914        subTable = (CFDictionaryRef) CFDictionaryGetValue(bundle->_queryTable, argDirStr);
1915
1916        if (!subTable) {
1917            // create the query table for the given sub dir
1918            subTable = _CFBundleCreateQueryTableAtPath(bundle, bundleURL, languages, resourcesDirectory, subdirectory);
1919
1920            CFDictionarySetValue(bundle->_queryTable, argDirStr, subTable);
1921        } else {
1922            CFRetain(subTable);
1923        }
1924        __CFSpinUnlock(&bundle->_queryLock);
1925        CFRelease(argDirStr);
1926    } else {
1927        subTable = _CFBundleCreateQueryTableAtPath(NULL, bundleURL, languages, resourcesDirectory, subdirectory);
1928    }
1929
1930    return subTable;
1931}
1932
1933static CFURLRef _CFBundleCreateRelativeURLFromBaseAndPath(CFStringRef path, CFURLRef base, UniChar slash, CFStringRef slashStr)
1934{
1935    CFURLRef url = NULL;
1936    CFRange resultRange;
1937    Boolean needToRelease = false;
1938    if (CFStringFindWithOptions(path, slashStr, CFRangeMake(0, CFStringGetLength(path)-1), kCFCompareBackwards, &resultRange)) {
1939        CFStringRef subPathCom = CFStringCreateWithSubstring(kCFAllocatorSystemDefault, path, CFRangeMake(0, resultRange.location));
1940        base = CFURLCreateCopyAppendingPathComponent(kCFAllocatorSystemDefault, base, subPathCom, YES);
1941        path = CFStringCreateWithSubstring(kCFAllocatorSystemDefault, path, CFRangeMake(resultRange.location+1, CFStringGetLength(path)-resultRange.location-1));
1942        CFRelease(subPathCom);
1943        needToRelease = true;
1944    }
1945    if (CFStringGetCharacterAtIndex(path, CFStringGetLength(path)-1) == slash) {
1946        url = (CFURLRef)CFURLCreateWithFileSystemPathRelativeToBase(kCFAllocatorSystemDefault, path, PLATFORM_PATH_STYLE, true, base);
1947    } else {
1948        url = (CFURLRef)CFURLCreateWithFileSystemPathRelativeToBase(kCFAllocatorSystemDefault, path, PLATFORM_PATH_STYLE, false, base);
1949    }
1950    if (needToRelease) {
1951        CFRelease(base);
1952        CFRelease(path);
1953    }
1954    return url;
1955}
1956
1957static void _CFBundleFindResourcesWithPredicate(CFMutableArrayRef interResult, CFDictionaryRef queryTable, Boolean (^predicate)(CFStringRef filename, Boolean *stop), Boolean *stop)
1958{
1959    CFIndex dictSize = CFDictionaryGetCount(queryTable);
1960    if (dictSize == 0) {
1961        return;
1962    }
1963    STACK_BUFFER_DECL(CFTypeRef, keys, dictSize);
1964    STACK_BUFFER_DECL(CFTypeRef, values, dictSize);
1965    CFDictionaryGetKeysAndValues(queryTable, keys, values);
1966    for (CFIndex i = 0; i < dictSize; i++) {
1967        if (predicate((CFStringRef)keys[i], stop)) {
1968            if (CFGetTypeID(values[i]) == CFStringGetTypeID()) {
1969                CFArrayAppendValue(interResult, values[i]);
1970            } else {
1971                CFArrayAppendArray(interResult, (CFArrayRef)values[i], CFRangeMake(0, CFArrayGetCount((CFArrayRef)values[i])));
1972            }
1973        }
1974
1975        if (*stop) break;
1976    }
1977}
1978
1979static CFTypeRef _CFBundleCopyURLsOfKey(CFBundleRef bundle, CFURLRef bundleURL, CFArrayRef languages, CFStringRef resourcesDirectory, CFStringRef subDir, CFStringRef key, CFStringRef lproj, Boolean returnArray, Boolean localized, uint8_t bundleVersion, Boolean (^predicate)(CFStringRef filename, Boolean *stop))
1980{
1981    CFTypeRef value = NULL;
1982    Boolean stop = false; // for predicate
1983    CFMutableArrayRef interResult = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
1984    CFDictionaryRef subTable = NULL;
1985
1986    CFMutableStringRef path = CFStringCreateMutableCopy(kCFAllocatorDefault, 0, resourcesDirectory);
1987    if (1 == bundleVersion) {
1988        CFIndex savedPathLength = CFStringGetLength(path);
1989        // add the non-localized resource dir
1990        _CFAppendPathComponent2(path, _CFBundleNonLocalizedResourcesDirectoryName);
1991        subTable = _CFBundleCopyQueryTable(bundle, bundleURL, languages, path, subDir);
1992        if (predicate) {
1993            _CFBundleFindResourcesWithPredicate(interResult, subTable, predicate, &stop);
1994        } else {
1995            value = CFDictionaryGetValue(subTable, key);
1996        }
1997        CFStringDelete(path, CFRangeMake(savedPathLength, CFStringGetLength(path) - savedPathLength));    // Strip the string back to the base path
1998    }
1999
2000    if (!value && !stop) {
2001        if (subTable) CFRelease(subTable);
2002        subTable = _CFBundleCopyQueryTable(bundle, bundleURL, languages, path, subDir);
2003        if (predicate) {
2004            _CFBundleFindResourcesWithPredicate(interResult, subTable, predicate, &stop);
2005        } else {
2006            // get the path or paths for the given key
2007            value = CFDictionaryGetValue(subTable, key);
2008        }
2009    }
2010
2011    // if localization is needed, we filter out the paths for the localization and put the valid ones in the interResult
2012    Boolean checkLP = true;
2013    CFIndex lpLen = lproj ? CFStringGetLength(lproj) : 0;
2014    if (localized && value) {
2015
2016        if (CFGetTypeID(value) == CFStringGetTypeID()){
2017            // We had one result, but since we are going to do a search in a different localization, we will convert the one result into an array of results.
2018            value = CFArrayCreate(kCFAllocatorSystemDefault, (const void **)&value, 1, &kCFTypeArrayCallBacks);
2019        } else {
2020            CFRetain(value);
2021        }
2022
2023        CFRange resultRange, searchRange;
2024        CFIndex pathValueLen;
2025        CFIndex limit = returnArray ? CFArrayGetCount((CFArrayRef)value) : 1;
2026        searchRange.location = 0;
2027        for (CFIndex i = 0; i < limit; i++) {
2028            CFStringRef pathValue = (CFStringRef) CFArrayGetValueAtIndex((CFArrayRef)value, i);
2029            pathValueLen = CFStringGetLength(pathValue);
2030            searchRange.length = pathValueLen;
2031
2032            // if we have subdir, we find the subdir and see if it is after the base path (bundle path + res dir)
2033            Boolean searchForLocalization = false;
2034            if (subDir && CFStringGetLength(subDir) > 0) {
2035                if (CFStringFindWithOptions(pathValue, subDir, searchRange, kCFCompareEqualTo, &resultRange) && resultRange.location != searchRange.location) {
2036                    searchForLocalization = true;
2037                }
2038            } else if (!(subDir && CFStringGetLength(subDir) > 0) && searchRange.length != 0) {
2039                if (CFStringFindWithOptions(pathValue, _CFBundleLprojExtensionWithDot, searchRange, kCFCompareEqualTo, &resultRange) && resultRange.location + 7 < pathValueLen) {
2040                    searchForLocalization = true;
2041                }
2042            }
2043
2044            if (searchForLocalization) {
2045                if (!lpLen || !(CFStringFindWithOptions(pathValue, lproj, searchRange, kCFCompareEqualTo | kCFCompareAnchored, &resultRange) && CFStringFindWithOptions(pathValue, CFSTR("."), CFRangeMake(resultRange.location + resultRange.length, 1), kCFCompareEqualTo, &resultRange))) {
2046                    break;
2047                }
2048                checkLP = false;
2049            }
2050
2051            CFArrayAppendValue(interResult, pathValue);
2052        }
2053
2054        CFRelease(value);
2055
2056        if (!returnArray && CFArrayGetCount(interResult) != 0) {
2057            checkLP = false;
2058        }
2059    } else if (value) {
2060        if (CFGetTypeID(value) == CFArrayGetTypeID()) {
2061            CFArrayAppendArray(interResult, (CFArrayRef)value, CFRangeMake(0, CFArrayGetCount((CFArrayRef)value)));
2062        } else {
2063            CFArrayAppendValue(interResult, value);
2064        }
2065    }
2066
2067    value = NULL;
2068    CFRelease(subTable);
2069
2070    // we fetch the result for a given lproj and join them with the nonlocalized result fetched above
2071    if (lpLen && checkLP) {
2072        CFMutableStringRef lprojSubdirName = CFStringCreateMutableCopy(kCFAllocatorDefault, 0, lproj);
2073        _CFAppendPathExtension2(lprojSubdirName, _CFBundleLprojExtension);
2074        if (subDir && CFStringGetLength(subDir) > 0) {
2075            _CFAppendTrailingPathSlash2(lprojSubdirName);
2076        }
2077        subTable = _CFBundleCopyQueryTable(bundle, bundleURL, languages, path, lprojSubdirName);
2078        CFRelease(lprojSubdirName);
2079        value = CFDictionaryGetValue(subTable, key);
2080
2081        if (value) {
2082            if (CFGetTypeID(value) == CFStringGetTypeID()) {
2083                CFArrayAppendValue(interResult, value);
2084            } else {
2085                CFArrayAppendArray(interResult, (CFArrayRef)value, CFRangeMake(0, CFArrayGetCount((CFArrayRef)value)));
2086            }
2087        }
2088
2089        CFRelease(subTable);
2090    }
2091
2092    // after getting paths, we create urls from the paths
2093    CFTypeRef result = NULL;
2094    if (CFArrayGetCount(interResult) > 0) {
2095        UniChar slash = _CFGetSlash();
2096        CFMutableStringRef urlStr = NULL;
2097        if (bundle) {
2098            urlStr = CFStringCreateMutableCopy(kCFAllocatorSystemDefault, 0, bundle->_bundleBasePath);
2099        } else {
2100            CFURLRef url = CFURLCopyAbsoluteURL(bundleURL);
2101            CFStringRef bundlePath = CFURLCopyFileSystemPath(url, PLATFORM_PATH_STYLE);
2102            urlStr = CFStringCreateMutableCopy(kCFAllocatorSystemDefault, 0, bundlePath);
2103            CFRelease(url);
2104            CFRelease(bundlePath);
2105        }
2106
2107        if (resourcesDirectory && CFStringGetLength(resourcesDirectory)) {
2108            _CFAppendPathComponent2(urlStr, resourcesDirectory);
2109        }
2110
2111        _CFAppendTrailingPathSlash2(urlStr);
2112
2113        if (!returnArray) {
2114            Boolean isOnlyTypeOrAllFiles = CFStringHasPrefix(key, _CFBundleTypeIndicator);
2115            isOnlyTypeOrAllFiles |= CFStringHasPrefix(key, _CFBundleAllFiles);
2116
2117            CFStringRef resultPath = (CFStringRef)CFArrayGetValueAtIndex((CFArrayRef)interResult, 0);
2118            if (!isOnlyTypeOrAllFiles) {
2119                // path is a part of an actual path in the query table, so it should not have a length greater than the buffer size
2120                CFStringAppend(urlStr, resultPath);
2121                if (CFStringGetCharacterAtIndex(resultPath, CFStringGetLength(resultPath)-1) == slash) {
2122                    result = (CFURLRef)CFURLCreateWithFileSystemPath(kCFAllocatorSystemDefault, urlStr, PLATFORM_PATH_STYLE, true);
2123                } else {
2124                    result = (CFURLRef)CFURLCreateWithFileSystemPath(kCFAllocatorSystemDefault, urlStr, PLATFORM_PATH_STYLE, false);
2125                }
2126            } else {
2127                // need to create relative URLs for binary compatibility issues
2128                CFURLRef base = CFURLCreateWithFileSystemPath(kCFAllocatorSystemDefault, urlStr, PLATFORM_PATH_STYLE, true);
2129                result = (CFURLRef)_CFBundleCreateRelativeURLFromBaseAndPath(resultPath, base, slash, _CFGetSlashStr());
2130                CFRelease(base);
2131            }
2132        } else {
2133            // need to create relative URLs for binary compatibility issues
2134            CFIndex numOfPaths = CFArrayGetCount((CFArrayRef)interResult);
2135            CFURLRef base = CFURLCreateWithFileSystemPath(kCFAllocatorSystemDefault, urlStr, PLATFORM_PATH_STYLE, true);
2136            CFMutableArrayRef urls = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
2137            for (CFIndex i = 0; i < numOfPaths; i++) {
2138                CFStringRef path = (CFStringRef)CFArrayGetValueAtIndex((CFArrayRef)interResult, i);
2139                CFURLRef url = _CFBundleCreateRelativeURLFromBaseAndPath(path, base, slash, _CFGetSlashStr());
2140                CFArrayAppendValue(urls, url);
2141                CFRelease(url);
2142            }
2143            result = urls;
2144            CFRelease(base);
2145        }
2146        CFRelease(urlStr);
2147    } else if (returnArray) {
2148        result = CFRetain(interResult);
2149    }
2150    if (path) CFRelease(path);
2151    CFRelease(interResult);
2152    return result;
2153}
2154
2155#pragma mark -
2156
2157// This is the main entry point for all resource lookup.
2158// Research shows that by far the most common scenario is to pass in a bundle object, a resource name, and a resource type, using the default localization.
2159// It is probably the case that more than a few resources will be looked up, making the cost of a readdir less than repeated stats. But it is a relative waste of memory to create strings for every file name in the bundle, especially since those are not what are returned to the caller (URLs are). So, an idea: cache the existence of the most common file names (Info.plist, en.lproj, etc) instead of creating entries for them. If other resources are requested, then go ahead and do the readdir and cache the rest of the file names.
2160// Another idea: if you want caching, you should create a bundle object. Otherwise we'll happily readdir each time.
2161CF_EXPORT CFTypeRef _CFBundleCopyFindResources(CFBundleRef bundle, CFURLRef bundleURL, CFArrayRef languages, CFStringRef resourceName, CFStringRef resourceType, CFStringRef subPath, CFStringRef lproj, Boolean returnArray, Boolean localized, Boolean (^predicate)(CFStringRef filename, Boolean *stop))
2162{
2163
2164    // Don't use any path info passed into the resource name
2165    CFStringRef realResourceName = NULL;
2166    CFStringRef subPathFromResourceName = NULL;
2167
2168    if (resourceName) {
2169        CFIndex slashLocation = -1;
2170        realResourceName = _CFCreateLastPathComponent(kCFAllocatorSystemDefault, resourceName, &slashLocation);
2171        if (slashLocation > 0) {
2172            // do not include the /
2173            subPathFromResourceName = CFStringCreateWithSubstring(kCFAllocatorSystemDefault, resourceName, CFRangeMake(0, slashLocation));
2174        }
2175
2176        // Normalize the resource name by converting it to file system representation. Otherwise when we look for the key in our tables, it will not match.
2177        // TODO: remove this in some way to avoid the malloc?
2178        char buff[CFMaxPathSize];
2179        if (CFStringGetFileSystemRepresentation(realResourceName, buff, CFMaxPathSize)) {
2180            CFRelease(realResourceName);
2181            realResourceName = CFStringCreateWithFileSystemRepresentation(kCFAllocatorSystemDefault, buff);
2182        }
2183    }
2184
2185    CFMutableStringRef key = NULL;
2186    const static UniChar extensionSep = '.';
2187
2188    if (realResourceName && CFStringGetLength(realResourceName) > 0 && resourceType && CFStringGetLength(resourceType) > 0) {
2189        // Testing shows that using a mutable string here is significantly faster than using the format functions.
2190        key = CFStringCreateMutableCopy(kCFAllocatorSystemDefault, 0, realResourceName);
2191        // Don't re-append a . if the resource name already has one
2192        if (CFStringGetCharacterAtIndex(resourceType, 0) != '.') CFStringAppendCharacters(key, &extensionSep, 1);
2193        CFStringAppend(key, resourceType);
2194    } else if (realResourceName && CFStringGetLength(realResourceName) > 0) {
2195        key = (CFMutableStringRef)CFRetain(realResourceName);
2196    } else if (resourceType && CFStringGetLength(resourceType) > 0) {
2197        key = CFStringCreateMutableCopy(kCFAllocatorSystemDefault, 0, _CFBundleTypeIndicator);
2198        // Don't re-append a . if the resource name already has one
2199        if (CFStringGetCharacterAtIndex(resourceType, 0) != '.') CFStringAppendCharacters(key, &extensionSep, 1);
2200        CFStringAppend(key, resourceType);
2201    } else {
2202        key = (CFMutableStringRef)CFRetain(_CFBundleAllFiles);
2203    }
2204
2205    CFStringRef realSubdirectory = NULL;
2206
2207    if (subPath && CFStringGetLength(subPath) && !subPathFromResourceName) {
2208        realSubdirectory = (CFStringRef)CFRetain(subPath);
2209    } else if (subPathFromResourceName && CFStringGetLength(subPathFromResourceName)) {
2210        realSubdirectory = (CFStringRef)CFRetain(subPathFromResourceName);
2211    }
2212
2213    uint8_t bundleVersion = bundle ? _CFBundleLayoutVersion(bundle) : 0;
2214    if (bundleURL && !languages) {
2215        languages = _CFBundleCopyLanguageSearchListInDirectory(kCFAllocatorSystemDefault, bundleURL, &bundleVersion);
2216    } else if (languages) {
2217        CFRetain(languages);
2218    }
2219
2220    CFStringRef resDir = _CFBundleGetResourceDirForVersion(bundleVersion);
2221
2222    CFTypeRef returnValue = _CFBundleCopyURLsOfKey(bundle, bundleURL, languages, resDir, realSubdirectory, key, lproj, returnArray, localized, bundleVersion, predicate);
2223
2224    if ((!returnValue || (CFGetTypeID(returnValue) == CFArrayGetTypeID() && CFArrayGetCount((CFArrayRef)returnValue) == 0)) && (0 == bundleVersion || 2 == bundleVersion)) {
2225        CFStringRef bundlePath = NULL;
2226        if (bundle) {
2227            bundlePath = bundle->_bundleBasePath;
2228            CFRetain(bundlePath);
2229        } else {
2230            CFURLRef absoluteURL = CFURLCopyAbsoluteURL(bundleURL);
2231            bundlePath = CFURLCopyFileSystemPath(absoluteURL, PLATFORM_PATH_STYLE);
2232            CFRelease(absoluteURL);
2233        }
2234        if ((0 == bundleVersion) || CFEqual(CFSTR("/Library/Spotlight"), bundlePath)){
2235            if (returnValue) CFRelease(returnValue);
2236            CFRange found;
2237            // 9 is the length of "Resources"
2238            if ((bundleVersion == 0 && realSubdirectory && CFEqual(realSubdirectory, CFSTR("Resources"))) || (bundleVersion == 2 && realSubdirectory && CFEqual(realSubdirectory, CFSTR("Contents/Resources")))) {
2239                if (realSubdirectory) CFRelease(realSubdirectory);
2240                realSubdirectory = CFSTR("");
2241            } else if ((bundleVersion == 0 && realSubdirectory && CFStringFindWithOptions(realSubdirectory, CFSTR("Resources/"), CFRangeMake(0, 10), kCFCompareEqualTo, &found) && found.location+10 < CFStringGetLength(realSubdirectory))) {
2242                CFStringRef tmpRealSubdirectory = CFStringCreateWithSubstring(kCFAllocatorSystemDefault, realSubdirectory, CFRangeMake(10, CFStringGetLength(realSubdirectory) - 10));
2243                if (realSubdirectory) CFRelease(realSubdirectory);
2244                realSubdirectory = tmpRealSubdirectory;
2245            } else if ((bundleVersion == 2 && realSubdirectory && CFStringFindWithOptions(realSubdirectory, CFSTR("Contents/Resources/"), CFRangeMake(0, 19), kCFCompareEqualTo, &found) && found.location+19 < CFStringGetLength(realSubdirectory))) {
2246                CFStringRef tmpRealSubdirectory = CFStringCreateWithSubstring(kCFAllocatorSystemDefault, realSubdirectory, CFRangeMake(19, CFStringGetLength(realSubdirectory) - 19));
2247                if (realSubdirectory) CFRelease(realSubdirectory);
2248                realSubdirectory = tmpRealSubdirectory;
2249            } else {
2250                // Assume no resources directory
2251                resDir = CFSTR("");
2252            }
2253            returnValue = _CFBundleCopyURLsOfKey(bundle, bundleURL, languages, resDir, realSubdirectory, key, lproj, returnArray, localized, bundleVersion, predicate);
2254        }
2255        CFRelease(bundlePath);
2256    }
2257
2258    if (realResourceName) CFRelease(realResourceName);
2259    if (realSubdirectory) CFRelease(realSubdirectory);
2260    if (subPathFromResourceName) CFRelease(subPathFromResourceName);
2261    if (languages) CFRelease(languages);
2262    CFRelease(key);
2263    return returnValue;
2264}
2265
2266#pragma mark -
2267#pragma mark Localized Strings
2268
2269
2270CF_EXPORT CFStringRef CFBundleCopyLocalizedString(CFBundleRef bundle, CFStringRef key, CFStringRef value, CFStringRef tableName) {
2271    CFStringRef result = NULL;
2272    CFDictionaryRef stringTable = NULL;
2273    static CFSpinLock_t CFBundleLocalizedStringLock = CFSpinLockInit;
2274
2275    if (!key) return (value ? (CFStringRef)CFRetain(value) : (CFStringRef)CFRetain(CFSTR("")));
2276
2277    // Make sure to check the mixed localizations key early -- if the main bundle has not yet been cached, then we need to create the cache of the Info.plist before we start asking for resources (11172381)
2278    (void)CFBundleAllowMixedLocalizations();
2279
2280    if (!tableName || CFEqual(tableName, CFSTR(""))) tableName = _CFBundleDefaultStringTableName;
2281
2282    __CFSpinLock(&CFBundleLocalizedStringLock);
2283    if (__CFBundleGetResourceData(bundle)->_stringTableCache) {
2284        stringTable = (CFDictionaryRef)CFDictionaryGetValue(__CFBundleGetResourceData(bundle)->_stringTableCache, tableName);
2285        if (stringTable) CFRetain(stringTable);
2286    }
2287    __CFSpinUnlock(&CFBundleLocalizedStringLock);
2288
2289    if (!stringTable) {
2290        // Go load the table.
2291        CFURLRef tableURL = CFBundleCopyResourceURL(bundle, tableName, _CFBundleStringTableType, NULL);
2292        if (tableURL) {
2293            CFStringRef nameForSharing = NULL;
2294            if (!stringTable) {
2295                CFDataRef tableData = NULL;
2296                SInt32 errCode;
2297                CFStringRef errStr;
2298#pragma GCC diagnostic push
2299#pragma GCC diagnostic ignored "-Wdeprecated"
2300                if (CFURLCreateDataAndPropertiesFromResource(kCFAllocatorSystemDefault, tableURL, &tableData, NULL, NULL, &errCode)) {
2301#pragma GCC diagnostic pop
2302                    stringTable = (CFDictionaryRef)CFPropertyListCreateFromXMLData(CFGetAllocator(bundle), tableData, kCFPropertyListImmutable, &errStr);
2303                    if (errStr) {
2304                        CFRelease(errStr);
2305                        errStr = NULL;
2306                    }
2307                    if (stringTable && CFDictionaryGetTypeID() != CFGetTypeID(stringTable)) {
2308                        CFRelease(stringTable);
2309                        stringTable = NULL;
2310                    }
2311                    CFRelease(tableData);
2312
2313                }
2314            }
2315            if (nameForSharing) CFRelease(nameForSharing);
2316            if (tableURL) CFRelease(tableURL);
2317        }
2318        if (!stringTable) stringTable = CFDictionaryCreate(CFGetAllocator(bundle), NULL, NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2319
2320        if (!CFStringHasSuffix(tableName, CFSTR(".nocache")) || !_CFExecutableLinkedOnOrAfter(CFSystemVersionLeopard)) {
2321            __CFSpinLock(&CFBundleLocalizedStringLock);
2322            if (!__CFBundleGetResourceData(bundle)->_stringTableCache) __CFBundleGetResourceData(bundle)->_stringTableCache = CFDictionaryCreateMutable(CFGetAllocator(bundle), 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2323            CFDictionarySetValue(__CFBundleGetResourceData(bundle)->_stringTableCache, tableName, stringTable);
2324            __CFSpinUnlock(&CFBundleLocalizedStringLock);
2325        }
2326    }
2327
2328    result = (CFStringRef)CFDictionaryGetValue(stringTable, key);
2329    if (!result) {
2330        if (!value) {
2331            result = (CFStringRef)CFRetain(key);
2332        } else if (CFEqual(value, CFSTR(""))) {
2333            result = (CFStringRef)CFRetain(key);
2334        } else {
2335            result = (CFStringRef)CFRetain(value);
2336        }
2337        __block Boolean capitalize = false;
2338        if (capitalize) {
2339            CFMutableStringRef capitalizedResult = CFStringCreateMutableCopy(kCFAllocatorSystemDefault, 0, result);
2340            CFLog(__kCFLogBundle, CFSTR("Localizable string \"%@\" not found in strings table \"%@\" of bundle %@."), key, tableName, bundle);
2341            CFStringUppercase(capitalizedResult, NULL);
2342            CFRelease(result);
2343            result = capitalizedResult;
2344        }
2345    } else {
2346        CFRetain(result);
2347    }
2348    CFRelease(stringTable);
2349    return result;
2350}
2351
2352