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 #ifdef HAVE_CONFIG_H
20 #include "config.h"
21 #endif
22
23 #include "php.h"
24 #include "php_bz2.h"
25
26 /* {{{ data structure */
27
28 enum strm_status {
29 PHP_BZ2_UNITIALIZED,
30 PHP_BZ2_RUNNING,
31 PHP_BZ2_FINISHED
32 };
33
34 typedef struct _php_bz2_filter_data {
35 bz_stream strm;
36 char *inbuf;
37 char *outbuf;
38 size_t inbuf_len;
39 size_t outbuf_len;
40
41 enum strm_status status; /* Decompress option */
42 unsigned int small_footprint : 1; /* Decompress option */
43 unsigned int expect_concatenated : 1; /* Decompress option */
44
45 int persistent;
46 } php_bz2_filter_data;
47
48 /* }}} */
49
50 /* {{{ Memory management wrappers */
51
php_bz2_alloc(void * opaque,int items,int size)52 static void *php_bz2_alloc(void *opaque, int items, int size)
53 {
54 return (void *)safe_pemalloc(items, size, 0, ((php_bz2_filter_data*)opaque)->persistent);
55 }
56
php_bz2_free(void * opaque,void * address)57 static void php_bz2_free(void *opaque, void *address)
58 {
59 pefree((void *)address, ((php_bz2_filter_data*)opaque)->persistent);
60 }
61 /* }}} */
62
63 /* {{{ bzip2.decompress filter implementation */
64
php_bz2_decompress_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)65 static php_stream_filter_status_t php_bz2_decompress_filter(
66 php_stream *stream,
67 php_stream_filter *thisfilter,
68 php_stream_bucket_brigade *buckets_in,
69 php_stream_bucket_brigade *buckets_out,
70 size_t *bytes_consumed,
71 int flags
72 )
73 {
74 php_bz2_filter_data *data;
75 php_stream_bucket *bucket;
76 size_t consumed = 0;
77 int status;
78 php_stream_filter_status_t exit_status = PSFS_FEED_ME;
79 bz_stream *streamp;
80
81 if (!Z_PTR(thisfilter->abstract)) {
82 /* Should never happen */
83 return PSFS_ERR_FATAL;
84 }
85
86 data = (php_bz2_filter_data *)Z_PTR(thisfilter->abstract);
87 streamp = &(data->strm);
88
89 while (buckets_in->head) {
90 size_t bin = 0, desired;
91
92 bucket = php_stream_bucket_make_writeable(buckets_in->head);
93 while (bin < bucket->buflen) {
94 if (data->status == PHP_BZ2_UNITIALIZED) {
95 status = BZ2_bzDecompressInit(streamp, 0, data->small_footprint);
96
97 if (BZ_OK != status) {
98 php_stream_bucket_delref(bucket);
99 return PSFS_ERR_FATAL;
100 }
101
102 data->status = PHP_BZ2_RUNNING;
103 }
104
105 if (data->status != PHP_BZ2_RUNNING) {
106 consumed += bucket->buflen;
107 break;
108 }
109
110 desired = bucket->buflen - bin;
111 if (desired > data->inbuf_len) {
112 desired = data->inbuf_len;
113 }
114 memcpy(data->strm.next_in, bucket->buf + bin, desired);
115 data->strm.avail_in = desired;
116
117 status = BZ2_bzDecompress(&(data->strm));
118
119 if (status == BZ_STREAM_END) {
120 BZ2_bzDecompressEnd(&(data->strm));
121 if (data->expect_concatenated) {
122 data->status = PHP_BZ2_UNITIALIZED;
123 } else {
124 data->status = PHP_BZ2_FINISHED;
125 }
126 } else if (status != BZ_OK) {
127 /* Something bad happened */
128 php_stream_bucket_delref(bucket);
129 return PSFS_ERR_FATAL;
130 }
131 desired -= data->strm.avail_in; /* desired becomes what we consumed this round through */
132 data->strm.next_in = data->inbuf;
133 data->strm.avail_in = 0;
134 consumed += desired;
135 bin += desired;
136
137 if (data->strm.avail_out < data->outbuf_len) {
138 php_stream_bucket *out_bucket;
139 size_t bucketlen = data->outbuf_len - data->strm.avail_out;
140 out_bucket = php_stream_bucket_new(stream, estrndup(data->outbuf, bucketlen), bucketlen, 1, 0);
141 php_stream_bucket_append(buckets_out, out_bucket);
142 data->strm.avail_out = data->outbuf_len;
143 data->strm.next_out = data->outbuf;
144 exit_status = PSFS_PASS_ON;
145 } else if (status == BZ_STREAM_END && data->strm.avail_out >= data->outbuf_len) {
146 /* no more data to decompress, and nothing was spat out */
147 php_stream_bucket_delref(bucket);
148 return PSFS_PASS_ON;
149 }
150 }
151
152 php_stream_bucket_delref(bucket);
153 }
154
155 if ((data->status == PHP_BZ2_RUNNING) && (flags & PSFS_FLAG_FLUSH_CLOSE)) {
156 /* Spit it out! */
157 status = BZ_OK;
158 while (status == BZ_OK) {
159 status = BZ2_bzDecompress(&(data->strm));
160 if (data->strm.avail_out < data->outbuf_len) {
161 size_t bucketlen = data->outbuf_len - data->strm.avail_out;
162
163 bucket = php_stream_bucket_new(stream, estrndup(data->outbuf, bucketlen), bucketlen, 1, 0);
164 php_stream_bucket_append(buckets_out, bucket);
165 data->strm.avail_out = data->outbuf_len;
166 data->strm.next_out = data->outbuf;
167 exit_status = PSFS_PASS_ON;
168 } else if (status == BZ_OK) {
169 break;
170 }
171 }
172 }
173
174 if (bytes_consumed) {
175 *bytes_consumed = consumed;
176 }
177
178 return exit_status;
179 }
180
php_bz2_decompress_dtor(php_stream_filter * thisfilter)181 static void php_bz2_decompress_dtor(php_stream_filter *thisfilter)
182 {
183 if (thisfilter && Z_PTR(thisfilter->abstract)) {
184 php_bz2_filter_data *data = Z_PTR(thisfilter->abstract);
185 if (data->status == PHP_BZ2_RUNNING) {
186 BZ2_bzDecompressEnd(&(data->strm));
187 }
188 pefree(data->inbuf, data->persistent);
189 pefree(data->outbuf, data->persistent);
190 pefree(data, data->persistent);
191 }
192 }
193
194 static const php_stream_filter_ops php_bz2_decompress_ops = {
195 php_bz2_decompress_filter,
196 php_bz2_decompress_dtor,
197 "bzip2.decompress"
198 };
199 /* }}} */
200
201 /* {{{ bzip2.compress filter implementation */
202
php_bz2_compress_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)203 static php_stream_filter_status_t php_bz2_compress_filter(
204 php_stream *stream,
205 php_stream_filter *thisfilter,
206 php_stream_bucket_brigade *buckets_in,
207 php_stream_bucket_brigade *buckets_out,
208 size_t *bytes_consumed,
209 int flags
210 )
211 {
212 php_bz2_filter_data *data;
213 php_stream_bucket *bucket;
214 size_t consumed = 0;
215 int status;
216 php_stream_filter_status_t exit_status = PSFS_FEED_ME;
217
218 if (!Z_PTR(thisfilter->abstract)) {
219 /* Should never happen */
220 return PSFS_ERR_FATAL;
221 }
222
223 data = (php_bz2_filter_data *)Z_PTR(thisfilter->abstract);
224
225 while (buckets_in->head) {
226 size_t bin = 0, desired;
227
228 bucket = php_stream_bucket_make_writeable(buckets_in->head);
229
230 while (bin < bucket->buflen) {
231 desired = bucket->buflen - bin;
232 if (desired > data->inbuf_len) {
233 desired = data->inbuf_len;
234 }
235 memcpy(data->strm.next_in, bucket->buf + bin, desired);
236 data->strm.avail_in = desired;
237
238 status = BZ2_bzCompress(&(data->strm), flags & PSFS_FLAG_FLUSH_CLOSE ? BZ_FINISH : (flags & PSFS_FLAG_FLUSH_INC ? BZ_FLUSH : BZ_RUN));
239 if (status != BZ_RUN_OK && status != BZ_FLUSH_OK && status != BZ_FINISH_OK) {
240 /* Something bad happened */
241 php_stream_bucket_delref(bucket);
242 return PSFS_ERR_FATAL;
243 }
244 desired -= data->strm.avail_in; /* desired becomes what we consumed this round through */
245 data->strm.next_in = data->inbuf;
246 data->strm.avail_in = 0;
247 consumed += desired;
248 bin += desired;
249
250 if (data->strm.avail_out < data->outbuf_len) {
251 php_stream_bucket *out_bucket;
252 size_t bucketlen = data->outbuf_len - data->strm.avail_out;
253
254 out_bucket = php_stream_bucket_new(stream, estrndup(data->outbuf, bucketlen), bucketlen, 1, 0);
255 php_stream_bucket_append(buckets_out, out_bucket);
256 data->strm.avail_out = data->outbuf_len;
257 data->strm.next_out = data->outbuf;
258 exit_status = PSFS_PASS_ON;
259 }
260 }
261 php_stream_bucket_delref(bucket);
262 }
263
264 if (flags & PSFS_FLAG_FLUSH_CLOSE) {
265 /* Spit it out! */
266 status = BZ_FINISH_OK;
267 while (status == BZ_FINISH_OK) {
268 status = BZ2_bzCompress(&(data->strm), BZ_FINISH);
269 if (data->strm.avail_out < data->outbuf_len) {
270 size_t bucketlen = data->outbuf_len - data->strm.avail_out;
271
272 bucket = php_stream_bucket_new(stream, estrndup(data->outbuf, bucketlen), bucketlen, 1, 0);
273 php_stream_bucket_append(buckets_out, bucket);
274 data->strm.avail_out = data->outbuf_len;
275 data->strm.next_out = data->outbuf;
276 exit_status = PSFS_PASS_ON;
277 }
278 }
279 }
280
281 if (bytes_consumed) {
282 *bytes_consumed = consumed;
283 }
284 return exit_status;
285 }
286
php_bz2_compress_dtor(php_stream_filter * thisfilter)287 static void php_bz2_compress_dtor(php_stream_filter *thisfilter)
288 {
289 if (Z_PTR(thisfilter->abstract)) {
290 php_bz2_filter_data *data = Z_PTR(thisfilter->abstract);
291 BZ2_bzCompressEnd(&(data->strm));
292 pefree(data->inbuf, data->persistent);
293 pefree(data->outbuf, data->persistent);
294 pefree(data, data->persistent);
295 }
296 }
297
298 static const php_stream_filter_ops php_bz2_compress_ops = {
299 php_bz2_compress_filter,
300 php_bz2_compress_dtor,
301 "bzip2.compress"
302 };
303
304 /* }}} */
305
306 /* {{{ bzip2.* common factory */
307
php_bz2_filter_create(const char * filtername,zval * filterparams,uint8_t persistent)308 static php_stream_filter *php_bz2_filter_create(const char *filtername, zval *filterparams, uint8_t persistent)
309 {
310 const php_stream_filter_ops *fops = NULL;
311 php_bz2_filter_data *data;
312 int status = BZ_OK;
313
314 /* Create this filter */
315 data = pecalloc(1, sizeof(php_bz2_filter_data), persistent);
316
317 /* Circular reference */
318 data->strm.opaque = (void *) data;
319
320 data->strm.bzalloc = php_bz2_alloc;
321 data->strm.bzfree = php_bz2_free;
322 data->persistent = persistent;
323 data->strm.avail_out = data->outbuf_len = data->inbuf_len = 2048;
324 data->strm.next_in = data->inbuf = (char *) pemalloc(data->inbuf_len, persistent);
325 data->strm.avail_in = 0;
326 data->strm.next_out = data->outbuf = (char *) pemalloc(data->outbuf_len, persistent);
327
328 if (strcasecmp(filtername, "bzip2.decompress") == 0) {
329 data->small_footprint = 0;
330 data->expect_concatenated = 0;
331
332 if (filterparams) {
333 zval *tmpzval = NULL;
334
335 if (Z_TYPE_P(filterparams) == IS_ARRAY || Z_TYPE_P(filterparams) == IS_OBJECT) {
336 if ((tmpzval = zend_hash_str_find(HASH_OF(filterparams), "concatenated", sizeof("concatenated")-1))) {
337 data->expect_concatenated = zend_is_true(tmpzval);
338 tmpzval = NULL;
339 }
340
341 tmpzval = zend_hash_str_find(HASH_OF(filterparams), "small", sizeof("small")-1);
342 } else {
343 tmpzval = filterparams;
344 }
345
346 if (tmpzval) {
347 data->small_footprint = zend_is_true(tmpzval);
348 }
349 }
350
351 data->status = PHP_BZ2_UNITIALIZED;
352 fops = &php_bz2_decompress_ops;
353 } else if (strcasecmp(filtername, "bzip2.compress") == 0) {
354 int blockSize100k = PHP_BZ2_FILTER_DEFAULT_BLOCKSIZE;
355 int workFactor = PHP_BZ2_FILTER_DEFAULT_WORKFACTOR;
356
357 if (filterparams) {
358 zval *tmpzval;
359
360 if (Z_TYPE_P(filterparams) == IS_ARRAY || Z_TYPE_P(filterparams) == IS_OBJECT) {
361 if ((tmpzval = zend_hash_str_find(HASH_OF(filterparams), "blocks", sizeof("blocks")-1))) {
362 /* How much memory to allocate (1 - 9) x 100kb */
363 zend_long blocks = zval_get_long(tmpzval);
364 if (blocks < 1 || blocks > 9) {
365 php_error_docref(NULL, E_WARNING, "Invalid parameter given for number of blocks to allocate. (" ZEND_LONG_FMT ")", blocks);
366 } else {
367 blockSize100k = (int) blocks;
368 }
369 }
370
371 if ((tmpzval = zend_hash_str_find(HASH_OF(filterparams), "work", sizeof("work")-1))) {
372 /* Work Factor (0 - 250) */
373 zend_long work = zval_get_long(tmpzval);
374 if (work < 0 || work > 250) {
375 php_error_docref(NULL, E_WARNING, "Invalid parameter given for work factor. (" ZEND_LONG_FMT ")", work);
376 } else {
377 workFactor = (int) work;
378 }
379 }
380 }
381 }
382
383 status = BZ2_bzCompressInit(&(data->strm), blockSize100k, 0, workFactor);
384 fops = &php_bz2_compress_ops;
385 } else {
386 status = BZ_DATA_ERROR;
387 }
388
389 if (status != BZ_OK) {
390 /* Unspecified (probably strm) error, let stream-filter error do its own whining */
391 pefree(data->strm.next_in, persistent);
392 pefree(data->strm.next_out, persistent);
393 pefree(data, persistent);
394 return NULL;
395 }
396
397 return php_stream_filter_alloc(fops, data, persistent);
398 }
399
400 const php_stream_filter_factory php_bz2_filter_factory = {
401 php_bz2_filter_create
402 };
403 /* }}} */
404
405 /*
406 * Local variables:
407 * tab-width: 4
408 * c-basic-offset: 4
409 * End:
410 * vim600: sw=4 ts=4 fdm=marker
411 * vim<600: sw=4 ts=4
412 */
413