@@ -16,87 +16,44 @@ #include "config.h" #include #include -#import "OFURL.h" +#import "OFURI.h" #import "OFArray.h" #import "OFDictionary.h" #ifdef OF_HAVE_FILES # import "OFFileManager.h" -# import "OFFileURLHandler.h" +# import "OFFileURIHandler.h" #endif #import "OFNumber.h" #import "OFOnce.h" +#import "OFPair.h" #import "OFString.h" #import "OFXMLElement.h" #import "OFInvalidArgumentException.h" #import "OFInvalidFormatException.h" #import "OFOutOfMemoryException.h" -@interface OFURLAllowedCharacterSetBase: OFCharacterSet -@end - -@interface OFURLAllowedCharacterSet: OFURLAllowedCharacterSetBase -@end - -@interface OFURLSchemeAllowedCharacterSet: OFURLAllowedCharacterSetBase -@end - -@interface OFURLPathAllowedCharacterSet: OFURLAllowedCharacterSetBase -@end - -@interface OFURLQueryOrFragmentAllowedCharacterSet: OFURLAllowedCharacterSetBase -@end - -@interface OFURLQueryKeyValueAllowedCharacterSet: OFURLAllowedCharacterSetBase -@end - -static OFCharacterSet *URLAllowedCharacterSet = nil; -static OFCharacterSet *URLSchemeAllowedCharacterSet = nil; -static OFCharacterSet *URLPathAllowedCharacterSet = nil; -static OFCharacterSet *URLQueryOrFragmentAllowedCharacterSet = nil; -static OFCharacterSet *URLQueryKeyValueAllowedCharacterSet = nil; - -static OFOnceControl URLAllowedCharacterSetOnce = OFOnceControlInitValue; -static OFOnceControl URLQueryOrFragmentAllowedCharacterSetOnce = - OFOnceControlInitValue; - -static void -initURLAllowedCharacterSet(void) -{ - URLAllowedCharacterSet = [[OFURLAllowedCharacterSet alloc] init]; -} - -static void -initURLSchemeAllowedCharacterSet(void) -{ - URLSchemeAllowedCharacterSet = - [[OFURLSchemeAllowedCharacterSet alloc] init]; -} - -static void -initURLPathAllowedCharacterSet(void) -{ - URLPathAllowedCharacterSet = - [[OFURLPathAllowedCharacterSet alloc] init]; -} - -static void -initURLQueryOrFragmentAllowedCharacterSet(void) -{ - URLQueryOrFragmentAllowedCharacterSet = - [[OFURLQueryOrFragmentAllowedCharacterSet alloc] init]; -} - -static void -initURLQueryKeyValueAllowedCharacterSet(void) -{ - URLQueryKeyValueAllowedCharacterSet = - [[OFURLQueryKeyValueAllowedCharacterSet alloc] init]; -} +@interface OFURIAllowedCharacterSetBase: OFCharacterSet +@end + +@interface OFURIAllowedCharacterSet: OFURIAllowedCharacterSetBase +@end + +@interface OFURISchemeAllowedCharacterSet: OFURIAllowedCharacterSetBase +@end + +@interface OFURIPathAllowedCharacterSet: OFURIAllowedCharacterSetBase +@end + +@interface OFURIQueryOrFragmentAllowedCharacterSet: OFURIAllowedCharacterSetBase +@end + +@interface OFURIQueryKeyValueAllowedCharacterSet: OFURIAllowedCharacterSetBase +@end OF_DIRECT_MEMBERS @interface OFInvertedCharacterSetWithoutPercent: OFCharacterSet { OFCharacterSet *_characterSet; @@ -103,13 +60,57 @@ bool (*_characterIsMember)(id, SEL, OFUnichar); } - (instancetype)initWithCharacterSet: (OFCharacterSet *)characterSet; @end + +static OFCharacterSet *URIAllowedCharacterSet = nil; +static OFCharacterSet *URISchemeAllowedCharacterSet = nil; +static OFCharacterSet *URIPathAllowedCharacterSet = nil; +static OFCharacterSet *URIQueryOrFragmentAllowedCharacterSet = nil; +static OFCharacterSet *URIQueryKeyValueAllowedCharacterSet = nil; + +static OFOnceControl URIAllowedCharacterSetOnce = OFOnceControlInitValue; +static OFOnceControl URIQueryOrFragmentAllowedCharacterSetOnce = + OFOnceControlInitValue; + +static void +initURIAllowedCharacterSet(void) +{ + URIAllowedCharacterSet = [[OFURIAllowedCharacterSet alloc] init]; +} + +static void +initURISchemeAllowedCharacterSet(void) +{ + URISchemeAllowedCharacterSet = + [[OFURISchemeAllowedCharacterSet alloc] init]; +} + +static void +initURIPathAllowedCharacterSet(void) +{ + URIPathAllowedCharacterSet = + [[OFURIPathAllowedCharacterSet alloc] init]; +} + +static void +initURIQueryOrFragmentAllowedCharacterSet(void) +{ + URIQueryOrFragmentAllowedCharacterSet = + [[OFURIQueryOrFragmentAllowedCharacterSet alloc] init]; +} + +static void +initURIQueryKeyValueAllowedCharacterSet(void) +{ + URIQueryKeyValueAllowedCharacterSet = + [[OFURIQueryKeyValueAllowedCharacterSet alloc] init]; +} bool -OFURLIsIPv6Host(OFString *host) +OFURIIsIPv6Host(OFString *host) { const char *UTF8String = host.UTF8String; bool hasColon = false; while (*UTF8String != '\0') { @@ -125,11 +126,11 @@ } return hasColon; } -@implementation OFURLAllowedCharacterSetBase +@implementation OFURIAllowedCharacterSetBase - (instancetype)autorelease { return self; } @@ -146,11 +147,11 @@ { return OFMaxRetainCount; } @end -@implementation OFURLAllowedCharacterSet +@implementation OFURIAllowedCharacterSet - (bool)characterIsMember: (OFUnichar)character { if (character < CHAR_MAX && OFASCIIIsAlnum(character)) return true; @@ -175,11 +176,11 @@ return false; } } @end -@implementation OFURLSchemeAllowedCharacterSet +@implementation OFURISchemeAllowedCharacterSet - (bool)characterIsMember: (OFUnichar)character { if (character < CHAR_MAX && OFASCIIIsAlnum(character)) return true; @@ -192,11 +193,11 @@ return false; } } @end -@implementation OFURLPathAllowedCharacterSet +@implementation OFURIPathAllowedCharacterSet - (bool)characterIsMember: (OFUnichar)character { if (character < CHAR_MAX && OFASCIIIsAlnum(character)) return true; @@ -224,11 +225,11 @@ return false; } } @end -@implementation OFURLQueryOrFragmentAllowedCharacterSet +@implementation OFURIQueryOrFragmentAllowedCharacterSet - (bool)characterIsMember: (OFUnichar)character { if (character < CHAR_MAX && OFASCIIIsAlnum(character)) return true; @@ -257,11 +258,11 @@ return false; } } @end -@implementation OFURLQueryKeyValueAllowedCharacterSet +@implementation OFURIQueryKeyValueAllowedCharacterSet - (bool)characterIsMember: (OFUnichar)character { if (character < CHAR_MAX && OFASCIIIsAlnum(character)) return true; @@ -321,410 +322,467 @@ @selector(characterIsMember:), character)); } @end void -OFURLVerifyIsEscaped(OFString *string, OFCharacterSet *characterSet) +OFURIVerifyIsEscaped(OFString *string, OFCharacterSet *characterSet, + bool allowPercent) { void *pool = objc_autoreleasePoolPush(); - characterSet = [[[OFInvertedCharacterSetWithoutPercent alloc] - initWithCharacterSet: characterSet] autorelease]; + if (allowPercent) + characterSet = [[[OFInvertedCharacterSetWithoutPercent alloc] + initWithCharacterSet: characterSet] autorelease]; + else + characterSet = characterSet.invertedSet; if ([string indexOfCharacterFromSet: characterSet] != OFNotFound) @throw [OFInvalidFormatException exception]; objc_autoreleasePoolPop(pool); } -@implementation OFCharacterSet (URLCharacterSets) -+ (OFCharacterSet *)URLSchemeAllowedCharacterSet -{ - static OFOnceControl onceControl = OFOnceControlInitValue; - OFOnce(&onceControl, initURLSchemeAllowedCharacterSet); - - return URLSchemeAllowedCharacterSet; -} - -+ (OFCharacterSet *)URLHostAllowedCharacterSet -{ - OFOnce(&URLAllowedCharacterSetOnce, initURLAllowedCharacterSet); - - return URLAllowedCharacterSet; -} - -+ (OFCharacterSet *)URLUserAllowedCharacterSet -{ - OFOnce(&URLAllowedCharacterSetOnce, initURLAllowedCharacterSet); - - return URLAllowedCharacterSet; -} - -+ (OFCharacterSet *)URLPasswordAllowedCharacterSet -{ - OFOnce(&URLAllowedCharacterSetOnce, initURLAllowedCharacterSet); - - return URLAllowedCharacterSet; -} - -+ (OFCharacterSet *)URLPathAllowedCharacterSet -{ - static OFOnceControl onceControl = OFOnceControlInitValue; - OFOnce(&onceControl, initURLPathAllowedCharacterSet); - - return URLPathAllowedCharacterSet; -} - -+ (OFCharacterSet *)URLQueryAllowedCharacterSet -{ - OFOnce(&URLQueryOrFragmentAllowedCharacterSetOnce, - initURLQueryOrFragmentAllowedCharacterSet); - - return URLQueryOrFragmentAllowedCharacterSet; -} - -+ (OFCharacterSet *)URLQueryKeyValueAllowedCharacterSet -{ - static OFOnceControl onceControl = OFOnceControlInitValue; - OFOnce(&onceControl, initURLQueryKeyValueAllowedCharacterSet); - - return URLQueryKeyValueAllowedCharacterSet; -} - -+ (OFCharacterSet *)URLFragmentAllowedCharacterSet -{ - OFOnce(&URLQueryOrFragmentAllowedCharacterSetOnce, - initURLQueryOrFragmentAllowedCharacterSet); - - return URLQueryOrFragmentAllowedCharacterSet; +@implementation OFCharacterSet (URICharacterSets) ++ (OFCharacterSet *)URISchemeAllowedCharacterSet +{ + static OFOnceControl onceControl = OFOnceControlInitValue; + OFOnce(&onceControl, initURISchemeAllowedCharacterSet); + + return URISchemeAllowedCharacterSet; +} + ++ (OFCharacterSet *)URIHostAllowedCharacterSet +{ + OFOnce(&URIAllowedCharacterSetOnce, initURIAllowedCharacterSet); + + return URIAllowedCharacterSet; +} + ++ (OFCharacterSet *)URIUserAllowedCharacterSet +{ + OFOnce(&URIAllowedCharacterSetOnce, initURIAllowedCharacterSet); + + return URIAllowedCharacterSet; +} + ++ (OFCharacterSet *)URIPasswordAllowedCharacterSet +{ + OFOnce(&URIAllowedCharacterSetOnce, initURIAllowedCharacterSet); + + return URIAllowedCharacterSet; +} + ++ (OFCharacterSet *)URIPathAllowedCharacterSet +{ + static OFOnceControl onceControl = OFOnceControlInitValue; + OFOnce(&onceControl, initURIPathAllowedCharacterSet); + + return URIPathAllowedCharacterSet; +} + ++ (OFCharacterSet *)URIQueryAllowedCharacterSet +{ + OFOnce(&URIQueryOrFragmentAllowedCharacterSetOnce, + initURIQueryOrFragmentAllowedCharacterSet); + + return URIQueryOrFragmentAllowedCharacterSet; +} + ++ (OFCharacterSet *)URIQueryKeyValueAllowedCharacterSet +{ + static OFOnceControl onceControl = OFOnceControlInitValue; + OFOnce(&onceControl, initURIQueryKeyValueAllowedCharacterSet); + + return URIQueryKeyValueAllowedCharacterSet; +} + ++ (OFCharacterSet *)URIFragmentAllowedCharacterSet +{ + OFOnce(&URIQueryOrFragmentAllowedCharacterSetOnce, + initURIQueryOrFragmentAllowedCharacterSet); + + return URIQueryOrFragmentAllowedCharacterSet; } @end -@implementation OFURL -+ (instancetype)URL +@implementation OFURI ++ (instancetype)URI { return [[[self alloc] init] autorelease]; } -+ (instancetype)URLWithString: (OFString *)string ++ (instancetype)URIWithString: (OFString *)string { return [[[self alloc] initWithString: string] autorelease]; } -+ (instancetype)URLWithString: (OFString *)string - relativeToURL: (OFURL *)URL ++ (instancetype)URIWithString: (OFString *)string + relativeToURI: (OFURI *)URI { return [[[self alloc] initWithString: string - relativeToURL: URL] autorelease]; + relativeToURI: URI] autorelease]; } #ifdef OF_HAVE_FILES -+ (instancetype)fileURLWithPath: (OFString *)path ++ (instancetype)fileURIWithPath: (OFString *)path { - return [[[self alloc] initFileURLWithPath: path] autorelease]; + return [[[self alloc] initFileURIWithPath: path] autorelease]; } -+ (instancetype)fileURLWithPath: (OFString *)path ++ (instancetype)fileURIWithPath: (OFString *)path isDirectory: (bool)isDirectory { - return [[[self alloc] initFileURLWithPath: path + return [[[self alloc] initFileURIWithPath: path isDirectory: isDirectory] autorelease]; } #endif + +static void +parseUserInfo(OFURI *self, const char *UTF8String, size_t length) +{ + const char *colon; + + if ((colon = memchr(UTF8String, ':', length)) != NULL) { + self->_percentEncodedUser = [[OFString alloc] + initWithUTF8String: UTF8String + length: colon - UTF8String]; + self->_percentEncodedPassword = [[OFString alloc] + initWithUTF8String: colon + 1 + length: length - (colon - UTF8String) - 1]; + + OFURIVerifyIsEscaped(self->_percentEncodedPassword, + [OFCharacterSet URIPasswordAllowedCharacterSet], true); + } else + self->_percentEncodedUser = [[OFString alloc] + initWithUTF8String: UTF8String + length: length]; + + OFURIVerifyIsEscaped(self->_percentEncodedUser, + [OFCharacterSet URIUserAllowedCharacterSet], true); +} + +static void +parseHostPort(OFURI *self, const char *UTF8String, size_t length) +{ + OFString *portString; + + if (*UTF8String == '[') { + const char *end = memchr(UTF8String, ']', length); + + if (end == NULL) + @throw [OFInvalidFormatException exception]; + + for (const char *iter = UTF8String + 1; iter < end; iter++) + if (!OFASCIIIsDigit(*iter) && *iter != ':' && + (*iter < 'a' || *iter > 'f') && + (*iter < 'A' || *iter > 'F')) + @throw [OFInvalidFormatException exception]; + + self->_percentEncodedHost = [[OFString alloc] + initWithUTF8String: UTF8String + length: end - UTF8String + 1]; + + length -= (end - UTF8String) + 1; + UTF8String = end + 1; + } else { + const char *colon = memchr(UTF8String, ':', length); + + if (colon != NULL) { + self->_percentEncodedHost = [[OFString alloc] + initWithUTF8String: UTF8String + length: colon - UTF8String]; + + length -= colon - UTF8String; + UTF8String = colon; + } else { + self->_percentEncodedHost = [[OFString alloc] + initWithUTF8String: UTF8String + length: length]; + + UTF8String += length; + length = 0; + } + + OFURIVerifyIsEscaped(self->_percentEncodedHost, + [OFCharacterSet URIHostAllowedCharacterSet], true); + } + + if (length == 0) + return; + + if (length <= 1 || *UTF8String != ':') + @throw [OFInvalidFormatException exception]; + + UTF8String++; + length--; + + for (size_t i = 0; i < length; i++) + if (!OFASCIIIsDigit(UTF8String[i])) + @throw [OFInvalidFormatException exception]; + + portString = [OFString stringWithUTF8String: UTF8String length: length]; + + if (portString.unsignedLongLongValue > 65535) + @throw [OFInvalidFormatException exception]; + + self->_port = [[OFNumber alloc] initWithUnsignedShort: + (unsigned short)portString.unsignedLongLongValue]; +} + +static size_t +parseAuthority(OFURI *self, const char *UTF8String, size_t length) +{ + size_t ret; + const char *slash, *at; + + if ((slash = memchr(UTF8String, '/', length)) != NULL) + length = slash - UTF8String; + + ret = length; + + if ((at = memchr(UTF8String, '@', length)) != NULL) { + parseUserInfo(self, UTF8String, at - UTF8String); + + length -= at - UTF8String + 1; + UTF8String = at + 1; + } + + parseHostPort(self, UTF8String, length); + + return ret; +} + +static void +parsePathQueryFragment(const char *UTF8String, size_t length, + OFString **pathString, OFString **queryString, OFString **fragmentString) +{ + const char *fragment, *query; + + if ((fragment = memchr(UTF8String, '#', length)) != NULL) { + *fragmentString = [OFString + stringWithUTF8String: fragment + 1 + length: length - (fragment - UTF8String) - 1]; + + OFURIVerifyIsEscaped(*fragmentString, + [OFCharacterSet URIQueryAllowedCharacterSet], true); + + length = fragment - UTF8String; + } + + if ((query = memchr(UTF8String, '?', length)) != NULL) { + *queryString = [OFString + stringWithUTF8String: query + 1 + length: length - (query - UTF8String) - 1]; + + OFURIVerifyIsEscaped(*queryString, + [OFCharacterSet URIFragmentAllowedCharacterSet], true); + + length = query - UTF8String; + } + + *pathString = [OFString stringWithUTF8String: UTF8String + length: length]; + + OFURIVerifyIsEscaped(*pathString, + [OFCharacterSet URIQueryAllowedCharacterSet], true); +} - (instancetype)initWithString: (OFString *)string { - char *UTF8String, *UTF8String2 = NULL; - self = [super init]; @try { void *pool = objc_autoreleasePoolPush(); - char *tmp, *tmp2; - bool isIPv6Host = false; - - UTF8String = UTF8String2 = OFStrDup(string.UTF8String); - - if ((tmp = strchr(UTF8String, ':')) == NULL) - @throw [OFInvalidFormatException exception]; - - if (strncmp(tmp, "://", 3) != 0) - @throw [OFInvalidFormatException exception]; - - for (tmp2 = UTF8String; tmp2 < tmp; tmp2++) - *tmp2 = OFASCIIToLower(*tmp2); - - _URLEncodedScheme = [[OFString alloc] - initWithUTF8String: UTF8String - length: tmp - UTF8String]; - - OFURLVerifyIsEscaped(_URLEncodedScheme, - [OFCharacterSet URLSchemeAllowedCharacterSet]); - - UTF8String = tmp + 3; - - if ((tmp = strchr(UTF8String, '/')) != NULL) { - *tmp = '\0'; - tmp++; - } - - if ((tmp2 = strchr(UTF8String, '@')) != NULL) { - char *tmp3; - - *tmp2 = '\0'; - tmp2++; - - if ((tmp3 = strchr(UTF8String, ':')) != NULL) { - *tmp3 = '\0'; - tmp3++; - - _URLEncodedUser = [[OFString alloc] - initWithUTF8String: UTF8String]; - _URLEncodedPassword = [[OFString alloc] - initWithUTF8String: tmp3]; - - OFURLVerifyIsEscaped(_URLEncodedPassword, - [OFCharacterSet - URLPasswordAllowedCharacterSet]); - } else - _URLEncodedUser = [[OFString alloc] - initWithUTF8String: UTF8String]; - - OFURLVerifyIsEscaped(_URLEncodedUser, - [OFCharacterSet URLUserAllowedCharacterSet]); - - UTF8String = tmp2; - } - - if (UTF8String[0] == '[') { - tmp2 = UTF8String++; - - while (OFASCIIIsDigit(*UTF8String) || - *UTF8String == ':' || - (*UTF8String >= 'a' && *UTF8String <= 'f') || - (*UTF8String >= 'A' && *UTF8String <= 'F')) - UTF8String++; - - if (*UTF8String != ']') - @throw [OFInvalidFormatException exception]; - - UTF8String++; - - _URLEncodedHost = [[OFString alloc] - initWithUTF8String: tmp2 - length: UTF8String - tmp2]; - - if (*UTF8String == ':') { - OFString *portString; - - tmp2 = ++UTF8String; - - while (*UTF8String != '\0') { - if (!OFASCIIIsDigit(*UTF8String)) - @throw [OFInvalidFormatException - exception]; - - UTF8String++; - } - - portString = [OFString - stringWithUTF8String: tmp2 - length: UTF8String - tmp2]; - - if (portString.length == 0 || - portString.unsignedLongLongValue > 65535) - @throw [OFInvalidFormatException - exception]; - - _port = [[OFNumber alloc] initWithUnsignedShort: - portString.unsignedLongLongValue]; - } else if (*UTF8String != '\0') - @throw [OFInvalidFormatException exception]; - - isIPv6Host = true; - } else if ((tmp2 = strchr(UTF8String, ':')) != NULL) { - OFString *portString; - - *tmp2 = '\0'; - tmp2++; - - _URLEncodedHost = [[OFString alloc] - initWithUTF8String: UTF8String]; - - portString = [OFString stringWithUTF8String: tmp2]; - - if (portString.unsignedLongLongValue > 65535) - @throw [OFInvalidFormatException exception]; - - _port = [[OFNumber alloc] initWithUnsignedShort: - portString.unsignedLongLongValue]; - } else { - _URLEncodedHost = [[OFString alloc] - initWithUTF8String: UTF8String]; - - if (_URLEncodedHost.length == 0) { - [_URLEncodedHost release]; - _URLEncodedHost = nil; - } - } - - if (_URLEncodedHost != nil && !isIPv6Host) - OFURLVerifyIsEscaped(_URLEncodedHost, - [OFCharacterSet URLHostAllowedCharacterSet]); - - if ((UTF8String = tmp) != NULL) { - if ((tmp = strchr(UTF8String, '#')) != NULL) { - *tmp = '\0'; - - _URLEncodedFragment = [[OFString alloc] - initWithUTF8String: tmp + 1]; - - OFURLVerifyIsEscaped(_URLEncodedFragment, - [OFCharacterSet - URLFragmentAllowedCharacterSet]); - } - - if ((tmp = strchr(UTF8String, '?')) != NULL) { - *tmp = '\0'; - - _URLEncodedQuery = [[OFString alloc] - initWithUTF8String: tmp + 1]; - - OFURLVerifyIsEscaped(_URLEncodedQuery, - [OFCharacterSet - URLQueryAllowedCharacterSet]); - } - - /* - * Some versions of GCC issue a false-positive warning - * (turned error) about a string overflow. This is a - * false positive because UTF8String is set to tmp - * above and tmp is either NULL or points *after* the - * slash for the path. So all we do here is go back to - * that slash and restore it. - */ -#if OF_GCC_VERSION >= 402 -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wpragmas" -# pragma GCC diagnostic ignored "-Wunknown-warning-option" -# pragma GCC diagnostic ignored "-Wstringop-overflow" -#endif - UTF8String--; - *UTF8String = '/'; -#if OF_GCC_VERSION >= 402 -# pragma GCC diagnostic pop -#endif - - _URLEncodedPath = [[OFString alloc] - initWithUTF8String: UTF8String]; - - OFURLVerifyIsEscaped(_URLEncodedPath, - [OFCharacterSet URLPathAllowedCharacterSet]); - } - - objc_autoreleasePoolPop(pool); + const char *UTF8String = string.UTF8String; + size_t length = string.UTF8StringLength; + const char *colon; + OFString *path, *query = nil, *fragment = nil; + + if ((colon = strchr(UTF8String, ':')) == NULL || + colon - UTF8String < 1 || !OFASCIIIsAlpha(UTF8String[0])) + @throw [OFInvalidFormatException exception]; + + _scheme = [[[OFString stringWithUTF8String: UTF8String + length: colon - UTF8String] + lowercaseString] copy]; + + OFURIVerifyIsEscaped(_scheme, + [OFCharacterSet URISchemeAllowedCharacterSet], false); + + length -= colon - UTF8String + 1; + UTF8String = colon + 1; + + if (length >= 2 && UTF8String[0] == '/' && + UTF8String[1] == '/') { + size_t authorityLength; + + UTF8String += 2; + length -= 2; + + authorityLength = parseAuthority(self, + UTF8String, length); + + UTF8String += authorityLength; + length -= authorityLength; + + if (length > 0) + OFEnsure(UTF8String[0] == '/'); + } + + parsePathQueryFragment(UTF8String, length, + &path, &query, &fragment); + _percentEncodedPath = [path copy]; + _percentEncodedQuery = [query copy]; + _percentEncodedFragment = [fragment copy]; + + objc_autoreleasePoolPop(pool); + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +static bool +isAbsolute(OFString *string) +{ + void *pool = objc_autoreleasePoolPush(); + + @try { + const char *UTF8String = string.UTF8String; + size_t length = string.UTF8StringLength; + + if (length < 1) + return false; + + if (!OFASCIIIsAlpha(UTF8String[0])) + return false; + + for (size_t i = 1; i < length; i++) { + if (UTF8String[i] == ':') + return true; + + if (!OFASCIIIsAlnum(UTF8String[i]) && + UTF8String[i] != '+' && UTF8String[i] != '-' && + UTF8String[i] != '.') + return false; + } + } @finally { + objc_autoreleasePoolPop(pool); + } + + return false; +} + +static OFString * +merge(OFString *base, OFString *path) +{ + OFMutableArray *components; + + if (base.length == 0) + base = @"/"; + + components = [[[base componentsSeparatedByString: @"/"] + mutableCopy] autorelease]; + + if (components.count == 1) + [components addObject: path]; + else + [components replaceObjectAtIndex: components.count - 1 + withObject: path]; + + return [components componentsJoinedByString: @"/"]; +} + +- (instancetype)initWithString: (OFString *)string relativeToURI: (OFURI *)URI +{ + bool absolute; + + @try { + absolute = isAbsolute(string); } @catch (id e) { [self release]; @throw e; - } @finally { - OFFreeMemory(UTF8String2); } - return self; -} - -- (instancetype)initWithString: (OFString *)string relativeToURL: (OFURL *)URL -{ - char *UTF8String, *UTF8String2 = NULL; - - if ([string containsString: @"://"]) + if (absolute) return [self initWithString: string]; self = [super init]; @try { void *pool = objc_autoreleasePoolPush(); - char *tmp; - - _URLEncodedScheme = [URL->_URLEncodedScheme copy]; - _URLEncodedHost = [URL->_URLEncodedHost copy]; - _port = [URL->_port copy]; - _URLEncodedUser = [URL->_URLEncodedUser copy]; - _URLEncodedPassword = [URL->_URLEncodedPassword copy]; - - UTF8String = UTF8String2 = OFStrDup(string.UTF8String); - - if ((tmp = strchr(UTF8String, '#')) != NULL) { - *tmp = '\0'; - _URLEncodedFragment = [[OFString alloc] - initWithUTF8String: tmp + 1]; - - OFURLVerifyIsEscaped(_URLEncodedFragment, - [OFCharacterSet URLFragmentAllowedCharacterSet]); - } - - if ((tmp = strchr(UTF8String, '?')) != NULL) { - *tmp = '\0'; - _URLEncodedQuery = [[OFString alloc] - initWithUTF8String: tmp + 1]; - - OFURLVerifyIsEscaped(_URLEncodedQuery, - [OFCharacterSet URLQueryAllowedCharacterSet]); - } - - if (*UTF8String == '/') - _URLEncodedPath = [[OFString alloc] - initWithUTF8String: UTF8String]; - else { - OFString *relativePath = - [OFString stringWithUTF8String: UTF8String]; - - if ([URL->_URLEncodedPath hasSuffix: @"/"]) - _URLEncodedPath = [[URL->_URLEncodedPath - stringByAppendingString: relativePath] - copy]; - else { - OFMutableString *path = [OFMutableString - stringWithString: - (URL->_URLEncodedPath != nil - ? URL->_URLEncodedPath - : @"/")]; - OFRange range = [path - rangeOfString: @"/" - options: OFStringSearchBackwards]; - - if (range.location == OFNotFound) - @throw [OFInvalidFormatException - exception]; - - range.location++; - range.length = path.length - range.location; - - [path replaceCharactersInRange: range - withString: relativePath]; - [path makeImmutable]; - - _URLEncodedPath = [path copy]; - } - } - - OFURLVerifyIsEscaped(_URLEncodedPath, - [OFCharacterSet URLPathAllowedCharacterSet]); + const char *UTF8String = string.UTF8String; + size_t length = string.UTF8StringLength; + bool hasAuthority = false; + OFString *path, *query = nil, *fragment = nil; + + _scheme = [URI->_scheme copy]; + + if (length >= 2 && UTF8String[0] == '/' && + UTF8String[1] == '/') { + size_t authorityLength; + + hasAuthority = true; + + UTF8String += 2; + length -= 2; + + authorityLength = parseAuthority(self, + UTF8String, length); + + UTF8String += authorityLength; + length -= authorityLength; + + if (length > 0) + OFEnsure(UTF8String[0] == '/'); + } else { + _percentEncodedHost = [URI->_percentEncodedHost copy]; + _port = [URI->_port copy]; + _percentEncodedUser = [URI->_percentEncodedUser copy]; + _percentEncodedPassword = + [URI->_percentEncodedPassword copy]; + } + + parsePathQueryFragment(UTF8String, length, + &path, &query, &fragment); + _percentEncodedFragment = [fragment copy]; + + if (hasAuthority) { + _percentEncodedPath = [path copy]; + _percentEncodedQuery = [query copy]; + } else { + if (path.length == 0) { + _percentEncodedPath = + [URI->_percentEncodedPath copy]; + _percentEncodedQuery = (query != nil + ? [query copy] + : [URI->_percentEncodedQuery copy]); + } else { + if ([path hasPrefix: @"/"]) + _percentEncodedPath = [path copy]; + else + _percentEncodedPath = [merge( + URI->_percentEncodedPath, path) + copy]; + + _percentEncodedQuery = [query copy]; + } + } objc_autoreleasePoolPop(pool); } @catch (id e) { [self release]; @throw e; - } @finally { - OFFreeMemory(UTF8String2); } return self; } #ifdef OF_HAVE_FILES -- (instancetype)initFileURLWithPath: (OFString *)path +- (instancetype)initFileURIWithPath: (OFString *)path { bool isDirectory; @try { void *pool = objc_autoreleasePoolPush(); @@ -733,23 +791,23 @@ } @catch (id e) { [self release]; @throw e; } - self = [self initFileURLWithPath: path isDirectory: isDirectory]; + self = [self initFileURIWithPath: path isDirectory: isDirectory]; return self; } -- (instancetype)initFileURLWithPath: (OFString *)path +- (instancetype)initFileURIWithPath: (OFString *)path isDirectory: (bool)isDirectory { self = [super init]; @try { void *pool = objc_autoreleasePoolPush(); - OFString *URLEncodedHost = nil; + OFString *percentEncodedHost = nil; if (!path.absolutePath) { OFString *currentDirectoryPath = [OFFileManager defaultManager].currentDirectoryPath; @@ -756,21 +814,21 @@ path = [currentDirectoryPath stringByAppendingPathComponent: path]; path = path.stringByStandardizingPath; } - path = [path - of_pathToURLPathWithURLEncodedHost: &URLEncodedHost]; - _URLEncodedHost = [URLEncodedHost copy]; + path = [path of_pathToURIPathWithPercentEncodedHost: + &percentEncodedHost]; + _percentEncodedHost = [percentEncodedHost copy]; if (isDirectory && ![path hasSuffix: @"/"]) path = [path stringByAppendingString: @"/"]; - _URLEncodedScheme = @"file"; - _URLEncodedPath = [[path - stringByURLEncodingWithAllowedCharacters: - [OFCharacterSet URLPathAllowedCharacterSet]] copy]; + _scheme = @"file"; + _percentEncodedPath = [[path + stringByAddingPercentEncodingWithAllowedCharacters: + [OFCharacterSet URIPathAllowedCharacterSet]] copy]; objc_autoreleasePoolPop(pool); } @catch (id e) { [self release]; @throw e; @@ -777,10 +835,20 @@ } return self; } #endif + +- (instancetype)init +{ + OF_INVALID_INIT_METHOD +} + +- (instancetype)of_init +{ + return [super init]; +} - (instancetype)initWithSerialization: (OFXMLElement *)element { void *pool = objc_autoreleasePoolPush(); OFString *stringValue; @@ -803,56 +871,54 @@ return self; } - (void)dealloc { - [_URLEncodedScheme release]; - [_URLEncodedHost release]; + [_scheme release]; + [_percentEncodedHost release]; [_port release]; - [_URLEncodedUser release]; - [_URLEncodedPassword release]; - [_URLEncodedPath release]; - [_URLEncodedQuery release]; - [_URLEncodedFragment release]; + [_percentEncodedUser release]; + [_percentEncodedPassword release]; + [_percentEncodedPath release]; + [_percentEncodedQuery release]; + [_percentEncodedFragment release]; [super dealloc]; } - (bool)isEqual: (id)object { - OFURL *URL; + OFURI *URI; if (object == self) return true; - if (![object isKindOfClass: [OFURL class]]) - return false; - - URL = object; - - if (URL->_URLEncodedScheme != _URLEncodedScheme && - ![URL->_URLEncodedScheme isEqual: _URLEncodedScheme]) - return false; - if (URL->_URLEncodedHost != _URLEncodedHost && - ![URL->_URLEncodedHost isEqual: _URLEncodedHost]) - return false; - if (URL->_port != _port && ![URL->_port isEqual: _port]) - return false; - if (URL->_URLEncodedUser != _URLEncodedUser && - ![URL->_URLEncodedUser isEqual: _URLEncodedUser]) - return false; - if (URL->_URLEncodedPassword != _URLEncodedPassword && - ![URL->_URLEncodedPassword isEqual: _URLEncodedPassword]) - return false; - if (URL->_URLEncodedPath != _URLEncodedPath && - ![URL->_URLEncodedPath isEqual: _URLEncodedPath]) - return false; - if (URL->_URLEncodedQuery != _URLEncodedQuery && - ![URL->_URLEncodedQuery isEqual: _URLEncodedQuery]) - return false; - if (URL->_URLEncodedFragment != _URLEncodedFragment && - ![URL->_URLEncodedFragment isEqual: _URLEncodedFragment]) + if (![object isKindOfClass: [OFURI class]]) + return false; + + URI = object; + + if (![URI->_scheme isEqual: _scheme]) + return false; + if (URI->_percentEncodedHost != _percentEncodedHost && + ![URI->_percentEncodedHost isEqual: _percentEncodedHost]) + return false; + if (URI->_port != _port && ![URI->_port isEqual: _port]) + return false; + if (URI->_percentEncodedUser != _percentEncodedUser && + ![URI->_percentEncodedUser isEqual: _percentEncodedUser]) + return false; + if (URI->_percentEncodedPassword != _percentEncodedPassword && + ![URI->_percentEncodedPassword isEqual: _percentEncodedPassword]) + return false; + if (![URI->_percentEncodedPath isEqual: _percentEncodedPath]) + return false; + if (URI->_percentEncodedQuery != _percentEncodedQuery && + ![URI->_percentEncodedQuery isEqual: _percentEncodedQuery]) + return false; + if (URI->_percentEncodedFragment != _percentEncodedFragment && + ![URI->_percentEncodedFragment isEqual: _percentEncodedFragment]) return false; return true; } @@ -860,110 +926,105 @@ { unsigned long hash; OFHashInit(&hash); - OFHashAddHash(&hash, _URLEncodedScheme.hash); - OFHashAddHash(&hash, _URLEncodedHost.hash); + OFHashAddHash(&hash, _scheme.hash); + OFHashAddHash(&hash, _percentEncodedHost.hash); OFHashAddHash(&hash, _port.hash); - OFHashAddHash(&hash, _URLEncodedUser.hash); - OFHashAddHash(&hash, _URLEncodedPassword.hash); - OFHashAddHash(&hash, _URLEncodedPath.hash); - OFHashAddHash(&hash, _URLEncodedQuery.hash); - OFHashAddHash(&hash, _URLEncodedFragment.hash); + OFHashAddHash(&hash, _percentEncodedUser.hash); + OFHashAddHash(&hash, _percentEncodedPassword.hash); + OFHashAddHash(&hash, _percentEncodedPath.hash); + OFHashAddHash(&hash, _percentEncodedQuery.hash); + OFHashAddHash(&hash, _percentEncodedFragment.hash); OFHashFinalize(&hash); return hash; } - (OFString *)scheme { - return _URLEncodedScheme.stringByURLDecoding; -} - -- (OFString *)URLEncodedScheme -{ - return _URLEncodedScheme; + return _scheme; } - (OFString *)host { - if ([_URLEncodedHost hasPrefix: @"["] && - [_URLEncodedHost hasSuffix: @"]"]) { - OFString *host = [_URLEncodedHost substringWithRange: - OFRangeMake(1, _URLEncodedHost.length - 2)]; + if ([_percentEncodedHost hasPrefix: @"["] && + [_percentEncodedHost hasSuffix: @"]"]) { + OFString *host = [_percentEncodedHost substringWithRange: + OFMakeRange(1, _percentEncodedHost.length - 2)]; - if (!OFURLIsIPv6Host(host)) + if (!OFURIIsIPv6Host(host)) @throw [OFInvalidArgumentException exception]; return host; } - return _URLEncodedHost.stringByURLDecoding; + return _percentEncodedHost.stringByRemovingPercentEncoding; } -- (OFString *)URLEncodedHost +- (OFString *)percentEncodedHost { - return _URLEncodedHost; + return _percentEncodedHost; } - (OFNumber *)port { return _port; } - (OFString *)user { - return _URLEncodedUser.stringByURLDecoding; + return _percentEncodedUser.stringByRemovingPercentEncoding; } -- (OFString *)URLEncodedUser +- (OFString *)percentEncodedUser { - return _URLEncodedUser; + return _percentEncodedUser; } - (OFString *)password { - return _URLEncodedPassword.stringByURLDecoding; + return _percentEncodedPassword.stringByRemovingPercentEncoding; } -- (OFString *)URLEncodedPassword +- (OFString *)percentEncodedPassword { - return _URLEncodedPassword; + return _percentEncodedPassword; } - (OFString *)path { - return _URLEncodedPath.stringByURLDecoding; + return _percentEncodedPath.stringByRemovingPercentEncoding; } -- (OFString *)URLEncodedPath +- (OFString *)percentEncodedPath { - return _URLEncodedPath; + return _percentEncodedPath; } - (OFArray *)pathComponents { void *pool = objc_autoreleasePoolPush(); #ifdef OF_HAVE_FILES - bool isFile = [_URLEncodedScheme isEqual: @"file"]; + bool isFile = [_scheme isEqual: @"file"]; #endif OFMutableArray *ret; size_t count; #ifdef OF_HAVE_FILES if (isFile) { - OFString *path = [_URLEncodedPath - of_URLPathToPathWithURLEncodedHost: nil]; + OFString *path = [_percentEncodedPath + of_URIPathToPathWithPercentEncodedHost: nil]; ret = [[path.pathComponents mutableCopy] autorelease]; if (![ret.firstObject isEqual: @"/"]) [ret insertObject: @"/" atIndex: 0]; } else #endif - ret = [[[_URLEncodedPath componentsSeparatedByString: @"/"] + ret = [[[_percentEncodedPath componentsSeparatedByString: @"/"] mutableCopy] autorelease]; count = ret.count; if (count > 0 && [ret.firstObject length] == 0) @@ -973,15 +1034,15 @@ OFString *component = [ret objectAtIndex: i]; #ifdef OF_HAVE_FILES if (isFile) component = - [component of_pathComponentToURLPathComponent]; + [component of_pathComponentToURIPathComponent]; #endif - [ret replaceObjectAtIndex: i - withObject: component.stringByURLDecoding]; + component = component.stringByRemovingPercentEncoding; + [ret replaceObjectAtIndex: i withObject: component]; } [ret makeImmutable]; [ret retain]; @@ -991,20 +1052,15 @@ } - (OFString *)lastPathComponent { void *pool = objc_autoreleasePoolPush(); - OFString *path = _URLEncodedPath; + OFString *path = _percentEncodedPath; const char *UTF8String, *lastComponent; size_t length; OFString *ret; - if (path == nil) { - objc_autoreleasePoolPop(pool); - return nil; - } - if ([path isEqual: @"/"]) { objc_autoreleasePoolPop(pool); return @"/"; } @@ -1022,48 +1078,55 @@ } ret = [OFString stringWithUTF8String: lastComponent length: length - (lastComponent - UTF8String)]; - ret = [ret.stringByURLDecoding retain]; + ret = [ret.stringByRemovingPercentEncoding retain]; objc_autoreleasePoolPop(pool); return [ret autorelease]; } - (OFString *)query { - return _URLEncodedQuery.stringByURLDecoding; + return _percentEncodedQuery.stringByRemovingPercentEncoding; } -- (OFString *)URLEncodedQuery +- (OFString *)percentEncodedQuery { - return _URLEncodedQuery; + return _percentEncodedQuery; } -- (OFDictionary OF_GENERIC(OFString *, OFString *) *)queryDictionary +- (OFArray OF_GENERIC(OFPair OF_GENERIC(OFString *, OFString *) *) *)queryItems { void *pool; OFArray OF_GENERIC(OFString *) *pairs; - OFMutableDictionary OF_GENERIC(OFString *, OFString *) *ret; + OFMutableArray OF_GENERIC(OFPair OF_GENERIC(OFString *, OFString *) *) + *ret; - if (_URLEncodedQuery == nil) + if (_percentEncodedQuery == nil) return nil; pool = objc_autoreleasePoolPush(); - pairs = [_URLEncodedQuery componentsSeparatedByString: @"&"]; - ret = [OFMutableDictionary dictionaryWithCapacity: pairs.count]; + pairs = [_percentEncodedQuery componentsSeparatedByString: @"&"]; + ret = [OFMutableArray arrayWithCapacity: pairs.count]; for (OFString *pair in pairs) { OFArray *parts = [pair componentsSeparatedByString: @"="]; + OFString *name, *value; if (parts.count != 2) @throw [OFInvalidFormatException exception]; - [ret setObject: [[parts objectAtIndex: 1] stringByURLDecoding] - forKey: [[parts objectAtIndex: 0] stringByURLDecoding]]; + name = [[parts objectAtIndex: 0] + stringByRemovingPercentEncoding]; + value = [[parts objectAtIndex: 1] + stringByRemovingPercentEncoding]; + + [ret addObject: [OFPair pairWithFirstObject: name + secondObject: value]]; } [ret makeImmutable]; [ret retain]; @@ -1072,36 +1135,35 @@ return [ret autorelease]; } - (OFString *)fragment { - return _URLEncodedFragment.stringByURLDecoding; + return _percentEncodedFragment.stringByRemovingPercentEncoding; } -- (OFString *)URLEncodedFragment +- (OFString *)percentEncodedFragment { - return _URLEncodedFragment; + return _percentEncodedFragment; } - (id)copy { return [self retain]; } - (id)mutableCopy { - OFURL *copy = [[OFMutableURL alloc] init]; + OFURI *copy = [[OFMutableURI alloc] initWithScheme: _scheme]; @try { - copy->_URLEncodedScheme = [_URLEncodedScheme copy]; - copy->_URLEncodedHost = [_URLEncodedHost copy]; + copy->_percentEncodedHost = [_percentEncodedHost copy]; copy->_port = [_port copy]; - copy->_URLEncodedUser = [_URLEncodedUser copy]; - copy->_URLEncodedPassword = [_URLEncodedPassword copy]; - copy->_URLEncodedPath = [_URLEncodedPath copy]; - copy->_URLEncodedQuery = [_URLEncodedQuery copy]; - copy->_URLEncodedFragment = [_URLEncodedFragment copy]; + copy->_percentEncodedUser = [_percentEncodedUser copy]; + copy->_percentEncodedPassword = [_percentEncodedPassword copy]; + copy->_percentEncodedPath = [_percentEncodedPath copy]; + copy->_percentEncodedQuery = [_percentEncodedQuery copy]; + copy->_percentEncodedFragment = [_percentEncodedFragment copy]; } @catch (id e) { [copy release]; @throw e; } @@ -1110,35 +1172,35 @@ - (OFString *)string { OFMutableString *ret = [OFMutableString string]; - [ret appendFormat: @"%@://", _URLEncodedScheme]; + [ret appendFormat: @"%@:", _scheme]; + + if (_percentEncodedHost != nil || _port != nil || + _percentEncodedUser != nil || _percentEncodedPassword != nil) + [ret appendString: @"//"]; - if (_URLEncodedUser != nil && _URLEncodedPassword != nil) + if (_percentEncodedUser != nil && _percentEncodedPassword != nil) [ret appendFormat: @"%@:%@@", - _URLEncodedUser, _URLEncodedPassword]; - else if (_URLEncodedUser != nil) - [ret appendFormat: @"%@@", _URLEncodedUser]; + _percentEncodedUser, + _percentEncodedPassword]; + else if (_percentEncodedUser != nil) + [ret appendFormat: @"%@@", _percentEncodedUser]; - if (_URLEncodedHost != nil) - [ret appendString: _URLEncodedHost]; + if (_percentEncodedHost != nil) + [ret appendString: _percentEncodedHost]; if (_port != nil) [ret appendFormat: @":%@", _port]; - if (_URLEncodedPath != nil) { - if (![_URLEncodedPath hasPrefix: @"/"]) - @throw [OFInvalidFormatException exception]; - - [ret appendString: _URLEncodedPath]; - } - - if (_URLEncodedQuery != nil) - [ret appendFormat: @"?%@", _URLEncodedQuery]; - - if (_URLEncodedFragment != nil) - [ret appendFormat: @"#%@", _URLEncodedFragment]; + [ret appendString: _percentEncodedPath]; + + if (_percentEncodedQuery != nil) + [ret appendFormat: @"?%@", _percentEncodedQuery]; + + if (_percentEncodedFragment != nil) + [ret appendFormat: @"#%@", _percentEncodedFragment]; [ret makeImmutable]; return ret; } @@ -1147,49 +1209,50 @@ - (OFString *)fileSystemRepresentation { void *pool = objc_autoreleasePoolPush(); OFString *path; - if (![_URLEncodedScheme isEqual: @"file"]) + if (![_scheme isEqual: @"file"]) @throw [OFInvalidArgumentException exception]; - if (![_URLEncodedPath hasPrefix: @"/"]) + if (![_percentEncodedPath hasPrefix: @"/"]) @throw [OFInvalidFormatException exception]; - path = [self.path of_URLPathToPathWithURLEncodedHost: _URLEncodedHost]; + path = [self.path + of_URIPathToPathWithPercentEncodedHost: _percentEncodedHost]; [path retain]; objc_autoreleasePoolPop(pool); return [path autorelease]; } #endif -- (OFURL *)URLByAppendingPathComponent: (OFString *)component +- (OFURI *)URIByAppendingPathComponent: (OFString *)component { - OFMutableURL *URL = [[self mutableCopy] autorelease]; - [URL appendPathComponent: component]; - [URL makeImmutable]; - return URL; + OFMutableURI *URI = [[self mutableCopy] autorelease]; + [URI appendPathComponent: component]; + [URI makeImmutable]; + return URI; } -- (OFURL *)URLByAppendingPathComponent: (OFString *)component +- (OFURI *)URIByAppendingPathComponent: (OFString *)component isDirectory: (bool)isDirectory { - OFMutableURL *URL = [[self mutableCopy] autorelease]; - [URL appendPathComponent: component isDirectory: isDirectory]; - [URL makeImmutable]; - return URL; + OFMutableURI *URI = [[self mutableCopy] autorelease]; + [URI appendPathComponent: component isDirectory: isDirectory]; + [URI makeImmutable]; + return URI; } -- (OFURL *)URLByStandardizingPath +- (OFURI *)URIByStandardizingPath { - OFMutableURL *URL = [[self mutableCopy] autorelease]; - [URL standardizePath]; - [URL makeImmutable]; - return URL; + OFMutableURI *URI = [[self mutableCopy] autorelease]; + [URI standardizePath]; + [URI makeImmutable]; + return URI; } - (OFString *)description { return [OFString stringWithFormat: @"<%@: %@>",