1/*
2  Copyright (C) 2000-2005 SKYRIX Software AG
3
4  This file is part of SOPE.
5
6  SOPE is free software; you can redistribute it and/or modify it under
7  the terms of the GNU Lesser General Public License as published by the
8  Free Software Foundation; either version 2, or (at your option) any
9  later version.
10
11  SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
12  WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14  License for more details.
15
16  You should have received a copy of the GNU Lesser General Public
17  License along with SOPE; see the file COPYING.  If not, write to the
18  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19  02111-1307, USA.
20*/
21
22#include "NSData+gzip.h"
23#include "common.h"
24
25#ifdef Assert
26#  undef Assert
27#endif
28
29#include <zlib.h>
30#ifndef DEF_MEM_LEVEL /* zutil.h */
31#  if MAX_MEM_LEVEL >= 8
32#    define DEF_MEM_LEVEL 8
33#  else
34#    define DEF_MEM_LEVEL  MAX_MEM_LEVEL
35#  endif
36#  define OS_CODE  0x07 /* TODO: probably need to adjust that ... */
37#endif
38
39#undef Assert
40
41@implementation NSData(gzip)
42
43- (NSData *)gzip {
44  return [self gzipWithLevel:Z_DEFAULT_COMPRESSION];
45}
46
47static inline void putLong(uLong x, NSMutableData *data, IMP addBytes) {
48  int n;
49  for (n = 0; n < 4; n++) {
50    unsigned char c = (int)(x & 0xff);
51    addBytes(data, @selector(appendBytes:length:), &c, 1);
52    x >>= 8;
53  }
54}
55
56- (NSData *)gzipWithLevel:(int)_level {
57  NSMutableData *data     = nil;
58  int           errorCode = 0;
59  unsigned      len       = [self length];
60  void          *src      = (void *)[self bytes];
61  IMP           addBytes  = NULL;
62  char          outBuf[4096];
63  z_stream      out;
64  uLong         crc;
65
66  NSAssert1((_level >= NGGZipMinimalCompression &&
67             _level <= NGGZipMaximalCompression)
68            || (_level == Z_DEFAULT_COMPRESSION),
69            @"invalid compression level %i (0-9)", _level);
70
71  data = [NSMutableData dataWithCapacity:
72                          (len / 10 < 128) ? len : len / 10];
73  addBytes = [data methodForSelector:@selector(appendBytes:length:)];
74
75  out.zalloc    = (alloc_func)NULL;
76  out.zfree     = (free_func)NULL;
77  out.opaque    = (voidpf)NULL;
78  out.next_out  = (Byte*)&outBuf;
79  out.avail_out = sizeof(outBuf);
80  out.next_in   = Z_NULL;
81  out.avail_in  = 0;
82  errorCode     = Z_OK;
83  crc           = crc32(0L, Z_NULL, 0);
84
85  errorCode = deflateInit2(&out, _level, Z_DEFLATED, -MAX_WBITS,
86                           DEF_MEM_LEVEL,
87                           0); // windowBits is passed <0 to suppress zlib header
88  if (errorCode != Z_OK) {
89    NSLog(@"ERROR: could not init deflate !");
90    return nil;
91  }
92
93  { // add gzip header
94    char buf[10] = {
95      0x1f, 0x8b,    // magic
96      Z_DEFLATED, 0, // flags
97      0, 0, 0, 0,    // time
98      0, OS_CODE     // flags
99    };
100    addBytes(data, @selector(appendBytes:length:), &buf, 10);
101  }
102
103  { // gz_write
104    out.next_in  = src;
105    out.avail_in = len;
106
107    while (out.avail_in > 0) {
108      if (out.avail_out == 0) {
109        out.next_out = (void *)&outBuf; // reset buffer position
110        addBytes(data, @selector(appendBytes:length:), &outBuf, sizeof(outBuf));
111        out.avail_out = sizeof(outBuf);
112      }
113      errorCode = deflate(&out, Z_NO_FLUSH);
114      if (errorCode != Z_OK) {
115        NSLog(@"ERROR: could not deflate chunk !");
116        if (out.state) deflateEnd(&out);
117        return nil;
118      }
119    }
120    crc = crc32(crc, src, len);
121  }
122
123  { // gz_flush
124    BOOL done = NO;
125
126    out.next_in  = NULL;
127    out.avail_in = 0; // should be zero already anyway
128
129    for (;;) {
130      len = sizeof(outBuf) - out.avail_out;
131
132      if (len > 0) {
133        addBytes(data, @selector(appendBytes:length:), &outBuf, len);
134        out.next_out  = (void *)&outBuf;
135        out.avail_out = sizeof(outBuf);
136      }
137      if (done)
138        break;
139      errorCode = deflate(&out, Z_FINISH);
140
141      // deflate has finished flushing only when it hasn't used up
142      // all the available space in the output buffer:
143      done = (out.avail_out != 0 || errorCode == Z_STREAM_END);
144
145      if (errorCode != Z_OK && errorCode != Z_STREAM_END)
146        break;
147    }
148    if (errorCode != Z_STREAM_END) {
149      NSLog(@"ERROR: flush failed.");
150      if (out.state) deflateEnd(&out);
151      return nil;
152    }
153  }
154  { // write trailer (checksum and filesize)
155    putLong(crc, data, addBytes);
156    putLong(out.total_in, data, addBytes);
157  }
158  if (out.state) deflateEnd(&out);
159
160  return data;
161}
162
163- (NSData *)compress
164{
165  return [self compressWithLevel: 3];
166}
167
168- (NSData *)compressWithLevel: (int) level
169{
170  NSData *result;
171  Bytef *destBuffer;
172  unsigned long destLen;
173  int rc;
174
175  destLen = [self length];
176  destBuffer = NSZoneMalloc (NULL, destLen);
177  rc = compress2 (destBuffer, &destLen, [self bytes], destLen, level);
178  if (rc == Z_OK)
179    result = [NSData dataWithBytesNoCopy: destBuffer
180                                  length: destLen
181                            freeWhenDone: YES];
182  else
183    result = nil;
184
185  return result;
186}
187
188@end
189
190void __link_NSData_gzip(void) {
191  __link_NSData_gzip();
192}
193