]> git.saurik.com Git - apple/cf.git/blob - CFURL.c
dfeae68b5e870dc4d66164adcca50e92cc3a4f7c
[apple/cf.git] / CFURL.c
1 /*
2 * Copyright (c) 2009 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 /* CFURL.c
24 Copyright (c) 1998-2009, Apple Inc. All rights reserved.
25 Responsibility: Becky Willrich
26 */
27
28 #include <CoreFoundation/CFURL.h>
29 #include <CoreFoundation/CFPriv.h>
30 #include <CoreFoundation/CFCharacterSetPriv.h>
31 #include <CoreFoundation/CFNumber.h>
32 #include "CFInternal.h"
33 #include <CoreFoundation/CFStringEncodingConverter.h>
34 #include <CoreFoundation/CFPriv.h>
35 #include <limits.h>
36 #include <stdlib.h>
37 #include <stdio.h>
38 #include <string.h>
39 #if DEPLOYMENT_TARGET_MACOSX
40 #include <CoreFoundation/CFNumberFormatter.h>
41 #include <unistd.h>
42 #include <sys/stat.h>
43 #include <sys/types.h>
44 #elif DEPLOYMENT_TARGET_EMBEDDED
45 #include <unistd.h>
46 #include <sys/stat.h>
47 #include <sys/types.h>
48 #elif DEPLOYMENT_TARGET_WINDOWS
49 #else
50 #error Unknown or unspecified DEPLOYMENT_TARGET
51 #endif
52
53 #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED
54 static CFArrayRef HFSPathToURLComponents(CFStringRef path, CFAllocatorRef alloc, Boolean isDir);
55 #endif
56 static CFArrayRef WindowsPathToURLComponents(CFStringRef path, CFAllocatorRef alloc, Boolean isDir);
57 static CFStringRef WindowsPathToURLPath(CFStringRef path, CFAllocatorRef alloc, Boolean isDir);
58 static CFStringRef POSIXPathToURLPath(CFStringRef path, CFAllocatorRef alloc, Boolean isDirectory);
59 CFStringRef CFURLCreateStringWithFileSystemPath(CFAllocatorRef allocator, CFURLRef anURL, CFURLPathStyle fsType, Boolean resolveAgainstBase);
60 CF_EXPORT CFURLRef _CFURLCreateCurrentDirectoryURL(CFAllocatorRef allocator);
61 #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED
62 static CFStringRef HFSPathToURLPath(CFStringRef path, CFAllocatorRef alloc, Boolean isDir);
63 #elif DEPLOYMENT_TARGET_WINDOWS
64 #else
65 #error Unknown or unspecified DEPLOYMENT_TARGET
66 #endif
67
68
69
70 #ifndef DEBUG_URL_MEMORY_USAGE
71 #define DEBUG_URL_MEMORY_USAGE 0
72 #endif
73
74 #if DEBUG_URL_MEMORY_USAGE
75 static CFAllocatorRef URLAllocator = NULL;
76 static UInt32 numFileURLsCreated = 0;
77 static UInt32 numFileURLsConverted = 0;
78 static UInt32 numFileURLsDealloced = 0;
79 static UInt32 numURLs = 0;
80 static UInt32 numDealloced = 0;
81 static UInt32 numExtraDataAllocated = 0;
82 static UInt32 numURLsWithBaseURL = 0;
83 static UInt32 numNonUTF8EncodedURLs = 0;
84 #endif
85
86 /* The bit flags in myURL->_flags */
87 #define HAS_SCHEME (0x0001)
88 #define HAS_USER (0x0002)
89 #define HAS_PASSWORD (0x0004)
90 #define HAS_HOST (0x0008)
91 #define HAS_PORT (0x0010)
92 #define HAS_PATH (0x0020)
93 #define HAS_PARAMETERS (0x0040)
94 #define HAS_QUERY (0x0080)
95 #define HAS_FRAGMENT (0x0100)
96 #define HAS_HTTP_SCHEME (0x0200)
97 // Last free bit (0x200) in lower word goes here!
98 #define IS_IPV6_ENCODED (0x0400)
99 #define IS_OLD_UTF8_STYLE (0x0800)
100 #define IS_DIRECTORY (0x1000)
101 #define IS_PARSED (0x2000)
102 #define IS_ABSOLUTE (0x4000)
103 #define IS_DECOMPOSABLE (0x8000)
104
105 #define PATH_TYPE_MASK (0x000F0000)
106 /* POSIX_AND_URL_PATHS_MATCH will only be true if the URL and POSIX paths are identical, character for character, except for the presence/absence of a trailing slash on directories */
107 #define POSIX_AND_URL_PATHS_MATCH (0x00100000)
108 #define ORIGINAL_AND_URL_STRINGS_MATCH (0x00200000)
109
110 /* If ORIGINAL_AND_URL_STRINGS_MATCH is false, these bits determine where they differ */
111 // Scheme can actually never differ because if there were escaped characters prior to the colon, we'd interpret the string as a relative path
112 // #define SCHEME_DIFFERS (0x00400000) unused
113 #define USER_DIFFERS (0x00800000)
114 #define PASSWORD_DIFFERS (0x01000000)
115 #define HOST_DIFFERS (0x02000000)
116 // Port can actually never differ because if there were a non-digit following a colon in the net location, we'd interpret the whole net location as the host
117 #define PORT_DIFFERS (0x04000000)
118 // #define PATH_DIFFERS (0x08000000) unused
119 // #define PARAMETERS_DIFFER (0x10000000) unused
120 // #define QUERY_DIFFERS (0x20000000) unused
121 #define PATH_HAS_FILE_ID (0x40000000)
122 #define HAS_FILE_SCHEME (0x80000000)
123
124 // Number of bits to shift to get from HAS_FOO to FOO_DIFFERS flag
125 #define BIT_SHIFT_FROM_COMPONENT_TO_DIFFERS_FLAG (22)
126
127 // Other useful defines
128 #define NET_LOCATION_MASK (HAS_HOST | HAS_USER | HAS_PASSWORD | HAS_PORT)
129 #define RESOURCE_SPECIFIER_MASK (HAS_PARAMETERS | HAS_QUERY | HAS_FRAGMENT)
130 #define FULL_URL_REPRESENTATION (0xF)
131
132 /* URL_PATH_TYPE(anURL) will be one of the CFURLPathStyle constants, in which case string is a file system path, or will be FULL_URL_REPRESENTATION, in which case the string is the full URL string. One caveat - string always has a trailing path delimiter if the url is a directory URL. This must be stripped before returning file system representations! */
133 #define URL_PATH_TYPE(url) (((url->_flags) & PATH_TYPE_MASK) >> 16)
134 #define PATH_DELIM_FOR_TYPE(fsType) ((fsType) == kCFURLHFSPathStyle ? ':' : (((fsType) == kCFURLWindowsPathStyle) ? '\\' : '/'))
135 #define PATH_DELIM_AS_STRING_FOR_TYPE(fsType) ((fsType) == kCFURLHFSPathStyle ? CFSTR(":") : (((fsType) == kCFURLWindowsPathStyle) ? CFSTR("\\") : CFSTR("/")))
136
137 #define FILE_ID_PREFIX ".file"
138 #define FILE_ID_KEY "id"
139
140 #define ASSERT_CHECK_PATHSTYLE(x) 0
141
142 // In order to reduce the sizeof ( __CFURL ), move these items into a seperate structure which is
143 // only allocated when necessary. In my tests, it's almost never needed -- very rarely does a CFURL have
144 // either a sanitized string or a reserved pointer for URLHandle.
145 struct _CFURLAdditionalData {
146 void *_reserved; // Reserved for URLHandle's use.
147 CFMutableStringRef _sanitizedString; // The fully compliant RFC string. This is only non-NULL if ORIGINAL_AND_URL_STRINGS_MATCH is false. This should never be mutated except when the sanatized string is first computed
148 CFHashCode hashValue;
149 };
150
151 struct __CFURL {
152 CFRuntimeBase _cfBase;
153 UInt32 _flags;
154 CFStringEncoding _encoding; // The encoding to use when asked to remove percent escapes; this is never consulted if IS_OLD_UTF8_STYLE is set.
155 CFStringRef _string; // Never NULL; the meaning of _string depends on URL_PATH_TYPE(myURL) (see above)
156 CFURLRef _base;
157 CFRange *ranges;
158 struct _CFURLAdditionalData* extra;
159 void *_resourceInfo; // For use by CarbonCore to cache property values. Retained and released by CFURL.
160 };
161
162
163 CF_INLINE void* _getReserved ( const struct __CFURL* url )
164 {
165 if ( url && url->extra )
166 return url->extra->_reserved;
167
168 return NULL;
169 }
170
171 CF_INLINE CFMutableStringRef _getSanitizedString ( const struct __CFURL* url )
172 {
173 if ( url && url->extra )
174 return url->extra->_sanitizedString;
175
176 return NULL;
177 }
178
179 static void* _getResourceInfo ( const struct __CFURL* url )
180 {
181 if ( url ) {
182 return url->_resourceInfo;
183 }
184
185 return NULL;
186 }
187
188 static void _CFURLAllocateExtraDataspace( struct __CFURL* url )
189 {
190 if ( url && ! url->extra )
191 { struct _CFURLAdditionalData* extra = (struct _CFURLAdditionalData*) CFAllocatorAllocate( CFGetAllocator( url), sizeof( struct _CFURLAdditionalData ), __kCFAllocatorGCScannedMemory);
192
193 extra->_reserved = _getReserved( url );
194 extra->_sanitizedString = _getSanitizedString( url );
195 extra->hashValue = 0;
196
197 url->extra = extra;
198
199 #if DEBUG_URL_MEMORY_USAGE
200 numExtraDataAllocated ++;
201 #endif
202 }
203 }
204
205 CF_INLINE void _setReserved ( struct __CFURL* url, void* reserved )
206 {
207 if ( url )
208 {
209 // Don't allocate extra space if we're just going to be storing NULL
210 if ( ! url->extra && reserved )
211 _CFURLAllocateExtraDataspace( url );
212
213 if ( url->extra )
214 __CFAssignWithWriteBarrier((void **)&url->extra->_reserved, reserved);
215 }
216 }
217
218 CF_INLINE void _setSanitizedString ( struct __CFURL* url, CFMutableStringRef sanitizedString )
219 {
220 if ( url )
221 {
222 // Don't allocate extra space if we're just going to be storing NULL
223 if ( ! url->extra && sanitizedString )
224 _CFURLAllocateExtraDataspace( url );
225
226 if ( url->extra )
227 url->extra->_sanitizedString = sanitizedString;
228 }
229 }
230
231 static void _setResourceInfo ( struct __CFURL* url, void* resourceInfo )
232 {
233 // Must be atomic
234 // Never a GC object
235 if ( url && OSAtomicCompareAndSwapPtrBarrier( NULL, resourceInfo, &url->_resourceInfo )) {
236 CFRetain( resourceInfo );
237 }
238 }
239
240 static void _convertToURLRepresentation(struct __CFURL *url);
241 static CFURLRef _CFURLCopyAbsoluteFileURL(CFURLRef relativeURL);
242 static CFStringRef _resolveFileSystemPaths(CFStringRef relativePath, CFStringRef basePath, Boolean baseIsDir, CFURLPathStyle fsType, CFAllocatorRef alloc);
243 static void _parseComponents(CFAllocatorRef alloc, CFStringRef string, CFURLRef base, UInt32 *flags, CFRange **range);
244 static CFRange _rangeForComponent(UInt32 flags, CFRange *ranges, UInt32 compFlag);
245 static CFRange _netLocationRange(UInt32 flags, CFRange *ranges);
246 static UInt32 _firstResourceSpecifierFlag(UInt32 flags);
247 static void computeSanitizedString(CFURLRef url);
248 static CFStringRef correctedComponent(CFStringRef component, UInt32 compFlag, CFStringEncoding enc);
249 static CFMutableStringRef resolveAbsoluteURLString(CFAllocatorRef alloc, CFStringRef relString, UInt32 relFlags, CFRange *relRanges, CFStringRef baseString, UInt32 baseFlags, CFRange *baseRanges);
250 static CFStringRef _resolvedPath(UniChar *pathStr, UniChar *end, UniChar pathDelimiter, Boolean stripLeadingDotDots, Boolean stripTrailingDelimiter, CFAllocatorRef alloc);
251
252
253 CF_INLINE void _parseComponentsOfURL(CFURLRef url) {
254 _parseComponents(CFGetAllocator(url), url->_string, url->_base, &(((struct __CFURL *)url)->_flags), &(((struct __CFURL *)url)->ranges));
255 }
256
257 static Boolean _createOldUTF8StyleURLs = false;
258
259 CF_INLINE Boolean createOldUTF8StyleURLs(void) {
260 return (_createOldUTF8StyleURLs);
261 }
262
263 // Our backdoor in case removing the UTF8 constraint for URLs creates unexpected problems. See radar 2902530 -- REW
264 CF_EXPORT
265 void _CFURLCreateOnlyUTF8CompatibleURLs(Boolean createUTF8URLs) {
266 _createOldUTF8StyleURLs = createUTF8URLs;
267 }
268
269 enum {
270 VALID = 1,
271 UNRESERVED = 2,
272 PATHVALID = 4,
273 SCHEME = 8,
274 HEXDIGIT = 16
275 };
276
277 static const unsigned char sURLValidCharacters[] = {
278 /* ' ' 32 */ 0,
279 /* '!' 33 */ VALID | UNRESERVED | PATHVALID ,
280 /* '"' 34 */ 0,
281 /* '#' 35 */ 0,
282 /* '$' 36 */ VALID | PATHVALID ,
283 /* '%' 37 */ 0,
284 /* '&' 38 */ VALID | PATHVALID ,
285 /* ''' 39 */ VALID | UNRESERVED | PATHVALID ,
286 /* '(' 40 */ VALID | UNRESERVED | PATHVALID ,
287 /* ')' 41 */ VALID | UNRESERVED | PATHVALID ,
288 /* '*' 42 */ VALID | UNRESERVED | PATHVALID ,
289 /* '+' 43 */ VALID | SCHEME | PATHVALID ,
290 /* ',' 44 */ VALID | PATHVALID ,
291 /* '-' 45 */ VALID | UNRESERVED | SCHEME | PATHVALID ,
292 /* '.' 46 */ VALID | UNRESERVED | SCHEME | PATHVALID ,
293 /* '/' 47 */ VALID | PATHVALID ,
294 /* '0' 48 */ VALID | UNRESERVED | SCHEME | PATHVALID | HEXDIGIT ,
295 /* '1' 49 */ VALID | UNRESERVED | SCHEME | PATHVALID | HEXDIGIT ,
296 /* '2' 50 */ VALID | UNRESERVED | SCHEME | PATHVALID | HEXDIGIT ,
297 /* '3' 51 */ VALID | UNRESERVED | SCHEME | PATHVALID | HEXDIGIT ,
298 /* '4' 52 */ VALID | UNRESERVED | SCHEME | PATHVALID | HEXDIGIT ,
299 /* '5' 53 */ VALID | UNRESERVED | SCHEME | PATHVALID | HEXDIGIT ,
300 /* '6' 54 */ VALID | UNRESERVED | SCHEME | PATHVALID | HEXDIGIT ,
301 /* '7' 55 */ VALID | UNRESERVED | SCHEME | PATHVALID | HEXDIGIT ,
302 /* '8' 56 */ VALID | UNRESERVED | SCHEME | PATHVALID | HEXDIGIT ,
303 /* '9' 57 */ VALID | UNRESERVED | SCHEME | PATHVALID | HEXDIGIT ,
304 /* ':' 58 */ VALID ,
305 /* ';' 59 */ VALID ,
306 /* '<' 60 */ 0,
307 /* '=' 61 */ VALID | PATHVALID ,
308 /* '>' 62 */ 0,
309 /* '?' 63 */ VALID ,
310 /* '@' 64 */ VALID ,
311 /* 'A' 65 */ VALID | UNRESERVED | SCHEME | PATHVALID | HEXDIGIT ,
312 /* 'B' 66 */ VALID | UNRESERVED | SCHEME | PATHVALID | HEXDIGIT ,
313 /* 'C' 67 */ VALID | UNRESERVED | SCHEME | PATHVALID | HEXDIGIT ,
314 /* 'D' 68 */ VALID | UNRESERVED | SCHEME | PATHVALID | HEXDIGIT ,
315 /* 'E' 69 */ VALID | UNRESERVED | SCHEME | PATHVALID | HEXDIGIT ,
316 /* 'F' 70 */ VALID | UNRESERVED | SCHEME | PATHVALID | HEXDIGIT ,
317 /* 'G' 71 */ VALID | UNRESERVED | SCHEME | PATHVALID ,
318 /* 'H' 72 */ VALID | UNRESERVED | SCHEME | PATHVALID ,
319 /* 'I' 73 */ VALID | UNRESERVED | SCHEME | PATHVALID ,
320 /* 'J' 74 */ VALID | UNRESERVED | SCHEME | PATHVALID ,
321 /* 'K' 75 */ VALID | UNRESERVED | SCHEME | PATHVALID ,
322 /* 'L' 76 */ VALID | UNRESERVED | SCHEME | PATHVALID ,
323 /* 'M' 77 */ VALID | UNRESERVED | SCHEME | PATHVALID ,
324 /* 'N' 78 */ VALID | UNRESERVED | SCHEME | PATHVALID ,
325 /* 'O' 79 */ VALID | UNRESERVED | SCHEME | PATHVALID ,
326 /* 'P' 80 */ VALID | UNRESERVED | SCHEME | PATHVALID ,
327 /* 'Q' 81 */ VALID | UNRESERVED | SCHEME | PATHVALID ,
328 /* 'R' 82 */ VALID | UNRESERVED | SCHEME | PATHVALID ,
329 /* 'S' 83 */ VALID | UNRESERVED | SCHEME | PATHVALID ,
330 /* 'T' 84 */ VALID | UNRESERVED | SCHEME | PATHVALID ,
331 /* 'U' 85 */ VALID | UNRESERVED | SCHEME | PATHVALID ,
332 /* 'V' 86 */ VALID | UNRESERVED | SCHEME | PATHVALID ,
333 /* 'W' 87 */ VALID | UNRESERVED | SCHEME | PATHVALID ,
334 /* 'X' 88 */ VALID | UNRESERVED | SCHEME | PATHVALID ,
335 /* 'Y' 89 */ VALID | UNRESERVED | SCHEME | PATHVALID ,
336 /* 'Z' 90 */ VALID | UNRESERVED | SCHEME | PATHVALID ,
337 /* '[' 91 */ 0,
338 /* '\' 92 */ 0,
339 /* ']' 93 */ 0,
340 /* '^' 94 */ 0,
341 /* '_' 95 */ VALID | UNRESERVED | PATHVALID ,
342 /* '`' 96 */ 0,
343 /* 'a' 97 */ VALID | UNRESERVED | SCHEME | PATHVALID | HEXDIGIT ,
344 /* 'b' 98 */ VALID | UNRESERVED | SCHEME | PATHVALID | HEXDIGIT ,
345 /* 'c' 99 */ VALID | UNRESERVED | SCHEME | PATHVALID | HEXDIGIT ,
346 /* 'd' 100 */ VALID | UNRESERVED | SCHEME | PATHVALID | HEXDIGIT ,
347 /* 'e' 101 */ VALID | UNRESERVED | SCHEME | PATHVALID | HEXDIGIT ,
348 /* 'f' 102 */ VALID | UNRESERVED | SCHEME | PATHVALID | HEXDIGIT ,
349 /* 'g' 103 */ VALID | UNRESERVED | SCHEME | PATHVALID ,
350 /* 'h' 104 */ VALID | UNRESERVED | SCHEME | PATHVALID ,
351 /* 'i' 105 */ VALID | UNRESERVED | SCHEME | PATHVALID ,
352 /* 'j' 106 */ VALID | UNRESERVED | SCHEME | PATHVALID ,
353 /* 'k' 107 */ VALID | UNRESERVED | SCHEME | PATHVALID ,
354 /* 'l' 108 */ VALID | UNRESERVED | SCHEME | PATHVALID ,
355 /* 'm' 109 */ VALID | UNRESERVED | SCHEME | PATHVALID ,
356 /* 'n' 110 */ VALID | UNRESERVED | SCHEME | PATHVALID ,
357 /* 'o' 111 */ VALID | UNRESERVED | SCHEME | PATHVALID ,
358 /* 'p' 112 */ VALID | UNRESERVED | SCHEME | PATHVALID ,
359 /* 'q' 113 */ VALID | UNRESERVED | SCHEME | PATHVALID ,
360 /* 'r' 114 */ VALID | UNRESERVED | SCHEME | PATHVALID ,
361 /* 's' 115 */ VALID | UNRESERVED | SCHEME | PATHVALID ,
362 /* 't' 116 */ VALID | UNRESERVED | SCHEME | PATHVALID ,
363 /* 'u' 117 */ VALID | UNRESERVED | SCHEME | PATHVALID ,
364 /* 'v' 118 */ VALID | UNRESERVED | SCHEME | PATHVALID ,
365 /* 'w' 119 */ VALID | UNRESERVED | SCHEME | PATHVALID ,
366 /* 'x' 120 */ VALID | UNRESERVED | SCHEME | PATHVALID ,
367 /* 'y' 121 */ VALID | UNRESERVED | SCHEME | PATHVALID ,
368 /* 'z' 122 */ VALID | UNRESERVED | SCHEME | PATHVALID ,
369 /* '{' 123 */ 0,
370 /* '|' 124 */ 0,
371 /* '}' 125 */ 0,
372 /* '~' 126 */ VALID | UNRESERVED | PATHVALID ,
373 /* '' 127 */ 0
374 };
375
376 CF_INLINE Boolean isURLLegalCharacter(UniChar ch) {
377 return ( ( 32 <= ch ) && ( ch <= 127 ) ) ? ( sURLValidCharacters[ ch - 32 ] & VALID ) : false;
378 }
379
380 CF_INLINE Boolean scheme_valid(UniChar ch) {
381 return ( ( 32 <= ch ) && ( ch <= 127 ) ) ? ( sURLValidCharacters[ ch - 32 ] & SCHEME ) : false;
382 }
383
384 // "Unreserved" as defined by RFC 2396
385 CF_INLINE Boolean isUnreservedCharacter(UniChar ch) {
386 return ( ( 32 <= ch ) && ( ch <= 127 ) ) ? ( sURLValidCharacters[ ch - 32 ] & UNRESERVED ) : false;
387 }
388
389 CF_INLINE Boolean isPathLegalCharacter(UniChar ch) {
390 return ( ( 32 <= ch ) && ( ch <= 127 ) ) ? ( sURLValidCharacters[ ch - 32 ] & PATHVALID ) : false;
391 }
392
393 CF_INLINE Boolean isHexDigit(UniChar ch) {
394 return ( ( 32 <= ch ) && ( ch <= 127 ) ) ? ( sURLValidCharacters[ ch - 32 ] & HEXDIGIT ) : false;
395 }
396
397 // Returns false if ch1 or ch2 isn't properly formatted
398 CF_INLINE Boolean _translateBytes(UniChar ch1, UniChar ch2, uint8_t *result) {
399 *result = 0;
400 if (ch1 >= '0' && ch1 <= '9') *result += (ch1 - '0');
401 else if (ch1 >= 'a' && ch1 <= 'f') *result += 10 + ch1 - 'a';
402 else if (ch1 >= 'A' && ch1 <= 'F') *result += 10 + ch1 - 'A';
403 else return false;
404
405 *result = (*result) << 4;
406 if (ch2 >= '0' && ch2 <= '9') *result += (ch2 - '0');
407 else if (ch2 >= 'a' && ch2 <= 'f') *result += 10 + ch2 - 'a';
408 else if (ch2 >= 'A' && ch2 <= 'F') *result += 10 + ch2 - 'A';
409 else return false;
410
411 return true;
412 }
413
414 CF_INLINE Boolean _haveTestedOriginalString(CFURLRef url) {
415 return ((url->_flags & ORIGINAL_AND_URL_STRINGS_MATCH) != 0) || (_getSanitizedString(url) != NULL);
416 }
417
418 typedef CFStringRef (*StringTransformation)(CFAllocatorRef, CFStringRef, CFIndex);
419 #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED
420 static CFArrayRef copyStringArrayWithTransformation(CFArrayRef array, StringTransformation transformation) {
421 CFAllocatorRef alloc = CFGetAllocator(array);
422 CFMutableArrayRef mArray = NULL;
423 CFIndex i, c = CFArrayGetCount(array);
424 for (i = 0; i < c; i ++) {
425 CFStringRef origComp = (CFStringRef)CFArrayGetValueAtIndex(array, i);
426 CFStringRef unescapedComp = transformation(alloc, origComp, i);
427 if (!unescapedComp) {
428 break;
429 }
430 if (unescapedComp != origComp) {
431 if (!mArray) {
432 mArray = CFArrayCreateMutableCopy(alloc, c, array);
433 }
434 CFArraySetValueAtIndex(mArray, i, unescapedComp);
435 }
436 CFRelease(unescapedComp);
437 }
438 if (i != c) {
439 if (mArray) CFRelease(mArray);
440 return NULL;
441 } else if (mArray) {
442 return mArray;
443 } else {
444 CFRetain(array);
445 return array;
446 }
447 }
448 #endif
449
450 // Returns NULL if str cannot be converted for whatever reason, str if str contains no characters in need of escaping, or a newly-created string with the appropriate % escape codes in place. Caller must always release the returned string.
451 CF_INLINE CFStringRef _replacePathIllegalCharacters(CFStringRef str, CFAllocatorRef alloc, Boolean preserveSlashes) {
452 if (preserveSlashes) {
453 return CFURLCreateStringByAddingPercentEscapes(alloc, str, NULL, CFSTR(";?"), kCFStringEncodingUTF8);
454 } else {
455 return CFURLCreateStringByAddingPercentEscapes(alloc, str, NULL, CFSTR(";?/"), kCFStringEncodingUTF8);
456 }
457 }
458
459 #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED
460 static CFStringRef escapePathComponent(CFAllocatorRef alloc, CFStringRef origComponent, CFIndex componentIndex) {
461 return CFURLCreateStringByAddingPercentEscapes(alloc, origComponent, NULL, CFSTR(";?/"), kCFStringEncodingUTF8);
462 }
463 #endif
464
465 // We have 2 UniChars of a surrogate; we must convert to the correct percent-encoded UTF8 string and append to str. Added so that file system URLs can always be converted from POSIX to full URL representation. -- REW, 8/20/2001
466 static Boolean _hackToConvertSurrogates(UniChar highChar, UniChar lowChar, CFMutableStringRef str) {
467 UniChar surrogate[2];
468 uint8_t bytes[6]; // Aki sez it should never take more than 6 bytes
469 CFIndex len;
470 uint8_t *currByte;
471 surrogate[0] = highChar;
472 surrogate[1] = lowChar;
473 if (CFStringEncodingUnicodeToBytes(kCFStringEncodingUTF8, 0, surrogate, 2, NULL, bytes, 6, &len) != kCFStringEncodingConversionSuccess) {
474 return false;
475 }
476 for (currByte = bytes; currByte < bytes + len; currByte ++) {
477 UniChar escapeSequence[3] = {'%', '\0', '\0'};
478 unsigned char high, low;
479 high = ((*currByte) & 0xf0) >> 4;
480 low = (*currByte) & 0x0f;
481 escapeSequence[1] = (high < 10) ? '0' + high : 'A' + high - 10;
482 escapeSequence[2] = (low < 10) ? '0' + low : 'A' + low - 10;
483 CFStringAppendCharacters(str, escapeSequence, 3);
484 }
485 return true;
486 }
487
488 static Boolean _appendPercentEscapesForCharacter(UniChar ch, CFStringEncoding encoding, CFMutableStringRef str) {
489 uint8_t bytes[6]; // 6 bytes is the maximum a single character could require in UTF8 (most common case); other encodings could require more
490 uint8_t *bytePtr = bytes, *currByte;
491 CFIndex byteLength;
492 CFAllocatorRef alloc = NULL;
493 if (CFStringEncodingUnicodeToBytes(encoding, 0, &ch, 1, NULL, bytePtr, 6, &byteLength) != kCFStringEncodingConversionSuccess) {
494 byteLength = CFStringEncodingByteLengthForCharacters(encoding, 0, &ch, 1);
495 if (byteLength <= 6) {
496 // The encoding cannot accomodate the character
497 return false;
498 }
499 alloc = CFGetAllocator(str);
500 bytePtr = (uint8_t *)CFAllocatorAllocate(alloc, byteLength, 0);
501 if (!bytePtr || CFStringEncodingUnicodeToBytes(encoding, 0, &ch, 1, NULL, bytePtr, byteLength, &byteLength) != kCFStringEncodingConversionSuccess) {
502 if (bytePtr) CFAllocatorDeallocate(alloc, bytePtr);
503 return false;
504 }
505 }
506 for (currByte = bytePtr; currByte < bytePtr + byteLength; currByte ++) {
507 UniChar escapeSequence[3] = {'%', '\0', '\0'};
508 unsigned char high, low;
509 high = ((*currByte) & 0xf0) >> 4;
510 low = (*currByte) & 0x0f;
511 escapeSequence[1] = (high < 10) ? '0' + high : 'A' + high - 10;
512 escapeSequence[2] = (low < 10) ? '0' + low : 'A' + low - 10;
513 CFStringAppendCharacters(str, escapeSequence, 3);
514 }
515 if (bytePtr != bytes) {
516 CFAllocatorDeallocate(alloc, bytePtr);
517 }
518 return true;
519 }
520
521 // Uses UTF-8 to translate all percent escape sequences; returns NULL if it encounters a format failure. May return the original string.
522 CFStringRef CFURLCreateStringByReplacingPercentEscapes(CFAllocatorRef alloc, CFStringRef originalString, CFStringRef charactersToLeaveEscaped) {
523 CFMutableStringRef newStr = NULL;
524 CFIndex length;
525 CFIndex mark = 0;
526 CFRange percentRange, searchRange;
527 CFStringRef escapedStr = NULL;
528 CFMutableStringRef strForEscapedChar = NULL;
529 UniChar escapedChar;
530 Boolean escapeAll = (charactersToLeaveEscaped && CFStringGetLength(charactersToLeaveEscaped) == 0);
531 Boolean failed = false;
532
533 if (!originalString) return NULL;
534
535 if (charactersToLeaveEscaped == NULL) {
536 return (CFStringRef)CFStringCreateCopy(alloc, originalString);
537 }
538
539 length = CFStringGetLength(originalString);
540 searchRange = CFRangeMake(0, length);
541
542 while (!failed && CFStringFindWithOptions(originalString, CFSTR("%"), searchRange, 0, &percentRange)) {
543 uint8_t bytes[4]; // Single UTF-8 character could require up to 4 bytes.
544 uint8_t numBytesExpected;
545 UniChar ch1, ch2;
546
547 escapedStr = NULL;
548 // Make sure we have at least 2 more characters
549 if (length - percentRange.location < 3) { failed = true; break; }
550
551 // if we don't have at least 2 more characters, we can't interpret the percent escape code,
552 // so we assume the percent character is legit, and let it pass into the string
553 ch1 = CFStringGetCharacterAtIndex(originalString, percentRange.location+1);
554 ch2 = CFStringGetCharacterAtIndex(originalString, percentRange.location+2);
555 if (!_translateBytes(ch1, ch2, bytes)) { failed = true; break; }
556 if (!(bytes[0] & 0x80)) {
557 numBytesExpected = 1;
558 } else if (!(bytes[0] & 0x20)) {
559 numBytesExpected = 2;
560 } else if (!(bytes[0] & 0x10)) {
561 numBytesExpected = 3;
562 } else {
563 numBytesExpected = 4;
564 }
565 if (numBytesExpected == 1) {
566 // one byte sequence (most common case); handle this specially
567 escapedChar = bytes[0];
568 if (!strForEscapedChar) {
569 strForEscapedChar = CFStringCreateMutableWithExternalCharactersNoCopy(alloc, &escapedChar, 1, 1, kCFAllocatorNull);
570 }
571 escapedStr = strForEscapedChar;
572 } else {
573 CFIndex j;
574 // Make sure up front that we have enough characters
575 if (length < percentRange.location + numBytesExpected * 3) { failed = true; break; }
576 for (j = 1; j < numBytesExpected; j ++) {
577 if (CFStringGetCharacterAtIndex(originalString, percentRange.location + 3*j) != '%') { failed = true; break; }
578 ch1 = CFStringGetCharacterAtIndex(originalString, percentRange.location + 3*j + 1);
579 ch2 = CFStringGetCharacterAtIndex(originalString, percentRange.location + 3*j + 2);
580 if (!_translateBytes(ch1, ch2, bytes+j)) { failed = true; break; }
581 }
582
583 // !!! We should do the low-level bit-twiddling ourselves; this is expensive! REW, 6/10/99
584 escapedStr = CFStringCreateWithBytes(alloc, bytes, numBytesExpected, kCFStringEncodingUTF8, false);
585 if (!escapedStr) {
586 failed = true;
587 } else if (CFStringGetLength(escapedStr) == 0 && numBytesExpected == 3 && bytes[0] == 0xef && bytes[1] == 0xbb && bytes[2] == 0xbf) {
588 // Somehow, the UCS-2 BOM got translated in to a UTF8 string
589 escapedChar = 0xfeff;
590 if (!strForEscapedChar) {
591 strForEscapedChar = CFStringCreateMutableWithExternalCharactersNoCopy(alloc, &escapedChar, 1, 1, kCFAllocatorNull);
592 }
593 CFRelease(escapedStr);
594 escapedStr = strForEscapedChar;
595 }
596 if (failed) break;
597 }
598
599 // The new character is in escapedChar; the number of percent escapes it took is in numBytesExpected.
600 searchRange.location = percentRange.location + 3 * numBytesExpected;
601 searchRange.length = length - searchRange.location;
602
603 if (!escapeAll) {
604 if (CFStringFind(charactersToLeaveEscaped, escapedStr, 0).location != kCFNotFound) {
605 if (escapedStr != strForEscapedChar) {
606 CFRelease(escapedStr);
607 escapedStr = NULL;
608 }
609 continue;
610 }
611 }
612
613 if (!newStr) {
614 newStr = CFStringCreateMutable(alloc, length);
615 }
616 if (percentRange.location - mark > 0) {
617 // The creation of this temporary string is unfortunate.
618 CFStringRef substring = CFStringCreateWithSubstring(alloc, originalString, CFRangeMake(mark, percentRange.location - mark));
619 CFStringAppend(newStr, substring);
620 CFRelease(substring);
621 }
622 CFStringAppend(newStr, escapedStr);
623 if (escapedStr != strForEscapedChar) {
624 CFRelease(escapedStr);
625 escapedStr = NULL;
626 }
627 mark = searchRange.location;// We need mark to be the index of the first character beyond the escape sequence
628 }
629
630 if (escapedStr && escapedStr != strForEscapedChar) CFRelease(escapedStr);
631 if (strForEscapedChar) CFRelease(strForEscapedChar);
632 if (failed) {
633 if (newStr) CFRelease(newStr);
634 return NULL;
635 } else if (newStr) {
636 if (mark < length) {
637 // Need to cat on the remainder of the string
638 CFStringRef substring = CFStringCreateWithSubstring(alloc, originalString, CFRangeMake(mark, length - mark));
639 CFStringAppend(newStr, substring);
640 CFRelease(substring);
641 }
642 return newStr;
643 } else {
644 return (CFStringRef)CFStringCreateCopy(alloc, originalString);
645 }
646 }
647
648 CF_EXPORT
649 CFStringRef CFURLCreateStringByReplacingPercentEscapesUsingEncoding(CFAllocatorRef alloc, CFStringRef originalString, CFStringRef charactersToLeaveEscaped, CFStringEncoding enc) {
650 if (enc == kCFStringEncodingUTF8) {
651 return CFURLCreateStringByReplacingPercentEscapes(alloc, originalString, charactersToLeaveEscaped);
652 } else {
653 CFMutableStringRef newStr = NULL;
654 CFMutableStringRef escapedStr = NULL;
655 CFIndex length;
656 CFIndex mark = 0;
657 CFRange percentRange, searchRange;
658 Boolean escapeAll = (charactersToLeaveEscaped && CFStringGetLength(charactersToLeaveEscaped) == 0);
659 Boolean failed = false;
660 uint8_t byteBuffer[8];
661 uint8_t *bytes = byteBuffer;
662 int capacityOfBytes = 8;
663
664 if (!originalString) return NULL;
665
666 if (charactersToLeaveEscaped == NULL) {
667 return (CFStringRef)CFStringCreateCopy(alloc, originalString);
668 }
669
670 length = CFStringGetLength(originalString);
671 searchRange = CFRangeMake(0, length);
672
673 while (!failed && CFStringFindWithOptions(originalString, CFSTR("%"), searchRange, 0, &percentRange)) {
674 UniChar ch1, ch2;
675 CFIndex percentLoc = percentRange.location;
676 CFStringRef convertedString;
677 int numBytesUsed = 0;
678 do {
679 // Make sure we have at least 2 more characters
680 if (length - percentLoc < 3) { failed = true; break; }
681
682 if (numBytesUsed == capacityOfBytes) {
683 if (bytes == byteBuffer) {
684 bytes = (uint8_t *)CFAllocatorAllocate(alloc, 16 * sizeof(uint8_t), 0);
685 memmove(bytes, byteBuffer, capacityOfBytes);
686 capacityOfBytes = 16;
687 } else {
688 void *oldbytes = bytes;
689 int oldcap = capacityOfBytes;
690 capacityOfBytes = 2*capacityOfBytes;
691 bytes = (uint8_t *)CFAllocatorAllocate(alloc, capacityOfBytes * sizeof(uint8_t), 0);
692 memmove(bytes, oldbytes, oldcap);
693 CFAllocatorDeallocate(alloc, oldbytes);
694 }
695 }
696 percentLoc ++;
697 ch1 = CFStringGetCharacterAtIndex(originalString, percentLoc);
698 percentLoc ++;
699 ch2 = CFStringGetCharacterAtIndex(originalString, percentLoc);
700 percentLoc ++;
701 if (!_translateBytes(ch1, ch2, bytes + numBytesUsed)) { failed = true; break; }
702 numBytesUsed ++;
703 } while (CFStringGetCharacterAtIndex(originalString, percentLoc) == '%');
704 searchRange.location = percentLoc;
705 searchRange.length = length - searchRange.location;
706
707 if (failed) break;
708 convertedString = CFStringCreateWithBytes(alloc, bytes, numBytesUsed, enc, false);
709 if (!convertedString) {
710 failed = true;
711 break;
712 }
713
714 if (!newStr) {
715 newStr = CFStringCreateMutable(alloc, length);
716 }
717 if (percentRange.location - mark > 0) {
718 // The creation of this temporary string is unfortunate.
719 CFStringRef substring = CFStringCreateWithSubstring(alloc, originalString, CFRangeMake(mark, percentRange.location - mark));
720 CFStringAppend(newStr, substring);
721 CFRelease(substring);
722 }
723
724 if (escapeAll) {
725 CFStringAppend(newStr, convertedString);
726 CFRelease(convertedString);
727 } else {
728 CFIndex i, c = CFStringGetLength(convertedString);
729 if (!escapedStr) {
730 escapedStr = CFStringCreateMutableWithExternalCharactersNoCopy(alloc, &ch1, 1, 1, kCFAllocatorNull);
731 }
732 for (i = 0; i < c; i ++) {
733 ch1 = CFStringGetCharacterAtIndex(convertedString, i);
734 if (CFStringFind(charactersToLeaveEscaped, escapedStr, 0).location == kCFNotFound) {
735 CFStringAppendCharacters(newStr, &ch1, 1);
736 } else {
737 // Must regenerate the escape sequence for this character; because we started with percent escapes, we know this call cannot fail
738 _appendPercentEscapesForCharacter(ch1, enc, newStr);
739 }
740 }
741 }
742 mark = searchRange.location;// We need mark to be the index of the first character beyond the escape sequence
743 }
744
745 if (escapedStr) CFRelease(escapedStr);
746 if (bytes != byteBuffer) CFAllocatorDeallocate(alloc, bytes);
747 if (failed) {
748 if (newStr) CFRelease(newStr);
749 return NULL;
750 } else if (newStr) {
751 if (mark < length) {
752 // Need to cat on the remainder of the string
753 CFStringRef substring = CFStringCreateWithSubstring(alloc, originalString, CFRangeMake(mark, length - mark));
754 CFStringAppend(newStr, substring);
755 CFRelease(substring);
756 }
757 return newStr;
758 } else {
759 return (CFStringRef)CFStringCreateCopy(alloc, originalString);
760 }
761 }
762 }
763
764
765 static CFStringRef _addPercentEscapesToString(CFAllocatorRef allocator, CFStringRef originalString, Boolean (*shouldReplaceChar)(UniChar, void*), CFIndex (*handlePercentChar)(CFIndex, CFStringRef, CFStringRef *, void *), CFStringEncoding encoding, void *context) {
766 CFMutableStringRef newString = NULL;
767 CFIndex idx, length;
768 CFStringInlineBuffer buf;
769
770 if (!originalString) return NULL;
771 length = CFStringGetLength(originalString);
772 if (length == 0) return (CFStringRef)CFStringCreateCopy(allocator, originalString);
773 CFStringInitInlineBuffer(originalString, &buf, CFRangeMake(0, length));
774
775 for (idx = 0; idx < length; idx ++) {
776 UniChar ch = CFStringGetCharacterFromInlineBuffer(&buf, idx);
777 Boolean shouldReplace = shouldReplaceChar(ch, context);
778 if (shouldReplace) {
779 // Perform the replacement
780 if (!newString) {
781 newString = CFStringCreateMutableCopy(CFGetAllocator(originalString), 0, originalString);
782 CFStringDelete(newString, CFRangeMake(idx, length-idx));
783 }
784 if (!_appendPercentEscapesForCharacter(ch, encoding, newString)) {
785 //#warning FIXME - once CFString supports finding glyph boundaries walk by glyph boundaries instead of by unichars
786 if (encoding == kCFStringEncodingUTF8 && CFCharacterSetIsSurrogateHighCharacter(ch) && idx + 1 < length && CFCharacterSetIsSurrogateLowCharacter(CFStringGetCharacterFromInlineBuffer(&buf, idx+1))) {
787 // Hack to guarantee we always safely convert file URLs between POSIX & full URL representation
788 if (_hackToConvertSurrogates(ch, CFStringGetCharacterFromInlineBuffer(&buf, idx+1), newString)) {
789 idx ++; // We consumed 2 characters, not 1
790 } else {
791 break;
792 }
793 } else {
794 break;
795 }
796 }
797 } else if (ch == '%' && handlePercentChar) {
798 CFStringRef replacementString = NULL;
799 CFIndex newIndex = handlePercentChar(idx, originalString, &replacementString, context);
800 if (newIndex < 0) {
801 break;
802 } else if (replacementString) {
803 if (!newString) {
804 newString = CFStringCreateMutableCopy(CFGetAllocator(originalString), 0, originalString);
805 CFStringDelete(newString, CFRangeMake(idx, length-idx));
806 }
807 CFStringAppend(newString, replacementString);
808 CFRelease(replacementString);
809 }
810 if (newIndex == idx) {
811 if (newString) {
812 CFStringAppendCharacters(newString, &ch, 1);
813 }
814 } else {
815 if (!replacementString && newString) {
816 CFIndex tmpIndex;
817 for (tmpIndex = idx; tmpIndex < newIndex; tmpIndex ++) {
818 ch = CFStringGetCharacterAtIndex(originalString, idx);
819 CFStringAppendCharacters(newString, &ch, 1);
820 }
821 }
822 idx = newIndex - 1;
823 }
824 } else if (newString) {
825 CFStringAppendCharacters(newString, &ch, 1);
826 }
827 }
828 if (idx < length) {
829 // Ran in to an encoding failure
830 if (newString) CFRelease(newString);
831 return NULL;
832 } else if (newString) {
833 return newString;
834 } else {
835 return (CFStringRef)CFStringCreateCopy(CFGetAllocator(originalString), originalString);
836 }
837 }
838
839
840 static Boolean _stringContainsCharacter(CFStringRef string, UniChar ch) {
841 CFIndex i, c = CFStringGetLength(string);
842 CFStringInlineBuffer buf;
843 CFStringInitInlineBuffer(string, &buf, CFRangeMake(0, c));
844 for (i = 0; i < c; i ++) if (__CFStringGetCharacterFromInlineBufferQuick(&buf, i) == ch) return true;
845 return false;
846 }
847
848 static Boolean _shouldPercentReplaceChar(UniChar ch, void *context) {
849 CFStringRef unescape = ((CFStringRef *)context)[0];
850 CFStringRef escape = ((CFStringRef *)context)[1];
851 Boolean shouldReplace = (isURLLegalCharacter(ch) == false);
852 if (shouldReplace) {
853 if (unescape && _stringContainsCharacter(unescape, ch)) {
854 shouldReplace = false;
855 }
856 } else if (escape && _stringContainsCharacter(escape, ch)) {
857 shouldReplace = true;
858 }
859 return shouldReplace;
860 }
861
862 CF_EXPORT CFStringRef CFURLCreateStringByAddingPercentEscapes(CFAllocatorRef allocator, CFStringRef originalString, CFStringRef charactersToLeaveUnescaped, CFStringRef legalURLCharactersToBeEscaped, CFStringEncoding encoding) {
863 CFStringRef strings[2];
864 strings[0] = charactersToLeaveUnescaped;
865 strings[1] = legalURLCharactersToBeEscaped;
866 return _addPercentEscapesToString(allocator, originalString, _shouldPercentReplaceChar, NULL, encoding, strings);
867 }
868
869
870 #if 0
871 static Boolean __CFURLCompare(CFTypeRef cf1, CFTypeRef cf2) {
872 CFURLRef url1 = (CFURLRef)cf1;
873 CFURLRef url2 = (CFURLRef)cf2;
874 UInt32 pathType1, pathType2;
875
876 __CFGenericValidateType(cf1, CFURLGetTypeID());
877 __CFGenericValidateType(cf2, CFURLGetTypeID());
878
879 if (url1 == url2) return kCFCompareEqualTo;
880
881 if ( url1->_base ) {
882 if (! url2->_base) return kCFCompareEqualTo;
883 if (!CFEqual( url1->_base, url2->_base )) return false;
884 } else if ( url2->_base) {
885 return false;
886 }
887
888 pathType1 = URL_PATH_TYPE(url1);
889 pathType2 = URL_PATH_TYPE(url2);
890 if (pathType1 == pathType2) {
891 if (pathType1 != FULL_URL_REPRESENTATION) {
892 return CFEqual(url1->_string, url2->_string);
893 } else {
894 // Do not compare the original strings; compare the sanatized strings.
895 return CFEqual(CFURLGetString(url1), CFURLGetString(url2));
896 }
897 } else {
898 // Try hard to avoid the expensive conversion from a file system representation to the canonical form
899 CFStringRef scheme1 = CFURLCopyScheme(url1);
900 CFStringRef scheme2 = CFURLCopyScheme(url2);
901 Boolean eq;
902 if (scheme1 && scheme2) {
903 eq = CFEqual(scheme1, scheme2);
904 CFRelease(scheme1);
905 CFRelease(scheme2);
906 } else if (!scheme1 && !scheme2) {
907 eq = TRUE;
908 } else {
909 eq = FALSE;
910 if (scheme1) CFRelease(scheme1);
911 else CFRelease(scheme2);
912 }
913 if (!eq) return false;
914
915 if (pathType1 == FULL_URL_REPRESENTATION) {
916 if (!(url1->_flags & IS_PARSED)) {
917 _parseComponentsOfURL(url1);
918 }
919 if (url1->_flags & (HAS_USER | HAS_PORT | HAS_PASSWORD | HAS_QUERY | HAS_PARAMETERS | HAS_FRAGMENT )) {
920 return false;
921 }
922 }
923
924 if (pathType2 == FULL_URL_REPRESENTATION) {
925 if (!(url2->_flags & IS_PARSED)) {
926 _parseComponentsOfURL(url2);
927 }
928 if (url2->_flags & (HAS_USER | HAS_PORT | HAS_PASSWORD | HAS_QUERY | HAS_PARAMETERS | HAS_FRAGMENT )) {
929 return false;
930 }
931 }
932
933 // No help for it; we now must convert to the canonical representation and compare.
934 return CFEqual(CFURLGetString(url1), CFURLGetString(url2));
935 }
936 }
937 #endif
938
939 static Boolean __CFURLEqual(CFTypeRef cf1, CFTypeRef cf2) {
940 CFURLRef url1 = (CFURLRef)cf1;
941 CFURLRef url2 = (CFURLRef)cf2;
942 UInt32 pathType1, pathType2;
943
944 __CFGenericValidateType(cf1, CFURLGetTypeID());
945 __CFGenericValidateType(cf2, CFURLGetTypeID());
946
947 if (url1 == url2) return true;
948 if ((url1->_flags & IS_PARSED) && (url2->_flags & IS_PARSED) && (url1->_flags & IS_DIRECTORY) != (url2->_flags & IS_DIRECTORY)) return false;
949 if ( url1->_base ) {
950 if (! url2->_base) return false;
951 if (!CFEqual( url1->_base, url2->_base )) return false;
952 } else if ( url2->_base) {
953 return false;
954 }
955
956 pathType1 = URL_PATH_TYPE(url1);
957 pathType2 = URL_PATH_TYPE(url2);
958 if (pathType1 == pathType2) {
959 if (pathType1 != FULL_URL_REPRESENTATION) {
960 return CFEqual(url1->_string, url2->_string);
961 } else {
962 // Do not compare the original strings; compare the sanatized strings.
963 return CFEqual(CFURLGetString(url1), CFURLGetString(url2));
964 }
965 } else {
966 // Try hard to avoid the expensive conversion from a file system representation to the canonical form
967 CFStringRef scheme1 = CFURLCopyScheme(url1);
968 CFStringRef scheme2 = CFURLCopyScheme(url2);
969 Boolean eq;
970 if (scheme1 && scheme2) {
971 eq = CFEqual(scheme1, scheme2);
972 CFRelease(scheme1);
973 CFRelease(scheme2);
974 } else if (!scheme1 && !scheme2) {
975 eq = TRUE;
976 } else {
977 eq = FALSE;
978 if (scheme1) CFRelease(scheme1);
979 else CFRelease(scheme2);
980 }
981 if (!eq) return false;
982
983 if (pathType1 == FULL_URL_REPRESENTATION) {
984 if (!(url1->_flags & IS_PARSED)) {
985 _parseComponentsOfURL(url1);
986 }
987 if (url1->_flags & (HAS_USER | HAS_PORT | HAS_PASSWORD | HAS_QUERY | HAS_PARAMETERS | HAS_FRAGMENT )) {
988 return false;
989 }
990 }
991
992 if (pathType2 == FULL_URL_REPRESENTATION) {
993 if (!(url2->_flags & IS_PARSED)) {
994 _parseComponentsOfURL(url2);
995 }
996 if (url2->_flags & (HAS_USER | HAS_PORT | HAS_PASSWORD | HAS_QUERY | HAS_PARAMETERS | HAS_FRAGMENT )) {
997 return false;
998 }
999 }
1000
1001 // No help for it; we now must convert to the canonical representation and compare.
1002 return CFEqual(CFURLGetString(url1), CFURLGetString(url2));
1003 }
1004 }
1005
1006 static CFHashCode __CFURLHash(CFTypeRef cf) {
1007 /* This is tricky, because we do not want the hash value to change as a file system URL is changed to its canonical representation, nor do we wish to force the conversion to the canonical representation. We choose instead to take the last path component (or "/" in the unlikely case that the path is empty), then hash on that. */
1008 struct __CFURL* url = (struct __CFURL*)cf;
1009 CFHashCode result = 0;
1010
1011 if ( url )
1012 {
1013 // Allocate our extra space if it isn't already allocated
1014 if ( url && ! url->extra )
1015 _CFURLAllocateExtraDataspace( url );
1016
1017 if ( url->extra ) {
1018 result = url->extra->hashValue;
1019
1020 if ( ! result ) {
1021 if (CFURLCanBeDecomposed(url)) {
1022 CFStringRef lastComp = CFURLCopyLastPathComponent(url);
1023 CFStringRef hostNameRef = CFURLCopyHostName(url );
1024
1025 result = 0;
1026
1027 if (lastComp) {
1028 result = CFHash(lastComp);
1029 CFRelease(lastComp);
1030 }
1031
1032 if ( hostNameRef ) {
1033 result ^= CFHash( hostNameRef );
1034 CFRelease( hostNameRef );
1035 }
1036 } else {
1037 result = CFHash(CFURLGetString(url));
1038 }
1039
1040 if ( ! result ) // never store a 0 value for the hashed value
1041 result = 1;
1042
1043 url->extra->hashValue = result;
1044 }
1045 }
1046 }
1047
1048 return result;
1049 }
1050
1051 static CFStringRef __CFURLCopyFormattingDescription(CFTypeRef cf, CFDictionaryRef formatOptions) {
1052 CFURLRef url = (CFURLRef)cf;
1053 __CFGenericValidateType(cf, CFURLGetTypeID());
1054 if (! url->_base) {
1055 CFRetain(url->_string);
1056 return url->_string;
1057 } else {
1058 // Do not dereference url->_base; it may be an ObjC object
1059 return CFStringCreateWithFormat(CFGetAllocator(url), NULL, CFSTR("%@ -- %@"), url->_string, url->_base);
1060 }
1061 }
1062
1063
1064 static CFStringRef __CFURLCopyDescription(CFTypeRef cf) {
1065 CFURLRef url = (CFURLRef)cf;
1066 CFStringRef result;
1067 CFAllocatorRef alloc = CFGetAllocator(url);
1068 if ( url->_base) {
1069 CFStringRef baseString = CFCopyDescription(url->_base);
1070 result = CFStringCreateWithFormat(alloc, NULL, CFSTR("<CFURL %p [%p]>{type = %d, string = %@, encoding = %d\n\tbase = %@}"), cf, alloc, URL_PATH_TYPE(url), url->_string, url->_encoding, baseString);
1071 CFRelease(baseString);
1072 } else {
1073 result = CFStringCreateWithFormat(alloc, NULL, CFSTR("<CFURL %p [%p]>{type = %d, string = %@, encoding = %d, base = (null)}"), cf, alloc, URL_PATH_TYPE(url), url->_string, url->_encoding);
1074 }
1075 return result;
1076 }
1077
1078 #if DEBUG_URL_MEMORY_USAGE
1079
1080 extern __attribute((used)) void __CFURLDumpMemRecord(void) {
1081 CFStringRef str = CFStringCreateWithFormat(kCFAllocatorSystemDefault, NULL, CFSTR("%d URLs created; %d destroyed\n%d file URLs created; %d converted; %d destroyed. %d urls had 'extra' data allocated, %d had base urls, %d were not UTF8 encoded\n"), numURLs, numDealloced, numFileURLsCreated, numFileURLsConverted, numFileURLsDealloced, numExtraDataAllocated, numURLsWithBaseURL, numNonUTF8EncodedURLs );
1082 CFShow(str);
1083 CFRelease(str);
1084 // if (URLAllocator) CFCountingAllocatorPrintPointers(URLAllocator);
1085 }
1086 #endif
1087
1088 static void __CFURLDeallocate(CFTypeRef cf) {
1089 CFURLRef url = (CFURLRef)cf;
1090 CFAllocatorRef alloc;
1091 __CFGenericValidateType(cf, CFURLGetTypeID());
1092 alloc = CFGetAllocator(url);
1093 #if DEBUG_URL_MEMORY_USAGE
1094 numDealloced ++;
1095 if (URL_PATH_TYPE(url) != FULL_URL_REPRESENTATION) {
1096 numFileURLsDealloced ++;
1097 }
1098 #endif
1099 if (url->_string) CFRelease(url->_string); // GC: 3879914
1100 if (url->_base) CFRelease(url->_base);
1101 if (url->ranges) CFAllocatorDeallocate(alloc, url->ranges);
1102 if (_getSanitizedString(url)) CFRelease(_getSanitizedString(url));
1103 if ( url->extra != NULL ) CFAllocatorDeallocate( alloc, url->extra );
1104 if (_getResourceInfo(url)) CFRelease(_getResourceInfo(url));
1105 }
1106
1107 static CFTypeID __kCFURLTypeID = _kCFRuntimeNotATypeID;
1108
1109 static const CFRuntimeClass __CFURLClass = {
1110 0,
1111 "CFURL",
1112 NULL, // init
1113 NULL, // copy
1114 __CFURLDeallocate,
1115 __CFURLEqual,
1116 __CFURLHash,
1117 __CFURLCopyFormattingDescription,
1118 __CFURLCopyDescription
1119 };
1120
1121 // When __CONSTANT_CFSTRINGS__ is not defined, we have separate macros for static and exported constant strings, but
1122 // when it is defined, we must prefix with static to prevent the string from being exported
1123 #ifdef __CONSTANT_CFSTRINGS__
1124 static CONST_STRING_DECL(kCFURLFileScheme, "file")
1125 static CONST_STRING_DECL(kCFURLDataScheme, "data")
1126 static CONST_STRING_DECL(kCFURLHTTPScheme, "http")
1127 static CONST_STRING_DECL(kCFURLLocalhost, "localhost")
1128 #else
1129 CONST_STRING_DECL(kCFURLFileScheme, "file")
1130 CONST_STRING_DECL(kCFURLDataScheme, "data")
1131 CONST_STRING_DECL(kCFURLHTTPScheme, "http")
1132 CONST_STRING_DECL(kCFURLLocalhost, "localhost")
1133 #endif
1134 __private_extern__ void __CFURLInitialize(void) {
1135 __kCFURLTypeID = _CFRuntimeRegisterClass(&__CFURLClass);
1136 }
1137
1138 /* Toll-free bridging support; get the true CFURL from an NSURL */
1139 CF_INLINE CFURLRef _CFURLFromNSURL(CFURLRef url) {
1140 CF_OBJC_FUNCDISPATCH0(__kCFURLTypeID, CFURLRef, url, "_cfurl");
1141 return url;
1142 }
1143
1144 CFTypeID CFURLGetTypeID(void) {
1145 return __kCFURLTypeID;
1146 }
1147
1148 __private_extern__ void CFShowURL(CFURLRef url) {
1149 if (!url) {
1150 fprintf(stdout, "(null)\n");
1151 return;
1152 }
1153 fprintf(stdout, "<CFURL %p>{", (const void*)url);
1154 if (CF_IS_OBJC(__kCFURLTypeID, url)) {
1155 fprintf(stdout, "ObjC bridged object}\n");
1156 return;
1157 }
1158 fprintf(stdout, "\n\tPath type: ");
1159 switch (URL_PATH_TYPE(url)) {
1160 case kCFURLPOSIXPathStyle:
1161 fprintf(stdout, "POSIX");
1162 break;
1163 case kCFURLHFSPathStyle:
1164 fprintf(stdout, "HFS");
1165 break;
1166 case kCFURLWindowsPathStyle:
1167 fprintf(stdout, "NTFS");
1168 break;
1169 case FULL_URL_REPRESENTATION:
1170 fprintf(stdout, "Native URL");
1171 break;
1172 default:
1173 fprintf(stdout, "UNRECOGNIZED PATH TYPE %d", (char)URL_PATH_TYPE(url));
1174 }
1175 fprintf(stdout, "\n\tRelative string: ");
1176 CFShow(url->_string);
1177 fprintf(stdout, "\tBase URL: ");
1178 if (url->_base) {
1179 fprintf(stdout, "<%p> ", (const void*)url->_base);
1180 CFShow(url->_base);
1181 } else {
1182 fprintf(stdout, "(null)\n");
1183 }
1184 fprintf(stdout, "\tFlags: 0x%x\n}\n", (unsigned int)url->_flags);
1185 }
1186
1187
1188 /***************************************************/
1189 /* URL creation and String/Data creation from URLS */
1190 /***************************************************/
1191 static void constructBuffers(CFAllocatorRef alloc, CFStringRef string, const char **cstring, const UniChar **ustring, Boolean *useCString, Boolean *freeCharacters) {
1192 CFIndex neededLength;
1193 CFIndex length;
1194 CFRange rg;
1195
1196 *cstring = CFStringGetCStringPtr(string, kCFStringEncodingISOLatin1);
1197 if (*cstring) {
1198 *ustring = NULL;
1199 *useCString = true;
1200 *freeCharacters = false;
1201 return;
1202 }
1203
1204 *ustring = CFStringGetCharactersPtr(string);
1205 if (*ustring) {
1206 *useCString = false;
1207 *freeCharacters = false;
1208 return;
1209 }
1210
1211 *freeCharacters = true;
1212 length = CFStringGetLength(string);
1213 rg = CFRangeMake(0, length);
1214 CFStringGetBytes(string, rg, kCFStringEncodingISOLatin1, 0, false, NULL, INT_MAX, &neededLength);
1215 if (neededLength == length) {
1216 char *buf = (char *)CFAllocatorAllocate(alloc, length, 0);
1217 CFStringGetBytes(string, rg, kCFStringEncodingISOLatin1, 0, false, (uint8_t *)buf, length, NULL);
1218 *cstring = buf;
1219 *useCString = true;
1220 } else {
1221 UniChar *buf = (UniChar *)CFAllocatorAllocate(alloc, length * sizeof(UniChar), 0);
1222 CFStringGetCharacters(string, rg, buf);
1223 *useCString = false;
1224 *ustring = buf;
1225 }
1226 }
1227
1228 #define STRING_CHAR(x) (useCString ? cstring[(x)] : ustring[(x)])
1229 static void _parseComponents(CFAllocatorRef alloc, CFStringRef string, CFURLRef baseURL, UInt32 *theFlags, CFRange **range) {
1230 CFRange ranges[9];
1231 /* index gives the URL part involved; to calculate the correct range index, use the number of the bit of the equivalent flag (i.e. the host flag is HAS_HOST, which is 0x8. so the range index for the host is 3.) Note that this is true in this function ONLY, since the ranges stored in (*range) are actually packed, skipping those URL components that don't exist. This is why the indices are hard-coded in this function. */
1232
1233 CFIndex idx, base_idx = 0;
1234 CFIndex string_length;
1235 UInt32 flags = (IS_PARSED | *theFlags);
1236 Boolean useCString, freeCharacters, isCompliant;
1237 uint8_t numRanges = 0;
1238 const char *cstring = NULL;
1239 const UniChar *ustring = NULL;
1240
1241 string_length = CFStringGetLength(string);
1242 constructBuffers(alloc, string, &cstring, &ustring, &useCString, &freeCharacters);
1243
1244 // Algorithm is as described in RFC 1808
1245 // 1: parse the fragment; remainder after left-most "#" is fragment
1246 for (idx = base_idx; idx < string_length; idx++) {
1247 if ('#' == STRING_CHAR(idx)) {
1248 flags |= HAS_FRAGMENT;
1249 ranges[8].location = idx + 1;
1250 ranges[8].length = string_length - (idx + 1);
1251 numRanges ++;
1252 string_length = idx; // remove fragment from parse string
1253 break;
1254 }
1255 }
1256 // 2: parse the scheme
1257 for (idx = base_idx; idx < string_length; idx++) {
1258 UniChar ch = STRING_CHAR(idx);
1259 if (':' == ch) {
1260 flags |= HAS_SCHEME;
1261 flags |= IS_ABSOLUTE;
1262 ranges[0].location = base_idx;
1263 ranges[0].length = idx;
1264 numRanges ++;
1265 base_idx = idx + 1;
1266 // optimization for http urls
1267 if (idx == 4 && STRING_CHAR(0) == 'h' && STRING_CHAR(1) == 't' && STRING_CHAR(2) == 't' && STRING_CHAR(3) == 'p') {
1268 flags |= HAS_HTTP_SCHEME;
1269 }
1270 // optimization for file urls
1271 if (idx == 4 && STRING_CHAR(0) == 'f' && STRING_CHAR(1) == 'i' && STRING_CHAR(2) == 'l' && STRING_CHAR(3) == 'e') {
1272 flags |= HAS_FILE_SCHEME;
1273 }
1274 break;
1275 } else if (!scheme_valid(ch)) {
1276 break; // invalid scheme character -- no scheme
1277 }
1278 }
1279
1280 // Make sure we have an RFC-1808 compliant URL - that's either something without a scheme, or scheme:/(stuff) or scheme://(stuff)
1281 // Strictly speaking, RFC 1808 & 2396 bar "scheme:" (with nothing following the colon); however, common usage
1282 // expects this to be treated identically to "scheme://" - REW, 12/08/03
1283 if (!(flags & HAS_SCHEME)) {
1284 isCompliant = true;
1285 } else if (base_idx == string_length) {
1286 isCompliant = false;
1287 } else if (STRING_CHAR(base_idx) != '/') {
1288 isCompliant = false;
1289 } else {
1290 isCompliant = true;
1291 }
1292
1293 if (!isCompliant) {
1294 // Clear the fragment flag if it's been set
1295 if (flags & HAS_FRAGMENT) {
1296 flags &= (~HAS_FRAGMENT);
1297 string_length = CFStringGetLength(string);
1298 }
1299 (*theFlags) = flags;
1300 (*range) = (CFRange *)CFAllocatorAllocate(alloc, sizeof(CFRange), 0);
1301 (*range)->location = ranges[0].location;
1302 (*range)->length = ranges[0].length;
1303
1304 if (freeCharacters) {
1305 CFAllocatorDeallocate(alloc, useCString ? (void *)cstring : (void *)ustring);
1306 }
1307 return;
1308 }
1309 // URL is 1808-compliant
1310 flags |= IS_DECOMPOSABLE;
1311
1312 // 3: parse the network location and login
1313 if (2 <= (string_length - base_idx) && '/' == STRING_CHAR(base_idx) && '/' == STRING_CHAR(base_idx+1)) {
1314 CFIndex base = 2 + base_idx, extent;
1315 for (idx = base; idx < string_length; idx++) {
1316 if ('/' == STRING_CHAR(idx) || '?' == STRING_CHAR(idx)) break;
1317 }
1318 extent = idx;
1319
1320 // net_loc parts extend from base to extent (but not including), which might be to end of string
1321 // net location is "<user>:<password>@<host>:<port>"
1322 if (extent != base) {
1323 for (idx = base; idx < extent; idx++) {
1324 if ('@' == STRING_CHAR(idx)) { // there is a user
1325 CFIndex idx2;
1326 flags |= HAS_USER;
1327 numRanges ++;
1328 ranges[1].location = base; // base of the user
1329 for (idx2 = base; idx2 < idx; idx2++) {
1330 if (':' == STRING_CHAR(idx2)) { // found a password separator
1331 flags |= HAS_PASSWORD;
1332 numRanges ++;
1333 ranges[2].location = idx2+1; // base of the password
1334 ranges[2].length = idx-(idx2+1); // password extent
1335 ranges[1].length = idx2 - base; // user extent
1336 break;
1337 }
1338 }
1339 if (!(flags & HAS_PASSWORD)) {
1340 // user extends to the '@'
1341 ranges[1].length = idx - base; // user extent
1342 }
1343 base = idx + 1;
1344 break;
1345 }
1346 }
1347 flags |= HAS_HOST;
1348 numRanges ++;
1349 ranges[3].location = base; // base of host
1350
1351 // base has been advanced past the user and password if they existed
1352 for (idx = base; idx < extent; idx++) {
1353 // IPV6 support (RFC 2732) DCJ June/10/2002
1354 if ('[' == STRING_CHAR(idx)) { // starting IPV6 explicit address
1355 // Find the ']' terminator of the IPv6 address, leave idx pointing to ']' or end
1356 for ( ; idx < extent; ++ idx ) {
1357 if ( ']' == STRING_CHAR(idx)) {
1358 flags |= IS_IPV6_ENCODED;
1359 break;
1360 }
1361 }
1362 }
1363 // there is a port if we see a colon. Only the last one is the port, though.
1364 else if ( ':' == STRING_CHAR(idx)) {
1365 flags |= HAS_PORT;
1366 numRanges ++;
1367 ranges[4].location = idx+1; // base of port
1368 ranges[4].length = extent - (idx+1); // port extent
1369 ranges[3].length = idx - base; // host extent
1370 break;
1371 }
1372 }
1373 if (!(flags & HAS_PORT)) {
1374 ranges[3].length = extent - base; // host extent
1375 }
1376 }
1377 base_idx = extent;
1378 }
1379
1380 // 4: parse the query; remainder after left-most "?" is query
1381 for (idx = base_idx; idx < string_length; idx++) {
1382 if ('?' == STRING_CHAR(idx)) {
1383 flags |= HAS_QUERY;
1384 numRanges ++;
1385 ranges[7].location = idx + 1;
1386 ranges[7].length = string_length - (idx+1);
1387 string_length = idx; // remove query from parse string
1388 break;
1389 }
1390 }
1391
1392 // 5: parse the parameters; remainder after left-most ";" is parameters
1393 for (idx = base_idx; idx < string_length; idx++) {
1394 if (';' == STRING_CHAR(idx)) {
1395 flags |= HAS_PARAMETERS;
1396 numRanges ++;
1397 ranges[6].location = idx + 1;
1398 ranges[6].length = string_length - (idx+1);
1399 string_length = idx; // remove parameters from parse string
1400 break;
1401 }
1402 }
1403
1404 // 6: parse the path; it's whatever's left between string_length & base_idx
1405 if (string_length - base_idx != 0 || (flags & NET_LOCATION_MASK))
1406 {
1407 // If we have a net location, we are 1808-compliant, and an empty path substring implies a path of "/"
1408 UniChar ch;
1409 Boolean isDir;
1410 CFRange pathRg;
1411 flags |= HAS_PATH;
1412 numRanges ++;
1413 pathRg.location = base_idx;
1414 pathRg.length = string_length - base_idx;
1415 ranges[5] = pathRg;
1416
1417 if (pathRg.length > 0) {
1418 Boolean sawPercent = FALSE;
1419 for (idx = pathRg.location; idx < string_length; idx++) {
1420 if ('%' == STRING_CHAR(idx)) {
1421 sawPercent = TRUE;
1422 break;
1423 }
1424 }
1425 #if DEPLOYMENT_TARGET_MACOSX
1426 if (pathRg.length > 6 && STRING_CHAR(pathRg.location) == '/' && STRING_CHAR(pathRg.location + 1) == '.' && STRING_CHAR(pathRg.location + 2) == 'f' && STRING_CHAR(pathRg.location + 3) == 'i' && STRING_CHAR(pathRg.location + 4) == 'l' && STRING_CHAR(pathRg.location + 5) == 'e' && STRING_CHAR(pathRg.location + 6) == '/') {
1427 flags |= PATH_HAS_FILE_ID;
1428 } else if (!sawPercent) {
1429 flags |= POSIX_AND_URL_PATHS_MATCH;
1430 }
1431 #elif DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_WINDOWS
1432 if (!sawPercent) {
1433 flags |= POSIX_AND_URL_PATHS_MATCH;
1434 }
1435 #else
1436 #error Unknown or unspecified DEPLOYMENT_TARGET
1437 #endif
1438
1439 ch = STRING_CHAR(pathRg.location + pathRg.length - 1);
1440 if (ch == '/') {
1441 isDir = true;
1442 } else if (ch == '.') {
1443 if (pathRg.length == 1) {
1444 isDir = true;
1445 } else {
1446 ch = STRING_CHAR(pathRg.location + pathRg.length - 2);
1447 if (ch == '/') {
1448 isDir = true;
1449 } else if (ch != '.') {
1450 isDir = false;
1451 } else if (pathRg.length == 2) {
1452 isDir = true;
1453 } else {
1454 isDir = (STRING_CHAR(pathRg.location + pathRg.length - 3) == '/');
1455 }
1456 }
1457 } else {
1458 isDir = false;
1459 }
1460 } else {
1461 isDir = (baseURL != NULL) ? CFURLHasDirectoryPath(baseURL) : false;
1462 }
1463 if (isDir) {
1464 flags |= IS_DIRECTORY;
1465 }
1466 }
1467
1468 if (freeCharacters) {
1469 CFAllocatorDeallocate(alloc, useCString ? (void *)cstring : (void *)ustring);
1470 }
1471 (*theFlags) = flags;
1472 (*range) = (CFRange *)CFAllocatorAllocate(alloc, sizeof(CFRange)*numRanges, 0);
1473 numRanges = 0;
1474 for (idx = 0, flags = 1; flags != (1<<9); flags = (flags<<1), idx ++) {
1475 if ((*theFlags) & flags) {
1476 (*range)[numRanges] = ranges[idx];
1477 numRanges ++;
1478 }
1479 }
1480 }
1481
1482 static Boolean scanCharacters(CFAllocatorRef alloc, CFMutableStringRef *escapedString, UInt32 *flags, const char *cstring, const UniChar *ustring, Boolean useCString, CFIndex base, CFIndex end, CFIndex *mark, UInt32 componentFlag, CFStringEncoding encoding) {
1483 CFIndex idx;
1484 Boolean sawIllegalChar = false;
1485 for (idx = base; idx < end; idx ++) {
1486 Boolean shouldEscape;
1487 UniChar ch = STRING_CHAR(idx);
1488 if (isURLLegalCharacter(ch)) {
1489 if ((componentFlag == HAS_USER || componentFlag == HAS_PASSWORD) && (ch == '/' || ch == '?' || ch == '@')) {
1490 shouldEscape = true;
1491 } else {
1492 shouldEscape = false;
1493 }
1494 } else if (ch == '%' && idx + 2 < end && isHexDigit(STRING_CHAR(idx + 1)) && isHexDigit(STRING_CHAR(idx+2))) {
1495 shouldEscape = false;
1496 } else if (componentFlag == HAS_HOST && ((idx == base && ch == '[') || (idx == end-1 && ch == ']'))) {
1497 shouldEscape = false;
1498 } else {
1499 shouldEscape = true;
1500 }
1501 if (!shouldEscape) continue;
1502
1503 sawIllegalChar = true;
1504 if (componentFlag && flags) {
1505 *flags |= (componentFlag << BIT_SHIFT_FROM_COMPONENT_TO_DIFFERS_FLAG);
1506 }
1507 if (!*escapedString) {
1508 *escapedString = CFStringCreateMutable(alloc, 0);
1509 }
1510 if (useCString) {
1511 CFStringRef tempString = CFStringCreateWithBytes(alloc, (uint8_t *)&(cstring[*mark]), idx - *mark, kCFStringEncodingISOLatin1, false);
1512 CFStringAppend(*escapedString, tempString);
1513 CFRelease(tempString);
1514 } else {
1515 CFStringAppendCharacters(*escapedString, &(ustring[*mark]), idx - *mark);
1516 }
1517 *mark = idx + 1;
1518 _appendPercentEscapesForCharacter(ch, encoding, *escapedString); // This can never fail because anURL->_string was constructed from the encoding passed in
1519 }
1520 return sawIllegalChar;
1521 }
1522
1523 static void computeSanitizedString(CFURLRef url) {
1524 CFAllocatorRef alloc = CFGetAllocator(url);
1525 CFIndex string_length = CFStringGetLength(url->_string);
1526 Boolean useCString, freeCharacters;
1527 const char *cstring = NULL;
1528 const UniChar *ustring = NULL;
1529 CFIndex base; // where to scan from
1530 CFIndex mark; // first character not-yet copied to sanitized string
1531 if (!(url->_flags & IS_PARSED)) {
1532 _parseComponentsOfURL(url);
1533 }
1534 constructBuffers(alloc, url->_string, &cstring, &ustring, &useCString, &freeCharacters);
1535 if (!(url->_flags & IS_DECOMPOSABLE)) {
1536 // Impossible to have a problem character in the scheme
1537 CFMutableStringRef sanitizedString = NULL;
1538 base = _rangeForComponent(url->_flags, url->ranges, HAS_SCHEME).length + 1;
1539 mark = 0;
1540 if (!scanCharacters(alloc, & sanitizedString, &(((struct __CFURL *)url)->_flags), cstring, ustring, useCString, base, string_length, &mark, 0, url->_encoding)) {
1541 ((struct __CFURL *)url)->_flags |= ORIGINAL_AND_URL_STRINGS_MATCH;
1542 }
1543 if ( sanitizedString ) {
1544 _setSanitizedString( (struct __CFURL*) url, sanitizedString );
1545 }
1546 } else {
1547 // Go component by component
1548 CFIndex currentComponent = HAS_USER;
1549 CFMutableStringRef sanitizedString = NULL;
1550 mark = 0;
1551 while (currentComponent < (HAS_FRAGMENT << 1)) {
1552 CFRange componentRange = _rangeForComponent(url->_flags, url->ranges, currentComponent);
1553 if (componentRange.location != kCFNotFound) {
1554 scanCharacters(alloc, & sanitizedString, &(((struct __CFURL *)url)->_flags), cstring, ustring, useCString, componentRange.location, componentRange.location + componentRange.length, &mark, currentComponent, url->_encoding);
1555 }
1556 currentComponent = currentComponent << 1;
1557 }
1558 if (sanitizedString) {
1559 _setSanitizedString((struct __CFURL *)url, sanitizedString);
1560 } else {
1561 ((struct __CFURL *)url)->_flags |= ORIGINAL_AND_URL_STRINGS_MATCH;
1562 }
1563 }
1564 if (_getSanitizedString(url) && mark != string_length) {
1565 if (useCString) {
1566 CFStringRef tempString = CFStringCreateWithBytes(alloc, (uint8_t *)&(cstring[mark]), string_length - mark, kCFStringEncodingISOLatin1, false);
1567 CFStringAppend(_getSanitizedString(url), tempString);
1568 CFRelease(tempString);
1569 } else {
1570 CFStringAppendCharacters(_getSanitizedString(url), &(ustring[mark]), string_length - mark);
1571 }
1572 }
1573 if (freeCharacters) {
1574 CFAllocatorDeallocate(alloc, useCString ? (void *)cstring : (void *)ustring);
1575 }
1576 }
1577
1578
1579 static CFStringRef correctedComponent(CFStringRef comp, UInt32 compFlag, CFStringEncoding enc) {
1580 CFAllocatorRef alloc = CFGetAllocator(comp);
1581 CFIndex string_length = CFStringGetLength(comp);
1582 Boolean useCString, freeCharacters;
1583 const char *cstring = NULL;
1584 const UniChar *ustring = NULL;
1585 CFIndex mark = 0; // first character not-yet copied to sanitized string
1586 CFMutableStringRef result = NULL;
1587
1588 constructBuffers(alloc, comp, &cstring, &ustring, &useCString, &freeCharacters);
1589 scanCharacters(alloc, &result, NULL, cstring, ustring, useCString, 0, string_length, &mark, compFlag, enc);
1590 if (result) {
1591 if (mark < string_length) {
1592 if (useCString) {
1593 CFStringRef tempString = CFStringCreateWithBytes(alloc, (uint8_t *)&(cstring[mark]), string_length - mark, kCFStringEncodingISOLatin1, false);
1594 CFStringAppend(result, tempString);
1595 CFRelease(tempString);
1596 } else {
1597 CFStringAppendCharacters(result, &(ustring[mark]), string_length - mark);
1598 }
1599 }
1600 } else {
1601 // This should nevr happen
1602 CFRetain(comp);
1603 result = (CFMutableStringRef)comp;
1604 }
1605 if (freeCharacters) {
1606 CFAllocatorDeallocate(alloc, useCString ? (void *)cstring : (void *)ustring);
1607 }
1608 return result;
1609 }
1610
1611 #undef STRING_CHAR
1612 CF_EXPORT CFURLRef _CFURLAlloc(CFAllocatorRef allocator) {
1613 struct __CFURL *url;
1614 #if DEBUG_URL_MEMORY_USAGE
1615 numURLs ++;
1616 // if (!URLAllocator) {
1617 // URLAllocator = CFCountingAllocatorCreate(NULL);
1618 // }
1619 allocator = URLAllocator;
1620 #endif
1621 url = (struct __CFURL *)_CFRuntimeCreateInstance(allocator, __kCFURLTypeID, sizeof(struct __CFURL) - sizeof(CFRuntimeBase), NULL);
1622 if (url) {
1623 url->_flags = 0;
1624 if (createOldUTF8StyleURLs()) {
1625 url->_flags |= IS_OLD_UTF8_STYLE;
1626 }
1627 url->_string = NULL;
1628 url->_base = NULL;
1629 url->ranges = NULL;
1630 // url->_reserved = NULL;
1631 url->_encoding = kCFStringEncodingUTF8;
1632 // url->_sanatizedString = NULL;
1633 url->extra = NULL;
1634 }
1635 return url;
1636 }
1637
1638 // It is the caller's responsibility to guarantee that if URLString is absolute, base is NULL. This is necessary to avoid duplicate processing for file system URLs, which had to decide whether to compute the cwd for the base; we don't want to duplicate that work. This ALSO means it's the caller's responsibility to set the IS_ABSOLUTE bit, since we may have a degenerate URL whose string is relative, but lacks a base.
1639 static void _CFURLInit(struct __CFURL *url, CFStringRef URLString, UInt32 fsType, CFURLRef base) {
1640 CFAssert2((fsType == FULL_URL_REPRESENTATION) || (fsType == kCFURLPOSIXPathStyle) || (fsType == kCFURLWindowsPathStyle) || (fsType == kCFURLHFSPathStyle) || ASSERT_CHECK_PATHSTYLE(fsType), __kCFLogAssertion, "%s(): Received bad fsType %d", __PRETTY_FUNCTION__, fsType);
1641
1642 // Coming in, the url has its allocator flag properly set, and its base initialized, and nothing else.
1643 url->_string = (CFStringRef)CFStringCreateCopy(CFGetAllocator(url), URLString);
1644 url->_flags |= (fsType << 16);
1645
1646 url->_base = base ? CFURLCopyAbsoluteURL(base) : NULL;
1647
1648 #if DEBUG_URL_MEMORY_USAGE
1649 if (fsType != FULL_URL_REPRESENTATION) {
1650 numFileURLsCreated ++;
1651 }
1652 if ( url->_base )
1653 numURLsWithBaseURL ++;
1654 #endif
1655 }
1656
1657 #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED
1658 CF_EXPORT void _CFURLInitFSPath(CFURLRef url, CFStringRef path) {
1659 CFIndex len = CFStringGetLength(path);
1660 if (len && CFStringGetCharacterAtIndex(path, 0) == '/') {
1661 _CFURLInit((struct __CFURL *)url, path, kCFURLPOSIXPathStyle, NULL);
1662 ((struct __CFURL *)url)->_flags |= IS_ABSOLUTE;
1663 } else {
1664 CFURLRef cwdURL = _CFURLCreateCurrentDirectoryURL(CFGetAllocator(url));
1665 _CFURLInit((struct __CFURL *)url, path, kCFURLPOSIXPathStyle, cwdURL);
1666 if ( cwdURL )
1667 CFRelease(cwdURL);
1668 }
1669 if (!len || '/' == CFStringGetCharacterAtIndex(path, len - 1))
1670 ((struct __CFURL *)url)->_flags |= IS_DIRECTORY;
1671 }
1672 #elif DEPLOYMENT_TARGET_WINDOWS
1673 CF_EXPORT void _CFURLInitFSPath(CFURLRef url, CFStringRef path) {
1674 CFIndex len = CFStringGetLength(path);
1675 UniChar firstChar = 0 < len ? CFStringGetCharacterAtIndex(path, 0) : 0;
1676 UniChar secondChar = 1 < len ? CFStringGetCharacterAtIndex(path, 1) : 0;
1677 Boolean isDrive = ('A' <= firstChar && firstChar <= 'Z') || ('a' <= firstChar && firstChar <= 'z');
1678 isDrive = isDrive && (secondChar == ':' || secondChar == '|');
1679 if (isDrive || (firstChar == '\\' && secondChar == '\\')) {
1680 _CFURLInit((struct __CFURL *)url, path, kCFURLWindowsPathStyle, NULL);
1681 ((struct __CFURL *)url)->_flags |= IS_ABSOLUTE;
1682 } else if (firstChar == '/') {
1683 _CFURLInit((struct __CFURL *)url, path, kCFURLPOSIXPathStyle, NULL);
1684 ((struct __CFURL *)url)->_flags |= IS_ABSOLUTE;
1685 } else {
1686 CFURLRef cwdURL = _CFURLCreateCurrentDirectoryURL(CFGetAllocator(url));
1687 _CFURLInit((struct __CFURL *)url, path, kCFURLPOSIXPathStyle, cwdURL);
1688 if ( cwdURL )
1689 CFRelease(cwdURL);
1690 }
1691 if (!len || '/' == CFStringGetCharacterAtIndex(path, len - 1))
1692 ((struct __CFURL *)url)->_flags |= IS_DIRECTORY;
1693 }
1694 #else
1695 #error Unknown or unspecified DEPLOYMENT_TARGET
1696 #endif
1697
1698 // Exported for Foundation's use
1699 CF_EXPORT Boolean _CFStringIsLegalURLString(CFStringRef string) {
1700 // Check each character to make sure it is a legal URL char. The valid characters are 'A'-'Z', 'a' - 'z', '0' - '9', plus the characters in "-_.!~*'()", and the set of reserved characters (these characters have special meanings in the URL syntax), which are ";/?:@&=+$,". In addition, percent escape sequences '%' hex-digit hex-digit are permitted.
1701 // Plus the hash character '#' which denotes the beginning of a fragment, and can appear exactly once in the entire URL string. -- REW, 12/13/2000
1702 CFStringInlineBuffer stringBuffer;
1703 CFIndex idx = 0, length;
1704 Boolean sawHash = false;
1705 if (!string) {
1706 CFAssert(false, __kCFLogAssertion, "Cannot create an CFURL from a NULL string");
1707 return false;
1708 }
1709 length = CFStringGetLength(string);
1710 CFStringInitInlineBuffer(string, &stringBuffer, CFRangeMake(0, length));
1711 while (idx < length) {
1712 UniChar ch = CFStringGetCharacterFromInlineBuffer(&stringBuffer, idx);
1713 idx ++;
1714
1715 // Make sure that two valid hex digits follow a '%' character
1716 if ( ch == '%' ) {
1717 if ( idx + 2 > length )
1718 {
1719 //CFAssert1(false, __kCFLogAssertion, "Detected illegal percent escape sequence at character %d when trying to create a CFURL", idx-1);
1720 idx = -1; // To guarantee index < length, and our failure case is triggered
1721 break;
1722 }
1723
1724 ch = CFStringGetCharacterFromInlineBuffer(&stringBuffer, idx);
1725 idx ++;
1726 if (! isHexDigit(ch) ) {
1727 //CFAssert1(false, __kCFLogAssertion, "Detected illegal percent escape sequence at character %d when trying to create a CFURL", idx-2);
1728 idx = -1;
1729 break;
1730 }
1731 ch = CFStringGetCharacterFromInlineBuffer(&stringBuffer, idx);
1732 idx ++;
1733 if (! isHexDigit(ch) ) {
1734 //CFAssert1(false, __kCFLogAssertion, "Detected illegal percent escape sequence at character %d when trying to create a CFURL", idx-3);
1735 idx = -1;
1736 break;
1737 }
1738
1739 continue;
1740 }
1741 if (ch == '[' || ch == ']') continue; // IPV6 support (RFC 2732) DCJ June/10/2002
1742 if (ch == '#') {
1743 if (sawHash) break;
1744 sawHash = true;
1745 continue;
1746 }
1747 if ( isURLLegalCharacter( ch ) )
1748 continue;
1749 break;
1750 }
1751 if (idx < length) {
1752 return false;
1753 }
1754 return true;
1755 }
1756
1757 CF_EXPORT void _CFURLInitWithString(CFURLRef myURL, CFStringRef string, CFURLRef baseURL) {
1758 struct __CFURL *url = (struct __CFURL *)myURL; // Supress annoying compile warnings
1759 Boolean isAbsolute = false;
1760 CFRange colon = CFStringFind(string, CFSTR(":"), 0);
1761 if (colon.location != kCFNotFound) {
1762 isAbsolute = true;
1763 CFIndex i;
1764 for (i = 0; i < colon.location; i++) {
1765 char ch = (char)CFStringGetCharacterAtIndex(string, i);
1766 if (!scheme_valid(ch)) {
1767 isAbsolute = false;
1768 break;
1769 }
1770 }
1771 }
1772 _CFURLInit(url, string, FULL_URL_REPRESENTATION, isAbsolute ? NULL : baseURL);
1773 if (isAbsolute) {
1774 url->_flags |= IS_ABSOLUTE;
1775 }
1776 }
1777
1778 struct __CFURLEncodingTranslationParameters {
1779 CFStringEncoding fromEnc;
1780 CFStringEncoding toEnc;
1781 const UniChar *addlChars;
1782 int count;
1783 Boolean escapeHighBit;
1784 Boolean escapePercents;
1785 Boolean agreesOverASCII;
1786 Boolean encodingsMatch;
1787 } ;
1788
1789 static Boolean _shouldEscapeForEncodingConversion(UniChar ch, void *context) {
1790 struct __CFURLEncodingTranslationParameters *info = (struct __CFURLEncodingTranslationParameters *)context;
1791 if (info->escapeHighBit && ch > 0x7F) {
1792 return true;
1793 } else if (ch == '%' && info->escapePercents) {
1794 return true;
1795 } else if (info->addlChars) {
1796 const UniChar *escChar = info->addlChars;
1797 int i;
1798 for (i = 0; i < info->count; escChar ++, i ++) {
1799 if (*escChar == ch) {
1800 return true;
1801 }
1802 }
1803 }
1804 return false;
1805 }
1806
1807 static CFIndex _convertEscapeSequence(CFIndex percentIndex, CFStringRef urlString, CFStringRef *newString, void *context) {
1808 struct __CFURLEncodingTranslationParameters *info = (struct __CFURLEncodingTranslationParameters *)context;
1809 CFMutableDataRef newData;
1810 Boolean sawNonASCIICharacter = false;
1811 CFIndex i = percentIndex;
1812 CFIndex length;
1813 *newString = NULL;
1814 if (info->encodingsMatch) return percentIndex + 3; // +3 because we want the two characters of the percent encoding to not be copied verbatim, as well
1815 newData = CFDataCreateMutable(CFGetAllocator(urlString), 0);
1816 length = CFStringGetLength(urlString);
1817
1818 while (i < length && CFStringGetCharacterAtIndex(urlString, i) == '%') {
1819 uint8_t byte;
1820 if (i+2 >= length || !_translateBytes(CFStringGetCharacterAtIndex(urlString, i+1), CFStringGetCharacterAtIndex(urlString, i+2), &byte)) {
1821 CFRelease(newData);
1822 return -1;
1823 }
1824 if (byte > 0x7f) sawNonASCIICharacter = true;
1825 CFDataAppendBytes(newData, &byte, 1);
1826 i += 3;
1827 }
1828 if (!sawNonASCIICharacter && info->agreesOverASCII) {
1829 return i;
1830 } else {
1831 CFStringRef tmp = CFStringCreateWithBytes(CFGetAllocator(urlString), CFDataGetBytePtr(newData), CFDataGetLength(newData), info->fromEnc, false);
1832 CFIndex tmpIndex, tmpLen;
1833 if (!tmp) {
1834 CFRelease(newData);
1835 return -1;
1836 }
1837 tmpLen = CFStringGetLength(tmp);
1838 *newString = CFStringCreateMutable(CFGetAllocator(urlString), 0);
1839 for (tmpIndex = 0; tmpIndex < tmpLen; tmpIndex ++) {
1840 if (!_appendPercentEscapesForCharacter(CFStringGetCharacterAtIndex(tmp, tmpIndex), info->toEnc, (CFMutableStringRef)(*newString))) {
1841 break;
1842 }
1843 }
1844 CFRelease(tmp);
1845 CFRelease(newData);
1846 if (tmpIndex < tmpLen) {
1847 CFRelease(*newString);
1848 *newString = NULL;
1849 return -1;
1850 } else {
1851 return i;
1852 }
1853 }
1854 }
1855
1856 /* Returned string is retained for the caller; if escapePercents is true, then we do not look for any %-escape encodings in urlString */
1857 static CFStringRef _convertPercentEscapes(CFStringRef urlString, CFStringEncoding fromEncoding, CFStringEncoding toEncoding, Boolean escapeAllHighBitCharacters, Boolean escapePercents, const UniChar *addlCharsToEscape, int numAddlChars) {
1858 struct __CFURLEncodingTranslationParameters context;
1859 context.fromEnc = fromEncoding;
1860 context.toEnc = toEncoding;
1861 context.addlChars = addlCharsToEscape;
1862 context.count = numAddlChars;
1863 context.escapeHighBit = escapeAllHighBitCharacters;
1864 context.escapePercents = escapePercents;
1865 context.agreesOverASCII = (__CFStringEncodingIsSupersetOfASCII(toEncoding) && __CFStringEncodingIsSupersetOfASCII(fromEncoding)) ? true : false;
1866 context.encodingsMatch = (fromEncoding == toEncoding) ? true : false;
1867 return _addPercentEscapesToString(CFGetAllocator(urlString), urlString, _shouldEscapeForEncodingConversion, _convertEscapeSequence, toEncoding, &context);
1868 }
1869
1870 // encoding will be used both to interpret the bytes of URLBytes, and to interpret any percent-escapes within the bytes.
1871 CFURLRef CFURLCreateWithBytes(CFAllocatorRef allocator, const uint8_t *URLBytes, CFIndex length, CFStringEncoding encoding, CFURLRef baseURL) {
1872 CFStringRef urlString = CFStringCreateWithBytes(allocator, URLBytes, length, encoding, false);
1873 CFURLRef result;
1874 if (!urlString || CFStringGetLength(urlString) == 0) {
1875 if (urlString) CFRelease(urlString);
1876 return NULL;
1877 }
1878 if (createOldUTF8StyleURLs()) {
1879 if (encoding != kCFStringEncodingUTF8) {
1880 CFStringRef tmp = _convertPercentEscapes(urlString, encoding, kCFStringEncodingUTF8, false, false, NULL, 0);
1881 CFRelease(urlString);
1882 urlString = tmp;
1883 if (!urlString) return NULL;
1884 }
1885 }
1886
1887 result = _CFURLAlloc(allocator);
1888 if (result) {
1889 _CFURLInitWithString(result, urlString, baseURL);
1890 if (encoding != kCFStringEncodingUTF8 && !createOldUTF8StyleURLs()) {
1891 ((struct __CFURL *)result)->_encoding = encoding;
1892 #if DEBUG_URL_MEMORY_USAGE
1893 if ( encoding != kCFStringEncodingUTF8 ) {
1894 numNonUTF8EncodedURLs++;
1895 }
1896 #endif
1897 }
1898 }
1899 CFRelease(urlString); // it's retained by result, now.
1900 return result;
1901 }
1902
1903 CFDataRef CFURLCreateData(CFAllocatorRef allocator, CFURLRef url, CFStringEncoding encoding, Boolean escapeWhitespace) {
1904 static const UniChar whitespaceChars[4] = {' ', '\n', '\r', '\t'};
1905 CFStringRef myStr = CFURLGetString(url);
1906 CFStringRef newStr;
1907 CFDataRef result;
1908 if (url->_flags & IS_OLD_UTF8_STYLE) {
1909 newStr = (encoding == kCFStringEncodingUTF8) ? (CFStringRef)CFRetain(myStr) : _convertPercentEscapes(myStr, kCFStringEncodingUTF8, encoding, true, false, escapeWhitespace ? whitespaceChars : NULL, escapeWhitespace ? 4 : 0);
1910 } else {
1911 newStr=myStr;
1912 CFRetain(newStr);
1913 }
1914 result = CFStringCreateExternalRepresentation(allocator, newStr, encoding, 0);
1915 CFRelease(newStr);
1916 return result;
1917 }
1918
1919 // Any escape sequences in URLString will be interpreted via UTF-8.
1920 CFURLRef CFURLCreateWithString(CFAllocatorRef allocator, CFStringRef URLString, CFURLRef baseURL) {
1921 CFURLRef url;
1922 if (!URLString || CFStringGetLength(URLString) == 0) return NULL;
1923 if (!_CFStringIsLegalURLString(URLString)) return NULL;
1924 url = _CFURLAlloc(allocator);
1925 if (url) {
1926 _CFURLInitWithString(url, URLString, baseURL);
1927 }
1928 return url;
1929 }
1930
1931 static CFURLRef _CFURLCreateWithArbitraryString(CFAllocatorRef allocator, CFStringRef URLString, CFURLRef baseURL) {
1932 CFURLRef url;
1933 if (!URLString || CFStringGetLength(URLString) == 0) return NULL;
1934 url = _CFURLAlloc(allocator);
1935 if (url) {
1936 _CFURLInitWithString(url, URLString, baseURL);
1937 }
1938 return url;
1939 }
1940
1941 CFURLRef CFURLCreateAbsoluteURLWithBytes(CFAllocatorRef alloc, const UInt8 *relativeURLBytes, CFIndex length, CFStringEncoding encoding, CFURLRef baseURL, Boolean useCompatibilityMode) {
1942 CFStringRef relativeString = CFStringCreateWithBytes(alloc, relativeURLBytes, length, encoding, false);
1943 if (!relativeString) {
1944 return NULL;
1945 }
1946 if (!useCompatibilityMode) {
1947 CFURLRef url = _CFURLCreateWithArbitraryString(alloc, relativeString, baseURL);
1948 CFRelease(relativeString);
1949 if (url) {
1950 ((struct __CFURL *)url)->_encoding = encoding;
1951 CFURLRef absURL = CFURLCopyAbsoluteURL(url);
1952 #if DEBUG_URL_MEMORY_USAGE
1953 if ( encoding != kCFStringEncodingUTF8 ) {
1954 numNonUTF8EncodedURLs++;
1955 }
1956 #endif
1957 CFRelease(url);
1958 return absURL;
1959 } else {
1960 return NULL;
1961 }
1962 } else {
1963 UInt32 absFlags = 0;
1964 CFRange *absRanges;
1965 CFStringRef absString = NULL;
1966 Boolean absStringIsMutable = false;
1967 CFURLRef absURL;
1968 if (!baseURL) {
1969 absString = relativeString;
1970 } else {
1971 UniChar ch = CFStringGetCharacterAtIndex(relativeString, 0);
1972 if (ch == '?' || ch == ';' || ch == '#') {
1973 // Nothing but parameter + query + fragment; append to the baseURL string
1974 CFStringRef baseString;
1975 if (CF_IS_OBJC(__kCFURLTypeID, baseURL)) {
1976 baseString = CFURLGetString(baseURL);
1977 } else {
1978 baseString = baseURL->_string;
1979 }
1980 absString = CFStringCreateMutable(alloc, CFStringGetLength(baseString) + CFStringGetLength(relativeString));
1981 CFStringAppend((CFMutableStringRef)absString, baseString);
1982 CFStringAppend((CFMutableStringRef)absString, relativeString);
1983 absStringIsMutable = true;
1984 } else {
1985 UInt32 relFlags = 0;
1986 CFRange *relRanges;
1987 CFStringRef relString = NULL;
1988 _parseComponents(alloc, relativeString, baseURL, &relFlags, &relRanges);
1989 if (relFlags & HAS_SCHEME) {
1990 CFStringRef baseScheme = CFURLCopyScheme(baseURL);
1991 CFRange relSchemeRange = _rangeForComponent(relFlags, relRanges, HAS_SCHEME);
1992 if (baseScheme && CFStringGetLength(baseScheme) == relSchemeRange.length && CFStringHasPrefix(relativeString, baseScheme)) {
1993 relString = CFStringCreateWithSubstring(alloc, relativeString, CFRangeMake(relSchemeRange.length+1, CFStringGetLength(relativeString) - relSchemeRange.length - 1));
1994 CFAllocatorDeallocate(alloc, relRanges);
1995 relFlags = 0;
1996 _parseComponents(alloc, relString, baseURL, &relFlags, &relRanges);
1997 } else {
1998 // Discard the base string; the relative string is absolute and we're not in the funky edge case where the schemes match
1999 CFRetain(relativeString);
2000 absString = relativeString;
2001 }
2002 if (baseScheme) CFRelease(baseScheme);
2003 } else {
2004 CFRetain(relativeString);
2005 relString = relativeString;
2006 }
2007 if (!absString) {
2008 if (!CF_IS_OBJC(__kCFURLTypeID, baseURL)) {
2009 if (!(baseURL->_flags & IS_PARSED)) {
2010 _parseComponentsOfURL(baseURL);
2011 }
2012 absString = resolveAbsoluteURLString(alloc, relString, relFlags, relRanges, baseURL->_string, baseURL->_flags, baseURL->ranges);
2013 } else {
2014 CFStringRef baseString;
2015 UInt32 baseFlags = 0;
2016 CFRange *baseRanges;
2017 if (CF_IS_OBJC(__kCFURLTypeID, baseURL)) {
2018 baseString = CFURLGetString(baseURL);
2019 } else {
2020 baseString = baseURL->_string;
2021 }
2022 _parseComponents(alloc, baseString, NULL, &baseFlags, &baseRanges);
2023 absString = resolveAbsoluteURLString(alloc, relString, relFlags, relRanges, baseString, baseFlags, baseRanges);
2024 CFAllocatorDeallocate(alloc, baseRanges);
2025 }
2026 absStringIsMutable = true;
2027 }
2028 if (relString) CFRelease(relString);
2029 CFAllocatorDeallocate(alloc, relRanges);
2030 }
2031 CFRelease(relativeString);
2032 }
2033 _parseComponents(alloc, absString, NULL, &absFlags, &absRanges);
2034 if (absFlags & HAS_PATH) {
2035 CFRange pathRg = _rangeForComponent(absFlags, absRanges, HAS_PATH);
2036 // This is expensive, but it allows us to reuse _resolvedPath. It should be cleaned up to get this allocation removed at some point. - REW
2037 UniChar *buf = (UniChar *)CFAllocatorAllocate(alloc, sizeof(UniChar) * (pathRg.length + 1), 0);
2038 CFStringRef newPath;
2039 CFStringGetCharacters(absString, pathRg, buf);
2040 buf[pathRg.length] = '\0';
2041 newPath = _resolvedPath(buf, buf + pathRg.length, '/', true, false, alloc);
2042 if (CFStringGetLength(newPath) != pathRg.length) {
2043 if (!absStringIsMutable) {
2044 CFStringRef tmp = CFStringCreateMutableCopy(alloc, CFStringGetLength(absString), absString);
2045 CFRelease(absString);
2046 absString = tmp;
2047 }
2048 CFStringReplace((CFMutableStringRef)absString, pathRg, newPath);
2049 }
2050 CFRelease(newPath);
2051 // Do not deallocate buf; newPath took ownership of it.
2052 }
2053 CFAllocatorDeallocate(alloc, absRanges);
2054 absURL = _CFURLCreateWithArbitraryString(alloc, absString, NULL);
2055 CFRelease(absString);
2056 if (absURL) {
2057 ((struct __CFURL *)absURL)->_encoding = encoding;
2058 #if DEBUG_URL_MEMORY_USAGE
2059 if ( encoding != kCFStringEncodingUTF8 ) {
2060 numNonUTF8EncodedURLs++;
2061 }
2062 #endif
2063 }
2064 return absURL;
2065 }
2066 }
2067
2068 /* This function is this way because I pulled it out of _resolvedURLPath (so that _resolvedFileSystemPath could use it), and I didn't want to spend a bunch of energy reworking the code. So instead of being a bit more intelligent about inputs, it just demands a slightly perverse set of parameters, to match the old _resolvedURLPath code. -- REW, 6/14/99 */
2069 static CFStringRef _resolvedPath(UniChar *pathStr, UniChar *end, UniChar pathDelimiter, Boolean stripLeadingDotDots, Boolean stripTrailingDelimiter, CFAllocatorRef alloc) {
2070 UniChar *idx = pathStr;
2071 while (idx < end) {
2072 if (*idx == '.') {
2073 if (idx+1 == end) {
2074 if (idx != pathStr) {
2075 *idx = '\0';
2076 end = idx;
2077 }
2078 break;
2079 } else if (*(idx+1) == pathDelimiter) {
2080 if (idx + 2 != end || idx != pathStr) {
2081 memmove(idx, idx+2, (end-(idx+2)+1) * sizeof(UniChar));
2082 end -= 2;
2083 continue;
2084 } else {
2085 // Do not delete the sole path component
2086 break;
2087 }
2088 } else if (( end-idx >= 2 ) && *(idx+1) == '.' && (idx+2 == end || (( end-idx > 2 ) && *(idx+2) == pathDelimiter))) {
2089 if (idx - pathStr >= 2) {
2090 // Need at least 2 characters between index and pathStr, because we know if index != newPath, then *(index-1) == pathDelimiter, and we need something before that to compact out.
2091 UniChar *lastDelim = idx-2;
2092 while (lastDelim >= pathStr && *lastDelim != pathDelimiter) lastDelim --;
2093 lastDelim ++;
2094 if (lastDelim != idx && (idx-lastDelim != 3 || *lastDelim != '.' || *(lastDelim +1) != '.')) {
2095 // We have a genuine component to compact out
2096 if (idx+2 != end) {
2097 unsigned numCharsToMove = end - (idx+3) + 1; // +1 to move the '\0' as well
2098 memmove(lastDelim, idx+3, numCharsToMove * sizeof(UniChar));
2099 end -= (idx + 3 - lastDelim);
2100 idx = lastDelim;
2101 continue;
2102 } else if (lastDelim != pathStr) {
2103 *lastDelim = '\0';
2104 end = lastDelim;
2105 break;
2106 } else {
2107 // Don't allow the path string to devolve to the empty string. Fall back to "." instead. - REW
2108 pathStr[0] = '.';
2109 pathStr[1] = '/';
2110 pathStr[2] = '\0';
2111 end = & pathStr[3];
2112 break;
2113 }
2114 }
2115 } else if (stripLeadingDotDots) {
2116 if (idx + 3 != end) {
2117 unsigned numCharsToMove = end - (idx + 3) + 1;
2118 memmove(idx, idx+3, numCharsToMove * sizeof(UniChar));
2119 end -= 3;
2120 continue;
2121 } else {
2122 // Do not devolve the last path component
2123 break;
2124 }
2125 }
2126 }
2127 }
2128 while (idx < end && *idx != pathDelimiter) idx ++;
2129 idx ++;
2130 }
2131 if (stripTrailingDelimiter && end > pathStr && end-1 != pathStr && *(end-1) == pathDelimiter) {
2132 end --;
2133 }
2134 return CFStringCreateWithCharactersNoCopy(alloc, pathStr, end - pathStr, alloc);
2135 }
2136
2137 static CFMutableStringRef resolveAbsoluteURLString(CFAllocatorRef alloc, CFStringRef relString, UInt32 relFlags, CFRange *relRanges, CFStringRef baseString, UInt32 baseFlags, CFRange *baseRanges) {
2138 CFMutableStringRef newString = CFStringCreateMutable(alloc, 0);
2139 CFIndex bufLen = CFStringGetLength(baseString) + CFStringGetLength(relString); // Overkill, but guarantees we never allocate again
2140 UniChar *buf = (UniChar *)CFAllocatorAllocate(alloc, bufLen * sizeof(UniChar), 0);
2141 CFRange rg;
2142
2143 rg = _rangeForComponent(baseFlags, baseRanges, HAS_SCHEME);
2144 if (rg.location != kCFNotFound) {
2145 CFStringGetCharacters(baseString, rg, buf);
2146 CFStringAppendCharacters(newString, buf, rg.length);
2147 CFStringAppendCString(newString, ":", kCFStringEncodingASCII);
2148 }
2149
2150 if (relFlags & NET_LOCATION_MASK) {
2151 CFStringAppend(newString, relString);
2152 } else {
2153 CFStringAppendCString(newString, "//", kCFStringEncodingASCII);
2154 rg = _netLocationRange(baseFlags, baseRanges);
2155 if (rg.location != kCFNotFound) {
2156 CFStringGetCharacters(baseString, rg, buf);
2157 CFStringAppendCharacters(newString, buf, rg.length);
2158 }
2159
2160 if (relFlags & HAS_PATH) {
2161 CFRange relPathRg = _rangeForComponent(relFlags, relRanges, HAS_PATH);
2162 CFRange basePathRg = _rangeForComponent(baseFlags, baseRanges, HAS_PATH);
2163 CFStringRef newPath;
2164 Boolean useRelPath = false;
2165 Boolean useBasePath = false;
2166 if (basePathRg.location == kCFNotFound) {
2167 useRelPath = true;
2168 } else if (relPathRg.length == 0) {
2169 useBasePath = true;
2170 } else if (CFStringGetCharacterAtIndex(relString, relPathRg.location) == '/') {
2171 useRelPath = true;
2172 } else if (basePathRg.location == kCFNotFound || basePathRg.length == 0) {
2173 useRelPath = true;
2174 }
2175 if (useRelPath) {
2176 newPath = CFStringCreateWithSubstring(alloc, relString, relPathRg);
2177 } else if (useBasePath) {
2178 newPath = CFStringCreateWithSubstring(alloc, baseString, basePathRg);
2179 } else {
2180 // #warning FIXME - Get rid of this allocation
2181 UniChar *newPathBuf = (UniChar *)CFAllocatorAllocate(alloc, sizeof(UniChar) * (relPathRg.length + basePathRg.length + 1), 0);
2182 UniChar *idx, *end;
2183 CFStringGetCharacters(baseString, basePathRg, newPathBuf);
2184 idx = newPathBuf + basePathRg.length - 1;
2185 while (idx != newPathBuf && *idx != '/') idx --;
2186 if (*idx == '/') idx ++;
2187 CFStringGetCharacters(relString, relPathRg, idx);
2188 end = idx + relPathRg.length;
2189 *end = 0;
2190 newPath = _resolvedPath(newPathBuf, end, '/', false, false, alloc);
2191 }
2192 /* Under Win32 absolute path can begin with letter
2193 * so we have to add one '/' to the newString
2194 * (Sergey Zubarev)
2195 */
2196 // No - the input strings here are URL path strings, not Win32 paths.
2197 // Absolute paths should have had a '/' prepended before this point.
2198 // I have removed Sergey Zubarev's change and left his comment (and
2199 // this one) as a record. - REW, 1/5/2004
2200
2201 // if the relative URL does not begin with a slash and
2202 // the base does not end with a slash, add a slash
2203 if ((basePathRg.location == kCFNotFound || basePathRg.length == 0) && CFStringGetCharacterAtIndex(newPath, 0) != '/') {
2204 CFStringAppendCString(newString, "/", kCFStringEncodingASCII);
2205 }
2206
2207 CFStringAppend(newString, newPath);
2208 CFRelease(newPath);
2209 rg.location = relPathRg.location + relPathRg.length;
2210 rg.length = CFStringGetLength(relString);
2211 if (rg.length > rg.location) {
2212 rg.length -= rg.location;
2213 CFStringGetCharacters(relString, rg, buf);
2214 CFStringAppendCharacters(newString, buf, rg.length);
2215 }
2216 } else {
2217 rg = _rangeForComponent(baseFlags, baseRanges, HAS_PATH);
2218 if (rg.location != kCFNotFound) {
2219 CFStringGetCharacters(baseString, rg, buf);
2220 CFStringAppendCharacters(newString, buf, rg.length);
2221 }
2222
2223 if (!(relFlags & RESOURCE_SPECIFIER_MASK)) {
2224 // ??? Can this ever happen?
2225 UInt32 rsrcFlag = _firstResourceSpecifierFlag(baseFlags);
2226 if (rsrcFlag) {
2227 rg.location = _rangeForComponent(baseFlags, baseRanges, rsrcFlag).location;
2228 rg.length = CFStringGetLength(baseString) - rg.location;
2229 rg.location --; // To pick up the separator
2230 rg.length ++;
2231 CFStringGetCharacters(baseString, rg, buf);
2232 CFStringAppendCharacters(newString, buf, rg.length);
2233 }
2234 } else if (relFlags & HAS_PARAMETERS) {
2235 rg = _rangeForComponent(relFlags, relRanges, HAS_PARAMETERS);
2236 rg.location --; // To get the semicolon that starts the parameters
2237 rg.length = CFStringGetLength(relString) - rg.location;
2238 CFStringGetCharacters(relString, rg, buf);
2239 CFStringAppendCharacters(newString, buf, rg.length);
2240 } else {
2241 // Sigh; we have to resolve these against one another
2242 rg = _rangeForComponent(baseFlags, baseRanges, HAS_PARAMETERS);
2243 if (rg.location != kCFNotFound) {
2244 CFStringAppendCString(newString, ";", kCFStringEncodingASCII);
2245 CFStringGetCharacters(baseString, rg, buf);
2246 CFStringAppendCharacters(newString, buf, rg.length);
2247 }
2248 rg = _rangeForComponent(relFlags, relRanges, HAS_QUERY);
2249 if (rg.location != kCFNotFound) {
2250 CFStringAppendCString(newString, "?", kCFStringEncodingASCII);
2251 CFStringGetCharacters(relString, rg, buf);
2252 CFStringAppendCharacters(newString, buf, rg.length);
2253 } else {
2254 rg = _rangeForComponent(baseFlags, baseRanges, HAS_QUERY);
2255 if (rg.location != kCFNotFound) {
2256 CFStringAppendCString(newString, "?", kCFStringEncodingASCII);
2257 CFStringGetCharacters(baseString, rg, buf);
2258 CFStringAppendCharacters(newString, buf, rg.length);
2259 }
2260 }
2261 // Only the relative portion of the URL can supply the fragment; otherwise, what would be in the relativeURL?
2262 rg = _rangeForComponent(relFlags, relRanges, HAS_FRAGMENT);
2263 if (rg.location != kCFNotFound) {
2264 CFStringAppendCString(newString, "#", kCFStringEncodingASCII);
2265 CFStringGetCharacters(relString, rg, buf);
2266 CFStringAppendCharacters(newString, buf, rg.length);
2267 }
2268 }
2269 }
2270 }
2271 CFAllocatorDeallocate(alloc, buf);
2272 return newString;
2273 }
2274
2275 CFURLRef CFURLCopyAbsoluteURL(CFURLRef relativeURL) {
2276 CFURLRef anURL, base;
2277 CFURLPathStyle fsType;
2278 CFAllocatorRef alloc = CFGetAllocator(relativeURL);
2279 CFStringRef baseString, newString;
2280 UInt32 baseFlags;
2281 CFRange *baseRanges;
2282 Boolean baseIsObjC;
2283
2284 CFAssert1(relativeURL != NULL, __kCFLogAssertion, "%s(): Cannot create an absolute URL from a NULL relative URL", __PRETTY_FUNCTION__);
2285 if (CF_IS_OBJC(__kCFURLTypeID, relativeURL)) {
2286 CF_OBJC_CALL0(CFURLRef, anURL, relativeURL, "absoluteURL");
2287 if (anURL) CFRetain(anURL);
2288 return anURL;
2289 }
2290
2291 __CFGenericValidateType(relativeURL, __kCFURLTypeID);
2292
2293 base = relativeURL->_base;
2294 if (!base) {
2295 return (CFURLRef)CFRetain(relativeURL);
2296 }
2297 baseIsObjC = CF_IS_OBJC(__kCFURLTypeID, base);
2298 fsType = URL_PATH_TYPE(relativeURL);
2299
2300 if (!baseIsObjC && fsType != FULL_URL_REPRESENTATION && fsType == URL_PATH_TYPE(base)) {
2301 return _CFURLCopyAbsoluteFileURL(relativeURL);
2302 }
2303 if (fsType != FULL_URL_REPRESENTATION) {
2304 _convertToURLRepresentation((struct __CFURL *)relativeURL);
2305 fsType = FULL_URL_REPRESENTATION;
2306 }
2307 if (!(relativeURL->_flags & IS_PARSED)) {
2308 _parseComponentsOfURL(relativeURL);
2309 }
2310 if ((relativeURL->_flags & POSIX_AND_URL_PATHS_MATCH) && !(relativeURL->_flags & (RESOURCE_SPECIFIER_MASK | NET_LOCATION_MASK)) && !baseIsObjC && (URL_PATH_TYPE(base) == kCFURLPOSIXPathStyle)) {
2311 // There's nothing to relativeURL's string except the path
2312 CFStringRef newPath = _resolveFileSystemPaths(relativeURL->_string, base->_string, CFURLHasDirectoryPath(base), kCFURLPOSIXPathStyle, alloc);
2313 CFURLRef result = CFURLCreateWithFileSystemPath(alloc, newPath, kCFURLPOSIXPathStyle, CFURLHasDirectoryPath(relativeURL));
2314 CFRelease(newPath);
2315 return result;
2316 }
2317
2318 if (!baseIsObjC) {
2319 CFURLPathStyle baseType = URL_PATH_TYPE(base);
2320 if (baseType != FULL_URL_REPRESENTATION) {
2321 _convertToURLRepresentation((struct __CFURL *)base);
2322 } else if (!(base->_flags & IS_PARSED)) {
2323 _parseComponentsOfURL(base);
2324 }
2325 baseString = base->_string;
2326 baseFlags = base->_flags;
2327 baseRanges = base->ranges;
2328 } else {
2329 baseString = CFURLGetString(base);
2330 baseFlags = 0;
2331 baseRanges = NULL;
2332 _parseComponents(alloc, baseString, NULL, &baseFlags, &baseRanges);
2333 }
2334
2335 newString = resolveAbsoluteURLString(alloc, relativeURL->_string, relativeURL->_flags, relativeURL->ranges, baseString, baseFlags, baseRanges);
2336 if (baseIsObjC) {
2337 CFAllocatorDeallocate(alloc, baseRanges);
2338 }
2339 anURL = _CFURLCreateWithArbitraryString(alloc, newString, NULL);
2340 CFRelease(newString);
2341 ((struct __CFURL *)anURL)->_encoding = relativeURL->_encoding;
2342 #if DEBUG_URL_MEMORY_USAGE
2343 if ( relativeURL->_encoding != kCFStringEncodingUTF8 ) {
2344 numNonUTF8EncodedURLs++;
2345 }
2346 #endif
2347 return anURL;
2348 }
2349
2350
2351 /*******************/
2352 /* Basic accessors */
2353 /*******************/
2354 CFStringEncoding _CFURLGetEncoding(CFURLRef url) {
2355 return url->_encoding;
2356 }
2357
2358 Boolean CFURLCanBeDecomposed(CFURLRef anURL) {
2359 anURL = _CFURLFromNSURL(anURL);
2360 if (URL_PATH_TYPE(anURL) != FULL_URL_REPRESENTATION) return true;
2361 if (!(anURL->_flags & IS_PARSED)) {
2362 _parseComponentsOfURL(anURL);
2363 }
2364 return ((anURL->_flags & IS_DECOMPOSABLE) != 0);
2365 }
2366
2367 CFStringRef CFURLGetString(CFURLRef url) {
2368 CF_OBJC_FUNCDISPATCH0(__kCFURLTypeID, CFStringRef , url, "relativeString");
2369 if (URL_PATH_TYPE(url) != FULL_URL_REPRESENTATION) {
2370 if (url->_base && (url->_flags & POSIX_AND_URL_PATHS_MATCH)) {
2371 return url->_string;
2372 }
2373 _convertToURLRepresentation((struct __CFURL *)url);
2374 }
2375 if (!_haveTestedOriginalString(url)) {
2376 computeSanitizedString(url);
2377 }
2378 if (url->_flags & ORIGINAL_AND_URL_STRINGS_MATCH) {
2379 return url->_string;
2380 } else {
2381 return _getSanitizedString( url );
2382 }
2383 }
2384
2385 CFIndex CFURLGetBytes(CFURLRef url, UInt8 *buffer, CFIndex bufferLength) {
2386 CFIndex length, charsConverted, usedLength;
2387 CFStringRef string;
2388 CFStringEncoding enc;
2389 if (CF_IS_OBJC(__kCFURLTypeID, url)) {
2390 string = CFURLGetString(url);
2391 enc = kCFStringEncodingUTF8;
2392 } else {
2393 if (URL_PATH_TYPE(url) != FULL_URL_REPRESENTATION) {
2394 _convertToURLRepresentation((struct __CFURL *)url);
2395 }
2396 string = url->_string;
2397 enc = url->_encoding;
2398 }
2399 length = CFStringGetLength(string);
2400 charsConverted = CFStringGetBytes(string, CFRangeMake(0, length), enc, 0, false, buffer, bufferLength, &usedLength);
2401 if (charsConverted != length) {
2402 return -1;
2403 } else {
2404 return usedLength;
2405 }
2406 }
2407
2408 CFURLRef CFURLGetBaseURL(CFURLRef anURL) {
2409 CF_OBJC_FUNCDISPATCH0(__kCFURLTypeID, CFURLRef, anURL, "baseURL");
2410 return anURL->_base;
2411 }
2412
2413 // Assumes the URL is already parsed
2414 static CFRange _rangeForComponent(UInt32 flags, CFRange *ranges, UInt32 compFlag) {
2415 UInt32 idx = 0;
2416 if (!(flags & compFlag)) return CFRangeMake(kCFNotFound, 0);
2417 while (!(compFlag & 1)) {
2418 compFlag = compFlag >> 1;
2419 if (flags & 1) {
2420 idx ++;
2421 }
2422 flags = flags >> 1;
2423 }
2424 return ranges[idx];
2425 }
2426
2427 static CFStringRef _retainedComponentString(CFURLRef url, UInt32 compFlag, Boolean fromOriginalString, Boolean removePercentEscapes) {
2428 CFRange rg;
2429 CFStringRef comp;
2430 CFAllocatorRef alloc = CFGetAllocator(url);
2431 CFAssert1(URL_PATH_TYPE(url) == FULL_URL_REPRESENTATION, __kCFLogAssertion, "%s(): passed a file system URL", __PRETTY_FUNCTION__);
2432 if (removePercentEscapes) fromOriginalString = true;
2433 if (!(url->_flags & IS_PARSED)) {
2434 _parseComponentsOfURL(url);
2435 }
2436 rg = _rangeForComponent(url->_flags, url->ranges, compFlag);
2437 if (rg.location == kCFNotFound) return NULL;
2438 if (compFlag & HAS_SCHEME && url->_flags & HAS_HTTP_SCHEME) {
2439 comp = kCFURLHTTPScheme;
2440 CFRetain(comp);
2441 } else if (compFlag & HAS_SCHEME && url->_flags & HAS_FILE_SCHEME) {
2442 comp = kCFURLFileScheme;
2443 CFRetain(comp);
2444 } else {
2445 comp = CFStringCreateWithSubstring(alloc, url->_string, rg);
2446 }
2447 if (!fromOriginalString) {
2448 if (!_haveTestedOriginalString(url)) {
2449 computeSanitizedString(url);
2450 }
2451 if (!(url->_flags & ORIGINAL_AND_URL_STRINGS_MATCH) && (url->_flags & (compFlag << BIT_SHIFT_FROM_COMPONENT_TO_DIFFERS_FLAG))) {
2452 CFStringRef newComp = correctedComponent(comp, compFlag, url->_encoding);
2453 CFRelease(comp);
2454 comp = newComp;
2455 }
2456 }
2457 if (removePercentEscapes) {
2458 CFStringRef tmp;
2459 if (url->_flags & IS_OLD_UTF8_STYLE || url->_encoding == kCFStringEncodingUTF8) {
2460 tmp = CFURLCreateStringByReplacingPercentEscapes(alloc, comp, CFSTR(""));
2461 } else {
2462 tmp = CFURLCreateStringByReplacingPercentEscapesUsingEncoding(alloc, comp, CFSTR(""), url->_encoding);
2463 }
2464 CFRelease(comp);
2465 comp = tmp;
2466 }
2467 return comp;
2468 }
2469
2470 CFStringRef CFURLCopyScheme(CFURLRef anURL) {
2471 CFStringRef scheme;
2472 if (CF_IS_OBJC(__kCFURLTypeID, anURL)) {
2473 CF_OBJC_CALL0(CFStringRef, scheme, anURL, "scheme");
2474 if (scheme) CFRetain(scheme);
2475 return scheme;
2476 }
2477 if (URL_PATH_TYPE(anURL) != FULL_URL_REPRESENTATION) {
2478 if (anURL->_base) {
2479 return CFURLCopyScheme(anURL->_base);
2480 } else {
2481 CFRetain(kCFURLFileScheme); // because caller will release it
2482 return kCFURLFileScheme;
2483 }
2484 }
2485 if (anURL->_flags & IS_PARSED && anURL->_flags & HAS_HTTP_SCHEME) {
2486 CFRetain(kCFURLHTTPScheme);
2487 return kCFURLHTTPScheme;
2488 }
2489 if (anURL->_flags & IS_PARSED && anURL->_flags & HAS_FILE_SCHEME) {
2490 CFRetain(kCFURLFileScheme);
2491 return kCFURLFileScheme;
2492 }
2493 scheme = _retainedComponentString(anURL, HAS_SCHEME, true, false);
2494 if (scheme) {
2495 return scheme;
2496 } else if (anURL->_base) {
2497 return CFURLCopyScheme(anURL->_base);
2498 } else {
2499 return NULL;
2500 }
2501 }
2502
2503 static CFRange _netLocationRange(UInt32 flags, CFRange *ranges) {
2504 CFRange netRgs[4];
2505 CFRange netRg = {kCFNotFound, 0};
2506 CFIndex i, c = 4;
2507
2508 if ((flags & NET_LOCATION_MASK) == 0) return CFRangeMake(kCFNotFound, 0);
2509
2510 netRgs[0] = _rangeForComponent(flags, ranges, HAS_USER);
2511 netRgs[1] = _rangeForComponent(flags, ranges, HAS_PASSWORD);
2512 netRgs[2] = _rangeForComponent(flags, ranges, HAS_HOST);
2513 netRgs[3] = _rangeForComponent(flags, ranges, HAS_PORT);
2514 for (i = 0; i < c; i ++) {
2515 if (netRgs[i].location == kCFNotFound) continue;
2516 if (netRg.location == kCFNotFound) {
2517 netRg = netRgs[i];
2518 } else {
2519 netRg.length = netRgs[i].location + netRgs[i].length - netRg.location;
2520 }
2521 }
2522 return netRg;
2523 }
2524
2525 CFStringRef CFURLCopyNetLocation(CFURLRef anURL) {
2526 anURL = _CFURLFromNSURL(anURL);
2527 if (URL_PATH_TYPE(anURL) != FULL_URL_REPRESENTATION) {
2528 // !!! This won't work if we go to putting the vol ref num in the net location for HFS
2529 if (anURL->_base) {
2530 return CFURLCopyNetLocation(anURL->_base);
2531 } else {
2532 CFRetain(kCFURLLocalhost);
2533 return kCFURLLocalhost;
2534 }
2535 }
2536 if (!(anURL->_flags & IS_PARSED)) {
2537 _parseComponentsOfURL(anURL);
2538 }
2539 if (anURL->_flags & NET_LOCATION_MASK) {
2540 // We provide the net location
2541 CFRange netRg = _netLocationRange(anURL->_flags, anURL->ranges);
2542 CFStringRef netLoc;
2543 if (!_haveTestedOriginalString(anURL)) {
2544 computeSanitizedString(anURL);
2545 }
2546 if (!(anURL->_flags & ORIGINAL_AND_URL_STRINGS_MATCH) && (anURL->_flags & (USER_DIFFERS | PASSWORD_DIFFERS | HOST_DIFFERS | PORT_DIFFERS))) {
2547 // Only thing that can come before the net location is the scheme. It's impossible for the scheme to contain percent escapes. Therefore, we can use the location of netRg in _sanatizedString, just not the length.
2548 CFRange netLocEnd;
2549 netRg.length = CFStringGetLength( _getSanitizedString(anURL)) - netRg.location;
2550 if (CFStringFindWithOptions(_getSanitizedString(anURL), CFSTR("/"), netRg, 0, &netLocEnd)) {
2551 netRg.length = netLocEnd.location - netRg.location;
2552 }
2553 netLoc = CFStringCreateWithSubstring(CFGetAllocator(anURL), _getSanitizedString(anURL), netRg);
2554 } else {
2555 netLoc = CFStringCreateWithSubstring(CFGetAllocator(anURL), anURL->_string, netRg);
2556 }
2557 return netLoc;
2558 } else if (anURL->_base) {
2559 return CFURLCopyNetLocation(anURL->_base);
2560 } else {
2561 return NULL;
2562 }
2563 }
2564
2565 // NOTE - if you want an absolute path, you must first get the absolute URL. If you want a file system path, use the file system methods above.
2566 CFStringRef CFURLCopyPath(CFURLRef anURL) {
2567 anURL = _CFURLFromNSURL(anURL);
2568 if (URL_PATH_TYPE(anURL) == kCFURLPOSIXPathStyle && (anURL->_flags & POSIX_AND_URL_PATHS_MATCH)) {
2569 CFRetain(anURL->_string);
2570 return anURL->_string;
2571 }
2572 if (URL_PATH_TYPE(anURL) != FULL_URL_REPRESENTATION) {
2573 _convertToURLRepresentation((struct __CFURL *)anURL);
2574 }
2575 return _retainedComponentString(anURL, HAS_PATH, false, false);
2576 }
2577
2578 /* NULL if CFURLCanBeDecomposed(anURL) is false; also does not resolve the URL against its base. See also CFCreateAbsoluteURL(). Note that, strictly speaking, any leading '/' is not considered part of the URL's path, although its presence or absence determines whether the path is absolute. CFURLCopyPath()'s return value includes any leading slash (giving the path the normal POSIX appearance); CFURLCopyStrictPath()'s return value omits any leading slash, and uses isAbsolute to report whether the URL's path is absolute.
2579
2580 CFURLCopyFileSystemPath() returns the URL's path as a file system path for the given path style. All percent escape sequences are replaced. The URL is not resolved against its base before computing the path.
2581 */
2582 CFStringRef CFURLCopyStrictPath(CFURLRef anURL, Boolean *isAbsolute) {
2583 CFStringRef path = CFURLCopyPath(anURL);
2584 if (!path || CFStringGetLength(path) == 0) {
2585 if (path) CFRelease(path);
2586 if (isAbsolute) *isAbsolute = false;
2587 return NULL;
2588 }
2589 if (CFStringGetCharacterAtIndex(path, 0) == '/') {
2590 CFStringRef tmp;
2591 if (isAbsolute) *isAbsolute = true;
2592 tmp = CFStringCreateWithSubstring(CFGetAllocator(path), path, CFRangeMake(1, CFStringGetLength(path)-1));
2593 CFRelease(path);
2594 path = tmp;
2595 } else {
2596 if (isAbsolute) *isAbsolute = false;
2597 }
2598 return path;
2599 }
2600
2601 Boolean CFURLHasDirectoryPath(CFURLRef anURL) {
2602 __CFGenericValidateType(anURL, __kCFURLTypeID);
2603 if (URL_PATH_TYPE(anURL) == FULL_URL_REPRESENTATION) {
2604 if (!(anURL->_flags & IS_PARSED)) {
2605 _parseComponentsOfURL(anURL);
2606 }
2607 if (!anURL->_base || (anURL->_flags & (HAS_PATH | NET_LOCATION_MASK))) {
2608 return ((anURL->_flags & IS_DIRECTORY) != 0);
2609 }
2610 return CFURLHasDirectoryPath(anURL->_base);
2611 }
2612 return ((anURL->_flags & IS_DIRECTORY) != 0);
2613 }
2614
2615 static UInt32 _firstResourceSpecifierFlag(UInt32 flags) {
2616 UInt32 firstRsrcSpecFlag = 0;
2617 UInt32 flag = HAS_FRAGMENT;
2618 while (flag != HAS_PATH) {
2619 if (flags & flag) {
2620 firstRsrcSpecFlag = flag;
2621 }
2622 flag = flag >> 1;
2623 }
2624 return firstRsrcSpecFlag;
2625 }
2626
2627 CFStringRef CFURLCopyResourceSpecifier(CFURLRef anURL) {
2628 anURL = _CFURLFromNSURL(anURL);
2629 __CFGenericValidateType(anURL, __kCFURLTypeID);
2630 if (URL_PATH_TYPE(anURL) != FULL_URL_REPRESENTATION) {
2631 return NULL;
2632 }
2633 if (!(anURL->_flags & IS_PARSED)) {
2634 _parseComponentsOfURL(anURL);
2635 }
2636 if (!(anURL->_flags & IS_DECOMPOSABLE)) {
2637 CFRange schemeRg = _rangeForComponent(anURL->_flags, anURL->ranges, HAS_SCHEME);
2638 CFIndex base = schemeRg.location + schemeRg.length + 1;
2639 if (!_haveTestedOriginalString(anURL)) {
2640 computeSanitizedString(anURL);
2641 }
2642 if (_getSanitizedString(anURL)) {
2643 // It is impossible to have a percent escape in the scheme (if there were one, we would have considered the URL a relativeURL with a colon in the path instead), so this range computation is always safe.
2644 return CFStringCreateWithSubstring(CFGetAllocator(anURL), _getSanitizedString(anURL), CFRangeMake(base, CFStringGetLength(_getSanitizedString(anURL))-base));
2645 } else {
2646 return CFStringCreateWithSubstring(CFGetAllocator(anURL), anURL->_string, CFRangeMake(base, CFStringGetLength(anURL->_string)-base));
2647 }
2648 } else {
2649 UInt32 firstRsrcSpecFlag = _firstResourceSpecifierFlag(anURL->_flags);
2650 UInt32 flag;
2651 if (firstRsrcSpecFlag) {
2652 Boolean canUseOriginalString = true;
2653 Boolean canUseSanitizedString = true;
2654 CFAllocatorRef alloc = CFGetAllocator(anURL);
2655 if (!_haveTestedOriginalString(anURL)) {
2656 computeSanitizedString(anURL);
2657 }
2658 if (!(anURL->_flags & ORIGINAL_AND_URL_STRINGS_MATCH)) {
2659 // See if any pieces in the resource specifier differ between sanitized string and original string
2660 for (flag = firstRsrcSpecFlag; flag != (HAS_FRAGMENT << 1); flag = flag << 1) {
2661 if (anURL->_flags & (flag << BIT_SHIFT_FROM_COMPONENT_TO_DIFFERS_FLAG)) {
2662 canUseOriginalString = false;
2663 break;
2664 }
2665 }
2666 }
2667 if (!canUseOriginalString) {
2668 // If none of the pieces prior to the first resource specifier flag differ, then we can use the offset from the original string as the offset in the sanitized string.
2669 for (flag = firstRsrcSpecFlag >> 1; flag != 0; flag = flag >> 1) {
2670 if (anURL->_flags & (flag << BIT_SHIFT_FROM_COMPONENT_TO_DIFFERS_FLAG)) {
2671 canUseSanitizedString = false;
2672 break;
2673 }
2674 }
2675 }
2676 if (canUseOriginalString) {
2677 CFRange rg = _rangeForComponent(anURL->_flags, anURL->ranges, firstRsrcSpecFlag);
2678 rg.location --; // Include the character that demarcates the component
2679 rg.length = CFStringGetLength(anURL->_string) - rg.location;
2680 return CFStringCreateWithSubstring(alloc, anURL->_string, rg);
2681 } else if (canUseSanitizedString) {
2682 CFRange rg = _rangeForComponent(anURL->_flags, anURL->ranges, firstRsrcSpecFlag);
2683 rg.location --; // Include the character that demarcates the component
2684 rg.length = CFStringGetLength(_getSanitizedString(anURL)) - rg.location;
2685 return CFStringCreateWithSubstring(alloc, _getSanitizedString(anURL), rg);
2686 } else {
2687 // Must compute the correct string to return; just reparse....
2688 UInt32 sanFlags = 0;
2689 CFRange *sanRanges = NULL;
2690 CFRange rg;
2691 _parseComponents(alloc, _getSanitizedString(anURL), anURL->_base, &sanFlags, &sanRanges);
2692 rg = _rangeForComponent(sanFlags, sanRanges, firstRsrcSpecFlag);
2693 CFAllocatorDeallocate(alloc, sanRanges);
2694 rg.location --; // Include the character that demarcates the component
2695 rg.length = CFStringGetLength(_getSanitizedString(anURL)) - rg.location;
2696 return CFStringCreateWithSubstring(CFGetAllocator(anURL), _getSanitizedString(anURL), rg);
2697 }
2698 } else {
2699 // The resource specifier cannot possibly come from the base.
2700 return NULL;
2701 }
2702 }
2703 }
2704
2705 /*************************************/
2706 /* Accessors that create new objects */
2707 /*************************************/
2708
2709 // For the next four methods, it is important to realize that, if a URL supplies any part of the net location (host, user, port, or password), it must supply all of the net location (i.e. none of it comes from its base URL). Also, it is impossible for a URL to be relative, supply none of the net location, and still have its (empty) net location take precedence over its base URL (because there's nothing that precedes the net location except the scheme, and if the URL supplied the scheme, it would be absolute, and there would be no base).
2710 CFStringRef CFURLCopyHostName(CFURLRef anURL) {
2711 CFStringRef tmp;
2712 if (CF_IS_OBJC(__kCFURLTypeID, anURL)) {
2713 CF_OBJC_CALL0(CFStringRef, tmp, anURL, "host");
2714 if (tmp) CFRetain(tmp);
2715 return tmp;
2716 }
2717 __CFGenericValidateType(anURL, __kCFURLTypeID);
2718 if (URL_PATH_TYPE(anURL) != FULL_URL_REPRESENTATION) {
2719 if (anURL->_base) {
2720 return CFURLCopyHostName(anURL->_base);
2721 } else {
2722 CFRetain(kCFURLLocalhost);
2723 return kCFURLLocalhost;
2724 }
2725 }
2726 tmp = _retainedComponentString(anURL, HAS_HOST, true, true);
2727 if (tmp) {
2728 if (anURL->_flags & IS_IPV6_ENCODED) {
2729 // Have to strip off the brackets to get the true hostname.
2730 // Assume that to be legal the first and last characters are brackets!
2731 CFStringRef strippedHost = CFStringCreateWithSubstring(CFGetAllocator(anURL), tmp, CFRangeMake(1, CFStringGetLength(tmp) - 2));
2732 CFRelease(tmp);
2733 tmp = strippedHost;
2734 }
2735 return tmp;
2736 } else if (anURL->_base && !(anURL->_flags & NET_LOCATION_MASK) && !(anURL->_flags & HAS_SCHEME)) {
2737 return CFURLCopyHostName(anURL->_base);
2738 } else {
2739 return NULL;
2740 }
2741 }
2742
2743 // Return -1 to indicate no port is specified
2744 SInt32 CFURLGetPortNumber(CFURLRef anURL) {
2745 CFStringRef port;
2746 if (CF_IS_OBJC(__kCFURLTypeID, anURL)) {
2747 CFNumberRef cfPort;
2748 CF_OBJC_CALL0(CFNumberRef, cfPort, anURL, "port");
2749 SInt32 num;
2750 if (cfPort && CFNumberGetValue(cfPort, kCFNumberSInt32Type, &num)) return num;
2751 return -1;
2752 }
2753 __CFGenericValidateType(anURL, __kCFURLTypeID);
2754 if (URL_PATH_TYPE(anURL) != FULL_URL_REPRESENTATION) {
2755 if (anURL->_base) {
2756 return CFURLGetPortNumber(anURL->_base);
2757 }
2758 return -1;
2759 }
2760 port = _retainedComponentString(anURL, HAS_PORT, true, false);
2761 if (port) {
2762 SInt32 portNum, idx, length = CFStringGetLength(port);
2763 CFStringInlineBuffer buf;
2764 CFStringInitInlineBuffer(port, &buf, CFRangeMake(0, length));
2765 idx = 0;
2766 if (!__CFStringScanInteger(&buf, NULL, &idx, false, &portNum) || (idx != length)) {
2767 portNum = -1;
2768 }
2769 CFRelease(port);
2770 return portNum;
2771 } else if (anURL->_base && !(anURL->_flags & NET_LOCATION_MASK) && !(anURL->_flags & HAS_SCHEME)) {
2772 return CFURLGetPortNumber(anURL->_base);
2773 } else {
2774 return -1;
2775 }
2776 }
2777
2778 CFStringRef CFURLCopyUserName(CFURLRef anURL) {
2779 CFStringRef user;
2780 if (CF_IS_OBJC(__kCFURLTypeID, anURL)) {
2781 CF_OBJC_CALL0(CFStringRef, user, anURL, "user");
2782 if (user) CFRetain(user);
2783 return user;
2784 }
2785 __CFGenericValidateType(anURL, __kCFURLTypeID);
2786 if (URL_PATH_TYPE(anURL) != FULL_URL_REPRESENTATION) {
2787 if (anURL->_base) {
2788 return CFURLCopyUserName(anURL->_base);
2789 }
2790 return NULL;
2791 }
2792 user = _retainedComponentString(anURL, HAS_USER, true, true);
2793 if (user) {
2794 return user;
2795 } else if (anURL->_base && !(anURL->_flags & NET_LOCATION_MASK) && !(anURL->_flags & HAS_SCHEME)) {
2796 return CFURLCopyUserName(anURL->_base);
2797 } else {
2798 return NULL;
2799 }
2800 }
2801
2802 CFStringRef CFURLCopyPassword(CFURLRef anURL) {
2803 CFStringRef passwd;
2804 if (CF_IS_OBJC(__kCFURLTypeID, anURL)) {
2805 CF_OBJC_CALL0(CFStringRef, passwd, anURL, "password");
2806 if (passwd) CFRetain(passwd);
2807 return passwd;
2808 }
2809 __CFGenericValidateType(anURL, __kCFURLTypeID);
2810 if (URL_PATH_TYPE(anURL) != FULL_URL_REPRESENTATION) {
2811 if (anURL->_base) {
2812 return CFURLCopyPassword(anURL->_base);
2813 }
2814 return NULL;
2815 }
2816 passwd = _retainedComponentString(anURL, HAS_PASSWORD, true, true);
2817 if (passwd) {
2818 return passwd;
2819 } else if (anURL->_base && !(anURL->_flags & NET_LOCATION_MASK) && !(anURL->_flags & HAS_SCHEME)) {
2820 return CFURLCopyPassword(anURL->_base);
2821 } else {
2822 return NULL;
2823 }
2824 }
2825
2826 // The NSURL methods do not deal with escaping escape characters at all; therefore, in order to properly bridge NSURL methods, and still provide the escaping behavior that we want, we need to create functions that match the ObjC behavior exactly, and have the public CFURL... functions call these. -- REW, 10/29/98
2827
2828 static CFStringRef _unescapedParameterString(CFURLRef anURL) {
2829 CFStringRef str;
2830 if (CF_IS_OBJC(__kCFURLTypeID, anURL)) {
2831 CF_OBJC_CALL0(CFStringRef, str, anURL, "parameterString");
2832 if (str) CFRetain(str);
2833 return str;
2834 }
2835 __CFGenericValidateType(anURL, __kCFURLTypeID);
2836 if (URL_PATH_TYPE(anURL) != FULL_URL_REPRESENTATION) {
2837 return NULL;
2838 }
2839 str = _retainedComponentString(anURL, HAS_PARAMETERS, false, false);
2840 if (str) return str;
2841 if (!(anURL->_flags & IS_DECOMPOSABLE)) return NULL;
2842 if (!anURL->_base || (anURL->_flags & (NET_LOCATION_MASK | HAS_PATH | HAS_SCHEME))) {
2843 return NULL;
2844 // Parameter string definitely coming from the relative portion of the URL
2845 }
2846 return _unescapedParameterString( anURL->_base);
2847 }
2848
2849 CFStringRef CFURLCopyParameterString(CFURLRef anURL, CFStringRef charactersToLeaveEscaped) {
2850 CFStringRef param = _unescapedParameterString(anURL);
2851 if (param) {
2852 CFStringRef result;
2853 if (anURL->_flags & IS_OLD_UTF8_STYLE || anURL->_encoding == kCFStringEncodingUTF8) {
2854 result = CFURLCreateStringByReplacingPercentEscapes(CFGetAllocator(anURL), param, charactersToLeaveEscaped);
2855 } else {
2856 result = CFURLCreateStringByReplacingPercentEscapesUsingEncoding(CFGetAllocator(anURL), param, charactersToLeaveEscaped, anURL->_encoding);
2857 }
2858 CFRelease(param);
2859 return result;
2860 }
2861 return NULL;
2862 }
2863
2864 static CFStringRef _unescapedQueryString(CFURLRef anURL) {
2865 CFStringRef str;
2866 if (CF_IS_OBJC(__kCFURLTypeID, anURL)) {
2867 CF_OBJC_CALL0(CFStringRef, str, anURL, "query");
2868 if (str) CFRetain(str);
2869 return str;
2870 }
2871 __CFGenericValidateType(anURL, __kCFURLTypeID);
2872 if (URL_PATH_TYPE(anURL) != FULL_URL_REPRESENTATION) {
2873 return NULL;
2874 }
2875 str = _retainedComponentString(anURL, HAS_QUERY, false, false);
2876 if (str) return str;
2877 if (!(anURL->_flags & IS_DECOMPOSABLE)) return NULL;
2878 if (!anURL->_base || (anURL->_flags & (HAS_SCHEME | NET_LOCATION_MASK | HAS_PATH | HAS_PARAMETERS))) {
2879 return NULL;
2880 }
2881 return _unescapedQueryString(anURL->_base);
2882 }
2883
2884 CFStringRef CFURLCopyQueryString(CFURLRef anURL, CFStringRef charactersToLeaveEscaped) {
2885 CFStringRef query = _unescapedQueryString(anURL);
2886 if (query) {
2887 CFStringRef tmp;
2888 if (anURL->_flags & IS_OLD_UTF8_STYLE || anURL->_encoding == kCFStringEncodingUTF8) {
2889 tmp = CFURLCreateStringByReplacingPercentEscapes(CFGetAllocator(anURL), query, charactersToLeaveEscaped);
2890 } else {
2891 tmp = CFURLCreateStringByReplacingPercentEscapesUsingEncoding(CFGetAllocator(anURL), query, charactersToLeaveEscaped, anURL->_encoding);
2892 }
2893 CFRelease(query);
2894 return tmp;
2895 }
2896 return NULL;
2897 }
2898
2899 // Fragments are NEVER taken from a base URL
2900 static CFStringRef _unescapedFragment(CFURLRef anURL) {
2901 CFStringRef str;
2902 if (CF_IS_OBJC(__kCFURLTypeID, anURL)) {
2903 CF_OBJC_CALL0(CFStringRef, str, anURL, "fragment");
2904 if (str) CFRetain(str);
2905 return str;
2906 }
2907 __CFGenericValidateType(anURL, __kCFURLTypeID);
2908 if (URL_PATH_TYPE(anURL) != FULL_URL_REPRESENTATION) {
2909 return NULL;
2910 }
2911 str = _retainedComponentString(anURL, HAS_FRAGMENT, false, false);
2912 return str;
2913 }
2914
2915 CFStringRef CFURLCopyFragment(CFURLRef anURL, CFStringRef charactersToLeaveEscaped) {
2916 CFStringRef fragment = _unescapedFragment(anURL);
2917 if (fragment) {
2918 CFStringRef tmp;
2919 if (anURL->_flags & IS_OLD_UTF8_STYLE || anURL->_encoding == kCFStringEncodingUTF8) {
2920 tmp = CFURLCreateStringByReplacingPercentEscapes(CFGetAllocator(anURL), fragment, charactersToLeaveEscaped);
2921 } else {
2922 tmp = CFURLCreateStringByReplacingPercentEscapesUsingEncoding(CFGetAllocator(anURL), fragment, charactersToLeaveEscaped, anURL->_encoding);
2923 }
2924 CFRelease(fragment);
2925 return tmp;
2926 }
2927 return NULL;
2928 }
2929
2930 static CFIndex insertionLocationForMask(CFURLRef url, CFOptionFlags mask) {
2931 CFIndex firstMaskFlag = 1;
2932 CFIndex lastComponentBeforeMask = 0;
2933 while (firstMaskFlag <= HAS_FRAGMENT) {
2934 if (firstMaskFlag & mask) break;
2935 if (url->_flags & firstMaskFlag) lastComponentBeforeMask = firstMaskFlag;
2936 firstMaskFlag = firstMaskFlag << 1;
2937 }
2938 if (lastComponentBeforeMask == 0) {
2939 // mask includes HAS_SCHEME
2940 return 0;
2941 } else if (lastComponentBeforeMask == HAS_SCHEME) {
2942 // Do not have to worry about the non-decomposable case here. However, we must be prepared for the degenerate
2943 // case file:/path/immediately/without/host
2944 CFRange schemeRg = _rangeForComponent(url->_flags, url->ranges, HAS_SCHEME);
2945 CFRange pathRg = _rangeForComponent(url->_flags, url->ranges, HAS_PATH);
2946 if (schemeRg.length + 1 == pathRg.location) {
2947 return schemeRg.length + 1;
2948 } else {
2949 return schemeRg.length + 3;
2950 }
2951 } else {
2952 // For all other components, the separator precedes the component, so there's no need
2953 // to add extra chars to get to the next insertion point
2954 CFRange rg = _rangeForComponent(url->_flags, url->ranges, lastComponentBeforeMask);
2955 return rg.location + rg.length;
2956 }
2957 }
2958
2959 static CFRange _CFURLGetCharRangeForMask(CFURLRef url, CFOptionFlags mask, CFRange *charRangeWithSeparators) {
2960 CFOptionFlags currentOption;
2961 CFOptionFlags firstMaskFlag = HAS_SCHEME;
2962 Boolean haveReachedMask = false;
2963 CFIndex beforeMask = 0;
2964 CFIndex afterMask = kCFNotFound;
2965 CFRange *currRange = url->ranges;
2966 CFRange maskRange = {kCFNotFound, 0};
2967 for (currentOption = 1; currentOption <= HAS_FRAGMENT; currentOption = currentOption << 1) {
2968 if (!haveReachedMask && (currentOption & mask) != 0) {
2969 firstMaskFlag = currentOption;
2970 haveReachedMask = true;
2971 }
2972 if (!(url->_flags & currentOption)) continue;
2973 if (!haveReachedMask) {
2974 beforeMask = currRange->location + currRange->length;
2975 } else if (currentOption <= mask) {
2976 if (maskRange.location == kCFNotFound) {
2977 maskRange = *currRange;
2978 } else {
2979 maskRange.length = currRange->location + currRange->length - maskRange.location;
2980 }
2981 } else {
2982 afterMask = currRange->location;
2983 break;
2984 }
2985 currRange ++;
2986 }
2987 if (afterMask == kCFNotFound) {
2988 afterMask = maskRange.location + maskRange.length;
2989 }
2990 charRangeWithSeparators->location = beforeMask;
2991 charRangeWithSeparators->length = afterMask - beforeMask;
2992 return maskRange;
2993 }
2994
2995 static CFRange _getCharRangeInDecomposableURL(CFURLRef url, CFURLComponentType component, CFRange *rangeIncludingSeparators) {
2996 CFOptionFlags mask;
2997 switch (component) {
2998 case kCFURLComponentScheme:
2999 mask = HAS_SCHEME;
3000 break;
3001 case kCFURLComponentNetLocation:
3002 mask = NET_LOCATION_MASK;
3003 break;
3004 case kCFURLComponentPath:
3005 mask = HAS_PATH;
3006 break;
3007 case kCFURLComponentResourceSpecifier:
3008 mask = RESOURCE_SPECIFIER_MASK;
3009 break;
3010 case kCFURLComponentUser:
3011 mask = HAS_USER;
3012 break;
3013 case kCFURLComponentPassword:
3014 mask = HAS_PASSWORD;
3015 break;
3016 case kCFURLComponentUserInfo:
3017 mask = HAS_USER | HAS_PASSWORD;
3018 break;
3019 case kCFURLComponentHost:
3020 mask = HAS_HOST;
3021 break;
3022 case kCFURLComponentPort:
3023 mask = HAS_PORT;
3024 break;
3025 case kCFURLComponentParameterString:
3026 mask = HAS_PARAMETERS;
3027 break;
3028 case kCFURLComponentQuery:
3029 mask = HAS_QUERY;
3030 break;
3031 case kCFURLComponentFragment:
3032 mask = HAS_FRAGMENT;
3033 break;
3034 default:
3035 rangeIncludingSeparators->location = kCFNotFound;
3036 rangeIncludingSeparators->length = 0;
3037 return CFRangeMake(kCFNotFound, 0);
3038 }
3039
3040 if ((url->_flags & mask) == 0) {
3041 rangeIncludingSeparators->location = insertionLocationForMask(url, mask);
3042 rangeIncludingSeparators->length = 0;
3043 return CFRangeMake(kCFNotFound, 0);
3044 } else {
3045 return _CFURLGetCharRangeForMask(url, mask, rangeIncludingSeparators);
3046 }
3047 }
3048
3049 static CFRange _getCharRangeInNonDecomposableURL(CFURLRef url, CFURLComponentType component, CFRange *rangeIncludingSeparators) {
3050 if (component == kCFURLComponentScheme) {
3051 CFRange schemeRg = _rangeForComponent(url->_flags, url->ranges, HAS_SCHEME);
3052 rangeIncludingSeparators->location = 0;
3053 rangeIncludingSeparators->length = schemeRg.length + 1;
3054 return schemeRg;
3055 } else if (component == kCFURLComponentResourceSpecifier) {
3056 CFRange schemeRg = _rangeForComponent(url->_flags, url->ranges, HAS_SCHEME);
3057 CFIndex stringLength = CFStringGetLength(url->_string);
3058 if (schemeRg.length + 1 == stringLength) {
3059 rangeIncludingSeparators->location = schemeRg.length + 1;
3060 rangeIncludingSeparators->length = 0;
3061 return CFRangeMake(kCFNotFound, 0);
3062 } else {
3063 rangeIncludingSeparators->location = schemeRg.length;
3064 rangeIncludingSeparators->length = stringLength - schemeRg.length;
3065 return CFRangeMake(schemeRg.length + 1, rangeIncludingSeparators->length - 1);
3066 }
3067 } else {
3068 rangeIncludingSeparators->location = kCFNotFound;
3069 rangeIncludingSeparators->length = 0;
3070 return CFRangeMake(kCFNotFound, 0);
3071 }
3072
3073 }
3074
3075 CFRange CFURLGetByteRangeForComponent(CFURLRef url, CFURLComponentType component, CFRange *rangeIncludingSeparators) {
3076 CFRange charRange, charRangeWithSeparators;
3077 CFRange byteRange;
3078 CFAssert2(component > 0 && component < 13, __kCFLogAssertion, "%s(): passed invalid component %d", __PRETTY_FUNCTION__, component);
3079 url = _CFURLFromNSURL(url);
3080 if (URL_PATH_TYPE(url) != FULL_URL_REPRESENTATION) {
3081 _convertToURLRepresentation((struct __CFURL *)url);
3082 }
3083 if (!(url->_flags & IS_PARSED)) {
3084 _parseComponentsOfURL(url);
3085 }
3086
3087 if (!(url->_flags & IS_DECOMPOSABLE)) {
3088 // Special-case this because non-decomposable URLs have a slightly strange flags setup
3089 charRange = _getCharRangeInNonDecomposableURL(url, component, &charRangeWithSeparators);
3090 } else {
3091 charRange = _getCharRangeInDecomposableURL(url, component, &charRangeWithSeparators);
3092 }
3093
3094 if (charRangeWithSeparators.location == kCFNotFound) {
3095 if (rangeIncludingSeparators) {
3096 rangeIncludingSeparators->location = kCFNotFound;
3097 rangeIncludingSeparators->length = 0;
3098 }
3099 return CFRangeMake(kCFNotFound, 0);
3100 } else if (rangeIncludingSeparators) {
3101 CFStringGetBytes(url->_string, CFRangeMake(0, charRangeWithSeparators.location), url->_encoding, 0, false, NULL, 0, &(rangeIncludingSeparators->location));
3102
3103 if (charRange.location == kCFNotFound) {
3104 byteRange = charRange;
3105 CFStringGetBytes(url->_string, charRangeWithSeparators, url->_encoding, 0, false, NULL, 0, &(rangeIncludingSeparators->length));
3106 } else {
3107 CFIndex maxCharRange = charRange.location + charRange.length;
3108 CFIndex maxCharRangeWithSeparators = charRangeWithSeparators.location + charRangeWithSeparators.length;
3109
3110 if (charRangeWithSeparators.location == charRange.location) {
3111 byteRange.location = rangeIncludingSeparators->location;
3112 } else {
3113 CFIndex numBytes;
3114 CFStringGetBytes(url->_string, CFRangeMake(charRangeWithSeparators.location, charRange.location - charRangeWithSeparators.location), url->_encoding, 0, false, NULL, 0, &numBytes);
3115 byteRange.location = charRangeWithSeparators.location + numBytes;
3116 }
3117 CFStringGetBytes(url->_string, charRange, url->_encoding, 0, false, NULL, 0, &(byteRange.length));
3118 if (maxCharRangeWithSeparators == maxCharRange) {
3119 rangeIncludingSeparators->length = byteRange.location + byteRange.length - rangeIncludingSeparators->location;
3120 } else {
3121 CFIndex numBytes;
3122 CFRange rg;
3123 rg.location = maxCharRange;
3124 rg.length = maxCharRangeWithSeparators - rg.location;
3125 CFStringGetBytes(url->_string, rg, url->_encoding, 0, false, NULL, 0, &numBytes);
3126 rangeIncludingSeparators->length = byteRange.location + byteRange.length + numBytes - rangeIncludingSeparators->location;
3127 }
3128 }
3129 } else if (charRange.location == kCFNotFound) {
3130 byteRange = charRange;
3131 } else {
3132 CFStringGetBytes(url->_string, CFRangeMake(0, charRange.location), url->_encoding, 0, false, NULL, 0, &(byteRange.location));
3133 CFStringGetBytes(url->_string, charRange, url->_encoding, 0, false, NULL, 0, &(byteRange.length));
3134 }
3135 return byteRange;
3136 }
3137
3138 /* Component support */
3139
3140 /* We convert to the CFURL immediately at the beginning of decomposition, so all the decomposition routines need not worry about having an ObjC NSURL */
3141 static CFStringRef schemeSpecificString(CFURLRef url) {
3142 Boolean isDir;
3143 isDir = ((url->_flags & IS_DIRECTORY) != 0);
3144 switch (URL_PATH_TYPE(url)) {
3145 case kCFURLPOSIXPathStyle:
3146 if (url->_flags & POSIX_AND_URL_PATHS_MATCH) {
3147 return (CFStringRef)CFRetain(url->_string);
3148 } else {
3149 return POSIXPathToURLPath(url->_string, CFGetAllocator(url), isDir);
3150 }
3151 #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED
3152 case kCFURLHFSPathStyle:
3153 return HFSPathToURLPath(url->_string, CFGetAllocator(url), isDir);
3154 #elif DEPLOYMENT_TARGET_WINDOWS
3155 #else
3156 #error Unknown or unspecified DEPLOYMENT_TARGET
3157 #endif
3158 case kCFURLWindowsPathStyle:
3159 return WindowsPathToURLPath(url->_string, CFGetAllocator(url), isDir);
3160 case FULL_URL_REPRESENTATION:
3161 return CFURLCopyResourceSpecifier(url);
3162 default:
3163 return NULL;
3164 }
3165 }
3166
3167 static Boolean decomposeToNonHierarchical(CFURLRef url, CFURLComponentsNonHierarchical *components) {
3168 if ( CFURLGetBaseURL(url) != NULL) {
3169 components->scheme = NULL;
3170 } else {
3171 components->scheme = CFURLCopyScheme(url);
3172 }
3173 components->schemeSpecific = schemeSpecificString(url);
3174 return true;
3175 }
3176
3177 static CFURLRef composeFromNonHierarchical(CFAllocatorRef alloc, const CFURLComponentsNonHierarchical *components) {
3178 CFStringRef str;
3179 if (components->scheme) {
3180 UniChar ch = ':';
3181 str = CFStringCreateMutableCopy(alloc, CFStringGetLength(components->scheme) + 1 + (components->schemeSpecific ? CFStringGetLength(components->schemeSpecific): 0), components->scheme);
3182 CFStringAppendCharacters((CFMutableStringRef)str, &ch, 1);
3183 if (components->schemeSpecific) CFStringAppend((CFMutableStringRef)str, components->schemeSpecific);
3184 } else if (components->schemeSpecific) {
3185 str = components->schemeSpecific;
3186 CFRetain(str);
3187 } else {
3188 str = NULL;
3189 }
3190 if (str) {
3191 CFURLRef url = CFURLCreateWithString(alloc, str, NULL);
3192 CFRelease(str);
3193 return url;
3194 } else {
3195 return NULL;
3196 }
3197 }
3198
3199 static Boolean decomposeToRFC1808(CFURLRef url, CFURLComponentsRFC1808 *components) {
3200 CFAllocatorRef alloc = CFGetAllocator(url);
3201 int pathType;
3202 static CFStringRef emptyStr = NULL;
3203 if (!emptyStr) {
3204 emptyStr = CFSTR("");
3205 }
3206
3207 if (!CFURLCanBeDecomposed(url)) {
3208 return false;
3209 }
3210 if ((pathType = URL_PATH_TYPE(url)) == FULL_URL_REPRESENTATION) {
3211 CFStringRef path = CFURLCopyPath(url);
3212 if (path) {
3213 components->pathComponents = CFStringCreateArrayBySeparatingStrings(alloc, path, CFSTR("/"));
3214 CFRelease(path);
3215 } else {
3216 components->pathComponents = NULL;
3217 }
3218 components->baseURL = CFURLGetBaseURL(url);
3219 if (components->baseURL) {
3220 CFRetain(components->baseURL);
3221 components->scheme = NULL;
3222 } else {
3223 components->scheme = _retainedComponentString(url, HAS_SCHEME, true, false);
3224 }
3225 components->user = _retainedComponentString(url, HAS_USER, false, false);
3226 components->password = _retainedComponentString(url, HAS_PASSWORD, false, false);
3227 components->host = _retainedComponentString(url, HAS_HOST, false, false);
3228 if (url->_flags & HAS_PORT) {
3229 components->port = CFURLGetPortNumber(url);
3230 } else {
3231 components->port = kCFNotFound;
3232 }
3233 components->parameterString = _retainedComponentString(url, HAS_PARAMETERS, false, false);
3234 components->query = _retainedComponentString(url, HAS_QUERY, false, false);
3235 components->fragment = _retainedComponentString(url, HAS_FRAGMENT, false, false);
3236 } else {
3237 switch (pathType) {
3238 case kCFURLPOSIXPathStyle: {
3239 CFStringRef pathStr;
3240 if (url->_flags & POSIX_AND_URL_PATHS_MATCH) {
3241 pathStr = url->_string;
3242 CFRetain(pathStr);
3243 } else {
3244 pathStr = POSIXPathToURLPath(url->_string, alloc, url->_flags & IS_DIRECTORY);
3245 }
3246 components->pathComponents = CFStringCreateArrayBySeparatingStrings(alloc, pathStr, CFSTR("/"));
3247 CFRelease(pathStr);
3248 break;
3249 }
3250 #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED
3251 case kCFURLHFSPathStyle:
3252 components->pathComponents = HFSPathToURLComponents(url->_string, alloc, ((url->_flags & IS_DIRECTORY) != 0));
3253 break;
3254 #elif DEPLOYMENT_TARGET_WINDOWS
3255 #else
3256 #error Unknown or unspecified DEPLOYMENT_TARGET
3257 #endif
3258 case kCFURLWindowsPathStyle:
3259 components->pathComponents = WindowsPathToURLComponents(url->_string, alloc, ((url->_flags & IS_DIRECTORY) != 0));
3260 break;
3261 default:
3262 components->pathComponents = NULL;
3263 }
3264 if (!components->pathComponents) {
3265 return false;
3266 }
3267 components->scheme = (CFStringRef)CFRetain(kCFURLFileScheme);
3268 components->user = NULL;
3269 components->password = NULL;
3270 components->host = (CFStringRef)CFRetain(kCFURLLocalhost);
3271 components->port = kCFNotFound;
3272 components->parameterString = NULL;
3273 components->query = NULL;
3274 components->fragment = NULL;
3275 components->baseURL = CFURLGetBaseURL(url);
3276 if (components->baseURL) CFRetain(components->baseURL);
3277 }
3278 return true;
3279 }
3280
3281 static CFURLRef composeFromRFC1808(CFAllocatorRef alloc, const CFURLComponentsRFC1808 *comp) {
3282 CFMutableStringRef urlString = CFStringCreateMutable(alloc, 0);
3283 CFURLRef base = comp->baseURL;
3284 CFURLRef url;
3285 Boolean hadPrePathComponent = false;
3286 if (comp->scheme) {
3287 base = NULL;
3288 CFStringAppend(urlString, comp->scheme);
3289 CFStringAppend(urlString, CFSTR("://"));
3290 hadPrePathComponent = true;
3291 }
3292 if (comp->user || comp->password) {
3293 if (comp->user) {
3294 CFStringAppend(urlString, comp->user);
3295 }
3296 if (comp->password) {
3297 CFStringAppend(urlString, CFSTR(":"));
3298 CFStringAppend(urlString, comp->password);
3299 }
3300 CFStringAppend(urlString, CFSTR("@"));
3301 hadPrePathComponent = true;
3302 }
3303 if (comp->host) {
3304 CFStringAppend(urlString, comp->host);
3305 hadPrePathComponent = true;
3306 }
3307 if (comp->port != kCFNotFound) {
3308 CFStringAppendFormat(urlString, NULL, CFSTR(":%d"), comp->port);
3309 hadPrePathComponent = true;
3310 }
3311
3312 if (hadPrePathComponent && (comp->pathComponents == NULL || CFArrayGetCount( comp->pathComponents ) == 0 || CFStringGetLength((CFStringRef)CFArrayGetValueAtIndex(comp->pathComponents, 0)) != 0)) {
3313 CFStringAppend(urlString, CFSTR("/"));
3314 }
3315 if (comp->pathComponents) {
3316 CFStringRef pathStr = CFStringCreateByCombiningStrings(alloc, comp->pathComponents, CFSTR("/"));
3317 CFStringAppend(urlString, pathStr);
3318 CFRelease(pathStr);
3319 }
3320 if (comp->parameterString) {
3321 CFStringAppend(urlString, CFSTR(";"));
3322 CFStringAppend(urlString, comp->parameterString);
3323 }
3324 if (comp->query) {
3325 CFStringAppend(urlString, CFSTR("?"));
3326 CFStringAppend(urlString, comp->query);
3327 }
3328 if (comp->fragment) {
3329 CFStringAppend(urlString, CFSTR("#"));
3330 CFStringAppend(urlString, comp->fragment);
3331 }
3332 url = CFURLCreateWithString(alloc, urlString, base);
3333 CFRelease(urlString);
3334 return url;
3335 }
3336
3337 static Boolean decomposeToRFC2396(CFURLRef url, CFURLComponentsRFC2396 *comp) {
3338 CFAllocatorRef alloc = CFGetAllocator(url);
3339 CFURLComponentsRFC1808 oldComp;
3340 CFStringRef tmpStr;
3341 if (!decomposeToRFC1808(url, &oldComp)) {
3342 return false;
3343 }
3344 comp->scheme = oldComp.scheme;
3345 if (oldComp.user) {
3346 if (oldComp.password) {
3347 comp->userinfo = CFStringCreateWithFormat(alloc, NULL, CFSTR("%@:%@"), oldComp.user, oldComp.password);
3348 CFRelease(oldComp.password);
3349 CFRelease(oldComp.user);
3350 } else {
3351 comp->userinfo = oldComp.user;
3352 }
3353 } else {
3354 comp->userinfo = NULL;
3355 }
3356 comp->host = oldComp.host;
3357 comp->port = oldComp.port;
3358 if (!oldComp.parameterString) {
3359 comp->pathComponents = oldComp.pathComponents;
3360 } else {
3361 int length = CFArrayGetCount(oldComp.pathComponents);
3362 comp->pathComponents = CFArrayCreateMutableCopy(alloc, length, oldComp.pathComponents);
3363 tmpStr = CFStringCreateWithFormat(alloc, NULL, CFSTR("%@;%@"), CFArrayGetValueAtIndex(comp->pathComponents, length - 1), oldComp.parameterString);
3364 CFArraySetValueAtIndex((CFMutableArrayRef)comp->pathComponents, length - 1, tmpStr);
3365 CFRelease(tmpStr);
3366 CFRelease(oldComp.pathComponents);
3367 CFRelease(oldComp.parameterString);
3368 }
3369 comp->query = oldComp.query;
3370 comp->fragment = oldComp.fragment;
3371 comp->baseURL = oldComp.baseURL;
3372 return true;
3373 }
3374
3375 static CFURLRef composeFromRFC2396(CFAllocatorRef alloc, const CFURLComponentsRFC2396 *comp) {
3376 CFMutableStringRef urlString = CFStringCreateMutable(alloc, 0);
3377 CFURLRef base = comp->baseURL;
3378 CFURLRef url;
3379 Boolean hadPrePathComponent = false;
3380 if (comp->scheme) {
3381 base = NULL;
3382 CFStringAppend(urlString, comp->scheme);
3383 CFStringAppend(urlString, CFSTR("://"));
3384 hadPrePathComponent = true;
3385 }
3386 if (comp->userinfo) {
3387 CFStringAppend(urlString, comp->userinfo);
3388 CFStringAppend(urlString, CFSTR("@"));
3389 hadPrePathComponent = true;
3390 }
3391 if (comp->host) {
3392 CFStringAppend(urlString, comp->host);
3393 if (comp->port != kCFNotFound) {
3394 CFStringAppendFormat(urlString, NULL, CFSTR(":%d"), comp->port);
3395 }
3396 hadPrePathComponent = true;
3397 }
3398 if (hadPrePathComponent && (comp->pathComponents == NULL || CFStringGetLength((CFStringRef)CFArrayGetValueAtIndex(comp->pathComponents, 0)) != 0)) {
3399 CFStringAppend(urlString, CFSTR("/"));
3400 }
3401 if (comp->pathComponents) {
3402 CFStringRef pathStr = CFStringCreateByCombiningStrings(alloc, comp->pathComponents, CFSTR("/"));
3403 CFStringAppend(urlString, pathStr);
3404 CFRelease(pathStr);
3405 }
3406 if (comp->query) {
3407 CFStringAppend(urlString, CFSTR("?"));
3408 CFStringAppend(urlString, comp->query);
3409 }
3410 if (comp->fragment) {
3411 CFStringAppend(urlString, CFSTR("#"));
3412 CFStringAppend(urlString, comp->fragment);
3413 }
3414 url = CFURLCreateWithString(alloc, urlString, base);
3415 CFRelease(urlString);
3416 return url;
3417 }
3418
3419 #undef CFURLCopyComponents
3420 #undef CFURLCreateFromComponents
3421
3422 CF_EXPORT
3423 Boolean _CFURLCopyComponents(CFURLRef url, CFURLComponentDecomposition decompositionType, void *components) {
3424 url = _CFURLFromNSURL(url);
3425 switch (decompositionType) {
3426 case kCFURLComponentDecompositionNonHierarchical:
3427 return decomposeToNonHierarchical(url, (CFURLComponentsNonHierarchical *)components);
3428 case kCFURLComponentDecompositionRFC1808:
3429 return decomposeToRFC1808(url, (CFURLComponentsRFC1808 *)components);
3430 case kCFURLComponentDecompositionRFC2396:
3431 return decomposeToRFC2396(url, (CFURLComponentsRFC2396 *)components);
3432 default:
3433 return false;
3434 }
3435 }
3436
3437 CF_EXPORT
3438 CFURLRef _CFURLCreateFromComponents(CFAllocatorRef alloc, CFURLComponentDecomposition decompositionType, const void *components) {
3439 switch (decompositionType) {
3440 case kCFURLComponentDecompositionNonHierarchical:
3441 return composeFromNonHierarchical(alloc, (const CFURLComponentsNonHierarchical *)components);
3442 case kCFURLComponentDecompositionRFC1808:
3443 return composeFromRFC1808(alloc, (const CFURLComponentsRFC1808 *)components);
3444 case kCFURLComponentDecompositionRFC2396:
3445 return composeFromRFC2396(alloc, (const CFURLComponentsRFC2396 *)components);
3446 default:
3447 return NULL;
3448 }
3449 }
3450
3451 CF_EXPORT void *__CFURLReservedPtr(CFURLRef url) {
3452 return _getReserved(url);
3453 }
3454
3455 CF_EXPORT void __CFURLSetReservedPtr(CFURLRef url, void *ptr) {
3456 _setReserved ( (struct __CFURL*) url, ptr );
3457 }
3458
3459 CF_EXPORT void *__CFURLResourceInfoPtr(CFURLRef url) {
3460 return _getResourceInfo(url);
3461 }
3462
3463 CF_EXPORT void __CFURLSetResourceInfoPtr(CFURLRef url, void *ptr) {
3464 _setResourceInfo ( (struct __CFURL*) url, ptr );
3465 }
3466
3467 /* File system stuff */
3468
3469 /* HFSPath<->URLPath functions at the bottom of the file */
3470 static CFArrayRef WindowsPathToURLComponents(CFStringRef path, CFAllocatorRef alloc, Boolean isDir) {
3471 CFArrayRef tmp;
3472 CFMutableArrayRef urlComponents = NULL;
3473 CFIndex i=0;
3474
3475 tmp = CFStringCreateArrayBySeparatingStrings(alloc, path, CFSTR("\\"));
3476 urlComponents = CFArrayCreateMutableCopy(alloc, 0, tmp);
3477 CFRelease(tmp);
3478
3479 CFStringRef str = (CFStringRef)CFArrayGetValueAtIndex(urlComponents, 0);
3480 if (CFStringGetLength(str) == 2 && CFStringGetCharacterAtIndex(str, 1) == ':') {
3481 CFArrayInsertValueAtIndex(urlComponents, 0, CFSTR("")); // So we get a leading '/' below
3482 i = 2; // Skip over the drive letter and the empty string we just inserted
3483 }
3484 CFIndex c;
3485 for (c = CFArrayGetCount(urlComponents); i < c; i ++) {
3486 CFStringRef fileComp = (CFStringRef)CFArrayGetValueAtIndex(urlComponents,i);
3487 CFStringRef urlComp = _replacePathIllegalCharacters(fileComp, alloc, false);
3488 if (!urlComp) {
3489 // Couldn't decode fileComp
3490 CFRelease(urlComponents);
3491 return NULL;
3492 }
3493 if (urlComp != fileComp) {
3494 CFArraySetValueAtIndex(urlComponents, i, urlComp);
3495 }
3496 CFRelease(urlComp);
3497 }
3498
3499 if (isDir) {
3500 if (CFStringGetLength((CFStringRef)CFArrayGetValueAtIndex(urlComponents, CFArrayGetCount(urlComponents) - 1)) != 0)
3501 CFArrayAppendValue(urlComponents, CFSTR(""));
3502 }
3503 return urlComponents;
3504 }
3505
3506 static CFStringRef WindowsPathToURLPath(CFStringRef path, CFAllocatorRef alloc, Boolean isDir) {
3507 CFArrayRef urlComponents;
3508 CFStringRef str;
3509
3510 if (CFStringGetLength(path) == 0) return CFStringCreateWithCString(alloc, "", kCFStringEncodingASCII);
3511 urlComponents = WindowsPathToURLComponents(path, alloc, isDir);
3512 if (!urlComponents) return CFStringCreateWithCString(alloc, "", kCFStringEncodingASCII);
3513
3514 // WindowsPathToURLComponents already added percent escapes for us; no need to add them again here.
3515 str = CFStringCreateByCombiningStrings(alloc, urlComponents, CFSTR("/"));
3516 CFRelease(urlComponents);
3517 return str;
3518 }
3519
3520 static CFStringRef POSIXPathToURLPath(CFStringRef path, CFAllocatorRef alloc, Boolean isDirectory) {
3521 CFStringRef pathString = _replacePathIllegalCharacters(path, alloc, true);
3522 if (isDirectory && CFStringGetCharacterAtIndex(path, CFStringGetLength(path)-1) != '/') {
3523 CFStringRef tmp = CFStringCreateWithFormat(alloc, NULL, CFSTR("%@/"), pathString);
3524 CFRelease(pathString);
3525 pathString = tmp;
3526 }
3527 return pathString;
3528 }
3529
3530 static CFStringRef URLPathToPOSIXPath(CFStringRef path, CFAllocatorRef allocator, CFStringEncoding encoding) {
3531 // This is the easiest case; just remove the percent escape codes and we're done
3532 CFStringRef result = CFURLCreateStringByReplacingPercentEscapesUsingEncoding(allocator, path, CFSTR(""), encoding);
3533 if (result) {
3534 CFIndex length = CFStringGetLength(result);
3535 if (length > 1 && CFStringGetCharacterAtIndex(result, length-1) == '/') {
3536 CFStringRef tmp = CFStringCreateWithSubstring(allocator, result, CFRangeMake(0, length-1));
3537 CFRelease(result);
3538 result = tmp;
3539 }
3540 }
3541 return result;
3542 }
3543
3544
3545 static CFStringRef URLPathToWindowsPath(CFStringRef path, CFAllocatorRef allocator, CFStringEncoding encoding) {
3546 // Check for a drive letter, then flip all the slashes
3547 CFStringRef result;
3548 CFArrayRef tmp = CFStringCreateArrayBySeparatingStrings(allocator, path, CFSTR("/"));
3549 SInt32 count = CFArrayGetCount(tmp);
3550 CFMutableArrayRef components = CFArrayCreateMutableCopy(allocator, count, tmp);
3551 CFStringRef newPath;
3552
3553
3554
3555 CFRelease(tmp);
3556 if (CFStringGetLength((CFStringRef)CFArrayGetValueAtIndex(components,count-1)) == 0) {
3557 CFArrayRemoveValueAtIndex(components, count-1);
3558 count --;
3559 }
3560
3561 if (count > 1 && CFStringGetLength((CFStringRef)CFArrayGetValueAtIndex(components, 0)) == 0) {
3562 // Absolute path; we need to check for a drive letter in the second component, and if so, remove the first component
3563 CFStringRef firstComponent = CFURLCreateStringByReplacingPercentEscapesUsingEncoding(allocator, (CFStringRef)CFArrayGetValueAtIndex(components, 1), CFSTR(""), encoding);
3564 UniChar ch;
3565
3566 {
3567 if (firstComponent) {
3568 if (CFStringGetLength(firstComponent) == 2 && ((ch = CFStringGetCharacterAtIndex(firstComponent, 1)) == '|' || ch == ':')) {
3569 // Drive letter
3570 CFArrayRemoveValueAtIndex(components, 0);
3571 if (ch == '|') {
3572 CFStringRef driveStr = CFStringCreateWithFormat(allocator, NULL, CFSTR("%c:"), CFStringGetCharacterAtIndex(firstComponent, 0));
3573 CFArraySetValueAtIndex(components, 0, driveStr);
3574 CFRelease(driveStr);
3575 }
3576 }
3577 CFRelease(firstComponent);
3578 }
3579 }
3580 }
3581 newPath = CFStringCreateByCombiningStrings(allocator, components, CFSTR("\\"));
3582 CFRelease(components);
3583 result = CFURLCreateStringByReplacingPercentEscapesUsingEncoding(allocator, newPath, CFSTR(""), encoding);
3584 CFRelease(newPath);
3585 return result;
3586 }
3587
3588
3589
3590 // converts url from a file system path representation to a standard representation
3591 static void _convertToURLRepresentation(struct __CFURL *url) {
3592 CFStringRef path = NULL;
3593 Boolean isDir = ((url->_flags & IS_DIRECTORY) != 0);
3594 Boolean isFileReferencePath = false;
3595 CFAllocatorRef alloc = CFGetAllocator(url);
3596
3597 #if DEBUG_URL_MEMORY_USAGE
3598 numFileURLsConverted ++;
3599 #endif
3600
3601 switch (URL_PATH_TYPE(url)) {
3602 case kCFURLPOSIXPathStyle:
3603 if (url->_flags & POSIX_AND_URL_PATHS_MATCH) {
3604 path = (CFStringRef)CFRetain(url->_string);
3605 } else {
3606 path = POSIXPathToURLPath(url->_string, alloc, isDir);
3607 }
3608 break;
3609 case kCFURLWindowsPathStyle:
3610 path = WindowsPathToURLPath(url->_string, alloc, isDir);
3611 break;
3612 }
3613 CFAssert2(path != NULL, __kCFLogAssertion, "%s(): Encountered malformed file system URL %@", __PRETTY_FUNCTION__, url);
3614 if (!url->_base) {
3615 CFMutableStringRef str = CFStringCreateMutable(alloc, 0);
3616 CFStringAppend(str, isFileReferencePath ? CFSTR("file://") : CFSTR("file://localhost"));
3617 CFStringAppend(str, path);
3618 url->_flags = (url->_flags & (IS_DIRECTORY)) | (FULL_URL_REPRESENTATION << 16) | IS_DECOMPOSABLE | IS_ABSOLUTE | IS_PARSED | HAS_SCHEME | HAS_FILE_SCHEME | HAS_HOST | HAS_PATH | ORIGINAL_AND_URL_STRINGS_MATCH | ( isFileReferencePath ? PATH_HAS_FILE_ID : 0 );
3619 CFRelease(url->_string);
3620 url->_string = str;
3621 url->ranges = (CFRange *)CFAllocatorAllocate(alloc, sizeof(CFRange) * 3, 0);
3622 url->ranges[0] = CFRangeMake(0, 4);
3623 url->ranges[1] = CFRangeMake(7, isFileReferencePath ? 0 : 9);
3624 url->ranges[2] = CFRangeMake(url->ranges[1].location + url->ranges[1].length, CFStringGetLength(path));
3625 CFRelease(path);
3626 } else {
3627 CFRelease(url->_string);
3628 url->_flags = (url->_flags & (IS_DIRECTORY)) | (FULL_URL_REPRESENTATION << 16) | IS_DECOMPOSABLE | IS_PARSED | HAS_PATH | ORIGINAL_AND_URL_STRINGS_MATCH;
3629 url->_string = path;
3630 url->ranges = (CFRange *)CFAllocatorAllocate(alloc, sizeof(CFRange), 0);
3631 *(url->ranges) = CFRangeMake(0, CFStringGetLength(path));
3632 }
3633 }
3634
3635 // relativeURL is known to be a file system URL whose base is a matching file system URL
3636 static CFURLRef _CFURLCopyAbsoluteFileURL(CFURLRef relativeURL) {
3637 CFAllocatorRef alloc = CFGetAllocator(relativeURL);
3638 CFURLPathStyle fsType = URL_PATH_TYPE(relativeURL);
3639 CFURLRef base = relativeURL->_base;
3640 CFStringRef newPath = _resolveFileSystemPaths(relativeURL->_string, base->_string, (base->_flags & IS_DIRECTORY) != 0, fsType, alloc);
3641 CFURLRef result = CFURLCreateWithFileSystemPath(alloc, newPath, fsType, (relativeURL->_flags & IS_DIRECTORY) != 0);
3642 CFRelease(newPath);
3643 return result;
3644 }
3645
3646 // Caller must release the returned string
3647 static CFStringRef _resolveFileSystemPaths(CFStringRef relativePath, CFStringRef basePath, Boolean baseIsDir, CFURLPathStyle fsType, CFAllocatorRef alloc) {
3648 CFIndex baseLen = CFStringGetLength(basePath);
3649 CFIndex relLen = CFStringGetLength(relativePath);
3650 UniChar pathDelimiter = PATH_DELIM_FOR_TYPE(fsType);
3651 UniChar *buf = (UniChar *)CFAllocatorAllocate(alloc, sizeof(UniChar)*(relLen + baseLen + 2), 0);
3652 CFStringGetCharacters(basePath, CFRangeMake(0, baseLen), buf);
3653 if (baseIsDir) {
3654 if (buf[baseLen-1] != pathDelimiter) {
3655 buf[baseLen] = pathDelimiter;
3656 baseLen ++;
3657 }
3658 } else {
3659 UniChar *ptr = buf + baseLen - 1;
3660 while (ptr > buf && *ptr != pathDelimiter) {
3661 ptr --;
3662 }
3663 baseLen = ptr - buf + 1;
3664 }
3665 if (fsType == kCFURLHFSPathStyle) {
3666 // HFS relative paths will begin with a colon, so we must remove the trailing colon from the base path first.
3667 baseLen --;
3668 }
3669 CFStringGetCharacters(relativePath, CFRangeMake(0, relLen), buf + baseLen);
3670 *(buf + baseLen + relLen) = '\0';
3671 return _resolvedPath(buf, buf + baseLen + relLen, pathDelimiter, false, true, alloc);
3672 }
3673
3674 CFURLRef _CFURLCreateCurrentDirectoryURL(CFAllocatorRef allocator) {
3675 CFURLRef url = NULL;
3676 uint8_t buf[CFMaxPathSize + 1];
3677 if (_CFGetCurrentDirectory((char *)buf, CFMaxPathLength)) {
3678 url = CFURLCreateFromFileSystemRepresentation(allocator, buf, strlen((char *)buf), true);
3679 }
3680 return url;
3681 }
3682
3683 CFURLRef CFURLCreateWithFileSystemPath(CFAllocatorRef allocator, CFStringRef filePath, CFURLPathStyle fsType, Boolean isDirectory) {
3684 Boolean isAbsolute = true;
3685 CFIndex len;
3686 CFURLRef baseURL, result;
3687
3688 CFAssert2(fsType == kCFURLPOSIXPathStyle || fsType == kCFURLHFSPathStyle || fsType == kCFURLWindowsPathStyle || ASSERT_CHECK_PATHSTYLE(fsType), __kCFLogAssertion, "%s(): encountered unknown path style %d", __PRETTY_FUNCTION__, fsType);
3689
3690 CFAssert1(filePath != NULL, __kCFLogAssertion, "%s(): NULL filePath argument not permitted", __PRETTY_FUNCTION__);
3691
3692 len = CFStringGetLength(filePath);
3693
3694 switch(fsType) {
3695 case kCFURLPOSIXPathStyle:
3696 isAbsolute = (len > 0 && CFStringGetCharacterAtIndex(filePath, 0) == '/');
3697 break;
3698 case kCFURLWindowsPathStyle:
3699 isAbsolute = (len >= 3 && CFStringGetCharacterAtIndex(filePath, 1) == ':' && CFStringGetCharacterAtIndex(filePath, 2) == '\\');
3700 /* Absolute path under Win32 can begin with "\\"
3701 * (Sergey Zubarev)
3702 */
3703 if (!isAbsolute) isAbsolute = (len > 2 && CFStringGetCharacterAtIndex(filePath, 0) == '\\' && CFStringGetCharacterAtIndex(filePath, 1) == '\\');
3704 break;
3705 case kCFURLHFSPathStyle:
3706 isAbsolute = (len > 0 && CFStringGetCharacterAtIndex(filePath, 0) != ':');
3707 break;
3708 }
3709 if (isAbsolute) {
3710 baseURL = NULL;
3711 } else {
3712 baseURL = _CFURLCreateCurrentDirectoryURL(allocator);
3713 }
3714 result = CFURLCreateWithFileSystemPathRelativeToBase(allocator, filePath, fsType, isDirectory, baseURL);
3715 if (baseURL) CFRelease(baseURL);
3716 return result;
3717 }
3718
3719 CF_EXPORT CFURLRef CFURLCreateWithFileSystemPathRelativeToBase(CFAllocatorRef allocator, CFStringRef filePath, CFURLPathStyle fsType, Boolean isDirectory, CFURLRef baseURL) {
3720 CFURLRef url;
3721 Boolean isAbsolute = true, releaseFilePath = false;
3722 UniChar pathDelim = '\0';
3723 CFIndex len;
3724
3725 CFAssert1(filePath != NULL, __kCFLogAssertion, "%s(): NULL path string not permitted", __PRETTY_FUNCTION__);
3726 CFAssert2(fsType == kCFURLPOSIXPathStyle || fsType == kCFURLHFSPathStyle || fsType == kCFURLWindowsPathStyle || ASSERT_CHECK_PATHSTYLE(fsType), __kCFLogAssertion, "%s(): encountered unknown path style %d", __PRETTY_FUNCTION__, fsType);
3727
3728 len = CFStringGetLength(filePath);
3729
3730 switch(fsType) {
3731 case kCFURLPOSIXPathStyle:
3732 isAbsolute = (len > 0 && CFStringGetCharacterAtIndex(filePath, 0) == '/');
3733
3734 pathDelim = '/';
3735 break;
3736 case kCFURLWindowsPathStyle:
3737 isAbsolute = (len >= 3 && CFStringGetCharacterAtIndex(filePath, 1) == ':' && CFStringGetCharacterAtIndex(filePath, 2) == '\\');
3738 /* Absolute path under Win32 can begin with "\\"
3739 * (Sergey Zubarev)
3740 */
3741 if (!isAbsolute)
3742 isAbsolute = (len > 2 && CFStringGetCharacterAtIndex(filePath, 0) == '\\' && CFStringGetCharacterAtIndex(filePath, 1) == '\\');
3743 pathDelim = '\\';
3744 break;
3745 case kCFURLHFSPathStyle:
3746 { CFRange fullStrRange = CFRangeMake( 0, CFStringGetLength( filePath ) );
3747
3748 isAbsolute = (len > 0 && CFStringGetCharacterAtIndex(filePath, 0) != ':');
3749 pathDelim = ':';
3750
3751 if ( _CFExecutableLinkedOnOrAfter( CFSystemVersionTiger ) &&
3752 filePath && CFStringFindWithOptions( filePath, CFSTR("::"), fullStrRange, 0, NULL ) ) {
3753 UniChar * chars = (UniChar *) malloc( fullStrRange.length * sizeof( UniChar ) );
3754 CFIndex index, writeIndex, firstColonOffset = -1;
3755
3756 CFStringGetCharacters( filePath, fullStrRange, chars );
3757
3758 for ( index = 0, writeIndex = 0 ; index < fullStrRange.length; index ++ ) {
3759 if ( chars[ index ] == ':' ) {
3760 if ( index + 1 < fullStrRange.length && chars[ index + 1 ] == ':' ) {
3761
3762 // Don't let :: go off the 'top' of the path -- which means that there always has to be at
3763 // least one ':' to the left of the current write position to go back to.
3764 if ( writeIndex > 0 && firstColonOffset >= 0 )
3765 {
3766 writeIndex --;
3767 while ( writeIndex > 0 && writeIndex >= firstColonOffset && chars[ writeIndex ] != ':' )
3768 writeIndex --;
3769 }
3770 index ++; // skip over the first ':', so we replace the ':' which is there with a new one
3771 }
3772
3773 if ( firstColonOffset == -1 )
3774 firstColonOffset = writeIndex;
3775 }
3776
3777 chars[ writeIndex ++ ] = chars[ index ];
3778 }
3779
3780 if ( releaseFilePath && filePath )
3781 CFRelease( filePath );
3782
3783 filePath = CFStringCreateWithCharacters( allocator, chars, writeIndex );
3784 // reset len because a canonical HFS path can be a different length than the original CFString
3785 len = CFStringGetLength(filePath);
3786 releaseFilePath = true;
3787
3788 free( chars );
3789 }
3790
3791 break;
3792 }
3793 }
3794 if (isAbsolute) {
3795 baseURL = NULL;
3796 }
3797
3798 if (isDirectory && len > 0 && CFStringGetCharacterAtIndex(filePath, len-1) != pathDelim) {
3799 CFMutableStringRef tempRef = CFStringCreateMutable(allocator, 0);
3800 CFStringAppend(tempRef, filePath);
3801 CFStringAppendCharacters(tempRef, &pathDelim, 1);
3802 if ( releaseFilePath && filePath ) CFRelease( filePath );
3803 filePath = tempRef;
3804 releaseFilePath = true;
3805 } else if (!isDirectory && len > 0 && CFStringGetCharacterAtIndex(filePath, len-1) == pathDelim) {
3806 if (len == 1 || CFStringGetCharacterAtIndex(filePath, len-2) == pathDelim) {
3807 // Override isDirectory
3808 isDirectory = true;
3809 } else {
3810 CFStringRef tempRef = CFStringCreateWithSubstring(allocator, filePath, CFRangeMake(0, len-1));
3811 if ( releaseFilePath && filePath )
3812 CFRelease( filePath );
3813 filePath = tempRef;
3814 releaseFilePath = true;
3815 }
3816 }
3817 if (!filePath || CFStringGetLength(filePath) == 0) {
3818 if (releaseFilePath && filePath) CFRelease(filePath);
3819 return NULL;
3820 }
3821 url = _CFURLAlloc(allocator);
3822 _CFURLInit((struct __CFURL *)url, filePath, fsType, baseURL);
3823 if (releaseFilePath) CFRelease(filePath);
3824 if (isDirectory) ((struct __CFURL *)url)->_flags |= IS_DIRECTORY;
3825 if (fsType == kCFURLPOSIXPathStyle) {
3826 // Check if relative path is equivalent to URL representation; this will be true if url->_string contains only characters from the unreserved character set, plus '/' to delimit the path, plus ';', '@', '&', '=', '+', '$', ',' (according to RFC 2396) -- REW, 12/1/2000
3827 // Per Section 5 of RFC 2396, there's a special problem if a colon apears in the first path segment - in this position, it can be mistaken for the scheme name. Otherwise, it's o.k., and can be safely identified as part of the path. In this one case, we need to prepend "./" to make it clear what's going on.... -- REW, 8/24/2001
3828 CFStringInlineBuffer buf;
3829 Boolean sawSlash = FALSE;
3830 Boolean mustPrependDotSlash = FALSE;
3831 CFIndex idx, length = CFStringGetLength(url->_string);
3832 CFStringInitInlineBuffer(url->_string, &buf, CFRangeMake(0, length));
3833 for (idx = 0; idx < length; idx ++) {
3834 UniChar ch = CFStringGetCharacterFromInlineBuffer(&buf, idx);
3835 if (!isPathLegalCharacter(ch)) break;
3836 if (!sawSlash) {
3837 if (ch == '/') {
3838 sawSlash = TRUE;
3839 } else if (ch == ':') {
3840 mustPrependDotSlash = TRUE;
3841 }
3842 }
3843 }
3844 if (idx == length) {
3845 ((struct __CFURL *)url)->_flags |= POSIX_AND_URL_PATHS_MATCH;
3846 }
3847 if (mustPrependDotSlash) {
3848 CFMutableStringRef newString = CFStringCreateMutable(allocator, 0);
3849 CFStringAppend(newString, CFSTR("./"));
3850 CFStringAppend(newString, url->_string);
3851 CFRelease(url->_string);
3852 ((struct __CFURL *)url)->_string = newString;
3853 }
3854 }
3855 return url;
3856 }
3857
3858 static Boolean _pathHasFileIDPrefix( CFStringRef path )
3859 {
3860 // path is not NULL, path has prefix "/.file/" and has at least one character following the prefix.
3861 static const CFStringRef fileIDPrefix = CFSTR( "/" FILE_ID_PREFIX "/" );
3862 return path && CFStringHasPrefix( path, fileIDPrefix ) && CFStringGetLength( path ) > CFStringGetLength( fileIDPrefix );
3863 }
3864
3865
3866 static Boolean _pathHasFileIDOnly( CFStringRef path )
3867 {
3868 // Is file ID rooted and contains no additonal path segments
3869 CFRange slashRange;
3870 return _pathHasFileIDPrefix( path ) && ( !CFStringFindWithOptions( path, CFSTR("/"), CFRangeMake( sizeof(FILE_ID_PREFIX) + 1, CFStringGetLength( path ) - sizeof(FILE_ID_PREFIX) - 1), 0, &slashRange ) || slashRange.location == CFStringGetLength( path ) - 1 );
3871 }
3872
3873
3874 CF_EXPORT CFStringRef CFURLCopyFileSystemPath(CFURLRef anURL, CFURLPathStyle pathStyle) {
3875 CFAssert2(pathStyle == kCFURLPOSIXPathStyle || pathStyle == kCFURLHFSPathStyle || pathStyle == kCFURLWindowsPathStyle || ASSERT_CHECK_PATHSTYLE(pathStyle), __kCFLogAssertion, "%s(): Encountered unknown path style %d", __PRETTY_FUNCTION__, pathStyle);
3876
3877 return CFURLCreateStringWithFileSystemPath(CFGetAllocator(anURL), anURL, pathStyle, false);
3878 }
3879
3880
3881 // There is no matching ObjC method for this functionality; because this function sits on top of the CFURL primitives, it's o.k. not to check for the need to dispatch an ObjC method instead, but this means care must be taken that this function never call anything that will result in dereferencing anURL without first checking for an ObjC dispatch. -- REW, 10/29/98
3882 CFStringRef CFURLCreateStringWithFileSystemPath(CFAllocatorRef allocator, CFURLRef anURL, CFURLPathStyle fsType, Boolean resolveAgainstBase) {
3883 CFURLRef base = resolveAgainstBase ? CFURLGetBaseURL(anURL) : NULL;
3884 CFStringRef basePath = base ? CFURLCreateStringWithFileSystemPath(allocator, base, fsType, false) : NULL;
3885 CFStringRef relPath = NULL;
3886
3887 if (!CF_IS_OBJC(__kCFURLTypeID, anURL)) {
3888 // We can grope the ivars
3889 CFURLPathStyle myType = URL_PATH_TYPE(anURL);
3890 if (myType == fsType) {
3891 relPath = (CFStringRef)CFRetain(anURL->_string);
3892 } else if (fsType == kCFURLPOSIXPathStyle && myType == FULL_URL_REPRESENTATION) {
3893 if (!(anURL->_flags & IS_PARSED)) {
3894 _parseComponentsOfURL(anURL);
3895 }
3896 if (anURL->_flags & POSIX_AND_URL_PATHS_MATCH) {
3897 relPath = _retainedComponentString(anURL, HAS_PATH, true, true);
3898 }
3899 }
3900 }
3901
3902 if (relPath == NULL) {
3903 CFStringRef urlPath = CFURLCopyPath(anURL);
3904 CFStringEncoding enc = (anURL->_flags & IS_OLD_UTF8_STYLE) ? kCFStringEncodingUTF8 : anURL->_encoding;
3905 if (urlPath) {
3906 switch (fsType) {
3907 case kCFURLPOSIXPathStyle:
3908 relPath = URLPathToPOSIXPath(urlPath, allocator, enc);
3909 break;
3910 case kCFURLHFSPathStyle:
3911 relPath = NULL;
3912 break;
3913 case kCFURLWindowsPathStyle:
3914 relPath = URLPathToWindowsPath(urlPath, allocator, enc);
3915 break;
3916 default:
3917 CFAssert2(true, __kCFLogAssertion, "%s(): Received unknown path type %d", __PRETTY_FUNCTION__, fsType);
3918 }
3919 CFRelease(urlPath);
3920 }
3921 }
3922
3923 // For Tiger, leave this behavior in for all path types. For Leopard, it would be nice to remove this entirely
3924 // and do a linked-on-or-later check so we don't break third parties.
3925 // See <rdar://problem/4003028> Converting volume name from POSIX to HFS form fails and
3926 // <rdar://problem/4018895> CF needs to back out 4003028 for icky details.
3927 if ( relPath && CFURLHasDirectoryPath(anURL) && CFStringGetLength(relPath) > 1 && CFStringGetCharacterAtIndex(relPath, CFStringGetLength(relPath)-1) == PATH_DELIM_FOR_TYPE(fsType)) {
3928 CFStringRef tmp = CFStringCreateWithSubstring(allocator, relPath, CFRangeMake(0, CFStringGetLength(relPath)-1));
3929 CFRelease(relPath);
3930 relPath = tmp;
3931 }
3932
3933 // Note that !resolveAgainstBase implies !base
3934 if (!basePath || !relPath) {
3935 return relPath;
3936 } else {
3937 CFStringRef result = _resolveFileSystemPaths(relPath, basePath, CFURLHasDirectoryPath(base), fsType, allocator);
3938 CFRelease(basePath);
3939 CFRelease(relPath);
3940 return result;
3941 }
3942 }
3943
3944 Boolean CFURLGetFileSystemRepresentation(CFURLRef url, Boolean resolveAgainstBase, uint8_t *buffer, CFIndex bufLen) {
3945 CFStringRef path;
3946 CFAllocatorRef alloc = CFGetAllocator(url);
3947
3948 if (!url) return false;
3949 #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED
3950 path = CFURLCreateStringWithFileSystemPath(alloc, url, kCFURLPOSIXPathStyle, resolveAgainstBase);
3951 if (path) {
3952 Boolean convResult = _CFStringGetFileSystemRepresentation(path, buffer, bufLen);
3953 CFRelease(path);
3954 return convResult;
3955 }
3956 #elif DEPLOYMENT_TARGET_WINDOWS
3957 path = CFURLCreateStringWithFileSystemPath(alloc, url, kCFURLWindowsPathStyle, resolveAgainstBase);
3958 if (path) {
3959 CFIndex usedLen;
3960 CFIndex pathLen = CFStringGetLength(path);
3961 CFIndex numConverted = CFStringGetBytes(path, CFRangeMake(0, pathLen), CFStringFileSystemEncoding(), 0, true, buffer, bufLen-1, &usedLen); // -1 because we need one byte to zero-terminate.
3962 CFRelease(path);
3963 if (numConverted == pathLen) {
3964 buffer[usedLen] = '\0';
3965 return true;
3966 }
3967 }
3968 #else
3969 #error Unknown or unspecified DEPLOYMENT_TARGET
3970 #endif
3971 return false;
3972 }
3973
3974 #if DEPLOYMENT_TARGET_WINDOWS
3975 CF_EXPORT Boolean _CFURLGetWideFileSystemRepresentation(CFURLRef url, Boolean resolveAgainstBase, wchar_t *buffer, CFIndex bufferLength) {
3976 CFStringRef path = CFURLCreateStringWithFileSystemPath(CFGetAllocator(url), url, kCFURLWindowsPathStyle, resolveAgainstBase);
3977 CFIndex pathLength, charsConverted, usedBufLen;
3978 if (!path) return false;
3979 pathLength = CFStringGetLength(path);
3980 if (pathLength+1 > bufferLength) {
3981 CFRelease(path);
3982 return false;
3983 }
3984 charsConverted = CFStringGetBytes(path, CFRangeMake(0, pathLength), kCFStringEncodingUTF16, 0, false, (UInt8 *)buffer, bufferLength*sizeof(wchar_t), &usedBufLen);
3985 // CFStringGetCharacters(path, CFRangeMake(0, pathLength), (UniChar *)buffer);
3986 CFRelease(path);
3987 if (charsConverted != pathLength || usedBufLen%sizeof(wchar_t) != 0) {
3988 return false;
3989 } else {
3990 buffer[usedBufLen/sizeof(wchar_t)] = 0;
3991 // buffer[pathLength] = 0;
3992 return true;
3993 }
3994 }
3995 #endif
3996
3997 CFURLRef CFURLCreateFromFileSystemRepresentation(CFAllocatorRef allocator, const uint8_t *buffer, CFIndex bufLen, Boolean isDirectory) {
3998 CFStringRef path = CFStringCreateWithBytes(allocator, buffer, bufLen, CFStringFileSystemEncoding(), false);
3999 CFURLRef newURL;
4000 if (!path) return NULL;
4001 #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED
4002 newURL = CFURLCreateWithFileSystemPath(allocator, path, kCFURLPOSIXPathStyle, isDirectory);
4003 #elif DEPLOYMENT_TARGET_WINDOWS
4004 newURL = CFURLCreateWithFileSystemPath(allocator, path, kCFURLWindowsPathStyle, isDirectory);
4005 #else
4006 #error Unknown or unspecified DEPLOYMENT_TARGET
4007 #endif
4008 CFRelease(path);
4009 return newURL;
4010 }
4011
4012 CF_EXPORT CFURLRef CFURLCreateFromFileSystemRepresentationRelativeToBase(CFAllocatorRef allocator, const uint8_t *buffer, CFIndex bufLen, Boolean isDirectory, CFURLRef baseURL) {
4013 CFStringRef path = CFStringCreateWithBytes(allocator, buffer, bufLen, CFStringFileSystemEncoding(), false);
4014 CFURLRef newURL;
4015 if (!path) return NULL;
4016 #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED
4017 newURL = CFURLCreateWithFileSystemPathRelativeToBase(allocator, path, kCFURLPOSIXPathStyle, isDirectory, baseURL);
4018 #elif DEPLOYMENT_TARGET_WINDOWS
4019 newURL = CFURLCreateWithFileSystemPathRelativeToBase(allocator, path, kCFURLWindowsPathStyle, isDirectory, baseURL);
4020 #else
4021 #error Unknown or unspecified DEPLOYMENT_TARGET
4022 #endif
4023 CFRelease(path);
4024 return newURL;
4025 }
4026
4027
4028 /******************************/
4029 /* Support for path utilities */
4030 /******************************/
4031
4032 // Assumes url is a CFURL (not an Obj-C NSURL)
4033 static CFRange _rangeOfLastPathComponent(CFURLRef url) {
4034 UInt32 pathType = URL_PATH_TYPE(url);
4035 CFRange pathRg, componentRg;
4036
4037 if (pathType == FULL_URL_REPRESENTATION) {
4038 if (!(url->_flags & IS_PARSED)) _parseComponentsOfURL(url);
4039 pathRg = _rangeForComponent(url->_flags, url->ranges, HAS_PATH);
4040 } else {
4041 pathRg = CFRangeMake(0, CFStringGetLength(url->_string));
4042 }
4043
4044 if (pathRg.location == kCFNotFound || pathRg.length == 0) {
4045 // No path
4046 return pathRg;
4047 }
4048 if (CFStringGetCharacterAtIndex(url->_string, pathRg.location + pathRg.length - 1) == PATH_DELIM_FOR_TYPE(pathType)) {
4049 pathRg.length --;
4050 if (pathRg.length == 0) {
4051 pathRg.length ++;
4052 return pathRg;
4053 }
4054 }
4055 if (CFStringFindWithOptions(url->_string, PATH_DELIM_AS_STRING_FOR_TYPE(pathType), pathRg, kCFCompareBackwards, &componentRg)) {
4056 componentRg.location ++;
4057 componentRg.length = pathRg.location + pathRg.length - componentRg.location;
4058 } else {
4059 componentRg = pathRg;
4060 }
4061 return componentRg;
4062 }
4063
4064 CFStringRef CFURLCopyLastPathComponent(CFURLRef url) {
4065 CFStringRef result;
4066
4067 if (CF_IS_OBJC(__kCFURLTypeID, url)) {
4068 CFStringRef path = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle);
4069 CFIndex length;
4070 CFRange rg, compRg;
4071 if (!path) return NULL;
4072 rg = CFRangeMake(0, CFStringGetLength(path));
4073 if ( rg.length == 0 ) return path;
4074 length = rg.length; // Remember this for comparison later
4075 if (CFStringGetCharacterAtIndex(path, rg.length - 1) == '/' ) {
4076 rg.length --;
4077 }
4078 if ( rg.length == 0 )
4079 {
4080 // If we have reduced the string to empty, then it's "/", and that's what we return as
4081 // the last path component.
4082 return path;
4083 }
4084 else if (CFStringFindWithOptions(path, CFSTR("/"), rg, kCFCompareBackwards, &compRg)) {
4085 rg.length = rg.location + rg.length - (compRg.location+1);
4086 rg.location = compRg.location + 1;
4087 }
4088 if (rg.location == 0 && rg.length == length) {
4089 result = path;
4090 } else {
4091 result = CFStringCreateWithSubstring(CFGetAllocator(url), path, rg);
4092 CFRelease(path);
4093 }
4094 } else {
4095 CFRange rg = _rangeOfLastPathComponent(url);
4096 if (rg.location == kCFNotFound || rg.length == 0) {
4097 // No path
4098 return (CFStringRef)CFRetain(CFSTR(""));
4099 }
4100 if (rg.length == 1 && CFStringGetCharacterAtIndex(url->_string, rg.location) == PATH_DELIM_FOR_TYPE(URL_PATH_TYPE(url))) {
4101 return (CFStringRef)CFRetain(CFSTR("/"));
4102 }
4103 result = CFStringCreateWithSubstring(CFGetAllocator(url), url->_string, rg);
4104 if (URL_PATH_TYPE(url) == FULL_URL_REPRESENTATION && !(url->_flags & POSIX_AND_URL_PATHS_MATCH)) {
4105 CFStringRef tmp;
4106 if (url->_flags & IS_OLD_UTF8_STYLE || url->_encoding == kCFStringEncodingUTF8) {
4107 tmp = CFURLCreateStringByReplacingPercentEscapes(CFGetAllocator(url), result, CFSTR(""));
4108 } else {
4109 tmp = CFURLCreateStringByReplacingPercentEscapesUsingEncoding(CFGetAllocator(url), result, CFSTR(""), url->_encoding);
4110 }
4111 CFRelease(result);
4112 result = tmp;
4113 }
4114 }
4115 return result;
4116 }
4117
4118 CFStringRef CFURLCopyPathExtension(CFURLRef url) {
4119 CFStringRef lastPathComp = CFURLCopyLastPathComponent(url);
4120 CFStringRef ext = NULL;
4121
4122 if (lastPathComp) {
4123 CFRange rg = CFStringFind(lastPathComp, CFSTR("."), kCFCompareBackwards);
4124 if (rg.location != kCFNotFound) {
4125 rg.location ++;
4126 rg.length = CFStringGetLength(lastPathComp) - rg.location;
4127 if (rg.length > 0) {
4128 ext = CFStringCreateWithSubstring(CFGetAllocator(url), lastPathComp, rg);
4129 } else {
4130 ext = (CFStringRef)CFRetain(CFSTR(""));
4131 }
4132 }
4133 CFRelease(lastPathComp);
4134 }
4135 return ext;
4136 }
4137
4138 CFURLRef CFURLCreateCopyAppendingPathComponent(CFAllocatorRef allocator, CFURLRef url, CFStringRef pathComponent, Boolean isDirectory) {
4139 UInt32 fsType;
4140 CFURLRef result;
4141 url = _CFURLFromNSURL(url);
4142 __CFGenericValidateType(url, __kCFURLTypeID);
4143 CFAssert1(pathComponent != NULL, __kCFLogAssertion, "%s(): Cannot be called with a NULL component to append", __PRETTY_FUNCTION__);
4144
4145 fsType = URL_PATH_TYPE(url);
4146 if (fsType != FULL_URL_REPRESENTATION && CFStringFindWithOptions(pathComponent, PATH_DELIM_AS_STRING_FOR_TYPE(fsType), CFRangeMake(0, CFStringGetLength(pathComponent)), 0, NULL)) {
4147 // Must convert to full representation, and then work with it
4148 fsType = FULL_URL_REPRESENTATION;
4149 _convertToURLRepresentation((struct __CFURL *)url);
4150 }
4151
4152 if (fsType == FULL_URL_REPRESENTATION) {
4153 CFMutableStringRef newString;
4154 CFStringRef newComp;
4155 CFRange pathRg;
4156 if (!(url->_flags & IS_PARSED)) _parseComponentsOfURL(url);
4157 if (!(url->_flags & HAS_PATH)) return NULL;
4158
4159 newString = CFStringCreateMutableCopy(allocator, 0, url->_string);
4160 newComp = CFURLCreateStringByAddingPercentEscapes(allocator, pathComponent, NULL, CFSTR(";?"), (url->_flags & IS_OLD_UTF8_STYLE) ? kCFStringEncodingUTF8 : url->_encoding);
4161 pathRg = _rangeForComponent(url->_flags, url->ranges, HAS_PATH);
4162 if (!pathRg.length || CFStringGetCharacterAtIndex(url->_string, pathRg.location + pathRg.length - 1) != '/') {
4163 CFStringInsert(newString, pathRg.location + pathRg.length, CFSTR("/"));
4164 pathRg.length ++;
4165 }
4166 CFStringInsert(newString, pathRg.location + pathRg.length, newComp);
4167 if (isDirectory) {
4168 CFStringInsert(newString, pathRg.location + pathRg.length + CFStringGetLength(newComp), CFSTR("/"));
4169 }
4170 CFRelease(newComp);
4171 result = _CFURLCreateWithArbitraryString(allocator, newString, url->_base);
4172 CFRelease(newString);
4173 } else {
4174 UniChar pathDelim = PATH_DELIM_FOR_TYPE(fsType);
4175 CFStringRef newString;
4176 if (CFStringGetCharacterAtIndex(url->_string, CFStringGetLength(url->_string) - 1) != pathDelim) {
4177 if (isDirectory) {
4178 newString = CFStringCreateWithFormat(allocator, NULL, CFSTR("%@%c%@%c"), url->_string, pathDelim, pathComponent, pathDelim);
4179 } else {
4180 newString = CFStringCreateWithFormat(allocator, NULL, CFSTR("%@%c%@"), url->_string, pathDelim, pathComponent);
4181 }
4182 } else {
4183 if (isDirectory) {
4184 newString = CFStringCreateWithFormat(allocator, NULL, CFSTR("%@%@%c"), url->_string, pathComponent, pathDelim);
4185 } else {
4186 newString = CFStringCreateWithFormat(allocator, NULL, CFSTR("%@%@"), url->_string, pathComponent);
4187 }
4188 }
4189 result = CFURLCreateWithFileSystemPathRelativeToBase(allocator, newString, fsType, isDirectory, url->_base);
4190 CFRelease(newString);
4191 }
4192 return result;
4193 }
4194
4195 CFURLRef CFURLCreateCopyDeletingLastPathComponent(CFAllocatorRef allocator, CFURLRef url) {
4196 CFURLRef result;
4197 CFMutableStringRef newString;
4198 CFRange lastCompRg, pathRg;
4199 Boolean appendDotDot = false;
4200 UInt32 fsType;
4201
4202 url = _CFURLFromNSURL(url);
4203 CFAssert1(url != NULL, __kCFLogAssertion, "%s(): NULL argument not allowed", __PRETTY_FUNCTION__);
4204 __CFGenericValidateType(url, __kCFURLTypeID);
4205
4206 fsType = URL_PATH_TYPE(url);
4207 if (fsType == FULL_URL_REPRESENTATION) {
4208 if (!(url->_flags & IS_PARSED)) _parseComponentsOfURL(url);
4209 if (!(url->_flags & HAS_PATH)) return NULL;
4210 pathRg = _rangeForComponent(url->_flags, url->ranges, HAS_PATH);
4211 } else {
4212 pathRg = CFRangeMake(0, CFStringGetLength(url->_string));
4213 }
4214 lastCompRg = _rangeOfLastPathComponent(url);
4215 if (lastCompRg.length == 0) {
4216 appendDotDot = true;
4217 } else if (lastCompRg.length == 1) {
4218 UniChar ch = CFStringGetCharacterAtIndex(url->_string, lastCompRg.location);
4219 if (ch == '.' || ch == PATH_DELIM_FOR_TYPE(fsType)) {
4220 appendDotDot = true;
4221 }
4222 } else if (lastCompRg.length == 2 && CFStringGetCharacterAtIndex(url->_string, lastCompRg.location) == '.' && CFStringGetCharacterAtIndex(url->_string, lastCompRg.location+1) == '.') {
4223 appendDotDot = true;
4224 }
4225
4226 newString = CFStringCreateMutableCopy(allocator, 0, url->_string);
4227 if (appendDotDot) {
4228 CFIndex delta = 0;
4229 if (pathRg.length > 0 && CFStringGetCharacterAtIndex(url->_string, pathRg.location + pathRg.length - 1) != PATH_DELIM_FOR_TYPE(fsType)) {
4230 CFStringInsert(newString, pathRg.location + pathRg.length, PATH_DELIM_AS_STRING_FOR_TYPE(fsType));
4231 delta ++;
4232 }
4233 CFStringInsert(newString, pathRg.location + pathRg.length + delta, CFSTR(".."));
4234 delta += 2;
4235 CFStringInsert(newString, pathRg.location + pathRg.length + delta, PATH_DELIM_AS_STRING_FOR_TYPE(fsType));
4236 delta ++;
4237 // We know we have "/../" at the end of the path; we wish to know if that's immediately preceded by "/." (but that "/." doesn't start the string), in which case we want to delete the "/.".
4238 if (pathRg.length + delta > 4 && CFStringGetCharacterAtIndex(newString, pathRg.location + pathRg.length + delta - 5) == '.') {
4239 if (pathRg.length+delta > 7 && CFStringGetCharacterAtIndex(newString, pathRg.location + pathRg.length + delta - 6) == PATH_DELIM_FOR_TYPE(fsType)) {
4240 CFStringDelete(newString, CFRangeMake(pathRg.location + pathRg.length + delta - 6, 2));
4241 } else if (pathRg.length+delta == 5) {
4242 CFStringDelete(newString, CFRangeMake(pathRg.location + pathRg.length + delta - 5, 2));
4243 }
4244 }
4245 } else if (lastCompRg.location == pathRg.location) {
4246 CFStringReplace(newString, pathRg, CFSTR("."));
4247 CFStringInsert(newString, 1, PATH_DELIM_AS_STRING_FOR_TYPE(fsType));
4248 } else {
4249 CFStringDelete(newString, CFRangeMake(lastCompRg.location, pathRg.location + pathRg.length - lastCompRg.location));
4250 }
4251 if (fsType == FULL_URL_REPRESENTATION) {
4252 result = _CFURLCreateWithArbitraryString(allocator, newString, url->_base);
4253 } else {
4254 result = CFURLCreateWithFileSystemPathRelativeToBase(allocator, newString, fsType, true, url->_base);
4255 }
4256 CFRelease(newString);
4257 return result;
4258 }
4259
4260 CFURLRef CFURLCreateCopyAppendingPathExtension(CFAllocatorRef allocator, CFURLRef url, CFStringRef extension) {
4261 CFMutableStringRef newString;
4262 CFURLRef result;
4263 CFRange rg;
4264 CFURLPathStyle fsType;
4265
4266 CFAssert1(url != NULL && extension != NULL, __kCFLogAssertion, "%s(): NULL argument not allowed", __PRETTY_FUNCTION__);
4267 url = _CFURLFromNSURL(url);
4268 __CFGenericValidateType(url, __kCFURLTypeID);
4269 __CFGenericValidateType(extension, CFStringGetTypeID());
4270
4271 rg = _rangeOfLastPathComponent(url);
4272 if (rg.location < 0) return NULL; // No path
4273 fsType = URL_PATH_TYPE(url);
4274 if (fsType != FULL_URL_REPRESENTATION && CFStringFindWithOptions(extension, PATH_DELIM_AS_STRING_FOR_TYPE(fsType), CFRangeMake(0, CFStringGetLength(extension)), 0, NULL)) {
4275 _convertToURLRepresentation((struct __CFURL *)url);
4276 fsType = FULL_URL_REPRESENTATION;
4277 rg = _rangeOfLastPathComponent(url);
4278 }
4279
4280 newString = CFStringCreateMutableCopy(allocator, 0, url->_string);
4281 CFStringInsert(newString, rg.location + rg.length, CFSTR("."));
4282 if (fsType == FULL_URL_REPRESENTATION) {
4283 CFStringRef newExt = CFURLCreateStringByAddingPercentEscapes(allocator, extension, NULL, CFSTR(";?/"), (url->_flags & IS_OLD_UTF8_STYLE) ? kCFStringEncodingUTF8 : url->_encoding);
4284 CFStringInsert(newString, rg.location + rg.length + 1, newExt);
4285 CFRelease(newExt);
4286 result = _CFURLCreateWithArbitraryString(allocator, newString, url->_base);
4287 } else {
4288 CFStringInsert(newString, rg.location + rg.length + 1, extension);
4289 result = CFURLCreateWithFileSystemPathRelativeToBase(allocator, newString, fsType, (url->_flags & IS_DIRECTORY) != 0 ? true : false, url->_base);
4290 }
4291 CFRelease(newString);
4292 return result;
4293 }
4294
4295 CFURLRef CFURLCreateCopyDeletingPathExtension(CFAllocatorRef allocator, CFURLRef url) {
4296 CFRange rg, dotRg;
4297 CFURLRef result;
4298
4299 CFAssert1(url != NULL, __kCFLogAssertion, "%s(): NULL argument not allowed", __PRETTY_FUNCTION__);
4300 url = _CFURLFromNSURL(url);
4301 __CFGenericValidateType(url, __kCFURLTypeID);
4302 rg = _rangeOfLastPathComponent(url);
4303 if (rg.location < 0) {
4304 result = NULL;
4305 } else if (rg.length && CFStringFindWithOptions(url->_string, CFSTR("."), rg, kCFCompareBackwards, &dotRg)) {
4306 CFMutableStringRef newString = CFStringCreateMutableCopy(allocator, 0, url->_string);
4307 dotRg.length = rg.location + rg.length - dotRg.location;
4308 CFStringDelete(newString, dotRg);
4309 if (URL_PATH_TYPE(url) == FULL_URL_REPRESENTATION) {
4310 result = _CFURLCreateWithArbitraryString(allocator, newString, url->_base);
4311 } else {
4312 result = CFURLCreateWithFileSystemPathRelativeToBase(allocator, newString, URL_PATH_TYPE(url), (url->_flags & IS_DIRECTORY) != 0 ? true : false, url->_base);
4313 }
4314 CFRelease(newString);
4315 } else {
4316 result = (CFURLRef)CFRetain(url);
4317 }
4318 return result;
4319 }
4320
4321
4322 #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED
4323 static CFArrayRef HFSPathToURLComponents(CFStringRef path, CFAllocatorRef alloc, Boolean isDir) {
4324 CFArrayRef components = CFStringCreateArrayBySeparatingStrings(alloc, path, CFSTR(":"));
4325 CFMutableArrayRef newComponents = CFArrayCreateMutableCopy(alloc, 0, components);
4326 Boolean doSpecialLeadingColon = false;
4327 UniChar firstChar = CFStringGetCharacterAtIndex(path, 0);
4328 UInt32 i, cnt;
4329 CFRelease(components);
4330
4331
4332 if (!doSpecialLeadingColon && firstChar != ':') {
4333 CFArrayInsertValueAtIndex(newComponents, 0, CFSTR(""));
4334 } else if (firstChar != ':') {
4335 // see what we need to add at the beginning. Under MacOS, if the
4336 // first character isn't a ':', then the first component is the
4337 // volume name, and we need to find the mount point. Bleah. If we
4338 // don't find a mount point, we're going to have to lie, and make something up.
4339 CFStringRef firstComp = (CFStringRef)CFArrayGetValueAtIndex(newComponents, 0);
4340 if (CFStringGetLength(firstComp) == 1 && CFStringGetCharacterAtIndex(firstComp, 0) == '/') {
4341 // "/" is the "magic" path for a UFS root directory
4342 CFArrayRemoveValueAtIndex(newComponents, 0);
4343 CFArrayInsertValueAtIndex(newComponents, 0, CFSTR(""));
4344 } else {
4345 // See if we can get a mount point.
4346 Boolean foundMountPoint = false;
4347 if (!foundMountPoint) {
4348 // Fall back to treating the volume name as the top level directory
4349 CFArrayInsertValueAtIndex(newComponents, 0, CFSTR(""));
4350 }
4351 }
4352 } else {
4353 CFArrayRemoveValueAtIndex(newComponents, 0);
4354 }
4355
4356 cnt = CFArrayGetCount(newComponents);
4357 for (i = 0; i < cnt; i ++) {
4358 CFStringRef comp = (CFStringRef)CFArrayGetValueAtIndex(newComponents, i);
4359 CFStringRef newComp = NULL;
4360 CFRange searchRg, slashRg;
4361 searchRg.location = 0;
4362 searchRg.length = CFStringGetLength(comp);
4363 while (CFStringFindWithOptions(comp, CFSTR("/"), searchRg, 0, &slashRg)) {
4364 if (!newComp) {
4365 newComp = CFStringCreateMutableCopy(alloc, searchRg.location + searchRg.length, comp);
4366 }
4367 CFStringReplace((CFMutableStringRef)newComp, slashRg, CFSTR(":"));
4368 searchRg.length = searchRg.location + searchRg.length - slashRg.location - 1;
4369 searchRg.location = slashRg.location + 1;
4370 }
4371 if (newComp) {
4372 CFArraySetValueAtIndex(newComponents, i, newComp);
4373 CFRelease(newComp);
4374 }
4375 }
4376 if (isDir && CFStringGetLength((CFStringRef)CFArrayGetValueAtIndex(newComponents, cnt-1)) != 0) {
4377 CFArrayAppendValue(newComponents, CFSTR(""));
4378 }
4379 return newComponents;
4380 }
4381 static CFStringRef HFSPathToURLPath(CFStringRef path, CFAllocatorRef alloc, Boolean isDir) {
4382 CFArrayRef components = HFSPathToURLComponents(path, alloc, isDir);
4383 CFArrayRef newComponents = components ? copyStringArrayWithTransformation(components, escapePathComponent) : NULL;
4384 CFIndex cnt;
4385 CFStringRef result;
4386 if (components) CFRelease(components);
4387 if (!newComponents) return NULL;
4388
4389 cnt = CFArrayGetCount(newComponents);
4390 if (cnt == 1 && CFStringGetLength((CFStringRef)CFArrayGetValueAtIndex(newComponents, 0)) == 0) {
4391 result = (CFStringRef)CFRetain(CFSTR("/"));
4392 } else {
4393 result = CFStringCreateByCombiningStrings(alloc, newComponents, CFSTR("/"));
4394 }
4395 CFRelease(newComponents);
4396 return result;
4397 }
4398 #elif DEPLOYMENT_TARGET_WINDOWS
4399 #else
4400 #error Unknown or unspecified DEPLOYMENT_TARGET
4401 #endif
4402
4403
4404
4405 // keys and vals must have space for at least 4 key/value pairs. No argument can be NULL.
4406 // Caller must release values, but not keys
4407 static void __CFURLCopyPropertyListKeysAndValues(CFURLRef url, CFTypeRef *keys, CFTypeRef *vals, CFIndex *count) {
4408 CFAllocatorRef alloc = CFGetAllocator(url);
4409 CFURLRef base = CFURLGetBaseURL(url);
4410 keys[0] = CFSTR("_CFURLStringType");
4411 keys[1] = CFSTR("_CFURLString");
4412 keys[2] = CFSTR("_CFURLBaseStringType");
4413 keys[3] = CFSTR("_CFURLBaseURLString");
4414 if (CF_IS_OBJC(__kCFURLTypeID, url)) {
4415 SInt32 urlType = FULL_URL_REPRESENTATION;
4416 vals[0] = CFNumberCreate(alloc, kCFNumberSInt32Type, &urlType);
4417 vals[1] = CFURLGetString(url);
4418 } else {
4419 SInt32 urlType = URL_PATH_TYPE(url);
4420 vals[0] = CFNumberCreate(alloc, kCFNumberSInt32Type, &urlType);
4421 if (url->_flags & IS_DIRECTORY) {
4422 if (CFStringGetCharacterAtIndex(url->_string, CFStringGetLength(url->_string) - 1) == PATH_DELIM_FOR_TYPE(urlType)) {
4423 vals[1] = CFRetain(url->_string);
4424 } else {
4425 vals[1] = CFStringCreateWithFormat(alloc, NULL, CFSTR("%@%c"), url->_string, PATH_DELIM_FOR_TYPE(urlType));
4426 }
4427 } else {
4428 if (CFStringGetCharacterAtIndex(url->_string, CFStringGetLength(url->_string) - 1) != PATH_DELIM_FOR_TYPE(urlType)) {
4429 vals[1] = CFRetain(url->_string);
4430 } else {
4431 vals[1] = CFStringCreateWithSubstring(alloc, url->_string, CFRangeMake(0, CFStringGetLength(url->_string) - 1));
4432 }
4433 }
4434 }
4435 if (base != NULL) {
4436 if (CF_IS_OBJC(__kCFURLTypeID, base)) {
4437 SInt32 urlType = FULL_URL_REPRESENTATION;
4438 vals[2] = CFNumberCreate(alloc, kCFNumberSInt32Type, &urlType);
4439 vals[3] = CFURLGetString(base);
4440 } else {
4441 SInt32 urlType = URL_PATH_TYPE(base);
4442 vals[2] = CFNumberCreate(alloc, kCFNumberSInt32Type, &urlType);
4443 if (base->_flags & IS_DIRECTORY) {
4444 if (CFStringGetCharacterAtIndex(base->_string, CFStringGetLength(base->_string) - 1) == PATH_DELIM_FOR_TYPE(urlType)) {
4445 vals[3] = CFRetain(base->_string);
4446 } else {
4447 vals[3] = CFStringCreateWithFormat(alloc, NULL, CFSTR("%@%c"), base->_string, PATH_DELIM_FOR_TYPE(urlType));
4448 }
4449 } else {
4450 if (CFStringGetCharacterAtIndex(base->_string, CFStringGetLength(base->_string) - 1) != PATH_DELIM_FOR_TYPE(urlType)) {
4451 vals[3] = CFRetain(base->_string);
4452 } else {
4453 vals[3] = CFStringCreateWithSubstring(alloc, base->_string, CFRangeMake(0, CFStringGetLength(base->_string) - 1));
4454 }
4455 }
4456 }
4457 *count = 4;
4458 } else {
4459 *count = 2;
4460 }
4461 }
4462
4463 // Private API for Finder to use
4464 CFPropertyListRef _CFURLCopyPropertyListRepresentation(CFURLRef url) {
4465 CFTypeRef keys[4], vals[4];
4466 CFDictionaryRef dict;
4467 CFIndex count, idx;
4468 __CFURLCopyPropertyListKeysAndValues(url, keys, vals, &count);
4469 dict = CFDictionaryCreate(CFGetAllocator(url), keys, vals, count, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
4470 for (idx = 0; idx < count; idx ++) {
4471 CFRelease(vals[idx]);
4472 }
4473 return dict;
4474 }
4475
4476 CFURLRef _CFURLCreateFromPropertyListRepresentation(CFAllocatorRef alloc, CFPropertyListRef pListRepresentation) {
4477 CFStringRef baseString, string;
4478 CFNumberRef baseTypeNum, urlTypeNum;
4479 SInt32 baseType, urlType;
4480 CFURLRef baseURL = NULL, url;
4481 CFDictionaryRef dict = (CFDictionaryRef)pListRepresentation;
4482
4483 // Start by getting all the pieces and verifying they're of the correct type.
4484 if (CFGetTypeID(pListRepresentation) != CFDictionaryGetTypeID()) {
4485 return NULL;
4486 }
4487 string = (CFStringRef)CFDictionaryGetValue(dict, CFSTR("_CFURLString"));
4488 if (!string || CFGetTypeID(string) != CFStringGetTypeID()) {
4489 return NULL;
4490 }
4491 urlTypeNum = (CFNumberRef)CFDictionaryGetValue(dict, CFSTR("_CFURLStringType"));
4492 if (!urlTypeNum || CFGetTypeID(urlTypeNum) != CFNumberGetTypeID() || !CFNumberGetValue(urlTypeNum, kCFNumberSInt32Type, &urlType) || (urlType != FULL_URL_REPRESENTATION && urlType != kCFURLPOSIXPathStyle && urlType != kCFURLHFSPathStyle && urlType != kCFURLWindowsPathStyle)) {
4493 return NULL;
4494 }
4495 baseString = (CFStringRef)CFDictionaryGetValue(dict, CFSTR("_CFURLBaseURLString"));
4496 if (baseString) {
4497 if (CFGetTypeID(baseString) != CFStringGetTypeID()) {
4498 return NULL;
4499 }
4500 baseTypeNum = (CFNumberRef)CFDictionaryGetValue(dict, CFSTR("_CFURLBaseStringType"));
4501 if (!baseTypeNum || CFGetTypeID(baseTypeNum) != CFNumberGetTypeID() || !CFNumberGetValue(baseTypeNum, kCFNumberSInt32Type, &baseType) ||
4502 (baseType != FULL_URL_REPRESENTATION && baseType != kCFURLPOSIXPathStyle && baseType != kCFURLHFSPathStyle && baseType != kCFURLWindowsPathStyle)) {
4503 return NULL;
4504 }
4505 if (baseType == FULL_URL_REPRESENTATION) {
4506 baseURL = _CFURLCreateWithArbitraryString(alloc, baseString, NULL);
4507 } else {
4508 baseURL = CFURLCreateWithFileSystemPathRelativeToBase(alloc, baseString, (CFURLPathStyle)baseType, CFStringGetCharacterAtIndex(baseString, CFStringGetLength(baseString)-1) == PATH_DELIM_FOR_TYPE(baseType), NULL);
4509 }
4510 }
4511 if (urlType == FULL_URL_REPRESENTATION) {
4512 url = _CFURLCreateWithArbitraryString(alloc, string, baseURL);
4513 } else {
4514 url = CFURLCreateWithFileSystemPathRelativeToBase(alloc, string, (CFURLPathStyle)urlType, CFStringGetCharacterAtIndex(string, CFStringGetLength(string)-1) == PATH_DELIM_FOR_TYPE(urlType), baseURL);
4515 }
4516 if (baseURL) CFRelease(baseURL);
4517 return url;
4518 }
4519