1/*
2   +----------------------------------------------------------------------+
3   | PHP Version 7                                                        |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 1997-2016 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 */
27typedef 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
41static 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
46static 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
54static 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
157static 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
170static 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.inflate filter implementation */
178
179static 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
268static 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
279static 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
289static 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);
395factory_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
424php_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