New file |
| | |
| | | // 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, 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"]; |
| | | |
| | | 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 { |
| | | NSDictionary __block *value; |
| | | dispatch_sync(self.requestHeaderModificationQueue, ^{ |
| | | value = [NSDictionary dictionaryWithDictionary:self.mutableHTTPRequestHeaders]; |
| | | }); |
| | | return value; |
| | | } |
| | | |
| | | - (void)setValue:(NSString *)value |
| | | forHTTPHeaderField:(NSString *)field |
| | | { |
| | | dispatch_barrier_async(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_async(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 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 { |
| | | dispatch_sync(self.requestHeaderModificationQueue, ^{ |
| | | [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]; |
| | | 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 <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; |
| | | } |
| | | |
| | | - (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 () <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"]; |
| | | } |
| | | |
| | | 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 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"]; |
| | | } |
| | | |
| | | 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 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 |