/* * Copyright (c) 2008, 2009, 2010, 2011 * Jonathan Schleifer <js@webkeks.org> * * All rights reserved. * * This file is part of ObjFW. It may be distributed under the terms of the * Q Public License 1.0, which can be found in the file LICENSE.QPL included in * the packaging of this file. * * Alternatively, it may be distributed under the terms of the GNU General * Public License, either version 2 or 3, which can be found in the file * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this * file. */ #include "config.h" #include <stdlib.h> #include <string.h> #include <assert.h> #import "OFURL.h" #import "OFString.h" #import "OFArray.h" #import "OFAutoreleasePool.h" #import "OFInvalidArgumentException.h" #import "OFInvalidFormatException.h" #import "OFOutOfMemoryException.h" #import "macros.h" #define ADD_STR_HASH(str) \ 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; self = [super init]; @try { char *tmp, *tmp2; if ((str_c2 = strdup([str cString])) == NULL) @throw [OFOutOfMemoryException newWithClass: isa requestedSize: [str cStringLength]]; str_c = str_c2; if (!strncmp(str_c, "file://", 7)) { scheme = @"file"; path = [[OFString alloc] initWithCString: str_c + 7]; return self; } else if (!strncmp(str_c, "http://", 7)) { scheme = @"http"; str_c += 7; } else if (!strncmp(str_c, "https://", 8)) { scheme = @"https"; str_c += 8; } else @throw [OFInvalidFormatException newWithClass: isa]; if ((tmp = strchr(str_c, '/')) != NULL) { *tmp = '\0'; tmp++; } if ((tmp2 = strchr(str_c, '@')) != NULL) { char *tmp3; *tmp2 = '\0'; tmp2++; if ((tmp3 = strchr(str_c, ':')) != NULL) { *tmp3 = '\0'; tmp3++; user = [[OFString alloc] initWithCString: str_c]; password = [[OFString alloc] initWithCString: tmp3]; } else user = [[OFString alloc] initWithCString: str_c]; str_c = tmp2; } if ((tmp2 = strchr(str_c, ':')) != NULL) { OFAutoreleasePool *pool; OFString *port_str; *tmp2 = '\0'; tmp2++; host = [[OFString alloc] initWithCString: str_c]; pool = [[OFAutoreleasePool alloc] init]; port_str = [[OFString alloc] initWithCString: tmp2]; if ([port_str decimalValue] > 65535) @throw [OFInvalidFormatException newWithClass: isa]; port = [port_str decimalValue]; [pool release]; } else { host = [[OFString alloc] initWithCString: str_c]; if ([scheme isEqual: @"http"]) port = 80; else if ([scheme isEqual: @"https"]) port = 443; else assert(0); } if ((str_c = tmp) != NULL) { 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]; } path = [[OFString alloc] initWithFormat: @"/%s", str_c]; } else path = @""; } @catch (id e) { [self release]; @throw e; } @finally { 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]; 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 { free(str_c2); } return self; } - (void)dealloc { [scheme release]; [host release]; [user release]; [password release]; [path release]; [parameters release]; [query release]; [fragment release]; [super dealloc]; } - (BOOL)isEqual: (id)obj { OFURL *url; if (![obj isKindOfClass: [OFURL class]]) return NO; url = obj; if (![url->scheme isEqual: scheme]) return NO; if (![url->host isEqual: host]) return NO; if (url->port != port) return NO; if (![url->user isEqual: user]) return NO; if (![url->password isEqual: password]) return NO; if (![url->path isEqual: path]) return NO; if (![url->parameters isEqual: parameters]) return NO; if (![url->query isEqual: query]) return NO; if (![url->fragment isEqual: fragment]) return NO; return YES; } - (uint32_t)hash { uint32_t hash, h; OF_HASH_INIT(hash); ADD_STR_HASH(scheme); ADD_STR_HASH(host); OF_HASH_ADD(hash, (port >> 8) & 0xFF); OF_HASH_ADD(hash, port & 0xFF); ADD_STR_HASH(user); ADD_STR_HASH(password); ADD_STR_HASH(path); ADD_STR_HASH(parameters); ADD_STR_HASH(query); ADD_STR_HASH(fragment); OF_HASH_FINALIZE(hash); return hash; } - copy { OFURL *new = [[OFURL alloc] init]; new->scheme = [scheme copy]; new->host = [host copy]; new->port = port; new->user = [user copy]; new->password = [password copy]; new->path = [path copy]; new->parameters = [parameters copy]; new->query = [query copy]; new->fragment = [fragment copy]; return new; } - (OFString*)scheme { OF_GETTER(scheme, YES) } - (void)setScheme: (OFString*)scheme_ { if (![scheme_ isEqual: @"http"] && ![scheme_ isEqual: @"https"]) @throw [OFInvalidArgumentException newWithClass: isa selector: _cmd]; OFString *old = scheme; scheme = [scheme_ copy]; [old release]; } - (OFString*)host { OF_GETTER(host, YES) } - (void)setHost: (OFString*)host_ { OF_SETTER(host, host_, YES, YES) } - (uint16_t)port { return port; } - (void)setPort: (uint16_t)port_ { port = port_; } - (OFString*)user { OF_GETTER(user, YES) } - (void)setUser: (OFString*)user_ { OF_SETTER(user, user_, YES, YES) } - (OFString*)password { OF_GETTER(password, YES) } - (void)setPassword: (OFString*)password_ { OF_SETTER(password, password_, YES, YES) } - (OFString*)path { OF_GETTER(path, YES) } - (void)setPath: (OFString*)path_ { OFString *old; if (([scheme isEqual: @"http"] || [scheme isEqual: @"https"]) && ![path_ hasPrefix: @"/"]) @throw [OFInvalidArgumentException newWithClass: isa selector: _cmd]; old = path; path = [path_ copy]; [old release]; } - (OFString*)parameters { OF_GETTER(parameters, YES) } - (void)setParameters: (OFString*)parameters_ { OF_SETTER(parameters, parameters_, YES, YES) } - (OFString*)query { OF_GETTER(query, YES) } - (void)setQuery: (OFString*)query_ { OF_SETTER(query, query_, YES, YES) } - (OFString*)fragment { OF_GETTER(fragment, YES) } - (void)setFragment: (OFString*)fragment_ { OF_SETTER(fragment, fragment_, YES, YES) } - (OFString*)description { OFMutableString *desc = [OFMutableString stringWithFormat: @"%@://", scheme]; BOOL needPort = YES; if ([scheme isEqual: @"file"]) { [desc appendString: path]; return desc; } if (user != nil && password != nil) [desc appendFormat: @"%@:%@@", user, password]; else if (user != nil) [desc appendFormat: @"%@@", user]; [desc appendString: host]; if (([scheme isEqual: @"http"] && port == 80) || ([scheme isEqual: @"https"] && port == 443)) needPort = NO; if (needPort) [desc appendFormat: @":%d", port]; [desc appendString: path]; if (parameters != nil) [desc appendFormat: @";%@", parameters]; if (query != nil) [desc appendFormat: @"?%@", query]; if (fragment != nil) [desc appendFormat: @"#%@", fragment]; /* * Class swizzle the string to be immutable. We declared the return type * to be OFString*, so it can't be modified anyway. But not swizzling it * would create a real copy each time -[copy] is called. */ desc->isa = [OFString class]; return desc; } @end