Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
*.inc -text
*.phpt -text
*.zstd binary
*.dic binary
Binary file added tests/files/ob_data.zstd
Binary file not shown.
Binary file added tests/files/ob_dcz.zstd
Binary file not shown.
24 changes: 24 additions & 0 deletions tests/ob_dcz_001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
--TEST--
output handler: dcz
--SKIPIF--
<?php
include (dirname(__FILE__) . '/ob_skipif.inc');
?>
--GET--
ob=dictionary
--ENV--
HTTP_ACCEPT_ENCODING=dcz
HTTP_AVAILABLE_DICTIONARY=:5wg7BLZeirApJAxOdI/QBi8RvwZuIJfPf0TwMo/x/yg=:
--FILE--
<?php
ini_set('zstd.output_compression', 1);
ini_set('zstd.output_compression_dict', dirname(__FILE__) . '/data.dic');

include(dirname(__FILE__) . '/data.inc');
echo "{$data}";
?>
--EXPECT_EXTERNAL--
files/ob_dcz.zstd
--EXPECTHEADERS--
Content-Encoding: dcz
Vary: Accept-Encoding, Available-Dictionary
20 changes: 20 additions & 0 deletions tests/ob_dcz_002.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
--TEST--
output handler: dcz: invalid available-dictionary
--SKIPIF--
<?php
include (dirname(__FILE__) . '/ob_skipif.inc');
?>
--GET--
ob=dictionary
--ENV--
HTTP_ACCEPT_ENCODING=dcz
--FILE--
<?php
ini_set('zstd.output_compression', 1);
ini_set('zstd.output_compression_dict', dirname(__FILE__) . '/data.dic');

include(dirname(__FILE__) . '/data.inc');
echo "{$data}";
--EXPECTF--
%a
Warning: %s: zstd: not found available-dictionary in Unknown on line 0
22 changes: 22 additions & 0 deletions tests/ob_dcz_003.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
--TEST--
output handler: dcz: invalid available-dictionary
--SKIPIF--
<?php
include (dirname(__FILE__) . '/ob_skipif.inc');
?>
--GET--
ob=dictionary
--ENV--
HTTP_ACCEPT_ENCODING=dcz
HTTP_AVAILABLE_DICTIONARY=:test:
--FILE--
<?php
ini_set('zstd.output_compression', 1);
ini_set('zstd.output_compression_dict', dirname(__FILE__) . '/data.dic');

include(dirname(__FILE__) . '/data.inc');
echo "{$data}";
?>
--EXPECTF--
%a
Warning: %s: zstd: invalid available-dictionary: request(:test:) != actual(%s) in Unknown on line 0
24 changes: 24 additions & 0 deletions tests/ob_dcz_004.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
--TEST--
output handler: zstd,dcz: invalid available-dictionary
--SKIPIF--
<?php
include (dirname(__FILE__) . '/ob_skipif.inc');
?>
--GET--
ob=dictionary
--ENV--
HTTP_ACCEPT_ENCODING=dcz,zstd
HTTP_AVAILABLE_DICTIONARY=:test:
--FILE--
<?php
ini_set('zstd.output_compression', 1);
ini_set('zstd.output_compression_dict', dirname(__FILE__) . '/data.dic');

include(dirname(__FILE__) . '/data.inc');
echo "{$data}";
?>
--EXPECT_EXTERNAL--
files/ob_data.zstd
--EXPECTHEADERS--
Content-Encoding: zstd
Vary: Accept-Encoding
98 changes: 89 additions & 9 deletions zstd.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@
#include <php.h>
#include <SAPI.h>
#include <php_ini.h>
#include <ext/hash/php_hash.h>
#include <ext/hash/php_hash_sha.h>
#include <ext/standard/base64.h>
#include <ext/standard/file.h>
#include <ext/standard/info.h>
#if PHP_VERSION_ID < 70200
Expand Down Expand Up @@ -80,6 +83,7 @@ struct _php_zstd_context {
ZSTD_DDict *ddict;
ZSTD_inBuffer input;
ZSTD_outBuffer output;
zend_uchar dict_digest[32];
zend_object std;
};

Expand Down Expand Up @@ -107,6 +111,7 @@ static void php_zstd_context_init(php_zstd_context *ctx)
ctx->output.dst = NULL;
ctx->output.size = 0;
ctx->output.pos = 0;
memset(ctx->dict_digest, 0, sizeof(ctx->dict_digest));
}

static void php_zstd_context_free(php_zstd_context *ctx)
Expand Down Expand Up @@ -1253,6 +1258,9 @@ static int APC_UNSERIALIZER_NAME(zstd)(APC_UNSERIALIZER_ARGS)
#if PHP_VERSION_ID >= 80000
#define PHP_ZSTD_OUTPUT_HANDLER_NAME "zstd output compression"

#define PHP_ZSTD_ENCODING_ZSTD (1 << 0)
#define PHP_ZSTD_ENCODING_DCZ (1 << 1)

static int php_zstd_output_encoding(void)
{
zval *enc;
Expand All @@ -1270,7 +1278,10 @@ static int php_zstd_output_encoding(void)
sizeof("HTTP_ACCEPT_ENCODING") - 1))) {
convert_to_string(enc);
if (strstr(Z_STRVAL_P(enc), "zstd")) {
PHP_ZSTD_G(compression_coding) = 1;
PHP_ZSTD_G(compression_coding) = PHP_ZSTD_ENCODING_ZSTD;
}
if (strstr(Z_STRVAL_P(enc), "dcz")) {
PHP_ZSTD_G(compression_coding) |= PHP_ZSTD_ENCODING_DCZ;
}
}
}
Expand Down Expand Up @@ -1308,6 +1319,50 @@ php_zstd_output_handler_load_dict(php_zstd_context *ctx)

