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