]> err.no Git - sope/blob - sope-core/NGExtensions/FdExt.subproj/NSURL+misc.m
Drop apache 1 build-dependency
[sope] / sope-core / NGExtensions / FdExt.subproj / NSURL+misc.m
1 /*
2   Copyright (C) 2000-2005 SKYRIX Software AG
3
4   This file is part of SOPE.
5
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
9   later version.
10
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.
15
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
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];
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);
103     }
104     return relPath;
105   }
106   
107   /* check whether we are already marked relative to _base .. */
108   if ([self baseURL] == _base) {
109     NSString *p;
110     
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:@"/"];
117     }
118 #endif
119     p = [self stringByAddingFragmentAndQueryToPath:p];
120     if (debugURLProcessing) {
121       NSLog(@"%s: url base and _base match => '%@'", 
122             __PRETTY_FUNCTION__, p);
123     }
124     return p;
125   }
126   
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);
134     }
135     return relPath;
136   }
137   
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);
143   }
144   relPath = [self stringByAddingFragmentAndQueryToPath:relPath];
145   
146   if (debugURLProcessing) {
147     NSLog(@"%s: same namespace, but no direct relative (%@, base %@) => '%@'", 
148           __PRETTY_FUNCTION__, 
149           [self absoluteString], [_base absoluteString], relPath);
150   }
151   return relPath;
152 }
153
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];
158 }
159
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;
166   
167   if (![[self scheme] isEqualToString:[_url scheme]])
168     return NO;
169   
170   if (!isEqual([self host], [_url host]))
171     return NO;
172   if (!isEqual([self port], [_url port]))
173     return NO;
174   if (!isEqual([self user], [_url user]))
175     return NO;
176   
177   return YES;
178 }
179
180 @end /* NSURL */
181
182 @implementation NSString(URLPathProcessing)
183
184 - (NSString *)urlPathRelativeToSelf {
185   /*
186     eg:                "/a/b/c.html"
187     should resolve to: "c.html"
188     
189     Directories are a bit more difficult, eg:
190       "/a/b/c/"
191     is resolved to
192       "../c/"
193   */
194   NSString *p;
195   NSString *lp;
196
197   /*
198     /SOGo/so/X/Mail/Y/INBOX/withsubdirs/
199     ..//SOGo/so/X/Mail/Y/INBOX/withsubdirs//
200   */
201   
202   p  = self;
203   lp = [p lastPathComponent];
204   if (![p hasSuffix:@"/"])
205     return lp;
206   
207   return [[@"../" stringByAppendingString:lp] stringByAppendingString:@"/"];
208 }
209
210 - (NSString *)urlPathRelativeToRoot {
211   NSString *p;
212   
213   p = self;
214   
215   if ([p isEqualToString:@"/"])
216     /* don't know better ... what is root-relative-to-root ? */
217     return @"/";
218   
219   if ([p length] == 0) {
220     NSLog(@"%s: invalid path (length 0), using /: %@",
221           __PRETTY_FUNCTION__, self);
222     return @"/";
223   }
224   
225   /* this is the same like the absolute path, only without a leading "/" .. */
226   return [p characterAtIndex:0] == '/' ? [p substringFromIndex:1] : p;
227 }
228
229 static NSString *calcRelativePathOfChildURL(NSString *self, NSString *_base) {
230   /*
231       the whole base URI is prefix of our URI:
232         case a)
233           b: "/a/b/c"
234           s: "/a/b/c/d"
235           >: "c/d"
236         case b)
237           b: "/a/b/c/"
238           s: "/a/b/c/d"
239           >: "d"
240         case c)
241           b: "/a/b/c"
242           s: "/a/b/ccc/d"
243           >: "ccc/d"
244         
245       b=s is already catched above and s is guaranteed to be
246       longer than b.
247   */
248   unsigned blen;
249   NSString *result;
250     
251   if (debugURLProcessing)
252       NSLog(@"%s:   has base as prefix ...", __PRETTY_FUNCTION__);
253   blen = [_base length];
254     
255   if ([_base characterAtIndex:(blen - 1)] == '/') {
256       /* last char of 'b' is '/' => case b) */
257       result = [self substringFromIndex:blen];
258   }
259   else {
260       /*
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 / ...)
263       */
264       NSRange  r;
265         
266       r = [_base rangeOfString:@"/" options:NSBackwardsSearch];
267       if (r.length == 0) {
268         NSLog(@"%s: invalid base, found no '/': '%@' !",
269               __PRETTY_FUNCTION__, _base);
270         result = self;
271       }
272       else {
273         /* no we have case b) ... */
274         result = [self substringFromIndex:(r.location + 1)];
275       }
276   }
277   return result;
278 }
279
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" ! */
283   NSString *s;
284   unsigned len;
285   NSRange  r;
286   
287   if (_other == self)
288     return self;
289   
290   s   = [self commonPrefixWithString:_other options:0];
291   len = [s length];
292   if (len == 0)
293     return s;
294   if ([s characterAtIndex:(len - 1)] == '/')
295     return s;
296   
297   r = [s rangeOfString:@"/" options:NSBackwardsSearch];
298   if (r.length == 0) /* hm, can't happen? */
299     return nil;
300   
301   return [s substringToIndex:(r.location + r.length)];;
302 }
303
304 static 
305 NSString *calcRelativePathOfNonChildURL(NSString *self, NSString *_base) {
306   unsigned numSlashes;
307   NSString *result;
308   NSString *prefix;
309   NSString *suffix;
310   unsigned plen;
311   
312   prefix     = [self commonDirPathPrefixWithString:_base];
313   plen       = [prefix length];
314   suffix     = [self substringFromIndex:plen];
315   numSlashes = 0;
316     
317   if (debugURLProcessing) {
318     NSLog(@"%s:   does not have base as prefix, common '%@'\n"
319           @"  self='%@'\n"
320           @"  base='%@'\n"
321           @"  suffix='%@'",
322           __PRETTY_FUNCTION__, prefix, self, _base, suffix);
323   }
324     
325   if (plen == 0) {
326       NSLog(@"%s: invalid strings, no common prefix ...: '%@' and '%@' !",
327               __PRETTY_FUNCTION__, self, _base);
328       return self;
329   }
330     
331   if (plen == 1) {
332       /*
333         common prefix is root. That is, nothing in common:
334           b: "/a/b"
335           s: "/l"
336           >: "../l"
337           
338           b: "/a/b/"
339           s: "/l"
340           >: "../../l"
341         (number of slashes without root * "..", then the trailer?)
342       */
343       unsigned i, len;
344       
345       len = [_base length];
346       
347       if ([prefix characterAtIndex:0] != '/') {
348         NSLog(@"%s: invalid strings, common prefix '%@' is not '/': "
349               @"'%@' and '%@' !",
350               __PRETTY_FUNCTION__, self, _base, prefix);
351       }
352       
353       for (i = 1 /* skip root */; i < len; i++) {
354         if ([_base characterAtIndex:i] == '/')
355           numSlashes++;
356       }
357   }
358   else {
359       /*
360         base: /dev/en/projects/bsd/index.html
361         self: /dev/en/macosx/
362         =>    ../../macosx/
363       */
364       NSString *basesuffix;
365       unsigned i, len;
366       
367       basesuffix = [_base substringFromIndex:plen];
368       len        = [basesuffix length];
369       
370       for (i = 0; i < len; i++) {
371         if ([basesuffix characterAtIndex:i] == '/')
372           numSlashes++;
373       }
374   }
375
376   if (debugURLProcessing)
377     NSLog(@"%s:   slashes: %d", __PRETTY_FUNCTION__, numSlashes);
378     
379   /* optimization for some depths */
380   switch (numSlashes) {
381     case 0: /* no slashes in base: b:/a, s:/images/a => images/a */
382       result = suffix;
383       break;
384     case 1: /* one slash in base: b:/a/, s:/images/a => ../images/a, etc */
385       result = [@"../" stringByAppendingString:suffix];
386       break;
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;
391     default: {
392       NSMutableString *ms;
393       unsigned i;
394       
395       ms = [NSMutableString stringWithCapacity:(numSlashes * 3)];
396       for (i = 0; i < numSlashes; i++)
397         [ms appendString:@"../"];
398       [ms appendString:suffix];
399       result = ms;
400       break;
401     }
402   }
403   if (debugURLProcessing)
404     NSLog(@"%s:  => '%@'", __PRETTY_FUNCTION__, result);
405   return result;
406 }
407
408 - (NSString *)urlPathRelativeToPath:(NSString *)_base {
409   /*
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).
412   */
413   // TODO: the implementation can probably be optimized a _LOT_
414   
415   if (_base == nil || [_base length] == 0) {
416     NSLog(@"%s: invalid base (nil or length 0), using absolute path '%@' ...",
417           __PRETTY_FUNCTION__, self);
418     return self;
419   }
420   
421   if ([_base isEqualToString:@"/"])
422     return [self urlPathRelativeToRoot];
423   if ([_base isEqualToString:self])
424     return [self urlPathRelativeToSelf];
425   
426   if (debugURLProcessing)
427     NSLog(@"%s: %@ relative to %@ ...", __PRETTY_FUNCTION__, self, _base);
428   
429   if ([self hasPrefix:_base])
430     return calcRelativePathOfChildURL(self, _base);
431   
432   return calcRelativePathOfNonChildURL(self, _base);
433 }
434
435 @end /* NSString(URLPathProcessing) */