Index: src/OFURL.m ================================================================== --- src/OFURL.m +++ src/OFURL.m @@ -390,10 +390,11 @@ self = [super init]; @try { void *pool = objc_autoreleasePoolPush(); char *tmp, *tmp2; + bool isIPv6Host = false; if ((UTF8String2 = of_strdup(string.UTF8String)) == NULL) @throw [OFOutOfMemoryException exceptionWithRequestedSize: string.UTF8StringLength]; @@ -449,11 +450,57 @@ [OFCharacterSet URLUserAllowedCharacterSet]); UTF8String = tmp2; } - if ((tmp2 = strchr(UTF8String, ':')) != NULL) { + if (UTF8String[0] == '[') { + tmp2 = UTF8String++; + + while (of_ascii_isdigit(*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 (!of_ascii_isdigit(*UTF8String)) + @throw [OFInvalidFormatException + exception]; + + UTF8String++; + } + + portString = [OFString + stringWithUTF8String: tmp2 + length: UTF8String - tmp2]; + + if (portString.length == 0 || + portString.decimalValue > 65535) + @throw [OFInvalidFormatException + exception]; + + _port = [[OFNumber alloc] initWithUInt16: + (uint16_t)portString.decimalValue]; + } else if (*UTF8String != '\0') + @throw [OFInvalidFormatException exception]; + + isIPv6Host = true; + } else if ((tmp2 = strchr(UTF8String, ':')) != NULL) { OFString *portString; *tmp2 = '\0'; tmp2++; @@ -469,12 +516,13 @@ (uint16_t)portString.decimalValue]; } else _URLEncodedHost = [[OFString alloc] initWithUTF8String: UTF8String]; - of_url_verify_escaped(_URLEncodedHost, - [OFCharacterSet URLHostAllowedCharacterSet]); + if (!isIPv6Host) + of_url_verify_escaped(_URLEncodedHost, + [OFCharacterSet URLHostAllowedCharacterSet]); if ((UTF8String = tmp) != NULL) { if ((tmp = strchr(UTF8String, '#')) != NULL) { *tmp = '\0'; Index: tests/OFURLTests.m ================================================================== --- tests/OFURLTests.m +++ tests/OFURLTests.m @@ -25,19 +25,21 @@ @implementation TestsAppDelegate (OFURLTests) - (void)URLTests { void *pool = objc_autoreleasePoolPush(); - OFURL *u1, *u2, *u3, *u4, *u5; + OFURL *u1, *u2, *u3, *u4, *u5, *u6, *u7; OFMutableURL *mu; TEST(@"+[URLWithString:]", R(u1 = [OFURL URLWithString: url_str]) && R(u2 = [OFURL URLWithString: @"http://foo:80"]) && R(u3 = [OFURL URLWithString: @"http://bar/"]) && R(u4 = [OFURL URLWithString: @"file:///etc/passwd"]) && - R(u5 = [OFURL URLWithString: @"http://foo/bar/qux/foo%2fbar"])) + R(u5 = [OFURL URLWithString: @"http://foo/bar/qux/foo%2fbar"]) && + R(u6 = [OFURL URLWithString: @"https://[12:34::56:abcd]/"]) && + R(u7 = [OFURL URLWithString: @"https://[12:34::56:abcd]:234/"])) EXPECT_EXCEPTION(@"+[URLWithString:] fails with invalid characters #1", OFInvalidFormatException, [OFURL URLWithString: @"ht,tp://foo"]) @@ -54,10 +56,22 @@ [OFURL URLWithString: @"http://foo/foo?`"]) EXPECT_EXCEPTION(@"+[URLWithString:] fails with invalid characters #5", OFInvalidFormatException, [OFURL URLWithString: @"http://foo/foo?foo#`"]) + + EXPECT_EXCEPTION(@"+[URLWithString:] fails with invalid characters #6", + OFInvalidFormatException, + [OFURL URLWithString: @"https://[g]/"]) + + EXPECT_EXCEPTION(@"+[URLWithString:] fails with invalid characters #7", + OFInvalidFormatException, + [OFURL URLWithString: @"https://[f]:/"]) + + EXPECT_EXCEPTION(@"+[URLWithString:] fails with invalid characters #8", + OFInvalidFormatException, + [OFURL URLWithString: @"https://[f]:f/"]) TEST(@"+[URLWithString:relativeToURL:]", [[[OFURL URLWithString: @"/foo" relativeToURL: u1] string] isEqual: @"ht%3atp://us%3Aer:p%40w@ho%3Ast:1234/foo"] && @@ -137,12 +151,16 @@ [u1.scheme isEqual: @"ht:tp"] && [u4.scheme isEqual: @"file"]) TEST(@"-[user]", [u1.user isEqual: @"us:er"] && u4.user == nil) TEST(@"-[password]", [u1.password isEqual: @"p@w"] && u4.password == nil) - TEST(@"-[host]", [u1.host isEqual: @"ho:st"] && [u4 port] == nil) - TEST(@"-[port]", [u1.port isEqual: [OFNumber numberWithUInt16: 1234]]) + TEST(@"-[host]", [u1.host isEqual: @"ho:st"] && + [u6.host isEqual: @"[12:34::56:abcd]"] && + [u7.host isEqual: @"[12:34::56:abcd]"]) + TEST(@"-[port]", [u1.port isEqual: [OFNumber numberWithUInt16: 1234]] && + [u4 port] == nil && + [u7.port isEqual: [OFNumber numberWithUInt16: 234]]) TEST(@"-[path]", [u1.path isEqual: @"/pa?th"] && [u4.path isEqual: @"/etc/passwd"]) TEST(@"-[pathComponents]", [u1.pathComponents isEqual: [OFArray arrayWithObjects: @"/", @"pa?th", nil]] &&