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