diff --git a/AFNetworking/AFHTTPClient.h b/AFNetworking/AFHTTPClient.h index 4bf9662..2969c8c 100644 --- a/AFNetworking/AFHTTPClient.h +++ b/AFNetworking/AFHTTPClient.h @@ -113,6 +113,8 @@ typedef enum { /** The `AFHTTPClientParameterEncoding` value corresponding to how parameters are encoded into a request body. This is `AFFormURLParameterEncoding` by default. + + @warning Some nested parameter structures, such as a keyed array of hashes containing inconsistent keys (i.e. `@{@"": @[@{@"a" : @(1)}, @{@"b" : @(2)}]}`), cannot be unambiguously represented in query strings. It is strongly recommended that an unambiguous encoding, such as `AFJSONParameterEncoding`, is used when posting complicated or nondeterministic parameter structures. */ @property (nonatomic, assign) AFHTTPClientParameterEncoding parameterEncoding; @@ -461,7 +463,7 @@ typedef enum { } `AFFormURLParameterEncoding` - Parameters are encoded into field/key pairs in the URL query string for `GET` `HEAD` and `DELETE` requests, and in the message body otherwise. + Parameters are encoded into field/key pairs in the URL query string for `GET` `HEAD` and `DELETE` requests, and in the message body otherwise. Dictionary keys are sorted with the `caseInsensitiveCompare:` selector of their description, in order to mitigate the possibility of ambiguous query strings being generated non-deterministically. See the warning for the `parameterEncoding` property for additional information. `AFJSONParameterEncoding` Parameters are encoded into JSON in the message body. diff --git a/AFNetworking/AFHTTPClient.m b/AFNetworking/AFHTTPClient.m index 98c574e..d302478 100644 --- a/AFNetworking/AFHTTPClient.m +++ b/AFNetworking/AFHTTPClient.m @@ -146,8 +146,13 @@ NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) { NSMutableArray *mutableQueryStringComponents = [NSMutableArray array]; if([value isKindOfClass:[NSDictionary class]]) { - [value enumerateKeysAndObjectsUsingBlock:^(id nestedKey, id nestedValue, BOOL *stop) { - [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)]; + // Sort dictionary keys to ensure consistent ordering in query string, which is important when deserializing potentially ambiguous sequences, such as an array of dictionaries + NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(caseInsensitiveCompare:)]; + [[[value allKeys] sortedArrayUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]] enumerateObjectsUsingBlock:^(id nestedKey, NSUInteger idx, BOOL *stop) { + id nestedValue = [value objectForKey:nestedKey]; + if (nestedValue) { + [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)]; + } }]; } else if([value isKindOfClass:[NSArray class]]) { [value enumerateObjectsUsingBlock:^(id nestedValue, NSUInteger idx, BOOL *stop) {