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