1 // Copyright 2018 Google
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
7 // http://www.apache.org/licenses/LICENSE-2.0
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
15 #import "GULNSData+zlib.h"
19 #define kChunkSize 1024
20 #define Z_DEFAULT_COMPRESSION (-1)
22 NSString *const GULNSDataZlibErrorDomain = @"com.google.GULNSDataZlibErrorDomain";
23 NSString *const GULNSDataZlibErrorKey = @"GULNSDataZlibErrorKey";
24 NSString *const GULNSDataZlibRemainingBytesKey = @"GULNSDataZlibRemainingBytesKey";
26 @implementation NSData (GULGzip)
28 + (NSData *)gul_dataByInflatingGzippedData:(NSData *)data error:(NSError **)error {
29 const void *bytes = [data bytes];
30 NSUInteger length = [data length];
31 if (!bytes || !length) {
35 #if defined(__LP64__) && __LP64__
36 // Don't support > 32bit length for 64 bit, see note in header.
37 if (length > UINT_MAX) {
43 bzero(&strm, sizeof(z_stream));
46 strm.avail_in = (unsigned int)length;
47 strm.next_in = (unsigned char *)bytes;
49 int windowBits = 15; // 15 to enable any window size
50 windowBits += 32; // and +32 to enable zlib or gzip header detection.
53 if ((retCode = inflateInit2(&strm, windowBits)) != Z_OK) {
55 NSDictionary *userInfo = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:retCode]
56 forKey:GULNSDataZlibErrorKey];
57 *error = [NSError errorWithDomain:GULNSDataZlibErrorDomain
58 code:GULNSDataZlibErrorInternal
64 // Hint the size at 4x the input size.
65 NSMutableData *result = [NSMutableData dataWithCapacity:(length * 4)];
66 unsigned char output[kChunkSize];
68 // Loop to collect the data.
70 // Update what we're passing in.
71 strm.avail_out = kChunkSize;
72 strm.next_out = output;
73 retCode = inflate(&strm, Z_NO_FLUSH);
74 if ((retCode != Z_OK) && (retCode != Z_STREAM_END)) {
76 NSMutableDictionary *userInfo =
77 [NSMutableDictionary dictionaryWithObject:[NSNumber numberWithInt:retCode]
78 forKey:GULNSDataZlibErrorKey];
80 NSString *message = [NSString stringWithUTF8String:strm.msg];
82 [userInfo setObject:message forKey:NSLocalizedDescriptionKey];
85 *error = [NSError errorWithDomain:GULNSDataZlibErrorDomain
86 code:GULNSDataZlibErrorInternal
92 // Collect what we got.
93 unsigned gotBack = kChunkSize - strm.avail_out;
95 [result appendBytes:output length:gotBack];
98 } while (retCode == Z_OK);
100 // Make sure there wasn't more data tacked onto the end of a valid compressed stream.
101 if (strm.avail_in != 0) {
103 NSDictionary *userInfo =
104 [NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedInt:strm.avail_in]
105 forKey:GULNSDataZlibRemainingBytesKey];
106 *error = [NSError errorWithDomain:GULNSDataZlibErrorDomain
107 code:GULNSDataZlibErrorDataRemaining
112 // The only way out of the loop was by hitting the end of the stream.
113 NSAssert(retCode == Z_STREAM_END,
114 @"Thought we finished inflate w/o getting a result of stream end, code %d", retCode);
122 + (NSData *)gul_dataByGzippingData:(NSData *)data error:(NSError **)error {
123 const void *bytes = [data bytes];
124 NSUInteger length = [data length];
126 int level = Z_DEFAULT_COMPRESSION;
127 if (!bytes || !length) {
131 #if defined(__LP64__) && __LP64__
132 // Don't support > 32bit length for 64 bit, see note in header.
133 if (length > UINT_MAX) {
135 *error = [NSError errorWithDomain:GULNSDataZlibErrorDomain
136 code:GULNSDataZlibErrorGreaterThan32BitsToCompress
144 bzero(&strm, sizeof(z_stream));
146 int memLevel = 8; // Default.
147 int windowBits = 15 + 16; // Enable gzip header instead of zlib header.
150 if ((retCode = deflateInit2(&strm, level, Z_DEFLATED, windowBits, memLevel,
151 Z_DEFAULT_STRATEGY)) != Z_OK) {
153 NSDictionary *userInfo = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:retCode]
154 forKey:GULNSDataZlibErrorKey];
155 *error = [NSError errorWithDomain:GULNSDataZlibErrorDomain
156 code:GULNSDataZlibErrorInternal
162 // Hint the size at 1/4 the input size.
163 NSMutableData *result = [NSMutableData dataWithCapacity:(length / 4)];
164 unsigned char output[kChunkSize];
167 strm.avail_in = (unsigned int)length;
168 strm.next_in = (unsigned char *)bytes;
172 // update what we're passing in
173 strm.avail_out = kChunkSize;
174 strm.next_out = output;
175 retCode = deflate(&strm, Z_FINISH);
176 if ((retCode != Z_OK) && (retCode != Z_STREAM_END)) {
178 NSDictionary *userInfo = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:retCode]
179 forKey:GULNSDataZlibErrorKey];
180 *error = [NSError errorWithDomain:GULNSDataZlibErrorDomain
181 code:GULNSDataZlibErrorInternal
187 // Collect what we got.
188 unsigned gotBack = kChunkSize - strm.avail_out;
190 [result appendBytes:output length:gotBack];
193 } while (retCode == Z_OK);
195 // If the loop exits, it used all input and the stream ended.
196 NSAssert(strm.avail_in == 0,
197 @"Should have finished deflating without using all input, %u bytes left", strm.avail_in);
198 NSAssert(retCode == Z_STREAM_END,
199 @"thought we finished deflate w/o getting a result of stream end, code %d", retCode);