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
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,33 @@ brotli.output\_compression\_dict | "" | PHP\_INI\_ALL
This is useful for already-compressed binary formats where
additional Brotli compression usually provides little benefit.

A token prefixed with `!` negates a match: it makes the extension
compress that MIME type even if it is in the built-in exclusion list.
Negation also supports exact matches and `type/*` wildcards. Since
`!` is a special character in php.ini, values that use it must be
quoted.

```ini
brotli.output_compression_exclude_types="!image/png"
```

Within the same list, if a MIME type matches multiple tokens, the
last matching token wins, regardless of whether it is positive or
negative (the same precedence rule `.gitignore` uses). This allows
patterns such as:

```ini
brotli.output_compression_exclude_types="image/*,!image/png"
```

which excludes all `image/*` types except `image/png`, since the
negation is listed after the wildcard.

Negation only applies within this setting: if it doesn't produce a
definitive match for the current MIME type, the built-in list is
still consulted. The built-in list's contents can be seen in the
phpinfo() output.

* brotli.output\_compression\_dict _string_

Specifies the path to the compressed dictionary file to be
Expand Down
49 changes: 37 additions & 12 deletions brotli.c
Original file line number Diff line number Diff line change
Expand Up @@ -382,16 +382,20 @@ static int php_brotli_output_encoding(void)
return BROTLI_G(compression_coding);
}

static int php_brotli_output_mimetype_excluded(const char *exclude)
/* returns 1 if a positive token matches, -1 if a negative (!) token
* matches, 0 if no token matches; when multiple tokens in the same
* list match, the last one listed wins (gitignore-style precedence) */
static int php_brotli_output_mimetype_match(const char *list)
{
#if defined(COMPILE_DL_BROTLI) && defined(ZTS)
ZEND_TSRMLS_CACHE_UPDATE();
#endif
const char *mimetype = SG(sapi_headers).mimetype;
const char *p, *end;
size_t mimetype_len;
int last_match = 0;

if (!mimetype || !*mimetype || !exclude || !*exclude) {
if (!mimetype || !*mimetype || !list || !*list) {
return 0;
}

Expand All @@ -400,10 +404,11 @@ static int php_brotli_output_mimetype_excluded(const char *exclude)
end++;
}
mimetype_len = end - mimetype;
p = exclude;
p = list;

while (*p) {
size_t token_len;
zend_bool negated = 0;

while (*p == ',' || *p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') {
p++;
Expand All @@ -416,26 +421,50 @@ static int php_brotli_output_mimetype_excluded(const char *exclude)

token_len = end - p;

if (token_len > 0 && *p == '!') {
negated = 1;
p++;
token_len--;
}

if (token_len > 0) {
zend_bool matched = 0;

if (token_len >= 2 && p[token_len - 2] == '/' && p[token_len - 1] == '*') {
size_t prefix_len = token_len - 1;

if (mimetype_len >= prefix_len &&
!strncasecmp(mimetype, p, prefix_len)) {
return 1;
matched = 1;
}
}

if (mimetype_len == token_len &&
if (!matched && mimetype_len == token_len &&
!strncasecmp(mimetype, p, token_len)) {
return 1;
matched = 1;
}

if (matched) {
last_match = negated ? -1 : 1;
}
}

p = end;
}

return 0;
return last_match;
}

static int php_brotli_output_mimetype_excluded(void)
{
int result = php_brotli_output_mimetype_match(
BROTLI_G(output_compression_exclude_types));

if (result != 0) {
return result > 0;
}

return php_brotli_output_mimetype_match(BROTLI_MIMETYPE_EXCLUDE) > 0;
}

static zend_string *php_brotli_output_handler_load_dict(php_brotli_context *ctx)
Expand Down Expand Up @@ -559,11 +588,7 @@ static int php_brotli_output_handler(void **handler_context,
php_brotli_context *ctx = *(php_brotli_context **)handler_context;

if ((output_context->op & PHP_OUTPUT_HANDLER_START)
&& (
php_brotli_output_mimetype_excluded(BROTLI_MIMETYPE_EXCLUDE)
||
php_brotli_output_mimetype_excluded(BROTLI_G(output_compression_exclude_types))
)) {
&& php_brotli_output_mimetype_excluded()) {
return FAILURE;
}

Expand Down
20 changes: 20 additions & 0 deletions tests/ob_exclude_005.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
--TEST--
brotli.output_compression_exclude_types built-in exclusion applies by default
--SKIPIF--
<?php
if (!extension_loaded('brotli')) die('skip need ext/brotli');
if (false === stristr(PHP_SAPI, 'cgi')) die('skip need sapi/cgi');
?>
--GET--
a=b
--INI--
brotli.output_compression=1
--ENV--
HTTP_ACCEPT_ENCODING=br
--FILE--
<?php
header("Content-Type: image/png");
echo "hi\n";
?>
--EXPECT--
hi
24 changes: 24 additions & 0 deletions tests/ob_exclude_006.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
--TEST--
brotli.output_compression_exclude_types negation token overrides built-in exclusion
--SKIPIF--
<?php
if (!extension_loaded('brotli')) die('skip need ext/brotli');
if (false === stristr(PHP_SAPI, 'cgi')) die('skip need sapi/cgi');
?>
--GET--
a=b
--INI--
brotli.output_compression=1
brotli.output_compression_exclude_types="!image/png"
--ENV--
HTTP_ACCEPT_ENCODING=br
--FILE--
<?php
header("Content-Type: image/png");
echo "hi\n";
?>
--EXPECT_EXTERNAL--
files/ob_hi.br
--EXPECTHEADERS--
Content-Encoding: br
Vary: Accept-Encoding
24 changes: 24 additions & 0 deletions tests/ob_exclude_007.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
--TEST--
brotli.output_compression_exclude_types later negation token wins over earlier positive match
--SKIPIF--
<?php
if (!extension_loaded('brotli')) die('skip need ext/brotli');
if (false === stristr(PHP_SAPI, 'cgi')) die('skip need sapi/cgi');
?>
--GET--
a=b
--INI--
brotli.output_compression=1
brotli.output_compression_exclude_types="image/*,!image/png"
--ENV--
HTTP_ACCEPT_ENCODING=br
--FILE--
<?php
header("Content-Type: image/png");
echo "hi\n";
?>
--EXPECT_EXTERNAL--
files/ob_hi.br
--EXPECTHEADERS--
Content-Encoding: br
Vary: Accept-Encoding
21 changes: 21 additions & 0 deletions tests/ob_exclude_008.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
--TEST--
brotli.output_compression_exclude_types later positive token wins over earlier negation
--SKIPIF--
<?php
if (!extension_loaded('brotli')) die('skip need ext/brotli');
if (false === stristr(PHP_SAPI, 'cgi')) die('skip need sapi/cgi');
?>
--GET--
a=b
--INI--
brotli.output_compression=1
brotli.output_compression_exclude_types="!image/png,image/*"
--ENV--
HTTP_ACCEPT_ENCODING=br
--FILE--
<?php
header("Content-Type: image/png");
echo "hi\n";
?>
--EXPECT--
hi
Loading