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