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