nxtbgthng / OAuth2Client

Client library for OAuth2 (currently built against draft 10 of the OAuth2 spec)
855 stars 217 forks source link

Option to send POST requests as application/x-www-form-urlencoded #102

Closed pszalko closed 10 years ago

pszalko commented 10 years ago

Currently NXOAuth2Connection always sends POST requests as multipart/form-data.

We had use case where OAuth2 server didn't accept multipart/form-data but required Content-Type set to application/x-www-form-urlencoded.

In current OAuth2Client implementation there is no easy way to change how POST requests are send to server.

It would be very helpful to allow developers to change Content-Type of POST requests to application/x-www-form-urlencoded.

kneeraj1762 commented 10 years ago

I have similar requirement how did you do it?

pszalko commented 10 years ago

I have changed NXOAuth2Connection.m file, here is my applyParameters:onRequest: method:

- (void)applyParameters:(NSDictionary *)parameters onRequest:(NSMutableURLRequest *)aRequest;
{
    if (!parameters) return;

    NSString *httpMethod = [aRequest HTTPMethod];
    if ([httpMethod caseInsensitiveCompare:@"POST"] != NSOrderedSame
        && [httpMethod caseInsensitiveCompare:@"PUT"] != NSOrderedSame) {
        aRequest.URL = [aRequest.URL nxoauth2_URLByAddingParameters:parameters];
    } else {

        // original code: HTTP multipart/form-data
//        NSInputStream *postBodyStream = [[NXOAuth2PostBodyStream alloc] initWithParameters:parameters];        
//        NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", [(NXOAuth2PostBodyStream *)postBodyStream boundary]];
//        NSString *contentLength = [NSString stringWithFormat:@"%lld", [(NXOAuth2PostBodyStream *)postBodyStream length]];
//        [aRequest setValue:contentType forHTTPHeaderField:@"Content-Type"];
//        [aRequest setValue:contentLength forHTTPHeaderField:@"Content-Length"];
//        
//        [aRequest setHTTPBodyStream:postBodyStream];

        // fix for oAuth Token request: Changed HTTP Content-Type to application/x-www-form-urlencoded
        // by (Przemek Szalko <przemek@mobtitude.com>)
        NSData *httpBody = [[parameters urlEncode] dataUsingEncoding:NSUTF8StringEncoding];

        NSString *contentType = [NSString stringWithFormat:@"application/x-www-form-urlencoded"];
        NSString *contentLength = [NSString stringWithFormat:@"%d", [httpBody length]];
        [aRequest setValue:contentType forHTTPHeaderField:@"Content-Type"];
        [aRequest setValue:contentLength forHTTPHeaderField:@"Content-Length"];
        [aRequest setHTTPBody:httpBody];
    }
}

It uses my custom NSDictionary category which encodes all entries from dictionary to HTTP Body string: [parameters urlEncode].

You need to encode all types of possible values in dictionary, for NSString the implementation is as follow:

// Encode a string to embed in an URL.
- (NSString*)urlEncode
{
   return (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(NULL,
                                                                                (CFStringRef) self,
                                                                                NULL,
                                                                                (CFStringRef) @"!*'();:@&=+$,/?%# ",
                                                                                kCFStringEncodingUTF8));
}

For NSArray and embedded NSDictionary I just iterate recursively through each item in dictionary or array and use the above code to escape NSStrings.

Implementation of urlEncode in NSDictionary:

- (NSString*)urlEncode
{
   return [[self encodeHttpBodyParamsWithParentKey:nil] componentsJoinedByString:@"&"];
}

- (NSArray*)encodeHttpBodyParamsWithParentKey:(NSString*)parentKey
{
   NSMutableArray *encodedParams = [NSMutableArray arrayWithCapacity:[self count]];

   for(NSString *dictKey in [self allKeys]) {

      id value = [self objectForKey:dictKey];
      NSString *key = dictKey;
      NSString *encodedValue;

      if([parentKey length] > 0) {
         key = [parentKey stringByAppendingFormat:@"[%@]", key];
      }

      // NSDictionary
      if([value isKindOfClass:[NSDictionary class]]) {
         NSArray *temp = [(NSDictionary*)value encodeHttpBodyParamsWithParentKey:dictKey];
         [encodedParams addObjectsFromArray:temp];

      // NSArray
      } else if([value isKindOfClass:[NSArray class]]) {
         NSArray *arrayValue = (NSArray*)value;
         NSArray *temp = [arrayValue encodeHttpBodyParamsWithKey:key];
         [encodedParams addObjectsFromArray:temp];

      // NSSet
      } else if([value isKindOfClass:[NSSet class]]) {
         NSArray *temp = [[value allObjects] encodeHttpBodyParamsWithKey:key];
         [encodedParams addObjectsFromArray:temp];

      // NSNull
      } else if([value isKindOfClass:[NSNull class]]) {
         [encodedParams addObject:[NSString stringWithFormat:@"%@=NULL", key]];

      // NSString & Others
      } else {
         encodedValue = [[value description] urlEncode];
         [encodedParams addObject:[NSString stringWithFormat:@"%@=%@", key, encodedValue]];
      }

   }

   return encodedParams;
}

Implementations of method urlEncode in NSArray and NSSet are almost identical.

akitchen commented 10 years ago

Seems like this is supported now. can this issue be closed?

toto commented 10 years ago

You are right :-)