1 /*
2 +----------------------------------------------------------------------+
3 | Copyright (c) The PHP Group |
4 +----------------------------------------------------------------------+
5 | This source file is subject to version 3.01 of the PHP license, |
6 | that is bundled with this package in the file LICENSE, and is |
7 | available through the world-wide-web at the following url: |
8 | https://www.php.net/license/3_01.txt |
9 | If you did not receive a copy of the PHP license and are unable to |
10 | obtain it through the world-wide-web, please send a note to |
11 | license@php.net so we can mail you a copy immediately. |
12 +----------------------------------------------------------------------+
13 | Authors: Sara Golemon (pollita@php.net) |
14 +----------------------------------------------------------------------+
15 */
16
17 #include "php.h"
18 #include "php_zlib.h"
19
20 /* {{{ data structure */
21
22 /* Passed as opaque in malloc callbacks */
23 typedef struct _php_zlib_filter_data {
24 z_stream strm;
25 unsigned char *inbuf;
26 size_t inbuf_len;
27 unsigned char *outbuf;
28 size_t outbuf_len;
29 int persistent;
30 bool finished; /* for zlib.deflate: signals that no flush is pending */
31 } php_zlib_filter_data;
32
33 /* }}} */
34
35 /* {{{ Memory management wrappers */
36
php_zlib_alloc(voidpf opaque,uInt items,uInt size)37 static voidpf php_zlib_alloc(voidpf opaque, uInt items, uInt size)
38 {
39 return (voidpf)safe_pemalloc(items, size, 0, ((php_zlib_filter_data*)opaque)->persistent);
40 }
41
php_zlib_free(voidpf opaque,voidpf address)42 static void php_zlib_free(voidpf opaque, voidpf address)
43 {
44 pefree((void*)address, ((php_zlib_filter_data*)opaque)->persistent);
45 }
46 /* }}} */
47
48 /* {{{ zlib.inflate filter implementation */
49
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)50 static php_stream_filter_status_t php_zlib_inflate_filter(
51 php_stream *stream,
52 php_stream_filter *thisfilter,
53 php_stream_bucket_brigade *buckets_in,
54 php_stream_bucket_brigade *buckets_out,
55 size_t *bytes_consumed,
56 int flags
57 )
58 {
59 php_zlib_filter_data *data;
60 php_stream_bucket *bucket;
61 size_t consumed = 0;
62 int status;
63 php_stream_filter_status_t exit_status = PSFS_FEED_ME;
64
65 if (!thisfilter || !Z_PTR(thisfilter->abstract)) {
66 /* Should never happen */
67 return PSFS_ERR_FATAL;
68 }
69
70 data = (php_zlib_filter_data *)(Z_PTR(thisfilter->abstract));
71
72 while (buckets_in->head) {
73 size_t bin = 0, desired;
74
75 bucket = php_stream_bucket_make_writeable(buckets_in->head);
76
77 while (bin < (unsigned int) bucket->buflen && !data->finished) {
78
79 desired = bucket->buflen - bin;
80 if (desired > data->inbuf_len) {
81 desired = data->inbuf_len;
82 }
83 memcpy(data->strm.next_in, bucket->buf + bin, desired);
84 data->strm.avail_in = desired;
85
86 status = inflate(&(data->strm), flags & PSFS_FLAG_FLUSH_CLOSE ? Z_FINISH : Z_SYNC_FLUSH);
87 if (status == Z_STREAM_END) {
88 inflateEnd(&(data->strm));
89 data->finished = '\1';
90 exit_status = PSFS_PASS_ON;
91 } else if (status != Z_OK && status != Z_BUF_ERROR) {
92 /* Something bad happened */
93 php_error_docref(NULL, E_NOTICE, "zlib: %s", zError(status));
94 php_stream_bucket_delref(bucket);
95 /* reset these because despite the error the filter may be used again */
96 data->strm.next_in = data->inbuf;
97 data->strm.avail_in = 0;
98 return PSFS_ERR_FATAL;
99 }
100 desired -= data->strm.avail_in; /* desired becomes what we consumed this round through */
101 data->strm.next_in = data->inbuf;
102 data->strm.avail_in = 0;
103 bin += desired;
104
105 if (data->strm.avail_out < data->outbuf_len) {
106 php_stream_bucket *out_bucket;
107 size_t bucketlen = data->outbuf_len - data->strm.avail_out;
108 out_bucket = php_stream_bucket_new(
109 stream, estrndup((char *) data->outbuf, bucketlen), bucketlen, 1, 0);
110 php_stream_bucket_append(buckets_out, out_bucket);
111 data->strm.avail_out = data->outbuf_len;
112 data->strm.next_out = data->outbuf;
113 exit_status = PSFS_PASS_ON;
114 }
115
116 }
117 consumed += bucket->buflen;
118 php_stream_bucket_delref(bucket);
119 }
120
121 if (!data->finished && flags & PSFS_FLAG_FLUSH_CLOSE) {
122 /* Spit it out! */
123 status = Z_OK;
124 while (status == Z_OK) {
125 status = inflate(&(data->strm), Z_FINISH);
126 if (data->strm.avail_out < data->outbuf_len) {
127 size_t bucketlen = data->outbuf_len - data->strm.avail_out;
128
129 bucket = php_stream_bucket_new(
130 stream, estrndup((char *) data->outbuf, bucketlen), bucketlen, 1, 0);
131 php_stream_bucket_append(buckets_out, bucket);
132 data->strm.avail_out = data->outbuf_len;
133 data->strm.next_out = data->outbuf;
134 exit_status = PSFS_PASS_ON;
135 }
136 }
137 }
138
139 if (bytes_consumed) {
140 *bytes_consumed = consumed;
141 }
142
143 return exit_status;
144 }
145
php_zlib_inflate_dtor(php_stream_filter * thisfilter)146 static void php_zlib_inflate_dtor(php_stream_filter *thisfilter)
147 {
148 if (thisfilter && Z_PTR(thisfilter->abstract)) {
149 php_zlib_filter_data *data = Z_PTR(thisfilter->abstract);
150 if (!data->finished) {
151 inflateEnd(&(data->strm));
152 }
153 pefree(data->inbuf, data->persistent);
154 pefree(data->outbuf, data->persistent);
155 pefree(data, data->persistent);
156 }
157 }
158
159 static const php_stream_filter_ops php_zlib_inflate_ops = {
160 php_zlib_inflate_filter,
161 php_zlib_inflate_dtor,
162 "zlib.inflate"
163 };
164 /* }}} */
165
166 /* {{{ zlib.deflate filter implementation */
167
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)168 static php_stream_filter_status_t php_zlib_deflate_filter(
169 php_stream *stream,
170 php_stream_filter *thisfilter,
171 php_stream_bucket_brigade *buckets_in,
172 php_stream_bucket_brigade *buckets_out,
173 size_t *bytes_consumed,
174 int flags
175 )
176 {
177 php_zlib_filter_data *data;
178 php_stream_bucket *bucket;
179 size_t consumed = 0;
180 int status;
181 php_stream_filter_status_t exit_status = PSFS_FEED_ME;
182
183 if (!thisfilter || !Z_PTR(thisfilter->abstract)) {
184 /* Should never happen */
185 return PSFS_ERR_FATAL;
186 }
187
188 data = (php_zlib_filter_data *)(Z_PTR(thisfilter->abstract));
189
190 while (buckets_in->head) {
191 size_t bin = 0, desired;
192
193 bucket = buckets_in->head;
194
195 bucket = php_stream_bucket_make_writeable(bucket);
196
197 while (bin < (unsigned int) bucket->buflen) {
198 int flush_mode;
199
200 desired = bucket->buflen - bin;
201 if (desired > data->inbuf_len) {
202 desired = data->inbuf_len;
203 }
204 memcpy(data->strm.next_in, bucket->buf + bin, desired);
205 data->strm.avail_in = desired;
206
207 flush_mode = flags & PSFS_FLAG_FLUSH_CLOSE ? Z_FULL_FLUSH : (flags & PSFS_FLAG_FLUSH_INC ? Z_SYNC_FLUSH : Z_NO_FLUSH);
208 data->finished = flush_mode != Z_NO_FLUSH;
209 status = deflate(&(data->strm), flush_mode);
210 if (status != Z_OK) {
211 /* Something bad happened */
212 php_stream_bucket_delref(bucket);
213 return PSFS_ERR_FATAL;
214 }
215 desired -= data->strm.avail_in; /* desired becomes what we consumed this round through */
216 data->strm.next_in = data->inbuf;
217 data->strm.avail_in = 0;
218 bin += desired;
219
220 if (data->strm.avail_out < data->outbuf_len) {
221 php_stream_bucket *out_bucket;
222 size_t bucketlen = data->outbuf_len - data->strm.avail_out;
223
224 out_bucket = php_stream_bucket_new(
225 stream, estrndup((char *) data->outbuf, bucketlen), bucketlen, 1, 0);
226 php_stream_bucket_append(buckets_out, out_bucket);
227 data->strm.avail_out = data->outbuf_len;
228 data->strm.next_out = data->outbuf;
229 exit_status = PSFS_PASS_ON;
230 }
231 }
232 consumed += bucket->buflen;
233 php_stream_bucket_delref(bucket);
234 }
235
236 if (flags & PSFS_FLAG_FLUSH_CLOSE || ((flags & PSFS_FLAG_FLUSH_INC) && !data->finished)) {
237 /* Spit it out! */
238 status = Z_OK;
239 while (status == Z_OK) {
240 status = deflate(&(data->strm), (flags & PSFS_FLAG_FLUSH_CLOSE ? Z_FINISH : Z_SYNC_FLUSH));
241 data->finished = 1;
242 if (data->strm.avail_out < data->outbuf_len) {
243 size_t bucketlen = data->outbuf_len - data->strm.avail_out;
244
245 bucket = php_stream_bucket_new(
246 stream, estrndup((char *) data->outbuf, bucketlen), bucketlen, 1, 0);
247 php_stream_bucket_append(buckets_out, bucket);
248 data->strm.avail_out = data->outbuf_len;
249 data->strm.next_out = data->outbuf;
250 exit_status = PSFS_PASS_ON;
251 }
252 }
253 }
254
255 if (bytes_consumed) {
256 *bytes_consumed = consumed;
257 }
258
259 return exit_status;
260 }
261
php_zlib_deflate_dtor(php_stream_filter * thisfilter)262 static void php_zlib_deflate_dtor(php_stream_filter *thisfilter)
263 {
264 if (thisfilter && Z_PTR(thisfilter->abstract)) {
265 php_zlib_filter_data *data = Z_PTR(thisfilter->abstract);
266 deflateEnd(&(data->strm));
267 pefree(data->inbuf, data->persistent);
268 pefree(data->outbuf, data->persistent);
269 pefree(data, data->persistent);
270 }
271 }
272
273 static const php_stream_filter_ops php_zlib_deflate_ops = {
274 php_zlib_deflate_filter,
275 php_zlib_deflate_dtor,
276 "zlib.deflate"
277 };
278
279 /* }}} */
280
281 /* {{{ zlib.* common factory */
282
php_zlib_filter_create(const char * filtername,zval * filterparams,uint8_t persistent)283 static php_stream_filter *php_zlib_filter_create(const char *filtername, zval *filterparams, uint8_t persistent)
284 {
285 const php_stream_filter_ops *fops = NULL;
286 php_zlib_filter_data *data;
287 int status;
288
289 /* Create this filter */
290 data = pecalloc(1, sizeof(php_zlib_filter_data), persistent);
291 if (!data) {
292 php_error_docref(NULL, E_WARNING, "Failed allocating %zd bytes", sizeof(php_zlib_filter_data));
293 return NULL;
294 }
295
296 /* Circular reference */
297 data->strm.opaque = (voidpf) data;
298
299 data->strm.zalloc = (alloc_func) php_zlib_alloc;
300 data->strm.zfree = (free_func) php_zlib_free;
301 data->strm.avail_out = data->outbuf_len = data->inbuf_len = 0x8000;
302 data->strm.next_in = data->inbuf = (Bytef *) pemalloc(data->inbuf_len, persistent);
303 if (!data->inbuf) {
304 php_error_docref(NULL, E_WARNING, "Failed allocating %zd bytes", data->inbuf_len);
305 pefree(data, persistent);
306 return NULL;
307 }
308 data->strm.avail_in = 0;
309 data->strm.next_out = data->outbuf = (Bytef *) pemalloc(data->outbuf_len, persistent);
310 if (!data->outbuf) {
311 php_error_docref(NULL, E_WARNING, "Failed allocating %zd bytes", data->outbuf_len);
312 pefree(data->inbuf, persistent);
313 pefree(data, persistent);
314 return NULL;
315 }
316
317 data->strm.data_type = Z_ASCII;
318
319 if (strcasecmp(filtername, "zlib.inflate") == 0) {
320 int windowBits = -MAX_WBITS;
321
322 if (filterparams) {
323 zval *tmpzval;
324
325 if ((Z_TYPE_P(filterparams) == IS_ARRAY || Z_TYPE_P(filterparams) == IS_OBJECT) &&
326 (tmpzval = zend_hash_str_find(HASH_OF(filterparams), "window", sizeof("window") - 1))) {
327 /* log-2 base of history window (9 - 15) */
328 zend_long tmp = zval_get_long(tmpzval);
329 if (tmp < -MAX_WBITS || tmp > MAX_WBITS + 32) {
330 php_error_docref(NULL, E_WARNING, "Invalid parameter given for window size (" ZEND_LONG_FMT ")", tmp);
331 } else {
332 windowBits = tmp;
333 }
334 }
335 }
336
337 /* RFC 1951 Inflate */
338 data->finished = '\0';
339 status = inflateInit2(&(data->strm), windowBits);
340 fops = &php_zlib_inflate_ops;
341 } else if (strcasecmp(filtername, "zlib.deflate") == 0) {
342 /* RFC 1951 Deflate */
343 int level = Z_DEFAULT_COMPRESSION;
344 int windowBits = -MAX_WBITS;
345 int memLevel = MAX_MEM_LEVEL;
346
347
348 if (filterparams) {
349 zval *tmpzval;
350 zend_long tmp;
351
352 /* filterparams can either be a scalar value to indicate compression level (shortcut method)
353 Or can be a hash containing one or more of 'window', 'memory', and/or 'level' members. */
354
355 switch (Z_TYPE_P(filterparams)) {
356 case IS_ARRAY:
357 case IS_OBJECT:
358 if ((tmpzval = zend_hash_str_find(HASH_OF(filterparams), "memory", sizeof("memory") -1))) {
359 /* Memory Level (1 - 9) */
360 tmp = zval_get_long(tmpzval);
361 if (tmp < 1 || tmp > MAX_MEM_LEVEL) {
362 php_error_docref(NULL, E_WARNING, "Invalid parameter given for memory level (" ZEND_LONG_FMT ")", tmp);
363 } else {
364 memLevel = tmp;
365 }
366 }
367
368 if ((tmpzval = zend_hash_str_find(HASH_OF(filterparams), "window", sizeof("window") - 1))) {
369 /* log-2 base of history window (9 - 15) */
370 tmp = zval_get_long(tmpzval);
371 if (tmp < -MAX_WBITS || tmp > MAX_WBITS + 16) {
372 php_error_docref(NULL, E_WARNING, "Invalid parameter given for window size (" ZEND_LONG_FMT ")", tmp);
373 } else {
374 windowBits = tmp;
375 }
376 }
377
378 if ((tmpzval = zend_hash_str_find(HASH_OF(filterparams), "level", sizeof("level") - 1))) {
379 tmp = zval_get_long(tmpzval);
380
381 /* Pseudo pass through to catch level validating code */
382 goto factory_setlevel;
383 }
384 break;
385 case IS_STRING:
386 case IS_DOUBLE:
387 case IS_LONG:
388 tmp = zval_get_long(filterparams);
389 factory_setlevel:
390 /* Set compression level within reason (-1 == default, 0 == none, 1-9 == least to most compression */
391 if (tmp < -1 || tmp > 9) {
392 php_error_docref(NULL, E_WARNING, "Invalid compression level specified. (" ZEND_LONG_FMT ")", tmp);
393 } else {
394 level = tmp;
395 }
396 break;
397 default:
398 php_error_docref(NULL, E_WARNING, "Invalid filter parameter, ignored");
399 }
400 }
401 status = deflateInit2(&(data->strm), level, Z_DEFLATED, windowBits, memLevel, 0);
402 data->finished = 1;
403 fops = &php_zlib_deflate_ops;
404 } else {
405 status = Z_DATA_ERROR;
406 }
407
408 if (status != Z_OK) {
409 /* Unspecified (probably strm) error, let stream-filter error do its own whining */
410 pefree(data->strm.next_in, persistent);
411 pefree(data->strm.next_out, persistent);
412 pefree(data, persistent);
413 return NULL;
414 }
415
416 return php_stream_filter_alloc(fops, data, persistent);
417 }
418
419 const php_stream_filter_factory php_zlib_filter_factory = {
420 php_zlib_filter_create
421 };
422 /* }}} */
423