1 /*
2 +----------------------------------------------------------------------+
3 | PHP Version 7 |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 1997-2017 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) {
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
php_zlib_inflate_dtor(php_stream_filter * thisfilter)157 static 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
170 static 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.deflate filter implementation */
178
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)179 static 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
php_zlib_deflate_dtor(php_stream_filter * thisfilter)268 static 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
279 static 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
php_zlib_filter_create(const char * filtername,zval * filterparams,int persistent)289 static 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);
395 factory_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
424 php_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