xref: /PHP-7.3/ext/bz2/bz2_filter.c (revision 8d3f8ca1)
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