2 * Copyright (c) 2015 Apple Inc. All rights reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
24 /* CFBundle_Resources.c
25 Copyright (c) 1999-2014, Apple Inc. All rights reserved.
26 Responsibility: Tony Parker
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>
37 #include "CFInternal.h"
38 #include <CoreFoundation/CFPriv.h>
44 #include <sys/types.h>
47 #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI || DEPLOYMENT_TARGET_LINUX
49 #include <sys/sysctl.h>
54 #if DEPLOYMENT_TARGET_WINDOWS
66 #define mkdir(a,b) _NS_mkdir(a)
67 #define rmdir _NS_rmdir
68 #define unlink _NS_unlink
73 #pragma mark Directory Contents and Caches
75 // These are here for compatibility, but they do nothing anymore
76 CF_EXPORT
void _CFBundleFlushCachesForURL(CFURLRef url
) { }
77 CF_EXPORT
void _CFBundleFlushCaches(void) { }
79 CF_PRIVATE
void _CFBundleFlushQueryTableCache(CFBundleRef bundle
) {
80 __CFLock(&bundle
->_queryLock
);
81 if (bundle
->_queryTable
) {
82 CFDictionaryRemoveAllValues(bundle
->_queryTable
);
84 __CFUnlock(&bundle
->_queryLock
);
88 #pragma mark Resource URL Lookup
90 static Boolean
_CFIsResourceCommon(char *path
, Boolean
*isDir
) {
93 if (_CFGetPathProperties(kCFAllocatorSystemDefault
, path
, &exists
, &mode
, NULL
, NULL
, NULL
, NULL
) == 0) {
94 if (isDir
) *isDir
= ((exists
&& ((mode
& S_IFMT
) == S_IFDIR
)) ? true : false);
95 return (exists
&& (mode
& 0444));
100 CF_PRIVATE Boolean
_CFIsResourceAtURL(CFURLRef url
, Boolean
*isDir
) {
101 char path
[CFMaxPathSize
];
102 if (!CFURLGetFileSystemRepresentation(url
, true, (uint8_t *)path
, CFMaxPathLength
)) return false;
104 return _CFIsResourceCommon(path
, isDir
);
107 CF_PRIVATE Boolean
_CFIsResourceAtPath(CFStringRef path
, Boolean
*isDir
) {
108 char pathBuf
[CFMaxPathSize
];
109 if (!CFStringGetFileSystemRepresentation(path
, pathBuf
, CFMaxPathSize
)) return false;
111 return _CFIsResourceCommon(pathBuf
, isDir
);
115 static CFStringRef
_CFBundleGetResourceDirForVersion(uint8_t version
) {
117 return _CFBundleSupportFilesDirectoryName1WithResources
;
118 } else if (2 == version
) {
119 return _CFBundleSupportFilesDirectoryName2WithResources
;
120 } else if (0 == version
) {
121 return _CFBundleResourcesDirectoryName
;
126 CF_PRIVATE
void _CFBundleAppendResourceDir(CFMutableStringRef path
, uint8_t version
) {
128 // /path/to/bundle/Support Files/
129 CFStringAppend(path
, _CFBundleSupportFilesDirectoryName1
);
130 _CFAppendTrailingPathSlash2(path
);
131 } else if (2 == version
) {
132 // /path/to/bundle/Contents/
133 CFStringAppend(path
, _CFBundleSupportFilesDirectoryName2
);
134 _CFAppendTrailingPathSlash2(path
);
136 if (0 == version
|| 1 == version
|| 2 == version
) {
137 // /path/to/bundle/<above>/Resources
138 CFStringAppend(path
, _CFBundleResourcesDirectoryName
);
142 CF_EXPORT CFURLRef
CFBundleCopyResourceURL(CFBundleRef bundle
, CFStringRef resourceName
, CFStringRef resourceType
, CFStringRef subDirName
) {
143 if (!bundle
) return NULL
;
144 CFURLRef result
= (CFURLRef
) _CFBundleCopyFindResources(bundle
, NULL
, NULL
, resourceName
, resourceType
, subDirName
, NULL
, false, false, NULL
);
148 CF_EXPORT CFArrayRef
CFBundleCopyResourceURLsOfType(CFBundleRef bundle
, CFStringRef resourceType
, CFStringRef subDirName
) {
149 if (!bundle
) return CFArrayCreateMutable(kCFAllocatorSystemDefault
, 0, &kCFTypeArrayCallBacks
);
150 CFArrayRef result
= (CFArrayRef
) _CFBundleCopyFindResources(bundle
, NULL
, NULL
, NULL
, resourceType
, subDirName
, NULL
, true, false, NULL
);
154 CF_EXPORT CFURLRef
_CFBundleCopyResourceURLForLanguage(CFBundleRef bundle
, CFStringRef resourceName
, CFStringRef resourceType
, CFStringRef subDirName
, CFStringRef language
) {
155 return CFBundleCopyResourceURLForLocalization(bundle
, resourceName
, resourceType
, subDirName
, language
);
158 CF_EXPORT CFURLRef
CFBundleCopyResourceURLForLocalization(CFBundleRef bundle
, CFStringRef resourceName
, CFStringRef resourceType
, CFStringRef subDirName
, CFStringRef localizationName
) {
159 if (!bundle
) return NULL
;
160 CFURLRef result
= (CFURLRef
) _CFBundleCopyFindResources(bundle
, NULL
, NULL
, resourceName
, resourceType
, subDirName
, localizationName
, false, true, NULL
);
164 CF_EXPORT CFArrayRef
_CFBundleCopyResourceURLsOfTypeForLanguage(CFBundleRef bundle
, CFStringRef resourceType
, CFStringRef subDirName
, CFStringRef language
) {
165 return CFBundleCopyResourceURLsOfTypeForLocalization(bundle
, resourceType
, subDirName
, language
);
168 CF_EXPORT CFArrayRef
CFBundleCopyResourceURLsOfTypeForLocalization(CFBundleRef bundle
, CFStringRef resourceType
, CFStringRef subDirName
, CFStringRef localizationName
) {
169 if (!bundle
) return CFArrayCreateMutable(kCFAllocatorSystemDefault
, 0, &kCFTypeArrayCallBacks
);
170 CFArrayRef result
= (CFArrayRef
) _CFBundleCopyFindResources(bundle
, NULL
, NULL
, NULL
, resourceType
, subDirName
, localizationName
, true, true, NULL
);
174 CF_EXPORT CFURLRef
CFBundleCopyResourceURLInDirectory(CFURLRef bundleURL
, CFStringRef resourceName
, CFStringRef resourceType
, CFStringRef subDirName
) {
175 CFURLRef result
= NULL
;
176 unsigned char buff
[CFMaxPathSize
];
177 CFURLRef newURL
= NULL
;
179 if (!CFURLGetFileSystemRepresentation(bundleURL
, true, buff
, CFMaxPathSize
)) return NULL
;
181 newURL
= CFURLCreateFromFileSystemRepresentation(kCFAllocatorSystemDefault
, buff
, strlen((char *)buff
), true);
182 if (!newURL
) newURL
= (CFURLRef
)CFRetain(bundleURL
);
183 if (_CFBundleCouldBeBundle(newURL
)) {
184 result
= (CFURLRef
) _CFBundleCopyFindResources(NULL
, bundleURL
, NULL
, resourceName
, resourceType
, subDirName
, NULL
, false, false, NULL
);
186 if (newURL
) CFRelease(newURL
);
190 CF_EXPORT CFArrayRef
CFBundleCopyResourceURLsOfTypeInDirectory(CFURLRef bundleURL
, CFStringRef resourceType
, CFStringRef subDirName
) {
191 CFArrayRef array
= NULL
;
192 unsigned char buff
[CFMaxPathSize
];
193 CFURLRef newURL
= NULL
;
195 if (!CFURLGetFileSystemRepresentation(bundleURL
, true, buff
, CFMaxPathSize
)) return NULL
;
197 newURL
= CFURLCreateFromFileSystemRepresentation(kCFAllocatorSystemDefault
, buff
, strlen((char *)buff
), true);
198 if (!newURL
) newURL
= (CFURLRef
)CFRetain(bundleURL
);
199 if (_CFBundleCouldBeBundle(newURL
)) {
200 array
= (CFArrayRef
) _CFBundleCopyFindResources(NULL
, bundleURL
, NULL
, NULL
, resourceType
, subDirName
, NULL
, true, false, NULL
);
202 if (newURL
) CFRelease(newURL
);
208 #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_WINDOWS
209 // Note that subDirName is expected to be the string for a URL
210 CF_INLINE Boolean
_CFBundleURLHasSubDir(CFURLRef url
, CFStringRef subDirName
) {
211 Boolean isDir
= false, result
= false;
212 CFURLRef dirURL
= CFURLCreateWithString(kCFAllocatorSystemDefault
, subDirName
, url
);
214 if (_CFIsResourceAtURL(dirURL
, &isDir
) && isDir
) result
= true;
221 CF_PRIVATE
uint8_t _CFBundleGetBundleVersionForURL(CFURLRef url
) {
222 // check for existence of "Resources" or "Contents" or "Support Files"
223 // but check for the most likely one first
224 // version 0: old-style "Resources" bundles
225 // version 1: obsolete "Support Files" bundles
226 // version 2: modern "Contents" bundles
227 // version 3: none of the above (see below)
228 // version 4: not a bundle (for main bundle only)
230 CFURLRef absoluteURL
= CFURLCopyAbsoluteURL(url
);
231 CFStringRef directoryPath
= CFURLCopyFileSystemPath(absoluteURL
, PLATFORM_PATH_STYLE
);
232 CFRelease(absoluteURL
);
234 Boolean hasFrameworkSuffix
= CFStringHasSuffix(CFURLGetString(url
), CFSTR(".framework/"));
235 #if DEPLOYMENT_TARGET_WINDOWS
236 hasFrameworkSuffix
= hasFrameworkSuffix
|| CFStringHasSuffix(CFURLGetString(url
), CFSTR(".framework\\"));
240 #define _CFBundleSupportFilesDirectoryName1 CFSTR("Support Files")
241 #define _CFBundleSupportFilesDirectoryName2 CFSTR("Contents")
242 #define _CFBundleResourcesDirectoryName CFSTR("Resources")
243 #define _CFBundleExecutablesDirectoryName CFSTR("Executables")
244 #define _CFBundleNonLocalizedResourcesDirectoryName CFSTR("Non-localized Resources")
246 __block
uint8_t localVersion
= 3;
247 CFIndex resourcesDirectoryLength
= CFStringGetLength(_CFBundleResourcesDirectoryName
);
248 CFIndex contentsDirectoryLength
= CFStringGetLength(_CFBundleSupportFilesDirectoryName2
);
249 CFIndex supportFilesDirectoryLength
= CFStringGetLength(_CFBundleSupportFilesDirectoryName1
);
251 __block Boolean foundResources
= false;
252 __block Boolean foundSupportFiles2
= false;
253 __block Boolean foundSupportFiles1
= false;
255 _CFIterateDirectory(directoryPath
, ^Boolean (CFStringRef fileName
, uint8_t fileType
) {
256 // We're looking for a few different names, and also some info on if it's a directory or not.
257 // 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.
258 if (fileType
== DT_DIR
|| fileType
== DT_LNK
) {
259 CFIndex fileNameLen
= CFStringGetLength(fileName
);
260 if (fileNameLen
== resourcesDirectoryLength
&& CFStringCompareWithOptions(fileName
, _CFBundleResourcesDirectoryName
, CFRangeMake(0, resourcesDirectoryLength
), kCFCompareCaseInsensitive
) == kCFCompareEqualTo
) {
261 foundResources
= true;
262 } else if (fileNameLen
== contentsDirectoryLength
&& CFStringCompareWithOptions(fileName
, _CFBundleSupportFilesDirectoryName2
, CFRangeMake(0, contentsDirectoryLength
), kCFCompareCaseInsensitive
) == kCFCompareEqualTo
) {
263 foundSupportFiles2
= true;
264 } else if (fileNameLen
== supportFilesDirectoryLength
&& CFStringCompareWithOptions(fileName
, _CFBundleSupportFilesDirectoryName1
, CFRangeMake(0, supportFilesDirectoryLength
), kCFCompareCaseInsensitive
) == kCFCompareEqualTo
) {
265 foundSupportFiles1
= true;
271 // The order of these if statements is important - the Resources directory presence takes precedence over Contents, and so forth.
272 if (hasFrameworkSuffix
) {
273 if (foundResources
) {
275 } else if (foundSupportFiles2
) {
277 } else if (foundSupportFiles1
) {
281 if (foundSupportFiles2
) {
283 } else if (foundResources
) {
285 } else if (foundSupportFiles1
) {
290 #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_WINDOWS
291 // 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.
292 if (localVersion
== 3) {
293 if (hasFrameworkSuffix
) {
294 if (_CFBundleURLHasSubDir(url
, _CFBundleResourcesURLFromBase0
)) localVersion
= 0;
295 else if (_CFBundleURLHasSubDir(url
, _CFBundleSupportFilesURLFromBase2
)) localVersion
= 2;
296 else if (_CFBundleURLHasSubDir(url
, _CFBundleSupportFilesURLFromBase1
)) localVersion
= 1;
298 if (_CFBundleURLHasSubDir(url
, _CFBundleSupportFilesURLFromBase2
)) localVersion
= 2;
299 else if (_CFBundleURLHasSubDir(url
, _CFBundleResourcesURLFromBase0
)) localVersion
= 0;
300 else if (_CFBundleURLHasSubDir(url
, _CFBundleSupportFilesURLFromBase1
)) localVersion
= 1;
305 CFRelease(directoryPath
);
310 #pragma mark Platforms
312 CF_EXPORT CFArrayRef
_CFBundleGetSupportedPlatforms(CFBundleRef bundle
) {
313 // This function is obsolete
317 CF_EXPORT CFStringRef
_CFBundleGetCurrentPlatform(void) {
318 #if DEPLOYMENT_TARGET_MACOSX
319 return CFSTR("MacOS");
320 #elif DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
321 return CFSTR("iPhoneOS");
322 #elif DEPLOYMENT_TARGET_WINDOWS
323 return CFSTR("Windows");
324 #elif DEPLOYMENT_TARGET_SOLARIS
325 return CFSTR("Solaris");
326 #elif DEPLOYMENT_TARGET_HPUX
327 return CFSTR("HPUX");
328 #elif DEPLOYMENT_TARGET_LINUX
329 return CFSTR("Linux");
330 #elif DEPLOYMENT_TARGET_FREEBSD
331 return CFSTR("FreeBSD");
333 #error Unknown or unspecified DEPLOYMENT_TARGET
337 CF_PRIVATE CFStringRef
_CFBundleGetPlatformExecutablesSubdirectoryName(void) {
338 #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
339 return CFSTR("MacOS");
340 #elif DEPLOYMENT_TARGET_WINDOWS
341 return CFSTR("Windows");
342 #elif DEPLOYMENT_TARGET_SOLARIS
343 return CFSTR("Solaris");
344 #elif DEPLOYMENT_TARGET_HPUX
345 return CFSTR("HPUX");
346 #elif DEPLOYMENT_TARGET_LINUX
347 return CFSTR("Linux");
348 #elif DEPLOYMENT_TARGET_FREEBSD
349 return CFSTR("FreeBSD");
351 #error Unknown or unspecified DEPLOYMENT_TARGET
355 CF_PRIVATE CFStringRef
_CFBundleGetAlternatePlatformExecutablesSubdirectoryName(void) {
356 #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
357 return CFSTR("Mac OS X");
358 #elif DEPLOYMENT_TARGET_WINDOWS
359 return CFSTR("WinNT");
360 #elif DEPLOYMENT_TARGET_SOLARIS
361 return CFSTR("Solaris");
362 #elif DEPLOYMENT_TARGET_HPUX
363 return CFSTR("HP-UX");
364 #elif DEPLOYMENT_TARGET_LINUX
365 return CFSTR("Linux");
366 #elif DEPLOYMENT_TARGET_FREEBSD
367 return CFSTR("FreeBSD");
369 #error Unknown or unspecified DEPLOYMENT_TARGET
373 CF_PRIVATE CFStringRef
_CFBundleGetOtherPlatformExecutablesSubdirectoryName(void) {
374 #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
375 return CFSTR("MacOSClassic");
376 #elif DEPLOYMENT_TARGET_WINDOWS
377 return CFSTR("Other");
378 #elif DEPLOYMENT_TARGET_HPUX
379 return CFSTR("Other");
380 #elif DEPLOYMENT_TARGET_SOLARIS
381 return CFSTR("Other");
382 #elif DEPLOYMENT_TARGET_LINUX
383 return CFSTR("Other");
384 #elif DEPLOYMENT_TARGET_FREEBSD
385 return CFSTR("Other");
387 #error Unknown or unspecified DEPLOYMENT_TARGET
391 CF_PRIVATE CFStringRef
_CFBundleGetOtherAlternatePlatformExecutablesSubdirectoryName(void) {
392 #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
393 return CFSTR("Mac OS 8");
394 #elif DEPLOYMENT_TARGET_WINDOWS
395 return CFSTR("Other");
396 #elif DEPLOYMENT_TARGET_HPUX
397 return CFSTR("Other");
398 #elif DEPLOYMENT_TARGET_SOLARIS
399 return CFSTR("Other");
400 #elif DEPLOYMENT_TARGET_LINUX
401 return CFSTR("Other");
402 #elif DEPLOYMENT_TARGET_FREEBSD
403 return CFSTR("Other");
405 #error Unknown or unspecified DEPLOYMENT_TARGET
409 CFArrayRef
CFBundleCopyExecutableArchitecturesForURL(CFURLRef url
) {
410 CFArrayRef result
= NULL
;
411 CFBundleRef bundle
= CFBundleCreate(kCFAllocatorSystemDefault
, url
);
413 result
= CFBundleCopyExecutableArchitectures(bundle
);
416 result
= _CFBundleCopyArchitecturesForExecutable(url
);
422 #pragma mark Resource Lookup - Query Table
424 static void _CFBundleAddValueForType(CFStringRef type
, CFMutableDictionaryRef queryTable
, CFMutableDictionaryRef typeDir
, CFTypeRef value
, CFMutableDictionaryRef addedTypes
, Boolean firstLproj
) {
425 CFMutableArrayRef tFiles
= (CFMutableArrayRef
) CFDictionaryGetValue(typeDir
, type
);
427 CFStringRef key
= CFStringCreateWithFormat(kCFAllocatorSystemDefault
, NULL
, CFSTR("%@.%@"), _CFBundleTypeIndicator
, type
);
428 tFiles
= CFArrayCreateMutable(kCFAllocatorSystemDefault
, 0, &kCFTypeArrayCallBacks
);
429 CFDictionarySetValue(queryTable
, key
, tFiles
);
430 CFDictionarySetValue(typeDir
, type
, tFiles
);
435 CFArrayAppendValue(tFiles
, value
);
436 } else if (firstLproj
) {
437 CFDictionarySetValue(addedTypes
, type
, type
);
438 CFArrayAppendValue(tFiles
, value
);
439 } else if (!(CFDictionaryGetValue(addedTypes
, type
))) {
440 CFArrayAppendValue(tFiles
, value
);
445 _CFBundleFileVersionNoProductNoPlatform
= 1,
446 _CFBundleFileVersionWithProductNoPlatform
,
447 _CFBundleFileVersionNoProductWithPlatform
,
448 _CFBundleFileVersionWithProductWithPlatform
,
449 _CFBundleFileVersionUnmatched
450 } _CFBundleFileVersion
;
452 static _CFBundleFileVersion
_CFBundleCheckFileProductAndPlatform(CFStringRef file
, CFRange searchRange
, CFStringRef product
, CFStringRef platform
)
454 _CFBundleFileVersion version
;
455 Boolean foundprod
, foundplat
;
456 foundplat
= foundprod
= NO
;
457 Boolean wrong
= false;
459 if (CFStringFindWithOptions(file
, CFSTR("~"), searchRange
, 0, NULL
)) {
460 if (CFStringGetLength(product
) != 1) {
461 // todo: really, search the same range again?
462 if (CFStringFindWithOptions(file
, product
, searchRange
, kCFCompareEqualTo
, NULL
)) {
467 wrong
= _CFBundleSupportedProductName(file
, searchRange
);
471 if (!wrong
&& CFStringFindWithOptions(file
, CFSTR("-"), searchRange
, 0, NULL
)) {
472 if (CFStringFindWithOptions(file
, platform
, searchRange
, kCFCompareEqualTo
, NULL
)) {
476 wrong
= _CFBundleSupportedPlatformName(file
, searchRange
);
481 version
= _CFBundleFileVersionUnmatched
;
482 } else if (foundplat
&& foundprod
) {
483 version
= _CFBundleFileVersionWithProductWithPlatform
;
484 } else if (foundplat
) {
485 version
= _CFBundleFileVersionNoProductWithPlatform
;
486 } else if (foundprod
) {
487 version
= _CFBundleFileVersionWithProductNoPlatform
;
489 version
= _CFBundleFileVersionNoProductNoPlatform
;
494 static _CFBundleFileVersion
_CFBundleVersionForFileName(CFStringRef fileName
, CFStringRef expectedProduct
, CFStringRef expectedPlatform
, CFRange
*outProductRange
, CFRange
*outPlatformRange
) {
495 // Search for a product name, e.g.: foo~iphone.jpg or bar~ipad
496 Boolean foundProduct
= false;
497 Boolean foundPlatform
= false;
498 CFIndex fileNameLen
= CFStringGetLength(fileName
);
499 CFRange productRange
;
500 CFRange platformRange
;
502 CFIndex dotLocation
= fileNameLen
;
503 for (CFIndex i
= fileNameLen
- 1; i
> 0; i
--) {
504 UniChar c
= CFStringGetCharacterAtIndex(fileName
, i
);
508 #if DEPLOYMENT_TARGET_EMBEDDED
509 // Product names are only supported on iOS
510 // ref docs here: "iOS Supports Device-Specific Resources" in "Resource Programming Guide"
511 else if (c
== '~' && !foundProduct
) {
512 productRange
= CFRangeMake(i
, dotLocation
- i
);
513 foundProduct
= (CFStringCompareWithOptions(fileName
, expectedProduct
, productRange
, kCFCompareAnchored
) == kCFCompareEqualTo
);
514 if (foundProduct
&& outProductRange
) *outProductRange
= productRange
;
519 platformRange
= CFRangeMake(i
, productRange
.location
- i
);
521 platformRange
= CFRangeMake(i
, dotLocation
- i
);
523 foundPlatform
= (CFStringCompareWithOptions(fileName
, expectedPlatform
, platformRange
, kCFCompareAnchored
) == kCFCompareEqualTo
);
524 if (foundPlatform
&& outPlatformRange
) *outPlatformRange
= platformRange
;
529 _CFBundleFileVersion version
;
530 if (foundPlatform
&& foundProduct
) {
531 version
= _CFBundleFileVersionWithProductWithPlatform
;
532 } else if (foundPlatform
) {
533 version
= _CFBundleFileVersionNoProductWithPlatform
;
534 } else if (foundProduct
) {
535 version
= _CFBundleFileVersionWithProductNoPlatform
;
537 version
= _CFBundleFileVersionNoProductNoPlatform
;
542 // Splits up a string into its various parts. Note that the out-types must be released by the caller if they exist.
543 static void _CFBundleSplitFileName(CFStringRef fileName
, CFStringRef
*noProductOrPlatform
, CFStringRef
*endType
, CFStringRef
*startType
, CFStringRef expectedProduct
, CFStringRef expectedPlatform
, _CFBundleFileVersion
*version
) {
544 CFIndex fileNameLen
= CFStringGetLength(fileName
);
546 if (endType
|| startType
) {
547 // Search for the type from the end (type defined as everything after the last '.')
548 // e.g., a file name like foo.jpg has a type of 'jpg'
549 Boolean foundDot
= false;
550 uint16_t dotLocation
= 0;
551 for (CFIndex i
= fileNameLen
; i
> 0; i
--) {
552 if (CFStringGetCharacterAtIndex(fileName
, i
- 1) == '.') {
559 if (foundDot
&& dotLocation
!= fileNameLen
- 1) {
560 if (endType
) *endType
= CFStringCreateWithSubstring(kCFAllocatorSystemDefault
, fileName
, CFRangeMake(dotLocation
+ 1, CFStringGetLength(fileName
) - dotLocation
- 1));
563 // Search for the type from the beginning (type defined as everything after the first '.')
564 // e.g., a file name like foo.jpg.gz has a type of 'jpg.gz'
566 for (CFIndex i
= 0; i
< fileNameLen
; i
++) {
567 if (CFStringGetCharacterAtIndex(fileName
, i
) == '.') {
568 // no need to create this again if it's the same as previous
569 if (i
!= dotLocation
) {
570 *startType
= CFStringCreateWithSubstring(kCFAllocatorSystemDefault
, fileName
, CFRangeMake(i
+ 1, CFStringGetLength(fileName
) - i
- 1));
578 CFRange productRange
, platformRange
;
579 *version
= _CFBundleVersionForFileName(fileName
, expectedProduct
, expectedPlatform
, &productRange
, &platformRange
);
581 Boolean foundPlatform
= (*version
== _CFBundleFileVersionNoProductWithPlatform
|| *version
== _CFBundleFileVersionWithProductWithPlatform
);
582 Boolean foundProduct
= (*version
== _CFBundleFileVersionWithProductNoPlatform
|| *version
== _CFBundleFileVersionWithProductWithPlatform
);
583 // Create a string that excludes both platform and product name
584 // e.g., foo-iphone~iphoneos.jpg -> foo.jpg
585 if (foundPlatform
|| foundProduct
) {
586 CFMutableStringRef fileNameScratch
= CFStringCreateMutableCopy(kCFAllocatorSystemDefault
, 0, fileName
);
587 CFIndex start
, length
= 0;
589 // 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.
591 start
= platformRange
.location
;
593 start
= productRange
.location
;
596 if (foundPlatform
&& foundProduct
) {
597 length
= platformRange
.length
+ productRange
.length
;
598 } else if (foundPlatform
) {
599 length
= platformRange
.length
;
600 } else if (foundProduct
) {
601 length
= productRange
.length
;
603 CFStringDelete(fileNameScratch
, CFRangeMake(start
, length
));
604 *noProductOrPlatform
= (CFStringRef
)fileNameScratch
;
608 static Boolean
_CFBundleReadDirectory(CFStringRef pathOfDir
, CFStringRef subdirectory
, CFMutableArrayRef allFiles
, Boolean hasFileAdded
, CFMutableDictionaryRef queryTable
, CFMutableDictionaryRef typeDir
, CFMutableDictionaryRef addedTypes
, Boolean firstLproj
, CFStringRef product
, CFStringRef platform
, CFStringRef lprojName
, Boolean appendLprojCharacters
) {
610 Boolean result
= true;
611 CFMutableStringRef pathPrefix
= NULL
;
613 pathPrefix
= CFStringCreateMutableCopy(kCFAllocatorSystemDefault
, 0, lprojName
);
614 if (appendLprojCharacters
) _CFAppendPathExtension2(pathPrefix
, _CFBundleLprojExtension
);
615 _CFAppendTrailingPathSlash2(pathPrefix
);
619 CFStringAppend(pathPrefix
, subdirectory
);
621 pathPrefix
= CFStringCreateMutableCopy(kCFAllocatorSystemDefault
, 0, subdirectory
);
623 UniChar lastChar
= CFStringGetCharacterAtIndex(subdirectory
, CFStringGetLength(subdirectory
)-1);
624 if (lastChar
!= _CFGetSlash()) {
625 _CFAppendTrailingPathSlash2(pathPrefix
);
629 _CFIterateDirectory(pathOfDir
, ^Boolean(CFStringRef fileName
, uint8_t fileType
) {
630 CFStringRef startType
= NULL
, endType
= NULL
, noProductOrPlatform
= NULL
;
631 _CFBundleFileVersion fileVersion
;
632 _CFBundleSplitFileName(fileName
, &noProductOrPlatform
, &endType
, &startType
, product
, platform
, &fileVersion
);
634 CFStringRef pathToFile
;
635 if (pathPrefix
&& CFStringGetLength(pathPrefix
) > 0) {
636 CFMutableStringRef tmp
= CFStringCreateMutableCopy(kCFAllocatorSystemDefault
, 0, pathPrefix
);
637 CFStringAppend(tmp
, fileName
);
638 pathToFile
= (CFStringRef
)tmp
;
640 pathToFile
= (CFStringRef
)CFRetain(fileName
);
643 // 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
644 Boolean appendSlash
= false;
645 if (fileType
== DT_DIR
) {
648 #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI || DEPLOYMENT_TARGET_LINUX || DEPLOYMENT_TARGET_FREEBSD
649 else if (fileType
== DT_UNKNOWN
) {
650 Boolean isDir
= false;
651 char subdirPath
[CFMaxPathLength
];
653 if (CFStringGetFileSystemRepresentation(pathOfDir
, subdirPath
, sizeof(subdirPath
))) {
654 strlcat(subdirPath
, "/", sizeof(subdirPath
));
655 char fileNameBuf
[CFMaxPathLength
];
656 if (CFStringGetFileSystemRepresentation(fileName
, fileNameBuf
, sizeof(fileNameBuf
))) {
657 strlcat(subdirPath
, fileNameBuf
, sizeof(subdirPath
));
658 if (stat(subdirPath
, &statBuf
) == 0) {
659 isDir
= ((statBuf
.st_mode
& S_IFMT
) == S_IFDIR
);
669 // This is fairly inefficient
670 CFMutableStringRef tmp
= CFStringCreateMutableCopy(kCFAllocatorSystemDefault
, 0, pathToFile
);
671 _CFAppendTrailingPathSlash2(tmp
);
672 CFRelease(pathToFile
);
673 pathToFile
= (CFStringRef
)tmp
;
676 // put it into all file array
678 CFArrayAppendValue(allFiles
, pathToFile
);
682 _CFBundleAddValueForType(startType
, queryTable
, typeDir
, pathToFile
, addedTypes
, firstLproj
);
686 _CFBundleAddValueForType(endType
, queryTable
, typeDir
, pathToFile
, addedTypes
, firstLproj
);
689 if (fileVersion
== _CFBundleFileVersionNoProductNoPlatform
|| fileVersion
== _CFBundleFileVersionUnmatched
) {
690 // No product/no platform, or unmatched files get added directly to the query table.
691 CFStringRef prevPath
= (CFStringRef
)CFDictionaryGetValue(queryTable
, fileName
);
693 CFDictionarySetValue(queryTable
, fileName
, pathToFile
);
696 // 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.
697 CFStringRef prevPath
= (CFStringRef
)CFDictionaryGetValue(queryTable
, fileName
);
699 CFDictionarySetValue(queryTable
, fileName
, pathToFile
);
702 // Then we add the more specific name as well, replacing the existing one if this is a more specific version.
703 if (noProductOrPlatform
) {
704 // add the path of the key into the query table
705 prevPath
= (CFStringRef
) CFDictionaryGetValue(queryTable
, noProductOrPlatform
);
707 CFDictionarySetValue(queryTable
, noProductOrPlatform
, pathToFile
);
709 if (!lprojName
|| CFStringHasPrefix(prevPath
, lprojName
)) {
710 // we need to know the version of exisiting path to see if we can replace it by the current path
713 searchRange
.location
= CFStringGetLength(lprojName
);
714 searchRange
.length
= CFStringGetLength(prevPath
) - searchRange
.location
;
716 searchRange
.location
= 0;
717 searchRange
.length
= CFStringGetLength(prevPath
);
719 _CFBundleFileVersion prevFileVersion
= _CFBundleCheckFileProductAndPlatform(prevPath
, searchRange
, product
, platform
);
720 switch (prevFileVersion
) {
721 case _CFBundleFileVersionNoProductNoPlatform
:
722 CFDictionarySetValue(queryTable
, noProductOrPlatform
, pathToFile
);
724 case _CFBundleFileVersionWithProductNoPlatform
:
725 if (fileVersion
== _CFBundleFileVersionWithProductWithPlatform
) CFDictionarySetValue(queryTable
, noProductOrPlatform
, pathToFile
);
727 case _CFBundleFileVersionNoProductWithPlatform
:
728 CFDictionarySetValue(queryTable
, noProductOrPlatform
, pathToFile
);
738 if (pathToFile
) CFRelease(pathToFile
);
739 if (startType
) CFRelease(startType
);
740 if (endType
) CFRelease(endType
);
741 if (noProductOrPlatform
) CFRelease(noProductOrPlatform
);
746 if (pathPrefix
) CFRelease(pathPrefix
);
751 static CFDictionaryRef
_CFBundleCreateQueryTableAtPath(CFStringRef inPath
, CFArrayRef languages
, CFStringRef resourcesDirectory
, CFStringRef subdirectory
)
754 CFMutableDictionaryRef queryTable
= CFDictionaryCreateMutable(kCFAllocatorSystemDefault
, 0, &kCFCopyStringDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
755 CFMutableArrayRef allFiles
= CFArrayCreateMutable(kCFAllocatorSystemDefault
, 0, &kCFTypeArrayCallBacks
);
756 CFMutableDictionaryRef typeDir
= CFDictionaryCreateMutable(kCFAllocatorSystemDefault
, 0, &kCFCopyStringDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
758 CFStringRef productName
= _CFGetProductName();//CFSTR("iphone");
759 CFStringRef platformName
= _CFGetPlatformName();//CFSTR("iphoneos");
760 if (CFEqual(productName
, CFSTR("ipod"))) {
761 productName
= CFSTR("iphone");
763 CFStringRef product
= CFStringCreateWithFormat(kCFAllocatorSystemDefault
, NULL
, CFSTR("~%@"), productName
);
764 CFStringRef platform
= CFStringCreateWithFormat(kCFAllocatorSystemDefault
, NULL
, CFSTR("-%@"), platformName
);
766 CFMutableStringRef path
= CFStringCreateMutableCopy(kCFAllocatorSystemDefault
, 0, inPath
);
768 if (resourcesDirectory
) {
769 _CFAppendPathComponent2(path
, resourcesDirectory
);
772 // Record the length of the base path, so we can strip off the stuff we'll be appending later
773 CFIndex basePathLen
= CFStringGetLength(path
);
776 _CFAppendPathComponent2(path
, subdirectory
);
778 // read the content in sub dir and put them into query table
779 _CFBundleReadDirectory(path
, subdirectory
, allFiles
, false, queryTable
, typeDir
, NULL
, false, product
, platform
, NULL
, false);
780 CFStringDelete(path
, CFRangeMake(basePathLen
, CFStringGetLength(path
) - basePathLen
)); // Strip the string back to the base path
782 CFIndex numOfAllFiles
= CFArrayGetCount(allFiles
);
784 CFIndex numLprojs
= languages
? CFArrayGetCount(languages
) : 0;
785 CFMutableDictionaryRef addedTypes
= CFDictionaryCreateMutable(kCFAllocatorSystemDefault
, 0, &kCFCopyStringDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
787 Boolean hasFileAdded
= false;
788 Boolean firstLproj
= true;
790 // First, search lproj for user's chosen language
791 if (numLprojs
>= 1) {
792 CFStringRef lprojTarget
= (CFStringRef
)CFArrayGetValueAtIndex(languages
, 0);
793 _CFAppendPathComponent2(path
, lprojTarget
);
794 _CFAppendPathExtension2(path
, _CFBundleLprojExtension
);
796 _CFAppendPathComponent2(path
, subdirectory
);
798 _CFBundleReadDirectory(path
, subdirectory
, allFiles
, hasFileAdded
, queryTable
, typeDir
, addedTypes
, firstLproj
, product
, platform
, lprojTarget
, true);
799 CFStringDelete(path
, CFRangeMake(basePathLen
, CFStringGetLength(path
) - basePathLen
)); // Strip the string back to the base path
801 if (!hasFileAdded
&& numOfAllFiles
< CFArrayGetCount(allFiles
)) {
807 // Next, search Base.lproj folder
808 _CFAppendPathComponent2(path
, _CFBundleBaseDirectory
);
809 _CFAppendPathExtension2(path
, _CFBundleLprojExtension
);
811 _CFAppendPathComponent2(path
, subdirectory
);
813 _CFBundleReadDirectory(path
, subdirectory
, allFiles
, hasFileAdded
, queryTable
, typeDir
, addedTypes
, YES
, product
, platform
, _CFBundleBaseDirectory
, true);
814 CFStringDelete(path
, CFRangeMake(basePathLen
, CFStringGetLength(path
) - basePathLen
)); // Strip the string back to the base path
816 if (!hasFileAdded
&& numOfAllFiles
< CFArrayGetCount(allFiles
)) {
820 // Finally, search remaining languages (development language first)
821 if (numLprojs
>= 2) {
822 // for each lproj we are interested in, read the content and put them into query table
823 for (CFIndex i
= 1; i
< CFArrayGetCount(languages
); i
++) {
824 CFStringRef lprojTarget
= (CFStringRef
) CFArrayGetValueAtIndex(languages
, i
);
825 _CFAppendPathComponent2(path
, lprojTarget
);
826 _CFAppendPathExtension2(path
, _CFBundleLprojExtension
);
828 _CFAppendPathComponent2(path
, subdirectory
);
830 _CFBundleReadDirectory(path
, subdirectory
, allFiles
, hasFileAdded
, queryTable
, typeDir
, addedTypes
, false, product
, platform
, lprojTarget
, true);
831 CFStringDelete(path
, CFRangeMake(basePathLen
, CFStringGetLength(path
) - basePathLen
)); // Strip the string back to the base path
833 if (!hasFileAdded
&& numOfAllFiles
< CFArrayGetCount(allFiles
)) {
839 CFRelease(addedTypes
);
842 // put the array of all files in sub dir to the query table
843 if (CFArrayGetCount(allFiles
) > 0) {
844 CFDictionarySetValue(queryTable
, _CFBundleAllFiles
, allFiles
);
855 // caller need to release the table
856 static CFDictionaryRef
_CFBundleCopyQueryTable(CFBundleRef bundle
, CFURLRef bundleURL
, CFArrayRef languages
, CFStringRef resourcesDirectory
, CFStringRef subdirectory
)
858 CFDictionaryRef subTable
= NULL
;
860 if (bundle
&& !languages
) {
861 languages
= _CFBundleCopyLanguageSearchListInBundle(bundle
);
862 } else if (languages
) {
867 CFMutableStringRef argDirStr
= NULL
;
869 argDirStr
= CFStringCreateMutableCopy(kCFAllocatorDefault
, 0, resourcesDirectory
);
870 _CFAppendPathComponent2(argDirStr
, subdirectory
);
872 argDirStr
= (CFMutableStringRef
)CFRetain(resourcesDirectory
);
875 __CFLock(&bundle
->_queryLock
);
877 // check if the query table for the given sub dir has been created
878 subTable
= (CFDictionaryRef
) CFDictionaryGetValue(bundle
->_queryTable
, argDirStr
);
881 // create the query table for the given sub dir
882 subTable
= _CFBundleCreateQueryTableAtPath(bundle
->_bundleBasePath
, languages
, resourcesDirectory
, subdirectory
);
884 CFDictionarySetValue(bundle
->_queryTable
, argDirStr
, subTable
);
888 __CFUnlock(&bundle
->_queryLock
);
889 CFRelease(argDirStr
);
891 CFURLRef url
= CFURLCopyAbsoluteURL(bundleURL
);
892 CFStringRef bundlePath
= CFURLCopyFileSystemPath(url
, PLATFORM_PATH_STYLE
);
894 subTable
= _CFBundleCreateQueryTableAtPath(bundlePath
, languages
, resourcesDirectory
, subdirectory
);
895 CFRelease(bundlePath
);
898 if (languages
) CFRelease(languages
);
902 static CFURLRef
_CFBundleCreateRelativeURLFromBaseAndPath(CFStringRef path
, CFURLRef base
, UniChar slash
, CFStringRef slashStr
)
906 Boolean needToRelease
= false;
907 if (CFStringFindWithOptions(path
, slashStr
, CFRangeMake(0, CFStringGetLength(path
)-1), kCFCompareBackwards
, &resultRange
)) {
908 CFStringRef subPathCom
= CFStringCreateWithSubstring(kCFAllocatorSystemDefault
, path
, CFRangeMake(0, resultRange
.location
));
909 base
= CFURLCreateCopyAppendingPathComponent(kCFAllocatorSystemDefault
, base
, subPathCom
, YES
);
910 path
= CFStringCreateWithSubstring(kCFAllocatorSystemDefault
, path
, CFRangeMake(resultRange
.location
+1, CFStringGetLength(path
)-resultRange
.location
-1));
911 CFRelease(subPathCom
);
912 needToRelease
= true;
914 if (CFStringGetCharacterAtIndex(path
, CFStringGetLength(path
)-1) == slash
) {
915 url
= (CFURLRef
)CFURLCreateWithFileSystemPathRelativeToBase(kCFAllocatorSystemDefault
, path
, PLATFORM_PATH_STYLE
, true, base
);
917 url
= (CFURLRef
)CFURLCreateWithFileSystemPathRelativeToBase(kCFAllocatorSystemDefault
, path
, PLATFORM_PATH_STYLE
, false, base
);
926 static void _CFBundleFindResourcesWithPredicate(CFMutableArrayRef interResult
, CFDictionaryRef queryTable
, Boolean (^predicate
)(CFStringRef filename
, Boolean
*stop
), Boolean
*stop
)
928 CFIndex dictSize
= CFDictionaryGetCount(queryTable
);
932 CFTypeRef
*keys
= (CFTypeRef
*)malloc(sizeof(CFTypeRef
) * dictSize
);
933 CFTypeRef
*values
= (CFTypeRef
*)malloc(sizeof(CFTypeRef
) * dictSize
);
934 if (!keys
|| !values
) return;
936 CFDictionaryGetKeysAndValues(queryTable
, keys
, values
);
937 for (CFIndex i
= 0; i
< dictSize
; i
++) {
938 if (predicate((CFStringRef
)keys
[i
], stop
)) {
939 if (CFGetTypeID(values
[i
]) == CFStringGetTypeID()) {
940 CFArrayAppendValue(interResult
, values
[i
]);
942 CFArrayAppendArray(interResult
, (CFArrayRef
)values
[i
], CFRangeMake(0, CFArrayGetCount((CFArrayRef
)values
[i
])));
953 static CFTypeRef
_CFBundleCopyURLsOfKey(CFBundleRef bundle
, CFURLRef bundleURL
, CFArrayRef bundleURLLanguages
, CFStringRef resourcesDirectory
, CFStringRef subDir
, CFStringRef key
, CFStringRef lproj
, Boolean returnArray
, Boolean localized
, uint8_t bundleVersion
, Boolean (^predicate
)(CFStringRef filename
, Boolean
*stop
))
955 CFTypeRef value
= NULL
;
956 Boolean stop
= false; // for predicate
957 CFMutableArrayRef interResult
= CFArrayCreateMutable(kCFAllocatorSystemDefault
, 0, &kCFTypeArrayCallBacks
);
958 CFDictionaryRef subTable
= NULL
;
960 CFMutableStringRef path
= CFStringCreateMutableCopy(kCFAllocatorDefault
, 0, resourcesDirectory
);
961 if (1 == bundleVersion
) {
962 CFIndex savedPathLength
= CFStringGetLength(path
);
963 // add the non-localized resource dir
964 _CFAppendPathComponent2(path
, _CFBundleNonLocalizedResourcesDirectoryName
);
965 subTable
= _CFBundleCopyQueryTable(bundle
, bundleURL
, bundleURLLanguages
, path
, subDir
);
967 _CFBundleFindResourcesWithPredicate(interResult
, subTable
, predicate
, &stop
);
969 value
= CFDictionaryGetValue(subTable
, key
);
971 CFStringDelete(path
, CFRangeMake(savedPathLength
, CFStringGetLength(path
) - savedPathLength
)); // Strip the string back to the base path
974 if (!value
&& !stop
) {
975 if (subTable
) CFRelease(subTable
);
976 subTable
= _CFBundleCopyQueryTable(bundle
, bundleURL
, bundleURLLanguages
, path
, subDir
);
978 _CFBundleFindResourcesWithPredicate(interResult
, subTable
, predicate
, &stop
);
980 // get the path or paths for the given key
981 value
= CFDictionaryGetValue(subTable
, key
);
985 // if localization is needed, we filter out the paths for the localization and put the valid ones in the interResult
986 Boolean checkLP
= true;
987 CFIndex lpLen
= lproj
? CFStringGetLength(lproj
) : 0;
988 if (localized
&& value
) {
990 if (CFGetTypeID(value
) == CFStringGetTypeID()){
991 // 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.
992 value
= CFArrayCreate(kCFAllocatorSystemDefault
, (const void **)&value
, 1, &kCFTypeArrayCallBacks
);
997 CFRange resultRange
, searchRange
;
998 CFIndex pathValueLen
;
999 CFIndex limit
= returnArray
? CFArrayGetCount((CFArrayRef
)value
) : 1;
1000 searchRange
.location
= 0;
1001 for (CFIndex i
= 0; i
< limit
; i
++) {
1002 CFStringRef pathValue
= (CFStringRef
) CFArrayGetValueAtIndex((CFArrayRef
)value
, i
);
1003 pathValueLen
= CFStringGetLength(pathValue
);
1004 searchRange
.length
= pathValueLen
;
1006 // if we have subdir, we find the subdir and see if it is after the base path (bundle path + res dir)
1007 Boolean searchForLocalization
= false;
1008 if (subDir
&& CFStringGetLength(subDir
) > 0) {
1009 if (CFStringFindWithOptions(pathValue
, subDir
, searchRange
, kCFCompareEqualTo
, &resultRange
) && resultRange
.location
!= searchRange
.location
) {
1010 searchForLocalization
= true;
1012 } else if (!(subDir
&& CFStringGetLength(subDir
) > 0) && searchRange
.length
!= 0) {
1013 if (CFStringFindWithOptions(pathValue
, _CFBundleLprojExtensionWithDot
, searchRange
, kCFCompareEqualTo
, &resultRange
) && resultRange
.location
+ 7 < pathValueLen
) {
1014 searchForLocalization
= true;
1018 if (searchForLocalization
) {
1019 if (!lpLen
|| !(CFStringFindWithOptions(pathValue
, lproj
, searchRange
, kCFCompareEqualTo
| kCFCompareAnchored
, &resultRange
) && CFStringFindWithOptions(pathValue
, CFSTR("."), CFRangeMake(resultRange
.location
+ resultRange
.length
, 1), kCFCompareEqualTo
, &resultRange
))) {
1025 CFArrayAppendValue(interResult
, pathValue
);
1030 if (!returnArray
&& CFArrayGetCount(interResult
) != 0) {
1034 if (CFGetTypeID(value
) == CFArrayGetTypeID()) {
1035 CFArrayAppendArray(interResult
, (CFArrayRef
)value
, CFRangeMake(0, CFArrayGetCount((CFArrayRef
)value
)));
1037 CFArrayAppendValue(interResult
, value
);
1042 CFRelease(subTable
);
1044 // we fetch the result for a given lproj and join them with the nonlocalized result fetched above
1045 if (lpLen
&& checkLP
) {
1046 CFMutableStringRef lprojSubdirName
= CFStringCreateMutableCopy(kCFAllocatorDefault
, 0, lproj
);
1047 _CFAppendPathExtension2(lprojSubdirName
, _CFBundleLprojExtension
);
1048 if (subDir
&& CFStringGetLength(subDir
) > 0) {
1049 _CFAppendPathComponent2(lprojSubdirName
, subDir
);
1051 subTable
= _CFBundleCopyQueryTable(bundle
, bundleURL
, bundleURLLanguages
, path
, lprojSubdirName
);
1052 CFRelease(lprojSubdirName
);
1053 value
= CFDictionaryGetValue(subTable
, key
);
1056 if (CFGetTypeID(value
) == CFStringGetTypeID()) {
1057 CFArrayAppendValue(interResult
, value
);
1059 CFArrayAppendArray(interResult
, (CFArrayRef
)value
, CFRangeMake(0, CFArrayGetCount((CFArrayRef
)value
)));
1063 CFRelease(subTable
);
1066 // after getting paths, we create urls from the paths
1067 CFTypeRef result
= NULL
;
1068 if (CFArrayGetCount(interResult
) > 0) {
1069 UniChar slash
= _CFGetSlash();
1070 CFMutableStringRef urlStr
= NULL
;
1072 urlStr
= CFStringCreateMutableCopy(kCFAllocatorSystemDefault
, 0, bundle
->_bundleBasePath
);
1074 CFURLRef url
= CFURLCopyAbsoluteURL(bundleURL
);
1075 CFStringRef bundlePath
= CFURLCopyFileSystemPath(url
, PLATFORM_PATH_STYLE
);
1076 urlStr
= CFStringCreateMutableCopy(kCFAllocatorSystemDefault
, 0, bundlePath
);
1078 CFRelease(bundlePath
);
1081 if (resourcesDirectory
&& CFStringGetLength(resourcesDirectory
)) {
1082 _CFAppendPathComponent2(urlStr
, resourcesDirectory
);
1085 _CFAppendTrailingPathSlash2(urlStr
);
1088 Boolean isOnlyTypeOrAllFiles
= CFStringHasPrefix(key
, _CFBundleTypeIndicator
);
1089 isOnlyTypeOrAllFiles
|= CFStringHasPrefix(key
, _CFBundleAllFiles
);
1091 CFStringRef resultPath
= (CFStringRef
)CFArrayGetValueAtIndex((CFArrayRef
)interResult
, 0);
1092 if (!isOnlyTypeOrAllFiles
) {
1093 // path is a part of an actual path in the query table, so it should not have a length greater than the buffer size
1094 CFStringAppend(urlStr
, resultPath
);
1095 if (CFStringGetCharacterAtIndex(resultPath
, CFStringGetLength(resultPath
)-1) == slash
) {
1096 result
= (CFURLRef
)CFURLCreateWithFileSystemPath(kCFAllocatorSystemDefault
, urlStr
, PLATFORM_PATH_STYLE
, true);
1098 result
= (CFURLRef
)CFURLCreateWithFileSystemPath(kCFAllocatorSystemDefault
, urlStr
, PLATFORM_PATH_STYLE
, false);
1101 // need to create relative URLs for binary compatibility issues
1102 CFURLRef base
= CFURLCreateWithFileSystemPath(kCFAllocatorSystemDefault
, urlStr
, PLATFORM_PATH_STYLE
, true);
1103 result
= (CFURLRef
)_CFBundleCreateRelativeURLFromBaseAndPath(resultPath
, base
, slash
, _CFGetSlashStr());
1107 // need to create relative URLs for binary compatibility issues
1108 CFIndex numOfPaths
= CFArrayGetCount((CFArrayRef
)interResult
);
1109 CFURLRef base
= CFURLCreateWithFileSystemPath(kCFAllocatorSystemDefault
, urlStr
, PLATFORM_PATH_STYLE
, true);
1110 CFMutableArrayRef urls
= CFArrayCreateMutable(kCFAllocatorSystemDefault
, 0, &kCFTypeArrayCallBacks
);
1111 for (CFIndex i
= 0; i
< numOfPaths
; i
++) {
1112 CFStringRef path
= (CFStringRef
)CFArrayGetValueAtIndex((CFArrayRef
)interResult
, i
);
1113 CFURLRef url
= _CFBundleCreateRelativeURLFromBaseAndPath(path
, base
, slash
, _CFGetSlashStr());
1114 CFArrayAppendValue(urls
, url
);
1121 } else if (returnArray
) {
1122 result
= CFRetain(interResult
);
1124 if (path
) CFRelease(path
);
1125 CFRelease(interResult
);
1132 // This is the main entry point for all resource lookup.
1133 // 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.
1134 // 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.
1135 // Another idea: if you want caching, you should create a bundle object. Otherwise we'll happily readdir each time.
1136 CF_EXPORT CFTypeRef
_CFBundleCopyFindResources(CFBundleRef bundle
, CFURLRef bundleURL
, CFArrayRef _unused_pass_null_
, CFStringRef resourceName
, CFStringRef resourceType
, CFStringRef subPath
, CFStringRef lproj
, Boolean returnArray
, Boolean localized
, Boolean (^predicate
)(CFStringRef filename
, Boolean
*stop
))
1139 // Don't use any path info passed into the resource name
1140 CFStringRef realResourceName
= NULL
;
1141 CFStringRef subPathFromResourceName
= NULL
;
1144 CFIndex slashLocation
= -1;
1145 realResourceName
= _CFCreateLastPathComponent(kCFAllocatorSystemDefault
, resourceName
, &slashLocation
);
1146 if (slashLocation
> 0) {
1147 // do not include the /
1148 subPathFromResourceName
= CFStringCreateWithSubstring(kCFAllocatorSystemDefault
, resourceName
, CFRangeMake(0, slashLocation
));
1151 if (slashLocation
> 0 && CFStringGetLength(realResourceName
) == 0 && slashLocation
== CFStringGetLength(resourceName
) - 1) {
1152 // Did we have a name with just a single / at the end? Taking the lastPathComponent will end up with an empty resource name, which is probably not what was expected.
1153 // Reset the name to be just the directory name.
1154 CFRelease(realResourceName
);
1155 realResourceName
= CFStringCreateCopy(kCFAllocatorSystemDefault
, subPathFromResourceName
);
1158 // 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.
1159 // TODO: remove this in some way to avoid the malloc?
1160 char buff
[CFMaxPathSize
];
1161 if (CFStringGetFileSystemRepresentation(realResourceName
, buff
, CFMaxPathSize
)) {
1162 CFRelease(realResourceName
);
1163 realResourceName
= CFStringCreateWithFileSystemRepresentation(kCFAllocatorSystemDefault
, buff
);
1167 CFMutableStringRef key
= NULL
;
1168 const static UniChar extensionSep
= '.';
1170 if (realResourceName
&& CFStringGetLength(realResourceName
) > 0 && resourceType
&& CFStringGetLength(resourceType
) > 0) {
1171 // Testing shows that using a mutable string here is significantly faster than using the format functions.
1172 key
= CFStringCreateMutableCopy(kCFAllocatorSystemDefault
, 0, realResourceName
);
1173 // Don't re-append a . if the resource name already has one
1174 if (CFStringGetCharacterAtIndex(resourceType
, 0) != '.') CFStringAppendCharacters(key
, &extensionSep
, 1);
1175 CFStringAppend(key
, resourceType
);
1176 } else if (realResourceName
&& CFStringGetLength(realResourceName
) > 0) {
1177 key
= (CFMutableStringRef
)CFRetain(realResourceName
);
1178 } else if (resourceType
&& CFStringGetLength(resourceType
) > 0) {
1179 key
= CFStringCreateMutableCopy(kCFAllocatorSystemDefault
, 0, _CFBundleTypeIndicator
);
1180 // Don't re-append a . if the resource name already has one
1181 if (CFStringGetCharacterAtIndex(resourceType
, 0) != '.') CFStringAppendCharacters(key
, &extensionSep
, 1);
1182 CFStringAppend(key
, resourceType
);
1184 key
= (CFMutableStringRef
)CFRetain(_CFBundleAllFiles
);
1187 CFStringRef realSubdirectory
= NULL
;
1189 bool hasSubPath
= subPath
&& CFStringGetLength(subPath
);
1190 bool hasSubPathFromResourceName
= subPathFromResourceName
&& CFStringGetLength(subPathFromResourceName
);
1192 if (hasSubPath
&& !hasSubPathFromResourceName
) {
1193 realSubdirectory
= (CFStringRef
)CFRetain(subPath
);
1194 } else if (!hasSubPath
&& hasSubPathFromResourceName
) {
1195 realSubdirectory
= (CFStringRef
)CFRetain(subPathFromResourceName
);
1196 } else if (hasSubPath
&& hasSubPathFromResourceName
) {
1197 // Multiple sub paths - we'll have to concatenate
1198 realSubdirectory
= CFStringCreateMutableCopy(kCFAllocatorSystemDefault
, 0, subPath
);
1199 _CFAppendPathComponent2((CFMutableStringRef
)realSubdirectory
, subPathFromResourceName
);
1202 uint8_t bundleVersion
= bundle
? _CFBundleLayoutVersion(bundle
) : 0;
1203 CFArrayRef bundleURLLanguages
= NULL
;
1205 bundleURLLanguages
= _CFBundleCopyLanguageSearchListInDirectory(bundleURL
, &bundleVersion
);
1208 CFStringRef resDir
= _CFBundleGetResourceDirForVersion(bundleVersion
);
1210 CFTypeRef returnValue
= _CFBundleCopyURLsOfKey(bundle
, bundleURL
, bundleURLLanguages
, resDir
, realSubdirectory
, key
, lproj
, returnArray
, localized
, bundleVersion
, predicate
);
1212 if ((!returnValue
|| (CFGetTypeID(returnValue
) == CFArrayGetTypeID() && CFArrayGetCount((CFArrayRef
)returnValue
) == 0)) && (0 == bundleVersion
|| 2 == bundleVersion
)) {
1213 CFStringRef bundlePath
= NULL
;
1215 bundlePath
= bundle
->_bundleBasePath
;
1216 CFRetain(bundlePath
);
1218 CFURLRef absoluteURL
= CFURLCopyAbsoluteURL(bundleURL
);
1219 bundlePath
= CFURLCopyFileSystemPath(absoluteURL
, PLATFORM_PATH_STYLE
);
1220 CFRelease(absoluteURL
);
1222 if ((0 == bundleVersion
) || CFEqual(CFSTR("/Library/Spotlight"), bundlePath
)){
1223 if (returnValue
) CFRelease(returnValue
);
1224 if ((bundleVersion
== 0 && realSubdirectory
&& CFEqual(realSubdirectory
, CFSTR("Resources"))) || (bundleVersion
== 2 && realSubdirectory
&& CFEqual(realSubdirectory
, CFSTR("Contents/Resources")))) {
1225 if (realSubdirectory
) CFRelease(realSubdirectory
);
1226 realSubdirectory
= CFSTR("");
1227 } else if (bundleVersion
== 0 && realSubdirectory
&& CFStringGetLength(realSubdirectory
) > 10 && CFStringHasPrefix(realSubdirectory
, CFSTR("Resources/"))) {
1228 CFStringRef tmpRealSubdirectory
= CFStringCreateWithSubstring(kCFAllocatorSystemDefault
, realSubdirectory
, CFRangeMake(10, CFStringGetLength(realSubdirectory
) - 10));
1229 if (realSubdirectory
) CFRelease(realSubdirectory
);
1230 realSubdirectory
= tmpRealSubdirectory
;
1231 } else if (bundleVersion
== 2 && realSubdirectory
&& CFStringGetLength(realSubdirectory
) > 19 && CFStringHasPrefix(realSubdirectory
, CFSTR("Contents/Resources/"))) {
1232 CFStringRef tmpRealSubdirectory
= CFStringCreateWithSubstring(kCFAllocatorSystemDefault
, realSubdirectory
, CFRangeMake(19, CFStringGetLength(realSubdirectory
) - 19));
1233 if (realSubdirectory
) CFRelease(realSubdirectory
);
1234 realSubdirectory
= tmpRealSubdirectory
;
1236 // Assume no resources directory
1239 returnValue
= _CFBundleCopyURLsOfKey(bundle
, bundleURL
, bundleURLLanguages
, resDir
, realSubdirectory
, key
, lproj
, returnArray
, localized
, bundleVersion
, predicate
);
1241 CFRelease(bundlePath
);
1244 if (realResourceName
) CFRelease(realResourceName
);
1245 if (realSubdirectory
) CFRelease(realSubdirectory
);
1246 if (subPathFromResourceName
) CFRelease(subPathFromResourceName
);
1247 if (bundleURLLanguages
) CFRelease(bundleURLLanguages
);