NewsBlur/clients/ios/Other Sources/Overshare Kit/NSMutableURLRequest+OSKUtilities.m
2014-05-19 13:33:42 -07:00

264 lines
12 KiB
Objective-C
Executable file

//
// NSMutableURLRequest+OSKUtilities.m
// Overshare
//
// Created by Jared Sinclair on 10/24/13.
// Copyright (c) 2013 Overshare Kit. All rights reserved.
//
#import "NSMutableURLRequest+OSKUtilities.h"
#import "OSKLogger.h"
static NSString * OSKBoundaryString = @"OvershareKit-nT6YdBLrnos4eaUY";
static NSString * OSKCRLF = @"\r\n";
static NSString * kOSKCharactersToBeEscapedInQueryString = @":/?&=;+!@#$()',*";
static NSString * OSKPercentEscapedQueryStringKeyFromStringWithEncoding(NSString *string, NSStringEncoding encoding) {
static NSString * const kOSKCharactersToLeaveUnescapedInQueryStringPairKey = @"[].";
return (__bridge_transfer NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (__bridge CFStringRef)string, (__bridge CFStringRef)kOSKCharactersToLeaveUnescapedInQueryStringPairKey, (__bridge CFStringRef)kOSKCharactersToBeEscapedInQueryString, CFStringConvertNSStringEncodingToEncoding(encoding));
}
static NSString * OSKPercentEscapedQueryStringValueFromStringWithEncoding(NSString *string, NSStringEncoding encoding) {
return (__bridge_transfer NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (__bridge CFStringRef)string, NULL, (__bridge CFStringRef)kOSKCharactersToBeEscapedInQueryString, CFStringConvertNSStringEncodingToEncoding(encoding));
}
// ====================================================================================================
@interface OSKQueryStringPair : NSObject
@property (readwrite, nonatomic, strong) id field;
@property (readwrite, nonatomic, strong) id value;
- (id)initWithField:(id)field value:(id)value;
- (NSString *)URLEncodedStringValueWithEncoding:(NSStringEncoding)stringEncoding;
@end
@implementation OSKQueryStringPair
- (id)initWithField:(id)field value:(id)value {
self = [super init];
if (!self) {
return nil;
}
self.field = field;
self.value = value;
return self;
}
- (NSString *)URLEncodedStringValueWithEncoding:(NSStringEncoding)stringEncoding {
if (!self.value || [self.value isEqual:[NSNull null]]) {
return OSKPercentEscapedQueryStringKeyFromStringWithEncoding([self.field description], stringEncoding);
} else {
return [NSString stringWithFormat:@"%@=%@", OSKPercentEscapedQueryStringKeyFromStringWithEncoding([self.field description], stringEncoding), OSKPercentEscapedQueryStringValueFromStringWithEncoding([self.value description], stringEncoding)];
}
}
@end
// ====================================================================================================
extern NSArray * OSKQueryStringPairsFromDictionary(NSDictionary *dictionary);
extern NSArray * OSKQueryStringPairsFromKeyAndValue(NSString *key, id value);
NSArray * OSKQueryStringPairsFromDictionary(NSDictionary *dictionary) {
return OSKQueryStringPairsFromKeyAndValue(nil, dictionary);
}
NSArray * OSKQueryStringPairsFromKeyAndValue(NSString *key, id value) {
NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];
if ([value isKindOfClass:[NSDictionary class]]) {
NSDictionary *dictionary = value;
// 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:)];
for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
id nestedValue = [dictionary objectForKey:nestedKey];
if (nestedValue) {
[mutableQueryStringComponents addObjectsFromArray:OSKQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)];
}
}
} else if ([value isKindOfClass:[NSArray class]]) {
NSArray *array = value;
for (id nestedValue in array) {
[mutableQueryStringComponents addObjectsFromArray:OSKQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)];
}
} else if ([value isKindOfClass:[NSSet class]]) {
NSSet *set = value;
for (id obj in set) {
[mutableQueryStringComponents addObjectsFromArray:OSKQueryStringPairsFromKeyAndValue(key, obj)];
}
} else {
[mutableQueryStringComponents addObject:[[OSKQueryStringPair alloc] initWithField:key value:value]];
}
return mutableQueryStringComponents;
}
static NSString * OSKQueryStringFromParametersWithEncoding(NSDictionary *parameters, NSStringEncoding stringEncoding) {
NSMutableArray *mutablePairs = [NSMutableArray array];
for (OSKQueryStringPair *pair in OSKQueryStringPairsFromDictionary(parameters)) {
[mutablePairs addObject:[pair URLEncodedStringValueWithEncoding:stringEncoding]];
}
return [mutablePairs componentsJoinedByString:@"&"];
}
// ====================================================================================================
@implementation NSMutableURLRequest (OSKUtilities)
+ (NSMutableURLRequest *)osk_requestWithMethod:(NSString *)method URLString:(NSString *)URLString parameters:(NSDictionary *)parameters serialization:(OSKParameterSerializationType)serialization {
NSParameterAssert(method);
NSParameterAssert(URLString);
NSURL *url = [NSURL URLWithString:URLString];
NSParameterAssert(url);
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
[request setHTTPMethod:method];
if (serialization == OSKParameterSerializationType_HTTPBody_JSON) {
request = [[self osk_requestByJSONSerializingRequest:request withParameters:parameters error:nil] mutableCopy];
}
else if (serialization == OSKParameterSerializationType_HTTPBody_FormData) {
request = [[self osk_requestByHTTPSerializingRequest:request withParameters:parameters error:nil] mutableCopy];
}
else {
request = [[self osk_requestByQueryStringSerializingRequest:request withParameters:parameters error:nil] mutableCopy];
}
return request;
}
+ (NSURLRequest *)osk_requestByQueryStringSerializingRequest:(NSURLRequest *)request withParameters:(NSDictionary *)parameters error:(NSError *__autoreleasing *)error {
NSParameterAssert(request);
NSMutableURLRequest *mutableRequest = [request mutableCopy];
if (!parameters) {
return mutableRequest;
}
NSString *query = OSKQueryStringFromParametersWithEncoding(parameters, NSUTF8StringEncoding);
mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
return mutableRequest;
}
+ (NSURLRequest *)osk_requestByHTTPSerializingRequest:(NSURLRequest *)request withParameters:(NSDictionary *)parameters error:(NSError *__autoreleasing *)error {
NSParameterAssert(request);
NSMutableURLRequest *mutableRequest = [request mutableCopy];
if (!parameters) {
return mutableRequest;
}
NSString *query = OSKQueryStringFromParametersWithEncoding(parameters, NSUTF8StringEncoding);
[mutableRequest setHTTPBody:[query dataUsingEncoding:NSUTF8StringEncoding]];
[mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
return mutableRequest;
}
+ (NSURLRequest *)osk_requestByJSONSerializingRequest:(NSURLRequest *)request withParameters:(NSDictionary *)parameters error:(NSError *__autoreleasing *)error {
NSParameterAssert(request);
NSMutableURLRequest *mutableRequest = [request mutableCopy];
if (!parameters) {
return mutableRequest;
}
NSString *charset = (__bridge NSString *)CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding));
[mutableRequest setValue:[NSString stringWithFormat:@"application/json; charset=%@", charset] forHTTPHeaderField:@"Content-Type"];
[mutableRequest setHTTPBody:[NSJSONSerialization dataWithJSONObject:parameters options:0 error:error]];
return mutableRequest;
}
+ (NSMutableURLRequest *)osk_MultipartFormUploadRequestWithMethod:(NSString *)method URLString:(NSString *)URLstring parameters:(NSDictionary *)parameters uploadData:(NSData *)uploadData filename:(NSString *)filename formName:(NSString *)formName mimeType:(NSString *)mimeType serialization:(OSKParameterSerializationType)serialization bodyData:(NSData **)outputData{
NSMutableData *mutableBodyData = [NSMutableData data];
NSString *openingBoundary = [NSString stringWithFormat:@"%@--%@%@", OSKCRLF, OSKBoundaryString, OSKCRLF];
[mutableBodyData appendData:[openingBoundary dataUsingEncoding:NSUTF8StringEncoding]];
if (parameters) {
if (serialization == OSKParameterSerializationType_HTTPBody_JSON) {
NSString *contentDisposition = [NSString stringWithFormat:@"Content-Disposition: form-data; name=\"metadata\"; filename=\"metadata.json\"%@", OSKCRLF];
[mutableBodyData appendData:[contentDisposition dataUsingEncoding:NSUTF8StringEncoding]];
NSString *contentType = [NSString stringWithFormat:@"Content-Type: application/json%@%@", OSKCRLF, OSKCRLF];
[mutableBodyData appendData:[contentType dataUsingEncoding:NSUTF8StringEncoding]];
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:parameters options:0 error:nil];
if (jsonData) {
[mutableBodyData appendData:jsonData];
}
}
else if (serialization == OSKParameterSerializationType_HTTPBody_FormData) {
for (OSKQueryStringPair *pair in OSKQueryStringPairsFromDictionary(parameters)) {
NSString *escapedKey = OSKPercentEscapedQueryStringKeyFromStringWithEncoding(pair.field, NSUTF8StringEncoding);
NSString *escapedValue = OSKPercentEscapedQueryStringValueFromStringWithEncoding(pair.value, NSUTF8StringEncoding);
NSString *contentDisposition = [NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"%@", escapedKey, OSKCRLF];
[mutableBodyData appendData:[contentDisposition dataUsingEncoding:NSUTF8StringEncoding]];
[mutableBodyData appendData:[escapedValue dataUsingEncoding:NSUTF8StringEncoding]];
}
}
else {
NSAssert(NO, @"Multipart form data requests cannot use the query string parameter serialization type.");
}
NSString *closingBoundary = [NSString stringWithFormat:@"%@--%@", OSKCRLF, OSKBoundaryString];
[mutableBodyData appendData:[closingBoundary dataUsingEncoding:NSUTF8StringEncoding]];
}
if (uploadData) {
NSString *contentDisposition = [NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"%@", formName, filename, OSKCRLF];
[mutableBodyData appendData:[contentDisposition dataUsingEncoding:NSUTF8StringEncoding]];
NSString *contentType = [NSString stringWithFormat:@"Content-Type: %@%@%@", mimeType, OSKCRLF, OSKCRLF];
[mutableBodyData appendData:[contentType dataUsingEncoding:NSUTF8StringEncoding]];
[mutableBodyData appendData:[NSData dataWithData:uploadData]];
NSString *closingBoundary = [NSString stringWithFormat:@"%@--%@", OSKCRLF, OSKBoundaryString];
[mutableBodyData appendData:[closingBoundary dataUsingEncoding:NSUTF8StringEncoding]];
}
NSString *closingDashes = @"--";
[mutableBodyData appendData:[closingDashes dataUsingEncoding:NSUTF8StringEncoding]];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", OSKBoundaryString];
[request setValue:contentType forHTTPHeaderField:@"Content-Type"];
[request setURL:[NSURL URLWithString:URLstring]];
[request setHTTPMethod:method];
*outputData = [NSData dataWithData:mutableBodyData];
return request;
}
@end