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