/* Copyright (C) 2000-2004 SKYRIX Software AG This file is part of OpenGroupware.org. OGo is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. OGo is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with OGo; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // $Id$ #include "NSURL+misc.h" #include "common.h" static BOOL debugURLProcessing = NO; @implementation NSURL(misc) - (NSString *)pathWithCorrectTrailingSlash { #if COCOA_Foundation_LIBRARY || NeXT_Foundation_LIBRARY /* At least on OSX 10.3 the -path method missing the trailing slash, eg: http://localhost:20000/dbd.woa/so/localhost/ gives: /dbd.woa/so/localhost */ NSString *p; if ((p = [self path]) == nil) return nil; if ([p hasSuffix:@"/"]) return p; if (![[self absoluteString] hasSuffix:@"/"]) return p; /* so we are running into the bug ... */ return [p stringByAppendingString:@"/"]; #else return [self path]; #endif } - (NSString *)stringByAddingFragmentAndQueryToPath:(NSString *)_path { NSString *lFrag, *lQuery; if ([self isFileURL]) return _path; lFrag = [self fragment]; lQuery = [self query]; if ((lFrag != nil) || (lQuery != nil)) { NSMutableString *ms; ms = [NSMutableString stringWithCapacity:([_path length] + 32)]; [ms appendString:_path]; if (lFrag) { [ms appendString:@"#"]; [ms appendString:lFrag]; } if (lQuery) { [ms appendString:@"?"]; [ms appendString:lQuery]; } return ms; } else return _path; } - (NSString *)stringValueRelativeToURL:(NSURL *)_base { /* Sample: base: http://localhost:20000/dbd.woa/so/localhost/ self: http://localhost:20000/dbd.woa/so/localhost/Databases/A => Databases/A Note: on Panther Foundation the -path misses the trailing slash! */ NSString *relPath; if (_base == self || _base == nil) { relPath = [[self pathWithCorrectTrailingSlash] urlPathRelativeToSelf]; relPath = [self stringByAddingFragmentAndQueryToPath:relPath]; if (debugURLProcessing) { NSLog(@"%s: no base or base is self => '%@'", __PRETTY_FUNCTION__, relPath); } return relPath; } /* check whether we are already marked relative to _base .. */ if ([self baseURL] == _base) { NSString *p; p = [self relativePath]; #if COCOA_Foundation_LIBRARY || NeXT_Foundation_LIBRARY /* see -pathWithCorrectTrailingSlash for bug description ... */ if (![p hasSuffix:@"/"]) { if ([[self absoluteString] hasSuffix:@"/"]) p = [p stringByAppendingString:@"/"]; } #endif p = [self stringByAddingFragmentAndQueryToPath:p]; if (debugURLProcessing) { NSLog(@"%s: url base and _base match => '%@'", __PRETTY_FUNCTION__, p); } return p; } /* check whether we are in the same path namespace ... */ if (![self isInSameNamespaceWithURL:_base]) { /* need to return full URL */ relPath = [self absoluteString]; if (debugURLProcessing) { NSLog(@"%s: url is in different namespace from base => '%@'", __PRETTY_FUNCTION__, relPath); } return relPath; } relPath = [[self pathWithCorrectTrailingSlash] urlPathRelativeToPath:[_base pathWithCorrectTrailingSlash]]; if (debugURLProcessing) { NSLog(@"%s: path '%@', base-path '%@' => rel '%@'", __PRETTY_FUNCTION__, [self path], [_base path], relPath); } relPath = [self stringByAddingFragmentAndQueryToPath:relPath]; if (debugURLProcessing) { NSLog(@"%s: same namespace, but no direct relative (%@, base %@) => '%@'", __PRETTY_FUNCTION__, self, _base, relPath); } return relPath; } static BOOL isEqual(id o1, id o2) { if (o1 == o2) return YES; if (o1 == nil || o2 == nil) return NO; return [o1 isEqual:o2]; } - (BOOL)isInSameNamespaceWithURL:(NSURL *)_url { if (_url == nil) return NO; if (_url == self) return YES; if ([self isFileURL] && [_url isFileURL]) return YES; if ([self baseURL] == _url) return YES; if ([_url baseURL] == self) return YES; if (![[self scheme] isEqualToString:[_url scheme]]) return NO; if (!isEqual([self host], [_url host])) return NO; if (!isEqual([self port], [_url port])) return NO; if (!isEqual([self user], [_url user])) return NO; return YES; } @end /* NSURL */ @implementation NSString(URLPathProcessing) - (NSString *)urlPathRelativeToSelf { /* eg: "/a/b/c.html" should resolve to: "c.html" Directories are a bit more difficult, eg: "/a/b/c/" is resolved to "../c/" */ NSString *p; NSString *lp; p = self; lp = [p lastPathComponent]; p = ([p hasSuffix:@"/"]) ? [NSString stringWithFormat:@"../%@/", p] : lp; return p; } - (NSString *)urlPathRelativeToRoot { NSString *p; p = self; if ([p isEqualToString:@"/"]) /* don't know better ... what is root-relative-to-root ? */ return @"/"; if ([p length] == 0) { NSLog(@"%s: invalid path (length 0), using /: %@", __PRETTY_FUNCTION__, self); return @"/"; } /* this is the same like the absoltute path, only without a leading "/" .. */ return [p substringFromIndex:1]; } - (NSString *)urlPathRelativeToPath:(NSString *)_base { /* This can be used for URLs in the same namespace. It should never return an absolute path (it only does in error conditions). */ if (_base == nil || [_base length] == 0) { NSLog(@"%s: invalid base (nil or length 0), using absolute path '%@' ...", __PRETTY_FUNCTION__, self); return self; } if ([_base isEqualToString:@"/"]) return [self urlPathRelativeToRoot]; if ([_base isEqualToString:self]) return [self urlPathRelativeToSelf]; if (debugURLProcessing) NSLog(@"%s: %@ relative to %@ ...", __PRETTY_FUNCTION__, self, _base); if ([self hasPrefix:_base]) { /* the whole base URI is prefix of our URI: case a) b: "/a/b/c" s: "/a/b/c/d" >: "c/d" case b) b: "/a/b/c/" s: "/a/b/c/d" >: "d" case c) b: "/a/b/c" s: "/a/b/ccc/d" >: "ccc/d" b=s is already catched above and s is guaranteed to be longer than b. */ unsigned blen; NSString *result; if (debugURLProcessing) NSLog(@"%s: has base as prefix ...", __PRETTY_FUNCTION__); blen = [_base length]; if ([_base characterAtIndex:(blen - 1)] == '/') { /* last char of 'b' is '/' => case b) */ result = [self substringFromIndex:blen]; } else { /* last char of 'b' is not a slash (either case a) or case c)), both are handled in the same way (search last / ...) */ NSRange r; r = [_base rangeOfString:@"/" options:NSBackwardsSearch]; if (r.length == 0) { NSLog(@"%s: invalid base, found no '/': '%@' !", __PRETTY_FUNCTION__, _base); result = self; } else { /* no we have case b) ... */ result = [self substringFromIndex:(r.location + 1)]; } } return result; } else { NSString *prefix; unsigned plen; prefix = [self commonPrefixWithString:_base options:0]; plen = [prefix length]; if (plen == 0) { NSLog(@"%s: invalid strings, no common prefix ...: '%@' and '%@' !", __PRETTY_FUNCTION__, self, _base); return self; } if (plen == 1) { /* common prefix is root. That is, nothing in common: b: "/a/b" s: "/l" >: "../l" b: "/a/b/" s: "/l" >: "../../l" */ if ([prefix characterAtIndex:0] != '/') { NSLog(@"%s: invalid strings, common prefix '%@' is not '/': " @"'%@' and '%@' !", __PRETTY_FUNCTION__, self, _base, prefix); } /* TODO: to be completed ... */ return self; } /* TODO: to be completed ... */ } return self; } @end /* NSString(URLPathProcessing) */