@@ -1,7 +1,7 @@ /* - * Copyright (c) 2008-2022 Jonathan Schleifer + * Copyright (c) 2008-2024 Jonathan Schleifer * * 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 @@ -16,43 +16,45 @@ #include "config.h" #include #include -#import "OFURI.h" +#import "OFIRI.h" #import "OFArray.h" #import "OFDictionary.h" #ifdef OF_HAVE_FILES # import "OFFileManager.h" -# import "OFFileURIHandler.h" +# import "OFFileIRIHandler.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 OFURIAllowedCharacterSetBase: OFCharacterSet -@end - -@interface OFURIAllowedCharacterSet: OFURIAllowedCharacterSetBase -@end - -@interface OFURISchemeAllowedCharacterSet: OFURIAllowedCharacterSetBase -@end - -@interface OFURIPathAllowedCharacterSet: OFURIAllowedCharacterSetBase -@end - -@interface OFURIQueryOrFragmentAllowedCharacterSet: OFURIAllowedCharacterSetBase -@end - -@interface OFURIQueryKeyValueAllowedCharacterSet: OFURIAllowedCharacterSetBase +@interface OFIRIAllowedCharacterSetBase: OFCharacterSet +@end + +@interface OFIRIAllowedCharacterSet: OFIRIAllowedCharacterSetBase +@end + +@interface OFIRISchemeAllowedCharacterSet: OFIRIAllowedCharacterSetBase +@end + +@interface OFIRIPathAllowedCharacterSet: OFIRIAllowedCharacterSetBase +@end + +@interface OFIRIQueryAllowedCharacterSet: OFIRIAllowedCharacterSetBase +@end + +@interface OFIRIQueryKeyValueAllowedCharacterSet: OFIRIAllowedCharacterSetBase +@end + +@interface OFIRIFragmentAllowedCharacterSet: OFIRIAllowedCharacterSetBase @end OF_DIRECT_MEMBERS @interface OFInvertedCharacterSetWithoutPercent: OFCharacterSet { @@ -61,56 +63,62 @@ } - (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 -OFURIIsIPv6Host(OFString *host) +static OFCharacterSet *IRIAllowedCharacterSet = nil; +static OFCharacterSet *IRISchemeAllowedCharacterSet = nil; +static OFCharacterSet *IRIPathAllowedCharacterSet = nil; +static OFCharacterSet *IRIQueryAllowedCharacterSet = nil; +static OFCharacterSet *IRIQueryKeyValueAllowedCharacterSet = nil; +static OFCharacterSet *IRIFragmentAllowedCharacterSet = nil; + +static OFOnceControl IRIAllowedCharacterSetOnce = OFOnceControlInitValue; + +static void +initIRIAllowedCharacterSet(void) +{ + IRIAllowedCharacterSet = [[OFIRIAllowedCharacterSet alloc] init]; +} + +static void +initIRISchemeAllowedCharacterSet(void) +{ + IRISchemeAllowedCharacterSet = + [[OFIRISchemeAllowedCharacterSet alloc] init]; +} + +static void +initIRIPathAllowedCharacterSet(void) +{ + IRIPathAllowedCharacterSet = + [[OFIRIPathAllowedCharacterSet alloc] init]; +} + +static void +initIRIQueryAllowedCharacterSet(void) +{ + IRIQueryAllowedCharacterSet = + [[OFIRIQueryAllowedCharacterSet alloc] init]; +} + +static void +initIRIQueryKeyValueAllowedCharacterSet(void) +{ + IRIQueryKeyValueAllowedCharacterSet = + [[OFIRIQueryKeyValueAllowedCharacterSet alloc] init]; +} + +static void +initIRIFragmentAllowedCharacterSet(void) +{ + IRIFragmentAllowedCharacterSet = + [[OFIRIFragmentAllowedCharacterSet alloc] init]; +} + +bool +OFIRIIsIPv6Host(OFString *host) { const char *UTF8String = host.UTF8String; bool hasColon = false; while (*UTF8String != '\0') { @@ -126,36 +134,76 @@ } return hasColon; } -@implementation OFURIAllowedCharacterSetBase -- (instancetype)autorelease -{ - return self; -} - -- (instancetype)retain -{ - return self; -} - -- (void)release -{ -} - -- (unsigned int)retainCount -{ - return OFMaxRetainCount; -} +static bool +isUnicode(OFUnichar character) +{ + if (character >= 0xA0 && character <= 0xD7FF) + return true; + if (character >= 0xF900 && character <= 0xFDCF) + return true; + if (character >= 0xFDF0 && character <= 0xFFEF) + return true; + if (character >= 0x10000 && character <= 0x1FFFD) + return true; + if (character >= 0x20000 && character <= 0x2FFFD) + return true; + if (character >= 0x30000 && character <= 0x3FFFD) + return true; + if (character >= 0x40000 && character <= 0x4FFFD) + return true; + if (character >= 0x50000 && character <= 0x5FFFD) + return true; + if (character >= 0x60000 && character <= 0x6FFFD) + return true; + if (character >= 0x70000 && character <= 0x7FFFD) + return true; + if (character >= 0x80000 && character <= 0x8FFFD) + return true; + if (character >= 0x90000 && character <= 0x9FFFD) + return true; + if (character >= 0xA0000 && character <= 0xAFFFD) + return true; + if (character >= 0xB0000 && character <= 0xBFFFD) + return true; + if (character >= 0xC0000 && character <= 0xCFFFD) + return true; + if (character >= 0xD0000 && character <= 0xDFFFD) + return true; + if (character >= 0xE0000 && character <= 0xEFFFD) + return true; + + return false; +} + +static bool +isUnicodePrivate(OFUnichar character) +{ + if (character >= 0xE00 && character <= 0xF8FF) + return true; + if (character >= 0xF0000 && character <= 0xFFFFD) + return true; + if (character >= 0x100000 && character <= 0x10FFFD) + return true; + + return false; +} + +@implementation OFIRIAllowedCharacterSetBase +OF_SINGLETON_METHODS @end -@implementation OFURIAllowedCharacterSet +@implementation OFIRIAllowedCharacterSet - (bool)characterIsMember: (OFUnichar)character { if (character < CHAR_MAX && OFASCIIIsAlnum(character)) return true; + + if (isUnicode(character)) + return true; switch (character) { case '-': case '.': case '_': @@ -176,11 +224,11 @@ return false; } } @end -@implementation OFURISchemeAllowedCharacterSet +@implementation OFIRISchemeAllowedCharacterSet - (bool)characterIsMember: (OFUnichar)character { if (character < CHAR_MAX && OFASCIIIsAlnum(character)) return true; @@ -193,15 +241,18 @@ return false; } } @end -@implementation OFURIPathAllowedCharacterSet +@implementation OFIRIPathAllowedCharacterSet - (bool)characterIsMember: (OFUnichar)character { if (character < CHAR_MAX && OFASCIIIsAlnum(character)) return true; + + if (isUnicode(character)) + return true; switch (character) { case '-': case '.': case '_': @@ -225,15 +276,18 @@ return false; } } @end -@implementation OFURIQueryOrFragmentAllowedCharacterSet +@implementation OFIRIQueryAllowedCharacterSet - (bool)characterIsMember: (OFUnichar)character { if (character < CHAR_MAX && OFASCIIIsAlnum(character)) return true; + + if (isUnicode(character) || isUnicodePrivate(character)) + return true; switch (character) { case '-': case '.': case '_': @@ -258,30 +312,69 @@ return false; } } @end -@implementation OFURIQueryKeyValueAllowedCharacterSet +@implementation OFIRIQueryKeyValueAllowedCharacterSet +- (bool)characterIsMember: (OFUnichar)character +{ + if (character < CHAR_MAX && OFASCIIIsAlnum(character)) + return true; + + if (isUnicode(character) || isUnicodePrivate(character)) + return true; + + switch (character) { + case '-': + case '.': + case '_': + case '~': + case '!': + case '$': + case '\'': + case '(': + case ')': + case '*': + case '+': + case ',': + case ';': + case ':': + case '@': + case '/': + case '?': + return true; + default: + return false; + } +} +@end + +@implementation OFIRIFragmentAllowedCharacterSet - (bool)characterIsMember: (OFUnichar)character { if (character < CHAR_MAX && OFASCIIIsAlnum(character)) return true; + + if (isUnicode(character)) + return true; switch (character) { case '-': case '.': case '_': case '~': case '!': case '$': + case '&': case '\'': case '(': case ')': case '*': case '+': case ',': case ';': + case '=': case ':': case '@': case '/': case '?': return true; @@ -322,11 +415,11 @@ @selector(characterIsMember:), character)); } @end void -OFURIVerifyIsEscaped(OFString *string, OFCharacterSet *characterSet, +OFIRIVerifyIsEscaped(OFString *string, OFCharacterSet *characterSet, bool allowPercent) { void *pool = objc_autoreleasePoolPush(); if (allowPercent) @@ -339,107 +432,105 @@ @throw [OFInvalidFormatException exception]; objc_autoreleasePoolPop(pool); } -@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; +@implementation OFCharacterSet (IRICharacterSets) ++ (OFCharacterSet *)IRISchemeAllowedCharacterSet +{ + static OFOnceControl onceControl = OFOnceControlInitValue; + OFOnce(&onceControl, initIRISchemeAllowedCharacterSet); + + return IRISchemeAllowedCharacterSet; +} + ++ (OFCharacterSet *)IRIHostAllowedCharacterSet +{ + OFOnce(&IRIAllowedCharacterSetOnce, initIRIAllowedCharacterSet); + + return IRIAllowedCharacterSet; +} + ++ (OFCharacterSet *)IRIUserAllowedCharacterSet +{ + OFOnce(&IRIAllowedCharacterSetOnce, initIRIAllowedCharacterSet); + + return IRIAllowedCharacterSet; +} + ++ (OFCharacterSet *)IRIPasswordAllowedCharacterSet +{ + OFOnce(&IRIAllowedCharacterSetOnce, initIRIAllowedCharacterSet); + + return IRIAllowedCharacterSet; +} + ++ (OFCharacterSet *)IRIPathAllowedCharacterSet +{ + static OFOnceControl onceControl = OFOnceControlInitValue; + OFOnce(&onceControl, initIRIPathAllowedCharacterSet); + + return IRIPathAllowedCharacterSet; +} + ++ (OFCharacterSet *)IRIQueryAllowedCharacterSet +{ + static OFOnceControl onceControl = OFOnceControlInitValue; + OFOnce(&onceControl, initIRIQueryAllowedCharacterSet); + + return IRIQueryAllowedCharacterSet; +} + ++ (OFCharacterSet *)IRIQueryKeyValueAllowedCharacterSet +{ + static OFOnceControl onceControl = OFOnceControlInitValue; + OFOnce(&onceControl, initIRIQueryKeyValueAllowedCharacterSet); + + return IRIQueryKeyValueAllowedCharacterSet; +} + ++ (OFCharacterSet *)IRIFragmentAllowedCharacterSet +{ + static OFOnceControl onceControl = OFOnceControlInitValue; + OFOnce(&onceControl, initIRIFragmentAllowedCharacterSet); + + return IRIFragmentAllowedCharacterSet; } @end -@implementation OFURI -+ (instancetype)URI +@implementation OFIRI ++ (instancetype)IRI { return [[[self alloc] init] autorelease]; } -+ (instancetype)URIWithString: (OFString *)string ++ (instancetype)IRIWithString: (OFString *)string { return [[[self alloc] initWithString: string] autorelease]; } -+ (instancetype)URIWithString: (OFString *)string - relativeToURI: (OFURI *)URI ++ (instancetype)IRIWithString: (OFString *)string relativeToIRI: (OFIRI *)IRI { return [[[self alloc] initWithString: string - relativeToURI: URI] autorelease]; + relativeToIRI: IRI] autorelease]; } #ifdef OF_HAVE_FILES -+ (instancetype)fileURIWithPath: (OFString *)path ++ (instancetype)fileIRIWithPath: (OFString *)path { - return [[[self alloc] initFileURIWithPath: path] autorelease]; + return [[[self alloc] initFileIRIWithPath: path] autorelease]; } -+ (instancetype)fileURIWithPath: (OFString *)path - isDirectory: (bool)isDirectory ++ (instancetype)fileIRIWithPath: (OFString *)path isDirectory: (bool)isDirectory { - return [[[self alloc] initFileURIWithPath: path + return [[[self alloc] initFileIRIWithPath: path isDirectory: isDirectory] autorelease]; } #endif static void -parseUserInfo(OFURI *self, const char *UTF8String, size_t length) +parseUserInfo(OFIRI *self, const char *UTF8String, size_t length) { const char *colon; if ((colon = memchr(UTF8String, ':', length)) != NULL) { self->_percentEncodedUser = [[OFString alloc] @@ -447,23 +538,23 @@ length: colon - UTF8String]; self->_percentEncodedPassword = [[OFString alloc] initWithUTF8String: colon + 1 length: length - (colon - UTF8String) - 1]; - OFURIVerifyIsEscaped(self->_percentEncodedPassword, - [OFCharacterSet URIPasswordAllowedCharacterSet], true); + OFIRIVerifyIsEscaped(self->_percentEncodedPassword, + [OFCharacterSet IRIPasswordAllowedCharacterSet], true); } else self->_percentEncodedUser = [[OFString alloc] initWithUTF8String: UTF8String length: length]; - OFURIVerifyIsEscaped(self->_percentEncodedUser, - [OFCharacterSet URIUserAllowedCharacterSet], true); + OFIRIVerifyIsEscaped(self->_percentEncodedUser, + [OFCharacterSet IRIUserAllowedCharacterSet], true); } static void -parseHostPort(OFURI *self, const char *UTF8String, size_t length) +parseHostPort(OFIRI *self, const char *UTF8String, size_t length) { OFString *portString; if (*UTF8String == '[') { const char *end = memchr(UTF8String, ']', length); @@ -500,12 +591,12 @@ UTF8String += length; length = 0; } - OFURIVerifyIsEscaped(self->_percentEncodedHost, - [OFCharacterSet URIHostAllowedCharacterSet], true); + OFIRIVerifyIsEscaped(self->_percentEncodedHost, + [OFCharacterSet IRIHostAllowedCharacterSet], true); } if (length == 0) return; @@ -527,11 +618,11 @@ self->_port = [[OFNumber alloc] initWithUnsignedShort: (unsigned short)portString.unsignedLongLongValue]; } static size_t -parseAuthority(OFURI *self, const char *UTF8String, size_t length) +parseAuthority(OFIRI *self, const char *UTF8String, size_t length) { size_t ret; const char *slash, *at; if ((slash = memchr(UTF8String, '/', length)) != NULL) @@ -560,32 +651,32 @@ if ((fragment = memchr(UTF8String, '#', length)) != NULL) { *fragmentString = [OFString stringWithUTF8String: fragment + 1 length: length - (fragment - UTF8String) - 1]; - OFURIVerifyIsEscaped(*fragmentString, - [OFCharacterSet URIQueryAllowedCharacterSet], true); + OFIRIVerifyIsEscaped(*fragmentString, + [OFCharacterSet IRIQueryAllowedCharacterSet], 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); + OFIRIVerifyIsEscaped(*queryString, + [OFCharacterSet IRIFragmentAllowedCharacterSet], true); length = query - UTF8String; } *pathString = [OFString stringWithUTF8String: UTF8String length: length]; - OFURIVerifyIsEscaped(*pathString, - [OFCharacterSet URIQueryAllowedCharacterSet], true); + OFIRIVerifyIsEscaped(*pathString, + [OFCharacterSet IRIPathAllowedCharacterSet], true); } - (instancetype)initWithString: (OFString *)string { self = [super init]; @@ -603,12 +694,12 @@ _scheme = [[[OFString stringWithUTF8String: UTF8String length: colon - UTF8String] lowercaseString] copy]; - OFURIVerifyIsEscaped(_scheme, - [OFCharacterSet URISchemeAllowedCharacterSet], false); + OFIRIVerifyIsEscaped(_scheme, + [OFCharacterSet IRISchemeAllowedCharacterSet], false); length -= colon - UTF8String + 1; UTF8String = colon + 1; if (length >= 2 && UTF8String[0] == '/' && @@ -692,11 +783,11 @@ withObject: path]; return [components componentsJoinedByString: @"/"]; } -- (instancetype)initWithString: (OFString *)string relativeToURI: (OFURI *)URI +- (instancetype)initWithString: (OFString *)string relativeToIRI: (OFIRI *)IRI { bool absolute; @try { absolute = isAbsolute(string); @@ -715,11 +806,11 @@ const char *UTF8String = string.UTF8String; size_t length = string.UTF8StringLength; bool hasAuthority = false; OFString *path, *query = nil, *fragment = nil; - _scheme = [URI->_scheme copy]; + _scheme = [IRI->_scheme copy]; if (length >= 2 && UTF8String[0] == '/' && UTF8String[1] == '/') { size_t authorityLength; @@ -735,15 +826,15 @@ length -= authorityLength; if (length > 0) OFEnsure(UTF8String[0] == '/'); } else { - _percentEncodedHost = [URI->_percentEncodedHost copy]; - _port = [URI->_port copy]; - _percentEncodedUser = [URI->_percentEncodedUser copy]; + _percentEncodedHost = [IRI->_percentEncodedHost copy]; + _port = [IRI->_port copy]; + _percentEncodedUser = [IRI->_percentEncodedUser copy]; _percentEncodedPassword = - [URI->_percentEncodedPassword copy]; + [IRI->_percentEncodedPassword copy]; } parsePathQueryFragment(UTF8String, length, &path, &query, &fragment); _percentEncodedFragment = [fragment copy]; @@ -752,20 +843,20 @@ _percentEncodedPath = [path copy]; _percentEncodedQuery = [query copy]; } else { if (path.length == 0) { _percentEncodedPath = - [URI->_percentEncodedPath copy]; + [IRI->_percentEncodedPath copy]; _percentEncodedQuery = (query != nil ? [query copy] - : [URI->_percentEncodedQuery copy]); + : [IRI->_percentEncodedQuery copy]); } else { if ([path hasPrefix: @"/"]) _percentEncodedPath = [path copy]; else _percentEncodedPath = [merge( - URI->_percentEncodedPath, path) + IRI->_percentEncodedPath, path) copy]; _percentEncodedQuery = [query copy]; } } @@ -778,11 +869,11 @@ return self; } #ifdef OF_HAVE_FILES -- (instancetype)initFileURIWithPath: (OFString *)path +- (instancetype)initFileIRIWithPath: (OFString *)path { bool isDirectory; @try { void *pool = objc_autoreleasePoolPush(); @@ -791,16 +882,16 @@ } @catch (id e) { [self release]; @throw e; } - self = [self initFileURIWithPath: path isDirectory: isDirectory]; + self = [self initFileIRIWithPath: path isDirectory: isDirectory]; return self; } -- (instancetype)initFileURIWithPath: (OFString *)path +- (instancetype)initFileIRIWithPath: (OFString *)path isDirectory: (bool)isDirectory { self = [super init]; @try { @@ -814,21 +905,21 @@ path = [currentDirectoryPath stringByAppendingPathComponent: path]; path = path.stringByStandardizingPath; } - path = [path of_pathToURIPathWithPercentEncodedHost: + path = [path of_pathToIRIPathWithPercentEncodedHost: &percentEncodedHost]; _percentEncodedHost = [percentEncodedHost copy]; if (isDirectory && ![path hasSuffix: @"/"]) path = [path stringByAppendingString: @"/"]; _scheme = @"file"; _percentEncodedPath = [[path stringByAddingPercentEncodingWithAllowedCharacters: - [OFCharacterSet URIPathAllowedCharacterSet]] copy]; + [OFCharacterSet IRIPathAllowedCharacterSet]] copy]; objc_autoreleasePoolPop(pool); } @catch (id e) { [self release]; @throw e; @@ -846,33 +937,10 @@ - (instancetype)of_init { return [super init]; } -- (instancetype)initWithSerialization: (OFXMLElement *)element -{ - void *pool = objc_autoreleasePoolPush(); - OFString *stringValue; - - @try { - if (![element.name isEqual: self.className] || - ![element.namespace isEqual: OFSerializationNS]) - @throw [OFInvalidArgumentException exception]; - - stringValue = element.stringValue; - } @catch (id e) { - [self release]; - @throw e; - } - - self = [self initWithString: stringValue]; - - objc_autoreleasePoolPop(pool); - - return self; -} - - (void)dealloc { [_scheme release]; [_percentEncodedHost release]; [_port release]; @@ -885,40 +953,40 @@ [super dealloc]; } - (bool)isEqual: (id)object { - OFURI *URI; + OFIRI *IRI; if (object == self) return true; - 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]) + if (![object isKindOfClass: [OFIRI class]]) + return false; + + IRI = object; + + if (![IRI->_scheme isEqual: _scheme]) + return false; + if (IRI->_percentEncodedHost != _percentEncodedHost && + ![IRI->_percentEncodedHost isEqual: _percentEncodedHost]) + return false; + if (IRI->_port != _port && ![IRI->_port isEqual: _port]) + return false; + if (IRI->_percentEncodedUser != _percentEncodedUser && + ![IRI->_percentEncodedUser isEqual: _percentEncodedUser]) + return false; + if (IRI->_percentEncodedPassword != _percentEncodedPassword && + ![IRI->_percentEncodedPassword isEqual: _percentEncodedPassword]) + return false; + if (![IRI->_percentEncodedPath isEqual: _percentEncodedPath]) + return false; + if (IRI->_percentEncodedQuery != _percentEncodedQuery && + ![IRI->_percentEncodedQuery isEqual: _percentEncodedQuery]) + return false; + if (IRI->_percentEncodedFragment != _percentEncodedFragment && + ![IRI->_percentEncodedFragment isEqual: _percentEncodedFragment]) return false; return true; } @@ -952,11 +1020,11 @@ if ([_percentEncodedHost hasPrefix: @"["] && [_percentEncodedHost hasSuffix: @"]"]) { OFString *host = [_percentEncodedHost substringWithRange: OFMakeRange(1, _percentEncodedHost.length - 2)]; - if (!OFURIIsIPv6Host(host)) + if (!OFIRIIsIPv6Host(host)) @throw [OFInvalidArgumentException exception]; return host; } @@ -1013,11 +1081,11 @@ size_t count; #ifdef OF_HAVE_FILES if (isFile) { OFString *path = [_percentEncodedPath - of_URIPathToPathWithPercentEncodedHost: nil]; + of_IRIPathToPathWithPercentEncodedHost: nil]; ret = [[path.pathComponents mutableCopy] autorelease]; if (![ret.firstObject isEqual: @"/"]) [ret insertObject: @"/" atIndex: 0]; } else @@ -1034,11 +1102,11 @@ OFString *component = [ret objectAtIndex: i]; #ifdef OF_HAVE_FILES if (isFile) component = - [component of_pathComponentToURIPathComponent]; + [component of_pathComponentToIRIPathComponent]; #endif component = component.stringByRemovingPercentEncoding; [ret replaceObjectAtIndex: i withObject: component]; } @@ -1150,11 +1218,11 @@ return [self retain]; } - (id)mutableCopy { - OFURI *copy = [[OFMutableURI alloc] initWithScheme: _scheme]; + OFIRI *copy = [[OFMutableIRI alloc] initWithScheme: _scheme]; @try { copy->_percentEncodedHost = [_percentEncodedHost copy]; copy->_port = [_port copy]; copy->_percentEncodedUser = [_percentEncodedUser copy]; @@ -1216,62 +1284,73 @@ if (![_percentEncodedPath hasPrefix: @"/"]) @throw [OFInvalidFormatException exception]; path = [self.path - of_URIPathToPathWithPercentEncodedHost: _percentEncodedHost]; + of_IRIPathToPathWithPercentEncodedHost: _percentEncodedHost]; [path retain]; objc_autoreleasePoolPop(pool); return [path autorelease]; } #endif -- (OFURI *)URIByAppendingPathComponent: (OFString *)component +- (OFIRI *)IRIByAppendingPathComponent: (OFString *)component { - OFMutableURI *URI = [[self mutableCopy] autorelease]; - [URI appendPathComponent: component]; - [URI makeImmutable]; - return URI; + OFMutableIRI *IRI = [[self mutableCopy] autorelease]; + [IRI appendPathComponent: component]; + [IRI makeImmutable]; + return IRI; } -- (OFURI *)URIByAppendingPathComponent: (OFString *)component +- (OFIRI *)IRIByAppendingPathComponent: (OFString *)component isDirectory: (bool)isDirectory { - OFMutableURI *URI = [[self mutableCopy] autorelease]; - [URI appendPathComponent: component isDirectory: isDirectory]; - [URI makeImmutable]; - return URI; + OFMutableIRI *IRI = [[self mutableCopy] autorelease]; + [IRI appendPathComponent: component isDirectory: isDirectory]; + [IRI makeImmutable]; + return IRI; +} + +- (OFIRI *)IRIByStandardizingPath +{ + OFMutableIRI *IRI = [[self mutableCopy] autorelease]; + [IRI standardizePath]; + [IRI makeImmutable]; + return IRI; } -- (OFURI *)URIByStandardizingPath +- (OFIRI *)IRIByAddingPercentEncodingForUnicodeCharacters { - OFMutableURI *URI = [[self mutableCopy] autorelease]; - [URI standardizePath]; - [URI makeImmutable]; - return URI; + OFMutableIRI *IRI = [[self mutableCopy] autorelease]; + void *pool = objc_autoreleasePoolPush(); + OFCharacterSet *ASCII = + [OFCharacterSet characterSetWithRange: OFMakeRange(0, 0x80)]; + + IRI.percentEncodedHost = [_percentEncodedHost + stringByAddingPercentEncodingWithAllowedCharacters: ASCII]; + IRI.percentEncodedUser = [_percentEncodedUser + stringByAddingPercentEncodingWithAllowedCharacters: ASCII]; + IRI.percentEncodedPassword = [_percentEncodedPassword + stringByAddingPercentEncodingWithAllowedCharacters: ASCII]; + IRI.percentEncodedPath = [_percentEncodedPath + stringByAddingPercentEncodingWithAllowedCharacters: ASCII]; + IRI.percentEncodedQuery = [_percentEncodedQuery + stringByAddingPercentEncodingWithAllowedCharacters: ASCII]; + IRI.percentEncodedFragment = [_percentEncodedFragment + stringByAddingPercentEncodingWithAllowedCharacters: ASCII]; + + [IRI makeImmutable]; + + objc_autoreleasePoolPop(pool); + + return IRI; } - (OFString *)description { return [OFString stringWithFormat: @"<%@: %@>", self.class, self.string]; } - -- (OFXMLElement *)XMLElementBySerializing -{ - void *pool = objc_autoreleasePoolPush(); - OFXMLElement *element; - - element = [OFXMLElement elementWithName: self.className - namespace: OFSerializationNS - stringValue: self.string]; - - [element retain]; - - objc_autoreleasePoolPop(pool); - - return [element autorelease]; -} @end