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