1 /*
2 +----------------------------------------------------------------------+
3 | Copyright (c) The PHP Group |
4 +----------------------------------------------------------------------+
5 | This source file is subject to version 3.01 of the PHP license, |
6 | that is bundled with this package in the file LICENSE, and is |
7 | available through the world-wide-web at the following url: |
8 | https://www.php.net/license/3_01.txt |
9 | If you did not receive a copy of the PHP license and are unable to |
10 | obtain it through the world-wide-web, please send a note to |
11 | license@php.net so we can mail you a copy immediately. |
12 +----------------------------------------------------------------------+
13 | Authors: Sara Golemon (pollita@php.net) |
14 +----------------------------------------------------------------------+
15 */
16
17 #ifdef HAVE_CONFIG_H
18 #include "config.h"
19 #endif
20
21 #include "php.h"
22 #include "php_bz2.h"
23
24 /* {{{ data structure */
25
26 enum strm_status {
27 PHP_BZ2_UNINITIALIZED,
28 PHP_BZ2_RUNNING,
29 PHP_BZ2_FINISHED
30 };
31
32 typedef struct _php_bz2_filter_data {
33 bz_stream strm;
34 char *inbuf;
35 char *outbuf;
36 size_t inbuf_len;
37 size_t outbuf_len;
38
39 enum strm_status status; /* Decompress option */
40 unsigned int small_footprint : 1; /* Decompress option */
41 unsigned int expect_concatenated : 1; /* Decompress option */
42 unsigned int is_flushed : 1; /* only for compression */
43
44 int persistent;
45 } php_bz2_filter_data;
46
47 /* }}} */
48
49 /* {{{ Memory management wrappers */
50
php_bz2_alloc(void * opaque,int items,int size)51 static void *php_bz2_alloc(void *opaque, int items, int size)
52 {
53 return (void *)safe_pemalloc(items, size, 0, ((php_bz2_filter_data*)opaque)->persistent);
54 }
55
php_bz2_free(void * opaque,void * address)56 static void php_bz2_free(void *opaque, void *address)
57 {
58 pefree((void *)address, ((php_bz2_filter_data*)opaque)->persistent);
59 }
60 /* }}} */
61
62 /* {{{ bzip2.decompress filter implementation */
63
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)64 static php_stream_filter_status_t php_bz2_decompress_filter(
65 php_stream *stream,
66 php_stream_filter *thisfilter,
67 php_stream_bucket_brigade *buckets_in,
68 php_stream_bucket_brigade *buckets_out,
69 size_t *bytes_consumed,
70 int flags
71 )
72 {
73 php_bz2_filter_data *data;
74 php_stream_bucket *bucket;
75 size_t consumed = 0;
76 int status;
77 php_stream_filter_status_t exit_status = PSFS_FEED_ME;
78 bz_stream *streamp;
79
80 if (!Z_PTR(thisfilter->abstract)) {
81 /* Should never happen */
82 return PSFS_ERR_FATAL;
83 }
84
85 data = (php_bz2_filter_data *)Z_PTR(thisfilter->abstract);
86 streamp = &(data->strm);
87
88 while (buckets_in->head) {
89 size_t bin = 0, desired;
90
91 bucket = php_stream_bucket_make_writeable(buckets_in->head);
92 while (bin < bucket->buflen) {
93 if (data->status == PHP_BZ2_UNINITIALIZED) {
94 status = BZ2_bzDecompressInit(streamp, 0, data->small_footprint);
95
96 if (BZ_OK != status) {
97 php_stream_bucket_delref(bucket);
98 return PSFS_ERR_FATAL;
99 }
100
101 data->status = PHP_BZ2_RUNNING;
102 }
103
104 if (data->status != PHP_BZ2_RUNNING) {
105 consumed += bucket->buflen;
106 break;
107 }
108
109 desired = bucket->buflen - bin;
110 if (desired > data->inbuf_len) {
111 desired = data->inbuf_len;
112 }
113 memcpy(data->strm.next_in, bucket->buf + bin, desired);
114 data->strm.avail_in = desired;
115
116 status = BZ2_bzDecompress(&(data->strm));
117
118 if (status == BZ_STREAM_END) {
119 BZ2_bzDecompressEnd(&(data->strm));
120 if (data->expect_concatenated) {
121 data->status = PHP_BZ2_UNINITIALIZED;
122 } else {
123 data->status = PHP_BZ2_FINISHED;
124 }
125 } else if (status != BZ_OK) {
126 /* Something bad happened */
127 php_error_docref(NULL, E_NOTICE, "bzip2 decompression failed");
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 int flush_mode;
232
233 desired = bucket->buflen - bin;
234 if (desired > data->inbuf_len) {
235 desired = data->inbuf_len;
236 }
237 memcpy(data->strm.next_in, bucket->buf + bin, desired);
238 data->strm.avail_in = desired;
239
240 flush_mode = flags & PSFS_FLAG_FLUSH_CLOSE ? BZ_FINISH : (flags & PSFS_FLAG_FLUSH_INC ? BZ_FLUSH : BZ_RUN);
241 data->is_flushed = flush_mode != BZ_RUN;
242 status = BZ2_bzCompress(&(data->strm), flush_mode);
243 if (status != BZ_RUN_OK && status != BZ_FLUSH_OK && status != BZ_FINISH_OK) {
244 /* Something bad happened */
245 php_stream_bucket_delref(bucket);
246 return PSFS_ERR_FATAL;
247 }
248 desired -= data->strm.avail_in; /* desired becomes what we consumed this round through */
249 data->strm.next_in = data->inbuf;
250 data->strm.avail_in = 0;
251 consumed += desired;
252 bin += desired;
253
254 if (data->strm.avail_out < data->outbuf_len) {
255 php_stream_bucket *out_bucket;
256 size_t bucketlen = data->outbuf_len - data->strm.avail_out;
257
258 out_bucket = php_stream_bucket_new(stream, estrndup(data->outbuf, bucketlen), bucketlen, 1, 0);
259 php_stream_bucket_append(buckets_out, out_bucket);
260 data->strm.avail_out = data->outbuf_len;
261 data->strm.next_out = data->outbuf;
262 exit_status = PSFS_PASS_ON;
263 }
264 }
265 php_stream_bucket_delref(bucket);
266 }
267
268 if (flags & PSFS_FLAG_FLUSH_CLOSE || ((flags & PSFS_FLAG_FLUSH_INC) && !data->is_flushed)) {
269 /* Spit it out! */
270 do {
271 status = BZ2_bzCompress(&(data->strm), (flags & PSFS_FLAG_FLUSH_CLOSE ? BZ_FINISH : BZ_FLUSH));
272 data->is_flushed = 1;
273 if (data->strm.avail_out < data->outbuf_len) {
274 size_t bucketlen = data->outbuf_len - data->strm.avail_out;
275
276 bucket = php_stream_bucket_new(stream, estrndup(data->outbuf, bucketlen), bucketlen, 1, 0);
277 php_stream_bucket_append(buckets_out, bucket);
278 data->strm.avail_out = data->outbuf_len;
279 data->strm.next_out = data->outbuf;
280 exit_status = PSFS_PASS_ON;
281 }
282 } while (status == (flags & PSFS_FLAG_FLUSH_CLOSE ? BZ_FINISH_OK : BZ_FLUSH_OK));
283 }
284
285 if (bytes_consumed) {
286 *bytes_consumed = consumed;
287 }
288 return exit_status;
289 }
290
php_bz2_compress_dtor(php_stream_filter * thisfilter)291 static void php_bz2_compress_dtor(php_stream_filter *thisfilter)
292 {
293 if (Z_PTR(thisfilter->abstract)) {
294 php_bz2_filter_data *data = Z_PTR(thisfilter->abstract);
295 BZ2_bzCompressEnd(&(data->strm));
296 pefree(data->inbuf, data->persistent);
297 pefree(data->outbuf, data->persistent);
298 pefree(data, data->persistent);
299 }
300 }
301
302 static const php_stream_filter_ops php_bz2_compress_ops = {
303 php_bz2_compress_filter,
304 php_bz2_compress_dtor,
305 "bzip2.compress"
306 };
307
308 /* }}} */
309
310 /* {{{ bzip2.* common factory */
311
php_bz2_filter_create(const char * filtername,zval * filterparams,uint8_t persistent)312 static php_stream_filter *php_bz2_filter_create(const char *filtername, zval *filterparams, uint8_t persistent)
313 {
314 const php_stream_filter_ops *fops = NULL;
315 php_bz2_filter_data *data;
316 int status = BZ_OK;
317
318 /* Create this filter */
319 data = pecalloc(1, sizeof(php_bz2_filter_data), persistent);
320
321 /* Circular reference */
322 data->strm.opaque = (void *) data;
323
324 data->strm.bzalloc = php_bz2_alloc;
325 data->strm.bzfree = php_bz2_free;
326 data->persistent = persistent;
327 data->strm.avail_out = data->outbuf_len = data->inbuf_len = 2048;
328 data->strm.next_in = data->inbuf = (char *) pemalloc(data->inbuf_len, persistent);
329 data->strm.avail_in = 0;
330 data->strm.next_out = data->outbuf = (char *) pemalloc(data->outbuf_len, persistent);
331
332 if (strcasecmp(filtername, "bzip2.decompress") == 0) {
333 data->small_footprint = 0;
334 data->expect_concatenated = 0;
335
336 if (filterparams) {
337 zval *tmpzval = NULL;
338
339 if (Z_TYPE_P(filterparams) == IS_ARRAY || Z_TYPE_P(filterparams) == IS_OBJECT) {
340 if ((tmpzval = zend_hash_str_find(HASH_OF(filterparams), "concatenated", sizeof("concatenated")-1))) {
341 data->expect_concatenated = zend_is_true(tmpzval);
342 tmpzval = NULL;
343 }
344
345 tmpzval = zend_hash_str_find(HASH_OF(filterparams), "small", sizeof("small")-1);
346 } else {
347 tmpzval = filterparams;
348 }
349
350 if (tmpzval) {
351 data->small_footprint = zend_is_true(tmpzval);
352 }
353 }
354
355 data->status = PHP_BZ2_UNINITIALIZED;
356 fops = &php_bz2_decompress_ops;
357 } else if (strcasecmp(filtername, "bzip2.compress") == 0) {
358 int blockSize100k = PHP_BZ2_FILTER_DEFAULT_BLOCKSIZE;
359 int workFactor = PHP_BZ2_FILTER_DEFAULT_WORKFACTOR;
360
361 if (filterparams) {
362 zval *tmpzval;
363
364 if (Z_TYPE_P(filterparams) == IS_ARRAY || Z_TYPE_P(filterparams) == IS_OBJECT) {
365 if ((tmpzval = zend_hash_str_find(HASH_OF(filterparams), "blocks", sizeof("blocks")-1))) {
366 /* How much memory to allocate (1 - 9) x 100kb */
367 zend_long blocks = zval_get_long(tmpzval);
368 if (blocks < 1 || blocks > 9) {
369 php_error_docref(NULL, E_WARNING, "Invalid parameter given for number of blocks to allocate (" ZEND_LONG_FMT ")", blocks);
370 } else {
371 blockSize100k = (int) blocks;
372 }
373 }
374
375 if ((tmpzval = zend_hash_str_find(HASH_OF(filterparams), "work", sizeof("work")-1))) {
376 /* Work Factor (0 - 250) */
377 zend_long work = zval_get_long(tmpzval);
378 if (work < 0 || work > 250) {
379 php_error_docref(NULL, E_WARNING, "Invalid parameter given for work factor (" ZEND_LONG_FMT ")", work);
380 } else {
381 workFactor = (int) work;
382 }
383 }
384 }
385 }
386
387 status = BZ2_bzCompressInit(&(data->strm), blockSize100k, 0, workFactor);
388 data->is_flushed = 1;
389 fops = &php_bz2_compress_ops;
390 } else {
391 status = BZ_DATA_ERROR;
392 }
393
394 if (status != BZ_OK) {
395 /* Unspecified (probably strm) error, let stream-filter error do its own whining */
396 pefree(data->strm.next_in, persistent);
397 pefree(data->strm.next_out, persistent);
398 pefree(data, persistent);
399 return NULL;
400 }
401
402 return php_stream_filter_alloc(fops, data, persistent);
403 }
404
405 const php_stream_filter_factory php_bz2_filter_factory = {
406 php_bz2_filter_create
407 };
408 /* }}} */
409