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