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