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, ®Code, &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, ®ion); 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, ®Code, 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