1 #ifdef HAVE_CONFIG_H
2 # include "config.h"
3 #endif
4
5 #include <php.h>
6 #include <SAPI.h>
7 #include <php_ini.h>
8 #include <ext/standard/info.h>
9 #if ZEND_MODULE_API_NO >= 20141001
10 #include <ext/standard/php_smart_string.h>
11 #else
12 #include <ext/standard/php_smart_str.h>
13 #endif
14 #if PHP_MAJOR_VERSION >= 7 && defined(HAVE_APCU_SUPPORT)
15 #include <ext/standard/php_var.h>
16 #include <ext/apcu/apc_serializer.h>
17 #include <zend_smart_str.h>
18 #endif
19 #include "php_brotli.h"
20
21 #ifndef TSRMLS_C
22 #define TSRMLS_C
23 #endif
24 #ifndef TSRMLS_CC
25 #define TSRMLS_CC
26 #endif
27 #ifndef TSRMLS_DC
28 #define TSRMLS_DC
29 #endif
30
31 #if PHP_VERSION_ID >= 70000
32 int le_state;
33 #endif
34
35 # pragma GCC diagnostic ignored "-Wpointer-sign"
36
37 ZEND_DECLARE_MODULE_GLOBALS(brotli);
38
39 static ZEND_FUNCTION(brotli_compress);
40 static ZEND_FUNCTION(brotli_uncompress);
41 #if PHP_VERSION_ID >= 70000
42 static ZEND_FUNCTION(brotli_compress_init);
43 static ZEND_FUNCTION(brotli_compress_add);
44 static ZEND_FUNCTION(brotli_uncompress_init);
45 static ZEND_FUNCTION(brotli_uncompress_add);
46 #endif
47
48 ZEND_BEGIN_ARG_INFO_EX(arginfo_brotli_compress, 0, 0, 1)
49 ZEND_ARG_INFO(0, data)
50 ZEND_ARG_INFO(0, quality)
51 ZEND_ARG_INFO(0, mode)
52 ZEND_END_ARG_INFO()
53
54 ZEND_BEGIN_ARG_INFO_EX(arginfo_brotli_uncompress, 0, 0, 1)
55 ZEND_ARG_INFO(0, data)
56 ZEND_ARG_INFO(0, max)
57 ZEND_END_ARG_INFO()
58
59 #if PHP_VERSION_ID >= 70000
60 ZEND_BEGIN_ARG_INFO_EX(arginfo_brotli_compress_init, 0, 0, 0)
61 ZEND_ARG_INFO(0, quality)
62 ZEND_ARG_INFO(0, mode)
63 ZEND_END_ARG_INFO()
64
65 ZEND_BEGIN_ARG_INFO_EX(arginfo_brotli_compress_add, 0, 0, 2)
66 ZEND_ARG_INFO(0, context)
67 ZEND_ARG_INFO(0, data)
68 ZEND_ARG_INFO(0, mode)
69 ZEND_END_ARG_INFO()
70
71 ZEND_BEGIN_ARG_INFO_EX(arginfo_brotli_uncompress_init, 0, 0, 0)
72 ZEND_END_ARG_INFO()
73
74 ZEND_BEGIN_ARG_INFO_EX(arginfo_brotli_uncompress_add, 0, 0, 2)
75 ZEND_ARG_INFO(0, context)
76 ZEND_ARG_INFO(0, data)
77 ZEND_ARG_INFO(0, mode)
78 ZEND_END_ARG_INFO()
79 #endif
80
81 #if PHP_MAJOR_VERSION >= 7 && defined(HAVE_APCU_SUPPORT)
82 static int APC_SERIALIZER_NAME(brotli)(APC_SERIALIZER_ARGS);
83 static int APC_UNSERIALIZER_NAME(brotli)(APC_UNSERIALIZER_ARGS);
84 #endif
85
86 static zend_function_entry brotli_functions[] = {
87 ZEND_FE(brotli_compress, arginfo_brotli_compress)
88 ZEND_FE(brotli_uncompress, arginfo_brotli_uncompress)
89 #if PHP_VERSION_ID > 50300 // PHP 5.3+
90 ZEND_NS_FALIAS(BROTLI_NS, compress,
91 brotli_compress, arginfo_brotli_compress)
92 ZEND_NS_FALIAS(BROTLI_NS, uncompress,
93 brotli_uncompress, arginfo_brotli_uncompress)
94 #endif
95 #if PHP_VERSION_ID >= 70000
96 ZEND_FE(brotli_compress_init, arginfo_brotli_compress_init)
97 ZEND_FE(brotli_compress_add, arginfo_brotli_compress_add)
98 ZEND_FE(brotli_uncompress_init, arginfo_brotli_uncompress_init)
99 ZEND_FE(brotli_uncompress_add, arginfo_brotli_uncompress_add)
100 ZEND_NS_FALIAS(BROTLI_NS, compress_init,
101 brotli_compress_init, arginfo_brotli_compress_init)
102 ZEND_NS_FALIAS(BROTLI_NS, compress_add,
103 brotli_compress_add, arginfo_brotli_compress_add)
104 ZEND_NS_FALIAS(BROTLI_NS, uncompress_init,
105 brotli_uncompress_init, arginfo_brotli_uncompress_init)
106 ZEND_NS_FALIAS(BROTLI_NS, uncompress_add,
107 brotli_uncompress_add, arginfo_brotli_uncompress_add)
108 #endif
109 ZEND_FE_END
110 };
111
112 static const size_t PHP_BROTLI_BUFFER_SIZE = 1 << 19;
113
php_brotli_encoder_create(BrotliEncoderState ** encoder,long quality,int lgwin,long mode)114 static int php_brotli_encoder_create(BrotliEncoderState **encoder,
115 long quality, int lgwin, long mode)
116 {
117 #if PHP_MAJOR_VERSION < 7
118 TSRMLS_FETCH();
119 #endif
120
121 *encoder = BrotliEncoderCreateInstance(NULL, NULL, NULL);
122 if (!*encoder) {
123 return FAILURE;
124 }
125
126 if (quality < BROTLI_MIN_QUALITY || quality > BROTLI_MAX_QUALITY) {
127 php_error_docref(NULL TSRMLS_CC, E_WARNING,
128 "brotli: compression level (%ld) "
129 "must be within %d..%d",
130 (long)quality, BROTLI_MIN_QUALITY, BROTLI_MAX_QUALITY);
131 quality = BROTLI_DEFAULT_QUALITY;
132 }
133 if (lgwin == 0) {
134 lgwin = BROTLI_DEFAULT_WINDOW;
135 }
136 if (mode != BROTLI_MODE_GENERIC &&
137 mode != BROTLI_MODE_TEXT &&
138 mode != BROTLI_MODE_FONT) {
139 php_error_docref(NULL TSRMLS_CC, E_WARNING,
140 "brotli: compression mode (%ld) must be %d, %d, %d",
141 (long)mode, BROTLI_MODE_GENERIC, BROTLI_MODE_TEXT,
142 BROTLI_MODE_FONT);
143 mode = BROTLI_MODE_GENERIC;
144 }
145
146 BrotliEncoderSetParameter(*encoder, BROTLI_PARAM_QUALITY, quality);
147 BrotliEncoderSetParameter(*encoder, BROTLI_PARAM_LGWIN, lgwin);
148 BrotliEncoderSetParameter(*encoder, BROTLI_PARAM_MODE, mode);
149
150 return SUCCESS;
151 }
152
php_brotli_decoder_create(BrotliDecoderState ** decoder)153 static int php_brotli_decoder_create(BrotliDecoderState **decoder)
154 {
155 *decoder = BrotliDecoderCreateInstance(NULL, NULL, NULL);
156 if (!*decoder) {
157 return FAILURE;
158 }
159
160 return SUCCESS;
161 }
162
163 #if PHP_VERSION_ID >= 70000
php_brotli_state_init(void)164 static php_brotli_state_context* php_brotli_state_init(void)
165 {
166 php_brotli_state_context *ctx
167 = (php_brotli_state_context *)ecalloc(1,
168 sizeof(php_brotli_state_context));
169 ctx->encoder = NULL;
170 ctx->decoder = NULL;
171 return ctx;
172 }
173
php_brotli_state_destroy(php_brotli_state_context * ctx)174 static void php_brotli_state_destroy(php_brotli_state_context *ctx)
175 {
176 if (ctx->encoder) {
177 BrotliEncoderDestroyInstance(ctx->encoder);
178 ctx->encoder = NULL;
179 }
180 if (ctx->decoder) {
181 BrotliDecoderDestroyInstance(ctx->decoder);
182 ctx->decoder = NULL;
183 }
184 efree(ctx);
185 }
186
php_brotli_state_rsrc_dtor(zend_resource * res)187 static void php_brotli_state_rsrc_dtor(zend_resource *res)
188 {
189 php_brotli_state_context *ctx = zend_fetch_resource(res, NULL, le_state);
190 php_brotli_state_destroy(ctx);
191 }
192 #endif
193
194 #if PHP_VERSION_ID > 50400 // Output Handler: 5.4+
195
196 #define PHP_BROTLI_OUTPUT_HANDLER "ob_brotli_handler"
197
php_brotli_output_encoding(void)198 static int php_brotli_output_encoding(void)
199 {
200 #if PHP_MAJOR_VERSION >= 7
201 #if defined(COMPILE_DL_BROTLI) && defined(ZTS)
202 ZEND_TSRMLS_CACHE_UPDATE();
203 #endif
204 zval *enc;
205 #else
206 zval **enc;
207 TSRMLS_FETCH();
208 #endif
209
210 if (!BROTLI_G(compression_coding)) {
211 #if PHP_MAJOR_VERSION >= 7
212 if ((Z_TYPE(PG(http_globals)[TRACK_VARS_SERVER]) == IS_ARRAY
213 || zend_is_auto_global_str(ZEND_STRL("_SERVER")))
214 && (enc = zend_hash_str_find(
215 Z_ARRVAL(PG(http_globals)[TRACK_VARS_SERVER]),
216 "HTTP_ACCEPT_ENCODING",
217 sizeof("HTTP_ACCEPT_ENCODING") - 1))) {
218 convert_to_string(enc);
219 if (strstr(Z_STRVAL_P(enc), "br")) {
220 BROTLI_G(compression_coding) = 1;
221 }
222 }
223 #else
224 if ((PG(http_globals)[TRACK_VARS_SERVER]
225 || zend_is_auto_global(ZEND_STRL("_SERVER") TSRMLS_CC)) &&
226 SUCCESS == zend_hash_find(Z_ARRVAL_P(PG(http_globals)[TRACK_VARS_SERVER]), "HTTP_ACCEPT_ENCODING", sizeof("HTTP_ACCEPT_ENCODING"), (void *) &enc)) {
227 convert_to_string(*enc);
228 if (strstr(Z_STRVAL_PP(enc), "br")) {
229 BROTLI_G(compression_coding) = 1;
230 }
231 }
232 #endif
233 }
234
235 return BROTLI_G(compression_coding);
236 }
237
php_brotli_context_close(php_brotli_context * ctx)238 static void php_brotli_context_close(php_brotli_context *ctx)
239 {
240 if (ctx->state.encoder) {
241 BrotliEncoderDestroyInstance(ctx->state.encoder);
242 ctx->state.encoder = NULL;
243 }
244 if (ctx->output) {
245 efree(ctx->output);
246 ctx->output = NULL;
247 }
248
249 ctx->available_in = 0;
250 ctx->next_in = NULL;
251 ctx->available_out = 0;
252 ctx->next_out = NULL;
253 }
254
php_brotli_output_handler(void ** handler_context,php_output_context * output_context)255 static int php_brotli_output_handler(void **handler_context,
256 php_output_context *output_context)
257 {
258 long quality = BROTLI_DEFAULT_QUALITY;
259 php_brotli_context *ctx = *(php_brotli_context **)handler_context;
260 #if PHP_MAJOR_VERSION < 7
261 TSRMLS_FETCH();
262 #endif
263
264 if (!php_brotli_output_encoding()) {
265 if ((output_context->op & PHP_OUTPUT_HANDLER_START)
266 && (output_context->op
267 != (PHP_OUTPUT_HANDLER_START
268 |PHP_OUTPUT_HANDLER_CLEAN|PHP_OUTPUT_HANDLER_FINAL))) {
269 sapi_add_header_ex(ZEND_STRL("Vary: Accept-Encoding"),
270 1, 0 TSRMLS_CC);
271 }
272 return FAILURE;
273 }
274
275 if (!BROTLI_G(output_compression) || !BROTLI_G(ob_handler)) {
276 return FAILURE;
277 }
278
279 #if PHP_VERSION_ID > 50400
280 quality = BROTLI_G(output_compression_level);
281 #endif
282 if (quality < BROTLI_MIN_QUALITY || quality > BROTLI_MAX_QUALITY) {
283 quality = BROTLI_DEFAULT_QUALITY;
284 }
285
286 if (output_context->op & PHP_OUTPUT_HANDLER_START) {
287 if (php_brotli_encoder_create(&ctx->state.encoder,
288 quality, 0, 0) != SUCCESS) {
289 return FAILURE;
290 }
291 }
292
293 if (!(output_context->op & PHP_OUTPUT_HANDLER_CLEAN)) {
294 if (SG(headers_sent)) {
295 php_brotli_context_close(ctx);
296 return FAILURE;
297 }
298
299 if (output_context->in.used) {
300 size_t size
301 = BrotliEncoderMaxCompressedSize(output_context->in.used);
302 if (!ctx->output) {
303 ctx->output = (uint8_t *)emalloc(size);
304 ctx->available_out = size;
305 } else {
306 ctx->available_out += size;
307 ctx->output = (uint8_t *)erealloc(ctx->output,
308 ctx->available_out);
309 }
310 if (!ctx->output) {
311 php_brotli_context_close(ctx);
312 return FAILURE;
313 }
314 ctx->next_out = ctx->output;
315
316 // append input
317 ctx->available_in = output_context->in.used;
318 ctx->next_in = (uint8_t *)output_context->in.data;
319 } else {
320 ctx->available_in = 0;
321 ctx->next_in = NULL;
322 }
323
324 if (!BrotliEncoderCompressStream(ctx->state.encoder,
325 (output_context->op
326 & PHP_OUTPUT_HANDLER_FINAL)
327 ? BROTLI_OPERATION_FINISH
328 : BROTLI_OPERATION_PROCESS,
329 &ctx->available_in,
330 &ctx->next_in,
331 &ctx->available_out,
332 &ctx->next_out,
333 NULL)) {
334 php_brotli_context_close(ctx);
335 return FAILURE;
336 }
337
338 if (output_context->op & PHP_OUTPUT_HANDLER_FINAL) {
339 size_t size = (size_t)(ctx->next_out - ctx->output);
340
341 uint8_t *data = (uint8_t *)emalloc(size);
342 memcpy(data, ctx->output, size);
343
344 output_context->out.data = data;
345 output_context->out.used = size;
346 output_context->out.free = 1;
347
348 php_brotli_context_close(ctx);
349
350 sapi_add_header_ex(ZEND_STRL("Content-Encoding: br"),
351 1, 1 TSRMLS_CC);
352 sapi_add_header_ex(ZEND_STRL("Vary: Accept-Encoding"),
353 1, 0 TSRMLS_CC);
354 }
355 } else {
356 php_brotli_context_close(ctx);
357
358 if (output_context->op & PHP_OUTPUT_HANDLER_FINAL) {
359 // discard
360 return SUCCESS;
361 } else {
362 // restart
363 if (php_brotli_encoder_create(&ctx->state.encoder,
364 quality, 0, 0) != SUCCESS) {
365 return FAILURE;
366 }
367 }
368 }
369
370 return SUCCESS;
371 }
372
php_brotli_output_handler_context_init(void)373 static php_brotli_context *php_brotli_output_handler_context_init(void)
374 {
375 php_brotli_context *ctx
376 = (php_brotli_context *)ecalloc(1, sizeof(php_brotli_context));
377 ctx->state.encoder = NULL;
378 ctx->state.decoder = NULL;
379 ctx->available_in = 0;
380 ctx->next_in = NULL;
381 ctx->available_out = 0;
382 ctx->next_out = NULL;
383 ctx->output = NULL;
384 return ctx;
385 }
386
php_brotli_output_handler_context_dtor(void * opaq TSRMLS_DC)387 static void php_brotli_output_handler_context_dtor(void *opaq TSRMLS_DC)
388 {
389 php_brotli_context *ctx = (php_brotli_context *)opaq;
390
391 if (ctx) {
392 php_brotli_context_close(ctx);
393 efree(ctx);
394 ctx = NULL;
395 }
396
397 BROTLI_G(handler_registered) = 0;
398 BROTLI_G(ob_handler) = NULL;
399 }
400
401 static php_output_handler*
php_brotli_output_handler_init(const char * handler_name,size_t handler_name_len,size_t chunk_size,int flags)402 php_brotli_output_handler_init(const char *handler_name,
403 size_t handler_name_len,
404 size_t chunk_size, int flags)
405 {
406 php_output_handler *handler = NULL;
407 #if PHP_MAJOR_VERSION < 7
408 TSRMLS_FETCH();
409 #endif
410
411 handler = php_output_handler_create_internal(handler_name, handler_name_len,
412 php_brotli_output_handler,
413 chunk_size, flags TSRMLS_CC);
414 if (!handler) {
415 return NULL;
416 }
417
418 BROTLI_G(handler_registered) = 1;
419
420 if (!BROTLI_G(ob_handler)) {
421 BROTLI_G(ob_handler) = php_brotli_output_handler_context_init();
422 }
423
424 php_output_handler_set_context(handler,
425 BROTLI_G(ob_handler),
426 php_brotli_output_handler_context_dtor
427 TSRMLS_CC);
428
429 BROTLI_G(output_compression) = 1;
430
431 return handler;
432 }
433
php_brotli_cleanup_ob_handler_mess(void)434 static void php_brotli_cleanup_ob_handler_mess(void)
435 {
436 #if PHP_MAJOR_VERSION < 7
437 TSRMLS_FETCH();
438 #endif
439
440 if (BROTLI_G(ob_handler)) {
441 php_brotli_output_handler_context_dtor(
442 (void *) BROTLI_G(ob_handler) TSRMLS_CC
443 );
444 BROTLI_G(ob_handler) = NULL;
445 }
446 }
447
php_brotli_output_compression_start(void)448 static void php_brotli_output_compression_start(void)
449 {
450 php_output_handler *h;
451 #if PHP_MAJOR_VERSION < 7
452 TSRMLS_FETCH();
453 #endif
454
455 switch (BROTLI_G(output_compression)) {
456 case 0:
457 break;
458 case 1:
459 /* break omitted intentionally */
460 default:
461 if (php_brotli_output_encoding() &&
462 (h = php_brotli_output_handler_init(
463 ZEND_STRL(PHP_BROTLI_OUTPUT_HANDLER),
464 PHP_OUTPUT_HANDLER_DEFAULT_SIZE,
465 PHP_OUTPUT_HANDLER_STDFLAGS))) {
466 php_output_handler_start(h TSRMLS_CC);
467 }
468 break;
469 }
470 }
471
PHP_INI_MH(OnUpdate_brotli_output_compression)472 static PHP_INI_MH(OnUpdate_brotli_output_compression)
473 {
474 int int_value, status;
475 zend_long *p;
476 #ifndef ZTS
477 char *base = (char *)mh_arg2;
478 #else
479 char *base;
480 base = (char *)ts_resource(*((int *) mh_arg2));
481 #endif
482
483 if (new_value == NULL) {
484 return FAILURE;
485 }
486
487 #if PHP_MAJOR_VERSION >= 7
488 if (!strncasecmp(ZSTR_VAL(new_value), "off", sizeof("off"))) {
489 int_value = 0;
490 } else if (!strncasecmp(ZSTR_VAL(new_value), "on", sizeof("on"))) {
491 int_value = 1;
492 } else if (zend_atoi(ZSTR_VAL(new_value), ZSTR_LEN(new_value))) {
493 int_value = 1;
494 } else {
495 int_value = 0;
496 }
497 #else
498 if (!strncasecmp(new_value, "off", sizeof("off"))) {
499 int_value = 0;
500 } else if (!strncasecmp(new_value, "on", sizeof("on"))) {
501 int_value = 1;
502 } else if (zend_atoi(new_value, new_value_length)) {
503 int_value = 1;
504 } else {
505 int_value = 0;
506 }
507 #endif
508
509 if (stage == PHP_INI_STAGE_RUNTIME) {
510 status = php_output_get_status(TSRMLS_C);
511 if (status & PHP_OUTPUT_SENT) {
512 php_error_docref("ref.outcontrol" TSRMLS_CC, E_WARNING,
513 "Cannot change brotli.output_compression"
514 " - headers already sent");
515 return FAILURE;
516 }
517 }
518
519 p = (zend_long *)(base+(size_t) mh_arg1);
520 *p = int_value;
521
522 if (stage == PHP_INI_STAGE_RUNTIME && int_value) {
523 if (!php_output_handler_started(ZEND_STRL(PHP_BROTLI_OUTPUT_HANDLER)
524 TSRMLS_CC)) {
525 php_brotli_output_compression_start();
526 }
527 }
528
529 return SUCCESS;
530 }
531
php_brotli_output_conflict(const char * handler_name,size_t handler_name_len TSRMLS_DC)532 static int php_brotli_output_conflict(const char *handler_name, size_t handler_name_len TSRMLS_DC)
533 {
534 if (php_output_get_level(TSRMLS_C)) {
535 if (php_output_handler_conflict(handler_name, handler_name_len,
536 ZEND_STRL(PHP_BROTLI_OUTPUT_HANDLER)
537 TSRMLS_CC)
538 || php_output_handler_conflict(handler_name, handler_name_len,
539 ZEND_STRL("ob_gzhandler")
540 TSRMLS_CC)) {
541 return FAILURE;
542 }
543 }
544 return SUCCESS;
545 }
546
547 PHP_INI_BEGIN()
548 STD_PHP_INI_ENTRY("brotli.output_compression", "0",
549 PHP_INI_ALL, OnUpdate_brotli_output_compression,
550 output_compression, zend_brotli_globals, brotli_globals)
551 STD_PHP_INI_ENTRY("brotli.output_compression_level", "-1",
552 PHP_INI_ALL, OnUpdateLong, output_compression_level,
553 zend_brotli_globals, brotli_globals)
PHP_INI_END()554 PHP_INI_END()
555
556 static void php_brotli_init_globals(zend_brotli_globals *brotli_globals)
557 {
558 brotli_globals->handler_registered = 0;
559 brotli_globals->compression_coding = 0;
560 brotli_globals->ob_handler = NULL;
561 }
562 #endif
563
564 typedef struct _php_brotli_stream_data {
565 BrotliEncoderState *cctx;
566 BrotliDecoderState *dctx;
567 BrotliDecoderResult result;
568 size_t available_in;
569 const uint8_t *next_in;
570 size_t available_out;
571 uint8_t *next_out;
572 uint8_t *output;
573 php_stream *stream;
574 } php_brotli_stream_data;
575
576 #define STREAM_DATA_FROM_STREAM() \
577 php_brotli_stream_data *self = (php_brotli_stream_data *) stream->abstract
578
579 #define STREAM_NAME "compress.brotli"
580
php_brotli_decompress_close(php_stream * stream,int close_handle TSRMLS_DC)581 static int php_brotli_decompress_close(php_stream *stream,
582 int close_handle TSRMLS_DC)
583 {
584 STREAM_DATA_FROM_STREAM();
585
586 if (!self) {
587 return EOF;
588 }
589
590 if (close_handle) {
591 if (self->stream) {
592 php_stream_close(self->stream);
593 self->stream = NULL;
594 }
595 }
596
597 if (self->dctx) {
598 BrotliDecoderDestroyInstance(self->dctx);
599 self->dctx = NULL;
600 }
601 if (self->output) {
602 efree(self->output);
603 }
604 efree(self);
605
606 stream->abstract = NULL;
607
608 return EOF;
609 }
610
611 #if PHP_VERSION_ID < 70400
php_brotli_decompress_read(php_stream * stream,char * buf,size_t count TSRMLS_DC)612 static size_t php_brotli_decompress_read(php_stream *stream,
613 char *buf,
614 size_t count TSRMLS_DC)
615 {
616 size_t ret = 0;
617 #else
618 static ssize_t php_brotli_decompress_read(php_stream *stream,
619 char *buf,
620 size_t count TSRMLS_DC)
621 {
622 ssize_t ret = 0;
623 #endif
624 STREAM_DATA_FROM_STREAM();
625
626 /* input */
627 uint8_t *input = (uint8_t *)emalloc(PHP_BROTLI_BUFFER_SIZE);
628 if (self->result == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT) {
629 if (php_stream_eof(self->stream)) {
630 /* corrupt input */
631 if (input) {
632 efree(input);
633 }
634 #if PHP_VERSION_ID < 70400
635 return 0;
636 #else
637 return -1;
638 #endif
639 }
640 self->available_in = php_stream_read(self->stream, input,
641 PHP_BROTLI_BUFFER_SIZE );
642 self->next_in = input;
643 }
644
645 /* output */
646 uint8_t *output = (uint8_t *)emalloc(count);
647 self->available_out = count;
648 self->next_out = output;
649
650 while (1) {
651 self->result = BrotliDecoderDecompressStream(self->dctx,
652 &self->available_in,
653 &self->next_in,
654 &self->available_out,
655 &self->next_out,
656 0);
657 if (self->result == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT
658 || self->result == BROTLI_DECODER_RESULT_SUCCESS) {
659 size_t out_size = (size_t)(self->next_out - output);
660 if (out_size) {
661 memcpy(buf, output, out_size);
662 ret += out_size;
663 }
664 break;
665 } else if (self->result == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT) {
666 /* more input */
667 if (php_stream_eof(self->stream)) {
668 /* corrupt input */
669 break;
670 }
671 self->available_in = php_stream_read(self->stream, input, count);
672 self->next_in = input;
673 } else {
674 /* decoder error */
675 break;
676 }
677 }
678
679 if (input) {
680 efree(input);
681 }
682 if (output) {
683 efree(output);
684 }
685
686 return ret;
687 }
688
689 static int php_brotli_compress_close(php_stream *stream,
690 int close_handle TSRMLS_DC)
691 {
692 STREAM_DATA_FROM_STREAM();
693
694 if (!self) {
695 return EOF;
696 }
697
698 const uint8_t *next_in = NULL;
699 size_t available_in = 0;
700
701 uint8_t *output = (uint8_t *)emalloc(PHP_BROTLI_BUFFER_SIZE);
702
703 while (!BrotliEncoderIsFinished(self->cctx)) {
704 uint8_t *next_out = output;
705 size_t available_out = PHP_BROTLI_BUFFER_SIZE;
706 if (BrotliEncoderCompressStream(self->cctx,
707 BROTLI_OPERATION_FINISH,
708 &available_in,
709 &next_in,
710 &available_out,
711 &next_out,
712 0)) {
713 size_t out_size = (size_t)(next_out - output);
714 if (out_size) {
715 php_stream_write(self->stream, output, out_size);
716 }
717 } else {
718 php_error_docref(NULL TSRMLS_CC, E_WARNING,
719 "brotli compress error\n");
720 }
721 }
722
723 efree(output);
724
725 if (close_handle) {
726 if (self->stream) {
727 php_stream_close(self->stream);
728 self->stream = NULL;
729 }
730 }
731
732 if (self->cctx) {
733 BrotliEncoderDestroyInstance(self->cctx);
734 self->cctx = NULL;
735 }
736 if (self->output) {
737 efree(self->output);
738 }
739 efree(self);
740 stream->abstract = NULL;
741
742 return EOF;
743 }
744
745 #if PHP_VERSION_ID < 70400
746 static size_t php_brotli_compress_write(php_stream *stream,
747 const char *buf,
748 size_t count TSRMLS_DC)
749 {
750 size_t ret = 0;
751 #else
752 static ssize_t php_brotli_compress_write(php_stream *stream,
753 const char *buf,
754 size_t count TSRMLS_DC)
755 {
756 ssize_t ret = 0;
757 #endif
758 STREAM_DATA_FROM_STREAM();
759
760 size_t available_in = count;
761 const uint8_t *next_in = (uint8_t *)buf;
762
763 uint8_t *output = (uint8_t *)emalloc(PHP_BROTLI_BUFFER_SIZE);
764
765 while (available_in) {
766 size_t available_out = PHP_BROTLI_BUFFER_SIZE;
767 uint8_t *next_out = output;
768
769 if (BrotliEncoderCompressStream(self->cctx,
770 BROTLI_OPERATION_PROCESS,
771 &available_in,
772 &next_in,
773 &available_out,
774 &next_out,
775 0)) {
776 size_t out_size = (size_t)(next_out - output);
777 if (out_size) {
778 php_stream_write(self->stream, output, out_size);
779 }
780 } else {
781 php_error_docref(NULL TSRMLS_CC, E_WARNING, "brotli compress error\n");
782 #if PHP_VERSION_ID >= 70400
783 return -1;
784 #endif
785 }
786 }
787
788 ret += count;
789
790 efree(output);
791
792 return ret;
793 }
794
795 static php_stream_ops php_stream_brotli_read_ops = {
796 NULL, /* write */
797 php_brotli_decompress_read,
798 php_brotli_decompress_close,
799 NULL, /* flush */
800 STREAM_NAME,
801 NULL, /* seek */
802 NULL, /* cast */
803 NULL, /* stat */
804 NULL /* set_option */
805 };
806
807 static php_stream_ops php_stream_brotli_write_ops = {
808 php_brotli_compress_write,
809 NULL, /* read */
810 php_brotli_compress_close,
811 NULL, /* flush */
812 STREAM_NAME,
813 NULL, /* seek */
814 NULL, /* cast */
815 NULL, /* stat */
816 NULL /* set_option */
817 };
818
819 static php_stream *
820 php_stream_brotli_opener(
821 php_stream_wrapper *wrapper,
822 #if PHP_VERSION_ID < 50600
823 char *path,
824 char *mode,
825 #else
826 const char *path,
827 const char *mode,
828 #endif
829 int options,
830 #if PHP_MAJOR_VERSION < 7
831 char **opened_path,
832 #else
833 zend_string **opened_path,
834 #endif
835 php_stream_context *context
836 STREAMS_DC TSRMLS_DC)
837 {
838 php_brotli_stream_data *self;
839 int level = BROTLI_DEFAULT_QUALITY;
840 int compress;
841
842 if (strncasecmp(STREAM_NAME, path, sizeof(STREAM_NAME)-1) == 0) {
843 path += sizeof(STREAM_NAME)-1;
844 if (strncmp("://", path, 3) == 0) {
845 path += 3;
846 }
847 }
848
849 if (php_check_open_basedir(path TSRMLS_CC)) {
850 return NULL;
851 }
852
853 if (!strcmp(mode, "w") || !strcmp(mode, "wb")) {
854 compress = 1;
855 } else if (!strcmp(mode, "r") || !strcmp(mode, "rb")) {
856 compress = 0;
857 } else {
858 php_error_docref(NULL TSRMLS_CC, E_ERROR, "brotli: invalid open mode");
859 return NULL;
860 }
861
862 if (context) {
863 #if PHP_MAJOR_VERSION >= 7
864 zval *tmpzval;
865
866 if (NULL != (tmpzval = php_stream_context_get_option(
867 context, "brotli", "level"))) {
868 level = zval_get_long(tmpzval);
869 }
870 #else
871 zval **tmpzval;
872
873 if (php_stream_context_get_option(
874 context, "brotli", "level", &tmpzval) == SUCCESS) {
875 convert_to_long_ex(tmpzval);
876 level = Z_LVAL_PP(tmpzval);
877 }
878 #endif
879 }
880 if (level > BROTLI_MAX_QUALITY) {
881 php_error_docref(NULL TSRMLS_CC, E_WARNING,
882 "brotli: compression level (%d) must be less than %d",
883 level, BROTLI_MAX_QUALITY);
884 level = BROTLI_MAX_QUALITY;
885 }
886
887 self = ecalloc(sizeof(*self), 1);
888 self->stream = php_stream_open_wrapper(path, mode,
889 options | REPORT_ERRORS, NULL);
890 if (!self->stream) {
891 efree(self);
892 return NULL;
893 }
894
895 /* File */
896 if (compress) {
897 self->dctx = NULL;
898 if (php_brotli_encoder_create(&self->cctx, level, 0, 0) != SUCCESS) {
899 php_error_docref(NULL TSRMLS_CC, E_WARNING,
900 "brotli: compression context failed");
901 php_stream_close(self->stream);
902 efree(self);
903 return NULL;
904 }
905 self->available_in = 0;
906 self->next_in = NULL;
907 self->available_out = 0;
908 self->next_out = NULL;
909 self->output = NULL;
910
911 return php_stream_alloc(&php_stream_brotli_write_ops, self, NULL, mode);
912
913 } else {
914 self->cctx = NULL;
915 if (php_brotli_decoder_create(&self->dctx) != SUCCESS) {
916 php_error_docref(NULL TSRMLS_CC, E_WARNING,
917 "brotli: decompression context failed");
918 php_stream_close(self->stream);
919 efree(self);
920 return NULL;
921 }
922
923 self->result = BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT;
924
925 self->available_in = 0;
926 self->next_in = NULL;
927 self->available_out = 0;
928 self->next_out = NULL;
929 self->output = NULL;
930
931 return php_stream_alloc(&php_stream_brotli_read_ops, self, NULL, mode);
932 }
933 return NULL;
934 }
935
936 static php_stream_wrapper_ops brotli_stream_wops = {
937 php_stream_brotli_opener,
938 NULL, /* close */
939 NULL, /* fstat */
940 NULL, /* stat */
941 NULL, /* opendir */
942 STREAM_NAME,
943 NULL, /* unlink */
944 NULL, /* rename */
945 NULL, /* mkdir */
946 NULL /* rmdir */
947 #if PHP_VERSION_ID >= 50400
948 , NULL
949 #endif
950 };
951
952 php_stream_wrapper php_stream_brotli_wrapper = {
953 &brotli_stream_wops,
954 NULL,
955 0 /* is_url */
956 };
957
958 ZEND_MINIT_FUNCTION(brotli)
959 {
960 #if PHP_VERSION_ID > 50400
961 ZEND_INIT_MODULE_GLOBALS(brotli, php_brotli_init_globals, NULL);
962 #endif
963
964 REGISTER_LONG_CONSTANT("BROTLI_GENERIC", BROTLI_MODE_GENERIC,
965 CONST_CS | CONST_PERSISTENT);
966 REGISTER_LONG_CONSTANT("BROTLI_TEXT", BROTLI_MODE_TEXT,
967 CONST_CS | CONST_PERSISTENT);
968 REGISTER_LONG_CONSTANT("BROTLI_FONT", BROTLI_MODE_FONT,
969 CONST_CS | CONST_PERSISTENT);
970
971 REGISTER_LONG_CONSTANT("BROTLI_COMPRESS_LEVEL_MIN", BROTLI_MIN_QUALITY,
972 CONST_CS | CONST_PERSISTENT);
973 REGISTER_LONG_CONSTANT("BROTLI_COMPRESS_LEVEL_MAX", BROTLI_MAX_QUALITY,
974 CONST_CS | CONST_PERSISTENT);
975 REGISTER_LONG_CONSTANT("BROTLI_COMPRESS_LEVEL_DEFAULT",
976 BROTLI_DEFAULT_QUALITY,
977 CONST_CS | CONST_PERSISTENT);
978
979 #if PHP_VERSION_ID >= 70000
980 REGISTER_LONG_CONSTANT("BROTLI_PROCESS", BROTLI_OPERATION_PROCESS,
981 CONST_CS | CONST_PERSISTENT);
982 REGISTER_LONG_CONSTANT("BROTLI_FINISH", BROTLI_OPERATION_FINISH,
983 CONST_CS | CONST_PERSISTENT);
984
985 le_state = zend_register_list_destructors_ex(php_brotli_state_rsrc_dtor,
986 NULL, "brotli.state",
987 module_number);
988 #endif
989
990 #if PHP_VERSION_ID > 50400
991 php_output_handler_alias_register(ZEND_STRL(PHP_BROTLI_OUTPUT_HANDLER),
992 php_brotli_output_handler_init TSRMLS_CC);
993 php_output_handler_conflict_register(ZEND_STRL(PHP_BROTLI_OUTPUT_HANDLER),
994 php_brotli_output_conflict TSRMLS_CC);
995
996 REGISTER_INI_ENTRIES();
997 #endif
998
999 php_register_url_stream_wrapper(STREAM_NAME,
1000 &php_stream_brotli_wrapper TSRMLS_CC);
1001
1002 #if PHP_MAJOR_VERSION >= 7 && defined(HAVE_APCU_SUPPORT)
1003 apc_register_serializer("brotli",
1004 APC_SERIALIZER_NAME(brotli),
1005 APC_UNSERIALIZER_NAME(brotli),
1006 NULL);
1007 #endif
1008
1009 return SUCCESS;
1010 }
1011
1012 ZEND_MSHUTDOWN_FUNCTION(brotli)
1013 {
1014 #if PHP_VERSION_ID > 50400
1015 UNREGISTER_INI_ENTRIES();
1016 #endif
1017 return SUCCESS;
1018 }
1019
1020 ZEND_RINIT_FUNCTION(brotli)
1021 {
1022 #if PHP_VERSION_ID > 50400
1023 BROTLI_G(compression_coding) = 0;
1024 if (!BROTLI_G(handler_registered)) {
1025 php_brotli_output_compression_start();
1026 }
1027 #endif
1028 return SUCCESS;
1029 }
1030
1031 ZEND_RSHUTDOWN_FUNCTION(brotli)
1032 {
1033 #if PHP_VERSION_ID > 50400
1034 if (BROTLI_G(handler_registered)) {
1035 php_brotli_cleanup_ob_handler_mess();
1036 }
1037 BROTLI_G(handler_registered) = 0;
1038 #endif
1039 return SUCCESS;
1040 }
1041
1042 ZEND_MINFO_FUNCTION(brotli)
1043 {
1044 php_info_print_table_start();
1045 php_info_print_table_row(2, "Brotli support", "enabled");
1046 php_info_print_table_row(2, "Extension Version", BROTLI_EXT_VERSION);
1047 #ifdef BROTLI_LIB_VERSION
1048 php_info_print_table_row(2, "Library Version", BROTLI_LIB_VERSION);
1049 #else
1050 uint32_t version = BrotliEncoderVersion();
1051 char buffer[64];
1052 snprintf(buffer, sizeof(buffer), "%d.%d.%d",
1053 version >> 24, (version >> 12) & 0xfff, version & 0xfff);
1054 php_info_print_table_row(2, "Library Version", buffer);
1055 #endif
1056 #if PHP_MAJOR_VERSION >= 7 && defined(HAVE_APCU_SUPPORT)
1057 php_info_print_table_row(2, "APCu serializer ABI", APC_SERIALIZER_ABI);
1058 #endif
1059 php_info_print_table_end();
1060 }
1061
1062 #if PHP_MAJOR_VERSION >= 7 && defined(HAVE_APCU_SUPPORT)
1063 static const zend_module_dep brotli_module_deps[] = {
1064 ZEND_MOD_OPTIONAL("apcu")
1065 ZEND_MOD_END
1066 };
1067 #endif
1068
1069 zend_module_entry brotli_module_entry = {
1070 #if PHP_MAJOR_VERSION >= 7 && defined(HAVE_APCU_SUPPORT)
1071 STANDARD_MODULE_HEADER_EX,
1072 NULL,
1073 brotli_module_deps,
1074 #elif ZEND_MODULE_API_NO >= 20010901
1075 STANDARD_MODULE_HEADER,
1076 #endif
1077 "brotli",
1078 brotli_functions,
1079 ZEND_MINIT(brotli),
1080 ZEND_MSHUTDOWN(brotli),
1081 ZEND_RINIT(brotli),
1082 ZEND_RSHUTDOWN(brotli),
1083 ZEND_MINFO(brotli),
1084 #if ZEND_MODULE_API_NO >= 20010901
1085 BROTLI_EXT_VERSION,
1086 #endif
1087 STANDARD_MODULE_PROPERTIES
1088 };
1089
1090 #ifdef COMPILE_DL_BROTLI
1091 #if PHP_MAJOR_VERSION >= 7 && defined(ZTS)
1092 ZEND_TSRMLS_CACHE_DEFINE()
1093 #endif
1094 ZEND_GET_MODULE(brotli)
1095 #endif
1096
1097 static ZEND_FUNCTION(brotli_compress)
1098 {
1099 char *in;
1100 #if ZEND_MODULE_API_NO >= 20141001
1101 size_t in_size;
1102 #else
1103 int in_size;
1104 #endif
1105 long quality = BROTLI_DEFAULT_QUALITY;
1106 long mode = BROTLI_MODE_GENERIC;
1107
1108 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
1109 "s|ll", &in, &in_size,
1110 &quality, &mode) == FAILURE) {
1111 RETURN_FALSE;
1112 }
1113
1114 size_t out_size = BrotliEncoderMaxCompressedSize(in_size);
1115 char *out = (char *)emalloc(out_size);
1116 if (!out) {
1117 php_error_docref(NULL TSRMLS_CC, E_WARNING,
1118 "Brotli compress memory allocate failed\n");
1119 RETURN_FALSE;
1120 }
1121
1122 if (mode != BROTLI_MODE_GENERIC &&
1123 mode != BROTLI_MODE_TEXT &&
1124 mode != BROTLI_MODE_FONT) {
1125 mode = BROTLI_MODE_GENERIC;
1126 }
1127
1128 if (quality < BROTLI_MIN_QUALITY || quality > BROTLI_MAX_QUALITY) {
1129 quality = BROTLI_DEFAULT_QUALITY;
1130 }
1131
1132 int lgwin = BROTLI_DEFAULT_WINDOW;
1133
1134 if (!BrotliEncoderCompress((int)quality, lgwin, (BrotliEncoderMode)mode,
1135 in_size, (const uint8_t*)in,
1136 &out_size, (uint8_t*)out)) {
1137 php_error_docref(NULL TSRMLS_CC, E_WARNING,
1138 "Brotli compress failed\n");
1139 efree(out);
1140 RETURN_FALSE;
1141 }
1142
1143 #if ZEND_MODULE_API_NO >= 20141001
1144 RETVAL_STRINGL(out, out_size);
1145 #else
1146 RETVAL_STRINGL(out, out_size, 1);
1147 #endif
1148 efree(out);
1149 }
1150
1151 #if PHP_VERSION_ID >= 70000
1152 static ZEND_FUNCTION(brotli_compress_init)
1153 {
1154 long quality = BROTLI_DEFAULT_QUALITY;
1155 long mode = BROTLI_MODE_GENERIC;
1156 php_brotli_state_context *ctx;
1157
1158 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
1159 "|ll", &quality, &mode) == FAILURE) {
1160 RETURN_FALSE;
1161 }
1162
1163 ctx = php_brotli_state_init();
1164
1165 if (php_brotli_encoder_create(&ctx->encoder,
1166 quality, 0, mode) != SUCCESS) {
1167 php_error_docref(NULL TSRMLS_CC, E_WARNING,
1168 "Brotli incremental compress init failed\n");
1169 RETURN_FALSE;
1170 }
1171
1172 RETURN_RES(zend_register_resource(ctx, le_state));
1173 }
1174
1175 static ZEND_FUNCTION(brotli_compress_add)
1176 {
1177 zval *res;
1178 php_brotli_state_context *ctx;
1179 size_t buffer_size, buffer_used;
1180 zend_long mode = BROTLI_OPERATION_PROCESS;
1181 char *in_buf;
1182 size_t in_size;
1183 smart_string out = {0};
1184
1185 if (zend_parse_parameters(ZEND_NUM_ARGS(), "rs|l",
1186 &res, &in_buf, &in_size, &mode) != SUCCESS) {
1187 RETURN_FALSE;
1188 }
1189
1190 ctx = zend_fetch_resource(Z_RES_P(res), NULL, le_state);
1191 if (ctx == NULL || ctx->encoder == NULL) {
1192 php_error_docref(NULL TSRMLS_CC, E_WARNING,
1193 "Brotli incremental compress resource failed\n");
1194 RETURN_FALSE;
1195 }
1196
1197 buffer_size = BrotliEncoderMaxCompressedSize(in_size);
1198 buffer_size = (buffer_size < 64) ? 64 : buffer_size;
1199 uint8_t *buffer = (uint8_t *)emalloc(buffer_size);
1200 if (!buffer) {
1201 php_error_docref(NULL TSRMLS_CC, E_WARNING,
1202 "Brotli incremental compress buffer failed\n");
1203 RETURN_FALSE;
1204 }
1205
1206 const uint8_t *next_in = in_buf;
1207 size_t available_in = in_size;
1208
1209 if (in_size > 0) {
1210 while (available_in) {
1211 size_t available_out = buffer_size;
1212 uint8_t *next_out = buffer;
1213 if (BrotliEncoderCompressStream(ctx->encoder,
1214 mode,
1215 &available_in,
1216 &next_in,
1217 &available_out,
1218 &next_out,
1219 0)) {
1220 buffer_used = (size_t)(next_out - buffer);
1221 if (buffer_used) {
1222 smart_string_appendl(&out, buffer, buffer_used);
1223 }
1224 } else {
1225 efree(buffer);
1226 smart_string_free(&out);
1227 php_error_docref(NULL TSRMLS_CC, E_WARNING,
1228 "Brotli incremental compress failed\n");
1229 RETURN_FALSE;
1230 }
1231 }
1232 }
1233
1234 if (mode == BROTLI_OPERATION_FINISH) {
1235 while (!BrotliEncoderIsFinished(ctx->encoder)) {
1236 size_t available_out = buffer_size;
1237 uint8_t *next_out = buffer;
1238 if (BrotliEncoderCompressStream(ctx->encoder,
1239 BROTLI_OPERATION_FINISH,
1240 &available_in,
1241 &next_in,
1242 &available_out,
1243 &next_out,
1244 0)) {
1245 buffer_used = (size_t)(next_out - buffer);
1246 if (buffer_used) {
1247 smart_string_appendl(&out, buffer, buffer_used);
1248 }
1249 } else {
1250 efree(buffer);
1251 smart_string_free(&out);
1252 php_error_docref(NULL TSRMLS_CC, E_WARNING,
1253 "Brotli incremental compress failed\n");
1254 RETURN_FALSE;
1255 }
1256 }
1257 }
1258
1259 RETVAL_STRINGL(out.c, out.len);
1260
1261 efree(buffer);
1262 smart_string_free(&out);
1263 }
1264 #endif
1265
1266 static ZEND_FUNCTION(brotli_uncompress)
1267 {
1268 long max_size = 0;
1269 char *in;
1270 #if ZEND_MODULE_API_NO >= 20141001
1271 size_t in_size;
1272 smart_string out = {0};
1273 #else
1274 int in_size;
1275 smart_str out = {0};
1276 #endif
1277
1278 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l",
1279 &in, &in_size, &max_size) == FAILURE) {
1280 RETURN_FALSE;
1281 }
1282
1283 if (max_size && max_size < in_size) {
1284 in_size = max_size;
1285 }
1286
1287 BrotliDecoderState *state = BrotliDecoderCreateInstance(NULL, NULL, NULL);
1288 if (!state) {
1289 php_error_docref(NULL TSRMLS_CC, E_WARNING,
1290 "Invalid Brotli state\n");
1291 RETURN_FALSE;
1292 }
1293
1294 size_t available_in = in_size;
1295 const uint8_t *next_in = (const uint8_t *)in;
1296 size_t buffer_size = PHP_BROTLI_BUFFER_SIZE;
1297 uint8_t *buffer = (uint8_t *)emalloc(buffer_size);
1298
1299 BrotliDecoderResult result = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT;
1300 while (result == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) {
1301 size_t available_out = buffer_size;
1302 uint8_t *next_out = buffer;
1303 result = BrotliDecoderDecompressStream(state, &available_in, &next_in,
1304 &available_out, &next_out,
1305 0);
1306 size_t used_out = buffer_size - available_out;
1307 if (used_out != 0) {
1308 #if ZEND_MODULE_API_NO >= 20141001
1309 smart_string_appendl(&out, buffer, used_out);
1310 #else
1311 smart_str_appendl(&out, buffer, used_out);
1312 #endif
1313 }
1314 }
1315
1316 BrotliDecoderDestroyInstance(state);
1317 efree(buffer);
1318
1319 if (result != BROTLI_DECODER_RESULT_SUCCESS) {
1320 php_error_docref(NULL TSRMLS_CC, E_WARNING,
1321 "Brotli decompress failed\n");
1322 #if ZEND_MODULE_API_NO >= 20141001
1323 smart_string_free(&out);
1324 #else
1325 smart_str_free(&out);
1326 #endif
1327 RETURN_FALSE;
1328 }
1329
1330 #if ZEND_MODULE_API_NO >= 20141001
1331 RETVAL_STRINGL(out.c, out.len);
1332 #else
1333 RETVAL_STRINGL(out.c, out.len, 1);
1334 #endif
1335
1336 #if ZEND_MODULE_API_NO >= 20141001
1337 smart_string_free(&out);
1338 #else
1339 smart_str_free(&out);
1340 #endif
1341 }
1342
1343 #if PHP_VERSION_ID >= 70000
1344 static ZEND_FUNCTION(brotli_uncompress_init)
1345 {
1346 php_brotli_state_context *ctx;
1347
1348 if (zend_parse_parameters_none() == FAILURE) {
1349 RETURN_FALSE;
1350 }
1351
1352 ctx = php_brotli_state_init();
1353
1354 if (php_brotli_decoder_create(&ctx->decoder) != SUCCESS) {
1355 php_error_docref(NULL TSRMLS_CC, E_WARNING,
1356 "Brotli incremental uncompress init failed\n");
1357 RETURN_FALSE;
1358 }
1359
1360 RETURN_RES(zend_register_resource(ctx, le_state));
1361 }
1362
1363 static ZEND_FUNCTION(brotli_uncompress_add)
1364 {
1365 zval *res;
1366 php_brotli_state_context *ctx;
1367 size_t buffer_size;
1368 zend_long mode = BROTLI_OPERATION_PROCESS;
1369 char *in_buf;
1370 size_t in_size;
1371 smart_string out = {0};
1372
1373 if (zend_parse_parameters(ZEND_NUM_ARGS(), "rs|l",
1374 &res, &in_buf, &in_size, &mode) != SUCCESS) {
1375 RETURN_FALSE;
1376 }
1377
1378 ctx = zend_fetch_resource(Z_RES_P(res), NULL, le_state);
1379 if (ctx == NULL || ctx->decoder == NULL) {
1380 php_error_docref(NULL TSRMLS_CC, E_WARNING,
1381 "Brotli incremental uncompress resource failed\n");
1382 RETURN_FALSE;
1383 }
1384
1385 if (in_size <= 0 && mode != BROTLI_OPERATION_FINISH) {
1386 RETURN_EMPTY_STRING();
1387 }
1388
1389 buffer_size = PHP_BROTLI_BUFFER_SIZE;
1390 uint8_t *buffer = (uint8_t *)emalloc(buffer_size);
1391 if (!buffer) {
1392 php_error_docref(NULL TSRMLS_CC, E_WARNING,
1393 "Brotli incremental uncompress buffer failed\n");
1394 RETURN_FALSE;
1395 }
1396
1397 const uint8_t *next_in = (const uint8_t *)in_buf;
1398 size_t available_in = in_size;
1399
1400 BrotliDecoderResult result = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT;
1401 while (result == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) {
1402 size_t available_out = buffer_size;
1403 uint8_t *next_out = buffer;
1404 result = BrotliDecoderDecompressStream(ctx->decoder,
1405 &available_in, &next_in,
1406 &available_out, &next_out,
1407 0);
1408 size_t buffer_used = buffer_size - available_out;
1409 if (buffer_used) {
1410 smart_string_appendl(&out, buffer, buffer_used);
1411 }
1412 }
1413
1414 RETVAL_STRINGL(out.c, out.len);
1415
1416 efree(buffer);
1417 smart_string_free(&out);
1418 }
1419 #endif
1420
1421 #if PHP_MAJOR_VERSION >= 7 && defined(HAVE_APCU_SUPPORT)
1422 static int APC_SERIALIZER_NAME(brotli)(APC_SERIALIZER_ARGS)
1423 {
1424 int result;
1425 int lgwin = BROTLI_DEFAULT_WINDOW, quality = BROTLI_DEFAULT_QUALITY;
1426 php_serialize_data_t var_hash;
1427 smart_str var = {0};
1428 BrotliEncoderMode mode = BROTLI_MODE_GENERIC;
1429
1430 PHP_VAR_SERIALIZE_INIT(var_hash);
1431 php_var_serialize(&var, (zval*) value, &var_hash);
1432 PHP_VAR_SERIALIZE_DESTROY(var_hash);
1433 if (var.s == NULL) {
1434 return 0;
1435 }
1436
1437 *buf_len = BrotliEncoderMaxCompressedSize(ZSTR_LEN(var.s));
1438 *buf = (char*) emalloc(*buf_len);
1439 if (*buf == NULL) {
1440 *buf_len = 0;
1441 return 0;
1442 }
1443
1444 if (!BrotliEncoderCompress(quality, lgwin, mode,
1445 ZSTR_LEN(var.s),
1446 (const uint8_t*) ZSTR_VAL(var.s),
1447 buf_len, (uint8_t*) *buf)) {
1448 efree(*buf);
1449 *buf = NULL;
1450 *buf_len = 0;
1451 result = 0;
1452 } else {
1453 result = 1;
1454 }
1455
1456 smart_str_free(&var);
1457
1458 return result;
1459 }
1460
1461 static int APC_UNSERIALIZER_NAME(brotli)(APC_UNSERIALIZER_ARGS)
1462 {
1463 const uint8_t *next_in = (const uint8_t*) buf;
1464 const unsigned char* tmp;
1465 int result;
1466 php_unserialize_data_t var_hash;
1467 size_t available_in = buf_len;
1468 size_t available_out, used_out;
1469 smart_str out = {NULL, 0};
1470 uint8_t *var, *next_out;
1471 BrotliDecoderResult res = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT;
1472 BrotliDecoderState *state;
1473
1474 state = BrotliDecoderCreateInstance(NULL, NULL, NULL);
1475 if (!state) {
1476 ZVAL_NULL(value);
1477 return 0;
1478 }
1479
1480 var = (uint8_t*) emalloc(PHP_BROTLI_BUFFER_SIZE);
1481 if (var == NULL) {
1482 ZVAL_NULL(value);
1483 return 0;
1484 }
1485
1486 while (res == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) {
1487 available_out = PHP_BROTLI_BUFFER_SIZE;
1488 next_out = var;
1489 res = BrotliDecoderDecompressStream(state, &available_in, &next_in,
1490 &available_out, &next_out,
1491 0);
1492 used_out = PHP_BROTLI_BUFFER_SIZE - available_out;
1493 if (used_out != 0) {
1494 smart_str_appendl(&out, var, used_out);
1495 }
1496 }
1497
1498 BrotliDecoderDestroyInstance(state);
1499 efree(var);
1500
1501 if (ZSTR_LEN(out.s) <= 0) {
1502 smart_str_free(&out);
1503 ZVAL_NULL(value);
1504 return 0;
1505 }
1506
1507 PHP_VAR_UNSERIALIZE_INIT(var_hash);
1508 tmp = ZSTR_VAL(out.s);
1509 result = php_var_unserialize(value, &tmp,
1510 ZSTR_VAL(out.s) + ZSTR_LEN(out.s),
1511 &var_hash);
1512 PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
1513
1514 if (!result) {
1515 php_error_docref(NULL, E_NOTICE,
1516 "Error at offset %ld of %ld bytes",
1517 (zend_long) ((char*) tmp - ZSTR_VAL(out.s)),
1518 (zend_long) ZSTR_LEN(out.s));
1519 ZVAL_NULL(value);
1520 result = 0;
1521 } else {
1522 result = 1;
1523 }
1524
1525 smart_str_free(&out);
1526
1527 return result;
1528 }
1529 #endif
1530