diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTURLUtilsTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTURLUtilsTests.m index bb22b7eb4..2c23af2b9 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTURLUtilsTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTURLUtilsTests.m @@ -30,6 +30,13 @@ 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 @@ 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 b3a7a6e41..5a83b8adb 100644 --- a/React/Base/RCTUtils.m +++ b/React/Base/RCTUtils.m @@ -591,9 +591,13 @@ NSString *RCTGetURLQueryParam(NSURL *URL, NSString *param) } 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 @@ NSURL *RCTURLByReplacingQueryParam(NSURL *URL, NSString *param, NSString *value) } 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; }