Index: src/OFURL.h ================================================================== --- src/OFURL.h +++ src/OFURL.h @@ -45,23 +45,41 @@ @property (copy) OFString *query; @property (copy) OFString *fragment; #endif /** - * \param str A string describing an URL + * \param str A string describing a URL * \return A new, autoreleased OFURL */ + URLWithString: (OFString*)str; +/** + * \param str A string describing a URL + * \param url An URL to which the string is relative + * \return A new, autoreleased OFURL + */ ++ URLWithString: (OFString*)str + relativeToURL: (OFURL*)url; + /** * Initializes an already allocated OFURL. * - * \param str A string describing an URL + * \param str A string describing a URL * \return An initialized OFURL */ - initWithString: (OFString*)str; +/** + * Initializes an already allocated OFURL. + * + * \param str A string describing a URL + * \param url A URL to which the string is relative + * \return An initialized OFURL + */ +- initWithString: (OFString*)str + relativeToURL: (OFURL*)url; + /** * \return The scheme part of the URL */ - (OFString*)scheme; Index: src/OFURL.m ================================================================== --- src/OFURL.m +++ src/OFURL.m @@ -20,10 +20,11 @@ #include #include #import "OFURL.h" #import "OFString.h" +#import "OFArray.h" #import "OFAutoreleasePool.h" #import "OFExceptions.h" #import "macros.h" #define ADD_STR_HASH(str) \ @@ -30,16 +31,68 @@ h = [str hash]; \ OF_HASH_ADD(hash, h >> 24); \ OF_HASH_ADD(hash, (h >> 16) & 0xFF); \ OF_HASH_ADD(hash, (h >> 8) & 0xFF); \ OF_HASH_ADD(hash, h & 0xFF); + +static OF_INLINE OFString* +resolve_relative_path(OFString *path) +{ + OFAutoreleasePool *pool = [[OFAutoreleasePool alloc] init]; + OFMutableArray *array; + OFString *ret; + BOOL done = NO; + + array = [[[path componentsSeparatedByString: @"/"] mutableCopy] + autorelease]; + + while (!done) { + id *array_c = [array cArray]; + size_t i, array_len = [array count]; + + done = YES; + + for (i = 0; i < array_len; i++) { + if ([array_c[i] isEqual: @"."]) { + [array removeObjectAtIndex: i]; + done = NO; + + break; + } + + if ([array_c[i] isEqual: @".."]) { + [array removeObjectAtIndex: i]; + + if (i > 0) + [array removeObjectAtIndex: i - 1]; + + done = NO; + + break; + } + } + } + + ret = [[array componentsJoinedByString: @"/"] retain]; + + [pool release]; + + return [ret autorelease]; +} @implementation OFURL + URLWithString: (OFString*)str { return [[[self alloc] initWithString: str] autorelease]; } + ++ URLWithString: (OFString*)str + relativeToURL: (OFURL*)url +{ + return [[[self alloc] initWithString: str + relativeToURL: url] autorelease]; +} - initWithString: (OFString*)str { char *str_c, *str_c2 = NULL; @@ -141,10 +194,84 @@ parameters = [[OFString alloc] initWithCString: tmp + 1]; } path = [[OFString alloc] initWithCString: str_c]; + } + } @catch (id e) { + [self release]; + @throw e; + } @finally { + if (str_c2 != NULL) + free(str_c2); + } + + return self; +} + +- initWithString: (OFString*)str + relativeToURL: (OFURL*)url +{ + char *str_c, *str_c2 = NULL; + + if ([str containsString: @"://"]) + return [self initWithString: str]; + + self = [super init]; + + @try { + char *tmp; + + scheme = [url->scheme copy]; + host = [url->host copy]; + port = url->port; + user = [url->user copy]; + password = [url->password copy]; + + if ((str_c2 = strdup([str cString])) == NULL) + @throw [OFOutOfMemoryException + newWithClass: isa + requestedSize: [str cStringLength]]; + + str_c = str_c2; + + if ((tmp = strchr(str_c, '#')) != NULL) { + *tmp = '\0'; + fragment = [[OFString alloc] initWithCString: tmp + 1]; + } + + if ((tmp = strchr(str_c, '?')) != NULL) { + *tmp = '\0'; + query = [[OFString alloc] initWithCString: tmp + 1]; + } + + if ((tmp = strchr(str_c, ';')) != NULL) { + *tmp = '\0'; + parameters = [[OFString alloc] + initWithCString: tmp + 1]; + } + + if (*str_c == '/') + path = [[OFString alloc] initWithCString: str_c + 1]; + else { + OFAutoreleasePool *pool; + OFString *s; + + pool = [[OFAutoreleasePool alloc] init]; + + if ([url->path hasSuffix: @"/"]) + s = [OFString stringWithFormat: @"%@%s", + url->path, + str_c]; + else + s = [OFString stringWithFormat: @"%@/../%s", + url->path, + str_c]; + + path = [resolve_relative_path(s) copy]; + + [pool release]; } } @catch (id e) { [self release]; @throw e; } @finally { Index: tests/OFURLTests.m ================================================================== --- tests/OFURLTests.m +++ tests/OFURLTests.m @@ -35,10 +35,23 @@ TEST(@"+[URLWithString:]", R(u1 = [OFURL URLWithString: url_str]) && R(u2 = [OFURL URLWithString: @"http://foo:80"]) && R(u3 = [OFURL URLWithString: @"http://bar/"])) + TEST(@"+[URLWithString:relativeToURL:]", + [[[OFURL URLWithString: @"/foo" + relativeToURL: u1] description] isEqual: + @"http://u:p@h:1234/foo"] && + [[[OFURL URLWithString: @"foo/bar?q" + relativeToURL: [OFURL URLWithString: @"http://h/qux/quux"]] + description] isEqual: @"http://h/qux/foo/bar?q"] && + [[[OFURL URLWithString: @"foo/bar" + relativeToURL: [OFURL URLWithString: @"http://h/qux/?x"]] + description] isEqual: @"http://h/qux/foo/bar"] && + [[[OFURL URLWithString: @"http://foo/?q" + relativeToURL: u1] description] isEqual: @"http://foo/?q"]) + TEST(@"-[description]", [[u1 description] isEqual: url_str] && [[u2 description] isEqual: @"http://foo"] && [[u3 description] isEqual: @"http://bar/"])