// 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 #else #import #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 - (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, strong) dispatch_queue_t requestHeaderModificationQueue; @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]; self.requestHeaderModificationQueue = dispatch_queue_create("requestHeaderModificationQueue", DISPATCH_QUEUE_CONCURRENT); // 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"]; // User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43 NSString *userAgent = nil; #if TARGET_OS_IOS 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_TV userAgent = [NSString stringWithFormat:@"%@/%@ (%@; tvOS %@; 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 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 { NSDictionary __block *value; dispatch_sync(self.requestHeaderModificationQueue, ^{ value = [NSDictionary dictionaryWithDictionary:self.mutableHTTPRequestHeaders]; }); return value; } - (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field { dispatch_barrier_sync(self.requestHeaderModificationQueue, ^{ [self.mutableHTTPRequestHeaders setValue:value forKey:field]; }); } - (NSString *)valueForHTTPHeaderField:(NSString *)field { NSString __block *value; dispatch_sync(self.requestHeaderModificationQueue, ^{ value = [self.mutableHTTPRequestHeaders valueForKey:field]; }); return value; } - (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 { dispatch_barrier_sync(self.requestHeaderModificationQueue, ^{ [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 self.mutableObservedChangedKeyPaths) { [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 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 { dispatch_sync(self.requestHeaderModificationQueue, ^{ [coder encodeObject:self.mutableHTTPRequestHeaders forKey:NSStringFromSelector(@selector(mutableHTTPRequestHeaders))]; }); [coder encodeObject:@(self.queryStringSerializationStyle) forKey:NSStringFromSelector(@selector(queryStringSerializationStyle))]; } #pragma mark - NSCopying - (instancetype)copyWithZone:(NSZone *)zone { AFHTTPRequestSerializer *serializer = [[[self class] allocWithZone:zone] init]; dispatch_sync(self.requestHeaderModificationQueue, ^{ 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 @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; } - (void)setRequest:(NSMutableURLRequest *)request { _request = [request mutableCopy]; } - (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 () @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 () { 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"]; } if (![NSJSONSerialization isValidJSONObject:parameters]) { if (error) { NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey: NSLocalizedStringFromTable(@"The `parameters` argument is not valid JSON.", @"AFNetworking", nil)}; *error = [[NSError alloc] initWithDomain:AFURLRequestSerializationErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:userInfo]; } return nil; } NSData *jsonData = [NSJSONSerialization dataWithJSONObject:parameters options:self.writingOptions error:error]; if (!jsonData) { return nil; } [mutableRequest setHTTPBody:jsonData]; } 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 encodeObject:@(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"]; } NSData *plistData = [NSPropertyListSerialization dataWithPropertyList:parameters format:self.format options:self.writeOptions error:error]; if (!plistData) { return nil; } [mutableRequest setHTTPBody:plistData]; } 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 encodeObject:@(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