php_stream_close(stream);

if (!data) {
return NULL;
}

if (PHP_ZSTD_G(compression_coding) & PHP_ZSTD_ENCODING_DCZ) {
zval *available;
if ((Z_TYPE(PG(http_globals)[TRACK_VARS_SERVER]) == IS_ARRAY
|| zend_is_auto_global_str(ZEND_STRL("_SERVER")))
&& (available = zend_hash_str_find(
Z_ARRVAL(PG(http_globals)[TRACK_VARS_SERVER]),
"HTTP_AVAILABLE_DICTIONARY",
sizeof("HTTP_AVAILABLE_DICTIONARY") - 1))) {
convert_to_string(available);

PHP_SHA256_CTX context;
PHP_SHA256Init(&context);
PHP_SHA256Update(&context, ZSTR_VAL(data), ZSTR_LEN(data));
PHP_SHA256Final(ctx->dict_digest, &context);

zend_string *b64;
b64 = php_base64_encode(ctx->dict_digest, sizeof(ctx->dict_digest));
if (b64) {
if (Z_STRLEN_P(available) <= ZSTR_LEN(b64)
|| memcmp(ZSTR_VAL(b64),
Z_STRVAL_P(available) + 1, ZSTR_LEN(b64))) {
php_error_docref(NULL, E_WARNING,
"zstd: invalid available-dictionary: "
"request(%s) != actual(%s)",
Z_STRVAL_P(available), ZSTR_VAL(b64));
PHP_ZSTD_G(compression_coding) &= ~PHP_ZSTD_ENCODING_DCZ;
zend_string_release(data);
data = NULL;
}
zend_string_free(b64);
}
} else {
php_error_docref(NULL, E_WARNING,
"zstd: not found available-dictionary");
PHP_ZSTD_G(compression_coding) &= ~PHP_ZSTD_ENCODING_DCZ;
zend_string_release(data);
data = NULL;
}
}

return data;
}

Expand All @@ -1320,12 +1375,11 @@ static zend_result php_zstd_output_handler_context_start(php_zstd_context *ctx)
}

zend_string *dict = php_zstd_output_handler_load_dict(ctx);

if (php_zstd_context_create_compress(ctx, level, dict) != SUCCESS) {
if (!PHP_ZSTD_G(compression_coding)) {
return FAILURE;
}

return SUCCESS;
return php_zstd_context_create_compress(ctx, level, dict);
}

static void php_zstd_output_handler_context_dtor(void *opaq)
Expand All @@ -1348,9 +1402,19 @@ php_zstd_output_handler_write(php_zstd_context *ctx,
if (output_context->out.size < ctx->output.size) {
output_context->out.size = ctx->output.size;
}
output_context->out.data = emalloc(output_context->out.size);

if ((output_context->op & PHP_OUTPUT_HANDLER_START)
&& (PHP_ZSTD_G(compression_coding) & PHP_ZSTD_ENCODING_DCZ)) {
output_context->out.size += 40;
output_context->out.data = emalloc(output_context->out.size);
memcpy(output_context->out.data, "\x5e\x2a\x4d\x18\x20\x00\x00\x00", 8);
memcpy(output_context->out.data + 8, ctx->dict_digest, 32);
output_context->out.used = 40;
} else {
output_context->out.data = emalloc(output_context->out.size);
output_context->out.used = 0;
}
output_context->out.free = 1;
output_context->out.used = 0;

do {
ctx->output.pos = 0;
Expand Down Expand Up @@ -1431,7 +1495,13 @@ php_zstd_output_handler(void **handler_context,
&& (output_context->op != (PHP_OUTPUT_HANDLER_START
|PHP_OUTPUT_HANDLER_CLEAN
|PHP_OUTPUT_HANDLER_FINAL))) {
sapi_add_header_ex(ZEND_STRL("Vary: Accept-Encoding"), 1, 0);
if (PHP_ZSTD_G(compression_coding) & PHP_ZSTD_ENCODING_DCZ) {
sapi_add_header_ex(
ZEND_STRL("Vary: Accept-Encoding, Available-Dictionary"),
1, 0);
} else {
sapi_add_header_ex(ZEND_STRL("Vary: Accept-Encoding"), 1, 0);
}
}
return FAILURE;
}
Expand All @@ -1450,8 +1520,18 @@ php_zstd_output_handler(void **handler_context,
if (SG(headers_sent) || !PHP_ZSTD_G(output_compression)) {
return FAILURE;
}
sapi_add_header_ex(ZEND_STRL("Content-Encoding: zstd"), 1, 1);
sapi_add_header_ex(ZEND_STRL("Vary: Accept-Encoding"), 1, 0);
if (PHP_ZSTD_G(compression_coding) & PHP_ZSTD_ENCODING_DCZ) {
sapi_add_header_ex(ZEND_STRL("Content-Encoding: dcz"),
1, 1);
sapi_add_header_ex(ZEND_STRL("Vary: Accept-Encoding, "
"Available-Dictionary"),
1, 0);
} else {
sapi_add_header_ex(ZEND_STRL("Content-Encoding: zstd"),
1, 1);
sapi_add_header_ex(ZEND_STRL("Vary: Accept-Encoding"),
1, 0);
}
php_output_handler_hook(PHP_OUTPUT_HANDLER_HOOK_IMMUTABLE,
NULL);
}
Expand Down
Loading