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