Index: src/OFMutableURI.h ================================================================== --- src/OFMutableURI.h +++ src/OFMutableURI.h @@ -28,24 +28,15 @@ OF_RESERVE_IVARS(OFMutableURI, 4) } /** * @brief The scheme part of the URI. - */ -@property OF_NULLABLE_PROPERTY (readwrite, copy, nonatomic) OFString *scheme; - -/** - * @brief The scheme part of the URI in percent-encoded form. - * - * Setting this retains the original percent-encoding used - if more characters - * than necessary are percent-encoded, it is kept this way. * * @throw OFInvalidFormatException The scheme being set is not in the correct * format */ -@property OF_NULLABLE_PROPERTY (readwrite, copy, nonatomic) - OFString *percentEncodedScheme; +@property OF_NULLABLE_PROPERTY (readwrite, copy, nonatomic) OFString *scheme; /** * @brief The host part of the URI. */ @property OF_NULLABLE_PROPERTY (readwrite, copy, nonatomic) OFString *host; @@ -62,10 +53,13 @@ @property OF_NULLABLE_PROPERTY (readwrite, copy, nonatomic) OFString *percentEncodedHost; /** * @brief The port part of the URI. + * + * @throw OFInvalidArgumentException The port is not valid (e.g. negative or + * too big) */ @property OF_NULLABLE_PROPERTY (readwrite, copy, nonatomic) OFNumber *port; /** * @brief The user part of the URI. Index: src/OFMutableURI.m ================================================================== --- src/OFMutableURI.m +++ src/OFMutableURI.m @@ -23,54 +23,36 @@ #endif #import "OFNumber.h" #import "OFPair.h" #import "OFString.h" +#import "OFInvalidArgumentException.h" #import "OFInvalidFormatException.h" @implementation OFMutableURI -@dynamic scheme, percentEncodedScheme, host, percentEncodedHost, port, user; -@dynamic percentEncodedUser, password, percentEncodedPassword, path; -@dynamic percentEncodedPath, pathComponents, query, percentEncodedQuery; -@dynamic queryItems, fragment, percentEncodedFragment; +@dynamic scheme, host, percentEncodedHost, port, user, percentEncodedUser; +@dynamic password, percentEncodedPassword, path, percentEncodedPath; +@dynamic pathComponents, query, percentEncodedQuery, queryItems, fragment; +@dynamic percentEncodedFragment; + (instancetype)URI { return [[[self alloc] init] autorelease]; } - (void)setScheme: (OFString *)scheme { void *pool = objc_autoreleasePoolPush(); - OFString *old = _percentEncodedScheme; + OFString *old = _scheme; if (scheme.length < 1 || !OFASCIIIsAlpha(*scheme.UTF8String)) @throw [OFInvalidFormatException exception]; - _percentEncodedScheme = [[scheme.lowercaseString - stringByAddingPercentEncodingWithAllowedCharacters: - [OFCharacterSet URISchemeAllowedCharacterSet]] copy]; - - [old release]; - - objc_autoreleasePoolPop(pool); -} - -- (void)setPercentEncodedScheme: (OFString *)percentEncodedScheme -{ - void *pool = objc_autoreleasePoolPush(); - OFString *old = _percentEncodedScheme; - - if (percentEncodedScheme.length < 1 || - !OFASCIIIsAlpha(*percentEncodedScheme.UTF8String)) - @throw [OFInvalidFormatException exception]; - - if (percentEncodedScheme != nil) - OFURIVerifyIsEscaped(percentEncodedScheme, - [OFCharacterSet URISchemeAllowedCharacterSet]); - - _percentEncodedScheme = [percentEncodedScheme.lowercaseString copy]; + OFURIVerifyIsEscaped(scheme, + [OFCharacterSet URISchemeAllowedCharacterSet], false); + + _scheme = [scheme.lowercaseString copy]; [old release]; objc_autoreleasePoolPop(pool); } @@ -102,20 +84,24 @@ if (!OFURIIsIPv6Host([percentEncodedHost substringWithRange: OFMakeRange(1, percentEncodedHost.length - 2)])) @throw [OFInvalidFormatException exception]; } else if (percentEncodedHost != nil) OFURIVerifyIsEscaped(percentEncodedHost, - [OFCharacterSet URIHostAllowedCharacterSet]); + [OFCharacterSet URIHostAllowedCharacterSet], true); old = _percentEncodedHost; _percentEncodedHost = [percentEncodedHost copy]; [old release]; } - (void)setPort: (OFNumber *)port { OFNumber *old = _port; + + if (port.longLongValue < 0 || port.longLongValue > 65535) + @throw [OFInvalidArgumentException exception]; + _port = [port copy]; [old release]; } - (void)setUser: (OFString *)user @@ -136,11 +122,11 @@ { OFString *old; if (percentEncodedUser != nil) OFURIVerifyIsEscaped(percentEncodedUser, - [OFCharacterSet URIUserAllowedCharacterSet]); + [OFCharacterSet URIUserAllowedCharacterSet], true); old = _percentEncodedUser; _percentEncodedUser = [percentEncodedUser copy]; [old release]; } @@ -163,11 +149,11 @@ { OFString *old; if (percentEncodedPassword != nil) OFURIVerifyIsEscaped(percentEncodedPassword, - [OFCharacterSet URIPasswordAllowedCharacterSet]); + [OFCharacterSet URIPasswordAllowedCharacterSet], true); old = _percentEncodedPassword; _percentEncodedPassword = [percentEncodedPassword copy]; [old release]; } @@ -190,11 +176,11 @@ { OFString *old; if (percentEncodedPath != nil) OFURIVerifyIsEscaped(percentEncodedPath, - [OFCharacterSet URIPathAllowedCharacterSet]); + [OFCharacterSet URIPathAllowedCharacterSet], true); old = _percentEncodedPath; _percentEncodedPath = [percentEncodedPath copy]; [old release]; } @@ -237,11 +223,11 @@ { OFString *old; if (percentEncodedQuery != nil) OFURIVerifyIsEscaped(percentEncodedQuery, - [OFCharacterSet URIQueryAllowedCharacterSet]); + [OFCharacterSet URIQueryAllowedCharacterSet], true); old = _percentEncodedQuery; _percentEncodedQuery = [percentEncodedQuery copy]; [old release]; } @@ -304,11 +290,11 @@ { OFString *old; if (percentEncodedFragment != nil) OFURIVerifyIsEscaped(percentEncodedFragment, - [OFCharacterSet URIFragmentAllowedCharacterSet]); + [OFCharacterSet URIFragmentAllowedCharacterSet], true); old = _percentEncodedFragment; _percentEncodedFragment = [percentEncodedFragment copy]; [old release]; } @@ -325,11 +311,11 @@ - (void)appendPathComponent: (OFString *)component { [self appendPathComponent: component isDirectory: false]; #ifdef OF_HAVE_FILES - if ([_percentEncodedScheme isEqual: @"file"] && + if ([_scheme isEqual: @"file"] && ![_percentEncodedPath hasSuffix: @"/"] && [[OFFileManager defaultManager] directoryExistsAtURI: self]) { void *pool = objc_autoreleasePoolPush(); OFString *path = [_percentEncodedPath stringByAppendingString: @"/"]; @@ -356,11 +342,11 @@ stringByAddingPercentEncodingWithAllowedCharacters: [OFCharacterSet URIPathAllowedCharacterSet]]; #if defined(OF_WINDOWS) || defined(OF_MSDOS) if ([_percentEncodedPath hasSuffix: @"/"] || - ([_percentEncodedScheme isEqual: @"file"] && + ([_scheme isEqual: @"file"] && [_percentEncodedPath hasSuffix: @":"])) #else if ([_percentEncodedPath hasSuffix: @"/"]) #endif path = [_percentEncodedPath stringByAppendingString: component]; Index: src/OFURI.h ================================================================== --- src/OFURI.h +++ src/OFURI.h @@ -30,11 +30,11 @@ * * @brief A class for parsing URIs as per RFC 3986 and accessing parts of it. */ @interface OFURI: OFObject { - OFString *_Nullable _percentEncodedScheme; + OFString *_Nullable _scheme; OFString *_Nullable _percentEncodedHost; OFNumber *_Nullable _port; OFString *_Nullable _percentEncodedUser; OFString *_Nullable _percentEncodedPassword; OFString *_Nullable _percentEncodedPath; @@ -46,16 +46,10 @@ /** * @brief The scheme part of the URI. */ @property OF_NULLABLE_PROPERTY (readonly, copy, nonatomic) OFString *scheme; -/** - * @brief The scheme part of the URI in percent-encoded form. - */ -@property OF_NULLABLE_PROPERTY (readonly, copy, nonatomic) - OFString *percentEncodedScheme; - /** * @brief The host part of the URI. */ @property OF_NULLABLE_PROPERTY (readonly, copy, nonatomic) OFString *host; @@ -379,13 +373,13 @@ #ifdef __cplusplus extern "C" { #endif extern bool OFURIIsIPv6Host(OFString *host); -extern void OFURIVerifyIsEscaped(OFString *, OFCharacterSet *); +extern void OFURIVerifyIsEscaped(OFString *, OFCharacterSet *, bool); #ifdef __cplusplus } #endif OF_ASSUME_NONNULL_END #import "OFMutableURI.h" Index: src/OFURI.m ================================================================== --- src/OFURI.m +++ src/OFURI.m @@ -322,16 +322,20 @@ @selector(characterIsMember:), character)); } @end void -OFURIVerifyIsEscaped(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); @@ -444,18 +448,18 @@ self->_percentEncodedPassword = [[OFString alloc] initWithUTF8String: colon + 1 length: length - (colon - UTF8String) - 1]; OFURIVerifyIsEscaped(self->_percentEncodedPassword, - [OFCharacterSet URIPasswordAllowedCharacterSet]); + [OFCharacterSet URIPasswordAllowedCharacterSet], true); } else self->_percentEncodedUser = [[OFString alloc] initWithUTF8String: UTF8String length: length]; OFURIVerifyIsEscaped(self->_percentEncodedUser, - [OFCharacterSet URIUserAllowedCharacterSet]); + [OFCharacterSet URIUserAllowedCharacterSet], true); } static void parseHostPort(OFURI *self, const char *UTF8String, size_t length) { @@ -497,11 +501,11 @@ UTF8String += length; length = 0; } OFURIVerifyIsEscaped(self->_percentEncodedHost, - [OFCharacterSet URIHostAllowedCharacterSet]); + [OFCharacterSet URIHostAllowedCharacterSet], true); } if (length == 0) return; @@ -557,11 +561,11 @@ *fragmentString = [OFString stringWithUTF8String: fragment + 1 length: length - (fragment - UTF8String) - 1]; OFURIVerifyIsEscaped(*fragmentString, - [OFCharacterSet URIQueryAllowedCharacterSet]); + [OFCharacterSet URIQueryAllowedCharacterSet], true); length = fragment - UTF8String; } if ((query = memchr(UTF8String, '?', length)) != NULL) { @@ -568,20 +572,20 @@ *queryString = [OFString stringWithUTF8String: query + 1 length: length - (query - UTF8String) - 1]; OFURIVerifyIsEscaped(*queryString, - [OFCharacterSet URIFragmentAllowedCharacterSet]); + [OFCharacterSet URIFragmentAllowedCharacterSet], true); length = query - UTF8String; } *pathString = [OFString stringWithUTF8String: UTF8String length: length]; OFURIVerifyIsEscaped(*pathString, - [OFCharacterSet URIQueryAllowedCharacterSet]); + [OFCharacterSet URIQueryAllowedCharacterSet], true); } - (instancetype)initWithString: (OFString *)string { self = [super init]; @@ -595,17 +599,16 @@ if ((colon = strchr(UTF8String, ':')) == NULL || colon - UTF8String < 1 || !OFASCIIIsAlpha(UTF8String[0])) @throw [OFInvalidFormatException exception]; - _percentEncodedScheme = [[[OFString - stringWithUTF8String: UTF8String - length: colon - UTF8String] lowercaseString] - copy]; + _scheme = [[[OFString stringWithUTF8String: UTF8String + length: colon - UTF8String] + lowercaseString] copy]; - OFURIVerifyIsEscaped(_percentEncodedScheme, - [OFCharacterSet URISchemeAllowedCharacterSet]); + OFURIVerifyIsEscaped(_scheme, + [OFCharacterSet URISchemeAllowedCharacterSet], false); length -= colon - UTF8String + 1; UTF8String = colon + 1; if (length >= 2 && UTF8String[0] == '/' && @@ -712,11 +715,11 @@ const char *UTF8String = string.UTF8String; size_t length = string.UTF8StringLength; bool hasAuthority = false; OFString *path, *query = nil, *fragment = nil; - _percentEncodedScheme = [URI->_percentEncodedScheme copy]; + _scheme = [URI->_scheme copy]; if (length >= 2 && UTF8String[0] == '/' && UTF8String[1] == '/') { size_t authorityLength; @@ -818,11 +821,11 @@ _percentEncodedHost = [percentEncodedHost copy]; if (isDirectory && ![path hasSuffix: @"/"]) path = [path stringByAppendingString: @"/"]; - _percentEncodedScheme = @"file"; + _scheme = @"file"; _percentEncodedPath = [[path stringByAddingPercentEncodingWithAllowedCharacters: [OFCharacterSet URIPathAllowedCharacterSet]] copy]; objc_autoreleasePoolPop(pool); @@ -858,11 +861,11 @@ return self; } - (void)dealloc { - [_percentEncodedScheme release]; + [_scheme release]; [_percentEncodedHost release]; [_port release]; [_percentEncodedUser release]; [_percentEncodedPassword release]; [_percentEncodedPath release]; @@ -882,12 +885,11 @@ if (![object isKindOfClass: [OFURI class]]) return false; URI = object; - if (URI->_percentEncodedScheme != _percentEncodedScheme && - ![URI->_percentEncodedScheme isEqual: _percentEncodedScheme]) + if (URI->_scheme != _scheme && ![URI->_scheme isEqual: _scheme]) return false; if (URI->_percentEncodedHost != _percentEncodedHost && ![URI->_percentEncodedHost isEqual: _percentEncodedHost]) return false; if (URI->_port != _port && ![URI->_port isEqual: _port]) @@ -915,11 +917,11 @@ { unsigned long hash; OFHashInit(&hash); - OFHashAddHash(&hash, _percentEncodedScheme.hash); + OFHashAddHash(&hash, _scheme.hash); OFHashAddHash(&hash, _percentEncodedHost.hash); OFHashAddHash(&hash, _port.hash); OFHashAddHash(&hash, _percentEncodedUser.hash); OFHashAddHash(&hash, _percentEncodedPassword.hash); OFHashAddHash(&hash, _percentEncodedPath.hash); @@ -931,16 +933,11 @@ return hash; } - (OFString *)scheme { - return _percentEncodedScheme.stringByRemovingPercentEncoding; -} - -- (OFString *)percentEncodedScheme -{ - return _percentEncodedScheme; + return _scheme; } - (OFString *)host { if ([_percentEncodedHost hasPrefix: @"["] && @@ -999,11 +996,11 @@ - (OFArray *)pathComponents { void *pool = objc_autoreleasePoolPush(); #ifdef OF_HAVE_FILES - bool isFile = [_percentEncodedScheme isEqual: @"file"]; + bool isFile = [_scheme isEqual: @"file"]; #endif OFMutableArray *ret; size_t count; #ifdef OF_HAVE_FILES @@ -1152,11 +1149,11 @@ - (id)mutableCopy { OFURI *copy = [[OFMutableURI alloc] init]; @try { - copy->_percentEncodedScheme = [_percentEncodedScheme copy]; + copy->_scheme = [_scheme copy]; copy->_percentEncodedHost = [_percentEncodedHost copy]; copy->_port = [_port copy]; copy->_percentEncodedUser = [_percentEncodedUser copy]; copy->_percentEncodedPassword = [_percentEncodedPassword copy]; copy->_percentEncodedPath = [_percentEncodedPath copy]; @@ -1172,11 +1169,11 @@ - (OFString *)string { OFMutableString *ret = [OFMutableString string]; - [ret appendFormat: @"%@:", _percentEncodedScheme]; + [ret appendFormat: @"%@:", _scheme]; if (_percentEncodedHost != nil || _port != nil || _percentEncodedUser != nil || _percentEncodedPassword != nil) [ret appendString: @"//"]; @@ -1210,11 +1207,11 @@ - (OFString *)fileSystemRepresentation { void *pool = objc_autoreleasePoolPush(); OFString *path; - if (![_percentEncodedScheme isEqual: @"file"]) + if (![_scheme isEqual: @"file"]) @throw [OFInvalidArgumentException exception]; if (![_percentEncodedPath hasPrefix: @"/"]) @throw [OFInvalidFormatException exception]; Index: tests/OFURITests.m ================================================================== --- tests/OFURITests.m +++ tests/OFURITests.m @@ -16,11 +16,11 @@ #include "config.h" #import "TestsAppDelegate.h" static OFString *const module = @"OFURI"; -static OFString *URIString = @"ht%3atp://us%3Aer:p%40w@ho%3Ast:1234/" +static OFString *URIString = @"ht+tp://us%3Aer:p%40w@ho%3Ast:1234/" @"pa%3Fth?que%23ry=1&f%26oo=b%3dar#frag%23ment"; @implementation TestsAppDelegate (OFURITests) - (void)URITests { @@ -73,11 +73,11 @@ OFInvalidFormatException, [OFURI URIWithString: @"https://[f]:f/"]) TEST(@"+[URIWithString:relativeToURI:]", [[[OFURI URIWithString: @"/foo" relativeToURI: URI1] string] - isEqual: @"ht%3atp://us%3Aer:p%40w@ho%3Ast:1234/foo"] && + isEqual: @"ht+tp://us%3Aer:p%40w@ho%3Ast:1234/foo"] && [[[OFURI URIWithString: @"foo/bar?q" relativeToURI: [OFURI URIWithString: @"http://h/qux/quux"]] string] isEqual: @"http://h/qux/foo/bar?q"] && [[[OFURI URIWithString: @"foo/bar" relativeToURI: [OFURI URIWithString: @"http://h/qux/?x"]] @@ -150,11 +150,11 @@ [URI8.string isEqual: @"urn:qux:foo"] && [URI9.string isEqual: @"file:/foo?query#frag"] && [URI10.string isEqual: @"file:foo@bar/qux?query#frag"]) TEST(@"-[scheme]", - [URI1.scheme isEqual: @"ht:tp"] && [URI4.scheme isEqual: @"file"] && + [URI1.scheme isEqual: @"ht+tp"] && [URI4.scheme isEqual: @"file"] && [URI9.scheme isEqual: @"file"] && [URI10.scheme isEqual: @"file"]) TEST(@"-[user]", [URI1.user isEqual: @"us:er"] && URI4.user == nil && URI10.user == nil) TEST(@"-[password]", @@ -212,21 +212,13 @@ EXPECT_EXCEPTION(@"Detection of invalid format", OFInvalidFormatException, [OFURI URIWithString: @"http"]) mutableURI = [OFMutableURI URI]; - TEST(@"-[setScheme:]", - (mutableURI.scheme = @"ht:tp") && - [mutableURI.percentEncodedScheme isEqual: @"ht%3Atp"]) - - TEST(@"-[setPercentEncodedScheme:]", - (mutableURI.percentEncodedScheme = @"ht%3Atp") && - [mutableURI.scheme isEqual: @"ht:tp"]) - EXPECT_EXCEPTION( @"-[setPercentEncodedScheme:] with invalid characters fails", - OFInvalidFormatException, mutableURI.percentEncodedScheme = @"~") + OFInvalidFormatException, mutableURI.scheme = @"%20") TEST(@"-[setHost:]", (mutableURI.host = @"ho:st") && [mutableURI.percentEncodedHost isEqual: @"ho%3Ast"] && (mutableURI.host = @"12:34:ab") &&