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