1 /* zlib.c: routines for zlib (de)compression of data
2 Copyright (c) 2002 Darren Salt, Philip Kendall
3 Copyright (c) 2015 Stuart Brady
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18
19 Author contact information:
20
21 Darren: E-mail: linux@youmustbejoking.demon.co.uk
22
23 Philip: E-mail: philip-fuse@shadowmagic.org.uk
24
25 */
26
27 #include "config.h"
28
29 #include <errno.h>
30 #include <stdio.h>
31 #include <string.h>
32 #include <sys/types.h>
33
34 #ifdef HAVE_UNISTD_H
35 #include <unistd.h>
36 #endif /* #ifdef HAVE_UNISTD_H */
37
38 #define ZLIB_CONST
39 #include <zlib.h>
40
41 #include "internals.h"
42
43 static libspectrum_error
44 skip_gzip_header( const libspectrum_byte **gzptr, size_t *gzlength );
45 static libspectrum_error
46 skip_null_terminated_string( const libspectrum_byte **ptr, size_t *length,
47 const char *name );
48 static libspectrum_error
49 zlib_inflate( const libspectrum_byte *gzptr, size_t gzlength,
50 libspectrum_byte **outptr, size_t *outlength, int gzip_hack );
51
52 libspectrum_error
libspectrum_zlib_inflate(const libspectrum_byte * gzptr,size_t gzlength,libspectrum_byte ** outptr,size_t * outlength)53 libspectrum_zlib_inflate( const libspectrum_byte *gzptr, size_t gzlength,
54 libspectrum_byte **outptr, size_t *outlength )
55 /* Inflates a block of data.
56 * Input: gzptr -> source (deflated) data
57 * *gzlength == source data length
58 * Output: *outptr -> inflated data (malloced in this fn)
59 * *outlength == length of the inflated data
60 * Returns: error flag (libspectrum_error)
61 */
62 {
63 return zlib_inflate( gzptr, gzlength, outptr, outlength, 0 );
64 }
65
66 libspectrum_error
libspectrum_gzip_inflate(const libspectrum_byte * gzptr,size_t gzlength,libspectrum_byte ** outptr,size_t * outlength)67 libspectrum_gzip_inflate( const libspectrum_byte *gzptr, size_t gzlength,
68 libspectrum_byte **outptr, size_t *outlength )
69 {
70 int error;
71
72 error = skip_gzip_header( &gzptr, &gzlength ); if( error ) return error;
73
74 return zlib_inflate( gzptr, gzlength, outptr, outlength, 1 );
75 }
76
77 libspectrum_error
libspectrum_zip_inflate(const libspectrum_byte * zipptr,size_t ziplength,libspectrum_byte ** outptr,size_t * outlength)78 libspectrum_zip_inflate( const libspectrum_byte *zipptr, size_t ziplength,
79 libspectrum_byte **outptr, size_t *outlength )
80 {
81 return zlib_inflate( zipptr, ziplength, outptr, outlength, 1 );
82 }
83
84 static libspectrum_error
zlib_inflate(const libspectrum_byte * gzptr,size_t gzlength,libspectrum_byte ** outptr,size_t * outlength,int gzip_hack)85 zlib_inflate( const libspectrum_byte *gzptr, size_t gzlength,
86 libspectrum_byte **outptr, size_t *outlength, int gzip_hack )
87 {
88 z_stream stream;
89 int error;
90
91 /* Use default memory management */
92 stream.zalloc = Z_NULL; stream.zfree = Z_NULL; stream.opaque = Z_NULL;
93
94 stream.next_in = gzptr; stream.avail_in = gzlength;
95
96 if( gzip_hack ) {
97
98 /*
99 * HACK ALERT (comment from zlib 1.1.14:gzio.c:143)
100 *
101 * windowBits is passed < 0 to tell that there is no zlib header.
102 * Note that in this case inflate *requires* an extra "dummy" byte
103 * after the compressed stream in order to complete decompression
104 * and return Z_STREAM_END. Here the gzip CRC32 ensures that 4 bytes
105 * are present after the compressed stream.
106 *
107 */
108 error = inflateInit2( &stream, -15 );
109
110 } else {
111
112 error = inflateInit( &stream );
113
114 }
115
116 switch( error ) {
117
118 case Z_OK: break;
119
120 case Z_MEM_ERROR:
121 libspectrum_print_error( LIBSPECTRUM_ERROR_MEMORY,
122 "out of memory at %s:%d", __FILE__, __LINE__ );
123 inflateEnd( &stream );
124 return LIBSPECTRUM_ERROR_MEMORY;
125
126 default:
127 libspectrum_print_error( LIBSPECTRUM_ERROR_LOGIC,
128 "error from inflateInit2: %s", stream.msg );
129 inflateEnd( &stream );
130 return LIBSPECTRUM_ERROR_MEMORY;
131
132 }
133
134 if( *outlength ) {
135
136 *outptr = libspectrum_new( libspectrum_byte, *outlength );
137 stream.next_out = *outptr; stream.avail_out = *outlength;
138 error = inflate( &stream, Z_FINISH );
139
140 } else {
141
142 *outptr = stream.next_out = NULL;
143 *outlength = stream.avail_out = 0;
144
145 do {
146
147 libspectrum_byte *ptr;
148
149 *outlength += 16384; stream.avail_out += 16384;
150 ptr = libspectrum_renew( libspectrum_byte, *outptr, *outlength );
151 stream.next_out = ptr + ( stream.next_out - *outptr );
152 *outptr = ptr;
153
154 error = inflate( &stream, 0 );
155
156 } while( error == Z_OK );
157
158 }
159
160 *outlength = stream.next_out - *outptr;
161 *outptr = libspectrum_renew( libspectrum_byte, *outptr, *outlength );
162
163 switch( error ) {
164
165 case Z_STREAM_END: break;
166
167 case Z_NEED_DICT:
168 libspectrum_print_error( LIBSPECTRUM_ERROR_UNKNOWN,
169 "gzip inflation needs dictionary" );
170 libspectrum_free( *outptr );
171 inflateEnd( &stream );
172 return LIBSPECTRUM_ERROR_UNKNOWN;
173
174 case Z_DATA_ERROR:
175 libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT, "corrupt gzip data" );
176 libspectrum_free( *outptr );
177 inflateEnd( &stream );
178 return LIBSPECTRUM_ERROR_CORRUPT;
179
180 case Z_MEM_ERROR:
181 libspectrum_print_error( LIBSPECTRUM_ERROR_MEMORY,
182 "out of memory at %s:%d", __FILE__, __LINE__ );
183 libspectrum_free( *outptr );
184 inflateEnd( &stream );
185 return LIBSPECTRUM_ERROR_MEMORY;
186
187 case Z_BUF_ERROR:
188 libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT,
189 "not enough space in gzip output buffer" );
190 libspectrum_free( *outptr );
191 inflateEnd( &stream );
192 return LIBSPECTRUM_ERROR_CORRUPT;
193
194 default:
195 libspectrum_print_error( LIBSPECTRUM_ERROR_LOGIC,
196 "gzip error from inflate: %s",
197 stream.msg );
198 libspectrum_free( *outptr );
199 inflateEnd( &stream );
200 return LIBSPECTRUM_ERROR_LOGIC;
201
202 }
203
204 error = inflateEnd( &stream );
205 if( error != Z_OK ) {
206 libspectrum_print_error( LIBSPECTRUM_ERROR_LOGIC,
207 "gzip error from inflateEnd: %s", stream.msg );
208 libspectrum_free( *outptr );
209 inflateEnd( &stream );
210 return LIBSPECTRUM_ERROR_LOGIC;
211 }
212
213 return LIBSPECTRUM_ERROR_NONE;
214 }
215
216 static libspectrum_error
skip_gzip_header(const libspectrum_byte ** gzptr,size_t * gzlength)217 skip_gzip_header( const libspectrum_byte **gzptr, size_t *gzlength )
218 {
219 libspectrum_byte flags;
220 libspectrum_error error;
221
222 if( *gzlength < 10 ) {
223 libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT,
224 "not enough data for gzip header" );
225 return LIBSPECTRUM_ERROR_CORRUPT;
226 }
227
228 if( (*gzptr)[0] != 0x1f || (*gzptr)[1] != 0x8b ) {
229 libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT,
230 "gzip header missing" );
231 return LIBSPECTRUM_ERROR_CORRUPT;
232 }
233
234 if( (*gzptr)[2] != 8 ) {
235 libspectrum_print_error( LIBSPECTRUM_ERROR_UNKNOWN,
236 "unknown gzip compression method %d",
237 (*gzptr)[2] );
238 return LIBSPECTRUM_ERROR_UNKNOWN;
239 }
240
241 flags = (*gzptr)[3];
242
243 (*gzptr) += 10; (*gzlength) -= 10;
244
245 if( flags & 0x04 ) { /* extra header present */
246
247 size_t length;
248
249 if( *gzlength < 2 ) {
250 libspectrum_print_error(
251 LIBSPECTRUM_ERROR_CORRUPT,
252 "not enough data for gzip extra header length"
253 );
254 return LIBSPECTRUM_ERROR_CORRUPT;
255 }
256
257 length = (*gzptr)[0] + (*gzptr)[1] * 0x100;
258 (*gzptr) += 2; (*gzlength) -= 2;
259
260 if( *gzlength < length ) {
261 libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT,
262 "not enough data for gzip extra header" );
263 return LIBSPECTRUM_ERROR_CORRUPT;
264 }
265
266 }
267
268 if( flags & 0x08 ) { /* original file name present */
269 error = skip_null_terminated_string( gzptr, gzlength, "original name" );
270 if( error ) return error;
271 }
272
273 if( flags & 0x10 ) { /* comment present */
274 error = skip_null_terminated_string( gzptr, gzlength, "comment" );
275 if( error ) return error;
276 }
277
278 if( flags & 0x02 ) { /* header CRC present */
279
280 if( *gzlength < 2 ) {
281 libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT,
282 "not enough data for gzip header CRC" );
283 return LIBSPECTRUM_ERROR_CORRUPT;
284 }
285
286 /* Could check the header CRC if we really wanted to */
287 (*gzptr) += 2; (*gzptr) -= 2;
288 }
289
290 return LIBSPECTRUM_ERROR_NONE;
291 }
292
293 static libspectrum_error
skip_null_terminated_string(const libspectrum_byte ** ptr,size_t * length,const char * name)294 skip_null_terminated_string( const libspectrum_byte **ptr, size_t *length,
295 const char *name )
296 {
297 while( **ptr && *length ) { (*ptr)++; (*length)--; }
298
299 if( !( *length ) ) {
300 libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT,
301 "not enough data for gzip %s", name );
302 return LIBSPECTRUM_ERROR_CORRUPT;
303 }
304
305 /* Skip the null as well */
306 (*ptr)++; (*length)--;
307
308 return LIBSPECTRUM_ERROR_NONE;
309 }
310
311 libspectrum_error
libspectrum_zlib_compress(const libspectrum_byte * data,size_t length,libspectrum_byte ** gzptr,size_t * gzlength)312 libspectrum_zlib_compress( const libspectrum_byte *data, size_t length,
313 libspectrum_byte **gzptr, size_t *gzlength )
314 /* Deflates a block of data.
315 * Input: data -> source data
316 * length == source data length
317 * Output: *gzptr -> deflated data (malloced in this fn),
318 * *gzlength == length of the deflated data
319 * Returns: error flag (libspectrum_error)
320 */
321 {
322 uLongf gzl = (uLongf)( length * 1.001 ) + 12;
323 int gzret;
324
325 *gzptr = libspectrum_new( libspectrum_byte, gzl );
326 gzret = compress2( *gzptr, &gzl, data, length, Z_BEST_COMPRESSION );
327
328 switch (gzret) {
329
330 case Z_OK: /* initialised OK */
331 *gzlength = gzl;
332 return LIBSPECTRUM_ERROR_NONE;
333
334 case Z_MEM_ERROR: /* out of memory */
335 libspectrum_free( *gzptr ); *gzptr = 0;
336 libspectrum_print_error( LIBSPECTRUM_ERROR_MEMORY,
337 "libspectrum_zlib_compress: out of memory" );
338 return LIBSPECTRUM_ERROR_MEMORY;
339
340 case Z_VERSION_ERROR: /* unrecognised version */
341 libspectrum_free( *gzptr ); *gzptr = 0;
342 libspectrum_print_error( LIBSPECTRUM_ERROR_UNKNOWN,
343 "libspectrum_zlib_compress: unknown version" );
344 return LIBSPECTRUM_ERROR_UNKNOWN;
345
346 case Z_BUF_ERROR: /* Not enough space in output buffer.
347 Shouldn't happen */
348 libspectrum_free( *gzptr ); *gzptr = 0;
349 libspectrum_print_error( LIBSPECTRUM_ERROR_LOGIC,
350 "libspectrum_zlib_compress: out of space?" );
351 return LIBSPECTRUM_ERROR_LOGIC;
352
353 default: /* some other error */
354 libspectrum_free( *gzptr ); *gzptr = 0;
355 libspectrum_print_error( LIBSPECTRUM_ERROR_LOGIC,
356 "libspectrum_zlib_compress: unexpected error?" );
357 return LIBSPECTRUM_ERROR_LOGIC;
358 }
359 }
360