2 Copyright (C) 2000-2004 SKYRIX Software AG
4 This file is part of OpenGroupware.org.
6 OGo 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 OGo 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 OGo; 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] urlPathRelativeToSelf];
98 relPath = [self stringByAddingFragmentAndQueryToPath:relPath];
99 if (debugURLProcessing) {
100 NSLog(@"%s: no base or base is self => '%@'",
101 __PRETTY_FUNCTION__, relPath);
106 /* check whether we are already marked relative to _base .. */
107 if ([self baseURL] == _base) {
110 p = [self relativePath];
111 #if COCOA_Foundation_LIBRARY || NeXT_Foundation_LIBRARY
112 /* see -pathWithCorrectTrailingSlash for bug description ... */
113 if (![p hasSuffix:@"/"]) {
114 if ([[self absoluteString] hasSuffix:@"/"])
115 p = [p stringByAppendingString:@"/"];
118 p = [self stringByAddingFragmentAndQueryToPath:p];
119 if (debugURLProcessing) {
120 NSLog(@"%s: url base and _base match => '%@'",
121 __PRETTY_FUNCTION__, p);
126 /* check whether we are in the same path namespace ... */
127 if (![self isInSameNamespaceWithURL:_base]) {
128 /* need to return full URL */
129 relPath = [self absoluteString];
130 if (debugURLProcessing) {
131 NSLog(@"%s: url is in different namespace from base => '%@'",
132 __PRETTY_FUNCTION__, relPath);
137 relPath = [[self pathWithCorrectTrailingSlash]
138 urlPathRelativeToPath:[_base pathWithCorrectTrailingSlash]];
139 if (debugURLProcessing) {
140 NSLog(@"%s: path '%@', base-path '%@' => rel '%@'", __PRETTY_FUNCTION__,
141 [self path], [_base path], relPath);
143 relPath = [self stringByAddingFragmentAndQueryToPath:relPath];
145 if (debugURLProcessing) {
146 NSLog(@"%s: same namespace, but no direct relative (%@, base %@) => '%@'",
148 [self absoluteString], [_base absoluteString], relPath);
153 static BOOL isEqual(id o1, id o2) {
154 if (o1 == o2) return YES;
155 if (o1 == nil || o2 == nil) return NO;
156 return [o1 isEqual:o2];
159 - (BOOL)isInSameNamespaceWithURL:(NSURL *)_url {
160 if (_url == nil) return NO;
161 if (_url == self) return YES;
162 if ([self isFileURL] && [_url isFileURL]) return YES;
163 if ([self baseURL] == _url) return YES;
164 if ([_url baseURL] == self) return YES;
166 if (![[self scheme] isEqualToString:[_url scheme]])
169 if (!isEqual([self host], [_url host]))
171 if (!isEqual([self port], [_url port]))
173 if (!isEqual([self user], [_url user]))
181 @implementation NSString(URLPathProcessing)
183 - (NSString *)urlPathRelativeToSelf {
186 should resolve to: "c.html"
188 Directories are a bit more difficult, eg:
197 lp = [p lastPathComponent];
199 p = ([p hasSuffix:@"/"])
200 ? [NSString stringWithFormat:@"../%@/", p]
205 - (NSString *)urlPathRelativeToRoot {
210 if ([p isEqualToString:@"/"])
211 /* don't know better ... what is root-relative-to-root ? */
214 if ([p length] == 0) {
215 NSLog(@"%s: invalid path (length 0), using /: %@",
216 __PRETTY_FUNCTION__, self);
220 /* this is the same like the absolute path, only without a leading "/" .. */
221 return [p characterAtIndex:0] == '/' ? [p substringFromIndex:1] : p;
224 static NSString *calcRelativePathOfChildURL(NSString *self, NSString *_base) {
226 the whole base URI is prefix of our URI:
240 b=s is already catched above and s is guaranteed to be
246 if (debugURLProcessing)
247 NSLog(@"%s: has base as prefix ...", __PRETTY_FUNCTION__);
248 blen = [_base length];
250 if ([_base characterAtIndex:(blen - 1)] == '/') {
251 /* last char of 'b' is '/' => case b) */
252 result = [self substringFromIndex:blen];
256 last char of 'b' is not a slash (either case a) or case c)),
257 both are handled in the same way (search last / ...)
261 r = [_base rangeOfString:@"/" options:NSBackwardsSearch];
263 NSLog(@"%s: invalid base, found no '/': '%@' !",
264 __PRETTY_FUNCTION__, _base);
268 /* no we have case b) ... */
269 result = [self substringFromIndex:(r.location + 1)];
275 - (NSString *)commonDirPathPrefixWithString:(NSString *)_other {
276 // TODO: the implementation can probably be optimized a _LOT_
277 /* eg "/home/images/" vs "/home/index.html" => "/home/", _not_ "/home/i" ! */
285 s = [self commonPrefixWithString:_other options:0];
289 if ([s characterAtIndex:(len - 1)] == '/')
292 r = [s rangeOfString:@"/" options:NSBackwardsSearch];
293 if (r.length == 0) /* hm, can't happen? */
296 return [s substringToIndex:(r.location + r.length)];;
300 NSString *calcRelativePathOfNonChildURL(NSString *self, NSString *_base) {
307 prefix = [self commonDirPathPrefixWithString:_base];
308 plen = [prefix length];
309 suffix = [self substringFromIndex:plen];
312 if (debugURLProcessing) {
313 NSLog(@"%s: does not have base as prefix, common '%@'\n"
317 __PRETTY_FUNCTION__, prefix, self, _base, suffix);
321 NSLog(@"%s: invalid strings, no common prefix ...: '%@' and '%@' !",
322 __PRETTY_FUNCTION__, self, _base);
328 common prefix is root. That is, nothing in common:
336 (number of slashes without root * "..", then the trailer?)
340 len = [_base length];
342 if ([prefix characterAtIndex:0] != '/') {
343 NSLog(@"%s: invalid strings, common prefix '%@' is not '/': "
345 __PRETTY_FUNCTION__, self, _base, prefix);
348 for (i = 1 /* skip root */; i < len; i++) {
349 if ([_base characterAtIndex:i] == '/')
355 base: /dev/en/projects/bsd/index.html
356 self: /dev/en/macosx/
359 NSString *basesuffix;
362 basesuffix = [_base substringFromIndex:plen];
363 len = [basesuffix length];
365 for (i = 0; i < len; i++) {
366 if ([basesuffix characterAtIndex:i] == '/')
371 if (debugURLProcessing)
372 NSLog(@"%s: slashes: %d", __PRETTY_FUNCTION__, numSlashes);
374 /* optimization for some depths */
375 switch (numSlashes) {
376 case 0: /* no slashes in base: b:/a, s:/images/a => images/a */
379 case 1: /* one slash in base: b:/a/, s:/images/a => ../images/a, etc */
380 result = [@"../" stringByAppendingString:suffix];
382 case 2: result = [@"../../" stringByAppendingString:suffix]; break;
383 case 3: result = [@"../../../" stringByAppendingString:suffix]; break;
384 case 4: result = [@"../../../../" stringByAppendingString:suffix]; break;
385 case 5: result = [@"../../../../../" stringByAppendingString:suffix];break;
390 ms = [NSMutableString stringWithCapacity:(numSlashes * 3)];
391 for (i = 0; i < numSlashes; i++)
392 [ms appendString:@"../"];
393 [ms appendString:suffix];
398 if (debugURLProcessing)
399 NSLog(@"%s: => '%@'", __PRETTY_FUNCTION__, result);
403 - (NSString *)urlPathRelativeToPath:(NSString *)_base {
405 This can be used for URLs in the same namespace. It should
406 never return an absolute path (it only does in error conditions).
408 // TODO: the implementation can probably be optimized a _LOT_
410 if (_base == nil || [_base length] == 0) {
411 NSLog(@"%s: invalid base (nil or length 0), using absolute path '%@' ...",
412 __PRETTY_FUNCTION__, self);
416 if ([_base isEqualToString:@"/"])
417 return [self urlPathRelativeToRoot];
418 if ([_base isEqualToString:self])
419 return [self urlPathRelativeToSelf];
421 if (debugURLProcessing)
422 NSLog(@"%s: %@ relative to %@ ...", __PRETTY_FUNCTION__, self, _base);
424 if ([self hasPrefix:_base])
425 return calcRelativePathOfChildURL(self, _base);
427 return calcRelativePathOfNonChildURL(self, _base);
430 @end /* NSString(URLPathProcessing) */