Index: src/OFDictionary.m ================================================================== --- src/OFDictionary.m +++ src/OFDictionary.m @@ -21,10 +21,11 @@ #include #import "OFDictionary.h" #import "OFDictionary_hashtable.h" #import "OFArray.h" +#import "OFCharacterSet.h" #import "OFString.h" #import "OFXMLElement.h" #import "OFData.h" #import "OFInvalidArgumentException.h" @@ -33,17 +34,23 @@ static struct { Class isa; } placeholder; +static OFCharacterSet *URLQueryPartAllowedCharacterSet = nil; + @interface OFDictionary () - (OFString *)of_JSONRepresentationWithOptions: (int)options depth: (size_t)depth; @end @interface OFDictionary_placeholder: OFDictionary @end + +@interface OFCharacterSet_URLQueryPartAllowed: OFCharacterSet ++ (OFCharacterSet *)URLQueryPartAllowedCharacterSet; +@end @implementation OFDictionary_placeholder - (instancetype)init { return (id)[[OFDictionary_hashtable alloc] init]; @@ -119,10 +126,70 @@ } - (void)dealloc { OF_DEALLOC_UNSUPPORTED +} +@end + +@implementation OFCharacterSet_URLQueryPartAllowed ++ (void)initialize +{ + if (self != [OFCharacterSet_URLQueryPartAllowed class]) + return; + + URLQueryPartAllowedCharacterSet = + [[OFCharacterSet_URLQueryPartAllowed alloc] init]; +} + ++ (OFCharacterSet *)URLQueryPartAllowedCharacterSet +{ + return URLQueryPartAllowedCharacterSet; +} + +- (instancetype)autorelease +{ + return self; +} + +- (instancetype)retain +{ + return self; +} + +- (void)release +{ +} + +- (unsigned int)retainCount +{ + return OF_RETAIN_COUNT_MAX; +} + +- (bool)characterIsMember: (of_unichar_t)character +{ + if (character < CHAR_MAX && of_ascii_isalnum(character)) + return true; + + switch (character) { + case '-': + case '.': + case '_': + case '~': + case '!': + case '$': + case '\'': + case '(': + case ')': + case '*': + case '+': + case ',': + case ';': + return true; + default: + return false; + } } @end @implementation OFDictionary + (void)initialize @@ -574,10 +641,12 @@ { OFMutableString *ret = [OFMutableString string]; void *pool = objc_autoreleasePoolPush(); OFEnumerator *keyEnumerator = [self keyEnumerator]; OFEnumerator *objectEnumerator = [self objectEnumerator]; + OFCharacterSet *allowed = [OFCharacterSet_URLQueryPartAllowed + URLQueryPartAllowedCharacterSet]; bool first = true; id key, object; while ((key = [keyEnumerator nextObject]) != nil && (object = [objectEnumerator nextObject]) != nil) { @@ -585,14 +654,14 @@ first = false; else [ret appendString: @"&"]; [ret appendString: [[key description] - stringByURLEncodingWithAllowedCharacters: "-._~!$'()*+,;"]]; + stringByURLEncodingWithAllowedCharacters: allowed]]; [ret appendString: @"="]; [ret appendString: [[object description] - stringByURLEncodingWithAllowedCharacters: "-._~!$'()*+,;"]]; + stringByURLEncodingWithAllowedCharacters: allowed]]; } [ret makeImmutable]; objc_autoreleasePoolPop(pool); Index: src/OFString+URLEncoding.h ================================================================== --- src/OFString+URLEncoding.h +++ src/OFString+URLEncoding.h @@ -16,36 +16,35 @@ #import "OFString.h" OF_ASSUME_NONNULL_BEGIN +@class OFCharacterSet; + #ifdef __cplusplus extern "C" { #endif extern int _OFString_URLEncoding_reference; #ifdef __cplusplus } #endif @interface OFString (URLEncoding) -/*! - * The string as an URL encoded string for use in a URL. - */ -@property (readonly, nonatomic) OFString *stringByURLEncoding; - /*! * The string as an URL decoded string. */ @property (readonly, nonatomic) OFString *stringByURLDecoding; /*! * @brief Encodes a string for use in a URL, but does not escape the specified - * ignored characters. + * allowed characters. * - * @param allowed A C string of characters that should not be escaped + * @param allowedCharacters A character set of characters that should not be + * escaped * * @return A new autoreleased string */ -- (OFString *)stringByURLEncodingWithAllowedCharacters: (const char *)allowed; +- (OFString *)stringByURLEncodingWithAllowedCharacters: + (OFCharacterSet *)allowedCharacters; @end OF_ASSUME_NONNULL_END Index: src/OFString+URLEncoding.m ================================================================== --- src/OFString+URLEncoding.m +++ src/OFString+URLEncoding.m @@ -18,70 +18,54 @@ #include #include #import "OFString+URLEncoding.h" +#import "OFCharacterSet.h" #import "OFInvalidFormatException.h" #import "OFOutOfMemoryException.h" /* Reference for static linking */ int _OFString_URLEncoding_reference; @implementation OFString (URLEncoding) -- (OFString *)stringByURLEncoding -{ - return [self stringByURLEncodingWithAllowedCharacters: - "-._~!$&'()*+,;="]; -} - -- (OFString *)stringByURLEncodingWithAllowedCharacters: (const char *)allowed -{ - void *pool = objc_autoreleasePoolPush(); - const char *string = [self UTF8String]; - size_t length = [self UTF8StringLength]; - char *retCString, *retCString2; - size_t i = 0; - - /* - * Worst case: 3 times longer than before. - * Oh, and we can't use [self allocWithSize:] here as self might be a - * @"" literal. - */ - if ((retCString = malloc(length * 3 + 1)) == NULL) - @throw [OFOutOfMemoryException exceptionWithRequestedSize: - length * 3 + 1]; - - while (length--) { - unsigned char c = *string++; - - if (of_ascii_isalnum(c) || strchr(allowed, c) != NULL) - retCString[i++] = c; - else { - unsigned char high, low; - - high = c >> 4; - low = c & 0x0F; - - retCString[i++] = '%'; - retCString[i++] = - (high > 9 ? high - 10 + 'A' : high + '0'); - retCString[i++] = - (low > 9 ? low - 10 + 'A' : low + '0'); - } - } - retCString[i] = '\0'; +- (OFString *)stringByURLEncodingWithAllowedCharacters: + (OFCharacterSet *)allowedCharacters +{ + OFMutableString *ret = [OFMutableString string]; + void *pool = objc_autoreleasePoolPush(); + const of_unichar_t *characters = [self characters]; + size_t length = [self length]; + bool (*characterIsMember)(id, SEL, of_unichar_t) = + (bool (*)(id, SEL, of_unichar_t))[allowedCharacters + methodForSelector: @selector(characterIsMember:)]; + + for (size_t i = 0; i < length; i++) { + of_unichar_t c = characters[i]; + + if (characterIsMember(allowedCharacters, + @selector(characterIsMember:), c)) + [ret appendCharacters: &c + length: 1]; + else { + unsigned char high = c >> 4; + unsigned char low = c & 0x0F; + of_unichar_t escaped[3]; + + escaped[0] = '%'; + escaped[1] = (high > 9 ? high - 10 + 'A' : high + '0'); + escaped[2] = (low > 9 ? low - 10 + 'A' : low + '0'); + + [ret appendCharacters: escaped + length: 3]; + } + } objc_autoreleasePoolPop(pool); - /* We don't care if it fails, as we only made it smaller. */ - if ((retCString2 = realloc(retCString, i + 1)) == NULL) - retCString2 = retCString; - - return [OFString stringWithUTF8StringNoCopy: retCString2 - length: i - freeWhenDone: true]; + return ret; } - (OFString *)stringByURLDecoding { void *pool = objc_autoreleasePoolPush(); Index: src/OFURL.h ================================================================== --- src/OFURL.h +++ src/OFURL.h @@ -13,10 +13,11 @@ * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this * file. */ #import "OFObject.h" +#import "OFCharacterSet.h" #import "OFSerialization.h" OF_ASSUME_NONNULL_BEGIN @class OFArray OF_GENERIC(ObjectType); @@ -237,9 +238,77 @@ */ + (instancetype)fileURLWithPath: (OFString *)path isDirectory: (bool)isDirectory; #endif @end + +@interface OFCharacterSet (URLCharacterSets) +#ifdef OF_HAVE_CLASS_PROPERTIES +@property (class, readonly, nonatomic) + OFCharacterSet *URLSchemeAllowedCharacterSet; +@property (class, readonly, nonatomic) + OFCharacterSet *URLHostAllowedCharacterSet; +@property (class, readonly, nonatomic) + OFCharacterSet *URLUserAllowedCharacterSet; +@property (class, readonly, nonatomic) + OFCharacterSet *URLPasswordAllowedCharacterSet; +@property (class, readonly, nonatomic) + OFCharacterSet *URLPathAllowedCharacterSet; +@property (class, readonly, nonatomic) + OFCharacterSet *URLQueryAllowedCharacterSet; +@property (class, readonly, nonatomic) + OFCharacterSet *URLFragmentAllowedCharacterSet; +#endif + +/*! + * @brief Returns the characters allowed in the scheme part of a URL. + * + * @return The characters allowed in the scheme part of a URL. + */ ++ (OFCharacterSet *)URLSchemeAllowedCharacterSet; + +/*! + * @brief Returns the characters allowed in the host part of a URL. + * + * @return The characters allowed in the host part of a URL. + */ ++ (OFCharacterSet *)URLHostAllowedCharacterSet; + +/*! + * @brief Returns the characters allowed in the user part of a URL. + * + * @return The characters allowed in the user part of a URL. + */ ++ (OFCharacterSet *)URLUserAllowedCharacterSet; + +/*! + * @brief Returns the characters allowed in the password part of a URL. + * + * @return The characters allowed in the password part of a URL. + */ ++ (OFCharacterSet *)URLPasswordAllowedCharacterSet; + +/*! + * @brief Returns the characters allowed in the path part of a URL. + * + * @return The characters allowed in the path part of a URL. + */ ++ (OFCharacterSet *)URLPathAllowedCharacterSet; + +/*! + * @brief Returns the characters allowed in the query part of a URL. + * + * @return The characters allowed in the query part of a URL. + */ ++ (OFCharacterSet *)URLQueryAllowedCharacterSet; + +/*! + * @brief Returns the characters allowed in the fragment part of a URL. + * + * @return The characters allowed in the fragment part of a URL. + */ ++ (OFCharacterSet *)URLFragmentAllowedCharacterSet; +@end OF_ASSUME_NONNULL_END #import "OFMutableURL.h" Index: src/OFURL.m ================================================================== --- src/OFURL.m +++ src/OFURL.m @@ -30,10 +30,257 @@ #import "OFXMLElement.h" #import "OFInvalidArgumentException.h" #import "OFInvalidFormatException.h" #import "OFOutOfMemoryException.h" + +static OFCharacterSet *URLAllowedCharacterSet = nil; +static OFCharacterSet *URLPathAllowedCharacterSet = nil; +static OFCharacterSet *URLQueryOrFragmentAllowedCharacterSet = nil; + +@interface OFCharacterSet_URLAllowed: OFCharacterSet ++ (OFCharacterSet *)URLAllowedCharacterSet; +@end + +@interface OFCharacterSet_URLPathAllowed: OFCharacterSet ++ (OFCharacterSet *)URLPathAllowedCharacterSet; +@end + +@interface OFCharacterSet_URLQueryOrFragmentAllowed: OFCharacterSet ++ (OFCharacterSet *)URLQueryOrFragmentAllowedCharacterSet; +@end + +@implementation OFCharacterSet_URLAllowed ++ (void)initialize +{ + if (self != [OFCharacterSet_URLAllowed class]) + return; + + URLAllowedCharacterSet = [[OFCharacterSet_URLAllowed alloc] init]; +} + ++ (OFCharacterSet *)URLAllowedCharacterSet +{ + return URLAllowedCharacterSet; +} + +- (instancetype)autorelease +{ + return self; +} + +- (instancetype)retain +{ + return self; +} + +- (void)release +{ +} + +- (unsigned int)retainCount +{ + return OF_RETAIN_COUNT_MAX; +} + +- (bool)characterIsMember: (of_unichar_t)character +{ + if (character < CHAR_MAX && of_ascii_isalnum(character)) + return true; + + switch (character) { + case '-': + case '.': + case '_': + case '~': + case '!': + case '$': + case '&': + case '\'': + case '(': + case ')': + case '*': + case '+': + case ',': + case ';': + case '=': + return true; + default: + return false; + } +} +@end + +@implementation OFCharacterSet_URLPathAllowed ++ (void)initialize +{ + if (self != [OFCharacterSet_URLPathAllowed class]) + return; + + URLPathAllowedCharacterSet = + [[OFCharacterSet_URLPathAllowed alloc] init]; +} + ++ (OFCharacterSet *)URLPathAllowedCharacterSet +{ + return URLPathAllowedCharacterSet; +} + +- (instancetype)autorelease +{ + return self; +} + +- (instancetype)retain +{ + return self; +} + +- (void)release +{ +} + +- (unsigned int)retainCount +{ + return OF_RETAIN_COUNT_MAX; +} + +- (bool)characterIsMember: (of_unichar_t)character +{ + if (character < CHAR_MAX && of_ascii_isalnum(character)) + return true; + + switch (character) { + case '-': + case '.': + case '_': + case '~': + case '!': + case '$': + case '&': + case '\'': + case '(': + case ')': + case '*': + case '+': + case ',': + case ';': + case '=': + case ':': + case '@': + case '/': + return true; + default: + return false; + } +} +@end + +@implementation OFCharacterSet_URLQueryOrFragmentAllowed ++ (void)initialize +{ + if (self != [OFCharacterSet_URLQueryOrFragmentAllowed class]) + return; + + URLQueryOrFragmentAllowedCharacterSet = + [[OFCharacterSet_URLQueryOrFragmentAllowed alloc] init]; +} + ++ (OFCharacterSet *)URLQueryOrFragmentAllowedCharacterSet +{ + return URLQueryOrFragmentAllowedCharacterSet; +} + +- (instancetype)autorelease +{ + return self; +} + +- (instancetype)retain +{ + return self; +} + +- (void)release +{ +} + +- (unsigned int)retainCount +{ + return OF_RETAIN_COUNT_MAX; +} + +- (bool)characterIsMember: (of_unichar_t)character +{ + if (character < CHAR_MAX && of_ascii_isalnum(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; + default: + return false; + } +} +@end + +@implementation OFCharacterSet (URLCharacterSets) ++ (OFCharacterSet *)URLSchemeAllowedCharacterSet +{ + return [OFCharacterSet_URLAllowed URLAllowedCharacterSet]; +} + ++ (OFCharacterSet *)URLHostAllowedCharacterSet +{ + return [OFCharacterSet_URLAllowed URLAllowedCharacterSet]; +} + ++ (OFCharacterSet *)URLUserAllowedCharacterSet +{ + return [OFCharacterSet_URLAllowed URLAllowedCharacterSet]; +} + ++ (OFCharacterSet *)URLPasswordAllowedCharacterSet +{ + return [OFCharacterSet_URLAllowed URLAllowedCharacterSet]; +} + ++ (OFCharacterSet *)URLPathAllowedCharacterSet +{ + return [OFCharacterSet_URLPathAllowed URLPathAllowedCharacterSet]; +} + ++ (OFCharacterSet *)URLQueryAllowedCharacterSet +{ + return [OFCharacterSet_URLQueryOrFragmentAllowed + URLQueryOrFragmentAllowedCharacterSet]; +} + ++ (OFCharacterSet *)URLFragmentAllowedCharacterSet +{ + return [OFCharacterSet_URLQueryOrFragmentAllowed + URLQueryOrFragmentAllowedCharacterSet]; +} +@end @implementation OFURL + (instancetype)URL { return [[[self alloc] init] autorelease]; @@ -399,21 +646,23 @@ return _scheme; } - (OFString *)URLEncodedScheme { - return [_scheme stringByURLEncoding]; + return [_scheme stringByURLEncodingWithAllowedCharacters: + [OFCharacterSet URLSchemeAllowedCharacterSet]]; } - (OFString *)host { return _host; } - (OFString *)URLEncodedHost { - return [_host stringByURLEncoding]; + return [_host stringByURLEncodingWithAllowedCharacters: + [OFCharacterSet URLHostAllowedCharacterSet]]; } - (OFNumber *)port { return _port; @@ -424,21 +673,23 @@ return _user; } - (OFString *)URLEncodedUser { - return [_user stringByURLEncoding]; + return [_user stringByURLEncodingWithAllowedCharacters: + [OFCharacterSet URLUserAllowedCharacterSet]]; } - (OFString *)password { return _password; } - (OFString *)URLEncodedPassword { - return [_password stringByURLEncoding]; + return [_password stringByURLEncodingWithAllowedCharacters: + [OFCharacterSet URLPasswordAllowedCharacterSet]]; } - (OFString *)path { return _path; @@ -445,11 +696,11 @@ } - (OFString *)URLEncodedPath { return [_path stringByURLEncodingWithAllowedCharacters: - "-._~!$&'()*+,;=:@/"]; + [OFCharacterSet URLPathAllowedCharacterSet]]; } - (OFArray *)pathComponents { return [_path componentsSeparatedByString: @"/"]; @@ -501,11 +752,11 @@ } - (OFString *)URLEncodedQuery { return [_query stringByURLEncodingWithAllowedCharacters: - "-._~!$&'()*+,;=:@/?"]; + [OFCharacterSet URLQueryAllowedCharacterSet]]; } - (OFString *)fragment { return _fragment; @@ -512,11 +763,11 @@ } - (OFString *)URLEncodedFragment { return [_fragment stringByURLEncodingWithAllowedCharacters: - "-._~!$&'()*+,;=:@/?"]; + [OFCharacterSet URLFragmentAllowedCharacterSet]]; } - (id)copy { return [self retain]; Index: tests/OFStringTests.m ================================================================== --- tests/OFStringTests.m +++ tests/OFStringTests.m @@ -21,10 +21,11 @@ #include #import "OFString.h" #import "OFMutableString_UTF8.h" #import "OFArray.h" +#import "OFCharacterSet.h" #import "OFURL.h" #import "OFAutoreleasePool.h" #import "OFInvalidArgumentException.h" #import "OFInvalidEncodingException.h" @@ -780,12 +781,13 @@ TEST(@"-[SHA512Hash]", [[C(@"asdfoobar") SHA512Hash] isEqual: @"0464c427da158b02161bb44a3090bbfc594611ef6a53603640454b56412a9247c" @"3579a329e53a5dc74676b106755e3394f9454a2d42273242615d32f80437d61"]) - TEST(@"-[stringByURLEncoding]", - [[C(@"foo\"ba'_~$]") stringByURLEncoding] + TEST(@"-[stringByURLEncodingWithAllowedCharacters:]", + [[C(@"foo\"ba'_~$]") stringByURLEncodingWithAllowedCharacters: + [OFCharacterSet URLPathAllowedCharacterSet]] isEqual: @"foo%22ba'_~$%5D"]) TEST(@"-[stringByURLDecoding]", [[C(@"foo%20bar%22+%24") stringByURLDecoding] isEqual: @"foo bar\"+$"])