Source/TNLHTTPRequest.m (307 lines of code) (raw):

// // TNLHTTPRequest.m // TwitterNetworkLayer // // Created on 2/28/15. // Copyright © 2020 Twitter. All rights reserved. // #import "NSDictionary+TNLAdditions.h" #import "TNL_Project.h" #import "TNLError.h" #import "TNLHTTPRequest.h" #import "TNLRequestConfiguration.h" NS_ASSUME_NONNULL_BEGIN #pragma mark - TNLHTTPRequest @implementation TNLHTTPRequest { @protected NSURL *_URL; TNLHTTPMethod _HTTPMethodValue; NSDictionary *_allHTTPHeaderFields; NSData *_HTTPBody; NSInputStream *_HTTPBodyStream; NSString *_HTTPBodyFilePath; } @synthesize URL = _URL; @synthesize HTTPMethodValue = _HTTPMethodValue; @synthesize allHTTPHeaderFields = _allHTTPHeaderFields; @synthesize HTTPBody = _HTTPBody; @synthesize HTTPBodyStream = _HTTPBodyStream; @synthesize HTTPBodyFilePath = _HTTPBodyFilePath; #pragma mark init - (instancetype)init { return [self initWithURL:nil]; } - (instancetype)initWithURL:(nullable NSURL *)url { return [self initWithURLRequest:(url) ? [[NSURLRequest alloc] initWithURL:url] : nil]; } - (instancetype)initWithURLRequest:(nullable NSURLRequest *)request { return [self initWithURLRequest:request HTTPBodyFilePath:nil]; } - (instancetype)initWithURLRequest:(nullable NSURLRequest *)request HTTPBodyFilePath:(nullable NSString *)bodyFilePath { NSString *const HTTPMethod = request.HTTPMethod; return [self initWithURL:request.URL HTTPMethodValue:(HTTPMethod) ? TNLHTTPMethodFromString(HTTPMethod) : TNLHTTPMethodGET HTTPHeaderFields:request.allHTTPHeaderFields HTTPBody:request.HTTPBody HTTPBodyStream:request.HTTPBodyStream HTTPBodyFilePath:bodyFilePath]; } - (instancetype)initWithURL:(nullable NSURL *)url HTTPMethodValue:(TNLHTTPMethod)method HTTPHeaderFields:(nullable NSDictionary *)fields HTTPBody:(nullable NSData *)body HTTPBodyStream:(nullable NSInputStream *)bodyStream HTTPBodyFilePath:(nullable NSString *)bodyFilePath { if (self = [super init]) { _URL = url; _HTTPMethodValue = method; _allHTTPHeaderFields = [fields copy]; _HTTPBody = body; // to avoid the cost of memcpy for large bodies, we'll put the responsibility on the caller to not modify the body _HTTPBodyStream = bodyStream; _HTTPBodyFilePath = [bodyFilePath copy]; } return self; } #pragma mark NSSecureCoding - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { NSURL *URL = [aDecoder decodeObjectOfClass:[NSURL class] forKey:@"URL"]; TNLHTTPMethod HTTPMethodValue = [aDecoder decodeIntegerForKey:@"HTTPMethodValue"]; NSDictionary *allHTTPHeaderFields = [[aDecoder decodeObjectOfClass:[NSDictionary class] forKey:@"allHTTPHeaderFields"] copy]; NSData *HTTPBody = [aDecoder decodeObjectOfClass:[NSData class] forKey:@"HTTPBody"]; NSString *HTTPBodyFilePath = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"HTTPBodyFilePath"]; return [self initWithURL:URL HTTPMethodValue:HTTPMethodValue HTTPHeaderFields:allHTTPHeaderFields HTTPBody:HTTPBody HTTPBodyStream:nil HTTPBodyFilePath:HTTPBodyFilePath]; } - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:_URL forKey:@"URL"]; [aCoder encodeInteger:_HTTPMethodValue forKey:@"HTTPMethodValue"]; [aCoder encodeObject:_allHTTPHeaderFields forKey:@"allHTTPHeaderFields"]; [aCoder encodeObject:_HTTPBody forKey:@"HTTPBody"]; [aCoder encodeObject:_HTTPBodyFilePath forKey:@"HTTPBodyFilePath"]; if (_HTTPBodyStream) { TNLAssert(NO && "HTTPBodyStream cannot be encoded!"); // @throw [NSException exceptionWithName:NSInvalidArchiveOperationException reason:[NSString stringWithFormat:@"Cannot encode a %@ with HTTPBodyStream set to anything other than nil!", NSStringFromClass([self class])] userInfo:nil]; } } + (BOOL)supportsSecureCoding { return YES; } #pragma mark Convenience Constructors + (instancetype)HTTPRequestWithRequest:(nullable id<TNLRequest>)request { return [[self alloc] initWithURL:[request respondsToSelector:@selector(URL)] ? request.URL : nil HTTPMethodValue:TNLRequestGetHTTPMethodValue(request) HTTPHeaderFields:[request respondsToSelector:@selector(allHTTPHeaderFields)] ? request.allHTTPHeaderFields : nil HTTPBody:[request respondsToSelector:@selector(HTTPBody)] ? request.HTTPBody : nil HTTPBodyStream:[request respondsToSelector:@selector(HTTPBodyStream)] ? request.HTTPBodyStream : nil HTTPBodyFilePath:[request respondsToSelector:@selector(HTTPBodyFilePath)] ? request.HTTPBodyFilePath : nil]; } + (instancetype)POSTRequestWithURL:(nullable NSURL *)url HTTPHeaderFields:(nullable NSDictionary *)fields HTTPBody:(nullable NSData *)body { return [[self alloc] initWithURL:url HTTPMethodValue:TNLHTTPMethodPOST HTTPHeaderFields:fields HTTPBody:body HTTPBodyStream:nil HTTPBodyFilePath:nil]; } + (instancetype)POSTRequestWithURL:(nullable NSURL *)url HTTPHeaderFields:(nullable NSDictionary *)fields HTTPBodyStream:(nullable NSInputStream *)bodyStream { return [[self alloc] initWithURL:url HTTPMethodValue:TNLHTTPMethodPOST HTTPHeaderFields:fields HTTPBody:nil HTTPBodyStream:bodyStream HTTPBodyFilePath:nil]; } + (instancetype)POSTRequestWithURL:(nullable NSURL *)url HTTPHeaderFields:(nullable NSDictionary *)fields HTTPBodyFilePath:(nullable NSString *)bodyFilePath { return [[self alloc] initWithURL:url HTTPMethodValue:TNLHTTPMethodPOST HTTPHeaderFields:fields HTTPBody:nil HTTPBodyStream:nil HTTPBodyFilePath:bodyFilePath]; } + (instancetype)GETRequestWithURL:(nullable NSURL *)url HTTPHeaderFields:(nullable NSDictionary *)fields { return [[self alloc] initWithURL:url HTTPMethodValue:TNLHTTPMethodGET HTTPHeaderFields:fields HTTPBody:nil HTTPBodyStream:nil HTTPBodyFilePath:nil]; } + (instancetype)PUTRequestWithURL:(nullable NSURL *)url HTTPHeaderFields:(nullable NSDictionary *)fields { return [[self alloc] initWithURL:url HTTPMethodValue:TNLHTTPMethodPUT HTTPHeaderFields:fields HTTPBody:nil HTTPBodyStream:nil HTTPBodyFilePath:nil]; } + (instancetype)DELETERequestWithURL:(nullable NSURL *)url HTTPHeaderFields:(nullable NSDictionary *)fields { return [[self alloc] initWithURL:url HTTPMethodValue:TNLHTTPMethodDELETE HTTPHeaderFields:fields HTTPBody:nil HTTPBodyStream:nil HTTPBodyFilePath:nil]; } + (instancetype)HEADRequestWithURL:(nullable NSURL *)url HTTPHeaderFields:(nullable NSDictionary *)fields { return [[self alloc] initWithURL:url HTTPMethodValue:TNLHTTPMethodHEAD HTTPHeaderFields:fields HTTPBody:nil HTTPBodyStream:nil HTTPBodyFilePath:nil]; } #pragma mark Properties - (nullable NSURL *)URL { return _URL; } - (TNLHTTPMethod)HTTPMethodValue { return _HTTPMethodValue; } - (nullable NSDictionary *)allHTTPHeaderFields { return _allHTTPHeaderFields; } - (nullable NSString *)valueForHTTPHeaderField:(NSString *)field { return [self.allHTTPHeaderFields tnl_objectForCaseInsensitiveKey:field]; } - (nullable NSData *)HTTPBody { return _HTTPBody; } - (nullable NSInputStream *)HTTPBodyStream { return _HTTPBodyStream; } - (nullable NSString *)HTTPBodyFilePath { return _HTTPBodyFilePath; } #pragma mark NSMutableCopying - (id)copyWithZone:(nullable NSZone *)zone { return self; } - (id)mutableCopyWithZone:(nullable NSZone *)zone { TNLMutableHTTPRequest *copy = [[TNLMutableHTTPRequest allocWithZone:zone] initWithURL:self.URL HTTPMethodValue:self.HTTPMethodValue HTTPHeaderFields:_allHTTPHeaderFields HTTPBody:self.HTTPBody HTTPBodyStream:self.HTTPBodyStream HTTPBodyFilePath:self.HTTPBodyFilePath]; return copy; } #pragma mark isEqual + hash - (NSUInteger)hash { NSUInteger hash = self.HTTPBody.hash + self.URL.hash + (NSUInteger)self.HTTPMethodValue + self.HTTPBodyFilePath.hash + self.HTTPBodyStream.hash + self.allHTTPHeaderFields.count; // use the count of headers, not the hash since the dictionary will be case incensitive in comparison return hash; } - (BOOL)isEqual:(id)object { if ([object isKindOfClass:[TNLHTTPRequest class]]) { return TNLRequestEqualToRequest(self, object, NO /*quickBodyCheck*/); } return [super isEqual:object]; } @end #pragma mark - TNLMutableHTTPRequest @implementation TNLMutableHTTPRequest @dynamic URL; @dynamic HTTPMethodValue; @dynamic allHTTPHeaderFields; @dynamic HTTPBody; @dynamic HTTPBodyStream; @dynamic HTTPBodyFilePath; - (void)setURL:(nullable NSURL *)URL PROP_RETAIN_ASSIGN_IMP(URL); - (void)setHTTPMethodValue:(TNLHTTPMethod)HTTPMethodValue PROP_RETAIN_ASSIGN_IMP(HTTPMethodValue); - (void)setHTTPBody:(nullable NSData *)HTTPBody PROP_RETAIN_ASSIGN_IMP(HTTPBody); - (void)setHTTPBodyStream:(nullable NSInputStream *)HTTPBodyStream PROP_RETAIN_ASSIGN_IMP(HTTPBodyStream); - (void)setHTTPBodyFilePath:(nullable NSString *)HTTPBodyFilePath PROP_COPY_IMP(HTTPBodyFilePath); - (void)setAllHTTPHeaderFields:(nullable NSDictionary *)allHTTPHeaderFields { if (_allHTTPHeaderFields != allHTTPHeaderFields) { _allHTTPHeaderFields = [allHTTPHeaderFields mutableCopy]; } } - (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field { [(NSMutableDictionary *)_allHTTPHeaderFields tnl_setObject:value forCaseInsensitiveKey:field]; } - (void)removeAllValuesForHTTPHeaderField:(NSString *)field { [(NSMutableDictionary *)_allHTTPHeaderFields tnl_removeObjectsForCaseInsensitiveKey:field]; } - (id)copyWithZone:(nullable NSZone *)zone { TNLHTTPRequest *copy = [[TNLHTTPRequest allocWithZone:zone] initWithURL:self.URL HTTPMethodValue:self.HTTPMethodValue HTTPHeaderFields:_allHTTPHeaderFields HTTPBody:self.HTTPBody HTTPBodyStream:self.HTTPBodyStream HTTPBodyFilePath:self.HTTPBodyFilePath]; return copy; } - (instancetype)initWithURL:(nullable NSURL *)url HTTPMethodValue:(TNLHTTPMethod)method HTTPHeaderFields:(nullable NSDictionary *)fields HTTPBody:(nullable NSData *)body HTTPBodyStream:(nullable NSInputStream *)bodyStream HTTPBodyFilePath:(nullable NSString *)bodyFilePath { self = [super initWithURL:url HTTPMethodValue:method HTTPHeaderFields:fields HTTPBody:body HTTPBodyStream:bodyStream HTTPBodyFilePath:bodyFilePath]; if (self) { _allHTTPHeaderFields = [NSMutableDictionary dictionaryWithDictionary:_allHTTPHeaderFields]; } return self; } @end NS_ASSUME_NONNULL_END