From 180fc06b7a54cd48a9ed7ace661cc4cdd262a689 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Tue, 5 Jan 2016 12:50:25 -0800 Subject: [PATCH] Fixed iOS 7 support for URL query functions Summary: public Unfortunately, it turns out that NSURLComponents.queryItems only works on iOS 8 and above. This diff re-implements the RCTGetURLQueryParam and RCTURLByReplacingQueryParam functions using functionality available in iOS 7. Reviewed By: javache Differential Revision: D2803679 fb-gh-sync-id: 56f10bef4894d16197975b6023b7aa5ab106d8cb --- .../UIExplorerUnitTests/RCTURLUtilsTests.m | 14 +++++++ React/Base/RCTUtils.m | 40 +++++++++++++++---- 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTURLUtilsTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTURLUtilsTests.m index bb22b7eb41c5e5..2c23af2b992654 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTURLUtilsTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTURLUtilsTests.m @@ -30,6 +30,13 @@ - (void)testGetQueryParam XCTAssertEqualObjects(bar, @"foo"); } +- (void)testGetEncodedParam +{ + NSURL *URL = [NSURL URLWithString:@"http://example.com?foo=You%20%26%20Me"]; + NSString *foo = RCTGetURLQueryParam(URL, @"foo"); + XCTAssertEqualObjects(foo, @"You & Me"); +} + - (void)testQueryParamNotFound { NSURL *URL = [NSURL URLWithString:@"http://example.com?foo=bar"]; @@ -58,6 +65,13 @@ - (void)testReplaceParam XCTAssertEqualObjects(result.absoluteString, @"http://example.com?foo=foo&bar=foo"); } +- (void)testReplaceEncodedParam +{ + NSURL *URL = [NSURL URLWithString:@"http://example.com?foo=You%20%26%20Me"]; + NSURL *result = RCTURLByReplacingQueryParam(URL, @"foo", @"Me & You"); + XCTAssertEqualObjects(result.absoluteString, @"http://example.com?foo=Me%20%26%20You"); +} + - (void)testAppendParam { NSURL *URL = [NSURL URLWithString:@"http://example.com?bar=foo"]; diff --git a/React/Base/RCTUtils.m b/React/Base/RCTUtils.m index b3a7a6e41b5462..5a83b8adb029e2 100644 --- a/React/Base/RCTUtils.m +++ b/React/Base/RCTUtils.m @@ -591,9 +591,13 @@ static void RCTGetRGBAColorComponents(CGColorRef color, CGFloat rgba[4]) } NSURLComponents *components = [NSURLComponents componentsWithURL:URL resolvingAgainstBaseURL:YES]; - for (NSURLQueryItem *item in components.queryItems.reverseObjectEnumerator) { - if ([item.name isEqualToString:param]) { - return item.value; + + // TODO: use NSURLComponents.queryItems once we drop support for iOS 7 + for (NSString *item in [components.percentEncodedQuery componentsSeparatedByString:@"&"].reverseObjectEnumerator) { + NSArray *keyValue = [item componentsSeparatedByString:@"="]; + NSString *key = [keyValue.firstObject stringByRemovingPercentEncoding]; + if ([key isEqualToString:param]) { + return [keyValue.lastObject stringByRemovingPercentEncoding]; } } return nil; @@ -607,22 +611,42 @@ static void RCTGetRGBAColorComponents(CGColorRef color, CGFloat rgba[4]) } NSURLComponents *components = [NSURLComponents componentsWithURL:URL resolvingAgainstBaseURL:YES]; + + // TODO: use NSURLComponents.queryItems once we drop support for iOS 7 + + // Unhelpfully, iOS doesn't provide this set as a constant + static NSCharacterSet *URLParamCharacterSet; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSMutableCharacterSet *characterSet = [NSMutableCharacterSet new]; + [characterSet formUnionWithCharacterSet:[NSCharacterSet URLQueryAllowedCharacterSet]]; + [characterSet removeCharactersInString:@"&=?"]; + URLParamCharacterSet = [characterSet copy]; + }); + + NSString *encodedParam = + [param stringByAddingPercentEncodingWithAllowedCharacters:URLParamCharacterSet]; + __block NSInteger paramIndex = NSNotFound; - NSMutableArray *queryItems = [components.queryItems mutableCopy]; + NSMutableArray *queryItems = [[components.percentEncodedQuery componentsSeparatedByString:@"&"] mutableCopy]; [queryItems enumerateObjectsWithOptions:NSEnumerationReverse usingBlock: - ^(NSURLQueryItem *item, NSUInteger i, BOOL *stop) { - if ([item.name isEqualToString:param]) { + ^(NSString *item, NSUInteger i, BOOL *stop) { + NSArray *keyValue = [item componentsSeparatedByString:@"="]; + if ([keyValue.firstObject isEqualToString:encodedParam]) { paramIndex = i; *stop = YES; } }]; - NSURLQueryItem *newItem = [NSURLQueryItem queryItemWithName:param value:value]; + NSString *encodedValue = + [value stringByAddingPercentEncodingWithAllowedCharacters:URLParamCharacterSet]; + + NSString *newItem = [encodedParam stringByAppendingFormat:@"=%@", encodedValue]; if (paramIndex == NSNotFound) { [queryItems addObject:newItem]; } else { [queryItems replaceObjectAtIndex:paramIndex withObject:newItem]; } - components.queryItems = queryItems; + components.percentEncodedQuery = [queryItems componentsJoinedByString:@"&"]; return components.URL; }