TwitterNetworkLayerTests/TNLXContentEncoding.m (365 lines of code) (raw):

// // TNLXContentEncoding.m // TwitterNetworkLayer // // Created on 11/21/16. // Copyright © 2020 Twitter. All rights reserved. // #include <zlib.h> #import <TwitterNetworkLayer/TNLContentCoding.h> #import "TNLXContentEncoding.h" typedef NS_ENUM(UInt32, TNLXZLibContentEncoderMode) { TNLXZLibContentEncoderDEFLATE = 'defl', TNLXZLibContentEncoderGZIP = 'gzip', }; static const NSUInteger kMinBodySizeForCompression = 512; #define kGZIP_WINDOW_BITS_OFFSET (16) #define kWINDOW_BITS (15) #define kMEM_LIMIT (8) #define kGZIP_WINDOW_BITS (kGZIP_WINDOW_BITS_OFFSET+kWINDOW_BITS) #define kDEFLATE_WINDOW_BITS (-MAX_WBITS) static const size_t kZipBufferSize = ((4 * 1024) /*page size*/ * 4); typedef void(^TIPXDataEnumerateBlock)(const void *bytes, NSRange byteRange, BOOL *stop); @interface TNLXZLibContentEncoder : NSObject <TNLContentEncoder> @property (nonatomic, readonly) TNLXZLibContentEncoderMode mode; - (instancetype)initWithMode:(TNLXZLibContentEncoderMode)mode; - (instancetype)init NS_UNAVAILABLE; + (instancetype)new NS_UNAVAILABLE; @end @interface TNLXZLibContentDecoderContext : NSObject <TNLContentDecoderContext> @property (nonatomic, readonly) TNLXZLibContentEncoderMode mode; @property (nonatomic, readonly, nonnull, unsafe_unretained) id<TNLContentDecoderClient> tnl_decoderClient; - (instancetype)initWithMode:(TNLXZLibContentEncoderMode)mode client:(id<TNLContentDecoderClient>)client; - (instancetype)init NS_UNAVAILABLE; + (instancetype)new NS_UNAVAILABLE; - (BOOL)decodeData:(NSData *)data error:(out NSError **)error; - (BOOL)finalizeAndReturnError:(out NSError **)error; @end @interface TNLXZLibContentDecoder : NSObject <TNLContentDecoder> @property (nonatomic, readonly) TNLXZLibContentEncoderMode mode; - (instancetype)initWithMode:(TNLXZLibContentEncoderMode)mode; - (instancetype)init NS_UNAVAILABLE; + (instancetype)new NS_UNAVAILABLE; @end @interface TNLXBase64ContentDecoderContext : NSObject <TNLContentDecoderContext> @property (nonatomic, readonly, nonnull, unsafe_unretained) id<TNLContentDecoderClient> tnl_decoderClient; - (instancetype)initWithClient:(id<TNLContentDecoderClient>)client; - (instancetype)init NS_UNAVAILABLE; + (instancetype)new NS_UNAVAILABLE; - (BOOL)decodeData:(NSData *)data error:(out NSError **)error; - (BOOL)finalizeAndReturnError:(out NSError **)error; @end @interface TNLXBase64ContentEncoder : NSObject <TNLContentEncoder> @end @interface TNLXBase64ContentDecoder : NSObject <TNLContentDecoder> @end @implementation TNLXContentEncoding + (id<TNLContentEncoder>)GZIPContentEncoder { return [[TNLXZLibContentEncoder alloc] initWithMode:TNLXZLibContentEncoderGZIP]; } + (id<TNLContentDecoder>)GZIPContentDecoder { return [[TNLXZLibContentDecoder alloc] initWithMode:TNLXZLibContentEncoderGZIP]; } + (id<TNLContentEncoder>)DEFLATEContentEncoder { return [[TNLXZLibContentEncoder alloc] initWithMode:TNLXZLibContentEncoderDEFLATE]; } + (id<TNLContentDecoder>)DEFLATEContentDecoder { return [[TNLXZLibContentDecoder alloc] initWithMode:TNLXZLibContentEncoderDEFLATE]; } + (id<TNLContentEncoder>)Base64ContentEncoder { return [[TNLXBase64ContentEncoder alloc] init]; } + (id<TNLContentDecoder>)Base64ContentDecoder { return [[TNLXBase64ContentDecoder alloc] init]; } @end @implementation TNLXZLibContentEncoder - (instancetype)initWithMode:(TNLXZLibContentEncoderMode)mode { if (self = [super init]) { _mode = mode; } return self; } - (NSString *)tnl_contentEncodingType { return (TNLXZLibContentEncoderGZIP == _mode) ? @"gzip" : @"deflate"; } - (NSData *)tnl_encodeHTTPBody:(NSData *)bodyData error:(out NSError **)error { if (bodyData.length < kMinBodySizeForCompression) { if (error) { *error = [NSError errorWithDomain:TNLContentEncodingErrorDomain code:TNLContentEncodingErrorCodeSkipEncoding userInfo:nil]; } return nil; } NSMutableData *mData = [NSMutableData data]; __block int zRetVal; z_stream zStream; __block z_streamp zStreamPtr = &zStream; memset(zStreamPtr, 0, sizeof(z_stream)); zRetVal = deflateInit2(zStreamPtr, Z_DEFAULT_COMPRESSION, Z_DEFLATED, (TNLXZLibContentEncoderGZIP == _mode) ? kGZIP_WINDOW_BITS : kDEFLATE_WINDOW_BITS, kMEM_LIMIT, Z_DEFAULT_STRATEGY); if (zRetVal == Z_OK) { unsigned char *outBuffer = (unsigned char *)malloc(kZipBufferSize); TIPXDataEnumerateBlock enumBlock = ^(const void *bytes, NSRange byteRange, BOOL *stop) { zStreamPtr->avail_in = (uInt)byteRange.length; zStreamPtr->next_in = (z_const Byte *)bytes; const int flush = byteRange.length > 0 ? Z_NO_FLUSH : Z_FINISH; do { zStreamPtr->avail_out = kZipBufferSize; zStreamPtr->next_out = outBuffer; zRetVal = deflate(zStreamPtr, flush); if (Z_OK != zRetVal && Z_STREAM_END != zRetVal) { // failure break; } const uInt bytesMoved = kZipBufferSize - zStreamPtr->avail_out; if (bytesMoved) { [mData appendBytes:outBuffer length:bytesMoved]; } if (Z_STREAM_END == zRetVal) { // done break; } if (Z_FINISH != flush && zStreamPtr->avail_in == 0) { // no more data to consume break; } } while (true); if (Z_OK != zRetVal) { *stop = YES; } }; [bodyData enumerateByteRangesUsingBlock:enumBlock]; if (zRetVal == Z_OK) { BOOL fakeStop; enumBlock(NULL, NSMakeRange(bodyData.length, 0), &fakeStop); if (zRetVal == Z_STREAM_END) { zRetVal = Z_OK; } else if (zRetVal == Z_OK) { zRetVal = Z_STREAM_ERROR; } } free(outBuffer); (void)deflateEnd(zStreamPtr); } if (Z_OK != zRetVal) { mData = nil; if (error) { *error = [NSError errorWithDomain:@"zlib.error" code:zRetVal userInfo:nil]; } } return mData; } @end @implementation TNLXZLibContentDecoder - (instancetype)initWithMode:(TNLXZLibContentEncoderMode)mode { if (self = [super init]) { _mode = mode; } return self; } - (NSString *)tnl_contentEncodingType { return (TNLXZLibContentEncoderGZIP == _mode) ? @"gzip" : @"deflate"; } - (id<TNLContentDecoderContext>)tnl_initializeDecodingWithContentEncoding:(NSString *)contentEncodingValue client:(id<TNLContentDecoderClient>)client error:(out NSError **)error { return [[TNLXZLibContentDecoderContext alloc] initWithMode:_mode client:client]; } - (BOOL)tnl_decode:(TNLXZLibContentDecoderContext *)context additionalData:(NSData *)data error:(out NSError **)error { return [context decodeData:data error:error]; } - (BOOL)tnl_finalizeDecoding:(TNLXZLibContentDecoderContext *)context error:(out NSError **)error { return [context finalizeAndReturnError:error]; } @end @implementation TNLXZLibContentDecoderContext { z_stream _zStream; unsigned char _outBuffer[kZipBufferSize]; unsigned char *_outRef; struct { int zStatus; BOOL didInit:1; } _flags; } - (instancetype)initWithMode:(TNLXZLibContentEncoderMode)mode client:(id<TNLContentDecoderClient>)client { if (self = [super init]) { _mode = mode; _tnl_decoderClient = client; _flags.zStatus = inflateInit2(&_zStream, (TNLXZLibContentEncoderGZIP == _mode) ? kGZIP_WINDOW_BITS : kDEFLATE_WINDOW_BITS); if (Z_OK == _flags.zStatus) { _outRef = _outBuffer; _flags.didInit = 1; } } return self; } - (void)dealloc { if (_flags.didInit) { inflateEnd(&_zStream); } } - (BOOL)decodeData:(NSData *)data error:(out NSError * __autoreleasing *)error { if (Z_OK == _flags.zStatus || Z_STREAM_END == _flags.zStatus) { [data enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *stop) { *stop = ![self decodeBytes:bytes length:byteRange.length error:error]; if (self->_flags.zStatus != Z_OK && self->_flags.zStatus != Z_STREAM_END) { *stop = YES; } }]; } if (error && *error) { return NO; } if (Z_OK != _flags.zStatus && Z_STREAM_END != _flags.zStatus) { if (error) { *error = [NSError errorWithDomain:@"zlib.error" code:_flags.zStatus userInfo:nil]; } return NO; } return YES; } - (BOOL)decodeBytes:(const Byte *)bytes length:(NSUInteger)length error:(out NSError **)error { _zStream.avail_in = (uInt)length; _zStream.next_in = (z_const Byte *)bytes; BOOL inflateSuccess = NO; do { _zStream.avail_out = kZipBufferSize; _zStream.next_out = _outRef; _flags.zStatus = inflate(&_zStream, Z_NO_FLUSH); inflateSuccess = (Z_OK == _flags.zStatus || Z_STREAM_END == _flags.zStatus); uInt bytesMoved = kZipBufferSize - _zStream.avail_out; if (inflateSuccess && bytesMoved) { id<TNLContentDecoderClient> delegate = self.tnl_decoderClient; NSData *data = [NSData dataWithBytes:_outRef length:bytesMoved]; if (![delegate tnl_dataWasDecoded:data error:error]) { return NO; } } } while (_zStream.avail_out == 0 && inflateSuccess); assert(_zStream.avail_in == 0); return YES; } - (BOOL)finalizeAndReturnError:(out NSError **)error { if (_flags.zStatus == Z_OK) { if (![self decodeBytes:NULL length:0 error:error]) { return NO; } } if (_flags.zStatus == Z_STREAM_END) { return YES; } if (error) { *error = [NSError errorWithDomain:@"zlib.error" code:((_flags.zStatus == Z_OK) ? Z_STREAM_ERROR : _flags.zStatus) userInfo:nil]; } return NO; } @end @implementation TNLXBase64ContentEncoder - (NSString *)tnl_contentEncodingType { return @"base64"; } - (NSData *)tnl_encodeHTTPBody:(NSData *)bodyData error:(out NSError **)error { return [bodyData base64EncodedDataWithOptions:(NSDataBase64Encoding64CharacterLineLength | NSDataBase64EncodingEndLineWithCarriageReturn | NSDataBase64EncodingEndLineWithLineFeed)]; } @end @implementation TNLXBase64ContentDecoder - (NSString *)tnl_contentEncodingType { return @"base64"; } - (id<TNLContentDecoderContext>)tnl_initializeDecodingWithContentEncoding:(NSString *)contentEncodingValue client:(id<TNLContentDecoderClient>)client error:(out NSError **)error { return [[TNLXBase64ContentDecoderContext alloc] initWithClient:client]; } - (BOOL)tnl_decode:(TNLXBase64ContentDecoderContext *)context additionalData:(NSData *)data error:(out NSError **)error { return [context decodeData:data error:error]; } - (BOOL)tnl_finalizeDecoding:(TNLXBase64ContentDecoderContext *)context error:(out NSError **)error { return [context finalizeAndReturnError:error]; } @end @implementation TNLXBase64ContentDecoderContext { NSMutableData *_carryOverData; } - (instancetype)initWithClient:(id<TNLContentDecoderClient>)client { if (self = [super init]) { _tnl_decoderClient = client; } return self; } - (BOOL)decodeData:(NSData *)data error:(out NSError **)error { NSData *decodedData = nil; if (_carryOverData) { [_carryOverData appendData:data]; decodedData = [[NSData alloc] initWithBase64EncodedData:_carryOverData options:NSDataBase64DecodingIgnoreUnknownCharacters]; if (decodedData) { _carryOverData = nil; } } else { decodedData = [[NSData alloc] initWithBase64EncodedData:data options:NSDataBase64DecodingIgnoreUnknownCharacters]; if (!decodedData) { _carryOverData = [data mutableCopy]; } } if (decodedData) { return [_tnl_decoderClient tnl_dataWasDecoded:decodedData error:error]; } return YES; } - (BOOL)finalizeAndReturnError:(out NSError **)error { if (_carryOverData) { if (error) { *error = [NSError errorWithDomain:NSPOSIXErrorDomain code:EINVAL userInfo:nil]; } return NO; } return YES; } @end