Index: src/OFHTTPClient.m ================================================================== --- src/OFHTTPClient.m +++ src/OFHTTPClient.m @@ -316,11 +316,10 @@ { void *pool = objc_autoreleasePoolPush(); OFURL *URL = [request URL]; OFString *scheme = [URL scheme]; of_http_request_method_t method = [request method]; - OFString *path; OFMutableString *requestString; OFString *user, *password; OFMutableDictionary OF_GENERIC(OFString*, OFString*) *headers; OFDataArray *body = [request body]; OFTCPSocket *socket; @@ -373,22 +372,21 @@ /* * As a work around for a bug with split packets in lighttpd when using * HTTPS, we construct the complete request in a buffer string and then * send it all at once. */ - path = [[URL path] stringByURLEncodingWithIgnoredCharacters: "/"]; if ([URL query] != nil) requestString = [OFMutableString stringWithFormat: - @"%s /%@?%@ HTTP/%@\r\n", - of_http_request_method_to_string(method), path, + @"%s %@?%@ HTTP/%@\r\n", + of_http_request_method_to_string(method), [URL path], [[URL query] stringByURLEncoding], [request protocolVersionString]]; else requestString = [OFMutableString stringWithFormat: - @"%s /%@ HTTP/%@\r\n", - of_http_request_method_to_string(method), path, + @"%s %@ HTTP/%@\r\n", + of_http_request_method_to_string(method), [URL path], [request protocolVersionString]]; headers = [[[request headers] mutableCopy] autorelease]; if (headers == nil) headers = [OFMutableDictionary dictionary]; Index: src/OFString+URLEncoding.h ================================================================== --- src/OFString+URLEncoding.h +++ src/OFString+URLEncoding.h @@ -36,15 +36,15 @@ /*! * @brief Encodes a string for use in a URL, but does not escape the specified * ignored characters. * - * @param ignored A C string of characters that should not be escaped + * @param allowed A C string of characters that should not be escaped * * @return A new autoreleased string */ -- (OFString*)stringByURLEncodingWithIgnoredCharacters: (const char*)ignored; +- (OFString*)stringByURLEncodingWithAllowedCharacters: (const char*)allowed; /*! * @brief Decodes a string used in a URL. * * @return A new autoreleased string Index: src/OFString+URLEncoding.m ================================================================== --- src/OFString+URLEncoding.m +++ src/OFString+URLEncoding.m @@ -29,14 +29,14 @@ int _OFString_URLEncoding_reference; @implementation OFString (URLEncoding) - (OFString*)stringByURLEncoding { - return [self stringByURLEncodingWithIgnoredCharacters: ""]; + return [self stringByURLEncodingWithAllowedCharacters: "$-_.!*()"]; } -- (OFString*)stringByURLEncodingWithIgnoredCharacters: (const char*)ignored +- (OFString*)stringByURLEncodingWithAllowedCharacters: (const char*)allowed { void *pool = objc_autoreleasePoolPush(); const char *string = [self UTF8String]; char *retCString; size_t i; @@ -57,13 +57,11 @@ /* * '+' is also listed in RFC 1738, however, '+' is sometimes * interpreted as space in HTTP. Therefore always escape it to * make sure it's always interpreted correctly. */ - if (!(c & 0x80) && (isalnum(c) || c == '$' || c == '-' || - c == '_' || c == '.' || c == '!' || c == '*' || c == '(' || - c == ')' || c == ',' || strchr(ignored, c) != NULL)) + if (!(c & 0x80) && (isalnum(c) || strchr(allowed, c) != NULL)) retCString[i++] = c; else { unsigned char high, low; high = c >> 4; Index: src/OFURL.m ================================================================== --- src/OFURL.m +++ src/OFURL.m @@ -85,19 +85,19 @@ @throw [OFInvalidFormatException exception]; for (tmp2 = UTF8String; tmp2 < tmp; tmp2++) *tmp2 = tolower((unsigned char)*tmp2); - _scheme = [[[OFString stringWithUTF8String: UTF8String - length: tmp - UTF8String] - stringByURLDecoding] copy]; + _scheme = [[OFString alloc] + initWithUTF8String: UTF8String + length: tmp - UTF8String]; UTF8String = tmp + 3; if ([_scheme isEqual: @"file"]) { - _path = [[[OFString stringWithUTF8String: - UTF8String] stringByURLDecoding] copy]; + _path = [[OFString alloc] + initWithUTF8String: UTF8String]; objc_autoreleasePoolPop(pool); return self; } @@ -114,17 +114,17 @@ if ((tmp3 = strchr(UTF8String, ':')) != NULL) { *tmp3 = '\0'; tmp3++; - _user = [[[OFString stringWithUTF8String: - UTF8String] stringByURLDecoding] copy]; - _password = [[[OFString stringWithUTF8String: - tmp3] stringByURLDecoding] copy]; + _user = [[OFString alloc] + initWithUTF8String: UTF8String]; + _password = [[OFString alloc] + initWithUTF8String: tmp3]; } else - _user = [[[OFString stringWithUTF8String: - UTF8String] stringByURLDecoding] copy]; + _user = [[OFString alloc] + initWithUTF8String: UTF8String]; UTF8String = tmp2; } if ((tmp2 = strchr(UTF8String, ':')) != NULL) { @@ -132,12 +132,12 @@ OFString *portString; *tmp2 = '\0'; tmp2++; - _host = [[[OFString stringWithUTF8String: - UTF8String] stringByURLDecoding] copy]; + _host = [[OFString alloc] + initWithUTF8String: UTF8String]; pool = objc_autoreleasePoolPush(); portString = [OFString stringWithUTF8String: tmp2]; if ([portString decimalValue] > 65535) @@ -145,12 +145,12 @@ _port = [portString decimalValue]; objc_autoreleasePoolPop(pool); } else { - _host = [[[OFString stringWithUTF8String: - UTF8String] stringByURLDecoding] copy]; + _host = [[OFString alloc] + initWithUTF8String: UTF8String]; if ([_scheme isEqual: @"http"]) _port = 80; else if ([_scheme isEqual: @"https"]) _port = 443; @@ -160,30 +160,33 @@ if ((UTF8String = tmp) != NULL) { if ((tmp = strchr(UTF8String, '#')) != NULL) { *tmp = '\0'; - _fragment = [[[OFString stringWithUTF8String: - tmp + 1] stringByURLDecoding] copy]; + _fragment = [[OFString alloc] + initWithUTF8String: tmp + 1]; } if ((tmp = strchr(UTF8String, '?')) != NULL) { *tmp = '\0'; - _query = [[[OFString stringWithUTF8String: - tmp + 1] stringByURLDecoding] copy]; + _query = [[OFString alloc] + initWithUTF8String: tmp + 1]; } if ((tmp = strchr(UTF8String, ';')) != NULL) { *tmp = '\0'; - _parameters = [[[OFString stringWithUTF8String: - tmp + 1] stringByURLDecoding] copy]; + _parameters = [[OFString alloc] + initWithUTF8String: tmp + 1]; } - _path = [[[OFString stringWithUTF8String: - UTF8String] stringByURLDecoding] copy]; + UTF8String--; + *UTF8String = '/'; + + _path = [[OFString alloc] + initWithUTF8String: UTF8String]; } objc_autoreleasePoolPop(pool); } @catch (id e) { [self release]; @@ -222,34 +225,33 @@ UTF8String = UTF8String2; if ((tmp = strchr(UTF8String, '#')) != NULL) { *tmp = '\0'; - _fragment = [[[OFString stringWithUTF8String: - tmp + 1] stringByURLDecoding] copy]; + _fragment = [[OFString alloc] + initWithUTF8String: tmp + 1]; } if ((tmp = strchr(UTF8String, '?')) != NULL) { *tmp = '\0'; - _query = [[[OFString stringWithUTF8String: - tmp + 1] stringByURLDecoding] copy]; + _query = [[OFString alloc] + initWithUTF8String: tmp + 1]; } if ((tmp = strchr(UTF8String, ';')) != NULL) { *tmp = '\0'; - _parameters = [[[OFString stringWithUTF8String: - tmp + 1] stringByURLDecoding] copy]; + _parameters = [[OFString alloc] + initWithUTF8String: tmp + 1]; } if (*UTF8String == '/') - _path = [[[OFString stringWithUTF8String: - UTF8String + 1] stringByURLDecoding] copy]; + _path = [[OFString alloc] + initWithUTF8String: UTF8String]; else { OFString *path, *s; - path = [[[OFString stringWithUTF8String: - UTF8String] stringByURLDecoding] copy]; + path = [OFString stringWithUTF8String: UTF8String]; if ([URL->_path hasSuffix: @"/"]) s = [URL->_path stringByAppendingString: path]; else s = [OFString stringWithFormat: @"%@/../%@", @@ -386,48 +388,48 @@ - (OFString*)string { OFMutableString *ret = [OFMutableString string]; void *pool = objc_autoreleasePoolPush(); - [ret appendFormat: @"%@://", [_scheme stringByURLEncoding]]; + [ret appendFormat: @"%@://", _scheme]; if ([_scheme isEqual: @"file"]) { if (_path != nil) - [ret appendString: [_path - stringByURLEncodingWithIgnoredCharacters: "/"]]; + [ret appendString: _path]; objc_autoreleasePoolPop(pool); return ret; } if (_user != nil && _password != nil) - [ret appendFormat: @"%@:%@@", - [_user stringByURLEncoding], - [_password stringByURLEncoding]]; + [ret appendFormat: @"%@:%@@", _user, _password]; else if (_user != nil) - [ret appendFormat: @"%@@", [_user stringByURLEncoding]]; + [ret appendFormat: @"%@@", _user]; if (_host != nil) - [ret appendString: [_host stringByURLEncoding]]; + [ret appendString: _host]; if (!(([_scheme isEqual: @"http"] && _port == 80) || ([_scheme isEqual: @"https"] && _port == 443) || ([_scheme isEqual: @"ftp"] && _port == 21))) [ret appendFormat: @":%u", _port]; - if (_path != nil) - [ret appendFormat: @"/%@", - [_path stringByURLEncodingWithIgnoredCharacters: "/"]]; + if (_path != nil) { + if (![_path hasPrefix: @"/"]) + @throw [OFInvalidFormatException exception]; + + [ret appendString: _path]; + } if (_parameters != nil) - [ret appendFormat: @";%@", [_parameters stringByURLEncoding]]; + [ret appendFormat: @";%@", _parameters]; if (_query != nil) - [ret appendFormat: @"?%@", [_query stringByURLEncoding]]; + [ret appendFormat: @"?%@", _query]; if (_fragment != nil) - [ret appendFormat: @"#%@", [_fragment stringByURLEncoding]]; + [ret appendFormat: @"#%@", _fragment]; objc_autoreleasePoolPop(pool); [ret makeImmutable]; Index: tests/OFURLTests.m ================================================================== --- tests/OFURLTests.m +++ tests/OFURLTests.m @@ -23,11 +23,11 @@ #import "OFInvalidFormatException.h" #import "TestsAppDelegate.h" static OFString *module = @"OFURL"; -static OFString *url_str = @"ht%3Atp://us%3Aer:p%40w@ho%3Ast:1234/" +static OFString *url_str = @"ht%3atp://us%3Aer:p%40w@ho%3Ast:1234/" @"pa%3Bth;pa%3Fram?que%23ry#frag%23ment"; @implementation TestsAppDelegate (OFURLTests) - (void)URLTests { @@ -41,11 +41,11 @@ R(u4 = [OFURL URLWithString: @"file:///etc/passwd"])) TEST(@"+[URLWithString:relativeToURL:]", [[[OFURL URLWithString: @"/foo" relativeToURL: u1] string] isEqual: - @"ht%3Atp://us%3Aer:p%40w@ho%3Ast:1234/foo"] && + @"ht%3atp://us%3Aer:p%40w@ho%3Ast:1234/foo"] && [[[OFURL URLWithString: @"foo/bar?q" relativeToURL: [OFURL URLWithString: @"http://h/qux/quux"]] string] isEqual: @"http://h/qux/foo/bar?q"] && [[[OFURL URLWithString: @"foo/bar" relativeToURL: [OFURL URLWithString: @"http://h/qux/?x"]] @@ -58,24 +58,26 @@ [[u2 string] isEqual: @"http://foo"] && [[u3 string] isEqual: @"http://bar/"] && [[u4 string] isEqual: @"file:///etc/passwd"]) TEST(@"-[scheme]", - [[u1 scheme] isEqual: @"ht:tp"] && [[u4 scheme] isEqual: @"file"]) - TEST(@"-[user]", [[u1 user] isEqual: @"us:er"] && [u4 user] == nil) + [[u1 scheme] isEqual: @"ht%3atp"] && [[u4 scheme] isEqual: @"file"]) + + TEST(@"-[user]", [[u1 user] isEqual: @"us%3Aer"] && [u4 user] == nil) TEST(@"-[password]", - [[u1 password] isEqual: @"p@w"] && [u4 password] == nil) - TEST(@"-[host]", [[u1 host] isEqual: @"ho:st"] && [u4 port] == 0) + [[u1 password] isEqual: @"p%40w"] && [u4 password] == nil) + TEST(@"-[host]", [[u1 host] isEqual: @"ho%3Ast"] && [u4 port] == 0) TEST(@"-[port]", [u1 port] == 1234) TEST(@"-[path]", - [[u1 path] isEqual: @"pa;th"] && + [[u1 path] isEqual: @"/pa%3Bth"] && [[u4 path] isEqual: @"/etc/passwd"]) TEST(@"-[parameters]", - [[u1 parameters] isEqual: @"pa?ram"] && [u4 parameters] == nil) - TEST(@"-[query]", [[u1 query] isEqual: @"que#ry"] && [u4 query] == nil) + [[u1 parameters] isEqual: @"pa%3Fram"] && [u4 parameters] == nil) + TEST(@"-[query]", + [[u1 query] isEqual: @"que%23ry"] && [u4 query] == nil) TEST(@"-[fragment]", - [[u1 fragment] isEqual: @"frag#ment"] && [u4 fragment] == nil) + [[u1 fragment] isEqual: @"frag%23ment"] && [u4 fragment] == nil) TEST(@"-[copy]", R(u4 = [[u1 copy] autorelease])) TEST(@"-[isEqual:]", [u1 isEqual: u4] && ![u2 isEqual: u3] && [[OFURL URLWithString: @"HTTP://bar/"] isEqual: u3]) Index: tests/serialization.xml ================================================================== --- tests/serialization.xml +++ tests/serialization.xml @@ -6,29 +6,10 @@ B"la - MDEyMzQ1Njc4OTo7PEFCQ0RFRkdISklLTE1OT1BRUlNUVVZXWFla - - - data - - - - Qu"xbar -test - 1234 - 40934a456d5cfaad - asd - 40934a456d5cfaad - - - - Hello - - Hello Wo ld! How are you? https://webkeks.org/ @@ -55,7 +36,26 @@ list + + MDEyMzQ1Njc4OTo7PEFCQ0RFRkdISklLTE1OT1BRUlNUVVZXWFla + + + data + + + + Qu"xbar +test + 1234 + 40934a456d5cfaad + asd + 40934a456d5cfaad + + + + Hello +