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