Skip to content
This repository has been archived by the owner on Jan 17, 2023. It is now read-only.

Commit

Permalink
[Issue #243] Fixing AFQueryStringFromParametersWithEncoding to encode…
Browse files Browse the repository at this point in the history
… nested array values correctly
  • Loading branch information
mattt committed Mar 19, 2012
1 parent 34b7d01 commit acd6e49
Showing 1 changed file with 82 additions and 50 deletions.
132 changes: 82 additions & 50 deletions AFNetworking/AFHTTPClient.m
Expand Up @@ -96,7 +96,7 @@ - (id)initWithStringEncoding:(NSStringEncoding)encoding;
}

NSString * AFURLEncodedStringFromStringWithEncoding(NSString *string, NSStringEncoding encoding) {
static NSString * const kAFLegalCharactersToBeEscaped = @"?!@#$^&%*+,:;='\"`<>()[]{}/\\|~ ";
static NSString * const kAFLegalCharactersToBeEscaped = @"?!@#$^&%*+=,:;'\"`<>()[]{}/\\|~ ";

/*
The documentation for `CFURLCreateStringByAddingPercentEscapes` suggests that one should "pre-process" URL strings with unpredictable sequences that may already contain percent escapes. However, if the string contains an unescaped sequence with '%' appearing without an escape code (such as when representing percentages like "42%"), `stringByReplacingPercentEscapesUsingEncoding` will return `nil`. Thus, the string is only unescaped if there are no invalid percent-escaped sequences.
Expand All @@ -109,61 +109,101 @@ - (id)initWithStringEncoding:(NSStringEncoding)encoding;
return [(NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)string, NULL, (CFStringRef)kAFLegalCharactersToBeEscaped, CFStringConvertNSStringEncodingToEncoding(encoding)) autorelease];
}

extern NSDictionary * AFQueryParametersFromParametersAtBaseKeyWithEncoding(id parameters, NSString *baseKey);
extern NSDictionary * AFQueryParametersFromParametersDictionaryAtBaseKeyWithEncoding(NSDictionary *parameters, NSString *baseKey);
extern NSDictionary * AFQueryParametersFromParametersArrayAtBaseKeyWithEncoding(NSArray *parameters, NSString *baseKey);
extern NSDictionary * AFQueryStringComponentFromParameterAtBaseKeyWithEncoding(id parameter, NSString *key);
#pragma mark -

NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *parameters, NSStringEncoding encoding) {
NSMutableArray *queryStringArray = [NSMutableArray array];

[AFQueryParametersFromParametersAtBaseKeyWithEncoding(parameters, nil) enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
[queryStringArray addObject:[NSString stringWithFormat:@"%@=%@", AFURLEncodedStringFromStringWithEncoding([key description], encoding), AFURLEncodedStringFromStringWithEncoding([obj description], encoding)]];
}];

return [queryStringArray componentsJoinedByString:@"&"];
@interface AFQueryStringComponent : NSObject {
@private
NSString *_key;
NSString *_value;
}

@property (readwrite, nonatomic, retain) id key;
@property (readwrite, nonatomic, retain) id value;

- (id)initWithKey:(NSString *)key value:(NSString *)value;

@end

@implementation AFQueryStringComponent
@synthesize key = _key;
@synthesize value = _value;

- (id)initWithKey:(NSString *)key value:(NSString *)value {
self = [super init];
if (!self) {
return nil;
}

self.key = key;
self.value = value;

return self;
}

- (void)dealloc {
[_key release];
[_value release];
[super dealloc];
}

- (NSString *)description {
return [NSString stringWithFormat:@"%@=%@", self.key, self.value];
}

@end

#pragma mark -

extern NSArray * AFQueryStringComponentsFromParametersAtBaseKeyWithEncoding(id parameters, NSString *baseKey, NSStringEncoding stringEncoding);
extern NSArray * AFQueryStringComponentsFromParametersDictionaryAtBaseKeyWithEncoding(NSDictionary *parameters, NSString *baseKey, NSStringEncoding stringEncoding);
extern NSArray * AFQueryStringComponentsFromParametersArrayAtBaseKeyWithEncoding(NSArray *parameters, NSString *baseKey, NSStringEncoding stringEncoding);

NSString * AFQueryStringFromParametersWithEncoding(id parameters, NSStringEncoding stringEncoding) {
return [[AFQueryStringComponentsFromParametersAtBaseKeyWithEncoding(parameters, nil, stringEncoding) valueForKeyPath:@"description"] componentsJoinedByString:@"&"];
}

NSDictionary * AFQueryParametersFromParametersAtBaseKeyWithEncoding(id parameters, NSString *baseKey) {
NSMutableDictionary *mutableParameterComponents = [NSMutableDictionary dictionary];
AFQueryStringComponent * AFQueryStringComponentFromKeyAndValueWithEncoding(id key, id value, NSStringEncoding stringEncoding) {
return [[[AFQueryStringComponent alloc] initWithKey:AFURLEncodedStringFromStringWithEncoding([key description], stringEncoding) value:AFURLEncodedStringFromStringWithEncoding([value description], stringEncoding)] autorelease];
}

NSArray * AFQueryStringComponentsFromParametersAtBaseKeyWithEncoding(id parameters, NSString *baseKey, NSStringEncoding stringEncoding) {
NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];

if([parameters isKindOfClass:[NSDictionary class]]) {
[mutableParameterComponents addEntriesFromDictionary:AFQueryParametersFromParametersDictionaryAtBaseKeyWithEncoding(parameters, baseKey)];
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringComponentsFromParametersDictionaryAtBaseKeyWithEncoding(parameters, baseKey, stringEncoding)];
} else if([parameters isKindOfClass:[NSArray class]]) {
[mutableParameterComponents addEntriesFromDictionary:AFQueryParametersFromParametersArrayAtBaseKeyWithEncoding(parameters, baseKey)];
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringComponentsFromParametersArrayAtBaseKeyWithEncoding(parameters, baseKey, stringEncoding)];
} else {
[mutableParameterComponents addEntriesFromDictionary:AFQueryStringComponentFromParameterAtBaseKeyWithEncoding(parameters, baseKey)];
[mutableQueryStringComponents addObject:AFQueryStringComponentFromKeyAndValueWithEncoding(baseKey, parameters, stringEncoding)];
}

return mutableParameterComponents;
return mutableQueryStringComponents;
}

NSDictionary * AFQueryParametersFromParametersDictionaryAtBaseKeyWithEncoding(NSDictionary *parameters, NSString *baseKey){
NSMutableDictionary *mutableParameterComponents = [NSMutableDictionary dictionary];
NSArray * AFQueryStringComponentsFromParametersDictionaryAtBaseKeyWithEncoding(NSDictionary *parameters, NSString *baseKey, NSStringEncoding stringEncoding){
NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];

id key = nil;
NSEnumerator *enumerator = [parameters keyEnumerator];
while ((key = [enumerator nextObject])) {
[parameters enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop) {
NSString *nextKey = baseKey ? [NSString stringWithFormat:@"%@[%@]", baseKey, key] : key;
[mutableParameterComponents addEntriesFromDictionary:AFQueryParametersFromParametersAtBaseKeyWithEncoding([parameters valueForKey:key], nextKey)];
}

return mutableParameterComponents;
if (nextKey && value) {
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringComponentsFromParametersAtBaseKeyWithEncoding(value, nextKey, stringEncoding)];
}
}];

return mutableQueryStringComponents;
}

NSDictionary * AFQueryParametersFromParametersArrayAtBaseKeyWithEncoding(NSArray *parameters, NSString *baseKey) {
NSMutableDictionary *mutableParameterComponents = [NSMutableDictionary dictionary];
NSArray * AFQueryStringComponentsFromParametersArrayAtBaseKeyWithEncoding(NSArray *parameters, NSString *baseKey, NSStringEncoding stringEncoding) {
NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];

for (id value in parameters) {
[parameters enumerateObjectsUsingBlock:^(id value, NSUInteger idx, BOOL *stop) {
NSString *nextKey = [NSString stringWithFormat:@"%@[]", baseKey];
[mutableParameterComponents addEntriesFromDictionary:AFQueryParametersFromParametersAtBaseKeyWithEncoding(value, nextKey)];
}

return mutableParameterComponents;
}
if (nextKey && value) {
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringComponentsFromParametersAtBaseKeyWithEncoding(value, nextKey, stringEncoding)];
}
}];

NSDictionary * AFQueryStringComponentFromParameterAtBaseKeyWithEncoding(id parameter, NSString *key) {
return [NSDictionary dictionaryWithObject:parameter forKey:key];
return mutableQueryStringComponents;
}

static NSString * AFJSONStringFromParameters(NSDictionary *parameters) {
Expand Down Expand Up @@ -398,18 +438,10 @@ - (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
NSMutableURLRequest *request = [self requestWithMethod:method path:path parameters:nil];
__block AFMultipartFormData *formData = [[AFMultipartFormData alloc] initWithStringEncoding:self.stringEncoding];

[AFQueryParametersFromParametersDictionaryAtBaseKeyWithEncoding(parameters, nil) enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop) {
NSData *data = nil;

if ([value isKindOfClass:[NSData class]]) {
data = value;
} else {
data = [[value description] dataUsingEncoding:self.stringEncoding];
}

[formData appendPartWithFormData:data name:[key description]];
}];

for (AFQueryStringComponent *component in AFQueryStringComponentsFromParametersAtBaseKeyWithEncoding(parameters, nil, self.stringEncoding)) {
[formData appendPartWithFormData:[component.value dataUsingEncoding:self.stringEncoding] name:component.key];
}

if (block) {
block(formData);
}
Expand Down

3 comments on commit acd6e49

@mhausherr
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello,

I didn't agree with this commit:

Line 166
return [[[AFQueryStringComponent alloc] initWithKey:AFURLEncodedStringFromStringWithEncoding([key description], stringEncoding) value:AFURLEncodedStringFromStringWithEncoding([value description], stringEncoding)] autorelease];
Must be
return [[[AFQueryStringComponent alloc] initWithKey:[key description] value:AFURLEncodedStringFromStringWithEncoding([value description], stringEncoding)] autorelease];

AFURLEncodedStringFromStringWithEncoding encode the '[' and ']' chars to '%5B' and '%5D' in the keys.

@mattt
Copy link
Contributor Author

@mattt mattt commented on acd6e49 Mar 27, 2012

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't necessarily disagree, but aren't the [ and ] characters equivalent to their percent-encoded representations on the server?

@mhausherr
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On a RoR server hosted by heroku, with a multipart request, a string-encoded Key doesn't work.
It's seems there's differences between multipart request and URL-encoded request.
I'll do some tests and come back.

Please sign in to comment.