Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement the application/x-www-form-urlencoded format for URL query components #291

Merged
merged 2 commits into from
Sep 19, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion Source/OIDURLQueryComponent.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,15 @@ NS_ASSUME_NONNULL_BEGIN
*/
extern BOOL gOIDURLQueryComponentForceIOS7Handling;

/*! @brief A utility class for creating and parsing URL query components.
/*! @brief A utility class for creating and parsing URL query components encoded with the
application/x-www-form-urlencoded format.
@description Supports application/x-www-form-urlencoded encoding and decoding, specifically
'+' is replaced with space before percent decoding. For encoding, simply percent encodes
space, as this is valid application/x-www-form-urlencoded.
@see https://tools.ietf.org/html/rfc6749#section-4.1.2
@see https://tools.ietf.org/html/rfc6749#section-4.1.3
@see https://tools.ietf.org/html/rfc6749#appendix-B
@see https://url.spec.whatwg.org/#urlencoded-parsing
*/
@interface OIDURLQueryComponent : NSObject {
// private variables
Expand Down
10 changes: 10 additions & 0 deletions Source/OIDURLQueryComponent.m
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ - (nullable instancetype)initWithURL:(NSURL *)URL {
if (!gOIDURLQueryComponentForceIOS7Handling) {
NSURLComponents *components =
[NSURLComponents componentsWithURL:URL resolvingAgainstBaseURL:NO];
// As OAuth uses application/x-www-form-urlencoded encoding, interprets '+' as a space
// in addition to regular percent decoding. https://url.spec.whatwg.org/#urlencoded-parsing
components.percentEncodedQuery =
[components.percentEncodedQuery stringByReplacingOccurrencesOfString:@"+"
withString:@"%20"];
// NB. @c queryItems are already percent decoded
NSArray<NSURLQueryItem *> *queryItems = components.queryItems;
for (NSURLQueryItem *queryItem in queryItems) {
[self addParameter:queryItem.name value:queryItem.value];
Expand All @@ -54,6 +60,10 @@ - (nullable instancetype)initWithURL:(NSURL *)URL {

// Fallback for iOS 7
NSString *query = URL.query;
// As OAuth uses application/x-www-form-urlencoded encoding, interprets '+' as a space
// in addition to regular percent decoding. https://url.spec.whatwg.org/#urlencoded-parsing
query = [query stringByReplacingOccurrencesOfString:@"+" withString:@"%20"];

NSArray<NSString *> *queryParts = [query componentsSeparatedByString:@"&"];
for (NSString *queryPart in queryParts) {
NSRange equalsRange = [queryPart rangeOfString:@"="];
Expand Down
82 changes: 82 additions & 0 deletions UnitTests/OIDURLQueryComponentTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,88 @@ - (void)testAddingParameter {
kTestParameterValue, @"");
}

/*! @brief Test that URI query items are decoded correctly, using application/x-www-form-urlencoded
encoding.
@see https://tools.ietf.org/html/rfc6749#section-4.1.2
@see https://tools.ietf.org/html/rfc6749#appendix-B
*/
- (void)test_formurlencoded_decoding {
// Authorization response URL template
NSString *responseURLtemplate = @"com.example.apps.1234-tepulg5joaks7:/?state=z634l182&code=4/WQA"
"stm4iiN_0Qi-n4mEo-jL-85CvQ&scope=%@&authuser=0&session_state=ab78c20&prompt=consent#";

NSString *expectedDecodedScope =
@"https://www.example.com/auth/plus.me https://www.example.com/auth/userinfo.profile";

// Tests an encoded scope with a '+'-encoded space
{
NSString* encodedScope =
@"https://www.example.com/auth/plus.me+https://www.example.com/auth/userinfo.profile";
NSString *authorizationResponse = [NSString stringWithFormat:responseURLtemplate,encodedScope];
OIDURLQueryComponent *query =
[[OIDURLQueryComponent alloc] initWithURL:[NSURL URLWithString:authorizationResponse]];
NSString* value = [query valuesForParameter:@"scope"][0];
XCTAssertEqualObjects(value,
expectedDecodedScope,
@"Failed to decode scope with '+' delimiter");
}
// Tests an encoded scope with a '%20'-encoded space
{
NSString* encodedScope =
@"https://www.example.com/auth/plus.me%20https://www.example.com/auth/userinfo.profile";
NSString *authorizationResponse = [NSString stringWithFormat:responseURLtemplate,encodedScope];
OIDURLQueryComponent *query =
[[OIDURLQueryComponent alloc] initWithURL:[NSURL URLWithString:authorizationResponse]];
NSString* value = [query valuesForParameter:@"scope"][0];
XCTAssertEqualObjects(value,
expectedDecodedScope,
@"Failed to decode scope with '%%20' delimiter");
}
// Tests that the example string from RFC6749 Appendix B is decoded correctly
{
NSString* encodedScope = @"+%25%26%2B%C2%A3%E2%82%AC";
NSString *authorizationResponse = [NSString stringWithFormat:responseURLtemplate,encodedScope];
OIDURLQueryComponent *query =
[[OIDURLQueryComponent alloc] initWithURL:[NSURL URLWithString:authorizationResponse]];
NSString* value = [query valuesForParameter:@"scope"][0];
XCTAssertEqualObjects(value,
@" %&+£€",
@"Failed to decode RFC6749 Appendix B sample string correctly.");
}
}

/*! @brief Test that URI query items are encoded correctly, using application/x-www-form-urlencoded
encoding. Note that AppAuth always encodes "+" as "%20" (as permitted) to reduce
ambiguity.
@see https://tools.ietf.org/html/rfc6749#section-4.1.3
@see https://tools.ietf.org/html/rfc6749#appendix-B
*/
- (void)test_formurlencoded_encoding {
NSURL *baseURL = [NSURL URLWithString:kTestURLRoot];
// Tests that space is encoded as %20
{
OIDURLQueryComponent *query = [[OIDURLQueryComponent alloc] initWithURL:baseURL];
[query addParameter:@"scope" value:@"openid profile"];
NSString *encodedParams = [query URLEncodedParameters];
NSString *expected = @"scope=openid%20profile";
XCTAssertEqualObjects(encodedParams,
expected,
@"Failed to encode space as %%20.");
}
// Tests that the example string from RFC6749 Appendix B is encoded correctly (but with space
// encoded as %20, not +, as allowed by application/x-www-form-urlencoded.
{
OIDURLQueryComponent *query = [[OIDURLQueryComponent alloc] initWithURL:baseURL];
[query addParameter:@"scope" value:@" %&+£€"];
// Tests the URLEncodedParameters method
NSString *encodedParams = [query URLEncodedParameters];
NSString *expected = @"scope=%20%25%26%2B%C2%A3%E2%82%AC";
XCTAssertEqualObjects(encodedParams,
expected,
@"Failed to encode RFC6749 Appendix B sample string correctly.");
}
}

- (void)testAddingTwoParameters {
OIDURLQueryComponent *query = [[OIDURLQueryComponent alloc] init];
[query addParameter:kTestParameterName value:kTestParameterValue];
Expand Down