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 #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;
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) {
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 			desired = bucket->buflen - bin;
200 			if (desired > data->inbuf_len) {
201 				desired = data->inbuf_len;
202 			}
203 			memcpy(data->strm.next_in, bucket->buf + bin, desired);
204 			data->strm.avail_in = desired;
205 
206 			status = deflate(&(data->strm), flags & PSFS_FLAG_FLUSH_CLOSE ? Z_FULL_FLUSH : (flags & PSFS_FLAG_FLUSH_INC ? Z_SYNC_FLUSH : Z_NO_FLUSH));
207 			if (status != Z_OK) {
208 				/* Something bad happened */
209 				php_stream_bucket_delref(bucket);
210 				return PSFS_ERR_FATAL;
211 			}
212 			desired -= data->strm.avail_in; /* desired becomes what we consumed this round through */
213 			data->strm.next_in = data->inbuf;
214 			data->strm.avail_in = 0;
215 			bin += desired;
216 
217 			if (data->strm.avail_out < data->outbuf_len) {
218 				php_stream_bucket *out_bucket;
219 				size_t bucketlen = data->outbuf_len - data->strm.avail_out;
220 
221 				out_bucket = php_stream_bucket_new(
222 					stream, estrndup((char *) data->outbuf, bucketlen), bucketlen, 1, 0);
223 				php_stream_bucket_append(buckets_out, out_bucket);
224 				data->strm.avail_out = data->outbuf_len;
225 				data->strm.next_out = data->outbuf;
226 				exit_status = PSFS_PASS_ON;
227 			}
228 		}
229 		consumed += bucket->buflen;
230 		php_stream_bucket_delref(bucket);
231 	}
232 
233 	if (flags & PSFS_FLAG_FLUSH_CLOSE) {
234 		/* Spit it out! */
235 		status = Z_OK;
236 		while (status == Z_OK) {
237 			status = deflate(&(data->strm), Z_FINISH);
238 			if (data->strm.avail_out < data->outbuf_len) {
239 				size_t bucketlen = data->outbuf_len - data->strm.avail_out;
240 
241 				bucket = php_stream_bucket_new(
242 					stream, estrndup((char *) data->outbuf, bucketlen), bucketlen, 1, 0);
243 				php_stream_bucket_append(buckets_out, bucket);
244 				data->strm.avail_out = data->outbuf_len;
245 				data->strm.next_out = data->outbuf;
246 				exit_status = PSFS_PASS_ON;
247 			}
248 		}
249 	}
250 
251 	if (bytes_consumed) {
252 		*bytes_consumed = consumed;
253 	}
254 
255 	return exit_status;
256 }
257 
php_zlib_deflate_dtor(php_stream_filter * thisfilter)258 static void php_zlib_deflate_dtor(php_stream_filter *thisfilter)
259 {
260 	if (thisfilter && Z_PTR(thisfilter->abstract)) {
261 		php_zlib_filter_data *data = Z_PTR(thisfilter->abstract);
262 		deflateEnd(&(data->strm));
263 		pefree(data->inbuf, data->persistent);
264 		pefree(data->outbuf, data->persistent);
265 		pefree(data, data->persistent);
266 	}
267 }
268 
269 static const php_stream_filter_ops php_zlib_deflate_ops = {
270 	php_zlib_deflate_filter,
271 	php_zlib_deflate_dtor,
272 	"zlib.deflate"
273 };
274 
275 /* }}} */
276 
277 /* {{{ zlib.* common factory */
278 
php_zlib_filter_create(const char * filtername,zval * filterparams,uint8_t persistent)279 static php_stream_filter *php_zlib_filter_create(const char *filtername, zval *filterparams, uint8_t persistent)
280 {
281 	const php_stream_filter_ops *fops = NULL;
282 	php_zlib_filter_data *data;
283 	int status;
284 
285 	/* Create this filter */
286 	data = pecalloc(1, sizeof(php_zlib_filter_data), persistent);
287 	if (!data) {
288 		php_error_docref(NULL, E_WARNING, "Failed allocating %zd bytes", sizeof(php_zlib_filter_data));
289 		return NULL;
290 	}
291 
292 	/* Circular reference */
293 	data->strm.opaque = (voidpf) data;
294 
295 	data->strm.zalloc = (alloc_func) php_zlib_alloc;
296 	data->strm.zfree = (free_func) php_zlib_free;
297 	data->strm.avail_out = data->outbuf_len = data->inbuf_len = 0x8000;
298 	data->strm.next_in = data->inbuf = (Bytef *) pemalloc(data->inbuf_len, persistent);
299 	if (!data->inbuf) {
300 		php_error_docref(NULL, E_WARNING, "Failed allocating %zd bytes", data->inbuf_len);
301 		pefree(data, persistent);
302 		return NULL;
303 	}
304 	data->strm.avail_in = 0;
305 	data->strm.next_out = data->outbuf = (Bytef *) pemalloc(data->outbuf_len, persistent);
306 	if (!data->outbuf) {
307 		php_error_docref(NULL, E_WARNING, "Failed allocating %zd bytes", data->outbuf_len);
308 		pefree(data->inbuf, persistent);
309 		pefree(data, persistent);
310 		return NULL;
311 	}
312 
313 	data->strm.data_type = Z_ASCII;
314 
315 	if (strcasecmp(filtername, "zlib.inflate") == 0) {
316 		int windowBits = -MAX_WBITS;
317 
318 		if (filterparams) {
319 			zval *tmpzval;
320 
321 			if ((Z_TYPE_P(filterparams) == IS_ARRAY || Z_TYPE_P(filterparams) == IS_OBJECT) &&
322 				(tmpzval = zend_hash_str_find(HASH_OF(filterparams), "window", sizeof("window") - 1))) {
323 				/* log-2 base of history window (9 - 15) */
324 				zend_long tmp = zval_get_long(tmpzval);
325 				if (tmp < -MAX_WBITS || tmp > MAX_WBITS + 32) {
326 					php_error_docref(NULL, E_WARNING, "Invalid parameter give for window size. (" ZEND_LONG_FMT ")", tmp);
327 				} else {
328 					windowBits = tmp;
329 				}
330 			}
331 		}
332 
333 		/* RFC 1951 Inflate */
334 		data->finished = '\0';
335 		status = inflateInit2(&(data->strm), windowBits);
336 		fops = &php_zlib_inflate_ops;
337 	} else if (strcasecmp(filtername, "zlib.deflate") == 0) {
338 		/* RFC 1951 Deflate */
339 		int level = Z_DEFAULT_COMPRESSION;
340 		int windowBits = -MAX_WBITS;
341 		int memLevel = MAX_MEM_LEVEL;
342 
343 
344 		if (filterparams) {
345 			zval *tmpzval;
346 			zend_long tmp;
347 
348 			/* filterparams can either be a scalar value to indicate compression level (shortcut method)
349                Or can be a hash containing one or more of 'window', 'memory', and/or 'level' members. */
350 
351 			switch (Z_TYPE_P(filterparams)) {
352 				case IS_ARRAY:
353 				case IS_OBJECT:
354 					if ((tmpzval = zend_hash_str_find(HASH_OF(filterparams), "memory", sizeof("memory") -1))) {
355 						/* Memory Level (1 - 9) */
356 						tmp = zval_get_long(tmpzval);
357 						if (tmp < 1 || tmp > MAX_MEM_LEVEL) {
358 							php_error_docref(NULL, E_WARNING, "Invalid parameter give for memory level. (" ZEND_LONG_FMT ")", tmp);
359 						} else {
360 							memLevel = tmp;
361 						}
362 					}
363 
364 					if ((tmpzval = zend_hash_str_find(HASH_OF(filterparams), "window", sizeof("window") - 1))) {
365 						/* log-2 base of history window (9 - 15) */
366 						tmp = zval_get_long(tmpzval);
367 						if (tmp < -MAX_WBITS || tmp > MAX_WBITS + 16) {
368 							php_error_docref(NULL, E_WARNING, "Invalid parameter give for window size. (" ZEND_LONG_FMT ")", tmp);
369 						} else {
370 							windowBits = tmp;
371 						}
372 					}
373 
374 					if ((tmpzval = zend_hash_str_find(HASH_OF(filterparams), "level", sizeof("level") - 1))) {
375 						tmp = zval_get_long(tmpzval);
376 
377 						/* Pseudo pass through to catch level validating code */
378 						goto factory_setlevel;
379 					}
380 					break;
381 				case IS_STRING:
382 				case IS_DOUBLE:
383 				case IS_LONG:
384 					tmp = zval_get_long(filterparams);
385 factory_setlevel:
386 					/* Set compression level within reason (-1 == default, 0 == none, 1-9 == least to most compression */
387 					if (tmp < -1 || tmp > 9) {
388 						php_error_docref(NULL, E_WARNING, "Invalid compression level specified. (" ZEND_LONG_FMT ")", tmp);
389 					} else {
390 						level = tmp;
391 					}
392 					break;
393 				default:
394 					php_error_docref(NULL, E_WARNING, "Invalid filter parameter, ignored");
395 			}
396 		}
397 		status = deflateInit2(&(data->strm), level, Z_DEFLATED, windowBits, memLevel, 0);
398 		fops = &php_zlib_deflate_ops;
399 	} else {
400 		status = Z_DATA_ERROR;
401 	}
402 
403 	if (status != Z_OK) {
404 		/* Unspecified (probably strm) error, let stream-filter error do its own whining */
405 		pefree(data->strm.next_in, persistent);
406 		pefree(data->strm.next_out, persistent);
407 		pefree(data, persistent);
408 		return NULL;
409 	}
410 
411 	return php_stream_filter_alloc(fops, data, persistent);
412 }
413 
414 const php_stream_filter_factory php_zlib_filter_factory = {
415 	php_zlib_filter_create
416 };
417 /* }}} */
418 
419 /*
420  * Local variables:
421  * tab-width: 4
422  * c-basic-offset: 4
423  * End:
424  * vim600: sw=4 ts=4 fdm=marker
425  * vim<600: sw=4 ts=4
426  */
427