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