From 277e1c8416d0bfceea7b9ae964807120233e022a Mon Sep 17 00:00:00 2001 From: Mike Abdullah Date: Tue, 23 Jul 2013 16:33:23 +0100 Subject: [PATCH 01/44] Lovely pair of APIs for retrieving URL query components --- KSURLComponents.h | 35 +++++++++ KSURLComponents.m | 65 ++++++++++++++++ TestKSFileUtilities/TestKSURLComponents.m | 91 +++++++++++++++++++++++ 3 files changed, 191 insertions(+) diff --git a/KSURLComponents.h b/KSURLComponents.h index 04e738a..5fd1382 100644 --- a/KSURLComponents.h +++ b/KSURLComponents.h @@ -78,4 +78,39 @@ @property (copy, readonly) NSString *percentEncodedFragment; +#pragma mark Query Parameters + +/** + Converts `.query` into a dictionary representation. + + http://example.com?key=value&foo=bar + + gives: + + @{ @"key" : @"value", @"foo" : @"bar" } + + Keys and values are percent decoded for your convenience. + + If you have a query which doesn't match `NSDictionary`'s design, drop down to + the primitive `-enumerateQueryParametersUsingBlock:` method instead. + + @return `nil` if query doesn't neatly fit an `NSDictionary` representation + */ +- (NSDictionary *)queryParameters; + +/** + Enumerates the parameters of `.query` + + Tolerates all forms of query: + + * Parameters without a value are reported as `nil` + * Parameters are reported in the order they appear in the URL + * Duplicate parameters are correctly reported too + + Keys and values are percent decoded for your convenience. + + @param A block called for each parameter of the query + */ +- (void)enumerateQueryParametersUsingBlock:(void (^)(NSString *key, NSString *value, BOOL *stop))block; + @end diff --git a/KSURLComponents.m b/KSURLComponents.m index b12a7b0..2fe7d0b 100644 --- a/KSURLComponents.m +++ b/KSURLComponents.m @@ -383,6 +383,71 @@ - (void)setFragment:(NSString *)fragment; CFRelease(escaped); } +#pragma mark Query Parameters + +- (NSDictionary *)queryParameters; +{ + __block NSMutableDictionary *result = [NSMutableDictionary dictionary]; + + [self enumerateQueryParametersUsingBlock:^(NSString *key, NSString *value, BOOL *stop) { + + // Bail if doesn't fit dictionary paradigm + if (!value || [result objectForKey:key]) + { + *stop = YES; + result = nil; + return; + } + + [result setObject:value forKey:key]; + }]; + + return result; +} + +- (void)enumerateQueryParametersUsingBlock:(void (^)(NSString *, NSString *, BOOL *))block; +{ + BOOL stop = NO; + + NSString *query = self.percentEncodedQuery; // we'll do our own decoding after separating components + NSRange searchRange = NSMakeRange(0, query.length); + + while (!stop) + { + NSRange keySeparatorRange = [query rangeOfString:@"=" options:NSLiteralSearch range:searchRange]; + if (keySeparatorRange.location == NSNotFound) keySeparatorRange = NSMakeRange(NSMaxRange(searchRange), 0); + + NSRange keyRange = NSMakeRange(searchRange.location, keySeparatorRange.location - searchRange.location); + NSString *key = [query substringWithRange:keyRange]; + + NSString *value = nil; + if (keySeparatorRange.length) // there might be no value, so report as nil + { + searchRange = NSMakeRange(NSMaxRange(keySeparatorRange), query.length - NSMaxRange(keySeparatorRange)); + + NSRange valueSeparatorRange = [query rangeOfString:@"&" options:NSLiteralSearch range:searchRange]; + if (valueSeparatorRange.location == NSNotFound) + { + valueSeparatorRange.location = NSMaxRange(searchRange); + stop = YES; + } + + NSRange valueRange = NSMakeRange(searchRange.location, valueSeparatorRange.location - searchRange.location); + value = [query substringWithRange:valueRange]; + + searchRange = NSMakeRange(NSMaxRange(valueSeparatorRange), query.length - NSMaxRange(valueSeparatorRange)); + } + else + { + stop = YES; + } + + block([key stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding], + [value stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding], + &stop); + } +} + #pragma mark Equality Testing - (BOOL)isEqual:(id)object; diff --git a/TestKSFileUtilities/TestKSURLComponents.m b/TestKSFileUtilities/TestKSURLComponents.m index b3c9c7a..20a9cfb 100644 --- a/TestKSFileUtilities/TestKSURLComponents.m +++ b/TestKSFileUtilities/TestKSURLComponents.m @@ -436,4 +436,95 @@ - (void)testCopying; [components2 release]; } +#pragma mark Query Parameters + +- (void)testNilQuery; +{ + KSURLComponents *components = [[KSURLComponents alloc] init]; + NSDictionary *parameters = [components queryParameters]; + + STAssertNil(parameters, nil); +} + +- (void)testEmptyQuery; +{ + KSURLComponents *components = [[KSURLComponents alloc] initWithString:@"scheme://host?"]; + + NSDictionary *parameters = [components queryParameters]; + STAssertNil(parameters, nil); + + __block BOOL blockCalled = NO; + [components enumerateQueryParametersUsingBlock:^(NSString *key, NSString *value, BOOL *stop) { + STAssertEqualObjects(key, @"", nil); + STAssertNil(value, nil); + blockCalled = YES; + }]; + STAssertTrue(blockCalled, nil); +} + +- (void)testNonParameterisedQuery; +{ + KSURLComponents *components = [[KSURLComponents alloc] initWithString:@"scheme://host?query"]; + + NSDictionary *parameters = [components queryParameters]; + STAssertNil(parameters, nil); + + __block BOOL blockCalled = NO; + [components enumerateQueryParametersUsingBlock:^(NSString *key, NSString *value, BOOL *stop) { + STAssertEqualObjects(key, @"query", nil); + STAssertNil(value, nil); + blockCalled = YES; + }]; + STAssertTrue(blockCalled, nil); +} + +- (void)testSingleQueryParameter; +{ + KSURLComponents *components = [[KSURLComponents alloc] initWithString:@"scheme://host?key=value"]; + + NSDictionary *parameters = [components queryParameters]; + STAssertEqualObjects(parameters, @{ @"key" : @"value" }, nil); +} + +- (void)testQueryParameters; +{ + KSURLComponents *components = [[KSURLComponents alloc] initWithString:@"scheme://host?key=value&foo=bar"]; + + NSDictionary *parameters = [components queryParameters]; + NSDictionary *expected = @{ @"key" : @"value", @"foo" : @"bar" }; + STAssertEqualObjects(parameters, expected, nil); +} + +- (void)testRepeatedKeys; +{ + KSURLComponents *components = [[KSURLComponents alloc] initWithString:@"scheme://host?key=value&key=value2"]; + + NSDictionary *parameters = [components queryParameters]; + STAssertNil(parameters, nil); + + __block int blockCalled = 0; + [components enumerateQueryParametersUsingBlock:^(NSString *key, NSString *value, BOOL *stop) { + STAssertEqualObjects(key, @"key", nil); + STAssertEqualObjects(value, (blockCalled ? @"value2" : @"value"), nil); + ++blockCalled; + }]; + STAssertEquals(blockCalled, 2, nil); +} + +- (void)testEqualsSignInQueryParameterValue; +{ + KSURLComponents *components = [[KSURLComponents alloc] initWithString:@"scheme://host?key=val=ue"]; + + NSDictionary *parameters = [components queryParameters]; + STAssertEqualObjects(parameters, @{ @"key" : @"val=ue" }, nil); +} + +- (void)testQueryParameterUnescaping; +{ + KSURLComponents *components = [[KSURLComponents alloc] initWithString:@"scheme://host?k%2Fy=va%2Fue"]; + + NSDictionary *parameters = [components queryParameters]; + STAssertEqualObjects(parameters, @{ @"k/y" : @"va/ue" }, nil); +} + @end From e2adee3cf82055be9ad84ab1f34856ae62a614b1 Mon Sep 17 00:00:00 2001 From: Mike Abdullah Date: Wed, 24 Jul 2013 11:46:20 +0100 Subject: [PATCH 02/44] Test empty keys and values --- TestKSFileUtilities/TestKSURLComponents.m | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/TestKSFileUtilities/TestKSURLComponents.m b/TestKSFileUtilities/TestKSURLComponents.m index 66d0090..79b9ae0 100644 --- a/TestKSFileUtilities/TestKSURLComponents.m +++ b/TestKSFileUtilities/TestKSURLComponents.m @@ -505,6 +505,22 @@ - (void)testQueryParameters; STAssertEqualObjects(parameters, expected, nil); } +- (void)testEmptyQueryParameterKey; +{ + KSURLComponents *components = [[KSURLComponents alloc] initWithString:@"scheme://host?=value"]; + + NSDictionary *parameters = [components queryParameters]; + STAssertEqualObjects(parameters, @{ @"" : @"value" }, nil); +} + +- (void)testEmptyQueryParameterValue; +{ + KSURLComponents *components = [[KSURLComponents alloc] initWithString:@"scheme://host?key="]; + + NSDictionary *parameters = [components queryParameters]; + STAssertEqualObjects(parameters, @{ @"key" : @"" }, nil); +} + - (void)testRepeatedKeys; { KSURLComponents *components = [[KSURLComponents alloc] initWithString:@"scheme://host?key=value&key=value2"]; From 169b7afbdffdaa24fe7aeca42200f387ecb09a5a Mon Sep 17 00:00:00 2001 From: Mike Abdullah Date: Wed, 24 Jul 2013 11:48:20 +0100 Subject: [PATCH 03/44] Redeclare queryParameters as a @property --- KSURLComponents.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KSURLComponents.h b/KSURLComponents.h index 5fd1382..8b16927 100644 --- a/KSURLComponents.h +++ b/KSURLComponents.h @@ -96,7 +96,7 @@ @return `nil` if query doesn't neatly fit an `NSDictionary` representation */ -- (NSDictionary *)queryParameters; +@property (readonly) NSDictionary *queryParameters; /** Enumerates the parameters of `.query` From 3a292f88c71c07ff1c5792c6b5ec26533650324e Mon Sep 17 00:00:00 2001 From: Mike Abdullah Date: Wed, 24 Jul 2013 12:10:08 +0100 Subject: [PATCH 04/44] Make .queryParameters readwrite --- KSURLComponents.h | 10 +++-- KSURLComponents.m | 50 +++++++++++++++++++++++ TestKSFileUtilities/TestKSURLComponents.m | 7 ++++ 3 files changed, 63 insertions(+), 4 deletions(-) diff --git a/KSURLComponents.h b/KSURLComponents.h index 8b16927..5a449bc 100644 --- a/KSURLComponents.h +++ b/KSURLComponents.h @@ -81,22 +81,24 @@ #pragma mark Query Parameters /** - Converts `.query` into a dictionary representation. + Converts between `.query` strings and their dictionary representation. + + For example: http://example.com?key=value&foo=bar - gives: + can be interpreted as: @{ @"key" : @"value", @"foo" : @"bar" } - Keys and values are percent decoded for your convenience. + and vice versa. Keys and values are percent decoded for your convenience. If you have a query which doesn't match `NSDictionary`'s design, drop down to the primitive `-enumerateQueryParametersUsingBlock:` method instead. @return `nil` if query doesn't neatly fit an `NSDictionary` representation */ -@property (readonly) NSDictionary *queryParameters; +@property (copy) NSDictionary *queryParameters; /** Enumerates the parameters of `.query` diff --git a/KSURLComponents.m b/KSURLComponents.m index 0e6d66c..cc6541f 100644 --- a/KSURLComponents.m +++ b/KSURLComponents.m @@ -426,6 +426,56 @@ - (NSDictionary *)queryParameters; return result; } +- (void)setQueryParameters:(NSDictionary *)parameters; +{ + if (!parameters) + { + self.query = nil; + return; + } + + // Build the list of parameters as a string + NSMutableString *query = [NSMutableString string]; + + NSEnumerator *enumerator = [parameters keyEnumerator]; + BOOL thisIsTheFirstParameter = YES; + + NSString *key; + while ((key = [enumerator nextObject])) + { + CFStringRef escapedKey = CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)key, NULL, CFSTR("+=&#"), kCFStringEncodingUTF8); + // Escape + for safety as some backends interpret it as a space + // = indicates the start of value, so must be escaped + // & indicates the start of next parameter, so must be escaped + // # indicates the start of fragment, so must be escaped + + NSString *parameter = [parameters objectForKey:key]; + + // Append the parameter and its key to the full query string + if (!thisIsTheFirstParameter) + { + [query appendString:@"&"]; + } + else + { + thisIsTheFirstParameter = NO; + } + + CFStringRef escapedValue = CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)parameter, NULL, CFSTR("+&#"), kCFStringEncodingUTF8); + // Escape + for safety as some backends interpret it as a space + // = is allowed in values, as there's no further value to indicate + // & indicates the start of next parameter, so must be escaped + // # indicates the start of fragment, so must be escaped + + [query appendFormat:@"%@=%@", escapedKey, escapedValue]; + + CFRelease(escapedKey); + CFRelease(escapedValue); + } + + self.percentEncodedQuery = query; +} + - (void)enumerateQueryParametersUsingBlock:(void (^)(NSString *, NSString *, BOOL *))block; { BOOL stop = NO; diff --git a/TestKSFileUtilities/TestKSURLComponents.m b/TestKSFileUtilities/TestKSURLComponents.m index 79b9ae0..5ef9afc 100644 --- a/TestKSFileUtilities/TestKSURLComponents.m +++ b/TestKSFileUtilities/TestKSURLComponents.m @@ -553,4 +553,11 @@ - (void)testQueryParameterUnescaping; STAssertEqualObjects(parameters, @{ @"k/y" : @"va/ue" }, nil); } +- (void)testNilQueryParameters; +{ + KSURLComponents *components = [[KSURLComponents alloc] init]; + components.queryParameters = nil; + STAssertEquals(components.percentEncodedQuery, nil, nil); +} + @end From 2e84a046750443423d0906b3f08a3d127d9c3663 Mon Sep 17 00:00:00 2001 From: Mike Abdullah Date: Wed, 24 Jul 2013 12:24:11 +0100 Subject: [PATCH 05/44] Flesh out query encoding tests --- TestKSFileUtilities/TestKSURLComponents.m | 32 +++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/TestKSFileUtilities/TestKSURLComponents.m b/TestKSFileUtilities/TestKSURLComponents.m index 5ef9afc..165f884 100644 --- a/TestKSFileUtilities/TestKSURLComponents.m +++ b/TestKSFileUtilities/TestKSURLComponents.m @@ -553,11 +553,39 @@ - (void)testQueryParameterUnescaping; STAssertEqualObjects(parameters, @{ @"k/y" : @"va/ue" }, nil); } -- (void)testNilQueryParameters; +- (void)testEncodeNilQueryParameters; { KSURLComponents *components = [[KSURLComponents alloc] init]; components.queryParameters = nil; - STAssertEquals(components.percentEncodedQuery, nil, nil); + STAssertNil(components.percentEncodedQuery, nil); +} + +- (void)testEncodeEmptyQueryParameters; +{ + KSURLComponents *components = [[KSURLComponents alloc] init]; + components.queryParameters = @{ }; + STAssertEqualObjects(components.percentEncodedQuery, @"", nil); +} + +- (void)testEncodeQueryParameter; +{ + KSURLComponents *components = [[KSURLComponents alloc] init]; + components.queryParameters = @{ @"key" : @"value" }; + STAssertEqualObjects(components.percentEncodedQuery, @"key=value", nil); +} + +- (void)testEncodeQueryParameters; +{ + KSURLComponents *components = [[KSURLComponents alloc] init]; + components.queryParameters = @{ @"key" : @"value", @"key2" : @"value2" }; + STAssertEqualObjects(components.percentEncodedQuery, @"key=value&key2=value2", nil); +} + +- (void)testEncodeQueryParameterEscaping; +{ + KSURLComponents *components = [[KSURLComponents alloc] init]; + components.queryParameters = @{ @"!*'();:@&=+$,/?#[]" : @"!*'();:@&=+$,/?#[]" }; + STAssertEqualObjects(components.percentEncodedQuery, @"!*'();:@%26%3D%2B$,/?%23%5B%5D=!*'();:@%26=%2B$,/?%23%5B%5D", nil); } @end From 8f8aaeb5c78ee2beb30edde61ba7260b4afb9ee9 Mon Sep 17 00:00:00 2001 From: Mike Abdullah Date: Tue, 15 Oct 2013 11:47:40 +0100 Subject: [PATCH 06/44] Introduce KSURLComponentsQueryParameterDecodingOptions --- KSURLComponents.h | 10 ++++++++-- KSURLComponents.m | 10 ++++++++-- TestKSFileUtilities/TestKSURLComponents.m | 6 +++--- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/KSURLComponents.h b/KSURLComponents.h index 5a449bc..4f5c790 100644 --- a/KSURLComponents.h +++ b/KSURLComponents.h @@ -26,6 +26,7 @@ #import + @interface KSURLComponents : NSObject { @private @@ -80,6 +81,10 @@ #pragma mark Query Parameters +typedef NS_OPTIONS(NSUInteger, KSURLComponentsQueryParameterDecodingOptions) { + KSURLComponentsQueryParameterDecodingPlusAsSpace = 1UL << 0, // + characters are interpreted as spaces, rather than regular + symbols +}; + /** Converts between `.query` strings and their dictionary representation. @@ -111,8 +116,9 @@ Keys and values are percent decoded for your convenience. - @param A block called for each parameter of the query + @param options A mask that specifies options for parameter decoding. + @param block A block called for each parameter of the query. */ -- (void)enumerateQueryParametersUsingBlock:(void (^)(NSString *key, NSString *value, BOOL *stop))block; +- (void)enumerateQueryParametersWithOptions:(KSURLComponentsQueryParameterDecodingOptions)options usingBlock:(void (^)(NSString *key, NSString *value, BOOL *stop))block; @end diff --git a/KSURLComponents.m b/KSURLComponents.m index 01d0dd2..1d39d6b 100644 --- a/KSURLComponents.m +++ b/KSURLComponents.m @@ -446,7 +446,7 @@ - (NSDictionary *)queryParameters; { __block NSMutableDictionary *result = [NSMutableDictionary dictionary]; - [self enumerateQueryParametersUsingBlock:^(NSString *key, NSString *value, BOOL *stop) { + [self enumerateQueryParametersWithOptions:0 usingBlock:^(NSString *key, NSString *value, BOOL *stop) { // Bail if doesn't fit dictionary paradigm if (!value || [result objectForKey:key]) @@ -512,7 +512,7 @@ - (void)setQueryParameters:(NSDictionary *)parameters; self.percentEncodedQuery = query; } -- (void)enumerateQueryParametersUsingBlock:(void (^)(NSString *, NSString *, BOOL *))block; +- (void)enumerateQueryParametersWithOptions:(KSURLComponentsQueryParameterDecodingOptions)options usingBlock:(void (^)(NSString *key, NSString *value, BOOL *stop))block; { BOOL stop = NO; @@ -549,6 +549,12 @@ - (void)enumerateQueryParametersUsingBlock:(void (^)(NSString *, NSString *, BOO stop = YES; } + if (options & KSURLComponentsQueryParameterDecodingPlusAsSpace) + { + key = [key stringByReplacingOccurrencesOfString:@"+" withString:@" "]; + value = [key stringByReplacingOccurrencesOfString:@"+" withString:@" "]; + } + block([key stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding], [value stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding], &stop); diff --git a/TestKSFileUtilities/TestKSURLComponents.m b/TestKSFileUtilities/TestKSURLComponents.m index 165f884..ef22412 100644 --- a/TestKSFileUtilities/TestKSURLComponents.m +++ b/TestKSFileUtilities/TestKSURLComponents.m @@ -464,7 +464,7 @@ - (void)testEmptyQuery; STAssertNil(parameters, nil); __block BOOL blockCalled = NO; - [components enumerateQueryParametersUsingBlock:^(NSString *key, NSString *value, BOOL *stop) { + [components enumerateQueryParametersWithOptions:0 usingBlock:^(NSString *key, NSString *value, BOOL *stop) { STAssertEqualObjects(key, @"", nil); STAssertNil(value, nil); blockCalled = YES; @@ -480,7 +480,7 @@ - (void)testNonParameterisedQuery; STAssertNil(parameters, nil); __block BOOL blockCalled = NO; - [components enumerateQueryParametersUsingBlock:^(NSString *key, NSString *value, BOOL *stop) { + [components enumerateQueryParametersWithOptions:0 usingBlock:^(NSString *key, NSString *value, BOOL *stop) { STAssertEqualObjects(key, @"query", nil); STAssertNil(value, nil); blockCalled = YES; @@ -529,7 +529,7 @@ - (void)testRepeatedKeys; STAssertNil(parameters, nil); __block int blockCalled = 0; - [components enumerateQueryParametersUsingBlock:^(NSString *key, NSString *value, BOOL *stop) { + [components enumerateQueryParametersWithOptions:0 usingBlock:^(NSString *key, NSString *value, BOOL *stop) { STAssertEqualObjects(key, @"key", nil); STAssertEqualObjects(value, (blockCalled ? @"value2" : @"value"), nil); ++blockCalled; From ca18a68f4ccdbc71324e159740a80609942e6dc8 Mon Sep 17 00:00:00 2001 From: Mike Abdullah Date: Tue, 15 Oct 2013 11:48:11 +0100 Subject: [PATCH 07/44] Enumeration block shouldn't be null --- KSURLComponents.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KSURLComponents.h b/KSURLComponents.h index 4f5c790..6ca8566 100644 --- a/KSURLComponents.h +++ b/KSURLComponents.h @@ -119,6 +119,6 @@ typedef NS_OPTIONS(NSUInteger, KSURLComponentsQueryParameterDecodingOptions) { @param options A mask that specifies options for parameter decoding. @param block A block called for each parameter of the query. */ -- (void)enumerateQueryParametersWithOptions:(KSURLComponentsQueryParameterDecodingOptions)options usingBlock:(void (^)(NSString *key, NSString *value, BOOL *stop))block; +- (void)enumerateQueryParametersWithOptions:(KSURLComponentsQueryParameterDecodingOptions)options usingBlock:(void (^)(NSString *key, NSString *value, BOOL *stop))block __attribute((nonnull(2))); @end From b7ddc9a7de800fff9403d992ed80edfbc1ba5f15 Mon Sep 17 00:00:00 2001 From: Mike Abdullah Date: Tue, 15 Oct 2013 11:58:05 +0100 Subject: [PATCH 08/44] Break apart .queryParameters property to take options too --- KSURLComponents.h | 25 ++++++++++++++++--- KSURLComponents.m | 6 ++--- TestKSFileUtilities/TestKSURLComponents.m | 30 +++++++++++------------ 3 files changed, 40 insertions(+), 21 deletions(-) diff --git a/KSURLComponents.h b/KSURLComponents.h index 6ca8566..1ded3d0 100644 --- a/KSURLComponents.h +++ b/KSURLComponents.h @@ -96,14 +96,33 @@ typedef NS_OPTIONS(NSUInteger, KSURLComponentsQueryParameterDecodingOptions) { @{ @"key" : @"value", @"foo" : @"bar" } - and vice versa. Keys and values are percent decoded for your convenience. + Keys and values are percent decoded according to `options`. If you have a query which doesn't match `NSDictionary`'s design, drop down to - the primitive `-enumerateQueryParametersUsingBlock:` method instead. + the primitive `-enumerateQueryParametersWithOptions:usingBlock:` method instead. + @param options A mask that specifies options for parameter decoding. @return `nil` if query doesn't neatly fit an `NSDictionary` representation */ -@property (copy) NSDictionary *queryParameters; +- (NSDictionary *)queryParametersWithOptions:(KSURLComponentsQueryParameterDecodingOptions)options; + +/** + Converts between `.query` strings and their dictionary representation. + + For example: + + @{ @"key" : @"value", @"foo" : @"bar" } + + can be represented as: + + http://example.com?key=value&foo=bar + + Keys and values are percent encoded according to `options`. + + @param parameters A dictionary to encode, whose keys and values are all strings. + @param options A mask that specifies options for parameter decoding. Pass `0` for now. + */ +- (void)setQueryParameters:(NSDictionary *)parameters options:(NSUInteger)options; /** Enumerates the parameters of `.query` diff --git a/KSURLComponents.m b/KSURLComponents.m index 1d39d6b..4a3f795 100644 --- a/KSURLComponents.m +++ b/KSURLComponents.m @@ -442,11 +442,11 @@ - (void)setFragment:(NSString *)fragment; #pragma mark Query Parameters -- (NSDictionary *)queryParameters; +- (NSDictionary *)queryParametersWithOptions:(KSURLComponentsQueryParameterDecodingOptions)options; { __block NSMutableDictionary *result = [NSMutableDictionary dictionary]; - [self enumerateQueryParametersWithOptions:0 usingBlock:^(NSString *key, NSString *value, BOOL *stop) { + [self enumerateQueryParametersWithOptions:options usingBlock:^(NSString *key, NSString *value, BOOL *stop) { // Bail if doesn't fit dictionary paradigm if (!value || [result objectForKey:key]) @@ -462,7 +462,7 @@ - (NSDictionary *)queryParameters; return result; } -- (void)setQueryParameters:(NSDictionary *)parameters; +- (void)setQueryParameters:(NSDictionary *)parameters options:(NSUInteger)options; { if (!parameters) { diff --git a/TestKSFileUtilities/TestKSURLComponents.m b/TestKSFileUtilities/TestKSURLComponents.m index ef22412..249c38c 100644 --- a/TestKSFileUtilities/TestKSURLComponents.m +++ b/TestKSFileUtilities/TestKSURLComponents.m @@ -451,7 +451,7 @@ - (void)testCopying; - (void)testNilQuery; { KSURLComponents *components = [[KSURLComponents alloc] init]; - NSDictionary *parameters = [components queryParameters]; + NSDictionary *parameters = [components queryParametersWithOptions:0]; STAssertNil(parameters, nil); } @@ -460,7 +460,7 @@ - (void)testEmptyQuery; { KSURLComponents *components = [[KSURLComponents alloc] initWithString:@"scheme://host?"]; - NSDictionary *parameters = [components queryParameters]; + NSDictionary *parameters = [components queryParametersWithOptions:0]; STAssertNil(parameters, nil); __block BOOL blockCalled = NO; @@ -476,7 +476,7 @@ - (void)testNonParameterisedQuery; { KSURLComponents *components = [[KSURLComponents alloc] initWithString:@"scheme://host?query"]; - NSDictionary *parameters = [components queryParameters]; + NSDictionary *parameters = [components queryParametersWithOptions:0]; STAssertNil(parameters, nil); __block BOOL blockCalled = NO; @@ -492,7 +492,7 @@ - (void)testSingleQueryParameter; { KSURLComponents *components = [[KSURLComponents alloc] initWithString:@"scheme://host?key=value"]; - NSDictionary *parameters = [components queryParameters]; + NSDictionary *parameters = [components queryParametersWithOptions:0]; STAssertEqualObjects(parameters, @{ @"key" : @"value" }, nil); } @@ -500,7 +500,7 @@ - (void)testQueryParameters; { KSURLComponents *components = [[KSURLComponents alloc] initWithString:@"scheme://host?key=value&foo=bar"]; - NSDictionary *parameters = [components queryParameters]; + NSDictionary *parameters = [components queryParametersWithOptions:0]; NSDictionary *expected = @{ @"key" : @"value", @"foo" : @"bar" }; STAssertEqualObjects(parameters, expected, nil); } @@ -509,7 +509,7 @@ - (void)testEmptyQueryParameterKey; { KSURLComponents *components = [[KSURLComponents alloc] initWithString:@"scheme://host?=value"]; - NSDictionary *parameters = [components queryParameters]; + NSDictionary *parameters = [components queryParametersWithOptions:0]; STAssertEqualObjects(parameters, @{ @"" : @"value" }, nil); } @@ -517,7 +517,7 @@ - (void)testEmptyQueryParameterValue; { KSURLComponents *components = [[KSURLComponents alloc] initWithString:@"scheme://host?key="]; - NSDictionary *parameters = [components queryParameters]; + NSDictionary *parameters = [components queryParametersWithOptions:0]; STAssertEqualObjects(parameters, @{ @"key" : @"" }, nil); } @@ -525,7 +525,7 @@ - (void)testRepeatedKeys; { KSURLComponents *components = [[KSURLComponents alloc] initWithString:@"scheme://host?key=value&key=value2"]; - NSDictionary *parameters = [components queryParameters]; + NSDictionary *parameters = [components queryParametersWithOptions:0]; STAssertNil(parameters, nil); __block int blockCalled = 0; @@ -541,7 +541,7 @@ - (void)testEqualsSignInQueryParameterValue; { KSURLComponents *components = [[KSURLComponents alloc] initWithString:@"scheme://host?key=val=ue"]; - NSDictionary *parameters = [components queryParameters]; + NSDictionary *parameters = [components queryParametersWithOptions:0]; STAssertEqualObjects(parameters, @{ @"key" : @"val=ue" }, nil); } @@ -549,42 +549,42 @@ - (void)testQueryParameterUnescaping; { KSURLComponents *components = [[KSURLComponents alloc] initWithString:@"scheme://host?k%2Fy=va%2Fue"]; - NSDictionary *parameters = [components queryParameters]; + NSDictionary *parameters = [components queryParametersWithOptions:0]; STAssertEqualObjects(parameters, @{ @"k/y" : @"va/ue" }, nil); } - (void)testEncodeNilQueryParameters; { KSURLComponents *components = [[KSURLComponents alloc] init]; - components.queryParameters = nil; + [components setQueryParameters:nil options:0]; STAssertNil(components.percentEncodedQuery, nil); } - (void)testEncodeEmptyQueryParameters; { KSURLComponents *components = [[KSURLComponents alloc] init]; - components.queryParameters = @{ }; + [components setQueryParameters:@{ } options:0]; STAssertEqualObjects(components.percentEncodedQuery, @"", nil); } - (void)testEncodeQueryParameter; { KSURLComponents *components = [[KSURLComponents alloc] init]; - components.queryParameters = @{ @"key" : @"value" }; + [components setQueryParameters:@{ @"key" : @"value" } options:0]; STAssertEqualObjects(components.percentEncodedQuery, @"key=value", nil); } - (void)testEncodeQueryParameters; { KSURLComponents *components = [[KSURLComponents alloc] init]; - components.queryParameters = @{ @"key" : @"value", @"key2" : @"value2" }; + [components setQueryParameters:@{ @"key" : @"value", @"key2" : @"value2" } options:0]; STAssertEqualObjects(components.percentEncodedQuery, @"key=value&key2=value2", nil); } - (void)testEncodeQueryParameterEscaping; { KSURLComponents *components = [[KSURLComponents alloc] init]; - components.queryParameters = @{ @"!*'();:@&=+$,/?#[]" : @"!*'();:@&=+$,/?#[]" }; + [components setQueryParameters:@{ @"!*'();:@&=+$,/?#[]" : @"!*'();:@&=+$,/?#[]" } options:0]; STAssertEqualObjects(components.percentEncodedQuery, @"!*'();:@%26%3D%2B$,/?%23%5B%5D=!*'();:@%26=%2B$,/?%23%5B%5D", nil); } From aee62646b9f5667d38a4df6f8d8c703dec80bac2 Mon Sep 17 00:00:00 2001 From: Mike Abdullah Date: Tue, 15 Oct 2013 12:03:35 +0100 Subject: [PATCH 09/44] Test + as space decoding option --- TestKSFileUtilities/TestKSURLComponents.m | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/TestKSFileUtilities/TestKSURLComponents.m b/TestKSFileUtilities/TestKSURLComponents.m index 249c38c..bf91bb9 100644 --- a/TestKSFileUtilities/TestKSURLComponents.m +++ b/TestKSFileUtilities/TestKSURLComponents.m @@ -553,6 +553,17 @@ - (void)testQueryParameterUnescaping; STAssertEqualObjects(parameters, @{ @"k/y" : @"va/ue" }, nil); } +- (void)testPlusSymbolInQueryParameters; +{ + KSURLComponents *components = [KSURLComponents componentsWithString:@"?size=%7B64%2C+64%7D"]; + + NSDictionary *parameters = [components queryParametersWithOptions:0]; + STAssertEqualObjects(parameters, @{ @"size" : @"{64,+64}" }, nil); + + parameters = [components queryParametersWithOptions:KSURLComponentsQueryParameterDecodingPlusAsSpace]; + STAssertEqualObjects(parameters, @{ @"size" : @"{64, 64}" }, nil); +} + - (void)testEncodeNilQueryParameters; { KSURLComponents *components = [[KSURLComponents alloc] init]; From 014f612fb261caab1cfe5a254b4b2f0baacfa10f Mon Sep 17 00:00:00 2001 From: Mike Abdullah Date: Tue, 15 Oct 2013 12:03:56 +0100 Subject: [PATCH 10/44] Fix copy & paste mistake Value was being transposed to key --- KSURLComponents.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KSURLComponents.m b/KSURLComponents.m index 4a3f795..1f242f8 100644 --- a/KSURLComponents.m +++ b/KSURLComponents.m @@ -552,7 +552,7 @@ - (void)enumerateQueryParametersWithOptions:(KSURLComponentsQueryParameterDecodi if (options & KSURLComponentsQueryParameterDecodingPlusAsSpace) { key = [key stringByReplacingOccurrencesOfString:@"+" withString:@" "]; - value = [key stringByReplacingOccurrencesOfString:@"+" withString:@" "]; + value = [value stringByReplacingOccurrencesOfString:@"+" withString:@" "]; } block([key stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding], From 9e66e3517155dfc46196aa86395ad79c92e44a35 Mon Sep 17 00:00:00 2001 From: Mike Abdullah Date: Tue, 15 Oct 2013 12:18:20 +0100 Subject: [PATCH 11/44] Remove options argument from query parameter setter for now --- KSURLComponents.h | 3 +-- KSURLComponents.m | 2 +- TestKSFileUtilities/TestKSURLComponents.m | 10 +++++----- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/KSURLComponents.h b/KSURLComponents.h index 1ded3d0..a526f79 100644 --- a/KSURLComponents.h +++ b/KSURLComponents.h @@ -120,9 +120,8 @@ typedef NS_OPTIONS(NSUInteger, KSURLComponentsQueryParameterDecodingOptions) { Keys and values are percent encoded according to `options`. @param parameters A dictionary to encode, whose keys and values are all strings. - @param options A mask that specifies options for parameter decoding. Pass `0` for now. */ -- (void)setQueryParameters:(NSDictionary *)parameters options:(NSUInteger)options; +- (void)setQueryParameters:(NSDictionary *)parameters; /** Enumerates the parameters of `.query` diff --git a/KSURLComponents.m b/KSURLComponents.m index 1f242f8..491a66c 100644 --- a/KSURLComponents.m +++ b/KSURLComponents.m @@ -462,7 +462,7 @@ - (NSDictionary *)queryParametersWithOptions:(KSURLComponentsQueryParameterDecod return result; } -- (void)setQueryParameters:(NSDictionary *)parameters options:(NSUInteger)options; +- (void)setQueryParameters:(NSDictionary *)parameters; { if (!parameters) { diff --git a/TestKSFileUtilities/TestKSURLComponents.m b/TestKSFileUtilities/TestKSURLComponents.m index bf91bb9..b66a454 100644 --- a/TestKSFileUtilities/TestKSURLComponents.m +++ b/TestKSFileUtilities/TestKSURLComponents.m @@ -567,35 +567,35 @@ - (void)testPlusSymbolInQueryParameters; - (void)testEncodeNilQueryParameters; { KSURLComponents *components = [[KSURLComponents alloc] init]; - [components setQueryParameters:nil options:0]; + [components setQueryParameters:nil]; STAssertNil(components.percentEncodedQuery, nil); } - (void)testEncodeEmptyQueryParameters; { KSURLComponents *components = [[KSURLComponents alloc] init]; - [components setQueryParameters:@{ } options:0]; + [components setQueryParameters:@{ }]; STAssertEqualObjects(components.percentEncodedQuery, @"", nil); } - (void)testEncodeQueryParameter; { KSURLComponents *components = [[KSURLComponents alloc] init]; - [components setQueryParameters:@{ @"key" : @"value" } options:0]; + [components setQueryParameters:@{ @"key" : @"value" }]; STAssertEqualObjects(components.percentEncodedQuery, @"key=value", nil); } - (void)testEncodeQueryParameters; { KSURLComponents *components = [[KSURLComponents alloc] init]; - [components setQueryParameters:@{ @"key" : @"value", @"key2" : @"value2" } options:0]; + [components setQueryParameters:@{ @"key" : @"value", @"key2" : @"value2" }]; STAssertEqualObjects(components.percentEncodedQuery, @"key=value&key2=value2", nil); } - (void)testEncodeQueryParameterEscaping; { KSURLComponents *components = [[KSURLComponents alloc] init]; - [components setQueryParameters:@{ @"!*'();:@&=+$,/?#[]" : @"!*'();:@&=+$,/?#[]" } options:0]; + [components setQueryParameters:@{ @"!*'();:@&=+$,/?#[]" : @"!*'();:@&=+$,/?#[]" }]; STAssertEqualObjects(components.percentEncodedQuery, @"!*'();:@%26%3D%2B$,/?%23%5B%5D=!*'();:@%26=%2B$,/?%23%5B%5D", nil); } From 461ab896eccf63bce995969236fcbf5e4149d3bc Mon Sep 17 00:00:00 2001 From: Mike Abdullah Date: Tue, 15 Oct 2013 12:39:09 +0100 Subject: [PATCH 12/44] Bring KSMailtoURLs into the test target --- KSFileUtilities.xcodeproj/project.pbxproj | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/KSFileUtilities.xcodeproj/project.pbxproj b/KSFileUtilities.xcodeproj/project.pbxproj index 77e41c0..653a493 100644 --- a/KSFileUtilities.xcodeproj/project.pbxproj +++ b/KSFileUtilities.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 27246F0F165BF184001B638E /* NSURL+IFUnicodeURL.m in Sources */ = {isa = PBXBuildFile; fileRef = 27246F0E165BF184001B638E /* NSURL+IFUnicodeURL.m */; }; + 27422A79180D609B00136F43 /* KSMailtoURLs.m in Sources */ = {isa = PBXBuildFile; fileRef = 27422A78180D609B00136F43 /* KSMailtoURLs.m */; }; 2757532C16AEA86500388503 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2757532B16AEA86500388503 /* WebKit.framework */; }; 27A60C0E1536E7F500EEE538 /* TestKSURLFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 27A60C0D1536E7F500EEE538 /* TestKSURLFormatter.m */; }; 27A900FD165BFFD800D23C4B /* toxxx.c in Sources */ = {isa = PBXBuildFile; fileRef = 27A900FB165BFFD800D23C4B /* toxxx.c */; }; @@ -35,6 +36,8 @@ /* Begin PBXFileReference section */ 27246F0D165BF184001B638E /* NSURL+IFUnicodeURL.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSURL+IFUnicodeURL.h"; path = "IFUnicodeURL/IFUnicodeURL/NSURL+IFUnicodeURL.h"; sourceTree = SOURCE_ROOT; }; 27246F0E165BF184001B638E /* NSURL+IFUnicodeURL.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSURL+IFUnicodeURL.m"; path = "IFUnicodeURL/IFUnicodeURL/NSURL+IFUnicodeURL.m"; sourceTree = SOURCE_ROOT; }; + 27422A77180D609B00136F43 /* KSMailtoURLs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KSMailtoURLs.h; sourceTree = SOURCE_ROOT; }; + 27422A78180D609B00136F43 /* KSMailtoURLs.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KSMailtoURLs.m; sourceTree = SOURCE_ROOT; }; 2757532B16AEA86500388503 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; 27A60C0C1536E7F500EEE538 /* TestKSURLFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestKSURLFormatter.h; sourceTree = ""; }; 27A60C0D1536E7F500EEE538 /* TestKSURLFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TestKSURLFormatter.m; sourceTree = ""; }; @@ -120,6 +123,8 @@ EE43C84613898D7B008ABD16 /* KSURLFormatter.m */, 27F0501A178829180019FC07 /* KSURLComponents.h */, 27F0501B178829180019FC07 /* KSURLComponents.m */, + 27422A77180D609B00136F43 /* KSMailtoURLs.h */, + 27422A78180D609B00136F43 /* KSMailtoURLs.m */, 27246F10165BF1D2001B638E /* IFUnicodeURL */, 27F86FBD164FBA9D003608CC /* KSEncodeURLString.h */, 27F86FBE164FBA9D003608CC /* KSEncodeURLString.m */, @@ -301,6 +306,7 @@ 27246F0F165BF184001B638E /* NSURL+IFUnicodeURL.m in Sources */, 27A900FD165BFFD800D23C4B /* toxxx.c in Sources */, 27A90100165C000B00D23C4B /* util.c in Sources */, + 27422A79180D609B00136F43 /* KSMailtoURLs.m in Sources */, 27A90103165C003A00D23C4B /* nameprep.c in Sources */, 27A90106165C005700D23C4B /* puny.c in Sources */, ); From 4432ec78aa82bcc3ca9a14e916f111fdf61914da Mon Sep 17 00:00:00 2001 From: Mike Abdullah Date: Tue, 15 Oct 2013 17:21:59 +0100 Subject: [PATCH 13/44] Remove reference to options argument --- KSURLComponents.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KSURLComponents.h b/KSURLComponents.h index 3541f03..a26b326 100644 --- a/KSURLComponents.h +++ b/KSURLComponents.h @@ -165,7 +165,7 @@ typedef NS_OPTIONS(NSUInteger, KSURLComponentsQueryParameterDecodingOptions) { http://example.com?key=value&foo=bar - Keys and values are percent encoded according to `options`. + Keys and values are percent encoded. @param parameters A dictionary to encode, whose keys and values are all strings. */ From 7cb488dc06ac42e37686595317632bbe615dfb89 Mon Sep 17 00:00:00 2001 From: Mike Abdullah Date: Mon, 9 Dec 2013 11:26:03 +0000 Subject: [PATCH 14/44] Make query handling a category Thanks @KeithDuncan for the suggestion --- KSURLComponents.h | 4 +- KSURLComponents.m | 167 ++++++++++++++++++++++++---------------------- 2 files changed, 89 insertions(+), 82 deletions(-) diff --git a/KSURLComponents.h b/KSURLComponents.h index a26b326..9972452 100644 --- a/KSURLComponents.h +++ b/KSURLComponents.h @@ -126,8 +126,10 @@ @property (nonatomic, copy, readonly) NSString *percentEncodedQuery; @property (nonatomic, copy, readonly) NSString *percentEncodedFragment; +@end + -#pragma mark Query Parameters +@interface KSURLComponents (KSQueryParameters) typedef NS_OPTIONS(NSUInteger, KSURLComponentsQueryParameterDecodingOptions) { KSURLComponentsQueryParameterDecodingPlusAsSpace = 1UL << 0, // + characters are interpreted as spaces, rather than regular + symbols diff --git a/KSURLComponents.m b/KSURLComponents.m index 1e5ae37..7c7f86d 100644 --- a/KSURLComponents.m +++ b/KSURLComponents.m @@ -482,6 +482,92 @@ - (void)setFragment:(NSString *)fragment; CFRelease(escaped); } +#pragma mark Equality Testing + +- (BOOL)isEqual:(id)object; +{ + if (![object isKindOfClass:[KSURLComponents class]]) return NO; + + NSString *myScheme = self.scheme; + NSString *otherScheme = [object scheme]; + if (myScheme != otherScheme && ![myScheme isEqualToString:otherScheme]) return NO; + + NSString *myUser = self.percentEncodedUser; + NSString *otherUser = [object percentEncodedUser]; + if (myUser != otherUser && ![myUser isEqualToString:otherUser]) return NO; + + NSString *myPassword = self.percentEncodedPassword; + NSString *otherPassword = [object percentEncodedPassword]; + if (myPassword != otherPassword && ![myPassword isEqualToString:otherPassword]) return NO; + + NSString *myHost = self.percentEncodedHost; + NSString *otherHost = [object percentEncodedHost]; + if (myHost != otherHost && ![myHost isEqualToString:otherHost]) return NO; + + NSNumber *myPort = self.port; + NSNumber *otherPort = [(KSURLComponents *)object port]; + if (myPort != otherPort && ![myPort isEqualToNumber:otherPort]) return NO; + + NSString *myPath = self.percentEncodedPath; + NSString *otherPath = [object percentEncodedPath]; + if (myPath != otherPath && ![myPath isEqualToString:otherPath]) return NO; + + NSString *myQuery = self.percentEncodedQuery; + NSString *otherQuery = [object percentEncodedQuery]; + if (myQuery != otherQuery && ![myQuery isEqualToString:otherQuery]) return NO; + + NSString *myFragment = self.percentEncodedFragment; + NSString *otherFragment = [object percentEncodedFragment]; + if (myFragment != otherFragment && ![myFragment isEqualToString:otherFragment]) return NO; + + return YES; +} + +- (NSUInteger)hash; +{ + // This could definitely be a better algorithm! + return self.scheme.hash + self.percentEncodedUser.hash + self.percentEncodedPassword.hash + self.percentEncodedPassword.hash + self.percentEncodedHost.hash + self.port.hash + self.percentEncodedPath.hash + self.percentEncodedQuery.hash + self.percentEncodedFragment.hash; +} + +#pragma mark NSCopying + +- (id)copyWithZone:(NSZone *)zone; +{ + KSURLComponents *result = [[KSURLComponents alloc] init]; + + result.scheme = self.scheme; + result.percentEncodedUser = self.percentEncodedUser; + result.percentEncodedPassword = self.percentEncodedPassword; + result.percentEncodedHost = self.percentEncodedHost; + result.port = self.port; + result.percentEncodedPath = self.percentEncodedPath; + result.percentEncodedQuery = self.percentEncodedQuery; + result.percentEncodedFragment = self.percentEncodedFragment; + + return result; +} + +#pragma mark Debugging + +- (NSString *)description; +{ + return [[super description] stringByAppendingFormat: + @" {scheme = %@, user = %@, password = %@, host = %@, port = %@, path = %@, query = %@, fragment = %@}", + self.scheme, + self.percentEncodedUser, + self.percentEncodedPassword, + self.percentEncodedHost, + self.port, + self.percentEncodedPath, + self.percentEncodedQuery, + self.percentEncodedFragment]; +} + +@end + + +@implementation KSURLComponents (KSQueryParameters) + #pragma mark Query Parameters - (NSDictionary *)queryParametersWithOptions:(KSURLComponentsQueryParameterDecodingOptions)options; @@ -603,85 +689,4 @@ - (void)enumerateQueryParametersWithOptions:(KSURLComponentsQueryParameterDecodi } } -#pragma mark Equality Testing - -- (BOOL)isEqual:(id)object; -{ - if (![object isKindOfClass:[KSURLComponents class]]) return NO; - - NSString *myScheme = self.scheme; - NSString *otherScheme = [object scheme]; - if (myScheme != otherScheme && ![myScheme isEqualToString:otherScheme]) return NO; - - NSString *myUser = self.percentEncodedUser; - NSString *otherUser = [object percentEncodedUser]; - if (myUser != otherUser && ![myUser isEqualToString:otherUser]) return NO; - - NSString *myPassword = self.percentEncodedPassword; - NSString *otherPassword = [object percentEncodedPassword]; - if (myPassword != otherPassword && ![myPassword isEqualToString:otherPassword]) return NO; - - NSString *myHost = self.percentEncodedHost; - NSString *otherHost = [object percentEncodedHost]; - if (myHost != otherHost && ![myHost isEqualToString:otherHost]) return NO; - - NSNumber *myPort = self.port; - NSNumber *otherPort = [(KSURLComponents *)object port]; - if (myPort != otherPort && ![myPort isEqualToNumber:otherPort]) return NO; - - NSString *myPath = self.percentEncodedPath; - NSString *otherPath = [object percentEncodedPath]; - if (myPath != otherPath && ![myPath isEqualToString:otherPath]) return NO; - - NSString *myQuery = self.percentEncodedQuery; - NSString *otherQuery = [object percentEncodedQuery]; - if (myQuery != otherQuery && ![myQuery isEqualToString:otherQuery]) return NO; - - NSString *myFragment = self.percentEncodedFragment; - NSString *otherFragment = [object percentEncodedFragment]; - if (myFragment != otherFragment && ![myFragment isEqualToString:otherFragment]) return NO; - - return YES; -} - -- (NSUInteger)hash; -{ - // This could definitely be a better algorithm! - return self.scheme.hash + self.percentEncodedUser.hash + self.percentEncodedPassword.hash + self.percentEncodedPassword.hash + self.percentEncodedHost.hash + self.port.hash + self.percentEncodedPath.hash + self.percentEncodedQuery.hash + self.percentEncodedFragment.hash; -} - -#pragma mark NSCopying - -- (id)copyWithZone:(NSZone *)zone; -{ - KSURLComponents *result = [[KSURLComponents alloc] init]; - - result.scheme = self.scheme; - result.percentEncodedUser = self.percentEncodedUser; - result.percentEncodedPassword = self.percentEncodedPassword; - result.percentEncodedHost = self.percentEncodedHost; - result.port = self.port; - result.percentEncodedPath = self.percentEncodedPath; - result.percentEncodedQuery = self.percentEncodedQuery; - result.percentEncodedFragment = self.percentEncodedFragment; - - return result; -} - -#pragma mark Debugging - -- (NSString *)description; -{ - return [[super description] stringByAppendingFormat: - @" {scheme = %@, user = %@, password = %@, host = %@, port = %@, path = %@, query = %@, fragment = %@}", - self.scheme, - self.percentEncodedUser, - self.percentEncodedPassword, - self.percentEncodedHost, - self.port, - self.percentEncodedPath, - self.percentEncodedQuery, - self.percentEncodedFragment]; -} - @end From e276c8551d82bebe54ff0636889c780845f96af0 Mon Sep 17 00:00:00 2001 From: Mike Abdullah Date: Thu, 12 Dec 2013 10:02:28 +0000 Subject: [PATCH 15/44] remove redundant #prama mark --- KSURLComponents.m | 2 -- 1 file changed, 2 deletions(-) diff --git a/KSURLComponents.m b/KSURLComponents.m index 7c7f86d..b80d466 100644 --- a/KSURLComponents.m +++ b/KSURLComponents.m @@ -568,8 +568,6 @@ - (NSString *)description; @implementation KSURLComponents (KSQueryParameters) -#pragma mark Query Parameters - - (NSDictionary *)queryParametersWithOptions:(KSURLComponentsQueryParameterDecodingOptions)options; { __block NSMutableDictionary *result = [NSMutableDictionary dictionary]; From bfa162ad04cbd5973010909c48571f5da5bc9664 Mon Sep 17 00:00:00 2001 From: Mike Abdullah Date: Thu, 12 Dec 2013 10:12:11 +0000 Subject: [PATCH 16/44] Try a KSURLQuery class --- KSFileUtilities.xcodeproj/project.pbxproj | 6 + KSURLQuery.h | 31 ++++ KSURLQuery.m | 170 ++++++++++++++++++++++ 3 files changed, 207 insertions(+) create mode 100644 KSURLQuery.h create mode 100644 KSURLQuery.m diff --git a/KSFileUtilities.xcodeproj/project.pbxproj b/KSFileUtilities.xcodeproj/project.pbxproj index 5398ed5..16f7508 100644 --- a/KSFileUtilities.xcodeproj/project.pbxproj +++ b/KSFileUtilities.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ 27A90103165C003A00D23C4B /* nameprep.c in Sources */ = {isa = PBXBuildFile; fileRef = 27A90101165C003A00D23C4B /* nameprep.c */; }; 27A90106165C005700D23C4B /* puny.c in Sources */ = {isa = PBXBuildFile; fileRef = 27A90104165C005600D23C4B /* puny.c */; }; 27DA2577148E641100209E50 /* TestKSURLUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 27DA2576148E641100209E50 /* TestKSURLUtilities.m */; }; + 27DC29041859BC2C0089D717 /* KSURLQuery.m in Sources */ = {isa = PBXBuildFile; fileRef = 27DC29031859BC2C0089D717 /* KSURLQuery.m */; }; 27F0501C178829220019FC07 /* KSURLComponents.m in Sources */ = {isa = PBXBuildFile; fileRef = 27F0501B178829180019FC07 /* KSURLComponents.m */; }; 27F0501E1788399E0019FC07 /* TestKSURLComponents.m in Sources */ = {isa = PBXBuildFile; fileRef = 27F0501D1788399E0019FC07 /* TestKSURLComponents.m */; }; 27F86FBF164FBA9D003608CC /* KSEncodeURLString.m in Sources */ = {isa = PBXBuildFile; fileRef = 27F86FBE164FBA9D003608CC /* KSEncodeURLString.m */; }; @@ -50,6 +51,8 @@ 27A90104165C005600D23C4B /* puny.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = puny.c; path = IFUnicodeURL/IDNSDK/puny.c; sourceTree = ""; }; 27A90105165C005700D23C4B /* puny.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = puny.h; path = IFUnicodeURL/IDNSDK/puny.h; sourceTree = ""; }; 27DA2576148E641100209E50 /* TestKSURLUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TestKSURLUtilities.m; sourceTree = ""; }; + 27DC29021859BC2C0089D717 /* KSURLQuery.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KSURLQuery.h; sourceTree = SOURCE_ROOT; }; + 27DC29031859BC2C0089D717 /* KSURLQuery.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KSURLQuery.m; sourceTree = SOURCE_ROOT; }; 27F0501A178829180019FC07 /* KSURLComponents.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KSURLComponents.h; sourceTree = SOURCE_ROOT; }; 27F0501B178829180019FC07 /* KSURLComponents.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KSURLComponents.m; sourceTree = SOURCE_ROOT; }; 27F0501D1788399E0019FC07 /* TestKSURLComponents.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TestKSURLComponents.m; sourceTree = ""; }; @@ -124,6 +127,8 @@ 27246F10165BF1D2001B638E /* IFUnicodeURL */, 27F0501A178829180019FC07 /* KSURLComponents.h */, 27F0501B178829180019FC07 /* KSURLComponents.m */, + 27DC29021859BC2C0089D717 /* KSURLQuery.h */, + 27DC29031859BC2C0089D717 /* KSURLQuery.m */, 27422A77180D609B00136F43 /* KSMailtoURLs.h */, 27422A78180D609B00136F43 /* KSMailtoURLs.m */, 27F86FBD164FBA9D003608CC /* KSEncodeURLString.h */, @@ -296,6 +301,7 @@ EE43C85213898D7B008ABD16 /* KSWebLocation.m in Sources */, EE43C85313898D7B008ABD16 /* KSWebLocationPasteboardUtilities.m in Sources */, 27F0501E1788399E0019FC07 /* TestKSURLComponents.m in Sources */, + 27DC29041859BC2C0089D717 /* KSURLQuery.m in Sources */, 27F0501C178829220019FC07 /* KSURLComponents.m in Sources */, EE43C85413898D7B008ABD16 /* KSWorkspaceUtilities.m in Sources */, 27DA2577148E641100209E50 /* TestKSURLUtilities.m in Sources */, diff --git a/KSURLQuery.h b/KSURLQuery.h new file mode 100644 index 0000000..3633e57 --- /dev/null +++ b/KSURLQuery.h @@ -0,0 +1,31 @@ +// +// KSURLQuery.h +// KSFileUtilities +// +// Created by Mike on 12/12/2013. +// Copyright (c) 2013 Karelia Software. All rights reserved. +// + +#import + +@interface KSURLQuery : NSObject +{ + @private + NSString *_percentEncodedString; +} + ++ (instancetype)queryWithURL:(NSURL *)url; ++ (instancetype)queryWithPercentEncodedString:(NSString *)percentEncodedQuery; + +@property(nonatomic, readonly, copy) NSString *percentEncodedString; + +typedef NS_OPTIONS(NSUInteger, KSURLQueryParameterDecodingOptions) { + KSURLQueryParameterDecodingPlusAsSpace = 1UL << 0, // + characters are interpreted as spaces, rather than regular + symbols +}; + +- (NSDictionary *)parametersWithOptions:(KSURLQueryParameterDecodingOptions)options; +- (void)enumerateParametersWithOptions:(KSURLQueryParameterDecodingOptions)options usingBlock:(void (^)(NSString *key, NSString *value, BOOL *stop))block __attribute((nonnull(2))); + +- (void)setParameters:(NSDictionary *)parameters; + +@end diff --git a/KSURLQuery.m b/KSURLQuery.m new file mode 100644 index 0000000..2f1775c --- /dev/null +++ b/KSURLQuery.m @@ -0,0 +1,170 @@ +// +// KSURLQuery.m +// KSFileUtilities +// +// Created by Mike on 12/12/2013. +// Copyright (c) 2013 Karelia Software. All rights reserved. +// + +#import "KSURLQuery.h" + +#import "KSURLComponents.h" + + +@interface KSURLQuery () +@property(nonatomic, readwrite, copy) NSString *percentEncodedString; +@end + + +@implementation KSURLQuery + ++ (instancetype)queryWithURL:(NSURL *)url; +{ + KSURLComponents *components = [[KSURLComponents alloc] initWithURL:url resolvingAgainstBaseURL:YES]; + KSURLQuery *result = [self queryWithPercentEncodedString:components.percentEncodedQuery]; + [components release]; + return result; +} + ++ (instancetype)queryWithPercentEncodedString:(NSString *)percentEncodedQuery; +{ + return [[[self alloc] initWithPercentEncodedString:percentEncodedQuery] autorelease]; +} + +- initWithPercentEncodedString:(NSString *)string; +{ + if (self = [self init]) + { + _percentEncodedString = [string copy]; + } + return self; +} + +- (void)dealloc +{ + [_percentEncodedString release]; + [super dealloc]; +} + +@synthesize percentEncodedString = _percentEncodedString; + +- (NSDictionary *)parametersWithOptions:(KSURLQueryParameterDecodingOptions)options; +{ + __block NSMutableDictionary *result = [NSMutableDictionary dictionary]; + + [self enumerateParametersWithOptions:options usingBlock:^(NSString *key, NSString *value, BOOL *stop) { + + // Bail if doesn't fit dictionary paradigm + if (!value || [result objectForKey:key]) + { + *stop = YES; + result = nil; + return; + } + + [result setObject:value forKey:key]; + }]; + + return result; +} + +- (void)enumerateParametersWithOptions:(KSURLQueryParameterDecodingOptions)options usingBlock:(void (^)(NSString *, NSString *, BOOL *))block; +{ + BOOL stop = NO; + + NSString *query = self.percentEncodedString; // we'll do our own decoding after separating components + NSRange searchRange = NSMakeRange(0, query.length); + + while (!stop) + { + NSRange keySeparatorRange = [query rangeOfString:@"=" options:NSLiteralSearch range:searchRange]; + if (keySeparatorRange.location == NSNotFound) keySeparatorRange = NSMakeRange(NSMaxRange(searchRange), 0); + + NSRange keyRange = NSMakeRange(searchRange.location, keySeparatorRange.location - searchRange.location); + NSString *key = [query substringWithRange:keyRange]; + + NSString *value = nil; + if (keySeparatorRange.length) // there might be no value, so report as nil + { + searchRange = NSMakeRange(NSMaxRange(keySeparatorRange), query.length - NSMaxRange(keySeparatorRange)); + + NSRange valueSeparatorRange = [query rangeOfString:@"&" options:NSLiteralSearch range:searchRange]; + if (valueSeparatorRange.location == NSNotFound) + { + valueSeparatorRange.location = NSMaxRange(searchRange); + stop = YES; + } + + NSRange valueRange = NSMakeRange(searchRange.location, valueSeparatorRange.location - searchRange.location); + value = [query substringWithRange:valueRange]; + + searchRange = NSMakeRange(NSMaxRange(valueSeparatorRange), query.length - NSMaxRange(valueSeparatorRange)); + } + else + { + stop = YES; + } + + if (options & KSURLComponentsQueryParameterDecodingPlusAsSpace) + { + key = [key stringByReplacingOccurrencesOfString:@"+" withString:@" "]; + value = [value stringByReplacingOccurrencesOfString:@"+" withString:@" "]; + } + + block([key stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding], + [value stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding], + &stop); + } +} + +- (void)setParameters:(NSDictionary *)parameters; +{ + if (!parameters) + { + self.percentEncodedString = nil; + return; + } + + // Build the list of parameters as a string + NSMutableString *query = [NSMutableString string]; + + NSEnumerator *enumerator = [parameters keyEnumerator]; + BOOL thisIsTheFirstParameter = YES; + + NSString *key; + while ((key = [enumerator nextObject])) + { + CFStringRef escapedKey = CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)key, NULL, CFSTR("+=&#"), kCFStringEncodingUTF8); + // Escape + for safety as some backends interpret it as a space + // = indicates the start of value, so must be escaped + // & indicates the start of next parameter, so must be escaped + // # indicates the start of fragment, so must be escaped + + NSString *parameter = [parameters objectForKey:key]; + + // Append the parameter and its key to the full query string + if (!thisIsTheFirstParameter) + { + [query appendString:@"&"]; + } + else + { + thisIsTheFirstParameter = NO; + } + + CFStringRef escapedValue = CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)parameter, NULL, CFSTR("+&#"), kCFStringEncodingUTF8); + // Escape + for safety as some backends interpret it as a space + // = is allowed in values, as there's no further value to indicate + // & indicates the start of next parameter, so must be escaped + // # indicates the start of fragment, so must be escaped + + [query appendFormat:@"%@=%@", escapedKey, escapedValue]; + + CFRelease(escapedKey); + CFRelease(escapedValue); + } + + self.percentEncodedString = query; +} + +@end From ad69038e7b02c1a10a765dbd3f3c350b68d00b50 Mon Sep 17 00:00:00 2001 From: Mike Abdullah Date: Thu, 12 Dec 2013 10:12:39 +0000 Subject: [PATCH 17/44] Internally hand off KSURLComponents' query parameter handling to KSURLQuery --- KSURLComponents.m | 115 ++++------------------------------------------ 1 file changed, 8 insertions(+), 107 deletions(-) diff --git a/KSURLComponents.m b/KSURLComponents.m index b80d466..6149a7a 100644 --- a/KSURLComponents.m +++ b/KSURLComponents.m @@ -26,6 +26,8 @@ #import "KSURLComponents.h" +#import "KSURLQuery.h" + @interface KSURLComponents () @property (nonatomic, copy, readwrite) NSString *percentEncodedUser; @@ -570,121 +572,20 @@ @implementation KSURLComponents (KSQueryParameters) - (NSDictionary *)queryParametersWithOptions:(KSURLComponentsQueryParameterDecodingOptions)options; { - __block NSMutableDictionary *result = [NSMutableDictionary dictionary]; - - [self enumerateQueryParametersWithOptions:options usingBlock:^(NSString *key, NSString *value, BOOL *stop) { - - // Bail if doesn't fit dictionary paradigm - if (!value || [result objectForKey:key]) - { - *stop = YES; - result = nil; - return; - } - - [result setObject:value forKey:key]; - }]; - - return result; + return [[KSURLQuery queryWithPercentEncodedString:self.percentEncodedQuery] parametersWithOptions:options]; } - (void)setQueryParameters:(NSDictionary *)parameters; { - if (!parameters) - { - self.query = nil; - return; - } - - // Build the list of parameters as a string - NSMutableString *query = [NSMutableString string]; - - NSEnumerator *enumerator = [parameters keyEnumerator]; - BOOL thisIsTheFirstParameter = YES; - - NSString *key; - while ((key = [enumerator nextObject])) - { - CFStringRef escapedKey = CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)key, NULL, CFSTR("+=&#"), kCFStringEncodingUTF8); - // Escape + for safety as some backends interpret it as a space - // = indicates the start of value, so must be escaped - // & indicates the start of next parameter, so must be escaped - // # indicates the start of fragment, so must be escaped - - NSString *parameter = [parameters objectForKey:key]; - - // Append the parameter and its key to the full query string - if (!thisIsTheFirstParameter) - { - [query appendString:@"&"]; - } - else - { - thisIsTheFirstParameter = NO; - } - - CFStringRef escapedValue = CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)parameter, NULL, CFSTR("+&#"), kCFStringEncodingUTF8); - // Escape + for safety as some backends interpret it as a space - // = is allowed in values, as there's no further value to indicate - // & indicates the start of next parameter, so must be escaped - // # indicates the start of fragment, so must be escaped - - [query appendFormat:@"%@=%@", escapedKey, escapedValue]; - - CFRelease(escapedKey); - CFRelease(escapedValue); - } - - self.percentEncodedQuery = query; + KSURLQuery *query = [[KSURLQuery alloc] init]; + [query setParameters:parameters]; + self.percentEncodedQuery = query.percentEncodedString; + [query release]; } - (void)enumerateQueryParametersWithOptions:(KSURLComponentsQueryParameterDecodingOptions)options usingBlock:(void (^)(NSString *key, NSString *value, BOOL *stop))block; { - BOOL stop = NO; - - NSString *query = self.percentEncodedQuery; // we'll do our own decoding after separating components - NSRange searchRange = NSMakeRange(0, query.length); - - while (!stop) - { - NSRange keySeparatorRange = [query rangeOfString:@"=" options:NSLiteralSearch range:searchRange]; - if (keySeparatorRange.location == NSNotFound) keySeparatorRange = NSMakeRange(NSMaxRange(searchRange), 0); - - NSRange keyRange = NSMakeRange(searchRange.location, keySeparatorRange.location - searchRange.location); - NSString *key = [query substringWithRange:keyRange]; - - NSString *value = nil; - if (keySeparatorRange.length) // there might be no value, so report as nil - { - searchRange = NSMakeRange(NSMaxRange(keySeparatorRange), query.length - NSMaxRange(keySeparatorRange)); - - NSRange valueSeparatorRange = [query rangeOfString:@"&" options:NSLiteralSearch range:searchRange]; - if (valueSeparatorRange.location == NSNotFound) - { - valueSeparatorRange.location = NSMaxRange(searchRange); - stop = YES; - } - - NSRange valueRange = NSMakeRange(searchRange.location, valueSeparatorRange.location - searchRange.location); - value = [query substringWithRange:valueRange]; - - searchRange = NSMakeRange(NSMaxRange(valueSeparatorRange), query.length - NSMaxRange(valueSeparatorRange)); - } - else - { - stop = YES; - } - - if (options & KSURLComponentsQueryParameterDecodingPlusAsSpace) - { - key = [key stringByReplacingOccurrencesOfString:@"+" withString:@" "]; - value = [value stringByReplacingOccurrencesOfString:@"+" withString:@" "]; - } - - block([key stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding], - [value stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding], - &stop); - } + [[KSURLQuery queryWithPercentEncodedString:self.percentEncodedQuery] enumerateParametersWithOptions:options usingBlock:block]; } @end From 2b0b2487e3cef72db746177614bff5a855bb4a56 Mon Sep 17 00:00:00 2001 From: Mike Abdullah Date: Thu, 12 Dec 2013 10:21:08 +0000 Subject: [PATCH 18/44] Note logic behind resolving --- KSURLQuery.m | 1 + 1 file changed, 1 insertion(+) diff --git a/KSURLQuery.m b/KSURLQuery.m index 2f1775c..9dddf12 100644 --- a/KSURLQuery.m +++ b/KSURLQuery.m @@ -20,6 +20,7 @@ @implementation KSURLQuery + (instancetype)queryWithURL:(NSURL *)url; { + // Always resolve, since unlike paths there's no way for two queries to be in some way concatenated KSURLComponents *components = [[KSURLComponents alloc] initWithURL:url resolvingAgainstBaseURL:YES]; KSURLQuery *result = [self queryWithPercentEncodedString:components.percentEncodedQuery]; [components release]; From 5d7c90774934e87239f0b9305ffee4680aea9346 Mon Sep 17 00:00:00 2001 From: Mike Abdullah Date: Thu, 12 Dec 2013 10:25:00 +0000 Subject: [PATCH 19/44] Use correct constant --- KSURLQuery.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KSURLQuery.m b/KSURLQuery.m index 9dddf12..49e9bcf 100644 --- a/KSURLQuery.m +++ b/KSURLQuery.m @@ -106,7 +106,7 @@ - (void)enumerateParametersWithOptions:(KSURLQueryParameterDecodingOptions)optio stop = YES; } - if (options & KSURLComponentsQueryParameterDecodingPlusAsSpace) + if (options & KSURLQueryParameterDecodingPlusAsSpace) { key = [key stringByReplacingOccurrencesOfString:@"+" withString:@" "]; value = [value stringByReplacingOccurrencesOfString:@"+" withString:@" "]; From ff59e75cae048b30cd37b6587f21168e8c93ee8f Mon Sep 17 00:00:00 2001 From: Mike Abdullah Date: Thu, 12 Dec 2013 10:26:53 +0000 Subject: [PATCH 20/44] Switch over to CFURL for query retrieval instead of KSURLComponents --- KSURLQuery.m | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/KSURLQuery.m b/KSURLQuery.m index 49e9bcf..33cbd6d 100644 --- a/KSURLQuery.m +++ b/KSURLQuery.m @@ -8,8 +8,6 @@ #import "KSURLQuery.h" -#import "KSURLComponents.h" - @interface KSURLQuery () @property(nonatomic, readwrite, copy) NSString *percentEncodedString; @@ -21,9 +19,14 @@ @implementation KSURLQuery + (instancetype)queryWithURL:(NSURL *)url; { // Always resolve, since unlike paths there's no way for two queries to be in some way concatenated - KSURLComponents *components = [[KSURLComponents alloc] initWithURL:url resolvingAgainstBaseURL:YES]; - KSURLQuery *result = [self queryWithPercentEncodedString:components.percentEncodedQuery]; - [components release]; + CFURLRef cfURL = CFURLCopyAbsoluteURL((CFURLRef)url); + + NSString *string = (NSString *)CFURLCopyQueryString(cfURL, + NULL); // leave unescaped + + KSURLQuery *result = [self queryWithPercentEncodedString:string]; + [string release]; + CFRelease(cfURL); return result; } From b368f47d2b771b7df86ca0fa3316cf29ea22bba0 Mon Sep 17 00:00:00 2001 From: Mike Abdullah Date: Thu, 12 Dec 2013 10:48:46 +0000 Subject: [PATCH 21/44] Shift tests over to KSURLQuery --- TestKSFileUtilities/TestKSURLComponents.m | 83 ++++++++++++----------- 1 file changed, 42 insertions(+), 41 deletions(-) diff --git a/TestKSFileUtilities/TestKSURLComponents.m b/TestKSFileUtilities/TestKSURLComponents.m index 29c7af6..9eaa97c 100644 --- a/TestKSFileUtilities/TestKSURLComponents.m +++ b/TestKSFileUtilities/TestKSURLComponents.m @@ -9,6 +9,7 @@ #import #import "KSURLComponents.h" +#import "KSURLQuery.h" @interface TestKSURLComponents : SenTestCase @@ -669,21 +670,21 @@ - (void)testCopying; - (void)testNilQuery; { - KSURLComponents *components = [[KSURLComponents alloc] init]; - NSDictionary *parameters = [components queryParametersWithOptions:0]; + KSURLQuery *query = [[KSURLQuery alloc] init]; + NSDictionary *parameters = [query parametersWithOptions:0]; STAssertNil(parameters, nil); } - (void)testEmptyQuery; { - KSURLComponents *components = [[KSURLComponents alloc] initWithString:@"scheme://host?"]; + KSURLQuery *query = [KSURLQuery queryWithURL:[NSURL URLWithString:@"scheme://host?"]]; - NSDictionary *parameters = [components queryParametersWithOptions:0]; + NSDictionary *parameters = [query parametersWithOptions:0]; STAssertNil(parameters, nil); __block BOOL blockCalled = NO; - [components enumerateQueryParametersWithOptions:0 usingBlock:^(NSString *key, NSString *value, BOOL *stop) { + [query enumerateParametersWithOptions:0 usingBlock:^(NSString *key, NSString *value, BOOL *stop) { STAssertEqualObjects(key, @"", nil); STAssertNil(value, nil); blockCalled = YES; @@ -693,13 +694,13 @@ - (void)testEmptyQuery; - (void)testNonParameterisedQuery; { - KSURLComponents *components = [[KSURLComponents alloc] initWithString:@"scheme://host?query"]; + KSURLQuery *query = [KSURLQuery queryWithURL:[NSURL URLWithString:@"scheme://host?query"]]; - NSDictionary *parameters = [components queryParametersWithOptions:0]; + NSDictionary *parameters = [query parametersWithOptions:0]; STAssertNil(parameters, nil); __block BOOL blockCalled = NO; - [components enumerateQueryParametersWithOptions:0 usingBlock:^(NSString *key, NSString *value, BOOL *stop) { + [query enumerateParametersWithOptions:0 usingBlock:^(NSString *key, NSString *value, BOOL *stop) { STAssertEqualObjects(key, @"query", nil); STAssertNil(value, nil); blockCalled = YES; @@ -709,46 +710,46 @@ - (void)testNonParameterisedQuery; - (void)testSingleQueryParameter; { - KSURLComponents *components = [[KSURLComponents alloc] initWithString:@"scheme://host?key=value"]; + KSURLQuery *query = [KSURLQuery queryWithURL:[NSURL URLWithString:@"scheme://host?key=value"]]; - NSDictionary *parameters = [components queryParametersWithOptions:0]; + NSDictionary *parameters = [query parametersWithOptions:0]; STAssertEqualObjects(parameters, @{ @"key" : @"value" }, nil); } - (void)testQueryParameters; { - KSURLComponents *components = [[KSURLComponents alloc] initWithString:@"scheme://host?key=value&foo=bar"]; + KSURLQuery *query = [KSURLQuery queryWithURL:[NSURL URLWithString:@"scheme://host?key=value&foo=bar"]]; - NSDictionary *parameters = [components queryParametersWithOptions:0]; + NSDictionary *parameters = [query parametersWithOptions:0]; NSDictionary *expected = @{ @"key" : @"value", @"foo" : @"bar" }; STAssertEqualObjects(parameters, expected, nil); } - (void)testEmptyQueryParameterKey; { - KSURLComponents *components = [[KSURLComponents alloc] initWithString:@"scheme://host?=value"]; + KSURLQuery *query = [KSURLQuery queryWithURL:[NSURL URLWithString:@"scheme://host?=value"]]; - NSDictionary *parameters = [components queryParametersWithOptions:0]; + NSDictionary *parameters = [query parametersWithOptions:0]; STAssertEqualObjects(parameters, @{ @"" : @"value" }, nil); } - (void)testEmptyQueryParameterValue; { - KSURLComponents *components = [[KSURLComponents alloc] initWithString:@"scheme://host?key="]; + KSURLQuery *query = [KSURLQuery queryWithURL:[NSURL URLWithString:@"scheme://host?key="]]; - NSDictionary *parameters = [components queryParametersWithOptions:0]; + NSDictionary *parameters = [query parametersWithOptions:0]; STAssertEqualObjects(parameters, @{ @"key" : @"" }, nil); } - (void)testRepeatedKeys; { - KSURLComponents *components = [[KSURLComponents alloc] initWithString:@"scheme://host?key=value&key=value2"]; + KSURLQuery *query = [KSURLQuery queryWithURL:[NSURL URLWithString:@"scheme://host?key=value&key=value2"]]; - NSDictionary *parameters = [components queryParametersWithOptions:0]; + NSDictionary *parameters = [query parametersWithOptions:0]; STAssertNil(parameters, nil); __block int blockCalled = 0; - [components enumerateQueryParametersWithOptions:0 usingBlock:^(NSString *key, NSString *value, BOOL *stop) { + [query enumerateParametersWithOptions:0 usingBlock:^(NSString *key, NSString *value, BOOL *stop) { STAssertEqualObjects(key, @"key", nil); STAssertEqualObjects(value, (blockCalled ? @"value2" : @"value"), nil); ++blockCalled; @@ -758,64 +759,64 @@ - (void)testRepeatedKeys; - (void)testEqualsSignInQueryParameterValue; { - KSURLComponents *components = [[KSURLComponents alloc] initWithString:@"scheme://host?key=val=ue"]; + KSURLQuery *query = [KSURLQuery queryWithURL:[NSURL URLWithString:@"scheme://host?key=val=ue"]]; - NSDictionary *parameters = [components queryParametersWithOptions:0]; + NSDictionary *parameters = [query parametersWithOptions:0]; STAssertEqualObjects(parameters, @{ @"key" : @"val=ue" }, nil); } - (void)testQueryParameterUnescaping; { - KSURLComponents *components = [[KSURLComponents alloc] initWithString:@"scheme://host?k%2Fy=va%2Fue"]; + KSURLQuery *query = [KSURLQuery queryWithURL:[NSURL URLWithString:@"scheme://host?k%2Fy=va%2Fue"]]; - NSDictionary *parameters = [components queryParametersWithOptions:0]; + NSDictionary *parameters = [query parametersWithOptions:0]; STAssertEqualObjects(parameters, @{ @"k/y" : @"va/ue" }, nil); } - (void)testPlusSymbolInQueryParameters; { - KSURLComponents *components = [KSURLComponents componentsWithString:@"?size=%7B64%2C+64%7D"]; + KSURLQuery *query = [KSURLQuery queryWithURL:[NSURL URLWithString:@"?size=%7B64%2C+64%7D"]]; - NSDictionary *parameters = [components queryParametersWithOptions:0]; + NSDictionary *parameters = [query parametersWithOptions:0]; STAssertEqualObjects(parameters, @{ @"size" : @"{64,+64}" }, nil); - parameters = [components queryParametersWithOptions:KSURLComponentsQueryParameterDecodingPlusAsSpace]; + parameters = [query parametersWithOptions:KSURLComponentsQueryParameterDecodingPlusAsSpace]; STAssertEqualObjects(parameters, @{ @"size" : @"{64, 64}" }, nil); } - (void)testEncodeNilQueryParameters; { - KSURLComponents *components = [[KSURLComponents alloc] init]; - [components setQueryParameters:nil]; - STAssertNil(components.percentEncodedQuery, nil); + KSURLQuery *query = [[KSURLQuery alloc] init]; + [query setParameters:nil]; + STAssertNil(query.percentEncodedString, nil); } - (void)testEncodeEmptyQueryParameters; { - KSURLComponents *components = [[KSURLComponents alloc] init]; - [components setQueryParameters:@{ }]; - STAssertEqualObjects(components.percentEncodedQuery, @"", nil); + KSURLQuery *query = [[KSURLQuery alloc] init]; + [query setParameters:@{ }]; + STAssertEqualObjects(query.percentEncodedString, @"", nil); } - (void)testEncodeQueryParameter; { - KSURLComponents *components = [[KSURLComponents alloc] init]; - [components setQueryParameters:@{ @"key" : @"value" }]; - STAssertEqualObjects(components.percentEncodedQuery, @"key=value", nil); + KSURLQuery *query = [[KSURLQuery alloc] init]; + [query setParameters:@{ @"key" : @"value" }]; + STAssertEqualObjects(query.percentEncodedString, @"key=value", nil); } - (void)testEncodeQueryParameters; { - KSURLComponents *components = [[KSURLComponents alloc] init]; - [components setQueryParameters:@{ @"key" : @"value", @"key2" : @"value2" }]; - STAssertEqualObjects(components.percentEncodedQuery, @"key=value&key2=value2", nil); + KSURLQuery *query = [[KSURLQuery alloc] init]; + [query setParameters:@{ @"key" : @"value", @"key2" : @"value2" }]; + STAssertEqualObjects(query.percentEncodedString, @"key=value&key2=value2", nil); } - (void)testEncodeQueryParameterEscaping; { - KSURLComponents *components = [[KSURLComponents alloc] init]; - [components setQueryParameters:@{ @"!*'();:@&=+$,/?#[]" : @"!*'();:@&=+$,/?#[]" }]; - STAssertEqualObjects(components.percentEncodedQuery, @"!*'();:@%26%3D%2B$,/?%23%5B%5D=!*'();:@%26=%2B$,/?%23%5B%5D", nil); + KSURLQuery *query = [[KSURLQuery alloc] init]; + [query setParameters:@{ @"!*'();:@&=+$,/?#[]" : @"!*'();:@&=+$,/?#[]" }]; + STAssertEqualObjects(query.percentEncodedString, @"!*'();:@%26%3D%2B$,/?%23%5B%5D=!*'();:@%26=%2B$,/?%23%5B%5D", nil); } @end From eedaeac4f14cc0932f39c535478decdfaf968a30 Mon Sep 17 00:00:00 2001 From: Mike Abdullah Date: Thu, 12 Dec 2013 11:25:51 +0000 Subject: [PATCH 22/44] Make percentEncodedString atomic Easy now for it to outlive the query object --- KSURLQuery.h | 2 +- KSURLQuery.m | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/KSURLQuery.h b/KSURLQuery.h index 3633e57..e6ae717 100644 --- a/KSURLQuery.h +++ b/KSURLQuery.h @@ -17,7 +17,7 @@ + (instancetype)queryWithURL:(NSURL *)url; + (instancetype)queryWithPercentEncodedString:(NSString *)percentEncodedQuery; -@property(nonatomic, readonly, copy) NSString *percentEncodedString; +@property(atomic, readonly, copy) NSString *percentEncodedString; typedef NS_OPTIONS(NSUInteger, KSURLQueryParameterDecodingOptions) { KSURLQueryParameterDecodingPlusAsSpace = 1UL << 0, // + characters are interpreted as spaces, rather than regular + symbols diff --git a/KSURLQuery.m b/KSURLQuery.m index 33cbd6d..8267795 100644 --- a/KSURLQuery.m +++ b/KSURLQuery.m @@ -10,7 +10,7 @@ @interface KSURLQuery () -@property(nonatomic, readwrite, copy) NSString *percentEncodedString; +@property(atomic, readwrite, copy) NSString *percentEncodedString; @end From 5791ca62c224670c47649761584abe997b84ecfa Mon Sep 17 00:00:00 2001 From: Mike Abdullah Date: Thu, 12 Dec 2013 11:26:12 +0000 Subject: [PATCH 23/44] Make KSURLComponents.percentEncodedQuery readwrite --- KSURLComponents.h | 4 ++-- KSURLComponents.m | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/KSURLComponents.h b/KSURLComponents.h index 9972452..d36b9ed 100644 --- a/KSURLComponents.h +++ b/KSURLComponents.h @@ -118,12 +118,12 @@ @property (nonatomic, copy) NSString *query; @property (nonatomic, copy) NSString *fragment; -// Getting these properties retains any percent encoding these components may have. Setting these properties is currently not supported as I am lazy and doing so is rarely useful. If you do have a use case, please send me a pull request or file an issue on GitHub. +// Getting these properties retains any percent encoding these components may have. Setting most of these properties is currently not supported as I am lazy and doing so is rarely useful. If you do have a use case, please send me a pull request or file an issue on GitHub. @property (nonatomic, copy, readonly) NSString *percentEncodedUser; @property (nonatomic, copy, readonly) NSString *percentEncodedPassword; @property (nonatomic, copy, readonly) NSString *percentEncodedHost; @property (nonatomic, copy, readonly) NSString *percentEncodedPath; -@property (nonatomic, copy, readonly) NSString *percentEncodedQuery; +@property (nonatomic, copy) NSString *percentEncodedQuery; @property (nonatomic, copy, readonly) NSString *percentEncodedFragment; @end diff --git a/KSURLComponents.m b/KSURLComponents.m index 6149a7a..c1ffb43 100644 --- a/KSURLComponents.m +++ b/KSURLComponents.m @@ -34,7 +34,6 @@ @interface KSURLComponents () @property (nonatomic, copy, readwrite) NSString *percentEncodedPassword; @property (nonatomic, copy, readwrite) NSString *percentEncodedHost; @property (nonatomic, copy, readwrite) NSString *percentEncodedPath; -@property (nonatomic, copy, readwrite) NSString *percentEncodedQuery; @property (nonatomic, copy, readwrite) NSString *percentEncodedFragment; @end @@ -449,6 +448,12 @@ - (void)setPath:(NSString *)path; } @synthesize percentEncodedQuery = _queryComponent; +- (void)setPercentEncodedQuery:(NSString *)percentEncodedQuery; +{ + // FIXME: Check the query is valid + percentEncodedQuery = [percentEncodedQuery copy]; + [_queryComponent release]; _queryComponent = percentEncodedQuery; +} - (NSString *)query; { return [self.percentEncodedQuery stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; From 2b6d302aed2b9fd19648b4c04552ac45f6cd202d Mon Sep 17 00:00:00 2001 From: Mike Abdullah Date: Thu, 12 Dec 2013 11:29:22 +0000 Subject: [PATCH 24/44] +[KSURLQuery encodeParameters:] convenience --- KSURLQuery.h | 6 ++++++ KSURLQuery.m | 13 +++++++++++++ 2 files changed, 19 insertions(+) diff --git a/KSURLQuery.h b/KSURLQuery.h index e6ae717..57e05d6 100644 --- a/KSURLQuery.h +++ b/KSURLQuery.h @@ -14,6 +14,12 @@ NSString *_percentEncodedString; } +#pragma mark Convenience ++ (NSString *)encodeParameters:(NSDictionary *)parameters; + + +#pragma mark + + (instancetype)queryWithURL:(NSURL *)url; + (instancetype)queryWithPercentEncodedString:(NSString *)percentEncodedQuery; diff --git a/KSURLQuery.m b/KSURLQuery.m index 8267795..422990e 100644 --- a/KSURLQuery.m +++ b/KSURLQuery.m @@ -16,6 +16,19 @@ @interface KSURLQuery () @implementation KSURLQuery +#pragma mark Convenience + ++ (NSString *)encodeParameters:(NSDictionary *)parameters; +{ + KSURLQuery *query = [[KSURLQuery alloc] init]; + [query setParameters:parameters]; + NSString *result = query.percentEncodedString; + [query release]; + return result; +} + +#pragma mark + + (instancetype)queryWithURL:(NSURL *)url; { // Always resolve, since unlike paths there's no way for two queries to be in some way concatenated From 0258ca5eb769051663aaae0b01ffd028f2f8de73 Mon Sep 17 00:00:00 2001 From: Mike Abdullah Date: Thu, 12 Dec 2013 12:28:41 +0000 Subject: [PATCH 25/44] Start document KSURLQuery better --- KSURLQuery.h | 67 +++++++++++++++++++++++++++++++++++++++++++++++----- KSURLQuery.m | 5 ++++ 2 files changed, 66 insertions(+), 6 deletions(-) diff --git a/KSURLQuery.h b/KSURLQuery.h index 57e05d6..ca24449 100644 --- a/KSURLQuery.h +++ b/KSURLQuery.h @@ -8,6 +8,12 @@ #import + +typedef NS_OPTIONS(NSUInteger, KSURLQueryParameterDecodingOptions) { + KSURLQueryParameterDecodingPlusAsSpace = 1UL << 0, // + characters are interpreted as spaces, rather than regular + symbols +}; + + @interface KSURLQuery : NSObject { @private @@ -15,23 +21,72 @@ } #pragma mark Convenience ++ (NSDictionary *)parametersFromURL:(NSURL *)url options:(KSURLQueryParameterDecodingOptions)options; + (NSString *)encodeParameters:(NSDictionary *)parameters; -#pragma mark - +#pragma mark Creating a KSURLQuery + (instancetype)queryWithURL:(NSURL *)url; + (instancetype)queryWithPercentEncodedString:(NSString *)percentEncodedQuery; -@property(atomic, readonly, copy) NSString *percentEncodedString; -typedef NS_OPTIONS(NSUInteger, KSURLQueryParameterDecodingOptions) { - KSURLQueryParameterDecodingPlusAsSpace = 1UL << 0, // + characters are interpreted as spaces, rather than regular + symbols -}; +#pragma mark Decoding Parameters +/** + Converts the query into a dictionary representation. + + For example: + + http://example.com?key=value&foo=bar + + can be interpreted as: + + @{ @"key" : @"value", @"foo" : @"bar" } + + Keys and values are percent decoded according to `options`. + + If you have a query which doesn't match `NSDictionary`'s design, drop down to + the primitive `-enumerateParametersWithOptions:usingBlock:` method instead. + + @param options A mask that specifies options for parameter decoding. + @return `nil` if query doesn't neatly fit an `NSDictionary` representation + */ - (NSDictionary *)parametersWithOptions:(KSURLQueryParameterDecodingOptions)options; + +/** + Enumerates the receiver's parameters, handling cases where an NSDictionary representation doesn't suffice. + + * Parameters are reported in the order they appear in the URL + * Keys and values are percent decoded for your convenience + * Parameters without a value are reported as `nil` + * Duplicate parameters are correctly reported too + + @param options A mask that specifies options for parameter decoding. + @param block A block called for each parameter of the query. + */ - (void)enumerateParametersWithOptions:(KSURLQueryParameterDecodingOptions)options usingBlock:(void (^)(NSString *key, NSString *value, BOOL *stop))block __attribute((nonnull(2))); + +#pragma mark Encoding Parameters + +/** + Replaces any existing query by encoding the `parameters` dictionary. + + For example: + + @{ @"key" : @"value", @"foo" : @"bar" } + + can be represented as: + + http://example.com?key=value&foo=bar + + Keys and values are percent encoded. + + @param parameters A dictionary to encode, whose keys and values are all strings. + */ - (void)setParameters:(NSDictionary *)parameters; +@property(atomic, readonly, copy) NSString *percentEncodedString; + + @end diff --git a/KSURLQuery.m b/KSURLQuery.m index 422990e..a728fcc 100644 --- a/KSURLQuery.m +++ b/KSURLQuery.m @@ -18,6 +18,11 @@ @implementation KSURLQuery #pragma mark Convenience ++ (NSDictionary *)parametersFromURL:(NSURL *)url options:(KSURLQueryParameterDecodingOptions)options; +{ + return [[self queryWithURL:url] parametersWithOptions:options]; +} + + (NSString *)encodeParameters:(NSDictionary *)parameters; { KSURLQuery *query = [[KSURLQuery alloc] init]; From ecbbaae15665746b9ca9de4bf1e634508a89d2cd Mon Sep 17 00:00:00 2001 From: Mike Abdullah Date: Thu, 12 Dec 2013 12:29:25 +0000 Subject: [PATCH 26/44] Remove query parameter handling from KSURLComponents --- KSURLComponents.h | 63 ----------------------------------------------- KSURLComponents.m | 23 ----------------- 2 files changed, 86 deletions(-) diff --git a/KSURLComponents.h b/KSURLComponents.h index d36b9ed..d433c0a 100644 --- a/KSURLComponents.h +++ b/KSURLComponents.h @@ -127,66 +127,3 @@ @property (nonatomic, copy, readonly) NSString *percentEncodedFragment; @end - - -@interface KSURLComponents (KSQueryParameters) - -typedef NS_OPTIONS(NSUInteger, KSURLComponentsQueryParameterDecodingOptions) { - KSURLComponentsQueryParameterDecodingPlusAsSpace = 1UL << 0, // + characters are interpreted as spaces, rather than regular + symbols -}; - -/** - Converts between `.query` strings and their dictionary representation. - - For example: - - http://example.com?key=value&foo=bar - - can be interpreted as: - - @{ @"key" : @"value", @"foo" : @"bar" } - - Keys and values are percent decoded according to `options`. - - If you have a query which doesn't match `NSDictionary`'s design, drop down to - the primitive `-enumerateQueryParametersWithOptions:usingBlock:` method instead. - - @param options A mask that specifies options for parameter decoding. - @return `nil` if query doesn't neatly fit an `NSDictionary` representation - */ -- (NSDictionary *)queryParametersWithOptions:(KSURLComponentsQueryParameterDecodingOptions)options; - -/** - Converts between `.query` strings and their dictionary representation. - - For example: - - @{ @"key" : @"value", @"foo" : @"bar" } - - can be represented as: - - http://example.com?key=value&foo=bar - - Keys and values are percent encoded. - - @param parameters A dictionary to encode, whose keys and values are all strings. - */ -- (void)setQueryParameters:(NSDictionary *)parameters; - -/** - Enumerates the parameters of `.query` - - Tolerates all forms of query: - - * Parameters without a value are reported as `nil` - * Parameters are reported in the order they appear in the URL - * Duplicate parameters are correctly reported too - - Keys and values are percent decoded for your convenience. - - @param options A mask that specifies options for parameter decoding. - @param block A block called for each parameter of the query. - */ -- (void)enumerateQueryParametersWithOptions:(KSURLComponentsQueryParameterDecodingOptions)options usingBlock:(void (^)(NSString *key, NSString *value, BOOL *stop))block __attribute((nonnull(2))); - -@end diff --git a/KSURLComponents.m b/KSURLComponents.m index c1ffb43..f71c7c1 100644 --- a/KSURLComponents.m +++ b/KSURLComponents.m @@ -571,26 +571,3 @@ - (NSString *)description; } @end - - -@implementation KSURLComponents (KSQueryParameters) - -- (NSDictionary *)queryParametersWithOptions:(KSURLComponentsQueryParameterDecodingOptions)options; -{ - return [[KSURLQuery queryWithPercentEncodedString:self.percentEncodedQuery] parametersWithOptions:options]; -} - -- (void)setQueryParameters:(NSDictionary *)parameters; -{ - KSURLQuery *query = [[KSURLQuery alloc] init]; - [query setParameters:parameters]; - self.percentEncodedQuery = query.percentEncodedString; - [query release]; -} - -- (void)enumerateQueryParametersWithOptions:(KSURLComponentsQueryParameterDecodingOptions)options usingBlock:(void (^)(NSString *key, NSString *value, BOOL *stop))block; -{ - [[KSURLQuery queryWithPercentEncodedString:self.percentEncodedQuery] enumerateParametersWithOptions:options usingBlock:block]; -} - -@end From e626860bf65ab6afe0bbb4e0a69b65dd68ccda1e Mon Sep 17 00:00:00 2001 From: Mike Abdullah Date: Thu, 12 Dec 2013 12:34:30 +0000 Subject: [PATCH 27/44] Document .percentEncodedString --- KSURLQuery.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/KSURLQuery.h b/KSURLQuery.h index ca24449..5283311 100644 --- a/KSURLQuery.h +++ b/KSURLQuery.h @@ -86,6 +86,12 @@ typedef NS_OPTIONS(NSUInteger, KSURLQueryParameterDecodingOptions) { */ - (void)setParameters:(NSDictionary *)parameters; +/** + @result The encoded representation of the receiver. + + Generally you then pass the result of this method to `NSURLComponents.percentEncodedQuery` + (or `KSURLComponents`) to build up a full URL. + */ @property(atomic, readonly, copy) NSString *percentEncodedString; From aa1fc5a801dd9583a64b35d2b039fb55ce56fdea Mon Sep 17 00:00:00 2001 From: Mike Abdullah Date: Thu, 12 Dec 2013 22:24:45 +0000 Subject: [PATCH 28/44] Correct constant --- TestKSFileUtilities/TestKSURLComponents.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TestKSFileUtilities/TestKSURLComponents.m b/TestKSFileUtilities/TestKSURLComponents.m index 9eaa97c..04f7bc8 100644 --- a/TestKSFileUtilities/TestKSURLComponents.m +++ b/TestKSFileUtilities/TestKSURLComponents.m @@ -780,7 +780,7 @@ - (void)testPlusSymbolInQueryParameters; NSDictionary *parameters = [query parametersWithOptions:0]; STAssertEqualObjects(parameters, @{ @"size" : @"{64,+64}" }, nil); - parameters = [query parametersWithOptions:KSURLComponentsQueryParameterDecodingPlusAsSpace]; + parameters = [query parametersWithOptions:KSURLQueryParameterDecodingPlusAsSpace]; STAssertEqualObjects(parameters, @{ @"size" : @"{64, 64}" }, nil); } From 1dd74aa851f37a9200030932c59548b6eaaada58 Mon Sep 17 00:00:00 2001 From: Mike Abdullah Date: Thu, 12 Dec 2013 22:25:04 +0000 Subject: [PATCH 29/44] -addParameter:value: method for building up queries pice-by-piece --- KSURLQuery.h | 9 +++++++++ KSURLQuery.m | 31 +++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/KSURLQuery.h b/KSURLQuery.h index 5283311..afda2fa 100644 --- a/KSURLQuery.h +++ b/KSURLQuery.h @@ -86,6 +86,15 @@ typedef NS_OPTIONS(NSUInteger, KSURLQueryParameterDecodingOptions) { */ - (void)setParameters:(NSDictionary *)parameters; +/** + Adds an extra parameter to end of the receiver. + + Enables the query to be built up piece-by-piece. This can be especially useful + when the ordering of parameters is critical, and/or parameters appear more than + once. + */ +- (void)addParameter:(NSString *)key value:(NSString *)value __attribute((nonnull(1))); + /** @result The encoded representation of the receiver. diff --git a/KSURLQuery.m b/KSURLQuery.m index a728fcc..bd36f74 100644 --- a/KSURLQuery.m +++ b/KSURLQuery.m @@ -189,4 +189,35 @@ - (void)setParameters:(NSDictionary *)parameters; self.percentEncodedString = query; } +- (void)addParameter:(NSString *)key value:(NSString *)value; +{ + CFStringRef escapedKey = CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)key, NULL, CFSTR("+=&#"), kCFStringEncodingUTF8); + // Escape + for safety as some backends interpret it as a space + // = indicates the start of value, so must be escaped + // & indicates the start of next parameter, so must be escaped + // # indicates the start of fragment, so must be escaped + + CFStringRef escapedValue = CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)value, NULL, CFSTR("+&#"), kCFStringEncodingUTF8); + // Escape + for safety as some backends interpret it as a space + // = is allowed in values, as there's no further value to indicate + // & indicates the start of next parameter, so must be escaped + // # indicates the start of fragment, so must be escaped + + // Append the parameter and its value to the full query string + NSString *query = self.percentEncodedString; + if (query) + { + query = [query stringByAppendingFormat:@"&%@=%@", escapedKey, escapedValue]; + } + else + { + query = [NSString stringWithFormat:@"%@=%@", escapedKey, escapedValue]; + } + + self.percentEncodedString = query; + + CFRelease(escapedKey); + CFRelease(escapedValue); +} + @end From f524b89dcaab5e8eb1bb3a319764a9fb96d776b9 Mon Sep 17 00:00:00 2001 From: Mike Abdullah Date: Thu, 12 Dec 2013 22:28:43 +0000 Subject: [PATCH 30/44] Reimplement -setParameters: to use -addParameter:value: --- KSURLQuery.m | 42 ++++++++---------------------------------- 1 file changed, 8 insertions(+), 34 deletions(-) diff --git a/KSURLQuery.m b/KSURLQuery.m index bd36f74..842449e 100644 --- a/KSURLQuery.m +++ b/KSURLQuery.m @@ -147,46 +147,20 @@ - (void)setParameters:(NSDictionary *)parameters; return; } + if (!parameters.count) + { + self.percentEncodedString = @""; + return; + } + // Build the list of parameters as a string - NSMutableString *query = [NSMutableString string]; - - NSEnumerator *enumerator = [parameters keyEnumerator]; - BOOL thisIsTheFirstParameter = YES; + NSEnumerator *enumerator = [parameters keyEnumerator]; NSString *key; while ((key = [enumerator nextObject])) { - CFStringRef escapedKey = CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)key, NULL, CFSTR("+=&#"), kCFStringEncodingUTF8); - // Escape + for safety as some backends interpret it as a space - // = indicates the start of value, so must be escaped - // & indicates the start of next parameter, so must be escaped - // # indicates the start of fragment, so must be escaped - - NSString *parameter = [parameters objectForKey:key]; - - // Append the parameter and its key to the full query string - if (!thisIsTheFirstParameter) - { - [query appendString:@"&"]; - } - else - { - thisIsTheFirstParameter = NO; - } - - CFStringRef escapedValue = CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)parameter, NULL, CFSTR("+&#"), kCFStringEncodingUTF8); - // Escape + for safety as some backends interpret it as a space - // = is allowed in values, as there's no further value to indicate - // & indicates the start of next parameter, so must be escaped - // # indicates the start of fragment, so must be escaped - - [query appendFormat:@"%@=%@", escapedKey, escapedValue]; - - CFRelease(escapedKey); - CFRelease(escapedValue); + [self addParameter:key value:[parameters objectForKey:key]]; } - - self.percentEncodedString = query; } - (void)addParameter:(NSString *)key value:(NSString *)value; From 17e8d3ef995cd7f3e3306c73a42770ee840f6808 Mon Sep 17 00:00:00 2001 From: Mike Abdullah Date: Thu, 12 Dec 2013 22:51:20 +0000 Subject: [PATCH 31/44] Publish -initWithPercentEncodedString: too --- KSURLQuery.h | 1 + 1 file changed, 1 insertion(+) diff --git a/KSURLQuery.h b/KSURLQuery.h index afda2fa..2274b0b 100644 --- a/KSURLQuery.h +++ b/KSURLQuery.h @@ -28,6 +28,7 @@ typedef NS_OPTIONS(NSUInteger, KSURLQueryParameterDecodingOptions) { #pragma mark Creating a KSURLQuery + (instancetype)queryWithURL:(NSURL *)url; + (instancetype)queryWithPercentEncodedString:(NSString *)percentEncodedQuery; +- initWithPercentEncodedString:(NSString *)string; #pragma mark Decoding Parameters From 560b800e409da07c637a613554dad68f0f069968 Mon Sep 17 00:00:00 2001 From: Mike Abdullah Date: Thu, 12 Dec 2013 23:22:44 +0000 Subject: [PATCH 32/44] Add +parametersOfPercentEncodedQuery:options: convenience too --- KSURLQuery.h | 1 + KSURLQuery.m | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/KSURLQuery.h b/KSURLQuery.h index 2274b0b..de9ff89 100644 --- a/KSURLQuery.h +++ b/KSURLQuery.h @@ -22,6 +22,7 @@ typedef NS_OPTIONS(NSUInteger, KSURLQueryParameterDecodingOptions) { #pragma mark Convenience + (NSDictionary *)parametersFromURL:(NSURL *)url options:(KSURLQueryParameterDecodingOptions)options; ++ (NSDictionary *)parametersOfPercentEncodedQuery:(NSString *)query options:(KSURLQueryParameterDecodingOptions)options; + (NSString *)encodeParameters:(NSDictionary *)parameters; diff --git a/KSURLQuery.m b/KSURLQuery.m index 842449e..6ce24fa 100644 --- a/KSURLQuery.m +++ b/KSURLQuery.m @@ -23,6 +23,11 @@ + (NSDictionary *)parametersFromURL:(NSURL *)url options:(KSURLQueryParameterDec return [[self queryWithURL:url] parametersWithOptions:options]; } ++ (NSDictionary *)parametersOfPercentEncodedQuery:(NSString *)query options:(KSURLQueryParameterDecodingOptions)options; +{ + return [[self queryWithPercentEncodedString:query] parametersWithOptions:options]; +} + + (NSString *)encodeParameters:(NSDictionary *)parameters; { KSURLQuery *query = [[KSURLQuery alloc] init]; From a67b23e723977e963288319d9546b1ee06d15ca5 Mon Sep 17 00:00:00 2001 From: Mike Abdullah Date: Thu, 12 Dec 2013 23:25:50 +0000 Subject: [PATCH 33/44] Remove +queryWithPercentEncodedString: --- KSURLQuery.h | 1 - KSURLQuery.m | 16 +++++++--------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/KSURLQuery.h b/KSURLQuery.h index de9ff89..f12b697 100644 --- a/KSURLQuery.h +++ b/KSURLQuery.h @@ -28,7 +28,6 @@ typedef NS_OPTIONS(NSUInteger, KSURLQueryParameterDecodingOptions) { #pragma mark Creating a KSURLQuery + (instancetype)queryWithURL:(NSURL *)url; -+ (instancetype)queryWithPercentEncodedString:(NSString *)percentEncodedQuery; - initWithPercentEncodedString:(NSString *)string; diff --git a/KSURLQuery.m b/KSURLQuery.m index 6ce24fa..12f087d 100644 --- a/KSURLQuery.m +++ b/KSURLQuery.m @@ -23,9 +23,12 @@ + (NSDictionary *)parametersFromURL:(NSURL *)url options:(KSURLQueryParameterDec return [[self queryWithURL:url] parametersWithOptions:options]; } -+ (NSDictionary *)parametersOfPercentEncodedQuery:(NSString *)query options:(KSURLQueryParameterDecodingOptions)options; ++ (NSDictionary *)parametersOfPercentEncodedQuery:(NSString *)string options:(KSURLQueryParameterDecodingOptions)options; { - return [[self queryWithPercentEncodedString:query] parametersWithOptions:options]; + KSURLQuery *query = [[self alloc] initWithPercentEncodedString:string]; + NSDictionary *result = [query parametersWithOptions:options]; + [query release]; + return result; } + (NSString *)encodeParameters:(NSDictionary *)parameters; @@ -47,15 +50,10 @@ + (instancetype)queryWithURL:(NSURL *)url; NSString *string = (NSString *)CFURLCopyQueryString(cfURL, NULL); // leave unescaped - KSURLQuery *result = [self queryWithPercentEncodedString:string]; + KSURLQuery *result = [[self alloc] initWithPercentEncodedString:string]; [string release]; CFRelease(cfURL); - return result; -} - -+ (instancetype)queryWithPercentEncodedString:(NSString *)percentEncodedQuery; -{ - return [[[self alloc] initWithPercentEncodedString:percentEncodedQuery] autorelease]; + return [result autorelease]; } - initWithPercentEncodedString:(NSString *)string; From 3d756b0f24abfdaaaf03f2351e83e2bb508a76c0 Mon Sep 17 00:00:00 2001 From: Mike Abdullah Date: Thu, 12 Dec 2013 23:47:12 +0000 Subject: [PATCH 34/44] Deprecate KSURLQueryUtilities in favour of KSURLQuery --- KSURLQueryUtilities.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/KSURLQueryUtilities.h b/KSURLQueryUtilities.h index 05d6094..fd2cf27 100644 --- a/KSURLQueryUtilities.h +++ b/KSURLQueryUtilities.h @@ -30,14 +30,14 @@ @interface NSURL (KSURLQueryUtilities) // It's common to use the query part of a URL for a dictionary-like series of parameters. This method will decode that for you, including handling strings which were escaped to fit the scheme -- (NSDictionary *)ks_queryParameters; +- (NSDictionary *)ks_queryParameters __deprecated_msg("use KSURLQuery instead"); // To do the reverse, construct a dictonary for the query and pass into either of these methods. You can base the result off of an existing URL, or specify all the components. -- (NSURL *)ks_URLWithQueryParameters:(NSDictionary *)parameters; +- (NSURL *)ks_URLWithQueryParameters:(NSDictionary *)parameters __deprecated_msg("use KSURLQuery instead"); + (NSURL *)ks_URLWithScheme:(NSString *)scheme host:(NSString *)host path:(NSString *)path - queryParameters:(NSDictionary *)parameters; + queryParameters:(NSDictionary *)parameters __deprecated_msg("use KSURLQuery instead"); // Primitive methods for if you need tighter control over handling query dictionaries + (NSString *)ks_queryWithParameters:(NSDictionary *)parameters; From 3d3831ae69a681354157c9825917bb0a3b62b1c5 Mon Sep 17 00:00:00 2001 From: Mike Abdullah Date: Thu, 12 Dec 2013 23:51:25 +0000 Subject: [PATCH 35/44] Switch KSMailtoURLs over to use KSURLQuery --- KSMailtoURLs.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/KSMailtoURLs.m b/KSMailtoURLs.m index 752e250..a2841fc 100644 --- a/KSMailtoURLs.m +++ b/KSMailtoURLs.m @@ -26,7 +26,7 @@ #import "KSMailtoURLs.h" -#import "KSURLQueryUtilities.h" +#import "KSURLQuery.h" NSString *KSURLMailtoScheme = @"mailto"; @@ -52,7 +52,7 @@ + (NSURL *)ks_mailtoURLWithEmailAddress:(NSString *)address headerLines:(NSDicti if (headers) { - NSString *query = [self ks_queryWithParameters:headers]; + NSString *query = [KSURLQuery encodeParameters:headers]; if ([query length]) { string = [string stringByAppendingFormat:@"?%@", query]; @@ -72,7 +72,7 @@ - (NSDictionary *)ks_mailHeaderLines; if (queryIndicatorRange.location != NSNotFound) { NSString *query = [urlString substringFromIndex:NSMaxRange(queryIndicatorRange)]; - return [NSURL ks_parametersOfQuery:query]; + return [KSURLQuery parametersOfPercentEncodedQuery:query options:0]; } return nil; From ac1b5451d72314c8f8ea373666979867165e753e Mon Sep 17 00:00:00 2001 From: Mike Abdullah Date: Thu, 12 Dec 2013 23:58:00 +0000 Subject: [PATCH 36/44] Deprecate NSString (KSURLQueryUtilities) too --- KSURLQueryUtilities.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/KSURLQueryUtilities.h b/KSURLQueryUtilities.h index fd2cf27..8334e14 100644 --- a/KSURLQueryUtilities.h +++ b/KSURLQueryUtilities.h @@ -49,7 +49,7 @@ @interface NSString (KSURLQueryUtilities) // Follows RFC2396, section 3.4 -- (NSString *)ks_stringByAddingQueryComponentPercentEscapes; -- (NSString *)ks_stringByReplacingQueryComponentPercentEscapes; +- (NSString *)ks_stringByAddingQueryComponentPercentEscapes __deprecated_msg("use KSURLQuery instead; keys and values have different encoding needs"); +- (NSString *)ks_stringByReplacingQueryComponentPercentEscapes __deprecated_msg("use KSURLQuery instead; not all servers use + to encode a space"); @end From 6c495f89e03e3a1218a71bf279916481110e73d5 Mon Sep 17 00:00:00 2001 From: Mike Abdullah Date: Fri, 13 Dec 2013 09:53:19 +0000 Subject: [PATCH 37/44] Simplify API to +decodeString:options: Also rearranged order in header to make more sense --- KSMailtoURLs.m | 2 +- KSURLQuery.h | 4 ++-- KSURLQuery.m | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/KSMailtoURLs.m b/KSMailtoURLs.m index a2841fc..7a4585f 100644 --- a/KSMailtoURLs.m +++ b/KSMailtoURLs.m @@ -72,7 +72,7 @@ - (NSDictionary *)ks_mailHeaderLines; if (queryIndicatorRange.location != NSNotFound) { NSString *query = [urlString substringFromIndex:NSMaxRange(queryIndicatorRange)]; - return [KSURLQuery parametersOfPercentEncodedQuery:query options:0]; + return [KSURLQuery decodeString:query options:0]; } return nil; diff --git a/KSURLQuery.h b/KSURLQuery.h index f12b697..7cf8b32 100644 --- a/KSURLQuery.h +++ b/KSURLQuery.h @@ -21,9 +21,9 @@ typedef NS_OPTIONS(NSUInteger, KSURLQueryParameterDecodingOptions) { } #pragma mark Convenience -+ (NSDictionary *)parametersFromURL:(NSURL *)url options:(KSURLQueryParameterDecodingOptions)options; -+ (NSDictionary *)parametersOfPercentEncodedQuery:(NSString *)query options:(KSURLQueryParameterDecodingOptions)options; + (NSString *)encodeParameters:(NSDictionary *)parameters; ++ (NSDictionary *)decodeString:(NSString *)percentEncodedQuery options:(KSURLQueryParameterDecodingOptions)options; ++ (NSDictionary *)parametersFromURL:(NSURL *)url options:(KSURLQueryParameterDecodingOptions)options; #pragma mark Creating a KSURLQuery diff --git a/KSURLQuery.m b/KSURLQuery.m index 12f087d..a865b9e 100644 --- a/KSURLQuery.m +++ b/KSURLQuery.m @@ -23,7 +23,7 @@ + (NSDictionary *)parametersFromURL:(NSURL *)url options:(KSURLQueryParameterDec return [[self queryWithURL:url] parametersWithOptions:options]; } -+ (NSDictionary *)parametersOfPercentEncodedQuery:(NSString *)string options:(KSURLQueryParameterDecodingOptions)options; ++ (NSDictionary *)decodeString:(NSString *)string options:(KSURLQueryParameterDecodingOptions)options; { KSURLQuery *query = [[self alloc] initWithPercentEncodedString:string]; NSDictionary *result = [query parametersWithOptions:options]; From 0a5397f5d3629192c357f70fe99e0ff392e5530e Mon Sep 17 00:00:00 2001 From: Mike Abdullah Date: Sat, 14 Dec 2013 23:51:04 +0000 Subject: [PATCH 38/44] Remove #import of KSURLQuery --- KSURLComponents.m | 2 -- 1 file changed, 2 deletions(-) diff --git a/KSURLComponents.m b/KSURLComponents.m index f71c7c1..c06206e 100644 --- a/KSURLComponents.m +++ b/KSURLComponents.m @@ -26,8 +26,6 @@ #import "KSURLComponents.h" -#import "KSURLQuery.h" - @interface KSURLComponents () @property (nonatomic, copy, readwrite) NSString *percentEncodedUser; From e710102becd7b3525003400279ed1c67625f3846 Mon Sep 17 00:00:00 2001 From: Mike Abdullah Date: Sun, 15 Dec 2013 11:25:04 +0000 Subject: [PATCH 39/44] Adopt block-based enumeration internally Should be a little faster than NSEnumerator, and still give the same results (in my testing) --- KSURLQuery.m | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/KSURLQuery.m b/KSURLQuery.m index a865b9e..ee9e7eb 100644 --- a/KSURLQuery.m +++ b/KSURLQuery.m @@ -157,13 +157,9 @@ - (void)setParameters:(NSDictionary *)parameters; } // Build the list of parameters as a string - NSEnumerator *enumerator = [parameters keyEnumerator]; - - NSString *key; - while ((key = [enumerator nextObject])) - { - [self addParameter:key value:[parameters objectForKey:key]]; - } + [parameters enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, BOOL *stop) { + [self addParameter:key value:value]; + }]; } - (void)addParameter:(NSString *)key value:(NSString *)value; From ab245ae796283205207f4d8d33d0aa4329045783 Mon Sep 17 00:00:00 2001 From: Mike Abdullah Date: Sun, 15 Dec 2013 11:27:57 +0000 Subject: [PATCH 40/44] Document -setParameters:' lack of ordering --- KSURLQuery.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/KSURLQuery.h b/KSURLQuery.h index 7cf8b32..2cad4a5 100644 --- a/KSURLQuery.h +++ b/KSURLQuery.h @@ -83,6 +83,10 @@ typedef NS_OPTIONS(NSUInteger, KSURLQueryParameterDecodingOptions) { Keys and values are percent encoded. + Since NSDictionary's API has no concept of ordering for keys, the order of the + parameters from calling this method is similarly undefined. If ordering is + important to your use case, use `-addParameter:value:` instead. + @param parameters A dictionary to encode, whose keys and values are all strings. */ - (void)setParameters:(NSDictionary *)parameters; From d8062939b219a9eee777532fe22281cd699d27cd Mon Sep 17 00:00:00 2001 From: Mike Abdullah Date: Sun, 15 Dec 2013 12:00:54 +0000 Subject: [PATCH 41/44] Support encoding arbitrary objects for values --- KSURLQuery.h | 10 ++++++++-- KSURLQuery.m | 4 ++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/KSURLQuery.h b/KSURLQuery.h index 2cad4a5..ff6ab34 100644 --- a/KSURLQuery.h +++ b/KSURLQuery.h @@ -81,7 +81,7 @@ typedef NS_OPTIONS(NSUInteger, KSURLQueryParameterDecodingOptions) { http://example.com?key=value&foo=bar - Keys and values are percent encoded. + See `-addParameter:value:` for full details on encoding of keys and values. Since NSDictionary's API has no concept of ordering for keys, the order of the parameters from calling this method is similarly undefined. If ordering is @@ -97,8 +97,14 @@ typedef NS_OPTIONS(NSUInteger, KSURLQueryParameterDecodingOptions) { Enables the query to be built up piece-by-piece. This can be especially useful when the ordering of parameters is critical, and/or parameters appear more than once. + + Both the key and value are percent encoded. + + `value` is sent a `-description` message to obtain a string representation + suitable for encoding, enabling objects like `NSNumber`s to be easily encoded, + as well as strings. */ -- (void)addParameter:(NSString *)key value:(NSString *)value __attribute((nonnull(1))); +- (void)addParameter:(NSString *)key value:(id )value __attribute((nonnull(1))); /** @result The encoded representation of the receiver. diff --git a/KSURLQuery.m b/KSURLQuery.m index ee9e7eb..af92917 100644 --- a/KSURLQuery.m +++ b/KSURLQuery.m @@ -162,7 +162,7 @@ - (void)setParameters:(NSDictionary *)parameters; }]; } -- (void)addParameter:(NSString *)key value:(NSString *)value; +- (void)addParameter:(NSString *)key value:(id )value; { CFStringRef escapedKey = CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)key, NULL, CFSTR("+=&#"), kCFStringEncodingUTF8); // Escape + for safety as some backends interpret it as a space @@ -170,7 +170,7 @@ - (void)addParameter:(NSString *)key value:(NSString *)value; // & indicates the start of next parameter, so must be escaped // # indicates the start of fragment, so must be escaped - CFStringRef escapedValue = CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)value, NULL, CFSTR("+&#"), kCFStringEncodingUTF8); + CFStringRef escapedValue = CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)value.description, NULL, CFSTR("+&#"), kCFStringEncodingUTF8); // Escape + for safety as some backends interpret it as a space // = is allowed in values, as there's no further value to indicate // & indicates the start of next parameter, so must be escaped From ca6868a946c675a6c95dd5fce61f851cb815b32f Mon Sep 17 00:00:00 2001 From: Mike Abdullah Date: Mon, 16 Dec 2013 09:07:22 +0000 Subject: [PATCH 42/44] -setParameters: encodes in alphabetical order It's consistent rather than random --- KSURLQuery.h | 7 ++++--- KSURLQuery.m | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/KSURLQuery.h b/KSURLQuery.h index ff6ab34..51fc46a 100644 --- a/KSURLQuery.h +++ b/KSURLQuery.h @@ -83,9 +83,10 @@ typedef NS_OPTIONS(NSUInteger, KSURLQueryParameterDecodingOptions) { See `-addParameter:value:` for full details on encoding of keys and values. - Since NSDictionary's API has no concept of ordering for keys, the order of the - parameters from calling this method is similarly undefined. If ordering is - important to your use case, use `-addParameter:value:` instead. + Parameters are encoded in alphabetical order (of keys) for consistency across + platforms and OS releases. If ordering is important to your use case, or you + particularly need to eke out a little more performance, use `-addParameter:value:` + directly instead. @param parameters A dictionary to encode, whose keys and values are all strings. */ diff --git a/KSURLQuery.m b/KSURLQuery.m index af92917..fb98afe 100644 --- a/KSURLQuery.m +++ b/KSURLQuery.m @@ -157,9 +157,10 @@ - (void)setParameters:(NSDictionary *)parameters; } // Build the list of parameters as a string - [parameters enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, BOOL *stop) { - [self addParameter:key value:value]; - }]; + for (NSString *aKey in [parameters.allKeys sortedArrayUsingSelector:@selector(compare:)]) + { + [self addParameter:aKey value:[parameters objectForKey:aKey]]; + } } - (void)addParameter:(NSString *)key value:(id )value; From 1bb3260323e8b065195ae978150e30598bea6183 Mon Sep 17 00:00:00 2001 From: Mike Abdullah Date: Tue, 19 Aug 2014 09:18:34 +0100 Subject: [PATCH 43/44] Test file:/// URLs briefly --- TestKSFileUtilities/TestKSURLUtilities.m | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/TestKSFileUtilities/TestKSURLUtilities.m b/TestKSFileUtilities/TestKSURLUtilities.m index 86ff9d4..b15da5e 100644 --- a/TestKSFileUtilities/TestKSURLUtilities.m +++ b/TestKSFileUtilities/TestKSURLUtilities.m @@ -142,6 +142,10 @@ - (void)testURLRelativeToURL [self checkURL:URL(@"http://example.com/foo/bar") relativeToURL:URL(@"http://example.com/bar/foo%2F/") againstExpectedResult:@"../../foo/bar"]; + // File URLs + [self checkURL:URL(@"file:///foo/bar/baz") relativeToURL:URL(@"file:///foo/bar/") againstExpectedResult:@"baz"]; + + // Crashed at one point STAssertEqualObjects([URL(@"") ks_stringRelativeToURL:URL(@"http://example.com/foo/")], nil, nil); From c84e76b9732efac28834d1d50b4410297f27b055 Mon Sep 17 00:00:00 2001 From: Mike Abdullah Date: Tue, 19 Aug 2014 09:28:56 +0100 Subject: [PATCH 44/44] Handle file:/// URLs specially --- KSURLUtilities.m | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/KSURLUtilities.m b/KSURLUtilities.m index f28fc6b..82f3625 100644 --- a/KSURLUtilities.m +++ b/KSURLUtilities.m @@ -266,13 +266,29 @@ - (NSString *)ks_stringRelativeToURL:(NSURL *)URL NSString *myHost = [self host]; if (!myHost) { + // Host-less file URLs get special treatment, as if they're localhost. Maybe that could/should + // be generalised but I'm not in a position to presently. + if (self.isFileURL) { + myHost = @"localhost"; + } + else { + // If self is an empty URL, there's no way to get to it. Falls through to here; return nil NSString *result = [self absoluteString]; return ([result length] ? result : nil); + } } NSString *otherHost = [URL host]; - if (!otherHost) BAIL; + if (!otherHost) { + // Host-less file URLs get special treatment, as if they're localhost + if (URL.isFileURL) { + otherHost = @"localhost"; + } + else { + BAIL; + } + } if ([myHost caseInsensitiveCompare:otherHost] != NSOrderedSame) BAIL;