| // AFURLRequestSerialization.m |
| // Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ ) |
| // |
| // Permission is hereby granted, free of charge, to any person obtaining a copy |
| // of this software and associated documentation files (the "Software"), to deal |
| // in the Software without restriction, including without limitation the rights |
| // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| // copies of the Software, and to permit persons to whom the Software is |
| // furnished to do so, subject to the following conditions: |
| // |
| // The above copyright notice and this permission notice shall be included in |
| // all copies or substantial portions of the Software. |
| // |
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| // THE SOFTWARE. |
| |
| #import "AFURLRequestSerialization.h" |
| |
| #if TARGET_OS_IOS || TARGET_OS_WATCH || TARGET_OS_TV |
| #import <MobileCoreServices/MobileCoreServices.h> |
| #else |
| #import <CoreServices/CoreServices.h> |
| #endif |
| |
| NSString * const AFURLRequestSerializationErrorDomain = @"com.alamofire.error.serialization.request"; |
| NSString * const AFNetworkingOperationFailingURLRequestErrorKey = @"com.alamofire.serialization.request.error.response"; |
| |
| typedef NSString * (^AFQueryStringSerializationBlock)(NSURLRequest *request, id parameters, NSError *__autoreleasing *error); |
| |
| /** |
| Returns a percent-escaped string following RFC 3986 for a query string key or value. |
| RFC 3986 states that the following characters are "reserved" characters. |
| - General Delimiters: ":", "#", "[", "]", "@", "?", "/" |
| - Sub-Delimiters: "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "=" |
| |
| In RFC 3986 - Section 3.4, it states that the "?" and "/" characters should not be escaped to allow |
| query strings to include a URL. Therefore, all "reserved" characters with the exception of "?" and "/" |
| should be percent-escaped in the query string. |
| - parameter string: The string to be percent-escaped. |
| - returns: The percent-escaped string. |
| */ |
| NSString * AFPercentEscapedStringFromString(NSString *string) { |
| static NSString * const kAFCharactersGeneralDelimitersToEncode = @":#[]@"; // does not include "?" or "/" due to RFC 3986 - Section 3.4 |
| static NSString * const kAFCharactersSubDelimitersToEncode = @"!$&'()*+,;="; |
| |
| NSMutableCharacterSet * allowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy]; |
| [allowedCharacterSet removeCharactersInString:[kAFCharactersGeneralDelimitersToEncode stringByAppendingString:kAFCharactersSubDelimitersToEncode]]; |
| |
| // FIXME: https://github.com/AFNetworking/AFNetworking/pull/3028 |
| // return [string stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet]; |
| |
| static NSUInteger const batchSize = 50; |
| |
| NSUInteger index = 0; |
| NSMutableString *escaped = @"".mutableCopy; |
| |
| while (index < string.length) { |
| NSUInteger length = MIN(string.length - index, batchSize); |
| NSRange range = NSMakeRange(index, length); |
| |
| // To avoid breaking up character sequences such as 👴🏻👮🏽 |
| range = [string rangeOfComposedCharacterSequencesForRange:range]; |
| |
| NSString *substring = [string substringWithRange:range]; |
| NSString *encoded = [substring stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet]; |
| [escaped appendString:encoded]; |
| |
| index += range.length; |
| } |
| |
| return escaped; |
| } |
| |
| #pragma mark - |
| |
| @interface AFQueryStringPair : NSObject |
| @property (readwrite, nonatomic, strong) id field; |
| @property (readwrite, nonatomic, strong) id value; |
| |
| - (instancetype)initWithField:(id)field value:(id)value; |
| |
| - (NSString *)URLEncodedStringValue; |
| @end |
| |
| @implementation AFQueryStringPair |
| |
| - (instancetype)initWithField:(id)field value:(id)value { |
| self = [super init]; |
| if (!self) { |
| return nil; |
| } |
| |
| self.field = field; |
| self.value = value; |
| |
| return self; |
| } |
| |
| - (NSString *)URLEncodedStringValue { |
| if (!self.value || [self.value isEqual:[NSNull null]]) { |
| return AFPercentEscapedStringFromString([self.field description]); |
| } else { |
| return [NSString stringWithFormat:@"%@=%@", AFPercentEscapedStringFromString([self.field description]), AFPercentEscapedStringFromString([self.value description])]; |
| } |
| } |
| |
| @end |
| |
| #pragma mark - |
| |
| FOUNDATION_EXPORT NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary); |
| FOUNDATION_EXPORT NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value); |
| |
| NSString * AFQueryStringFromParameters(NSDictionary *parameters) { |
| NSMutableArray *mutablePairs = [NSMutableArray array]; |
| for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) { |
| [mutablePairs addObject:[pair URLEncodedStringValue]]; |
| } |
| |
| return [mutablePairs componentsJoinedByString:@"&"]; |
| } |
| |
| NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) { |
| return AFQueryStringPairsFromKeyAndValue(nil, dictionary); |
| } |
| |
| NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) { |
| NSMutableArray *mutableQueryStringComponents = [NSMutableArray array]; |
| |
| NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)]; |
| |
| 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 |
| for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) { |
| id nestedValue = dictionary[nestedKey]; |
| if (nestedValue) { |
| [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)]; |
| } |
| } |
| } else if ([value isKindOfClass:[NSArray class]]) { |
| NSArray *array = value; |
| for (id nestedValue in array) { |
| [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)]; |
| } |
| } else if ([value isKindOfClass:[NSSet class]]) { |
| NSSet *set = value; |
| for (id obj in [set sortedArrayUsingDescriptors:@[ sortDescriptor ]]) { |
| [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue(key, obj)]; |
| } |
| } else { |
| [mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]]; |
| } |
| |
| return mutableQueryStringComponents; |
| } |
| |
| #pragma mark - |
| |
| @interface AFStreamingMultipartFormData : NSObject <AFMultipartFormData> |
| - (instancetype)initWithURLRequest:(NSMutableURLRequest *)urlRequest |
| stringEncoding:(NSStringEncoding)encoding; |
| |
| - (NSMutableURLRequest *)requestByFinalizingMultipartFormData; |
| @end |
| |
| #pragma mark - |
| |
| static NSArray * AFHTTPRequestSerializerObservedKeyPaths() { |
| static NSArray *_AFHTTPRequestSerializerObservedKeyPaths = nil; |
| static dispatch_once_t onceToken; |
| dispatch_once(&onceToken, ^{ |
| _AFHTTPRequestSerializerObservedKeyPaths = @[NSStringFromSelector(@selector(allowsCellularAccess)), NSStringFromSelector(@selector(cachePolicy)), NSStringFromSelector(@selector(HTTPShouldHandleCookies)), NSStringFromSelector(@selector(HTTPShouldUsePipelining)), NSStringFromSelector(@selector(networkServiceType)), NSStringFromSelector(@selector(timeoutInterval))]; |
| }); |
| |
| return _AFHTTPRequestSerializerObservedKeyPaths; |
| } |
| |
| static void *AFHTTPRequestSerializerObserverContext = &AFHTTPRequestSerializerObserverContext; |
| |
| @interface AFHTTPRequestSerializer () |
| @property (readwrite, nonatomic, strong) NSMutableSet *mutableObservedChangedKeyPaths; |
| @property (readwrite, nonatomic, strong) NSMutableDictionary *mutableHTTPRequestHeaders; |
| @property (readwrite, nonatomic, assign) AFHTTPRequestQueryStringSerializationStyle queryStringSerializationStyle; |
| @property (readwrite, nonatomic, copy) AFQueryStringSerializationBlock queryStringSerialization; |
| @end |
| |
| @implementation AFHTTPRequestSerializer |
| |
| + (instancetype)serializer { |
| return [[self alloc] init]; |
| } |
| |
| - (instancetype)init { |
| self = [super init]; |
| if (!self) { |
| return nil; |
| } |
| |
| self.stringEncoding = NSUTF8StringEncoding; |
| |
| self.mutableHTTPRequestHeaders = [NSMutableDictionary dictionary]; |
| |
| // Accept-Language HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4 |
| NSMutableArray *acceptLanguagesComponents = [NSMutableArray array]; |
| [[NSLocale preferredLanguages] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { |
| float q = 1.0f - (idx * 0.1f); |
| [acceptLanguagesComponents addObject:[NSString stringWithFormat:@"%@;q=%0.1g", obj, q]]; |
| *stop = q <= 0.5f; |
| }]; |
| [self setValue:[acceptLanguagesComponents componentsJoinedByString:@", "] forHTTPHeaderField:@"Accept-Language"]; |
| |
| NSString *userAgent = nil; |
| #if TARGET_OS_IOS |
| // User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43 |
| userAgent = [NSString stringWithFormat:@"%@/%@ (%@; iOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[UIDevice currentDevice] model], [[UIDevice currentDevice] systemVersion], [[UIScreen mainScreen] scale]]; |
| #elif TARGET_OS_WATCH |
| // User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43 |
| userAgent = [NSString stringWithFormat:@"%@/%@ (%@; watchOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[WKInterfaceDevice currentDevice] model], [[WKInterfaceDevice currentDevice] systemVersion], [[WKInterfaceDevice currentDevice] screenScale]]; |
| #elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED) |
| userAgent = [NSString stringWithFormat:@"%@/%@ (Mac OS X %@)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[NSProcessInfo processInfo] operatingSystemVersionString]]; |
| #endif |
| if (userAgent) { |
| if (![userAgent canBeConvertedToEncoding:NSASCIIStringEncoding]) { |
| NSMutableString *mutableUserAgent = [userAgent mutableCopy]; |
| if (CFStringTransform((__bridge CFMutableStringRef)(mutableUserAgent), NULL, (__bridge CFStringRef)@"Any-Latin; Latin-ASCII; [:^ASCII:] Remove", false)) { |
| userAgent = mutableUserAgent; |
| } |
| } |
| [self setValue:userAgent forHTTPHeaderField:@"User-Agent"]; |
| } |
| |
| // HTTP Method Definitions; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html |
| self.HTTPMethodsEncodingParametersInURI = [NSSet setWithObjects:@"GET", @"HEAD", @"DELETE", nil]; |
| |
| self.mutableObservedChangedKeyPaths = [NSMutableSet set]; |
| for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) { |
| if ([self respondsToSelector:NSSelectorFromString(keyPath)]) { |
| [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext]; |
| } |
| } |
| |
| return self; |
| } |
| |
| - (void)dealloc { |
| for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) { |
| if ([self respondsToSelector:NSSelectorFromString(keyPath)]) { |
| [self removeObserver:self forKeyPath:keyPath context:AFHTTPRequestSerializerObserverContext]; |
| } |
| } |
| } |
| |
| #pragma mark - |
| |
| // Workarounds for crashing behavior using Key-Value Observing with XCTest |
| // See https://github.com/AFNetworking/AFNetworking/issues/2523 |
| |
| - (void)setAllowsCellularAccess:(BOOL)allowsCellularAccess { |
| [self willChangeValueForKey:NSStringFromSelector(@selector(allowsCellularAccess))]; |
| _allowsCellularAccess = allowsCellularAccess; |
| [self didChangeValueForKey:NSStringFromSelector(@selector(allowsCellularAccess))]; |
| } |
| |
| - (void)setCachePolicy:(NSURLRequestCachePolicy)cachePolicy { |
| [self willChangeValueForKey:NSStringFromSelector(@selector(cachePolicy))]; |
| _cachePolicy = cachePolicy; |
| [self didChangeValueForKey:NSStringFromSelector(@selector(cachePolicy))]; |
| } |
| |
| - (void)setHTTPShouldHandleCookies:(BOOL)HTTPShouldHandleCookies { |
| [self willChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldHandleCookies))]; |
| _HTTPShouldHandleCookies = HTTPShouldHandleCookies; |
| [self didChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldHandleCookies))]; |
| } |
| |
| - (void)setHTTPShouldUsePipelining:(BOOL)HTTPShouldUsePipelining { |
| [self willChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldUsePipelining))]; |
| _HTTPShouldUsePipelining = HTTPShouldUsePipelining; |
| [self didChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldUsePipelining))]; |
| } |
| |
| - (void)setNetworkServiceType:(NSURLRequestNetworkServiceType)networkServiceType { |
| [self willChangeValueForKey:NSStringFromSelector(@selector(networkServiceType))]; |
| _networkServiceType = networkServiceType; |
| [self didChangeValueForKey:NSStringFromSelector(@selector(networkServiceType))]; |
| } |
| |
| - (void)setTimeoutInterval:(NSTimeInterval)timeoutInterval { |
| [self willChangeValueForKey:NSStringFromSelector(@selector(timeoutInterval))]; |
| _timeoutInterval = timeoutInterval; |
| [self didChangeValueForKey:NSStringFromSelector(@selector(timeoutInterval))]; |
| } |
| |
| #pragma mark - |
| |
| - (NSDictionary *)HTTPRequestHeaders { |
| return [NSDictionary dictionaryWithDictionary:self.mutableHTTPRequestHeaders]; |
| } |
| |
| - (void)setValue:(NSString *)value |
| forHTTPHeaderField:(NSString *)field |
| { |
| [self.mutableHTTPRequestHeaders setValue:value forKey:field]; |
| } |
| |
| - (NSString *)valueForHTTPHeaderField:(NSString *)field { |
| return [self.mutableHTTPRequestHeaders valueForKey:field]; |
| } |
| |
| - (void)setAuthorizationHeaderFieldWithUsername:(NSString *)username |
| password:(NSString *)password |
| { |
| NSData *basicAuthCredentials = [[NSString stringWithFormat:@"%@:%@", username, password] dataUsingEncoding:NSUTF8StringEncoding]; |
| NSString *base64AuthCredentials = [basicAuthCredentials base64EncodedStringWithOptions:(NSDataBase64EncodingOptions)0]; |
| [self setValue:[NSString stringWithFormat:@"Basic %@", base64AuthCredentials] forHTTPHeaderField:@"Authorization"]; |
| } |
| |
| - (void)clearAuthorizationHeader { |
| [self.mutableHTTPRequestHeaders removeObjectForKey:@"Authorization"]; |
| } |
| |
| #pragma mark - |
| |
| - (void)setQueryStringSerializationWithStyle:(AFHTTPRequestQueryStringSerializationStyle)style { |
| self.queryStringSerializationStyle = style; |
| self.queryStringSerialization = nil; |
| } |
| |
| - (void)setQueryStringSerializationWithBlock:(NSString *(^)(NSURLRequest *, id, NSError *__autoreleasing *))block { |
| self.queryStringSerialization = block; |
| } |
| |
| #pragma mark - |
| |
| - (NSMutableURLRequest *)requestWithMethod:(NSString *)method |
| URLString:(NSString *)URLString |
| parameters:(id)parameters |
| error:(NSError *__autoreleasing *)error |
| { |
| NSParameterAssert(method); |
| NSParameterAssert(URLString); |
| |
| NSURL *url = [NSURL URLWithString:URLString]; |
| |
| NSParameterAssert(url); |
| |
| NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url]; |
| mutableRequest.HTTPMethod = method; |
| |
| for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) { |
| if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) { |
| [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath]; |
| } |
| } |
| |
| mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy]; |
| |
| return mutableRequest; |
| } |
| |
| - (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method |
| URLString:(NSString *)URLString |
| parameters:(NSDictionary *)parameters |
| constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block |
| error:(NSError *__autoreleasing *)error |
| { |
| NSParameterAssert(method); |
| NSParameterAssert(![method isEqualToString:@"GET"] && ![method isEqualToString:@"HEAD"]); |
| |
| NSMutableURLRequest *mutableRequest = [self requestWithMethod:method URLString:URLString parameters:nil error:error]; |
| |
| __block AFStreamingMultipartFormData *formData = [[AFStreamingMultipartFormData alloc] initWithURLRequest:mutableRequest stringEncoding:NSUTF8StringEncoding]; |
| |
| if (parameters) { |
| for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) { |
| NSData *data = nil; |
| if ([pair.value isKindOfClass:[NSData class]]) { |
| data = pair.value; |
| } else if ([pair.value isEqual:[NSNull null]]) { |
| data = [NSData data]; |
| } else { |
| data = [[pair.value description] dataUsingEncoding:self.stringEncoding]; |
| } |
| |
| if (data) { |
| [formData appendPartWithFormData:data name:[pair.field description]]; |
| } |
| } |
| } |
| |
| if (block) { |
| block(formData); |
| } |
| |
| return [formData requestByFinalizingMultipartFormData]; |
| } |
| |
| - (NSMutableURLRequest *)requestWithMultipartFormRequest:(NSURLRequest *)request |
| writingStreamContentsToFile:(NSURL *)fileURL |
| completionHandler:(void (^)(NSError *error))handler |
| { |
| NSParameterAssert(request.HTTPBodyStream); |
| NSParameterAssert([fileURL isFileURL]); |
| |
| NSInputStream *inputStream = request.HTTPBodyStream; |
| NSOutputStream *outputStream = [[NSOutputStream alloc] initWithURL:fileURL append:NO]; |
| __block NSError *error = nil; |
| |
| dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ |
| [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; |
| [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; |
| |
| [inputStream open]; |
| [outputStream open]; |
| |
| while ([inputStream hasBytesAvailable] && [outputStream hasSpaceAvailable]) { |
| uint8_t buffer[1024]; |
| |
| NSInteger bytesRead = [inputStream read:buffer maxLength:1024]; |
| if (inputStream.streamError || bytesRead < 0) { |
| error = inputStream.streamError; |
| break; |
| } |
| |
| NSInteger bytesWritten = [outputStream write:buffer maxLength:(NSUInteger)bytesRead]; |
| if (outputStream.streamError || bytesWritten < 0) { |
| error = outputStream.streamError; |
| break; |
| } |
| |
| if (bytesRead == 0 && bytesWritten == 0) { |
| break; |
| } |
| } |
| |
| [outputStream close]; |
| [inputStream close]; |
| |
| if (handler) { |
| dispatch_async(dispatch_get_main_queue(), ^{ |
| handler(error); |
| }); |
| } |
| }); |
| |
| NSMutableURLRequest *mutableRequest = [request mutableCopy]; |
| mutableRequest.HTTPBodyStream = nil; |
| |
| return mutableRequest; |
| } |
| |
| #pragma mark - AFURLRequestSerialization |
| |
| - (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request |
| withParameters:(id)parameters |
| error:(NSError *__autoreleasing *)error |
| { |
| NSParameterAssert(request); |
| |
| NSMutableURLRequest *mutableRequest = [request mutableCopy]; |
| |
| [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) { |
| if (![request valueForHTTPHeaderField:field]) { |
| [mutableRequest setValue:value forHTTPHeaderField:field]; |
| } |
| }]; |
| |
| NSString *query = nil; |
| if (parameters) { |
| if (self.queryStringSerialization) { |
| NSError *serializationError; |
| query = self.queryStringSerialization(request, parameters, &serializationError); |
| |
| if (serializationError) { |
| if (error) { |
| *error = serializationError; |
| } |
| |
| return nil; |
| } |
| } else { |
| switch (self.queryStringSerializationStyle) { |
| case AFHTTPRequestQueryStringDefaultStyle: |
| query = AFQueryStringFromParameters(parameters); |
| break; |
| } |
| } |
| } |
| |
| if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) { |
| if (query && query.length > 0) { |
| mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]]; |
| } |
| } else { |
| // #2864: an empty string is a valid x-www-form-urlencoded payload |
| if (!query) { |
| query = @""; |
| } |
| if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) { |
| [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"]; |
| } |
| [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]]; |
| } |
| |
| return mutableRequest; |
| } |
| |
| #pragma mark - NSKeyValueObserving |
| |
| + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key { |
| if ([AFHTTPRequestSerializerObservedKeyPaths() containsObject:key]) { |
| return NO; |
| } |
| |
| return [super automaticallyNotifiesObserversForKey:key]; |
| } |
| |
| - (void)observeValueForKeyPath:(NSString *)keyPath |
| ofObject:(__unused id)object |
| change:(NSDictionary *)change |
| context:(void *)context |
| { |
| if (context == AFHTTPRequestSerializerObserverContext) { |
| if ([change[NSKeyValueChangeNewKey] isEqual:[NSNull null]]) { |
| [self.mutableObservedChangedKeyPaths removeObject:keyPath]; |
| } else { |
| [self.mutableObservedChangedKeyPaths addObject:keyPath]; |
| } |
| } |
| } |
| |
| #pragma mark - NSSecureCoding |
| |
| + (BOOL)supportsSecureCoding { |
| return YES; |
| } |
| |
| - (instancetype)initWithCoder:(NSCoder *)decoder { |
| self = [self init]; |
| if (!self) { |
| return nil; |
| } |
| |
| self.mutableHTTPRequestHeaders = [[decoder decodeObjectOfClass:[NSDictionary class] forKey:NSStringFromSelector(@selector(mutableHTTPRequestHeaders))] mutableCopy]; |
| self.queryStringSerializationStyle = (AFHTTPRequestQueryStringSerializationStyle)[[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(queryStringSerializationStyle))] unsignedIntegerValue]; |
| |
| return self; |
| } |
| |
| - (void)encodeWithCoder:(NSCoder *)coder { |
| [coder encodeObject:self.mutableHTTPRequestHeaders forKey:NSStringFromSelector(@selector(mutableHTTPRequestHeaders))]; |
| [coder encodeInteger:self.queryStringSerializationStyle forKey:NSStringFromSelector(@selector(queryStringSerializationStyle))]; |
| } |
| |
| #pragma mark - NSCopying |
| |
| - (instancetype)copyWithZone:(NSZone *)zone { |
| AFHTTPRequestSerializer *serializer = [[[self class] allocWithZone:zone] init]; |
| serializer.mutableHTTPRequestHeaders = [self.mutableHTTPRequestHeaders mutableCopyWithZone:zone]; |
| serializer.queryStringSerializationStyle = self.queryStringSerializationStyle; |
| serializer.queryStringSerialization = self.queryStringSerialization; |
| |
| return serializer; |
| } |
| |
| @end |
| |
| #pragma mark - |
| |
| static NSString * AFCreateMultipartFormBoundary() { |
| return [NSString stringWithFormat:@"Boundary+%08X%08X", arc4random(), arc4random()]; |
| } |
| |
| static NSString * const kAFMultipartFormCRLF = @"\r\n"; |
| |
| static inline NSString * AFMultipartFormInitialBoundary(NSString *boundary) { |
| return [NSString stringWithFormat:@"--%@%@", boundary, kAFMultipartFormCRLF]; |
| } |
| |
| static inline NSString * AFMultipartFormEncapsulationBoundary(NSString *boundary) { |
| return [NSString stringWithFormat:@"%@--%@%@", kAFMultipartFormCRLF, boundary, kAFMultipartFormCRLF]; |
| } |
| |
| static inline NSString * AFMultipartFormFinalBoundary(NSString *boundary) { |
| return [NSString stringWithFormat:@"%@--%@--%@", kAFMultipartFormCRLF, boundary, kAFMultipartFormCRLF]; |
| } |
| |
| static inline NSString * AFContentTypeForPathExtension(NSString *extension) { |
| NSString *UTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)extension, NULL); |
| NSString *contentType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)UTI, kUTTagClassMIMEType); |
| if (!contentType) { |
| return @"application/octet-stream"; |
| } else { |
| return contentType; |
| } |
| } |
| |
| NSUInteger const kAFUploadStream3GSuggestedPacketSize = 1024 * 16; |
| NSTimeInterval const kAFUploadStream3GSuggestedDelay = 0.2; |
| |
| @interface AFHTTPBodyPart : NSObject |
| @property (nonatomic, assign) NSStringEncoding stringEncoding; |
| @property (nonatomic, strong) NSDictionary *headers; |
| @property (nonatomic, copy) NSString *boundary; |
| @property (nonatomic, strong) id body; |
| @property (nonatomic, assign) unsigned long long bodyContentLength; |
| @property (nonatomic, strong) NSInputStream *inputStream; |
| |
| @property (nonatomic, assign) BOOL hasInitialBoundary; |
| @property (nonatomic, assign) BOOL hasFinalBoundary; |
| |
| @property (readonly, nonatomic, assign, getter = hasBytesAvailable) BOOL bytesAvailable; |
| @property (readonly, nonatomic, assign) unsigned long long contentLength; |
| |
| - (NSInteger)read:(uint8_t *)buffer |
| maxLength:(NSUInteger)length; |
| @end |
| |
| @interface AFMultipartBodyStream : NSInputStream <NSStreamDelegate> |
| @property (nonatomic, assign) NSUInteger numberOfBytesInPacket; |
| @property (nonatomic, assign) NSTimeInterval delay; |
| @property (nonatomic, strong) NSInputStream *inputStream; |
| @property (readonly, nonatomic, assign) unsigned long long contentLength; |
| @property (readonly, nonatomic, assign, getter = isEmpty) BOOL empty; |
| |
| - (instancetype)initWithStringEncoding:(NSStringEncoding)encoding; |
| - (void)setInitialAndFinalBoundaries; |
| - (void)appendHTTPBodyPart:(AFHTTPBodyPart *)bodyPart; |
| @end |
| |
| #pragma mark - |
| |
| @interface AFStreamingMultipartFormData () |
| @property (readwrite, nonatomic, copy) NSMutableURLRequest *request; |
| @property (readwrite, nonatomic, assign) NSStringEncoding stringEncoding; |
| @property (readwrite, nonatomic, copy) NSString *boundary; |
| @property (readwrite, nonatomic, strong) AFMultipartBodyStream *bodyStream; |
| @end |
| |
| @implementation AFStreamingMultipartFormData |
| |
| - (instancetype)initWithURLRequest:(NSMutableURLRequest *)urlRequest |
| stringEncoding:(NSStringEncoding)encoding |
| { |
| self = [super init]; |
| if (!self) { |
| return nil; |
| } |
| |
| self.request = urlRequest; |
| self.stringEncoding = encoding; |
| self.boundary = AFCreateMultipartFormBoundary(); |
| self.bodyStream = [[AFMultipartBodyStream alloc] initWithStringEncoding:encoding]; |
| |
| return self; |
| } |
| |
| - (BOOL)appendPartWithFileURL:(NSURL *)fileURL |
| name:(NSString *)name |
| error:(NSError * __autoreleasing *)error |
| { |
| NSParameterAssert(fileURL); |
| NSParameterAssert(name); |
| |
| NSString *fileName = [fileURL lastPathComponent]; |
| NSString *mimeType = AFContentTypeForPathExtension([fileURL pathExtension]); |
| |
| return [self appendPartWithFileURL:fileURL name:name fileName:fileName mimeType:mimeType error:error]; |
| } |
| |
| - (BOOL)appendPartWithFileURL:(NSURL *)fileURL |
| name:(NSString *)name |
| fileName:(NSString *)fileName |
| mimeType:(NSString *)mimeType |
| error:(NSError * __autoreleasing *)error |
| { |
| NSParameterAssert(fileURL); |
| NSParameterAssert(name); |
| NSParameterAssert(fileName); |
| NSParameterAssert(mimeType); |
| |
| if (![fileURL isFileURL]) { |
| NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey: NSLocalizedStringFromTable(@"Expected URL to be a file URL", @"AFNetworking", nil)}; |
| if (error) { |
| *error = [[NSError alloc] initWithDomain:AFURLRequestSerializationErrorDomain code:NSURLErrorBadURL userInfo:userInfo]; |
| } |
| |
| return NO; |
| } else if ([fileURL checkResourceIsReachableAndReturnError:error] == NO) { |
| NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey: NSLocalizedStringFromTable(@"File URL not reachable.", @"AFNetworking", nil)}; |
| if (error) { |
| *error = [[NSError alloc] initWithDomain:AFURLRequestSerializationErrorDomain code:NSURLErrorBadURL userInfo:userInfo]; |
| } |
| |
| return NO; |
| } |
| |
| NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[fileURL path] error:error]; |
| if (!fileAttributes) { |
| return NO; |
| } |
| |
| NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary]; |
| [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, fileName] forKey:@"Content-Disposition"]; |
| [mutableHeaders setValue:mimeType forKey:@"Content-Type"]; |
| |
| AFHTTPBodyPart *bodyPart = [[AFHTTPBodyPart alloc] init]; |
| bodyPart.stringEncoding = self.stringEncoding; |
| bodyPart.headers = mutableHeaders; |
| bodyPart.boundary = self.boundary; |
| bodyPart.body = fileURL; |
| bodyPart.bodyContentLength = [fileAttributes[NSFileSize] unsignedLongLongValue]; |
| [self.bodyStream appendHTTPBodyPart:bodyPart]; |
| |
| return YES; |
| } |
| |
| - (void)appendPartWithInputStream:(NSInputStream *)inputStream |
| name:(NSString *)name |
| fileName:(NSString *)fileName |
| length:(int64_t)length |
| mimeType:(NSString *)mimeType |
| { |
| NSParameterAssert(name); |
| NSParameterAssert(fileName); |
| NSParameterAssert(mimeType); |
| |
| NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary]; |
| [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, fileName] forKey:@"Content-Disposition"]; |
| [mutableHeaders setValue:mimeType forKey:@"Content-Type"]; |
| |
| AFHTTPBodyPart *bodyPart = [[AFHTTPBodyPart alloc] init]; |
| bodyPart.stringEncoding = self.stringEncoding; |
| bodyPart.headers = mutableHeaders; |
| bodyPart.boundary = self.boundary; |
| bodyPart.body = inputStream; |
| |
| bodyPart.bodyContentLength = (unsigned long long)length; |
| |
| [self.bodyStream appendHTTPBodyPart:bodyPart]; |
| } |
| |
| - (void)appendPartWithFileData:(NSData *)data |
| name:(NSString *)name |
| fileName:(NSString *)fileName |
| mimeType:(NSString *)mimeType |
| { |
| NSParameterAssert(name); |
| NSParameterAssert(fileName); |
| NSParameterAssert(mimeType); |
| |
| NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary]; |
| [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, fileName] forKey:@"Content-Disposition"]; |
| [mutableHeaders setValue:mimeType forKey:@"Content-Type"]; |
| |
| [self appendPartWithHeaders:mutableHeaders body:data]; |
| } |
| |
| - (void)appendPartWithFormData:(NSData *)data |
| name:(NSString *)name |
| { |
| NSParameterAssert(name); |
| |
| NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary]; |
| [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"", name] forKey:@"Content-Disposition"]; |
| |
| [self appendPartWithHeaders:mutableHeaders body:data]; |
| } |
| |
| - (void)appendPartWithHeaders:(NSDictionary *)headers |
| body:(NSData *)body |
| { |
| NSParameterAssert(body); |
| |
| AFHTTPBodyPart *bodyPart = [[AFHTTPBodyPart alloc] init]; |
| bodyPart.stringEncoding = self.stringEncoding; |
| bodyPart.headers = headers; |
| bodyPart.boundary = self.boundary; |
| bodyPart.bodyContentLength = [body length]; |
| bodyPart.body = body; |
| |
| [self.bodyStream appendHTTPBodyPart:bodyPart]; |
| } |
| |
| - (void)throttleBandwidthWithPacketSize:(NSUInteger)numberOfBytes |
| delay:(NSTimeInterval)delay |
| { |
| self.bodyStream.numberOfBytesInPacket = numberOfBytes; |
| self.bodyStream.delay = delay; |
| } |
| |
| - (NSMutableURLRequest *)requestByFinalizingMultipartFormData { |
| if ([self.bodyStream isEmpty]) { |
| return self.request; |
| } |
| |
| // Reset the initial and final boundaries to ensure correct Content-Length |
| [self.bodyStream setInitialAndFinalBoundaries]; |
| [self.request setHTTPBodyStream:self.bodyStream]; |
| |
| [self.request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", self.boundary] forHTTPHeaderField:@"Content-Type"]; |
| [self.request setValue:[NSString stringWithFormat:@"%llu", [self.bodyStream contentLength]] forHTTPHeaderField:@"Content-Length"]; |
| |
| return self.request; |
| } |
| |
| @end |
| |
| #pragma mark - |
| |
| @interface NSStream () |
| @property (readwrite) NSStreamStatus streamStatus; |
| @property (readwrite, copy) NSError *streamError; |
| @end |
| |
| @interface AFMultipartBodyStream () <NSCopying> |
| @property (readwrite, nonatomic, assign) NSStringEncoding stringEncoding; |
| @property (readwrite, nonatomic, strong) NSMutableArray *HTTPBodyParts; |
| @property (readwrite, nonatomic, strong) NSEnumerator *HTTPBodyPartEnumerator; |
| @property (readwrite, nonatomic, strong) AFHTTPBodyPart *currentHTTPBodyPart; |
| @property (readwrite, nonatomic, strong) NSOutputStream *outputStream; |
| @property (readwrite, nonatomic, strong) NSMutableData *buffer; |
| @end |
| |
| @implementation AFMultipartBodyStream |
| #if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000) || (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 1100) |
| @synthesize delegate; |
| #endif |
| @synthesize streamStatus; |
| @synthesize streamError; |
| |
| - (instancetype)initWithStringEncoding:(NSStringEncoding)encoding { |
| self = [super init]; |
| if (!self) { |
| return nil; |
| } |
| |
| self.stringEncoding = encoding; |
| self.HTTPBodyParts = [NSMutableArray array]; |
| self.numberOfBytesInPacket = NSIntegerMax; |
| |
| return self; |
| } |
| |
| - (void)setInitialAndFinalBoundaries { |
| if ([self.HTTPBodyParts count] > 0) { |
| for (AFHTTPBodyPart *bodyPart in self.HTTPBodyParts) { |
| bodyPart.hasInitialBoundary = NO; |
| bodyPart.hasFinalBoundary = NO; |
| } |
| |
| [[self.HTTPBodyParts firstObject] setHasInitialBoundary:YES]; |
| [[self.HTTPBodyParts lastObject] setHasFinalBoundary:YES]; |
| } |
| } |
| |
| - (void)appendHTTPBodyPart:(AFHTTPBodyPart *)bodyPart { |
| [self.HTTPBodyParts addObject:bodyPart]; |
| } |
| |
| - (BOOL)isEmpty { |
| return [self.HTTPBodyParts count] == 0; |
| } |
| |
| #pragma mark - NSInputStream |
| |
| - (NSInteger)read:(uint8_t *)buffer |
| maxLength:(NSUInteger)length |
| { |
| if ([self streamStatus] == NSStreamStatusClosed) { |
| return 0; |
| } |
| |
| NSInteger totalNumberOfBytesRead = 0; |
| |
| while ((NSUInteger)totalNumberOfBytesRead < MIN(length, self.numberOfBytesInPacket)) { |
| if (!self.currentHTTPBodyPart || ![self.currentHTTPBodyPart hasBytesAvailable]) { |
| if (!(self.currentHTTPBodyPart = [self.HTTPBodyPartEnumerator nextObject])) { |
| break; |
| } |
| } else { |
| NSUInteger maxLength = MIN(length, self.numberOfBytesInPacket) - (NSUInteger)totalNumberOfBytesRead; |
| NSInteger numberOfBytesRead = [self.currentHTTPBodyPart read:&buffer[totalNumberOfBytesRead] maxLength:maxLength]; |
| if (numberOfBytesRead == -1) { |
| self.streamError = self.currentHTTPBodyPart.inputStream.streamError; |
| break; |
| } else { |
| totalNumberOfBytesRead += numberOfBytesRead; |
| |
| if (self.delay > 0.0f) { |
| [NSThread sleepForTimeInterval:self.delay]; |
| } |
| } |
| } |
| } |
| |
| return totalNumberOfBytesRead; |
| } |
| |
| - (BOOL)getBuffer:(__unused uint8_t **)buffer |
| length:(__unused NSUInteger *)len |
| { |
| return NO; |
| } |
| |
| - (BOOL)hasBytesAvailable { |
| return [self streamStatus] == NSStreamStatusOpen; |
| } |
| |
| #pragma mark - NSStream |
| |
| - (void)open { |
| if (self.streamStatus == NSStreamStatusOpen) { |
| return; |
| } |
| |
| self.streamStatus = NSStreamStatusOpen; |
| |
| [self setInitialAndFinalBoundaries]; |
| self.HTTPBodyPartEnumerator = [self.HTTPBodyParts objectEnumerator]; |
| } |
| |
| - (void)close { |
| self.streamStatus = NSStreamStatusClosed; |
| } |
| |
| - (id)propertyForKey:(__unused NSString *)key { |
| return nil; |
| } |
| |
| - (BOOL)setProperty:(__unused id)property |
| forKey:(__unused NSString *)key |
| { |
| return NO; |
| } |
| |
| - (void)scheduleInRunLoop:(__unused NSRunLoop *)aRunLoop |
| forMode:(__unused NSString *)mode |
| {} |
| |
| - (void)removeFromRunLoop:(__unused NSRunLoop *)aRunLoop |
| forMode:(__unused NSString *)mode |
| {} |
| |
| - (unsigned long long)contentLength { |
| unsigned long long length = 0; |
| for (AFHTTPBodyPart *bodyPart in self.HTTPBodyParts) { |
| length += [bodyPart contentLength]; |
| } |
| |
| return length; |
| } |
| |
| #pragma mark - Undocumented CFReadStream Bridged Methods |
| |
| - (void)_scheduleInCFRunLoop:(__unused CFRunLoopRef)aRunLoop |
| forMode:(__unused CFStringRef)aMode |
| {} |
| |
| - (void)_unscheduleFromCFRunLoop:(__unused CFRunLoopRef)aRunLoop |
| forMode:(__unused CFStringRef)aMode |
| {} |
| |
| - (BOOL)_setCFClientFlags:(__unused CFOptionFlags)inFlags |
| callback:(__unused CFReadStreamClientCallBack)inCallback |
| context:(__unused CFStreamClientContext *)inContext { |
| return NO; |
| } |
| |
| #pragma mark - NSCopying |
| |
| - (instancetype)copyWithZone:(NSZone *)zone { |
| AFMultipartBodyStream *bodyStreamCopy = [[[self class] allocWithZone:zone] initWithStringEncoding:self.stringEncoding]; |
| |
| for (AFHTTPBodyPart *bodyPart in self.HTTPBodyParts) { |
| [bodyStreamCopy appendHTTPBodyPart:[bodyPart copy]]; |
| } |
| |
| [bodyStreamCopy setInitialAndFinalBoundaries]; |
| |
| return bodyStreamCopy; |
| } |
| |
| @end |
| |
| #pragma mark - |
| |
| typedef enum { |
| AFEncapsulationBoundaryPhase = 1, |
| AFHeaderPhase = 2, |
| AFBodyPhase = 3, |
| AFFinalBoundaryPhase = 4, |
| } AFHTTPBodyPartReadPhase; |
| |
| @interface AFHTTPBodyPart () <NSCopying> { |
| AFHTTPBodyPartReadPhase _phase; |
| NSInputStream *_inputStream; |
| unsigned long long _phaseReadOffset; |
| } |
| |
| - (BOOL)transitionToNextPhase; |
| - (NSInteger)readData:(NSData *)data |
| intoBuffer:(uint8_t *)buffer |
| maxLength:(NSUInteger)length; |
| @end |
| |
| @implementation AFHTTPBodyPart |
| |
| - (instancetype)init { |
| self = [super init]; |
| if (!self) { |
| return nil; |
| } |
| |
| [self transitionToNextPhase]; |
| |
| return self; |
| } |
| |
| - (void)dealloc { |
| if (_inputStream) { |
| [_inputStream close]; |
| _inputStream = nil; |
| } |
| } |
| |
| - (NSInputStream *)inputStream { |
| if (!_inputStream) { |
| if ([self.body isKindOfClass:[NSData class]]) { |
| _inputStream = [NSInputStream inputStreamWithData:self.body]; |
| } else if ([self.body isKindOfClass:[NSURL class]]) { |
| _inputStream = [NSInputStream inputStreamWithURL:self.body]; |
| } else if ([self.body isKindOfClass:[NSInputStream class]]) { |
| _inputStream = self.body; |
| } else { |
| _inputStream = [NSInputStream inputStreamWithData:[NSData data]]; |
| } |
| } |
| |
| return _inputStream; |
| } |
| |
| - (NSString *)stringForHeaders { |
| NSMutableString *headerString = [NSMutableString string]; |
| for (NSString *field in [self.headers allKeys]) { |
| [headerString appendString:[NSString stringWithFormat:@"%@: %@%@", field, [self.headers valueForKey:field], kAFMultipartFormCRLF]]; |
| } |
| [headerString appendString:kAFMultipartFormCRLF]; |
| |
| return [NSString stringWithString:headerString]; |
| } |
| |
| - (unsigned long long)contentLength { |
| unsigned long long length = 0; |
| |
| NSData *encapsulationBoundaryData = [([self hasInitialBoundary] ? AFMultipartFormInitialBoundary(self.boundary) : AFMultipartFormEncapsulationBoundary(self.boundary)) dataUsingEncoding:self.stringEncoding]; |
| length += [encapsulationBoundaryData length]; |
| |
| NSData *headersData = [[self stringForHeaders] dataUsingEncoding:self.stringEncoding]; |
| length += [headersData length]; |
| |
| length += _bodyContentLength; |
| |
| NSData *closingBoundaryData = ([self hasFinalBoundary] ? [AFMultipartFormFinalBoundary(self.boundary) dataUsingEncoding:self.stringEncoding] : [NSData data]); |
| length += [closingBoundaryData length]; |
| |
| return length; |
| } |
| |
| - (BOOL)hasBytesAvailable { |
| // Allows `read:maxLength:` to be called again if `AFMultipartFormFinalBoundary` doesn't fit into the available buffer |
| if (_phase == AFFinalBoundaryPhase) { |
| return YES; |
| } |
| |
| switch (self.inputStream.streamStatus) { |
| case NSStreamStatusNotOpen: |
| case NSStreamStatusOpening: |
| case NSStreamStatusOpen: |
| case NSStreamStatusReading: |
| case NSStreamStatusWriting: |
| return YES; |
| case NSStreamStatusAtEnd: |
| case NSStreamStatusClosed: |
| case NSStreamStatusError: |
| default: |
| return NO; |
| } |
| } |
| |
| - (NSInteger)read:(uint8_t *)buffer |
| maxLength:(NSUInteger)length |
| { |
| NSInteger totalNumberOfBytesRead = 0; |
| |
| if (_phase == AFEncapsulationBoundaryPhase) { |
| NSData *encapsulationBoundaryData = [([self hasInitialBoundary] ? AFMultipartFormInitialBoundary(self.boundary) : AFMultipartFormEncapsulationBoundary(self.boundary)) dataUsingEncoding:self.stringEncoding]; |
| totalNumberOfBytesRead += [self readData:encapsulationBoundaryData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)]; |
| } |
| |
| if (_phase == AFHeaderPhase) { |
| NSData *headersData = [[self stringForHeaders] dataUsingEncoding:self.stringEncoding]; |
| totalNumberOfBytesRead += [self readData:headersData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)]; |
| } |
| |
| if (_phase == AFBodyPhase) { |
| NSInteger numberOfBytesRead = 0; |
| |
| numberOfBytesRead = [self.inputStream read:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)]; |
| if (numberOfBytesRead == -1) { |
| return -1; |
| } else { |
| totalNumberOfBytesRead += numberOfBytesRead; |
| |
| if ([self.inputStream streamStatus] >= NSStreamStatusAtEnd) { |
| [self transitionToNextPhase]; |
| } |
| } |
| } |
| |
| if (_phase == AFFinalBoundaryPhase) { |
| NSData *closingBoundaryData = ([self hasFinalBoundary] ? [AFMultipartFormFinalBoundary(self.boundary) dataUsingEncoding:self.stringEncoding] : [NSData data]); |
| totalNumberOfBytesRead += [self readData:closingBoundaryData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)]; |
| } |
| |
| return totalNumberOfBytesRead; |
| } |
| |
| - (NSInteger)readData:(NSData *)data |
| intoBuffer:(uint8_t *)buffer |
| maxLength:(NSUInteger)length |
| { |
| NSRange range = NSMakeRange((NSUInteger)_phaseReadOffset, MIN([data length] - ((NSUInteger)_phaseReadOffset), length)); |
| [data getBytes:buffer range:range]; |
| |
| _phaseReadOffset += range.length; |
| |
| if (((NSUInteger)_phaseReadOffset) >= [data length]) { |
| [self transitionToNextPhase]; |
| } |
| |
| return (NSInteger)range.length; |
| } |
| |
| - (BOOL)transitionToNextPhase { |
| if (![[NSThread currentThread] isMainThread]) { |
| dispatch_sync(dispatch_get_main_queue(), ^{ |
| [self transitionToNextPhase]; |
| }); |
| return YES; |
| } |
| |
| switch (_phase) { |
| case AFEncapsulationBoundaryPhase: |
| _phase = AFHeaderPhase; |
| break; |
| case AFHeaderPhase: |
| [self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; |
| [self.inputStream open]; |
| _phase = AFBodyPhase; |
| break; |
| case AFBodyPhase: |
| [self.inputStream close]; |
| _phase = AFFinalBoundaryPhase; |
| break; |
| case AFFinalBoundaryPhase: |
| default: |
| _phase = AFEncapsulationBoundaryPhase; |
| break; |
| } |
| _phaseReadOffset = 0; |
| |
| return YES; |
| } |
| |
| #pragma mark - NSCopying |
| |
| - (instancetype)copyWithZone:(NSZone *)zone { |
| AFHTTPBodyPart *bodyPart = [[[self class] allocWithZone:zone] init]; |
| |
| bodyPart.stringEncoding = self.stringEncoding; |
| bodyPart.headers = self.headers; |
| bodyPart.bodyContentLength = self.bodyContentLength; |
| bodyPart.body = self.body; |
| bodyPart.boundary = self.boundary; |
| |
| return bodyPart; |
| } |
| |
| @end |
| |
| #pragma mark - |
| |
| @implementation AFJSONRequestSerializer |
| |
| + (instancetype)serializer { |
| return [self serializerWithWritingOptions:(NSJSONWritingOptions)0]; |
| } |
| |
| + (instancetype)serializerWithWritingOptions:(NSJSONWritingOptions)writingOptions |
| { |
| AFJSONRequestSerializer *serializer = [[self alloc] init]; |
| serializer.writingOptions = writingOptions; |
| |
| return serializer; |
| } |
| |
| #pragma mark - AFURLRequestSerialization |
| |
| - (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request |
| withParameters:(id)parameters |
| error:(NSError *__autoreleasing *)error |
| { |
| NSParameterAssert(request); |
| |
| if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) { |
| return [super requestBySerializingRequest:request withParameters:parameters error:error]; |
| } |
| |
| NSMutableURLRequest *mutableRequest = [request mutableCopy]; |
| |
| [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) { |
| if (![request valueForHTTPHeaderField:field]) { |
| [mutableRequest setValue:value forHTTPHeaderField:field]; |
| } |
| }]; |
| |
| if (parameters) { |
| if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) { |
| [mutableRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; |
| } |
| |
| [mutableRequest setHTTPBody:[NSJSONSerialization dataWithJSONObject:parameters options:self.writingOptions error:error]]; |
| } |
| |
| return mutableRequest; |
| } |
| |
| #pragma mark - NSSecureCoding |
| |
| - (instancetype)initWithCoder:(NSCoder *)decoder { |
| self = [super initWithCoder:decoder]; |
| if (!self) { |
| return nil; |
| } |
| |
| self.writingOptions = [[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(writingOptions))] unsignedIntegerValue]; |
| |
| return self; |
| } |
| |
| - (void)encodeWithCoder:(NSCoder *)coder { |
| [super encodeWithCoder:coder]; |
| |
| [coder encodeInteger:self.writingOptions forKey:NSStringFromSelector(@selector(writingOptions))]; |
| } |
| |
| #pragma mark - NSCopying |
| |
| - (instancetype)copyWithZone:(NSZone *)zone { |
| AFJSONRequestSerializer *serializer = [super copyWithZone:zone]; |
| serializer.writingOptions = self.writingOptions; |
| |
| return serializer; |
| } |
| |
| @end |
| |
| #pragma mark - |
| |
| @implementation AFPropertyListRequestSerializer |
| |
| + (instancetype)serializer { |
| return [self serializerWithFormat:NSPropertyListXMLFormat_v1_0 writeOptions:0]; |
| } |
| |
| + (instancetype)serializerWithFormat:(NSPropertyListFormat)format |
| writeOptions:(NSPropertyListWriteOptions)writeOptions |
| { |
| AFPropertyListRequestSerializer *serializer = [[self alloc] init]; |
| serializer.format = format; |
| serializer.writeOptions = writeOptions; |
| |
| return serializer; |
| } |
| |
| #pragma mark - AFURLRequestSerializer |
| |
| - (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request |
| withParameters:(id)parameters |
| error:(NSError *__autoreleasing *)error |
| { |
| NSParameterAssert(request); |
| |
| if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) { |
| return [super requestBySerializingRequest:request withParameters:parameters error:error]; |
| } |
| |
| NSMutableURLRequest *mutableRequest = [request mutableCopy]; |
| |
| [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) { |
| if (![request valueForHTTPHeaderField:field]) { |
| [mutableRequest setValue:value forHTTPHeaderField:field]; |
| } |
| }]; |
| |
| if (parameters) { |
| if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) { |
| [mutableRequest setValue:@"application/x-plist" forHTTPHeaderField:@"Content-Type"]; |
| } |
| |
| [mutableRequest setHTTPBody:[NSPropertyListSerialization dataWithPropertyList:parameters format:self.format options:self.writeOptions error:error]]; |
| } |
| |
| return mutableRequest; |
| } |
| |
| #pragma mark - NSSecureCoding |
| |
| - (instancetype)initWithCoder:(NSCoder *)decoder { |
| self = [super initWithCoder:decoder]; |
| if (!self) { |
| return nil; |
| } |
| |
| self.format = (NSPropertyListFormat)[[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(format))] unsignedIntegerValue]; |
| self.writeOptions = [[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(writeOptions))] unsignedIntegerValue]; |
| |
| return self; |
| } |
| |
| - (void)encodeWithCoder:(NSCoder *)coder { |
| [super encodeWithCoder:coder]; |
| |
| [coder encodeInteger:self.format forKey:NSStringFromSelector(@selector(format))]; |
| [coder encodeObject:@(self.writeOptions) forKey:NSStringFromSelector(@selector(writeOptions))]; |
| } |
| |
| #pragma mark - NSCopying |
| |
| - (instancetype)copyWithZone:(NSZone *)zone { |
| AFPropertyListRequestSerializer *serializer = [super copyWithZone:zone]; |
| serializer.format = self.format; |
| serializer.writeOptions = self.writeOptions; |
| |
| return serializer; |
| } |
| |
| @end |