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