add brotli filter and tests
authorMichael Wallner <mike@php.net>
Mon, 26 Feb 2018 16:25:46 +0000 (17:25 +0100)
committerMichael Wallner <mike@php.net>
Mon, 26 Feb 2018 16:25:46 +0000 (17:25 +0100)
src/php_http_encoding.c
src/php_http_encoding.h
src/php_http_encoding_brotli.c
src/php_http_filter.c
tests/encstream015.phpt [new file with mode: 0644]
tests/encstream016.phpt [new file with mode: 0644]
tests/encstream017.phpt [new file with mode: 0644]
tests/encstream018.phpt [new file with mode: 0644]
tests/encstream019.phpt [new file with mode: 0644]
tests/filterbrotli.phpt [new file with mode: 0644]

index ad43853..e4d016e 100644 (file)
@@ -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);
 
index 0995021..3b59829 100644 (file)
@@ -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
index f51775b..a46a2f6 100644 (file)
@@ -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;
 
index 654edff..2b81100 100644 (file)
@@ -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 (file)
index 0000000..f2fcf4b
--- /dev/null
@@ -0,0 +1,41 @@
+--TEST--
+encoding stream brotli static
+--SKIPIF--
+<?php
+include "skipif.inc";
+?>
+--FILE--
+<?php
+echo "Test\n";
+
+$file = file_get_contents(__FILE__);
+var_dump($file ===
+       http\Encoding\Stream\Debrotli::decode(
+               http\Encoding\Stream\Enbrotli::encode(
+                       $file, http\Encoding\Stream\Enbrotli::MODE_GENERIC
+               )
+       )
+);
+var_dump($file ===
+       http\Encoding\Stream\Debrotli::decode(
+               http\Encoding\Stream\Enbrotli::encode(
+                       $file, http\Encoding\Stream\Enbrotli::MODE_TEXT
+               )
+       )
+);
+var_dump($file ===
+       http\Encoding\Stream\Debrotli::decode(
+               http\Encoding\Stream\Enbrotli::encode(
+                       $file, http\Encoding\Stream\Enbrotli::MODE_FONT
+               )
+       )
+);
+
+?>
+DONE
+--EXPECT--
+Test
+bool(true)
+bool(true)
+bool(true)
+DONE
diff --git a/tests/encstream016.phpt b/tests/encstream016.phpt
new file mode 100644 (file)
index 0000000..2ecf9b6
--- /dev/null
@@ -0,0 +1,34 @@
+--TEST--
+encoding stream brotli auto flush
+--SKIPIF--
+<?php
+include "skipif.inc";
+class_exists("http\\Encoding\\Stream\\Enbrotli") or die("SKIP need brotli support");
+
+?>
+--FILE--
+<?php
+echo "Test\n";
+
+$defl = new http\Encoding\Stream\Enbrotli(http\Encoding\Stream::FLUSH_FULL);
+$infl = new http\Encoding\Stream\Debrotli;
+
+for ($f = fopen(__FILE__, "rb"); !feof($f); $data = fread($f, 0x100)) {
+       if (isset($data)) {
+               if ($data !== $d=$infl->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 (file)
index 0000000..6ca281d
--- /dev/null
@@ -0,0 +1,33 @@
+--TEST--
+encoding stream brotli without flush
+--SKIPIF--
+<?php
+include "skipif.inc";
+class_exists("http\\Encoding\\Stream\\Enbrotli") or die("SKIP need brotli support");
+?>
+--FILE--
+<?php
+echo "Test\n";
+
+$defl = new http\Encoding\Stream\Enbrotli;
+$infl = new http\Encoding\Stream\Debrotli;
+$file = file(__FILE__);
+$data = "";
+foreach ($file as $line) {
+       if (strlen($temp = $defl->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 (file)
index 0000000..8b98bec
--- /dev/null
@@ -0,0 +1,42 @@
+--TEST--
+encoding stream brotli with explicit flush
+--SKIPIF--
+<?php
+include "skipif.inc";
+class_exists("http\\Encoding\\Stream\\Enbrotli") or die("SKIP need brotli support");
+?>
+--FILE--
+<?php
+echo "Test\n";
+
+$enc = new http\Encoding\Stream\Enbrotli;
+$dec = new http\Encoding\Stream\Debrotli;
+$file = file(__FILE__);
+$data = "";
+foreach ($file as $line) {
+       $data .= $dec->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 (file)
index 0000000..7fa83c8
--- /dev/null
@@ -0,0 +1,21 @@
+--TEST--
+encoding stream brotli error
+--SKIPIF--
+<?php
+include "skipif.inc";
+class_exists("http\\Encoding\\Stream\\Enbrotli") or die("SKIP need brotli support");
+?>
+--FILE--
+<?php
+echo "Test\n";
+
+var_dump(http\Encoding\Stream\Debrotli::decode("if this goes through, something's pretty wrong"));
+
+?>
+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 (file)
index 0000000..7c4ffaf
--- /dev/null
@@ -0,0 +1,32 @@
+--TEST--
+brotli filter
+--SKIPIF--
+<?php 
+include "skipif.inc";
+class_exists("http\\Encoding\\Stream\\Enbrotli", false) or die("SKIP need brotli support");
+?>
+--FILE--
+<?php
+list($in, $out) = stream_socket_pair(
+    STREAM_PF_UNIX,
+    STREAM_SOCK_STREAM,
+    STREAM_IPPROTO_IP
+);
+stream_filter_append($in, "http.brotli_decode", STREAM_FILTER_READ);
+stream_filter_append($out, "http.brotli_encode", STREAM_FILTER_WRITE,
+    http\Encoding\Stream\Enbrotli::LEVEL_MAX);
+
+$file = file(__FILE__);
+foreach ($file as $line) {
+    fwrite($out, $line);
+    fflush($out);
+}
+fclose($out);
+if (implode("",$file) !== ($read = fread($in, filesize(__FILE__)))) {
+    echo "got: $read\n";
+}
+fclose($in);
+?>
+DONE
+--EXPECT--
+DONE