]> err.no Git - sope/blob - sope-core/NGExtensions/FdExt.subproj/NSURL+misc.m
7870f2de19fad1f4e49ce5c73f3aaefbee176f3d
[sope] / sope-core / NGExtensions / FdExt.subproj / NSURL+misc.m
1 /*
2   Copyright (C) 2000-2004 SKYRIX Software AG
3
4   This file is part of OpenGroupware.org.
5
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
9   later version.
10
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.
15
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
19   02111-1307, USA.
20 */
21
22 #include "NSURL+misc.h"
23 #include "common.h"
24
25 static BOOL debugURLProcessing = NO;
26
27 @implementation NSURL(misc)
28
29 - (NSString *)pathWithCorrectTrailingSlash {
30 #if COCOA_Foundation_LIBRARY || NeXT_Foundation_LIBRARY
31   /*
32     At least on OSX 10.3 the -path method missing the trailing slash, eg:
33       http://localhost:20000/dbd.woa/so/localhost/
34     gives:
35       /dbd.woa/so/localhost
36   */
37   NSString *p;
38   
39   if ((p = [self path]) == nil)
40     return nil;
41   
42   if ([p hasSuffix:@"/"])
43     return p;
44
45   if (![[self absoluteString] hasSuffix:@"/"])
46     return p;
47   
48   /* so we are running into the bug ... */
49   return [p stringByAppendingString:@"/"];
50 #else
51   return [self path];
52 #endif
53 }
54
55 - (NSString *)stringByAddingFragmentAndQueryToPath:(NSString *)_path {
56   NSString *lFrag, *lQuery;
57   
58   if ([self isFileURL])
59     return _path;
60   
61   lFrag   = [self fragment];
62   lQuery  = [self query];
63   
64   if ((lFrag != nil) || (lQuery != nil)) {
65     NSMutableString *ms;
66     
67     ms = [NSMutableString stringWithCapacity:([_path length] + 32)];
68     
69     [ms appendString:_path];
70     
71     if (lFrag) {
72       [ms appendString:@"#"];
73       [ms appendString:lFrag];
74     }
75     if (lQuery) {
76       [ms appendString:@"?"];
77       [ms appendString:lQuery];
78     }
79     return ms;
80   }
81   else
82     return _path;
83 }
84
85 - (NSString *)stringValueRelativeToURL:(NSURL *)_base {
86   /*
87     Sample:
88       self: http://localhost:20000/dbd.woa/so/localhost/Databases/A
89       base: http://localhost:20000/dbd.woa/so/localhost/
90          => Databases/A
91     
92     Note: on Panther Foundation the -path misses the trailing slash!
93   */
94   NSString *relPath;
95   
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);
102     }
103     return relPath;
104   }
105   
106   /* check whether we are already marked relative to _base .. */
107   if ([self baseURL] == _base) {
108     NSString *p;
109     
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:@"/"];
116     }
117 #endif
118     p = [self stringByAddingFragmentAndQueryToPath:p];
119     if (debugURLProcessing) {
120       NSLog(@"%s: url base and _base match => '%@'", 
121             __PRETTY_FUNCTION__, p);
122     }
123     return p;
124   }
125   
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);
133     }
134     return relPath;
135   }
136   
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);
142   }
143   relPath = [self stringByAddingFragmentAndQueryToPath:relPath];
144   
145   if (debugURLProcessing) {
146     NSLog(@"%s: same namespace, but no direct relative (%@, base %@) => '%@'", 
147           __PRETTY_FUNCTION__, 
148           [self absoluteString], [_base absoluteString], relPath);
149   }
150   return relPath;
151 }
152
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];
157 }
158
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;
165   
166   if (![[self scheme] isEqualToString:[_url scheme]])
167     return NO;
168   
169   if (!isEqual([self host], [_url host]))
170     return NO;
171   if (!isEqual([self port], [_url port]))
172     return NO;
173   if (!isEqual([self user], [_url user]))
174     return NO;
175   
176   return YES;
177 }
178
179 @end /* NSURL */
180
181 @implementation NSString(URLPathProcessing)
182
183 - (NSString *)urlPathRelativeToSelf {
184   /*
185     eg:                "/a/b/c.html"
186     should resolve to: "c.html"
187     
188     Directories are a bit more difficult, eg:
189       "/a/b/c/"
190     is resolved to
191       "../c/"
192   */
193   NSString *p;
194   NSString *lp;
195   
196   p  = self;
197   lp = [p lastPathComponent];
198   
199   p = ([p hasSuffix:@"/"])
200     ? [NSString stringWithFormat:@"../%@/", p]
201     : lp;
202   return p;
203 }
204
205 - (NSString *)urlPathRelativeToRoot {
206   NSString *p;
207   
208   p = self;
209   
210   if ([p isEqualToString:@"/"])
211     /* don't know better ... what is root-relative-to-root ? */
212     return @"/";
213   
214   if ([p length] == 0) {
215     NSLog(@"%s: invalid path (length 0), using /: %@",
216           __PRETTY_FUNCTION__, self);
217     return @"/";
218   }
219   
220   /* this is the same like the absolute path, only without a leading "/" .. */
221   return [p characterAtIndex:0] == '/' ? [p substringFromIndex:1] : p;
222 }
223
224 static NSString *calcRelativePathOfChildURL(NSString *self, NSString *_base) {
225   /*
226       the whole base URI is prefix of our URI:
227         case a)
228           b: "/a/b/c"
229           s: "/a/b/c/d"
230           >: "c/d"
231         case b)
232           b: "/a/b/c/"
233           s: "/a/b/c/d"
234           >: "d"
235         case c)
236           b: "/a/b/c"
237           s: "/a/b/ccc/d"
238           >: "ccc/d"
239         
240       b=s is already catched above and s is guaranteed to be
241       longer than b.
242   */
243   unsigned blen;
244   NSString *result;
245     
246   if (debugURLProcessing)
247       NSLog(@"%s:   has base as prefix ...", __PRETTY_FUNCTION__);
248   blen = [_base length];
249     
250   if ([_base characterAtIndex:(blen - 1)] == '/') {
251       /* last char of 'b' is '/' => case b) */
252       result = [self substringFromIndex:blen];
253   }
254   else {
255       /*
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 / ...)
258       */
259       NSRange  r;
260         
261       r = [_base rangeOfString:@"/" options:NSBackwardsSearch];
262       if (r.length == 0) {
263         NSLog(@"%s: invalid base, found no '/': '%@' !",
264               __PRETTY_FUNCTION__, _base);
265         result = self;
266       }
267       else {
268         /* no we have case b) ... */
269         result = [self substringFromIndex:(r.location + 1)];
270       }
271   }
272   return result;
273 }
274
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" ! */
278   NSString *s;
279   unsigned len;
280   NSRange  r;
281   
282   if (_other == self)
283     return self;
284   
285   s   = [self commonPrefixWithString:_other options:0];
286   len = [s length];
287   if (len == 0)
288     return s;
289   if ([s characterAtIndex:(len - 1)] == '/')
290     return s;
291   
292   r = [s rangeOfString:@"/" options:NSBackwardsSearch];
293   if (r.length == 0) /* hm, can't happen? */
294     return nil;
295   
296   return [s substringToIndex:(r.location + r.length)];;
297 }
298
299 static 
300 NSString *calcRelativePathOfNonChildURL(NSString *self, NSString *_base) {
301   unsigned numSlashes;
302   NSString *result;
303   NSString *prefix;
304   NSString *suffix;
305   unsigned plen;
306   
307   prefix     = [self commonDirPathPrefixWithString:_base];
308   plen       = [prefix length];
309   suffix     = [self substringFromIndex:plen];
310   numSlashes = 0;
311     
312   if (debugURLProcessing) {
313     NSLog(@"%s:   does not have base as prefix, common '%@'\n"
314           @"  self='%@'\n"
315           @"  base='%@'\n"
316           @"  suffix='%@'",
317           __PRETTY_FUNCTION__, prefix, self, _base, suffix);
318   }
319     
320   if (plen == 0) {
321       NSLog(@"%s: invalid strings, no common prefix ...: '%@' and '%@' !",
322               __PRETTY_FUNCTION__, self, _base);
323       return self;
324   }
325     
326   if (plen == 1) {
327       /*
328         common prefix is root. That is, nothing in common:
329           b: "/a/b"
330           s: "/l"
331           >: "../l"
332           
333           b: "/a/b/"
334           s: "/l"
335           >: "../../l"
336         (number of slashes without root * "..", then the trailer?)
337       */
338       unsigned i, len;
339       
340       len = [_base length];
341       
342       if ([prefix characterAtIndex:0] != '/') {
343         NSLog(@"%s: invalid strings, common prefix '%@' is not '/': "
344               @"'%@' and '%@' !",
345               __PRETTY_FUNCTION__, self, _base, prefix);
346       }
347       
348       for (i = 1 /* skip root */; i < len; i++) {
349         if ([_base characterAtIndex:i] == '/')
350           numSlashes++;
351       }
352   }
353   else {
354       /*
355         base: /dev/en/projects/bsd/index.html
356         self: /dev/en/macosx/
357         =>    ../../macosx/
358       */
359       NSString *basesuffix;
360       unsigned i, len;
361       
362       basesuffix = [_base substringFromIndex:plen];
363       len        = [basesuffix length];
364       
365       for (i = 0; i < len; i++) {
366         if ([basesuffix characterAtIndex:i] == '/')
367           numSlashes++;
368       }
369   }
370
371   if (debugURLProcessing)
372     NSLog(@"%s:   slashes: %d", __PRETTY_FUNCTION__, numSlashes);
373     
374   /* optimization for some depths */
375   switch (numSlashes) {
376     case 0: /* no slashes in base: b:/a, s:/images/a => images/a */
377       result = suffix;
378       break;
379     case 1: /* one slash in base: b:/a/, s:/images/a => ../images/a, etc */
380       result = [@"../" stringByAppendingString:suffix];
381       break;
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;
386     default: {
387       NSMutableString *ms;
388       unsigned i;
389       
390       ms = [NSMutableString stringWithCapacity:(numSlashes * 3)];
391       for (i = 0; i < numSlashes; i++)
392         [ms appendString:@"../"];
393       [ms appendString:suffix];
394       result = ms;
395       break;
396     }
397   }
398   if (debugURLProcessing)
399     NSLog(@"%s:  => '%@'", __PRETTY_FUNCTION__, result);
400   return result;
401 }
402
403 - (NSString *)urlPathRelativeToPath:(NSString *)_base {
404   /*
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).
407   */
408   // TODO: the implementation can probably be optimized a _LOT_
409   
410   if (_base == nil || [_base length] == 0) {
411     NSLog(@"%s: invalid base (nil or length 0), using absolute path '%@' ...",
412           __PRETTY_FUNCTION__, self);
413     return self;
414   }
415   
416   if ([_base isEqualToString:@"/"])
417     return [self urlPathRelativeToRoot];
418   if ([_base isEqualToString:self])
419     return [self urlPathRelativeToSelf];
420   
421   if (debugURLProcessing)
422     NSLog(@"%s: %@ relative to %@ ...", __PRETTY_FUNCTION__, self, _base);
423   
424   if ([self hasPrefix:_base])
425     return calcRelativePathOfChildURL(self, _base);
426   
427   return calcRelativePathOfNonChildURL(self, _base);
428 }
429
430 @end /* NSString(URLPathProcessing) */