2 Copyright (C) 2000-2005 SKYRIX Software AG
4 This file is part of SOPE.
6 SOPE is free software; you can redistribute it and/or modify it under
7 the terms of the GNU Lesser General Public License as published by the
8 Free Software Foundation; either version 2, or (at your option) any
11 SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
14 License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with SOPE; see the file COPYING. If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
22 #include "NSURL+misc.h"
25 static BOOL debugURLProcessing = NO;
27 @implementation NSURL(misc)
29 - (NSString *)pathWithCorrectTrailingSlash {
30 #if COCOA_Foundation_LIBRARY || NeXT_Foundation_LIBRARY
32 At least on OSX 10.3 the -path method missing the trailing slash, eg:
33 http://localhost:20000/dbd.woa/so/localhost/
39 if ((p = [self path]) == nil)
42 if ([p hasSuffix:@"/"])
45 if (![[self absoluteString] hasSuffix:@"/"])
48 /* so we are running into the bug ... */
49 return [p stringByAppendingString:@"/"];
55 - (NSString *)stringByAddingFragmentAndQueryToPath:(NSString *)_path {
56 NSString *lFrag, *lQuery;
61 lFrag = [self fragment];
62 lQuery = [self query];
64 if ((lFrag != nil) || (lQuery != nil)) {
67 ms = [NSMutableString stringWithCapacity:([_path length] + 32)];
69 [ms appendString:_path];
72 [ms appendString:@"#"];
73 [ms appendString:lFrag];
76 [ms appendString:@"?"];
77 [ms appendString:lQuery];
85 - (NSString *)stringValueRelativeToURL:(NSURL *)_base {
88 self: http://localhost:20000/dbd.woa/so/localhost/Databases/A
89 base: http://localhost:20000/dbd.woa/so/localhost/
92 Note: on Panther Foundation the -path misses the trailing slash!
96 if (_base == self || _base == nil) {
97 relPath = [self pathWithCorrectTrailingSlash];
98 relPath = [relPath urlPathRelativeToSelf];
99 relPath = [self stringByAddingFragmentAndQueryToPath:relPath];
100 if (debugURLProcessing) {
101 NSLog(@"%s: no base or base is self => '%@'",
102 __PRETTY_FUNCTION__, relPath);
107 /* check whether we are already marked relative to _base .. */
108 if ([self baseURL] == _base) {
111 p = [self relativePath];
112 #if COCOA_Foundation_LIBRARY || NeXT_Foundation_LIBRARY
113 /* see -pathWithCorrectTrailingSlash for bug description ... */
114 if (![p hasSuffix:@"/"]) {
115 if ([[self absoluteString] hasSuffix:@"/"])
116 p = [p stringByAppendingString:@"/"];
119 p = [self stringByAddingFragmentAndQueryToPath:p];
120 if (debugURLProcessing) {
121 NSLog(@"%s: url base and _base match => '%@'",
122 __PRETTY_FUNCTION__, p);
127 /* check whether we are in the same path namespace ... */
128 if (![self isInSameNamespaceWithURL:_base]) {
129 /* need to return full URL */
130 relPath = [self absoluteString];
131 if (debugURLProcessing) {
132 NSLog(@"%s: url is in different namespace from base => '%@'",
133 __PRETTY_FUNCTION__, relPath);
138 relPath = [[self pathWithCorrectTrailingSlash]
139 urlPathRelativeToPath:[_base pathWithCorrectTrailingSlash]];
140 if (debugURLProcessing) {
141 NSLog(@"%s: path '%@', base-path '%@' => rel '%@'", __PRETTY_FUNCTION__,
142 [self path], [_base path], relPath);
144 relPath = [self stringByAddingFragmentAndQueryToPath:relPath];
146 if (debugURLProcessing) {
147 NSLog(@"%s: same namespace, but no direct relative (%@, base %@) => '%@'",
149 [self absoluteString], [_base absoluteString], relPath);
154 static BOOL isEqual(id o1, id o2) {
155 if (o1 == o2) return YES;
156 if (o1 == nil || o2 == nil) return NO;
157 return [o1 isEqual:o2];
160 - (BOOL)isInSameNamespaceWithURL:(NSURL *)_url {
161 if (_url == nil) return NO;
162 if (_url == self) return YES;
163 if ([self isFileURL] && [_url isFileURL]) return YES;
164 if ([self baseURL] == _url) return YES;
165 if ([_url baseURL] == self) return YES;
167 if (![[self scheme] isEqualToString:[_url scheme]])
170 if (!isEqual([self host], [_url host]))
172 if (!isEqual([self port], [_url port]))
174 if (!isEqual([self user], [_url user]))
182 @implementation NSString(URLPathProcessing)
184 - (NSString *)urlPathRelativeToSelf {
187 should resolve to: "c.html"
189 Directories are a bit more difficult, eg:
198 /SOGo/so/X/Mail/Y/INBOX/withsubdirs/
199 ..//SOGo/so/X/Mail/Y/INBOX/withsubdirs//
203 lp = [p lastPathComponent];
204 if (![p hasSuffix:@"/"])
207 return [[@"../" stringByAppendingString:lp] stringByAppendingString:@"/"];
210 - (NSString *)urlPathRelativeToRoot {
215 if ([p isEqualToString:@"/"])
216 /* don't know better ... what is root-relative-to-root ? */
219 if ([p length] == 0) {
220 NSLog(@"%s: invalid path (length 0), using /: %@",
221 __PRETTY_FUNCTION__, self);
225 /* this is the same like the absolute path, only without a leading "/" .. */
226 return [p characterAtIndex:0] == '/' ? [p substringFromIndex:1] : p;
229 static NSString *calcRelativePathOfChildURL(NSString *self, NSString *_base) {
231 the whole base URI is prefix of our URI:
245 b=s is already catched above and s is guaranteed to be
251 if (debugURLProcessing)
252 NSLog(@"%s: has base as prefix ...", __PRETTY_FUNCTION__);
253 blen = [_base length];
255 if ([_base characterAtIndex:(blen - 1)] == '/') {
256 /* last char of 'b' is '/' => case b) */
257 result = [self substringFromIndex:blen];
261 last char of 'b' is not a slash (either case a) or case c)),
262 both are handled in the same way (search last / ...)
266 r = [_base rangeOfString:@"/" options:NSBackwardsSearch];
268 NSLog(@"%s: invalid base, found no '/': '%@' !",
269 __PRETTY_FUNCTION__, _base);
273 /* no we have case b) ... */
274 result = [self substringFromIndex:(r.location + 1)];
280 - (NSString *)commonDirPathPrefixWithString:(NSString *)_other {
281 // TODO: the implementation can probably be optimized a _LOT_
282 /* eg "/home/images/" vs "/home/index.html" => "/home/", _not_ "/home/i" ! */
290 s = [self commonPrefixWithString:_other options:0];
294 if ([s characterAtIndex:(len - 1)] == '/')
297 r = [s rangeOfString:@"/" options:NSBackwardsSearch];
298 if (r.length == 0) /* hm, can't happen? */
301 return [s substringToIndex:(r.location + r.length)];;
305 NSString *calcRelativePathOfNonChildURL(NSString *self, NSString *_base) {
312 prefix = [self commonDirPathPrefixWithString:_base];
313 plen = [prefix length];
314 suffix = [self substringFromIndex:plen];
317 if (debugURLProcessing) {
318 NSLog(@"%s: does not have base as prefix, common '%@'\n"
322 __PRETTY_FUNCTION__, prefix, self, _base, suffix);
326 NSLog(@"%s: invalid strings, no common prefix ...: '%@' and '%@' !",
327 __PRETTY_FUNCTION__, self, _base);
333 common prefix is root. That is, nothing in common:
341 (number of slashes without root * "..", then the trailer?)
345 len = [_base length];
347 if ([prefix characterAtIndex:0] != '/') {
348 NSLog(@"%s: invalid strings, common prefix '%@' is not '/': "
350 __PRETTY_FUNCTION__, self, _base, prefix);
353 for (i = 1 /* skip root */; i < len; i++) {
354 if ([_base characterAtIndex:i] == '/')
360 base: /dev/en/projects/bsd/index.html
361 self: /dev/en/macosx/
364 NSString *basesuffix;
367 basesuffix = [_base substringFromIndex:plen];
368 len = [basesuffix length];
370 for (i = 0; i < len; i++) {
371 if ([basesuffix characterAtIndex:i] == '/')
376 if (debugURLProcessing)
377 NSLog(@"%s: slashes: %d", __PRETTY_FUNCTION__, numSlashes);
379 /* optimization for some depths */
380 switch (numSlashes) {
381 case 0: /* no slashes in base: b:/a, s:/images/a => images/a */
384 case 1: /* one slash in base: b:/a/, s:/images/a => ../images/a, etc */
385 result = [@"../" stringByAppendingString:suffix];
387 case 2: result = [@"../../" stringByAppendingString:suffix]; break;
388 case 3: result = [@"../../../" stringByAppendingString:suffix]; break;
389 case 4: result = [@"../../../../" stringByAppendingString:suffix]; break;
390 case 5: result = [@"../../../../../" stringByAppendingString:suffix];break;
395 ms = [NSMutableString stringWithCapacity:(numSlashes * 3)];
396 for (i = 0; i < numSlashes; i++)
397 [ms appendString:@"../"];
398 [ms appendString:suffix];
403 if (debugURLProcessing)
404 NSLog(@"%s: => '%@'", __PRETTY_FUNCTION__, result);
408 - (NSString *)urlPathRelativeToPath:(NSString *)_base {
410 This can be used for URLs in the same namespace. It should
411 never return an absolute path (it only does in error conditions).
413 // TODO: the implementation can probably be optimized a _LOT_
415 if (_base == nil || [_base length] == 0) {
416 NSLog(@"%s: invalid base (nil or length 0), using absolute path '%@' ...",
417 __PRETTY_FUNCTION__, self);
421 if ([_base isEqualToString:@"/"])
422 return [self urlPathRelativeToRoot];
423 if ([_base isEqualToString:self])
424 return [self urlPathRelativeToSelf];
426 if (debugURLProcessing)
427 NSLog(@"%s: %@ relative to %@ ...", __PRETTY_FUNCTION__, self, _base);
429 if ([self hasPrefix:_base])
430 return calcRelativePathOfChildURL(self, _base);
432 return calcRelativePathOfNonChildURL(self, _base);
435 @end /* NSString(URLPathProcessing) */