aboutsummaryrefslogtreecommitdiffhomepage
path: root/Firebase/Utilities/NSData+zlib/GULNSData+zlib.m
blob: cd3394a41d6ec1431de89c74c820acc9716a406e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
// Copyright 2018 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#import "GULNSData+zlib.h"

#import <zlib.h>

#define kChunkSize 1024
#define Z_DEFAULT_COMPRESSION (-1)

NSString *const GULNSDataZlibErrorDomain = @"com.google.GULNSDataZlibErrorDomain";
NSString *const GULNSDataZlibErrorKey = @"GULNSDataZlibErrorKey";
NSString *const GULNSDataZlibRemainingBytesKey = @"GULNSDataZlibRemainingBytesKey";

@implementation NSData (GULGzip)

+ (NSData *)gul_dataByInflatingGzippedData:(NSData *)data error:(NSError **)error {
  const void *bytes = [data bytes];
  NSUInteger length = [data length];
  if (!bytes || !length) {
    return nil;
  }

#if defined(__LP64__) && __LP64__
  // Don't support > 32bit length for 64 bit, see note in header.
  if (length > UINT_MAX) {
    return nil;
  }
#endif

  z_stream strm;
  bzero(&strm, sizeof(z_stream));

  // Setup the input.
  strm.avail_in = (unsigned int)length;
  strm.next_in = (unsigned char *)bytes;

  int windowBits = 15;  // 15 to enable any window size
  windowBits += 32;     // and +32 to enable zlib or gzip header detection.

  int retCode;
  if ((retCode = inflateInit2(&strm, windowBits)) != Z_OK) {
    if (error) {
      NSDictionary *userInfo = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:retCode]
                                                           forKey:GULNSDataZlibErrorKey];
      *error = [NSError errorWithDomain:GULNSDataZlibErrorDomain
                                   code:GULNSDataZlibErrorInternal
                               userInfo:userInfo];
    }
    return nil;
  }

  // Hint the size at 4x the input size.
  NSMutableData *result = [NSMutableData dataWithCapacity:(length * 4)];
  unsigned char output[kChunkSize];

  // Loop to collect the data.
  do {
    // Update what we're passing in.
    strm.avail_out = kChunkSize;
    strm.next_out = output;
    retCode = inflate(&strm, Z_NO_FLUSH);
    if ((retCode != Z_OK) && (retCode != Z_STREAM_END)) {
      if (error) {
        NSMutableDictionary *userInfo =
            [NSMutableDictionary dictionaryWithObject:[NSNumber numberWithInt:retCode]
                                               forKey:GULNSDataZlibErrorKey];
        if (strm.msg) {
          NSString *message = [NSString stringWithUTF8String:strm.msg];
          if (message) {
            [userInfo setObject:message forKey:NSLocalizedDescriptionKey];
          }
        }
        *error = [NSError errorWithDomain:GULNSDataZlibErrorDomain
                                     code:GULNSDataZlibErrorInternal
                                 userInfo:userInfo];
      }
      inflateEnd(&strm);
      return nil;
    }
    // Collect what we got.
    unsigned gotBack = kChunkSize - strm.avail_out;
    if (gotBack > 0) {
      [result appendBytes:output length:gotBack];
    }

  } while (retCode == Z_OK);

  // Make sure there wasn't more data tacked onto the end of a valid compressed stream.
  if (strm.avail_in != 0) {
    if (error) {
      NSDictionary *userInfo =
          [NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedInt:strm.avail_in]
                                      forKey:GULNSDataZlibRemainingBytesKey];
      *error = [NSError errorWithDomain:GULNSDataZlibErrorDomain
                                   code:GULNSDataZlibErrorDataRemaining
                               userInfo:userInfo];
    }
    result = nil;
  }
  // The only way out of the loop was by hitting the end of the stream.
  NSAssert(retCode == Z_STREAM_END,
           @"Thought we finished inflate w/o getting a result of stream end, code %d", retCode);

  // Clean up.
  inflateEnd(&strm);

  return result;
}

+ (NSData *)gul_dataByGzippingData:(NSData *)data error:(NSError **)error {
  const void *bytes = [data bytes];
  NSUInteger length = [data length];

  int level = Z_DEFAULT_COMPRESSION;
  if (!bytes || !length) {
    return nil;
  }

#if defined(__LP64__) && __LP64__
  // Don't support > 32bit length for 64 bit, see note in header.
  if (length > UINT_MAX) {
    if (error) {
      *error = [NSError errorWithDomain:GULNSDataZlibErrorDomain
                                   code:GULNSDataZlibErrorGreaterThan32BitsToCompress
                               userInfo:nil];
    }
    return nil;
  }
#endif

  z_stream strm;
  bzero(&strm, sizeof(z_stream));

  int memLevel = 8;          // Default.
  int windowBits = 15 + 16;  // Enable gzip header instead of zlib header.

  int retCode;
  if ((retCode = deflateInit2(&strm, level, Z_DEFLATED, windowBits, memLevel,
                              Z_DEFAULT_STRATEGY)) != Z_OK) {
    if (error) {
      NSDictionary *userInfo = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:retCode]
                                                           forKey:GULNSDataZlibErrorKey];
      *error = [NSError errorWithDomain:GULNSDataZlibErrorDomain
                                   code:GULNSDataZlibErrorInternal
                               userInfo:userInfo];
    }
    return nil;
  }

  // Hint the size at 1/4 the input size.
  NSMutableData *result = [NSMutableData dataWithCapacity:(length / 4)];
  unsigned char output[kChunkSize];

  // Setup the input.
  strm.avail_in = (unsigned int)length;
  strm.next_in = (unsigned char *)bytes;

  // Collect the data.
  do {
    // update what we're passing in
    strm.avail_out = kChunkSize;
    strm.next_out = output;
    retCode = deflate(&strm, Z_FINISH);
    if ((retCode != Z_OK) && (retCode != Z_STREAM_END)) {
      if (error) {
        NSDictionary *userInfo = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:retCode]
                                                             forKey:GULNSDataZlibErrorKey];
        *error = [NSError errorWithDomain:GULNSDataZlibErrorDomain
                                     code:GULNSDataZlibErrorInternal
                                 userInfo:userInfo];
      }
      deflateEnd(&strm);
      return nil;
    }
    // Collect what we got.
    unsigned gotBack = kChunkSize - strm.avail_out;
    if (gotBack > 0) {
      [result appendBytes:output length:gotBack];
    }

  } while (retCode == Z_OK);

  // If the loop exits, it used all input and the stream ended.
  NSAssert(strm.avail_in == 0,
           @"Should have finished deflating without using all input, %u bytes left", strm.avail_in);
  NSAssert(retCode == Z_STREAM_END,
           @"thought we finished deflate w/o getting a result of stream end, code %d", retCode);

  // Clean up.
  deflateEnd(&strm);

  return result;
}

@end