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