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