Index: src/OFURI.m ================================================================== --- src/OFURI.m +++ src/OFURI.m @@ -45,15 +45,18 @@ @end @interface OFURIPathAllowedCharacterSet: OFURIAllowedCharacterSetBase @end -@interface OFURIQueryOrFragmentAllowedCharacterSet: OFURIAllowedCharacterSetBase +@interface OFURIQueryAllowedCharacterSet: OFURIAllowedCharacterSetBase @end @interface OFURIQueryKeyValueAllowedCharacterSet: OFURIAllowedCharacterSetBase @end + +@interface OFURIFragmentAllowedCharacterSet: OFURIAllowedCharacterSetBase +@end OF_DIRECT_MEMBERS @interface OFInvertedCharacterSetWithoutPercent: OFCharacterSet { OFCharacterSet *_characterSet; @@ -64,16 +67,15 @@ @end static OFCharacterSet *URIAllowedCharacterSet = nil; static OFCharacterSet *URISchemeAllowedCharacterSet = nil; static OFCharacterSet *URIPathAllowedCharacterSet = nil; -static OFCharacterSet *URIQueryOrFragmentAllowedCharacterSet = nil; +static OFCharacterSet *URIQueryAllowedCharacterSet = nil; static OFCharacterSet *URIQueryKeyValueAllowedCharacterSet = nil; +static OFCharacterSet *URIFragmentAllowedCharacterSet = nil; static OFOnceControl URIAllowedCharacterSetOnce = OFOnceControlInitValue; -static OFOnceControl URIQueryOrFragmentAllowedCharacterSetOnce = - OFOnceControlInitValue; static void initURIAllowedCharacterSet(void) { URIAllowedCharacterSet = [[OFURIAllowedCharacterSet alloc] init]; @@ -92,22 +94,29 @@ URIPathAllowedCharacterSet = [[OFURIPathAllowedCharacterSet alloc] init]; } static void -initURIQueryOrFragmentAllowedCharacterSet(void) +initURIQueryAllowedCharacterSet(void) { - URIQueryOrFragmentAllowedCharacterSet = - [[OFURIQueryOrFragmentAllowedCharacterSet alloc] init]; + URIQueryAllowedCharacterSet = + [[OFURIQueryAllowedCharacterSet alloc] init]; } static void initURIQueryKeyValueAllowedCharacterSet(void) { URIQueryKeyValueAllowedCharacterSet = [[OFURIQueryKeyValueAllowedCharacterSet alloc] init]; } + +static void +initURIFragmentAllowedCharacterSet(void) +{ + URIFragmentAllowedCharacterSet = + [[OFURIFragmentAllowedCharacterSet alloc] init]; +} bool OFURIIsIPv6Host(OFString *host) { const char *UTF8String = host.UTF8String; @@ -125,10 +134,23 @@ UTF8String++; } return hasColon; } + +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 OFURIAllowedCharacterSetBase - (instancetype)autorelease { return self; @@ -153,10 +175,13 @@ - (bool)characterIsMember: (OFUnichar)character { if (character < CHAR_MAX && OFASCIIIsAlnum(character)) return true; + if (character > 0x7F) + return !isUnicodePrivate(character); + switch (character) { case '-': case '.': case '_': case '~': @@ -199,10 +224,13 @@ - (bool)characterIsMember: (OFUnichar)character { if (character < CHAR_MAX && OFASCIIIsAlnum(character)) return true; + if (character > 0x7F) + return !isUnicodePrivate(character); + switch (character) { case '-': case '.': case '_': case '~': @@ -225,15 +253,18 @@ return false; } } @end -@implementation OFURIQueryOrFragmentAllowedCharacterSet +@implementation OFURIQueryAllowedCharacterSet - (bool)characterIsMember: (OFUnichar)character { if (character < CHAR_MAX && OFASCIIIsAlnum(character)) return true; + + if (character > 0x7F) + return true; switch (character) { case '-': case '.': case '_': @@ -264,24 +295,63 @@ - (bool)characterIsMember: (OFUnichar)character { if (character < CHAR_MAX && OFASCIIIsAlnum(character)) return true; + if (character > 0x7F) + 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 OFURIFragmentAllowedCharacterSet +- (bool)characterIsMember: (OFUnichar)character +{ + if (character < CHAR_MAX && OFASCIIIsAlnum(character)) + return true; + + if (character > 0x7F) + return !isUnicodePrivate(character); + switch (character) { case '-': case '.': case '_': case '~': case '!': case '$': + case '&': case '\'': case '(': case ')': case '*': case '+': case ',': case ';': + case '=': case ':': case '@': case '/': case '?': return true; @@ -379,14 +449,14 @@ return URIPathAllowedCharacterSet; } + (OFCharacterSet *)URIQueryAllowedCharacterSet { - OFOnce(&URIQueryOrFragmentAllowedCharacterSetOnce, - initURIQueryOrFragmentAllowedCharacterSet); + static OFOnceControl onceControl = OFOnceControlInitValue; + OFOnce(&onceControl, initURIQueryAllowedCharacterSet); - return URIQueryOrFragmentAllowedCharacterSet; + return URIQueryAllowedCharacterSet; } + (OFCharacterSet *)URIQueryKeyValueAllowedCharacterSet { static OFOnceControl onceControl = OFOnceControlInitValue; @@ -395,14 +465,14 @@ return URIQueryKeyValueAllowedCharacterSet; } + (OFCharacterSet *)URIFragmentAllowedCharacterSet { - OFOnce(&URIQueryOrFragmentAllowedCharacterSetOnce, - initURIQueryOrFragmentAllowedCharacterSet); + static OFOnceControl onceControl = OFOnceControlInitValue; + OFOnce(&onceControl, initURIFragmentAllowedCharacterSet); - return URIQueryOrFragmentAllowedCharacterSet; + return URIFragmentAllowedCharacterSet; } @end @implementation OFURI + (instancetype)URI @@ -581,11 +651,11 @@ *pathString = [OFString stringWithUTF8String: UTF8String length: length]; OFURIVerifyIsEscaped(*pathString, - [OFCharacterSet URIQueryAllowedCharacterSet], true); + [OFCharacterSet URIPathAllowedCharacterSet], true); } - (instancetype)initWithString: (OFString *)string { self = [super init]; Index: tests/OFURITests.m ================================================================== --- tests/OFURITests.m +++ tests/OFURITests.m @@ -24,11 +24,11 @@ @implementation TestsAppDelegate (OFURITests) - (void)URITests { void *pool = objc_autoreleasePoolPush(); OFURI *URI1, *URI2, *URI3, *URI4, *URI5, *URI6, *URI7, *URI8, *URI9; - OFURI *URI10; + OFURI *URI10, *URI11; OFMutableURI *mutableURI; TEST(@"+[URIWithString:]", R(URI1 = [OFURI URIWithString: URIString]) && R(URI2 = [OFURI URIWithString: @"http://foo:80"]) && @@ -37,11 +37,12 @@ R(URI5 = [OFURI URIWithString: @"http://foo/bar/qux/foo%2fbar"]) && R(URI6 = [OFURI URIWithString: @"https://[12:34::56:abcd]/"]) && R(URI7 = [OFURI URIWithString: @"https://[12:34::56:abcd]:234/"]) && R(URI8 = [OFURI URIWithString: @"urn:qux:foo"]) && R(URI9 = [OFURI URIWithString: @"file:/foo?query#frag"]) && - R(URI10 = [OFURI URIWithString: @"file:foo@bar/qux?query#frag"])) + R(URI10 = [OFURI URIWithString: @"file:foo@bar/qux?query#frag"]) && + R(URI11 = [OFURI URIWithString: @"http://ä/ö?ü"])) EXPECT_EXCEPTION(@"+[URIWithString:] fails with invalid characters #1", OFInvalidFormatException, [OFURI URIWithString: @"ht,tp://foo"]) @@ -70,10 +71,14 @@ [OFURI URIWithString: @"https://[f]:/"]) EXPECT_EXCEPTION(@"+[URIWithString:] fails with invalid characters #8", OFInvalidFormatException, [OFURI URIWithString: @"https://[f]:f/"]) + + EXPECT_EXCEPTION(@"+[URIWithString:] fails with invalid characters #9", + OFInvalidFormatException, + [OFURI URIWithString: @"foo:"]) TEST(@"+[URIWithString:relativeToURI:]", [[[OFURI URIWithString: @"/foo" relativeToURI: URI1] string] isEqual: @"ht+tp://us%3Aer:p%40w@ho%3Ast:1234/foo"] && [[[OFURI URIWithString: @"foo/bar?q" @@ -147,34 +152,39 @@ [URI5.string isEqual: @"http://foo/bar/qux/foo%2fbar"] && [URI6.string isEqual: @"https://[12:34::56:abcd]/"] && [URI7.string isEqual: @"https://[12:34::56:abcd]:234/"] && [URI8.string isEqual: @"urn:qux:foo"] && [URI9.string isEqual: @"file:/foo?query#frag"] && - [URI10.string isEqual: @"file:foo@bar/qux?query#frag"]) + [URI10.string isEqual: @"file:foo@bar/qux?query#frag"] && + [URI11.string isEqual: @"http://ä/ö?ü"]) TEST(@"-[scheme]", [URI1.scheme isEqual: @"ht+tp"] && [URI4.scheme isEqual: @"file"] && - [URI9.scheme isEqual: @"file"] && [URI10.scheme isEqual: @"file"]) + [URI9.scheme isEqual: @"file"] && [URI10.scheme isEqual: @"file"] && + [URI11.scheme isEqual: @"http"]) TEST(@"-[user]", [URI1.user isEqual: @"us:er"] && URI4.user == nil && - URI10.user == nil) + URI10.user == nil && URI11.user == nil) TEST(@"-[password]", [URI1.password isEqual: @"p@w"] && URI4.password == nil && - URI10.password == nil) + URI10.password == nil && URI11.password == nil) TEST(@"-[host]", [URI1.host isEqual: @"ho:st"] && [URI6.host isEqual: @"12:34::56:abcd"] && [URI7.host isEqual: @"12:34::56:abcd"] && - URI8.host == nil && URI9.host == nil && URI10.host == nil) + URI8.host == nil && URI9.host == nil && URI10.host == nil && + [URI11.host isEqual: @"ä"]) TEST(@"-[port]", URI1.port.unsignedShortValue == 1234 && [URI4 port] == nil && URI7.port.unsignedShortValue == 234 && - URI8.port == nil && URI9.port == nil && URI10.port == nil) + URI8.port == nil && URI9.port == nil && URI10.port == nil && + URI11.port == nil) TEST(@"-[path]", [URI1.path isEqual: @"/pa?th"] && [URI4.path isEqual: @"/etc/passwd"] && [URI8.path isEqual: @"qux:foo"] && [URI9.path isEqual: @"/foo"] && - [URI10.path isEqual: @"foo@bar/qux"]) + [URI10.path isEqual: @"foo@bar/qux"] && + [URI11.path isEqual: @"/ö"]) TEST(@"-[pathComponents]", [URI1.pathComponents isEqual: [OFArray arrayWithObjects: @"/", @"pa?th", nil]] && [URI4.pathComponents isEqual: [OFArray arrayWithObjects: @"/", @"etc", @"passwd", nil]] && @@ -190,11 +200,12 @@ [[[OFURI URIWithString: @"http://host/"] lastPathComponent] isEqual: @"/"] && [URI5.lastPathComponent isEqual: @"foo/bar"]) TEST(@"-[query]", [URI1.query isEqual: @"que#ry=1&f&oo=b=ar"] && URI4.query == nil && - [URI9.query isEqual: @"query"] && [URI10.query isEqual: @"query"]) + [URI9.query isEqual: @"query"] && [URI10.query isEqual: @"query"] && + [URI11.query isEqual: @"ü"]) TEST(@"-[queryItems]", [URI1.queryItems isEqual: [OFArray arrayWithObjects: [OFPair pairWithFirstObject: @"que#ry" secondObject: @"1"], [OFPair pairWithFirstObject: @"f&oo" secondObject: @"b=ar"], nil]]); TEST(@"-[fragment]",