2828#include <php.h>
2929#include <SAPI.h>
3030#include <php_ini.h>
31+ #include <ext/hash/php_hash.h>
32+ #include <ext/hash/php_hash_sha.h>
33+ #include <ext/standard/base64.h>
3134#include <ext/standard/file.h>
3235#include <ext/standard/info.h>
3336#if PHP_VERSION_ID < 70200
@@ -80,6 +83,7 @@ struct _php_zstd_context {
8083 ZSTD_DDict * ddict ;
8184 ZSTD_inBuffer input ;
8285 ZSTD_outBuffer output ;
86+ zend_uchar dict_digest [32 ];
8387 zend_object std ;
8488};
8589
@@ -107,6 +111,7 @@ static void php_zstd_context_init(php_zstd_context *ctx)
107111 ctx -> output .dst = NULL ;
108112 ctx -> output .size = 0 ;
109113 ctx -> output .pos = 0 ;
114+ memset (ctx -> dict_digest , 0 , sizeof (ctx -> dict_digest ));
110115}
111116
112117static void php_zstd_context_free (php_zstd_context * ctx )
@@ -1253,6 +1258,9 @@ static int APC_UNSERIALIZER_NAME(zstd)(APC_UNSERIALIZER_ARGS)
12531258#if PHP_VERSION_ID >= 80000
12541259#define PHP_ZSTD_OUTPUT_HANDLER_NAME "zstd output compression"
12551260
1261+ #define PHP_ZSTD_ENCODING_ZSTD (1 << 0)
1262+ #define PHP_ZSTD_ENCODING_DCZ (1 << 1)
1263+
12561264static int php_zstd_output_encoding (void )
12571265{
12581266 zval * enc ;
@@ -1270,7 +1278,10 @@ static int php_zstd_output_encoding(void)
12701278 sizeof ("HTTP_ACCEPT_ENCODING" ) - 1 ))) {
12711279 convert_to_string (enc );
12721280 if (strstr (Z_STRVAL_P (enc ), "zstd" )) {
1273- PHP_ZSTD_G (compression_coding ) = 1 ;
1281+ PHP_ZSTD_G (compression_coding ) = PHP_ZSTD_ENCODING_ZSTD ;
1282+ }
1283+ if (strstr (Z_STRVAL_P (enc ), "dcz" )) {
1284+ PHP_ZSTD_G (compression_coding ) |= PHP_ZSTD_ENCODING_DCZ ;
12741285 }
12751286 }
12761287 }
@@ -1308,6 +1319,50 @@ php_zstd_output_handler_load_dict(php_zstd_context *ctx)
13081319
13091320 php_stream_close (stream );
13101321
1322+ if (!data ) {
1323+ return NULL ;
1324+ }
1325+
1326+ if (PHP_ZSTD_G (compression_coding ) & PHP_ZSTD_ENCODING_DCZ ) {
1327+ zval * available ;
1328+ if ((Z_TYPE (PG (http_globals )[TRACK_VARS_SERVER ]) == IS_ARRAY
1329+ || zend_is_auto_global_str (ZEND_STRL ("_SERVER" )))
1330+ && (available = zend_hash_str_find (
1331+ Z_ARRVAL (PG (http_globals )[TRACK_VARS_SERVER ]),
1332+ "HTTP_AVAILABLE_DICTIONARY" ,
1333+ sizeof ("HTTP_AVAILABLE_DICTIONARY" ) - 1 ))) {
1334+ convert_to_string (available );
1335+
1336+ PHP_SHA256_CTX context ;
1337+ PHP_SHA256Init (& context );
1338+ PHP_SHA256Update (& context , ZSTR_VAL (data ), ZSTR_LEN (data ));
1339+ PHP_SHA256Final (ctx -> dict_digest , & context );
1340+
1341+ zend_string * b64 ;
1342+ b64 = php_base64_encode (ctx -> dict_digest , sizeof (ctx -> dict_digest ));
1343+ if (b64 ) {
1344+ if (Z_STRLEN_P (available ) <= ZSTR_LEN (b64 )
1345+ || memcmp (ZSTR_VAL (b64 ),
1346+ Z_STRVAL_P (available ) + 1 , ZSTR_LEN (b64 ))) {
1347+ php_error_docref (NULL , E_WARNING ,
1348+ "zstd: invalid available-dictionary: "
1349+ "request(%s) != actual(%s)" ,
1350+ Z_STRVAL_P (available ), ZSTR_VAL (b64 ));
1351+ PHP_ZSTD_G (compression_coding ) &= ~PHP_ZSTD_ENCODING_DCZ ;
1352+ zend_string_release (data );
1353+ data = NULL ;
1354+ }
1355+ zend_string_free (b64 );
1356+ }
1357+ } else {
1358+ php_error_docref (NULL , E_WARNING ,
1359+ "zstd: not found available-dictionary" );
1360+ PHP_ZSTD_G (compression_coding ) &= ~PHP_ZSTD_ENCODING_DCZ ;
1361+ zend_string_release (data );
1362+ data = NULL ;
1363+ }
1364+ }
1365+
13111366 return data ;
13121367}
13131368
@@ -1347,9 +1402,19 @@ php_zstd_output_handler_write(php_zstd_context *ctx,
13471402 if (output_context -> out .size < ctx -> output .size ) {
13481403 output_context -> out .size = ctx -> output .size ;
13491404 }
1350- output_context -> out .data = emalloc (output_context -> out .size );
1405+
1406+ if ((output_context -> op & PHP_OUTPUT_HANDLER_START )
1407+ && (PHP_ZSTD_G (compression_coding ) & PHP_ZSTD_ENCODING_DCZ )) {
1408+ output_context -> out .size += 40 ;
1409+ output_context -> out .data = emalloc (output_context -> out .size );
1410+ memcpy (output_context -> out .data , "\x5e\x2a\x4d\x18\x20\x00\x00\x00" , 8 );
1411+ memcpy (output_context -> out .data + 8 , ctx -> dict_digest , 32 );
1412+ output_context -> out .used = 40 ;
1413+ } else {
1414+ output_context -> out .data = emalloc (output_context -> out .size );
1415+ output_context -> out .used = 0 ;
1416+ }
13511417 output_context -> out .free = 1 ;
1352- output_context -> out .used = 0 ;
13531418
13541419 do {
13551420 ctx -> output .pos = 0 ;
@@ -1430,7 +1495,13 @@ php_zstd_output_handler(void **handler_context,
14301495 && (output_context -> op != (PHP_OUTPUT_HANDLER_START
14311496 |PHP_OUTPUT_HANDLER_CLEAN
14321497 |PHP_OUTPUT_HANDLER_FINAL ))) {
1433- sapi_add_header_ex (ZEND_STRL ("Vary: Accept-Encoding" ), 1 , 0 );
1498+ if (PHP_ZSTD_G (compression_coding ) & PHP_ZSTD_ENCODING_DCZ ) {
1499+ sapi_add_header_ex (
1500+ ZEND_STRL ("Vary: Accept-Encoding, Available-Dictionary" ),
1501+ 1 , 0 );
1502+ } else {
1503+ sapi_add_header_ex (ZEND_STRL ("Vary: Accept-Encoding" ), 1 , 0 );
1504+ }
14341505 }
14351506 return FAILURE ;
14361507 }
@@ -1449,8 +1520,18 @@ php_zstd_output_handler(void **handler_context,
14491520 if (SG (headers_sent ) || !PHP_ZSTD_G (output_compression )) {
14501521 return FAILURE ;
14511522 }
1452- sapi_add_header_ex (ZEND_STRL ("Content-Encoding: zstd" ), 1 , 1 );
1453- sapi_add_header_ex (ZEND_STRL ("Vary: Accept-Encoding" ), 1 , 0 );
1523+ if (PHP_ZSTD_G (compression_coding ) & PHP_ZSTD_ENCODING_DCZ ) {
1524+ sapi_add_header_ex (ZEND_STRL ("Content-Encoding: dcz" ),
1525+ 1 , 1 );
1526+ sapi_add_header_ex (ZEND_STRL ("Vary: Accept-Encoding, "
1527+ "Available-Dictionary" ),
1528+ 1 , 0 );
1529+ } else {
1530+ sapi_add_header_ex (ZEND_STRL ("Content-Encoding: zstd" ),
1531+ 1 , 1 );
1532+ sapi_add_header_ex (ZEND_STRL ("Vary: Accept-Encoding" ),
1533+ 1 , 0 );
1534+ }
14541535 php_output_handler_hook (PHP_OUTPUT_HANDLER_HOOK_IMMUTABLE ,
14551536 NULL );
14561537 }
0 commit comments