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