xref: /PHP-5.4/ext/zlib/zlib_filter.c (revision 54fbbded)
1 /*
2    +----------------------------------------------------------------------+
3    | PHP Version 5                                                        |
4    +----------------------------------------------------------------------+
5    | Copyright (c) 1997-2014 The PHP Group                                |
6    +----------------------------------------------------------------------+
7    | This source file is subject to version 3.01 of the PHP license,      |
8    | that is bundled with this package in the file LICENSE, and is        |
9    | available through the world-wide-web at the following url:           |
10    | http://www.php.net/license/3_01.txt                                  |
11    | If you did not receive a copy of the PHP license and are unable to   |
12    | obtain it through the world-wide-web, please send a note to          |
13    | license@php.net so we can mail you a copy immediately.               |
14    +----------------------------------------------------------------------+
15    | Authors: Sara Golemon (pollita@php.net)                              |
16    +----------------------------------------------------------------------+
17 */
18 
19 /* $Id$ */
20 
21 #include "php.h"
22 #include "php_zlib.h"
23 
24 /* {{{ data structure */
25 
26 /* Passed as opaque in malloc callbacks */
27 typedef struct _php_zlib_filter_data {
28 	int persistent;
29 	z_stream strm;
30 	char *inbuf;
31 	size_t inbuf_len;
32 	char *outbuf;
33 	size_t outbuf_len;
34 	zend_bool finished;
35 } php_zlib_filter_data;
36 
37 /* }}} */
38 
39 /* {{{ Memory management wrappers */
40 
php_zlib_alloc(voidpf opaque,uInt items,uInt size)41 static voidpf php_zlib_alloc(voidpf opaque, uInt items, uInt size)
42 {
43 	return (voidpf)safe_pemalloc(items, size, 0, ((php_zlib_filter_data*)opaque)->persistent);
44 }
45 
php_zlib_free(voidpf opaque,voidpf address)46 static void php_zlib_free(voidpf opaque, voidpf address)
47 {
48 	pefree((void*)address, ((php_zlib_filter_data*)opaque)->persistent);
49 }
50 /* }}} */
51 
52 /* {{{ zlib.inflate filter implementation */
53 
php_zlib_inflate_filter(php_stream * stream,php_stream_filter * thisfilter,php_stream_bucket_brigade * buckets_in,php_stream_bucket_brigade * buckets_out,size_t * bytes_consumed,int flags TSRMLS_DC)54 static php_stream_filter_status_t php_zlib_inflate_filter(
55 	php_stream *stream,
56 	php_stream_filter *thisfilter,
57 	php_stream_bucket_brigade *buckets_in,
58 	php_stream_bucket_brigade *buckets_out,
59 	size_t *bytes_consumed,
60 	int flags
61 	TSRMLS_DC)
62 {
63 	php_zlib_filter_data *data;
64 	php_stream_bucket *bucket;
65 	size_t consumed = 0, original_out, original_in;
66 	int status;
67 	php_stream_filter_status_t exit_status = PSFS_FEED_ME;
68 	z_stream *streamp;
69 
70 	if (!thisfilter || !thisfilter->abstract) {
71 		/* Should never happen */
72 		return PSFS_ERR_FATAL;
73 	}
74 
75 	data = (php_zlib_filter_data *)(thisfilter->abstract);
76 	streamp = &(data->strm);
77 	original_in = data->strm.total_in;
78 	original_out = data->strm.total_out;
79 
80 	while (buckets_in->head) {
81 		size_t bin = 0, desired;
82 
83 		bucket = buckets_in->head;
84 
85 		bucket = php_stream_bucket_make_writeable(buckets_in->head TSRMLS_CC);
86 
87 		while (bin < (unsigned int) bucket->buflen) {
88 
89 			if (data->finished) {
90 				consumed += bucket->buflen;
91 				break;
92 			}
93 
94 			desired = bucket->buflen - bin;
95 			if (desired > data->inbuf_len) {
96 				desired = data->inbuf_len;
97 			}
98 			memcpy(data->strm.next_in, bucket->buf + bin, desired);
99 			data->strm.avail_in = desired;
100 
101 			status = inflate(&(data->strm), flags & PSFS_FLAG_FLUSH_CLOSE ? Z_FINISH : Z_SYNC_FLUSH);
102 			if (status == Z_STREAM_END) {
103 				inflateEnd(&(data->strm));
104 				data->finished = '\1';
105 			} else if (status != Z_OK) {
106 				/* Something bad happened */
107 				php_stream_bucket_delref(bucket TSRMLS_CC);
108 				/* reset these because despite the error the filter may be used again */
109 				data->strm.next_in = data->inbuf;
110 				data->strm.avail_in = 0;
111 				return PSFS_ERR_FATAL;
112 			}
113 			desired -= data->strm.avail_in; /* desired becomes what we consumed this round through */
114 			data->strm.next_in = data->inbuf;
115 			data->strm.avail_in = 0;
116 			bin += desired;
117 
118 			if (data->strm.avail_out < data->outbuf_len) {
119 				php_stream_bucket *out_bucket;
120 				size_t bucketlen = data->outbuf_len - data->strm.avail_out;
121 				out_bucket = php_stream_bucket_new(stream, estrndup(data->outbuf, bucketlen), bucketlen, 1, 0 TSRMLS_CC);
122 				php_stream_bucket_append(buckets_out, out_bucket TSRMLS_CC);
123 				data->strm.avail_out = data->outbuf_len;
124 				data->strm.next_out = data->outbuf;
125 				exit_status = PSFS_PASS_ON;
126 			} else if (status == Z_STREAM_END && data->strm.avail_out >= data->outbuf_len) {
127 				/* no more data to decompress, and nothing was spat out */
128 				php_stream_bucket_delref(bucket TSRMLS_CC);
129 				return PSFS_PASS_ON;
130 			}
131 
132 		}
133 		consumed += bucket->buflen;
134 		php_stream_bucket_delref(bucket TSRMLS_CC);
135 	}
136 
137 	if (!data->finished && flags & PSFS_FLAG_FLUSH_CLOSE) {
138 		/* Spit it out! */
139 		status = Z_OK;
140 		while (status == Z_OK) {
141 			status = inflate(&(data->strm), Z_FINISH);
142 			if (data->strm.avail_out < data->outbuf_len) {
143 				size_t bucketlen = data->outbuf_len - data->strm.avail_out;
144 
145 				bucket = php_stream_bucket_new(stream, estrndup(data->outbuf, bucketlen), bucketlen, 1, 0 TSRMLS_CC);
146 				php_stream_bucket_append(buckets_out, bucket TSRMLS_CC);
147 				data->strm.avail_out = data->outbuf_len;
148 				data->strm.next_out = data->outbuf;
149 				exit_status = PSFS_PASS_ON;
150 			}
151 		}
152 	}
153 
154 	if (bytes_consumed) {
155 		*bytes_consumed = consumed;
156 	}
157 
158 	return exit_status;
159 }
160 
php_zlib_inflate_dtor(php_stream_filter * thisfilter TSRMLS_DC)161 static void php_zlib_inflate_dtor(php_stream_filter *thisfilter TSRMLS_DC)
162 {
163 	if (thisfilter && thisfilter->abstract) {
164 		php_zlib_filter_data *data = thisfilter->abstract;
165 		if (!data->finished) {
166 			inflateEnd(&(data->strm));
167 		}
168 		pefree(data->inbuf, data->persistent);
169 		pefree(data->outbuf, data->persistent);
170 		pefree(data, data->persistent);
171 	}
172 }
173 
174 static php_stream_filter_ops php_zlib_inflate_ops = {
175 	php_zlib_inflate_filter,
176 	php_zlib_inflate_dtor,
177 	"zlib.inflate"
178 };
179 /* }}} */
180 
181 /* {{{ zlib.inflate filter implementation */
182 
php_zlib_deflate_filter(php_stream * stream,php_stream_filter * thisfilter,php_stream_bucket_brigade * buckets_in,php_stream_bucket_brigade * buckets_out,size_t * bytes_consumed,int flags TSRMLS_DC)183 static php_stream_filter_status_t php_zlib_deflate_filter(
184 	php_stream *stream,
185 	php_stream_filter *thisfilter,
186 	php_stream_bucket_brigade *buckets_in,
187 	php_stream_bucket_brigade *buckets_out,
188 	size_t *bytes_consumed,
189 	int flags
190 	TSRMLS_DC)
191 {
192 	php_zlib_filter_data *data;
193 	php_stream_bucket *bucket;
194 	size_t consumed = 0, original_out, original_in;
195 	int status;
196 	php_stream_filter_status_t exit_status = PSFS_FEED_ME;
197 	z_stream *streamp;
198 
199 	if (!thisfilter || !thisfilter->abstract) {
200 		/* Should never happen */
201 		return PSFS_ERR_FATAL;
202 	}
203 
204 	data = (php_zlib_filter_data *)(thisfilter->abstract);
205 	streamp = &(data->strm);
206 	original_in = data->strm.total_in;
207 	original_out = data->strm.total_out;
208 
209 	while (buckets_in->head) {
210 		size_t bin = 0, desired;
211 
212 		bucket = buckets_in->head;
213 
214 		bucket = php_stream_bucket_make_writeable(bucket TSRMLS_CC);
215 
216 		while (bin < (unsigned int) bucket->buflen) {
217 			desired = bucket->buflen - bin;
218 			if (desired > data->inbuf_len) {
219 				desired = data->inbuf_len;
220 			}
221 			memcpy(data->strm.next_in, bucket->buf + bin, desired);
222 			data->strm.avail_in = desired;
223 
224 			status = deflate(&(data->strm), flags & PSFS_FLAG_FLUSH_CLOSE ? Z_FULL_FLUSH : (flags & PSFS_FLAG_FLUSH_INC ? Z_SYNC_FLUSH : Z_NO_FLUSH));
225 			if (status != Z_OK) {
226 				/* Something bad happened */
227 				php_stream_bucket_delref(bucket TSRMLS_CC);
228 				return PSFS_ERR_FATAL;
229 			}
230 			desired -= data->strm.avail_in; /* desired becomes what we consumed this round through */
231 			data->strm.next_in = data->inbuf;
232 			data->strm.avail_in = 0;
233 			bin += desired;
234 
235 			if (data->strm.avail_out < data->outbuf_len) {
236 				php_stream_bucket *out_bucket;
237 				size_t bucketlen = data->outbuf_len - data->strm.avail_out;
238 
239 				out_bucket = php_stream_bucket_new(stream, estrndup(data->outbuf, bucketlen), bucketlen, 1, 0 TSRMLS_CC);
240 				php_stream_bucket_append(buckets_out, out_bucket TSRMLS_CC);
241 				data->strm.avail_out = data->outbuf_len;
242 				data->strm.next_out = data->outbuf;
243 				exit_status = PSFS_PASS_ON;
244 			}
245 		}
246 		consumed += bucket->buflen;
247 		php_stream_bucket_delref(bucket TSRMLS_CC);
248 	}
249 
250 	if (flags & PSFS_FLAG_FLUSH_CLOSE) {
251 		/* Spit it out! */
252 		status = Z_OK;
253 		while (status == Z_OK) {
254 			status = deflate(&(data->strm), Z_FINISH);
255 			if (data->strm.avail_out < data->outbuf_len) {
256 				size_t bucketlen = data->outbuf_len - data->strm.avail_out;
257 
258 				bucket = php_stream_bucket_new(stream, estrndup(data->outbuf, bucketlen), bucketlen, 1, 0 TSRMLS_CC);
259 				php_stream_bucket_append(buckets_out, bucket TSRMLS_CC);
260 				data->strm.avail_out = data->outbuf_len;
261 				data->strm.next_out = data->outbuf;
262 				exit_status = PSFS_PASS_ON;
263 			}
264 		}
265 	}
266 
267 	if (bytes_consumed) {
268 		*bytes_consumed = consumed;
269 	}
270 
271 	return exit_status;
272 }
273 
php_zlib_deflate_dtor(php_stream_filter * thisfilter TSRMLS_DC)274 static void php_zlib_deflate_dtor(php_stream_filter *thisfilter TSRMLS_DC)
275 {
276 	if (thisfilter && thisfilter->abstract) {
277 		php_zlib_filter_data *data = thisfilter->abstract;
278 		deflateEnd(&(data->strm));
279 		pefree(data->inbuf, data->persistent);
280 		pefree(data->outbuf, data->persistent);
281 		pefree(data, data->persistent);
282 	}
283 }
284 
285 static php_stream_filter_ops php_zlib_deflate_ops = {
286 	php_zlib_deflate_filter,
287 	php_zlib_deflate_dtor,
288 	"zlib.deflate"
289 };
290 
291 /* }}} */
292 
293 /* {{{ zlib.* common factory */
294 
php_zlib_filter_create(const char * filtername,zval * filterparams,int persistent TSRMLS_DC)295 static php_stream_filter *php_zlib_filter_create(const char *filtername, zval *filterparams, int persistent TSRMLS_DC)
296 {
297 	php_stream_filter_ops *fops = NULL;
298 	php_zlib_filter_data *data;
299 	int status;
300 
301 	/* Create this filter */
302 	data = pecalloc(1, sizeof(php_zlib_filter_data), persistent);
303 	if (!data) {
304 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed allocating %zd bytes", sizeof(php_zlib_filter_data));
305 		return NULL;
306 	}
307 
308 	/* Circular reference */
309 	data->strm.opaque = (voidpf) data;
310 
311 	data->strm.zalloc = (alloc_func) php_zlib_alloc;
312 	data->strm.zfree = (free_func) php_zlib_free;
313 	data->strm.avail_out = data->outbuf_len = data->inbuf_len = 0x8000;
314 	data->strm.next_in = data->inbuf = (Bytef *) pemalloc(data->inbuf_len, persistent);
315 	if (!data->inbuf) {
316 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed allocating %zd bytes", data->inbuf_len);
317 		pefree(data, persistent);
318 		return NULL;
319 	}
320 	data->strm.avail_in = 0;
321 	data->strm.next_out = data->outbuf = (Bytef *) pemalloc(data->outbuf_len, persistent);
322 	if (!data->outbuf) {
323 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed allocating %zd bytes", data->outbuf_len);
324 		pefree(data->inbuf, persistent);
325 		pefree(data, persistent);
326 		return NULL;
327 	}
328 
329 	data->strm.data_type = Z_ASCII;
330 
331 	if (strcasecmp(filtername, "zlib.inflate") == 0) {
332 		int windowBits = -MAX_WBITS;
333 
334 		if (filterparams) {
335 			zval **tmpzval;
336 
337 			if ((Z_TYPE_P(filterparams) == IS_ARRAY || Z_TYPE_P(filterparams) == IS_OBJECT) &&
338 				zend_hash_find(HASH_OF(filterparams), "window", sizeof("window"), (void **) &tmpzval) == SUCCESS) {
339 				zval tmp;
340 
341 				/* log-2 base of history window (9 - 15) */
342 				tmp = **tmpzval;
343 				zval_copy_ctor(&tmp);
344 				convert_to_long(&tmp);
345 				if (Z_LVAL(tmp) < -MAX_WBITS || Z_LVAL(tmp) > MAX_WBITS + 32) {
346 					php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid parameter give for window size. (%ld)", Z_LVAL(tmp));
347 				} else {
348 					windowBits = Z_LVAL(tmp);
349 				}
350 			}
351 		}
352 
353 		/* RFC 1951 Inflate */
354 		data->finished = '\0';
355 		status = inflateInit2(&(data->strm), windowBits);
356 		fops = &php_zlib_inflate_ops;
357 	} else if (strcasecmp(filtername, "zlib.deflate") == 0) {
358 		/* RFC 1951 Deflate */
359 		int level = Z_DEFAULT_COMPRESSION;
360 		int windowBits = -MAX_WBITS;
361 		int memLevel = MAX_MEM_LEVEL;
362 
363 
364 		if (filterparams) {
365 			zval **tmpzval, tmp;
366 
367 			/* filterparams can either be a scalar value to indicate compression level (shortcut method)
368                Or can be a hash containing one or more of 'window', 'memory', and/or 'level' members. */
369 
370 			switch (Z_TYPE_P(filterparams)) {
371 				case IS_ARRAY:
372 				case IS_OBJECT:
373 					if (zend_hash_find(HASH_OF(filterparams), "memory", sizeof("memory"), (void**) &tmpzval) == SUCCESS) {
374 						tmp = **tmpzval;
375 						zval_copy_ctor(&tmp);
376 						convert_to_long(&tmp);
377 
378 						/* Memory Level (1 - 9) */
379 						if (Z_LVAL(tmp) < 1 || Z_LVAL(tmp) > MAX_MEM_LEVEL) {
380 							php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid parameter give for memory level. (%ld)", Z_LVAL(tmp));
381 						} else {
382 							memLevel = Z_LVAL(tmp);
383 						}
384 					}
385 
386 					if (zend_hash_find(HASH_OF(filterparams), "window", sizeof("window"), (void**) &tmpzval) == SUCCESS) {
387 						tmp = **tmpzval;
388 						zval_copy_ctor(&tmp);
389 						convert_to_long(&tmp);
390 
391 						/* log-2 base of history window (9 - 15) */
392 						if (Z_LVAL(tmp) < -MAX_WBITS || Z_LVAL(tmp) > MAX_WBITS + 16) {
393 							php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid parameter give for window size. (%ld)", Z_LVAL(tmp));
394 						} else {
395 							windowBits = Z_LVAL(tmp);
396 						}
397 					}
398 
399 					if (zend_hash_find(HASH_OF(filterparams), "level", sizeof("level"), (void**) &tmpzval) == SUCCESS) {
400 						tmp = **tmpzval;
401 
402 						/* Psuedo pass through to catch level validating code */
403 						goto factory_setlevel;
404 					}
405 					break;
406 				case IS_STRING:
407 				case IS_DOUBLE:
408 				case IS_LONG:
409 					tmp = *filterparams;
410 factory_setlevel:
411 					zval_copy_ctor(&tmp);
412 					convert_to_long(&tmp);
413 
414 					/* Set compression level within reason (-1 == default, 0 == none, 1-9 == least to most compression */
415 					if (Z_LVAL(tmp) < -1 || Z_LVAL(tmp) > 9) {
416 						php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid compression level specified. (%ld)", Z_LVAL(tmp));
417 					} else {
418 						level = Z_LVAL(tmp);
419 					}
420 					break;
421 				default:
422 					php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid filter parameter, ignored");
423 			}
424 		}
425 		status = deflateInit2(&(data->strm), level, Z_DEFLATED, windowBits, memLevel, 0);
426 		fops = &php_zlib_deflate_ops;
427 	} else {
428 		status = Z_DATA_ERROR;
429 	}
430 
431 	if (status != Z_OK) {
432 		/* Unspecified (probably strm) error, let stream-filter error do its own whining */
433 		pefree(data->strm.next_in, persistent);
434 		pefree(data->strm.next_out, persistent);
435 		pefree(data, persistent);
436 		return NULL;
437 	}
438 
439 	return php_stream_filter_alloc(fops, data, persistent);
440 }
441 
442 php_stream_filter_factory php_zlib_filter_factory = {
443 	php_zlib_filter_create
444 };
445 /* }}} */
446 
447 /*
448  * Local variables:
449  * tab-width: 4
450  * c-basic-offset: 4
451  * End:
452  * vim600: sw=4 ts=4 fdm=marker
453  * vim<600: sw=4 ts=4
454  */
455