Source/NSURLResponse+TNLAdditions.m (123 lines of code) (raw):

// // NSURLResponse+TNLAdditions.m // TwitterNetworkLayer // // Created on 11/13/14. // Copyright © 2020 Twitter. All rights reserved. // #include <objc/runtime.h> #import "NSDictionary+TNLAdditions.h" #import "NSURLResponse+TNLAdditions.h" #import "TNL_Project.h" #import "TNLHTTP.h" NS_ASSUME_NONNULL_BEGIN static const char TNLContentEncodingAssociatedObjectKey[] = "tnl.content.encoding"; @implementation NSURLResponse (TNLAdditions) - (BOOL)tnl_isEqualToResponse:(nullable NSURLResponse *)response { if ([self isEqual:response]) { return YES; } IS_EQUAL_OBJ_PROP_CHECK(self, response, URL); IS_EQUAL_OBJ_PROP_CHECK(self, response, MIMEType); if (self.expectedContentLength != response.expectedContentLength) { return NO; } IS_EQUAL_OBJ_PROP_CHECK(self, response, textEncodingName); IS_EQUAL_OBJ_PROP_CHECK(self, response, suggestedFilename); return YES; } @end @implementation NSHTTPURLResponse (TNLAdditions) + (nullable id)tnl_parseRetryAfterValueFromString:(nullable NSString *)retryAfterStringValue { // Parsing of value is based on the definition in the HTTP/1.1 spec // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html Section 14.37 retryAfterStringValue = [retryAfterStringValue stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; if (retryAfterStringValue.length == 0) { return nil; } // Manually parse integer uint64_t invalidBitsMask = 0xFFFFFFFF80000000; uint64_t retryAfterDuration = 0; for (NSUInteger i = 0; i < retryAfterStringValue.length; i++) { unichar c = [retryAfterStringValue characterAtIndex:i]; if (c < '0' || c > '9') { // Value is not a positive integer retryAfterDuration = invalidBitsMask; break; } retryAfterDuration *= 10; retryAfterDuration += (c - '0'); if (TNL_BITMASK_INTERSECTS_FLAGS(retryAfterDuration, invalidBitsMask)) { // Overflow, invalid 32 bit integer break; } } if (TNL_BITMASK_EXCLUDES_FLAGS(retryAfterDuration, invalidBitsMask)) { // value is an integer (and therefore a delay) TNLAssert(retryAfterDuration <= INT32_MAX); return @((NSTimeInterval)retryAfterDuration); } else { // value is a string that MUST be an HTTP date (otherwise, the value is invalid) return TNLHTTPDateFromString(retryAfterStringValue, NULL); } } + (NSTimeInterval)tnl_delayFromRetryAfterValue:(nullable id)retryAfterValue { if ([retryAfterValue isKindOfClass:[NSNumber class]]) { return [(NSNumber *)retryAfterValue doubleValue]; } else if ([retryAfterValue isKindOfClass:[NSDate class]]) { return [(NSDate *)retryAfterValue timeIntervalSinceNow]; } return 0; } - (nullable id)tnl_parsedRetryAfterValue { NSString *retryAfter = self.allHeaderFields[@"Retry-After"]; return [[self class] tnl_parseRetryAfterValueFromString:retryAfter]; } - (BOOL)tnl_isEqualToResponse:(nullable NSURLResponse *)response { if ([self isEqual:response]) { return YES; } if (![super tnl_isEqualToResponse:response]) { return NO; } NSHTTPURLResponse *httpResponse = (id)response; if (![httpResponse isKindOfClass:[NSHTTPURLResponse class]]) { return NO; } if (self.statusCode != httpResponse.statusCode) { return NO; } NSDictionary *selfLowercaseHeaders = [self.allHeaderFields tnl_mutableCopyWithLowercaseKeys]; NSDictionary *otherLowercaseHeaders = [httpResponse.allHeaderFields tnl_mutableCopyWithLowercaseKeys]; if (![selfLowercaseHeaders isEqualToDictionary:otherLowercaseHeaders]) { return NO; } return YES; } - (nullable NSString *)tnl_contentEncoding { NSString *contentEncoding = objc_getAssociatedObject(self, TNLContentEncodingAssociatedObjectKey); if (!contentEncoding) { contentEncoding = [self.allHeaderFields tnl_objectForCaseInsensitiveKey:@"Content-Encoding"]; objc_setAssociatedObject(self, TNLContentEncodingAssociatedObjectKey, contentEncoding ?: [NSNull null], OBJC_ASSOCIATION_RETAIN /*atomic*/); } if (contentEncoding == (id)[NSNull null]) { contentEncoding = nil; } return contentEncoding; } - (long long)tnl_expectedResponseBodySize { NSString *contentLengthString = [self.allHeaderFields tnl_objectForCaseInsensitiveKey:@"Content-Length"]; const long long headerContentLength = (contentLengthString) ? [contentLengthString longLongValue] : -1; if (headerContentLength >= 0) { return headerContentLength; } return self.expectedContentLength; } - (long long)tnl_expectedResponseBodyExpandedDataSize { long long contentLength = self.expectedContentLength; if (contentLength <= 0) { NSString *contentLengthString = [self.allHeaderFields tnl_objectForCaseInsensitiveKey:@"Content-Length"]; if (contentLengthString) { contentLength = [contentLengthString longLongValue]; } else { contentLength = -1; } } return contentLength; } @end NS_ASSUME_NONNULL_END