diff --git a/composer.json b/composer.json index 00f5ebb4..e63bdb6b 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,8 @@ ], "require": { "php": ">=5.6.0", - "nette/utils": "^2.4" + "nette/utils": "^2.4", + "nette/tokenizer": "^2.2" }, "require-dev": { "nette/di": "^2.3", diff --git a/src/Http/Helpers.php b/src/Http/Helpers.php index fe281b0d..9f437047 100644 --- a/src/Http/Helpers.php +++ b/src/Http/Helpers.php @@ -49,6 +49,62 @@ public static function ipMatch($ip, $mask) } + public static function parseHeader($value) + { + $tokenizer = new Nette\Utils\Tokenizer([ + 'word' => '[\\w!#$%&\'*+\\-.\\^`|\\~/]+', // "/" manually allowed + + 'quoted' => '"(?:\\\\[\x01-\x7F]|[^"\\\\\x00\x80-\xFF])*+"', + 'equal' => '=', + 'semicolon' => ';', + 'ows' => '\h+', + ]); + + $tokens = $tokenizer->tokenize("$value;"); + $parsed = []; + $k = NULL; + $v = NULL; + $e = FALSE; + + foreach ($tokens as list($value, $offset, $type)) { + if ($type === 'word') { + if ($e && $v === NULL) { + $v = $value; + } elseif ($k === NULL) { + $k = $value; + } else { + throw new \Exception(); + } + + } elseif ($type === 'quoted') { + $value = stripslashes(substr($value, 1, -1)); + if ($e && $v === NULL) { + $v = $value; + } else { + throw new \Exception(); + } + + } elseif ($type === 'semicolon') { + if ($k === NULL) { + throw new \Exception(); + } + $parsed[$k] = $v; + $k = $v = NULL; + $e = FALSE; + + } elseif ($type === 'equal') { + if ($e || $k === NULL) { + throw new \Exception(); + } + + $e = TRUE; + } + } + + return $parsed; + } + + /** * Removes duplicate cookies from response. * @return void diff --git a/tests/Http/Helpers.phpt b/tests/Http/Helpers.phpt index 76a95919..c0ebb3c6 100644 --- a/tests/Http/Helpers.phpt +++ b/tests/Http/Helpers.phpt @@ -45,3 +45,47 @@ test(function () { Assert::same('Tue, 15 Nov 1994 08:12:31 GMT', Helpers::formatDate(new DateTime('1994-11-15T06:12:31-0200'))); Assert::same('Tue, 15 Nov 1994 08:12:31 GMT', Helpers::formatDate(784887151)); }); + + + +test(function () { + Assert::same( + ['text/html' => NULL], + Helpers::parseHeader('text/html') + ); + + Assert::same( + ['text/html' => NULL, 'charset' => 'iso-8859-1'], + Helpers::parseHeader('text/html; charset="iso-8859-1"') + ); + + Assert::same( + ['text/html' => NULL, 'charset' => 'ISO-8859-4'], + Helpers::parseHeader('text/html; charset=ISO-8859-4') + ); + + Assert::same( + ['text/html' => NULL, 'charset' => 'utf-8'], + Helpers::parseHeader('text/html;charset=utf-8') + ); + + Assert::same( + ['INLINE' => NULL, 'FILENAME' => 'an example.html'], + Helpers::parseHeader('INLINE; FILENAME= "an example.html"') + ); + + // SUPPORT THIS? + Assert::same( + ['attachment' => NULL, 'filename' => '€ rates'], + Helpers::parseHeader('attachment; filename*= UTF-8\'\'%e2%82%ac%20rates') + ); + +/* + + Content-Type: token "/" token params + + params = *( OWS ";" OWS parameter ) + param = token "=" ( token / quoted-string ) + + */ +});