From 6e51a51f7f316102113460250bc66f593063ad55 Mon Sep 17 00:00:00 2001 From: Michael Wallner Date: Mon, 26 Feb 2018 17:25:46 +0100 Subject: [PATCH] add brotli filter and tests --- src/php_http_encoding.c | 22 +++++++-- src/php_http_encoding.h | 1 + src/php_http_encoding_brotli.c | 19 ++++++-- src/php_http_filter.c | 85 +++++++++++++++++++++++----------- tests/encstream015.phpt | 41 ++++++++++++++++ tests/encstream016.phpt | 34 ++++++++++++++ tests/encstream017.phpt | 33 +++++++++++++ tests/encstream018.phpt | 42 +++++++++++++++++ tests/encstream019.phpt | 21 +++++++++ tests/filterbrotli.phpt | 32 +++++++++++++ 10 files changed, 295 insertions(+), 35 deletions(-) create mode 100644 tests/encstream015.phpt create mode 100644 tests/encstream016.phpt create mode 100644 tests/encstream017.phpt create mode 100644 tests/encstream018.phpt create mode 100644 tests/encstream019.phpt create mode 100644 tests/filterbrotli.phpt diff --git a/src/php_http_encoding.c b/src/php_http_encoding.c index ad43853..e4d016e 100644 --- a/src/php_http_encoding.c +++ b/src/php_http_encoding.c @@ -164,7 +164,9 @@ ZEND_RESULT_CODE php_http_encoding_stream_reset(php_http_encoding_stream_t **s) if (EXPECTED((*s)->ops->dtor)) { (*s)->ops->dtor(*s); } + if (EXPECTED(ss = (*s)->ops->init(*s))) { + ss->flags &= ~PHP_HTTP_ENCODING_STREAM_DIRTY; *s = ss; return SUCCESS; } @@ -173,10 +175,15 @@ ZEND_RESULT_CODE php_http_encoding_stream_reset(php_http_encoding_stream_t **s) ZEND_RESULT_CODE php_http_encoding_stream_update(php_http_encoding_stream_t *s, const char *in_str, size_t in_len, char **out_str, size_t *out_len) { - if (UNEXPECTED(!s->ops->update)) { - return FAILURE; + ZEND_RESULT_CODE rc = FAILURE; + + if (EXPECTED(s->ops->update)) { + rc = s->ops->update(s, in_str, in_len, out_str, out_len); } - return s->ops->update(s, in_str, in_len, out_str, out_len); + + s->flags |= PHP_HTTP_ENCODING_STREAM_DIRTY; + + return rc; } ZEND_RESULT_CODE php_http_encoding_stream_flush(php_http_encoding_stream_t *s, char **out_str, size_t *out_len) @@ -192,7 +199,7 @@ ZEND_RESULT_CODE php_http_encoding_stream_flush(php_http_encoding_stream_t *s, c zend_bool php_http_encoding_stream_done(php_http_encoding_stream_t *s) { if (!s->ops->done) { - return 0; + return !(s->flags & PHP_HTTP_ENCODING_STREAM_DIRTY); } return s->ops->done(s); } @@ -202,6 +209,9 @@ ZEND_RESULT_CODE php_http_encoding_stream_finish(php_http_encoding_stream_t *s, if (!s->ops->finish) { *out_str = NULL; *out_len = 0; + + s->flags &= ~PHP_HTTP_ENCODING_STREAM_DIRTY; + return SUCCESS; } return s->ops->finish(s, out_str, out_len); @@ -472,6 +482,10 @@ zend_object *php_http_encoding_stream_object_clone(zval *object) php_http_encoding_stream_object_t *new_obj, *old_obj = PHP_HTTP_OBJ(NULL, object); php_http_encoding_stream_t *cpy = php_http_encoding_stream_copy(old_obj->stream, NULL); + if (!cpy) { + return NULL; + } + new_obj = php_http_encoding_stream_object_new_ex(old_obj->zo.ce, cpy); zend_objects_clone_members(&new_obj->zo, &old_obj->zo); diff --git a/src/php_http_encoding.h b/src/php_http_encoding.h index 0995021..3b59829 100644 --- a/src/php_http_encoding.h +++ b/src/php_http_encoding.h @@ -16,6 +16,7 @@ extern PHP_MINIT_FUNCTION(http_encoding); #define PHP_HTTP_ENCODING_STREAM_PERSISTENT 0x01000000 +#define PHP_HTTP_ENCODING_STREAM_DIRTY 0x02000000 #define PHP_HTTP_ENCODING_STREAM_FLUSH_NONE 0x00000000 #define PHP_HTTP_ENCODING_STREAM_FLUSH_SYNC 0x00100000 diff --git a/src/php_http_encoding_brotli.c b/src/php_http_encoding_brotli.c index f51775b..a46a2f6 100644 --- a/src/php_http_encoding_brotli.c +++ b/src/php_http_encoding_brotli.c @@ -96,6 +96,7 @@ static ZEND_RESULT_CODE enbrotli_update(php_http_encoding_stream_t *s, const cha } else { *encoded = NULL; *encoded_len = 0; + php_http_buffer_dtor(&out); } return SUCCESS; } @@ -162,12 +163,19 @@ static ZEND_RESULT_CODE enbrotli_flush(php_http_encoding_stream_t *s, char **enc static ZEND_RESULT_CODE enbrotli_finish(php_http_encoding_stream_t *s, char **encoded, size_t *encoded_len) { - return enbrotli_flush_ex(s, BROTLI_OPERATION_FINISH, encoded, encoded_len); + ZEND_RESULT_CODE rc; + + do { + rc = enbrotli_flush_ex(s, BROTLI_OPERATION_FINISH, encoded, encoded_len); + } while (SUCCESS == rc && !BrotliEncoderIsFinished(s->ctx)); + + return rc; } static zend_bool enbrotli_done(php_http_encoding_stream_t *s) { - return BrotliEncoderIsFinished(s->ctx); + return !(s->flags & PHP_HTTP_ENCODING_STREAM_DIRTY) + || BrotliEncoderIsFinished(s->ctx); } static void enbrotli_dtor(php_http_encoding_stream_t *s) @@ -227,6 +235,7 @@ static ZEND_RESULT_CODE debrotli_update(php_http_encoding_stream_t *s, const cha *decoded = out.data; *decoded_len = out.used; } else { + php_http_buffer_dtor(&out); *decoded = NULL; *decoded_len = 0; } @@ -235,13 +244,13 @@ static ZEND_RESULT_CODE debrotli_update(php_http_encoding_stream_t *s, const cha php_http_buffer_dtor(&out); - php_error_docref(NULL, E_WARNING, "Could not brotli decode data: %s", BrotliDecoderErrorString(rc)); + php_error_docref(NULL, E_WARNING, "Could not brotli decode data: %s", BrotliDecoderErrorString(BrotliDecoderGetErrorCode(s->ctx))); return FAILURE; } static zend_bool debrotli_done(php_http_encoding_stream_t *s) { - return BrotliDecoderIsFinished(s->ctx); + return !BrotliDecoderIsUsed(s->ctx) || BrotliDecoderIsFinished(s->ctx); } static void debrotli_dtor(php_http_encoding_stream_t *s) @@ -341,7 +350,7 @@ static PHP_METHOD(HttpEnbrotliStream, encode) size_t len; zend_long flags = PHP_HTTP_ENBROTLI_MODE_GENERIC | PHP_HTTP_ENBROTLI_WBITS_DEF | PHP_HTTP_ENBROTLI_LEVEL_DEF; - if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS(), "s", &str, &len, &flags)) { + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS(), "s|l", &str, &len, &flags)) { char *enc_str = NULL; size_t enc_len; diff --git a/src/php_http_filter.c b/src/php_http_filter.c index 654edff..2b81100 100644 --- a/src/php_http_filter.c +++ b/src/php_http_filter.c @@ -75,7 +75,7 @@ typedef struct _http_chunked_decode_filter_buffer_t { ulong hexlen; } PHP_HTTP_FILTER_BUFFER(chunked_decode); -typedef php_http_encoding_stream_t PHP_HTTP_FILTER_BUFFER(zlib); +typedef php_http_encoding_stream_t PHP_HTTP_FILTER_BUFFER(stream); static PHP_HTTP_FILTER_FUNCTION(chunked_decode) { @@ -266,22 +266,10 @@ static PHP_HTTP_FILTER_FUNCTION(chunked_encode) return PSFS_PASS_ON; } -static PHP_HTTP_FILTER_OPS(chunked_decode) = { - PHP_HTTP_FILTER_FUNC(chunked_decode), - PHP_HTTP_FILTER_DTOR(chunked_decode), - "http.chunked_decode" -}; - -static PHP_HTTP_FILTER_OPS(chunked_encode) = { - PHP_HTTP_FILTER_FUNC(chunked_encode), - NULL, - "http.chunked_encode" -}; - -static PHP_HTTP_FILTER_FUNCTION(zlib) +static PHP_HTTP_FILTER_FUNCTION(stream) { php_stream_bucket *ptr, *nxt; - PHP_HTTP_FILTER_BUFFER(zlib) *buffer = Z_PTR(this->abstract); + PHP_HTTP_FILTER_BUFFER(stream) *buffer = Z_PTR(this->abstract); if (bytes_consumed) { *bytes_consumed = 0; @@ -307,7 +295,7 @@ static PHP_HTTP_FILTER_FUNCTION(zlib) } #if DBG_FILTER - fprintf(stderr, "update: deflate (-> %zu) (w: %zu, r: %zu)\n", encoded_len, stream->writepos, stream->readpos); + fprintf(stderr, "update: compress (-> %zu) (w: %zu, r: %zu)\n", encoded_len, stream->writepos, stream->readpos); #endif if (encoded) { @@ -329,7 +317,7 @@ static PHP_HTTP_FILTER_FUNCTION(zlib) } #if DBG_FILTER - fprintf(stderr, "flush: deflate (-> %zu)\n", encoded_len); + fprintf(stderr, "flush: compress (-> %zu)\n", encoded_len); #endif if (encoded) { @@ -349,7 +337,7 @@ static PHP_HTTP_FILTER_FUNCTION(zlib) } #if DBG_FILTER - fprintf(stderr, "finish: deflate (-> %zu)\n", encoded_len); + fprintf(stderr, "finish: compress (-> %zu)\n", encoded_len); #endif if (encoded) { @@ -362,24 +350,49 @@ static PHP_HTTP_FILTER_FUNCTION(zlib) return PSFS_PASS_ON; } -static PHP_HTTP_FILTER_DESTRUCTOR(zlib) + +static PHP_HTTP_FILTER_DESTRUCTOR(stream) { - PHP_HTTP_FILTER_BUFFER(zlib) *buffer = Z_PTR(this->abstract); + PHP_HTTP_FILTER_BUFFER(stream) *buffer = Z_PTR(this->abstract); php_http_encoding_stream_free(&buffer); } +static PHP_HTTP_FILTER_OPS(chunked_decode) = { + PHP_HTTP_FILTER_FUNC(chunked_decode), + PHP_HTTP_FILTER_DTOR(chunked_decode), + "http.chunked_decode" +}; + +static PHP_HTTP_FILTER_OPS(chunked_encode) = { + PHP_HTTP_FILTER_FUNC(chunked_encode), + NULL, + "http.chunked_encode" +}; + static PHP_HTTP_FILTER_OPS(deflate) = { - PHP_HTTP_FILTER_FUNC(zlib), - PHP_HTTP_FILTER_DTOR(zlib), + PHP_HTTP_FILTER_FUNC(stream), + PHP_HTTP_FILTER_DTOR(stream), "http.deflate" }; static PHP_HTTP_FILTER_OPS(inflate) = { - PHP_HTTP_FILTER_FUNC(zlib), - PHP_HTTP_FILTER_DTOR(zlib), + PHP_HTTP_FILTER_FUNC(stream), + PHP_HTTP_FILTER_DTOR(stream), "http.inflate" }; +static PHP_HTTP_FILTER_OPS(brotli_encode) = { + PHP_HTTP_FILTER_FUNC(stream), + PHP_HTTP_FILTER_DTOR(stream), + "http.brotli_encode" +}; + +static PHP_HTTP_FILTER_OPS(brotli_decode) = { + PHP_HTTP_FILTER_FUNC(stream), + PHP_HTTP_FILTER_DTOR(stream), + "http.brotli_decode" +}; + #if PHP_VERSION_ID >= 70200 static php_stream_filter *http_filter_create(const char *name, zval *params, uint8_t p) #else @@ -420,7 +433,7 @@ static php_stream_filter *http_filter_create(const char *name, zval *params, int } else if (!strcasecmp(name, "http.inflate")) { - PHP_HTTP_FILTER_BUFFER(zlib) *b = NULL; + PHP_HTTP_FILTER_BUFFER(stream) *b = NULL; if ((b = php_http_encoding_stream_init(NULL, php_http_encoding_stream_get_inflate_ops(), flags))) { if (!(f = php_stream_filter_alloc(&PHP_HTTP_FILTER_OP(inflate), b, p))) { @@ -430,13 +443,33 @@ static php_stream_filter *http_filter_create(const char *name, zval *params, int } else if (!strcasecmp(name, "http.deflate")) { - PHP_HTTP_FILTER_BUFFER(zlib) *b = NULL; + PHP_HTTP_FILTER_BUFFER(stream) *b = NULL; if ((b = php_http_encoding_stream_init(NULL, php_http_encoding_stream_get_deflate_ops(), flags))) { if (!(f = php_stream_filter_alloc(&PHP_HTTP_FILTER_OP(deflate), b, p))) { php_http_encoding_stream_free(&b); } } + } else + + if (!strcasecmp(name, "http.brotli_encode")) { + PHP_HTTP_FILTER_BUFFER(stream) *b = NULL; + + if ((b = php_http_encoding_stream_init(NULL, php_http_encoding_stream_get_enbrotli_ops(), flags))) { + if (!(f = php_stream_filter_alloc(&PHP_HTTP_FILTER_OP(brotli_encode), b, p))) { + php_http_encoding_stream_free(&b); + } + } + } else + + if (!strcasecmp(name, "http.brotli_decode")) { + PHP_HTTP_FILTER_BUFFER(stream) *b = NULL; + + if ((b = php_http_encoding_stream_init(NULL, php_http_encoding_stream_get_debrotli_ops(), flags))) { + if (!(f = php_stream_filter_alloc(&PHP_HTTP_FILTER_OP(brotli_decode), b, p))) { + php_http_encoding_stream_free(&b); + } + } } return f; diff --git a/tests/encstream015.phpt b/tests/encstream015.phpt new file mode 100644 index 0000000..f2fcf4b --- /dev/null +++ b/tests/encstream015.phpt @@ -0,0 +1,41 @@ +--TEST-- +encoding stream brotli static +--SKIPIF-- + +--FILE-- + +DONE +--EXPECT-- +Test +bool(true) +bool(true) +bool(true) +DONE diff --git a/tests/encstream016.phpt b/tests/encstream016.phpt new file mode 100644 index 0000000..2ecf9b6 --- /dev/null +++ b/tests/encstream016.phpt @@ -0,0 +1,34 @@ +--TEST-- +encoding stream brotli auto flush +--SKIPIF-- + +--FILE-- +update($defl->update($data))) { + printf("uh-oh »%s« != »%s«\n", $data, $d); + } + } +} + +echo $infl->update($defl->finish()); +echo $infl->finish(); + +var_dump($infl->done(), $defl->done()); +?> +DONE +--EXPECT-- +Test +bool(true) +bool(true) +DONE diff --git a/tests/encstream017.phpt b/tests/encstream017.phpt new file mode 100644 index 0000000..6ca281d --- /dev/null +++ b/tests/encstream017.phpt @@ -0,0 +1,33 @@ +--TEST-- +encoding stream brotli without flush +--SKIPIF-- + +--FILE-- +update($line))) { + foreach(str_split($temp) as $byte) { + $data .= $infl->update($byte); + } + } +} +if (strlen($temp = $defl->finish())) { + $data .= $infl->update($temp); +} +$data .= $infl->finish(); +var_dump(implode("", $file) === $data); +?> +DONE +--EXPECT-- +Test +bool(true) +DONE diff --git a/tests/encstream018.phpt b/tests/encstream018.phpt new file mode 100644 index 0000000..8b98bec --- /dev/null +++ b/tests/encstream018.phpt @@ -0,0 +1,42 @@ +--TEST-- +encoding stream brotli with explicit flush +--SKIPIF-- + +--FILE-- +flush(); + if (strlen($temp = $enc->update($line))) { + $data .= $dec->update($temp); + $data .= $dec->flush(); + } + if (strlen($temp = $enc->flush())) { + $data .= $dec->update($temp); + $data .= $dec->flush(); + } +} +if (strlen($temp = $enc->finish())) { + $data .= $dec->update($temp); +} +var_dump($enc->done()); +$data .= $dec->finish(); +var_dump($dec->done()); +var_dump(implode("", $file) === $data); + +?> +DONE +--EXPECT-- +Test +bool(true) +bool(true) +bool(true) +DONE diff --git a/tests/encstream019.phpt b/tests/encstream019.phpt new file mode 100644 index 0000000..7fa83c8 --- /dev/null +++ b/tests/encstream019.phpt @@ -0,0 +1,21 @@ +--TEST-- +encoding stream brotli error +--SKIPIF-- + +--FILE-- + +DONE +--EXPECTF-- +Test + +Warning: http\Encoding\Stream\Debrotli::decode(): Could not brotli decode data: %s in %s on line %d +bool(false) +DONE diff --git a/tests/filterbrotli.phpt b/tests/filterbrotli.phpt new file mode 100644 index 0000000..7c4ffaf --- /dev/null +++ b/tests/filterbrotli.phpt @@ -0,0 +1,32 @@ +--TEST-- +brotli filter +--SKIPIF-- + +--FILE-- + +DONE +--EXPECT-- +DONE -- 2.30.2