]> git.saurik.com Git - apple/cf.git/blob - CFBundle_Resources.c
CF-1152.14.tar.gz
[apple/cf.git] / CFBundle_Resources.c
1 /*
2 * Copyright (c) 2015 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-2014, 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
47 #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI || DEPLOYMENT_TARGET_LINUX
48 #include <unistd.h>
49 #include <sys/sysctl.h>
50 #include <sys/stat.h>
51 #include <dirent.h>
52 #endif
53
54 #if DEPLOYMENT_TARGET_WINDOWS
55 #include <io.h>
56 #include <fcntl.h>
57 #include <sys/stat.h>
58 #include <errno.h>
59
60 #define close _close
61 #define write _write
62 #define read _read
63 #define open _NS_open
64 #define stat _NS_stat
65 #define fstat _fstat
66 #define mkdir(a,b) _NS_mkdir(a)
67 #define rmdir _NS_rmdir
68 #define unlink _NS_unlink
69
70 #endif
71
72 #pragma mark -
73 #pragma mark Directory Contents and Caches
74
75 // These are here for compatibility, but they do nothing anymore
76 CF_EXPORT void _CFBundleFlushCachesForURL(CFURLRef url) { }
77 CF_EXPORT void _CFBundleFlushCaches(void) { }
78
79 CF_PRIVATE void _CFBundleFlushQueryTableCache(CFBundleRef bundle) {
80 __CFLock(&bundle->_queryLock);
81 if (bundle->_queryTable) {
82 CFDictionaryRemoveAllValues(bundle->_queryTable);
83 }
84 __CFUnlock(&bundle->_queryLock);
85 }
86
87 #pragma mark -
88 #pragma mark Resource URL Lookup
89
90 static Boolean _CFIsResourceCommon(char *path, Boolean *isDir) {
91 Boolean exists;
92 SInt32 mode;
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));
96 }
97 return false;
98 }
99
100 CF_PRIVATE Boolean _CFIsResourceAtURL(CFURLRef url, Boolean *isDir) {
101 char path[CFMaxPathSize];
102 if (!CFURLGetFileSystemRepresentation(url, true, (uint8_t *)path, CFMaxPathLength)) return false;
103
104 return _CFIsResourceCommon(path, isDir);
105 }
106
107 CF_PRIVATE Boolean _CFIsResourceAtPath(CFStringRef path, Boolean *isDir) {
108 char pathBuf[CFMaxPathSize];
109 if (!CFStringGetFileSystemRepresentation(path, pathBuf, CFMaxPathSize)) return false;
110
111 return _CFIsResourceCommon(pathBuf, isDir);
112 }
113
114
115 static CFStringRef _CFBundleGetResourceDirForVersion(uint8_t version) {
116 if (1 == version) {
117 return _CFBundleSupportFilesDirectoryName1WithResources;
118 } else if (2 == version) {
119 return _CFBundleSupportFilesDirectoryName2WithResources;
120 } else if (0 == version) {
121 return _CFBundleResourcesDirectoryName;
122 }
123 return CFSTR("");
124 }
125
126 CF_PRIVATE void _CFBundleAppendResourceDir(CFMutableStringRef path, uint8_t version) {
127 if (1 == 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);
135 }
136 if (0 == version || 1 == version || 2 == version) {
137 // /path/to/bundle/<above>/Resources
138 CFStringAppend(path, _CFBundleResourcesDirectoryName);
139 }
140 }
141
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);
145 return result;
146 }
147
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);
151 return result;
152 }
153
154 CF_EXPORT CFURLRef _CFBundleCopyResourceURLForLanguage(CFBundleRef bundle, CFStringRef resourceName, CFStringRef resourceType, CFStringRef subDirName, CFStringRef language) {
155 return CFBundleCopyResourceURLForLocalization(bundle, resourceName, resourceType, subDirName, language);
156 }
157
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);
161 return result;
162 }
163
164 CF_EXPORT CFArrayRef _CFBundleCopyResourceURLsOfTypeForLanguage(CFBundleRef bundle, CFStringRef resourceType, CFStringRef subDirName, CFStringRef language) {
165 return CFBundleCopyResourceURLsOfTypeForLocalization(bundle, resourceType, subDirName, language);
166 }
167
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);
171 return result;
172 }
173
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;
178
179 if (!CFURLGetFileSystemRepresentation(bundleURL, true, buff, CFMaxPathSize)) return NULL;
180
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);
185 }
186 if (newURL) CFRelease(newURL);
187 return result;
188 }
189
190 CF_EXPORT CFArrayRef CFBundleCopyResourceURLsOfTypeInDirectory(CFURLRef bundleURL, CFStringRef resourceType, CFStringRef subDirName) {
191 CFArrayRef array = NULL;
192 unsigned char buff[CFMaxPathSize];
193 CFURLRef newURL = NULL;
194
195 if (!CFURLGetFileSystemRepresentation(bundleURL, true, buff, CFMaxPathSize)) return NULL;
196
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);
201 }
202 if (newURL) CFRelease(newURL);
203 return array;
204 }
205
206 #pragma mark -
207
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);
213 if (dirURL) {
214 if (_CFIsResourceAtURL(dirURL, &isDir) && isDir) result = true;
215 CFRelease(dirURL);
216 }
217 return result;
218 }
219 #endif
220
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)
229
230 CFURLRef absoluteURL = CFURLCopyAbsoluteURL(url);
231 CFStringRef directoryPath = CFURLCopyFileSystemPath(absoluteURL, PLATFORM_PATH_STYLE);
232 CFRelease(absoluteURL);
233
234 Boolean hasFrameworkSuffix = CFStringHasSuffix(CFURLGetString(url), CFSTR(".framework/"));
235 #if DEPLOYMENT_TARGET_WINDOWS
236 hasFrameworkSuffix = hasFrameworkSuffix || CFStringHasSuffix(CFURLGetString(url), CFSTR(".framework\\"));
237 #endif
238
239 /*
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")
245 */
246 __block uint8_t localVersion = 3;
247 CFIndex resourcesDirectoryLength = CFStringGetLength(_CFBundleResourcesDirectoryName);
248 CFIndex contentsDirectoryLength = CFStringGetLength(_CFBundleSupportFilesDirectoryName2);
249 CFIndex supportFilesDirectoryLength = CFStringGetLength(_CFBundleSupportFilesDirectoryName1);
250
251 __block Boolean foundResources = false;
252 __block Boolean foundSupportFiles2 = false;
253 __block Boolean foundSupportFiles1 = false;
254
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;
266 }
267 }
268 return true;
269 });
270
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) {
274 localVersion = 0;
275 } else if (foundSupportFiles2) {
276 localVersion = 2;
277 } else if (foundSupportFiles1) {
278 localVersion = 1;
279 }
280 } else {
281 if (foundSupportFiles2) {
282 localVersion = 2;
283 } else if (foundResources) {
284 localVersion = 0;
285 } else if (foundSupportFiles1) {
286 localVersion = 1;
287 }
288 }
289
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;
297 } else {
298 if (_CFBundleURLHasSubDir(url, _CFBundleSupportFilesURLFromBase2)) localVersion = 2;
299 else if (_CFBundleURLHasSubDir(url, _CFBundleResourcesURLFromBase0)) localVersion = 0;
300 else if (_CFBundleURLHasSubDir(url, _CFBundleSupportFilesURLFromBase1)) localVersion = 1;
301 }
302 }
303 #endif
304
305 CFRelease(directoryPath);
306 return localVersion;
307 }
308
309 #pragma mark -
310 #pragma mark Platforms
311
312 CF_EXPORT CFArrayRef _CFBundleGetSupportedPlatforms(CFBundleRef bundle) {
313 // This function is obsolete
314 return NULL;
315 }
316
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");
332 #else
333 #error Unknown or unspecified DEPLOYMENT_TARGET
334 #endif
335 }
336
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");
350 #else
351 #error Unknown or unspecified DEPLOYMENT_TARGET
352 #endif
353 }
354
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");
368 #else
369 #error Unknown or unspecified DEPLOYMENT_TARGET
370 #endif
371 }
372
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");
386 #else
387 #error Unknown or unspecified DEPLOYMENT_TARGET
388 #endif
389 }
390
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");
404 #else
405 #error Unknown or unspecified DEPLOYMENT_TARGET
406 #endif
407 }
408
409 CFArrayRef CFBundleCopyExecutableArchitecturesForURL(CFURLRef url) {
410 CFArrayRef result = NULL;
411 CFBundleRef bundle = CFBundleCreate(kCFAllocatorSystemDefault, url);
412 if (bundle) {
413 result = CFBundleCopyExecutableArchitectures(bundle);
414 CFRelease(bundle);
415 } else {
416 result = _CFBundleCopyArchitecturesForExecutable(url);
417 }
418 return result;
419 }
420
421 #pragma mark -
422 #pragma mark Resource Lookup - Query Table
423
424 static void _CFBundleAddValueForType(CFStringRef type, CFMutableDictionaryRef queryTable, CFMutableDictionaryRef typeDir, CFTypeRef value, CFMutableDictionaryRef addedTypes, Boolean firstLproj) {
425 CFMutableArrayRef tFiles = (CFMutableArrayRef) CFDictionaryGetValue(typeDir, type);
426 if (!tFiles) {
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);
431 CFRelease(tFiles);
432 CFRelease(key);
433 }
434 if (!addedTypes) {
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);
441 }
442 }
443
444 typedef enum {
445 _CFBundleFileVersionNoProductNoPlatform = 1,
446 _CFBundleFileVersionWithProductNoPlatform,
447 _CFBundleFileVersionNoProductWithPlatform,
448 _CFBundleFileVersionWithProductWithPlatform,
449 _CFBundleFileVersionUnmatched
450 } _CFBundleFileVersion;
451
452 static _CFBundleFileVersion _CFBundleCheckFileProductAndPlatform(CFStringRef file, CFRange searchRange, CFStringRef product, CFStringRef platform)
453 {
454 _CFBundleFileVersion version;
455 Boolean foundprod, foundplat;
456 foundplat = foundprod = NO;
457 Boolean wrong = false;
458
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)) {
463 foundprod = YES;
464 }
465 }
466 if (!foundprod) {
467 wrong = _CFBundleSupportedProductName(file, searchRange);
468 }
469 }
470
471 if (!wrong && CFStringFindWithOptions(file, CFSTR("-"), searchRange, 0, NULL)) {
472 if (CFStringFindWithOptions(file, platform, searchRange, kCFCompareEqualTo, NULL)) {
473 foundplat = YES;
474 }
475 if (!foundplat) {
476 wrong = _CFBundleSupportedPlatformName(file, searchRange);
477 }
478 }
479
480 if (wrong) {
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;
488 } else {
489 version = _CFBundleFileVersionNoProductNoPlatform;
490 }
491 return version;
492 }
493
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;
501
502 CFIndex dotLocation = fileNameLen;
503 for (CFIndex i = fileNameLen - 1; i > 0; i--) {
504 UniChar c = CFStringGetCharacterAtIndex(fileName, i);
505 if (c == '.') {
506 dotLocation = i;
507 }
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;
515 }
516 #endif
517 else if (c == '-') {
518 if (foundProduct) {
519 platformRange = CFRangeMake(i, productRange.location - i);
520 } else {
521 platformRange = CFRangeMake(i, dotLocation - i);
522 }
523 foundPlatform = (CFStringCompareWithOptions(fileName, expectedPlatform, platformRange, kCFCompareAnchored) == kCFCompareEqualTo);
524 if (foundPlatform && outPlatformRange) *outPlatformRange = platformRange;
525 break;
526 }
527 }
528
529 _CFBundleFileVersion version;
530 if (foundPlatform && foundProduct) {
531 version = _CFBundleFileVersionWithProductWithPlatform;
532 } else if (foundPlatform) {
533 version = _CFBundleFileVersionNoProductWithPlatform;
534 } else if (foundProduct) {
535 version = _CFBundleFileVersionWithProductNoPlatform;
536 } else {
537 version = _CFBundleFileVersionNoProductNoPlatform;
538 }
539 return version;
540 }
541
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);
545
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) == '.') {
553 foundDot = true;
554 dotLocation = i - 1;
555 break;
556 }
557 }
558
559 if (foundDot && dotLocation != fileNameLen - 1) {
560 if (endType) *endType = CFStringCreateWithSubstring(kCFAllocatorSystemDefault, fileName, CFRangeMake(dotLocation + 1, CFStringGetLength(fileName) - dotLocation - 1));
561 }
562
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'
565 if (startType) {
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));
571 }
572 break;
573 }
574 }
575 }
576 }
577
578 CFRange productRange, platformRange;
579 *version = _CFBundleVersionForFileName(fileName, expectedProduct, expectedPlatform, &productRange, &platformRange);
580
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;
588
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.
590 if (foundPlatform) {
591 start = platformRange.location;
592 } else {
593 start = productRange.location;
594 }
595
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;
602 }
603 CFStringDelete(fileNameScratch, CFRangeMake(start, length));
604 *noProductOrPlatform = (CFStringRef)fileNameScratch;
605 }
606 }
607
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) {
609
610 Boolean result = true;
611 CFMutableStringRef pathPrefix = NULL;
612 if (lprojName) {
613 pathPrefix = CFStringCreateMutableCopy(kCFAllocatorSystemDefault, 0, lprojName);
614 if (appendLprojCharacters) _CFAppendPathExtension2(pathPrefix, _CFBundleLprojExtension);
615 _CFAppendTrailingPathSlash2(pathPrefix);
616 }
617 if (subdirectory) {
618 if (pathPrefix) {
619 CFStringAppend(pathPrefix, subdirectory);
620 } else {
621 pathPrefix = CFStringCreateMutableCopy(kCFAllocatorSystemDefault, 0, subdirectory);
622 }
623 UniChar lastChar = CFStringGetCharacterAtIndex(subdirectory, CFStringGetLength(subdirectory)-1);
624 if (lastChar != _CFGetSlash()) {
625 _CFAppendTrailingPathSlash2(pathPrefix);
626 }
627 }
628
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);
633
634 CFStringRef pathToFile;
635 if (pathPrefix && CFStringGetLength(pathPrefix) > 0) {
636 CFMutableStringRef tmp = CFStringCreateMutableCopy(kCFAllocatorSystemDefault, 0, pathPrefix);
637 CFStringAppend(tmp, fileName);
638 pathToFile = (CFStringRef)tmp;
639 } else {
640 pathToFile = (CFStringRef)CFRetain(fileName);
641 }
642
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) {
646 appendSlash = true;
647 }
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];
652 struct stat statBuf;
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);
660 }
661 if (isDir) {
662 appendSlash = true;
663 }
664 }
665 }
666 }
667 #endif
668 if (appendSlash) {
669 // This is fairly inefficient
670 CFMutableStringRef tmp = CFStringCreateMutableCopy(kCFAllocatorSystemDefault, 0, pathToFile);
671 _CFAppendTrailingPathSlash2(tmp);
672 CFRelease(pathToFile);
673 pathToFile = (CFStringRef)tmp;
674 }
675
676 // put it into all file array
677 if (!hasFileAdded) {
678 CFArrayAppendValue(allFiles, pathToFile);
679 }
680
681 if (startType) {
682 _CFBundleAddValueForType(startType, queryTable, typeDir, pathToFile, addedTypes, firstLproj);
683 }
684
685 if (endType) {
686 _CFBundleAddValueForType(endType, queryTable, typeDir, pathToFile, addedTypes, firstLproj);
687 }
688
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);
692 if (!prevPath) {
693 CFDictionarySetValue(queryTable, fileName, pathToFile);
694 }
695 } else {
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);
698 if (!prevPath) {
699 CFDictionarySetValue(queryTable, fileName, pathToFile);
700 }
701
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);
706 if (!prevPath) {
707 CFDictionarySetValue(queryTable, noProductOrPlatform, pathToFile);
708 } else {
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
711 CFRange searchRange;
712 if (lprojName) {
713 searchRange.location = CFStringGetLength(lprojName);
714 searchRange.length = CFStringGetLength(prevPath) - searchRange.location;
715 } else {
716 searchRange.location = 0;
717 searchRange.length = CFStringGetLength(prevPath);
718 }
719 _CFBundleFileVersion prevFileVersion = _CFBundleCheckFileProductAndPlatform(prevPath, searchRange, product, platform);
720 switch (prevFileVersion) {
721 case _CFBundleFileVersionNoProductNoPlatform:
722 CFDictionarySetValue(queryTable, noProductOrPlatform, pathToFile);
723 break;
724 case _CFBundleFileVersionWithProductNoPlatform:
725 if (fileVersion == _CFBundleFileVersionWithProductWithPlatform) CFDictionarySetValue(queryTable, noProductOrPlatform, pathToFile);
726 break;
727 case _CFBundleFileVersionNoProductWithPlatform:
728 CFDictionarySetValue(queryTable, noProductOrPlatform, pathToFile);
729 break;
730 default:
731 break;
732 }
733 }
734 }
735 }
736 }
737
738 if (pathToFile) CFRelease(pathToFile);
739 if (startType) CFRelease(startType);
740 if (endType) CFRelease(endType);
741 if (noProductOrPlatform) CFRelease(noProductOrPlatform);
742
743 return true;
744 });
745
746 if (pathPrefix) CFRelease(pathPrefix);
747 return result;
748 }
749
750
751 static CFDictionaryRef _CFBundleCreateQueryTableAtPath(CFStringRef inPath, CFArrayRef languages, CFStringRef resourcesDirectory, CFStringRef subdirectory)
752 {
753
754 CFMutableDictionaryRef queryTable = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
755 CFMutableArrayRef allFiles = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
756 CFMutableDictionaryRef typeDir = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
757
758 CFStringRef productName = _CFGetProductName();//CFSTR("iphone");
759 CFStringRef platformName = _CFGetPlatformName();//CFSTR("iphoneos");
760 if (CFEqual(productName, CFSTR("ipod"))) {
761 productName = CFSTR("iphone");
762 }
763 CFStringRef product = CFStringCreateWithFormat(kCFAllocatorSystemDefault, NULL, CFSTR("~%@"), productName);
764 CFStringRef platform = CFStringCreateWithFormat(kCFAllocatorSystemDefault, NULL, CFSTR("-%@"), platformName);
765
766 CFMutableStringRef path = CFStringCreateMutableCopy(kCFAllocatorSystemDefault, 0, inPath);
767
768 if (resourcesDirectory) {
769 _CFAppendPathComponent2(path, resourcesDirectory);
770 }
771
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);
774
775 if (subdirectory) {
776 _CFAppendPathComponent2(path, subdirectory);
777 }
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
781
782 CFIndex numOfAllFiles = CFArrayGetCount(allFiles);
783
784 CFIndex numLprojs = languages ? CFArrayGetCount(languages) : 0;
785 CFMutableDictionaryRef addedTypes = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
786
787 Boolean hasFileAdded = false;
788 Boolean firstLproj = true;
789
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);
795 if (subdirectory) {
796 _CFAppendPathComponent2(path, subdirectory);
797 }
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
800
801 if (!hasFileAdded && numOfAllFiles < CFArrayGetCount(allFiles)) {
802 hasFileAdded = true;
803 }
804 firstLproj = false;
805 }
806
807 // Next, search Base.lproj folder
808 _CFAppendPathComponent2(path, _CFBundleBaseDirectory);
809 _CFAppendPathExtension2(path, _CFBundleLprojExtension);
810 if (subdirectory) {
811 _CFAppendPathComponent2(path, subdirectory);
812 }
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
815
816 if (!hasFileAdded && numOfAllFiles < CFArrayGetCount(allFiles)) {
817 hasFileAdded = true;
818 }
819
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);
827 if (subdirectory) {
828 _CFAppendPathComponent2(path, subdirectory);
829 }
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
832
833 if (!hasFileAdded && numOfAllFiles < CFArrayGetCount(allFiles)) {
834 hasFileAdded = true;
835 }
836 }
837 }
838
839 CFRelease(addedTypes);
840 CFRelease(path);
841
842 // put the array of all files in sub dir to the query table
843 if (CFArrayGetCount(allFiles) > 0) {
844 CFDictionarySetValue(queryTable, _CFBundleAllFiles, allFiles);
845 }
846
847 CFRelease(platform);
848 CFRelease(product);
849 CFRelease(allFiles);
850 CFRelease(typeDir);
851
852 return queryTable;
853 }
854
855 // caller need to release the table
856 static CFDictionaryRef _CFBundleCopyQueryTable(CFBundleRef bundle, CFURLRef bundleURL, CFArrayRef languages, CFStringRef resourcesDirectory, CFStringRef subdirectory)
857 {
858 CFDictionaryRef subTable = NULL;
859
860 if (bundle && !languages) {
861 languages = _CFBundleCopyLanguageSearchListInBundle(bundle);
862 } else if (languages) {
863 CFRetain(languages);
864 }
865
866 if (bundle) {
867 CFMutableStringRef argDirStr = NULL;
868 if (subdirectory) {
869 argDirStr = CFStringCreateMutableCopy(kCFAllocatorDefault, 0, resourcesDirectory);
870 _CFAppendPathComponent2(argDirStr, subdirectory);
871 } else {
872 argDirStr = (CFMutableStringRef)CFRetain(resourcesDirectory);
873 }
874
875 __CFLock(&bundle->_queryLock);
876
877 // check if the query table for the given sub dir has been created
878 subTable = (CFDictionaryRef) CFDictionaryGetValue(bundle->_queryTable, argDirStr);
879
880 if (!subTable) {
881 // create the query table for the given sub dir
882 subTable = _CFBundleCreateQueryTableAtPath(bundle->_bundleBasePath, languages, resourcesDirectory, subdirectory);
883
884 CFDictionarySetValue(bundle->_queryTable, argDirStr, subTable);
885 } else {
886 CFRetain(subTable);
887 }
888 __CFUnlock(&bundle->_queryLock);
889 CFRelease(argDirStr);
890 } else {
891 CFURLRef url = CFURLCopyAbsoluteURL(bundleURL);
892 CFStringRef bundlePath = CFURLCopyFileSystemPath(url, PLATFORM_PATH_STYLE);
893 CFRelease(url);
894 subTable = _CFBundleCreateQueryTableAtPath(bundlePath, languages, resourcesDirectory, subdirectory);
895 CFRelease(bundlePath);
896 }
897
898 if (languages) CFRelease(languages);
899 return subTable;
900 }
901
902 static CFURLRef _CFBundleCreateRelativeURLFromBaseAndPath(CFStringRef path, CFURLRef base, UniChar slash, CFStringRef slashStr)
903 {
904 CFURLRef url = NULL;
905 CFRange resultRange;
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;
913 }
914 if (CFStringGetCharacterAtIndex(path, CFStringGetLength(path)-1) == slash) {
915 url = (CFURLRef)CFURLCreateWithFileSystemPathRelativeToBase(kCFAllocatorSystemDefault, path, PLATFORM_PATH_STYLE, true, base);
916 } else {
917 url = (CFURLRef)CFURLCreateWithFileSystemPathRelativeToBase(kCFAllocatorSystemDefault, path, PLATFORM_PATH_STYLE, false, base);
918 }
919 if (needToRelease) {
920 CFRelease(base);
921 CFRelease(path);
922 }
923 return url;
924 }
925
926 static void _CFBundleFindResourcesWithPredicate(CFMutableArrayRef interResult, CFDictionaryRef queryTable, Boolean (^predicate)(CFStringRef filename, Boolean *stop), Boolean *stop)
927 {
928 CFIndex dictSize = CFDictionaryGetCount(queryTable);
929 if (dictSize == 0) {
930 return;
931 }
932 CFTypeRef *keys = (CFTypeRef *)malloc(sizeof(CFTypeRef) * dictSize);
933 CFTypeRef *values = (CFTypeRef *)malloc(sizeof(CFTypeRef) * dictSize);
934 if (!keys || !values) return;
935
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]);
941 } else {
942 CFArrayAppendArray(interResult, (CFArrayRef)values[i], CFRangeMake(0, CFArrayGetCount((CFArrayRef)values[i])));
943 }
944 }
945
946 if (*stop) break;
947 }
948
949 free(keys);
950 free(values);
951 }
952
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))
954 {
955 CFTypeRef value = NULL;
956 Boolean stop = false; // for predicate
957 CFMutableArrayRef interResult = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
958 CFDictionaryRef subTable = NULL;
959
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);
966 if (predicate) {
967 _CFBundleFindResourcesWithPredicate(interResult, subTable, predicate, &stop);
968 } else {
969 value = CFDictionaryGetValue(subTable, key);
970 }
971 CFStringDelete(path, CFRangeMake(savedPathLength, CFStringGetLength(path) - savedPathLength)); // Strip the string back to the base path
972 }
973
974 if (!value && !stop) {
975 if (subTable) CFRelease(subTable);
976 subTable = _CFBundleCopyQueryTable(bundle, bundleURL, bundleURLLanguages, path, subDir);
977 if (predicate) {
978 _CFBundleFindResourcesWithPredicate(interResult, subTable, predicate, &stop);
979 } else {
980 // get the path or paths for the given key
981 value = CFDictionaryGetValue(subTable, key);
982 }
983 }
984
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) {
989
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);
993 } else {
994 CFRetain(value);
995 }
996
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;
1005
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;
1011 }
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;
1015 }
1016 }
1017
1018 if (searchForLocalization) {
1019 if (!lpLen || !(CFStringFindWithOptions(pathValue, lproj, searchRange, kCFCompareEqualTo | kCFCompareAnchored, &resultRange) && CFStringFindWithOptions(pathValue, CFSTR("."), CFRangeMake(resultRange.location + resultRange.length, 1), kCFCompareEqualTo, &resultRange))) {
1020 break;
1021 }
1022 checkLP = false;
1023 }
1024
1025 CFArrayAppendValue(interResult, pathValue);
1026 }
1027
1028 CFRelease(value);
1029
1030 if (!returnArray && CFArrayGetCount(interResult) != 0) {
1031 checkLP = false;
1032 }
1033 } else if (value) {
1034 if (CFGetTypeID(value) == CFArrayGetTypeID()) {
1035 CFArrayAppendArray(interResult, (CFArrayRef)value, CFRangeMake(0, CFArrayGetCount((CFArrayRef)value)));
1036 } else {
1037 CFArrayAppendValue(interResult, value);
1038 }
1039 }
1040
1041 value = NULL;
1042 CFRelease(subTable);
1043
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);
1050 }
1051 subTable = _CFBundleCopyQueryTable(bundle, bundleURL, bundleURLLanguages, path, lprojSubdirName);
1052 CFRelease(lprojSubdirName);
1053 value = CFDictionaryGetValue(subTable, key);
1054
1055 if (value) {
1056 if (CFGetTypeID(value) == CFStringGetTypeID()) {
1057 CFArrayAppendValue(interResult, value);
1058 } else {
1059 CFArrayAppendArray(interResult, (CFArrayRef)value, CFRangeMake(0, CFArrayGetCount((CFArrayRef)value)));
1060 }
1061 }
1062
1063 CFRelease(subTable);
1064 }
1065
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;
1071 if (bundle) {
1072 urlStr = CFStringCreateMutableCopy(kCFAllocatorSystemDefault, 0, bundle->_bundleBasePath);
1073 } else {
1074 CFURLRef url = CFURLCopyAbsoluteURL(bundleURL);
1075 CFStringRef bundlePath = CFURLCopyFileSystemPath(url, PLATFORM_PATH_STYLE);
1076 urlStr = CFStringCreateMutableCopy(kCFAllocatorSystemDefault, 0, bundlePath);
1077 CFRelease(url);
1078 CFRelease(bundlePath);
1079 }
1080
1081 if (resourcesDirectory && CFStringGetLength(resourcesDirectory)) {
1082 _CFAppendPathComponent2(urlStr, resourcesDirectory);
1083 }
1084
1085 _CFAppendTrailingPathSlash2(urlStr);
1086
1087 if (!returnArray) {
1088 Boolean isOnlyTypeOrAllFiles = CFStringHasPrefix(key, _CFBundleTypeIndicator);
1089 isOnlyTypeOrAllFiles |= CFStringHasPrefix(key, _CFBundleAllFiles);
1090
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);
1097 } else {
1098 result = (CFURLRef)CFURLCreateWithFileSystemPath(kCFAllocatorSystemDefault, urlStr, PLATFORM_PATH_STYLE, false);
1099 }
1100 } else {
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());
1104 CFRelease(base);
1105 }
1106 } else {
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);
1115 CFRelease(url);
1116 }
1117 result = urls;
1118 CFRelease(base);
1119 }
1120 CFRelease(urlStr);
1121 } else if (returnArray) {
1122 result = CFRetain(interResult);
1123 }
1124 if (path) CFRelease(path);
1125 CFRelease(interResult);
1126 return result;
1127 }
1128
1129 #pragma mark -
1130
1131
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))
1137 {
1138
1139 // Don't use any path info passed into the resource name
1140 CFStringRef realResourceName = NULL;
1141 CFStringRef subPathFromResourceName = NULL;
1142
1143 if (resourceName) {
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));
1149 }
1150
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);
1156 }
1157
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);
1164 }
1165 }
1166
1167 CFMutableStringRef key = NULL;
1168 const static UniChar extensionSep = '.';
1169
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);
1183 } else {
1184 key = (CFMutableStringRef)CFRetain(_CFBundleAllFiles);
1185 }
1186
1187 CFStringRef realSubdirectory = NULL;
1188
1189 bool hasSubPath = subPath && CFStringGetLength(subPath);
1190 bool hasSubPathFromResourceName = subPathFromResourceName && CFStringGetLength(subPathFromResourceName);
1191
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);
1200 }
1201
1202 uint8_t bundleVersion = bundle ? _CFBundleLayoutVersion(bundle) : 0;
1203 CFArrayRef bundleURLLanguages = NULL;
1204 if (bundleURL) {
1205 bundleURLLanguages = _CFBundleCopyLanguageSearchListInDirectory(bundleURL, &bundleVersion);
1206 }
1207
1208 CFStringRef resDir = _CFBundleGetResourceDirForVersion(bundleVersion);
1209
1210 CFTypeRef returnValue = _CFBundleCopyURLsOfKey(bundle, bundleURL, bundleURLLanguages, resDir, realSubdirectory, key, lproj, returnArray, localized, bundleVersion, predicate);
1211
1212 if ((!returnValue || (CFGetTypeID(returnValue) == CFArrayGetTypeID() && CFArrayGetCount((CFArrayRef)returnValue) == 0)) && (0 == bundleVersion || 2 == bundleVersion)) {
1213 CFStringRef bundlePath = NULL;
1214 if (bundle) {
1215 bundlePath = bundle->_bundleBasePath;
1216 CFRetain(bundlePath);
1217 } else {
1218 CFURLRef absoluteURL = CFURLCopyAbsoluteURL(bundleURL);
1219 bundlePath = CFURLCopyFileSystemPath(absoluteURL, PLATFORM_PATH_STYLE);
1220 CFRelease(absoluteURL);
1221 }
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;
1235 } else {
1236 // Assume no resources directory
1237 resDir = CFSTR("");
1238 }
1239 returnValue = _CFBundleCopyURLsOfKey(bundle, bundleURL, bundleURLLanguages, resDir, realSubdirectory, key, lproj, returnArray, localized, bundleVersion, predicate);
1240 }
1241 CFRelease(bundlePath);
1242 }
1243
1244 if (realResourceName) CFRelease(realResourceName);
1245 if (realSubdirectory) CFRelease(realSubdirectory);
1246 if (subPathFromResourceName) CFRelease(subPathFromResourceName);
1247 if (bundleURLLanguages) CFRelease(bundleURLLanguages);
1248 CFRelease(key);
1249
1250
1251 return returnValue;
1252 }