1 /*
2 +----------------------------------------------------------------------+
3 | PHP Version 7 |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 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; /* for zlib.deflate: signals that no flush is pending */
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 && status != Z_BUF_ERROR) {
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 int flush_mode;
200
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 flush_mode = flags & PSFS_FLAG_FLUSH_CLOSE ? Z_FULL_FLUSH : (flags & PSFS_FLAG_FLUSH_INC ? Z_SYNC_FLUSH : Z_NO_FLUSH);
209 data->finished = flush_mode != Z_NO_FLUSH;
210 status = deflate(&(data->strm), flush_mode);
211 if (status != Z_OK) {
212 /* Something bad happened */
213 php_stream_bucket_delref(bucket);
214 return PSFS_ERR_FATAL;
215 }
216 desired -= data->strm.avail_in; /* desired becomes what we consumed this round through */
217 data->strm.next_in = data->inbuf;
218 data->strm.avail_in = 0;
219 bin += desired;
220
221 if (data->strm.avail_out < data->outbuf_len) {
222 php_stream_bucket *out_bucket;
223 size_t bucketlen = data->outbuf_len - data->strm.avail_out;
224
225 out_bucket = php_stream_bucket_new(
226 stream, estrndup((char *) data->outbuf, bucketlen), bucketlen, 1, 0);
227 php_stream_bucket_append(buckets_out, out_bucket);
228 data->strm.avail_out = data->outbuf_len;
229 data->strm.next_out = data->outbuf;
230 exit_status = PSFS_PASS_ON;
231 }
232 }
233 consumed += bucket->buflen;
234 php_stream_bucket_delref(bucket);
235 }
236
237 if (flags & PSFS_FLAG_FLUSH_CLOSE || ((flags & PSFS_FLAG_FLUSH_INC) && !data->finished)) {
238 /* Spit it out! */
239 status = Z_OK;
240 while (status == Z_OK) {
241 status = deflate(&(data->strm), (flags & PSFS_FLAG_FLUSH_CLOSE ? Z_FINISH : Z_SYNC_FLUSH));
242 data->finished = 1;
243 if (data->strm.avail_out < data->outbuf_len) {
244 size_t bucketlen = data->outbuf_len - data->strm.avail_out;
245
246 bucket = php_stream_bucket_new(
247 stream, estrndup((char *) data->outbuf, bucketlen), bucketlen, 1, 0);
248 php_stream_bucket_append(buckets_out, bucket);
249 data->strm.avail_out = data->outbuf_len;
250 data->strm.next_out = data->outbuf;
251 exit_status = PSFS_PASS_ON;
252 }
253 }
254 }
255
256 if (bytes_consumed) {
257 *bytes_consumed = consumed;
258 }
259
260 return exit_status;
261 }
262
php_zlib_deflate_dtor(php_stream_filter * thisfilter)263 static void php_zlib_deflate_dtor(php_stream_filter *thisfilter)
264 {
265 if (thisfilter && Z_PTR(thisfilter->abstract)) {
266 php_zlib_filter_data *data = Z_PTR(thisfilter->abstract);
267 deflateEnd(&(data->strm));
268 pefree(data->inbuf, data->persistent);
269 pefree(data->outbuf, data->persistent);
270 pefree(data, data->persistent);
271 }
272 }
273
274 static const php_stream_filter_ops php_zlib_deflate_ops = {
275 php_zlib_deflate_filter,
276 php_zlib_deflate_dtor,
277 "zlib.deflate"
278 };
279
280 /* }}} */
281
282 /* {{{ zlib.* common factory */
283
php_zlib_filter_create(const char * filtername,zval * filterparams,uint8_t persistent)284 static php_stream_filter *php_zlib_filter_create(const char *filtername, zval *filterparams, uint8_t persistent)
285 {
286 const php_stream_filter_ops *fops = NULL;
287 php_zlib_filter_data *data;
288 int status;
289
290 /* Create this filter */
291 data = pecalloc(1, sizeof(php_zlib_filter_data), persistent);
292 if (!data) {
293 php_error_docref(NULL, E_WARNING, "Failed allocating %zd bytes", sizeof(php_zlib_filter_data));
294 return NULL;
295 }
296
297 /* Circular reference */
298 data->strm.opaque = (voidpf) data;
299
300 data->strm.zalloc = (alloc_func) php_zlib_alloc;
301 data->strm.zfree = (free_func) php_zlib_free;
302 data->strm.avail_out = data->outbuf_len = data->inbuf_len = 0x8000;
303 data->strm.next_in = data->inbuf = (Bytef *) pemalloc(data->inbuf_len, persistent);
304 if (!data->inbuf) {
305 php_error_docref(NULL, E_WARNING, "Failed allocating %zd bytes", data->inbuf_len);
306 pefree(data, persistent);
307 return NULL;
308 }
309 data->strm.avail_in = 0;
310 data->strm.next_out = data->outbuf = (Bytef *) pemalloc(data->outbuf_len, persistent);
311 if (!data->outbuf) {
312 php_error_docref(NULL, E_WARNING, "Failed allocating %zd bytes", data->outbuf_len);
313 pefree(data->inbuf, persistent);
314 pefree(data, persistent);
315 return NULL;
316 }
317
318 data->strm.data_type = Z_ASCII;
319
320 if (strcasecmp(filtername, "zlib.inflate") == 0) {
321 int windowBits = -MAX_WBITS;
322
323 if (filterparams) {
324 zval *tmpzval;
325
326 if ((Z_TYPE_P(filterparams) == IS_ARRAY || Z_TYPE_P(filterparams) == IS_OBJECT) &&
327 (tmpzval = zend_hash_str_find(HASH_OF(filterparams), "window", sizeof("window") - 1))) {
328 /* log-2 base of history window (9 - 15) */
329 zend_long tmp = zval_get_long(tmpzval);
330 if (tmp < -MAX_WBITS || tmp > MAX_WBITS + 32) {
331 php_error_docref(NULL, E_WARNING, "Invalid parameter give for window size. (" ZEND_LONG_FMT ")", tmp);
332 } else {
333 windowBits = tmp;
334 }
335 }
336 }
337
338 /* RFC 1951 Inflate */
339 data->finished = '\0';
340 status = inflateInit2(&(data->strm), windowBits);
341 fops = &php_zlib_inflate_ops;
342 } else if (strcasecmp(filtername, "zlib.deflate") == 0) {
343 /* RFC 1951 Deflate */
344 int level = Z_DEFAULT_COMPRESSION;
345 int windowBits = -MAX_WBITS;
346 int memLevel = MAX_MEM_LEVEL;
347
348
349 if (filterparams) {
350 zval *tmpzval;
351 zend_long tmp;
352
353 /* filterparams can either be a scalar value to indicate compression level (shortcut method)
354 Or can be a hash containing one or more of 'window', 'memory', and/or 'level' members. */
355
356 switch (Z_TYPE_P(filterparams)) {
357 case IS_ARRAY:
358 case IS_OBJECT:
359 if ((tmpzval = zend_hash_str_find(HASH_OF(filterparams), "memory", sizeof("memory") -1))) {
360 /* Memory Level (1 - 9) */
361 tmp = zval_get_long(tmpzval);
362 if (tmp < 1 || tmp > MAX_MEM_LEVEL) {
363 php_error_docref(NULL, E_WARNING, "Invalid parameter give for memory level. (" ZEND_LONG_FMT ")", tmp);
364 } else {
365 memLevel = tmp;
366 }
367 }
368
369 if ((tmpzval = zend_hash_str_find(HASH_OF(filterparams), "window", sizeof("window") - 1))) {
370 /* log-2 base of history window (9 - 15) */
371 tmp = zval_get_long(tmpzval);
372 if (tmp < -MAX_WBITS || tmp > MAX_WBITS + 16) {
373 php_error_docref(NULL, E_WARNING, "Invalid parameter give for window size. (" ZEND_LONG_FMT ")", tmp);
374 } else {
375 windowBits = tmp;
376 }
377 }
378
379 if ((tmpzval = zend_hash_str_find(HASH_OF(filterparams), "level", sizeof("level") - 1))) {
380 tmp = zval_get_long(tmpzval);
381
382 /* Pseudo pass through to catch level validating code */
383 goto factory_setlevel;
384 }
385 break;
386 case IS_STRING:
387 case IS_DOUBLE:
388 case IS_LONG:
389 tmp = zval_get_long(filterparams);
390 factory_setlevel:
391 /* Set compression level within reason (-1 == default, 0 == none, 1-9 == least to most compression */
392 if (tmp < -1 || tmp > 9) {
393 php_error_docref(NULL, E_WARNING, "Invalid compression level specified. (" ZEND_LONG_FMT ")", tmp);
394 } else {
395 level = tmp;
396 }
397 break;
398 default:
399 php_error_docref(NULL, E_WARNING, "Invalid filter parameter, ignored");
400 }
401 }
402 status = deflateInit2(&(data->strm), level, Z_DEFLATED, windowBits, memLevel, 0);
403 data->finished = 1;
404 fops = &php_zlib_deflate_ops;
405 } else {
406 status = Z_DATA_ERROR;
407 }
408
409 if (status != Z_OK) {
410 /* Unspecified (probably strm) error, let stream-filter error do its own whining */
411 pefree(data->strm.next_in, persistent);
412 pefree(data->strm.next_out, persistent);
413 pefree(data, persistent);
414 return NULL;
415 }
416
417 return php_stream_filter_alloc(fops, data, persistent);
418 }
419
420 const php_stream_filter_factory php_zlib_filter_factory = {
421 php_zlib_filter_create
422 };
423 /* }}} */
424