feature #24699 [HttpFoundation] Add HeaderUtils class (c960657)
This PR was merged into the 4.1-dev branch.
Discussion
----------
[HttpFoundation] Add HeaderUtils class
| Q | A
| ------------- | ---
| Branch? | 3.4
| Bug fix? | yes
| New feature? | yes
| BC breaks? | no
| Deprecations? | no
| Tests pass? | yes
| Fixed tickets |
| License | MIT
| Doc PR |
In several places in HttpFoundation we parse HTTP header values using a variety of regular expressions. Some of them fail in various corner cases.
Parsing HTTP headers is not entirely trivial. We must be able to parse quoted strings with backslash escaping properly and ignore white-space in certain places.
In practice, our limitations in this respect may not be a big problem. We only care about a few different HTTP request headers, and they are usually restricted to a simple values without quoted strings etc. However, this is no excuse for not doing it right :-)
This PR introduces a new utility class for parsing headers. This allows Symfony itself and third-party code to parse HTTP headers in a robust way without using complex regular expressions that are difficult to write and error prone.
Commits
-------
b435e80cae
[HttpFoundation] Add HeaderUtility class
This commit is contained in:
commit
cbc2376803
@ -52,12 +52,17 @@ class AcceptHeader
|
||||
{
|
||||
$index = 0;
|
||||
|
||||
return new self(array_map(function ($itemValue) use (&$index) {
|
||||
$item = AcceptHeaderItem::fromString($itemValue);
|
||||
$parts = HeaderUtils::split((string) $headerValue, ',;=');
|
||||
|
||||
return new self(array_map(function ($subParts) use (&$index) {
|
||||
$part = array_shift($subParts);
|
||||
$attributes = HeaderUtils::combineParts($subParts);
|
||||
|
||||
$item = new AcceptHeaderItem($part[0], $attributes);
|
||||
$item->setIndex($index++);
|
||||
|
||||
return $item;
|
||||
}, preg_split('/\s*(?:,*("[^"]+"),*|,*(\'[^\']+\'),*|,+)\s*/', $headerValue, 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE)));
|
||||
}, $parts));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -40,24 +40,12 @@ class AcceptHeaderItem
|
||||
*/
|
||||
public static function fromString($itemValue)
|
||||
{
|
||||
$bits = preg_split('/\s*(?:;*("[^"]+");*|;*(\'[^\']+\');*|;+)\s*/', $itemValue, 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
|
||||
$value = array_shift($bits);
|
||||
$attributes = array();
|
||||
$parts = HeaderUtils::split($itemValue, ';=');
|
||||
|
||||
$lastNullAttribute = null;
|
||||
foreach ($bits as $bit) {
|
||||
if (($start = substr($bit, 0, 1)) === ($end = substr($bit, -1)) && ('"' === $start || '\'' === $start)) {
|
||||
$attributes[$lastNullAttribute] = substr($bit, 1, -1);
|
||||
} elseif ('=' === $end) {
|
||||
$lastNullAttribute = $bit = substr($bit, 0, -1);
|
||||
$attributes[$bit] = null;
|
||||
} else {
|
||||
$parts = explode('=', $bit);
|
||||
$attributes[$parts[0]] = isset($parts[1]) && strlen($parts[1]) > 0 ? $parts[1] : '';
|
||||
}
|
||||
}
|
||||
$part = array_shift($parts);
|
||||
$attributes = HeaderUtils::combineParts($parts, 1);
|
||||
|
||||
return new self(($start = substr($value, 0, 1)) === ($end = substr($value, -1)) && ('"' === $start || '\'' === $start) ? substr($value, 1, -1) : $value, $attributes);
|
||||
return new self($part[0], $attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -69,9 +57,7 @@ class AcceptHeaderItem
|
||||
{
|
||||
$string = $this->value.($this->quality < 1 ? ';q='.$this->quality : '');
|
||||
if (count($this->attributes) > 0) {
|
||||
$string .= ';'.implode(';', array_map(function ($name, $value) {
|
||||
return sprintf(preg_match('/[,;=]/', $value) ? '%s="%s"' : '%s=%s', $name, $value);
|
||||
}, array_keys($this->attributes), $this->attributes));
|
||||
$string .= '; '.HeaderUtils::joinAssoc($this->attributes, ';');
|
||||
}
|
||||
|
||||
return $string;
|
||||
|
@ -218,20 +218,15 @@ class BinaryFileResponse extends Response
|
||||
if ('x-accel-redirect' === strtolower($type)) {
|
||||
// Do X-Accel-Mapping substitutions.
|
||||
// @link http://wiki.nginx.org/X-accel#X-Accel-Redirect
|
||||
foreach (explode(',', $request->headers->get('X-Accel-Mapping', '')) as $mapping) {
|
||||
$mapping = explode('=', $mapping, 2);
|
||||
|
||||
if (2 === count($mapping)) {
|
||||
$pathPrefix = trim($mapping[0]);
|
||||
$location = trim($mapping[1]);
|
||||
|
||||
$parts = HeaderUtils::split($request->headers->get('X-Accel-Mapping', ''), ',=');
|
||||
$mappings = HeaderUtils::combineParts($parts);
|
||||
foreach ($mappings as $pathPrefix => $location) {
|
||||
if (substr($path, 0, strlen($pathPrefix)) === $pathPrefix) {
|
||||
$path = $location.substr($path, strlen($pathPrefix));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->headers->set($type, $path);
|
||||
$this->maxlen = 0;
|
||||
} elseif ($request->headers->has('Range')) {
|
||||
|
@ -16,6 +16,7 @@ CHANGELOG
|
||||
`IniSizeFileException`, `NoFileException`, `NoTmpDirFileException`, `PartialFileException` to
|
||||
handle failed `UploadedFile`.
|
||||
* added `MigratingSessionHandler` for migrating between two session handlers without losing sessions
|
||||
* added `HeaderUtils`.
|
||||
|
||||
4.0.0
|
||||
-----
|
||||
|
@ -50,34 +50,20 @@ class Cookie
|
||||
'raw' => !$decode,
|
||||
'samesite' => null,
|
||||
);
|
||||
foreach (explode(';', $cookie) as $part) {
|
||||
if (false === strpos($part, '=')) {
|
||||
$key = trim($part);
|
||||
$value = true;
|
||||
} else {
|
||||
list($key, $value) = explode('=', trim($part), 2);
|
||||
$key = trim($key);
|
||||
$value = trim($value);
|
||||
}
|
||||
if (!isset($data['name'])) {
|
||||
$data['name'] = $decode ? urldecode($key) : $key;
|
||||
$data['value'] = true === $value ? null : ($decode ? urldecode($value) : $value);
|
||||
continue;
|
||||
}
|
||||
switch ($key = strtolower($key)) {
|
||||
case 'name':
|
||||
case 'value':
|
||||
break;
|
||||
case 'max-age':
|
||||
$data['expires'] = time() + (int) $value;
|
||||
break;
|
||||
default:
|
||||
$data[$key] = $value;
|
||||
break;
|
||||
}
|
||||
|
||||
$parts = HeaderUtils::split($cookie, ';=');
|
||||
$part = array_shift($parts);
|
||||
|
||||
$name = $decode ? urldecode($part[0]) : $part[0];
|
||||
$value = isset($part[1]) ? ($decode ? urldecode($part[1]) : $part[1]) : null;
|
||||
|
||||
$data = HeaderUtils::combineParts($parts) + $data;
|
||||
|
||||
if (isset($data['max-age'])) {
|
||||
$data['expires'] = time() + (int) $data['max-age'];
|
||||
}
|
||||
|
||||
return new static($data['name'], $data['value'], $data['expires'], $data['path'], $data['domain'], $data['secure'], $data['httponly'], $data['raw'], $data['samesite']);
|
||||
return new static($name, $value, $data['expires'], $data['path'], $data['domain'], $data['secure'], $data['httponly'], $data['raw'], $data['samesite']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -294,21 +294,9 @@ class HeaderBag implements \IteratorAggregate, \Countable
|
||||
|
||||
protected function getCacheControlHeader()
|
||||
{
|
||||
$parts = array();
|
||||
ksort($this->cacheControl);
|
||||
foreach ($this->cacheControl as $key => $value) {
|
||||
if (true === $value) {
|
||||
$parts[] = $key;
|
||||
} else {
|
||||
if (preg_match('#[^a-zA-Z0-9._-]#', $value)) {
|
||||
$value = '"'.$value.'"';
|
||||
}
|
||||
|
||||
$parts[] = "$key=$value";
|
||||
}
|
||||
}
|
||||
|
||||
return implode(', ', $parts);
|
||||
return HeaderUtils::joinAssoc($this->cacheControl, ',');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -320,12 +308,8 @@ class HeaderBag implements \IteratorAggregate, \Countable
|
||||
*/
|
||||
protected function parseCacheControl($header)
|
||||
{
|
||||
$cacheControl = array();
|
||||
preg_match_all('#([a-zA-Z][a-zA-Z_-]*)\s*(?:=(?:"([^"]*)"|([^ \t",;]*)))?#', $header, $matches, PREG_SET_ORDER);
|
||||
foreach ($matches as $match) {
|
||||
$cacheControl[strtolower($match[1])] = isset($match[3]) ? $match[3] : (isset($match[2]) ? $match[2] : true);
|
||||
}
|
||||
$parts = HeaderUtils::split($header, ',=');
|
||||
|
||||
return $cacheControl;
|
||||
return HeaderUtils::combineParts($parts);
|
||||
}
|
||||
}
|
||||
|
174
src/Symfony/Component/HttpFoundation/HeaderUtils.php
Normal file
174
src/Symfony/Component/HttpFoundation/HeaderUtils.php
Normal file
@ -0,0 +1,174 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\HttpFoundation;
|
||||
|
||||
/**
|
||||
* HTTP header utility functions.
|
||||
*
|
||||
* @author Christian Schmidt <github@chsc.dk>
|
||||
*/
|
||||
class HeaderUtils
|
||||
{
|
||||
/**
|
||||
* This class should not be instantiated.
|
||||
*/
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits an HTTP header by one or more separators.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* HeaderUtils::split("da, en-gb;q=0.8", ",;")
|
||||
* // => array(array("da"), array("en-gb"), array("q", "0.8"))
|
||||
*
|
||||
* @param string $header HTTP header value
|
||||
* @param string $separators List of characters to split on, ordered by
|
||||
* precedence, e.g. ",", ";=", or ",;="
|
||||
*
|
||||
* @return array Nested array with as many levels as there are characters in
|
||||
* $separators
|
||||
*/
|
||||
public static function split(string $header, string $separators): array
|
||||
{
|
||||
$quotedSeparators = preg_quote($separators);
|
||||
|
||||
preg_match_all('
|
||||
/
|
||||
(?!\s)
|
||||
(?:
|
||||
# quoted-string
|
||||
"(?:[^"\\\\]|\\\\.)*(?:"|\\\\|$)
|
||||
|
|
||||
# token
|
||||
[^"'.$quotedSeparators.']+
|
||||
)+
|
||||
(?<!\s)
|
||||
|
|
||||
# separator
|
||||
\s*
|
||||
(?<separator>['.$quotedSeparators.'])
|
||||
\s*
|
||||
/x', trim($header), $matches, PREG_SET_ORDER);
|
||||
|
||||
return self::groupParts($matches, $separators);
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines an array of arrays into one associative array.
|
||||
*
|
||||
* Each of the nested arrays should have one or two elements. The first
|
||||
* value will be used as the keys in the associative array, and the second
|
||||
* will be used as the values, or true if the nested array only contains one
|
||||
* element.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* HeaderUtils::combineParts(array(array("foo", "abc"), array("bar")))
|
||||
* // => array("foo" => "abc", "bar" => true)
|
||||
*/
|
||||
public static function combineParts(array $parts): array
|
||||
{
|
||||
$assoc = array();
|
||||
foreach ($parts as $part) {
|
||||
$name = strtolower($part[0]);
|
||||
$value = $part[1] ?? true;
|
||||
$assoc[$name] = $value;
|
||||
}
|
||||
|
||||
return $assoc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Joins an associative array into a string for use in an HTTP header.
|
||||
*
|
||||
* The key and value of each entry are joined with "=", and all entries
|
||||
* is joined with the specified separator and an additional space (for
|
||||
* readability). Values are quoted if necessary.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* HeaderUtils::joinAssoc(array("foo" => "abc", "bar" => true, "baz" => "a b c"), ",")
|
||||
* // => 'foo=bar, baz, baz="a b c"'
|
||||
*/
|
||||
public static function joinAssoc(array $assoc, string $separator): string
|
||||
{
|
||||
$parts = array();
|
||||
foreach ($assoc as $name => $value) {
|
||||
if (true === $value) {
|
||||
$parts[] = $name;
|
||||
} else {
|
||||
$parts[] = $name.'='.self::quote($value);
|
||||
}
|
||||
}
|
||||
|
||||
return implode($separator.' ', $parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a string as a quoted string, if necessary.
|
||||
*
|
||||
* If a string contains characters not allowed by the "token" construct in
|
||||
* the HTTP specification, it is backslash-escaped and enclosed in quotes
|
||||
* to match the "quoted-string" construct.
|
||||
*/
|
||||
public static function quote(string $s): string
|
||||
{
|
||||
if (preg_match('/^[a-z0-9!#$%&\'*.^_`|~-]+$/i', $s)) {
|
||||
return $s;
|
||||
}
|
||||
|
||||
return '"'.addcslashes($s, '"\\"').'"';
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a quoted string.
|
||||
*
|
||||
* If passed an unquoted string that matches the "token" construct (as
|
||||
* defined in the HTTP specification), it is passed through verbatimly.
|
||||
*/
|
||||
public static function unquote(string $s): string
|
||||
{
|
||||
return preg_replace('/\\\\(.)|"/', '$1', $s);
|
||||
}
|
||||
|
||||
private static function groupParts(array $matches, string $separators): array
|
||||
{
|
||||
$separator = $separators[0];
|
||||
$partSeparators = substr($separators, 1);
|
||||
|
||||
$i = 0;
|
||||
$partMatches = array();
|
||||
foreach ($matches as $match) {
|
||||
if (isset($match['separator']) && $match['separator'] === $separator) {
|
||||
++$i;
|
||||
} else {
|
||||
$partMatches[$i][] = $match;
|
||||
}
|
||||
}
|
||||
|
||||
$parts = array();
|
||||
if ($partSeparators) {
|
||||
foreach ($partMatches as $matches) {
|
||||
$parts[] = self::groupParts($matches, $partSeparators);
|
||||
}
|
||||
} else {
|
||||
foreach ($partMatches as $matches) {
|
||||
$parts[] = self::unquote($matches[0][0]);
|
||||
}
|
||||
}
|
||||
|
||||
return $parts;
|
||||
}
|
||||
}
|
@ -1944,8 +1944,16 @@ class Request
|
||||
}
|
||||
|
||||
if ((self::$trustedHeaderSet & self::HEADER_FORWARDED) && $this->headers->has(self::$trustedHeaders[self::HEADER_FORWARDED])) {
|
||||
$forwardedValues = $this->headers->get(self::$trustedHeaders[self::HEADER_FORWARDED]);
|
||||
$forwardedValues = preg_match_all(sprintf('{(?:%s)=(?:"?\[?)([a-zA-Z0-9\.:_\-/]*+)}', self::$forwardedParams[$type]), $forwardedValues, $matches) ? $matches[1] : array();
|
||||
$forwarded = $this->headers->get(self::$trustedHeaders[self::HEADER_FORWARDED]);
|
||||
$parts = HeaderUtils::split($forwarded, ',;=');
|
||||
$forwardedValues = array();
|
||||
$param = self::$forwardedParams[$type];
|
||||
foreach ($parts as $subParts) {
|
||||
$assoc = HeaderUtils::combineParts($subParts);
|
||||
if (isset($assoc[$param])) {
|
||||
$forwardedValues[] = $assoc[$param];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $ip) {
|
||||
@ -1978,9 +1986,17 @@ class Request
|
||||
$firstTrustedIp = null;
|
||||
|
||||
foreach ($clientIps as $key => $clientIp) {
|
||||
// Remove port (unfortunately, it does happen)
|
||||
if (preg_match('{((?:\d+\.){3}\d+)\:\d+}', $clientIp, $match)) {
|
||||
$clientIps[$key] = $clientIp = $match[1];
|
||||
if (strpos($clientIp, '.')) {
|
||||
// Strip :port from IPv4 addresses. This is allowed in Forwarded
|
||||
// and may occur in X-Forwarded-For.
|
||||
$i = strpos($clientIp, ':');
|
||||
if ($i) {
|
||||
$clientIps[$key] = $clientIp = substr($clientIp, 0, $i);
|
||||
}
|
||||
} elseif ('[' == $clientIp[0]) {
|
||||
// Strip brackets and :port from IPv6 addresses.
|
||||
$i = strpos($clientIp, ']', 1);
|
||||
$clientIps[$key] = $clientIp = substr($clientIp, 1, $i - 1);
|
||||
}
|
||||
|
||||
if (!filter_var($clientIp, FILTER_VALIDATE_IP)) {
|
||||
|
@ -290,13 +290,12 @@ class ResponseHeaderBag extends HeaderBag
|
||||
throw new \InvalidArgumentException('The filename and the fallback cannot contain the "/" and "\\" characters.');
|
||||
}
|
||||
|
||||
$output = sprintf('%s; filename="%s"', $disposition, str_replace('"', '\\"', $filenameFallback));
|
||||
|
||||
$params = array('filename' => $filenameFallback);
|
||||
if ($filename !== $filenameFallback) {
|
||||
$output .= sprintf("; filename*=utf-8''%s", rawurlencode($filename));
|
||||
$params['filename*'] = "utf-8''".rawurlencode($filename);
|
||||
}
|
||||
|
||||
return $output;
|
||||
return $disposition.'; '.HeaderUtils::joinAssoc($params, ';');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -66,7 +66,7 @@ class AcceptHeaderItemTest extends TestCase
|
||||
),
|
||||
array(
|
||||
'text/plain', array('charset' => 'utf-8', 'param' => 'this;should,not=matter', 'footnotes' => 'true'),
|
||||
'text/plain;charset=utf-8;param="this;should,not=matter";footnotes=true',
|
||||
'text/plain; charset=utf-8; param="this;should,not=matter"; footnotes=true',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ class BinaryFileResponseTest extends ResponseTestCase
|
||||
$response = BinaryFileResponse::create($file, 404, array(), true, ResponseHeaderBag::DISPOSITION_INLINE);
|
||||
$this->assertEquals(404, $response->getStatusCode());
|
||||
$this->assertFalse($response->headers->has('ETag'));
|
||||
$this->assertEquals('inline; filename="README.md"', $response->headers->get('Content-Disposition'));
|
||||
$this->assertEquals('inline; filename=README.md', $response->headers->get('Content-Disposition'));
|
||||
}
|
||||
|
||||
public function testConstructWithNonAsciiFilename()
|
||||
@ -66,7 +66,7 @@ class BinaryFileResponseTest extends ResponseTestCase
|
||||
$response = new BinaryFileResponse(__FILE__);
|
||||
$response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, 'föö.html');
|
||||
|
||||
$this->assertSame('attachment; filename="f__.html"; filename*=utf-8\'\'f%C3%B6%C3%B6.html', $response->headers->get('Content-Disposition'));
|
||||
$this->assertSame('attachment; filename=f__.html; filename*=utf-8\'\'f%C3%B6%C3%B6.html', $response->headers->get('Content-Disposition'));
|
||||
}
|
||||
|
||||
public function testSetContentDispositionGeneratesSafeFallbackFilenameForWronglyEncodedFilename()
|
||||
@ -77,7 +77,7 @@ class BinaryFileResponseTest extends ResponseTestCase
|
||||
$response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, $iso88591EncodedFilename);
|
||||
|
||||
// the parameter filename* is invalid in this case (rawurldecode('f%F6%F6') does not provide a UTF-8 string but an ISO-8859-1 encoded one)
|
||||
$this->assertSame('attachment; filename="f__.html"; filename*=utf-8\'\'f%F6%F6.html', $response->headers->get('Content-Disposition'));
|
||||
$this->assertSame('attachment; filename=f__.html; filename*=utf-8\'\'f%F6%F6.html', $response->headers->get('Content-Disposition'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -201,6 +201,9 @@ class CookieTest extends TestCase
|
||||
|
||||
$cookie = Cookie::fromString('foo=bar', true);
|
||||
$this->assertEquals(new Cookie('foo', 'bar', 0, '/', null, false, false), $cookie);
|
||||
|
||||
$cookie = Cookie::fromString('foo', true);
|
||||
$this->assertEquals(new Cookie('foo', null, 0, '/', null, false, false), $cookie);
|
||||
}
|
||||
|
||||
public function testFromStringWithHttpOnly()
|
||||
|
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\HttpFoundation\Tests;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpFoundation\HeaderUtils;
|
||||
|
||||
class HeaderUtilsTest extends TestCase
|
||||
{
|
||||
public function testSplit()
|
||||
{
|
||||
$this->assertSame(array('foo=123', 'bar'), HeaderUtils::split('foo=123,bar', ','));
|
||||
$this->assertSame(array('foo=123', 'bar'), HeaderUtils::split('foo=123, bar', ','));
|
||||
$this->assertSame(array(array('foo=123', 'bar')), HeaderUtils::split('foo=123; bar', ',;'));
|
||||
$this->assertSame(array(array('foo=123'), array('bar')), HeaderUtils::split('foo=123, bar', ',;'));
|
||||
$this->assertSame(array('foo', '123, bar'), HeaderUtils::split('foo=123, bar', '='));
|
||||
$this->assertSame(array('foo', '123, bar'), HeaderUtils::split(' foo = 123, bar ', '='));
|
||||
$this->assertSame(array(array('foo', '123'), array('bar')), HeaderUtils::split('foo=123, bar', ',='));
|
||||
$this->assertSame(array(array(array('foo', '123')), array(array('bar'), array('foo', '456'))), HeaderUtils::split('foo=123, bar; foo=456', ',;='));
|
||||
$this->assertSame(array(array(array('foo', 'a,b;c=d'))), HeaderUtils::split('foo="a,b;c=d"', ',;='));
|
||||
|
||||
$this->assertSame(array('foo', 'bar'), HeaderUtils::split('foo,,,, bar', ','));
|
||||
$this->assertSame(array('foo', 'bar'), HeaderUtils::split(',foo, bar,', ','));
|
||||
$this->assertSame(array('foo', 'bar'), HeaderUtils::split(' , foo, bar, ', ','));
|
||||
$this->assertSame(array('foo bar'), HeaderUtils::split('foo "bar"', ','));
|
||||
$this->assertSame(array('foo bar'), HeaderUtils::split('"foo" bar', ','));
|
||||
$this->assertSame(array('foo bar'), HeaderUtils::split('"foo" "bar"', ','));
|
||||
|
||||
// These are not a valid header values. We test that they parse anyway,
|
||||
// and that both the valid and invalid parts are returned.
|
||||
$this->assertSame(array(), HeaderUtils::split('', ','));
|
||||
$this->assertSame(array(), HeaderUtils::split(',,,', ','));
|
||||
$this->assertSame(array('foo', 'bar', 'baz'), HeaderUtils::split('foo, "bar", "baz', ','));
|
||||
$this->assertSame(array('foo', 'bar, baz'), HeaderUtils::split('foo, "bar, baz', ','));
|
||||
$this->assertSame(array('foo', 'bar, baz\\'), HeaderUtils::split('foo, "bar, baz\\', ','));
|
||||
$this->assertSame(array('foo', 'bar, baz\\'), HeaderUtils::split('foo, "bar, baz\\\\', ','));
|
||||
}
|
||||
|
||||
public function testCombineAssoc()
|
||||
{
|
||||
$this->assertSame(array('foo' => '123'), HeaderUtils::combineParts(array(array('foo', '123'))));
|
||||
$this->assertSame(array('foo' => true), HeaderUtils::combineParts(array(array('foo'))));
|
||||
$this->assertSame(array('foo' => true), HeaderUtils::combineParts(array(array('Foo'))));
|
||||
$this->assertSame(array('foo' => '123', 'bar' => true), HeaderUtils::combineParts(array(array('foo', '123'), array('bar'))));
|
||||
}
|
||||
|
||||
public function testJoinAssoc()
|
||||
{
|
||||
$this->assertSame('foo', HeaderUtils::joinAssoc(array('foo' => true), ','));
|
||||
$this->assertSame('foo; bar', HeaderUtils::joinAssoc(array('foo' => true, 'bar' => true), ';'));
|
||||
$this->assertSame('foo=123', HeaderUtils::joinAssoc(array('foo' => '123'), ','));
|
||||
$this->assertSame('foo="1 2 3"', HeaderUtils::joinAssoc(array('foo' => '1 2 3'), ','));
|
||||
$this->assertSame('foo="1 2 3", bar', HeaderUtils::joinAssoc(array('foo' => '1 2 3', 'bar' => true), ','));
|
||||
}
|
||||
|
||||
public function testQuote()
|
||||
{
|
||||
$this->assertSame('foo', HeaderUtils::quote('foo'));
|
||||
$this->assertSame('az09!#$%&\'*.^_`|~-', HeaderUtils::quote('az09!#$%&\'*.^_`|~-'));
|
||||
$this->assertSame('"foo bar"', HeaderUtils::quote('foo bar'));
|
||||
$this->assertSame('"foo [bar]"', HeaderUtils::quote('foo [bar]'));
|
||||
$this->assertSame('"foo \"bar\""', HeaderUtils::quote('foo "bar"'));
|
||||
$this->assertSame('"foo \\\\ bar"', HeaderUtils::quote('foo \\ bar'));
|
||||
}
|
||||
|
||||
public function testUnquote()
|
||||
{
|
||||
$this->assertEquals('foo', HeaderUtils::unquote('foo'));
|
||||
$this->assertEquals('az09!#$%&\'*.^_`|~-', HeaderUtils::unquote('az09!#$%&\'*.^_`|~-'));
|
||||
$this->assertEquals('foo bar', HeaderUtils::unquote('"foo bar"'));
|
||||
$this->assertEquals('foo [bar]', HeaderUtils::unquote('"foo [bar]"'));
|
||||
$this->assertEquals('foo "bar"', HeaderUtils::unquote('"foo \"bar\""'));
|
||||
$this->assertEquals('foo "bar"', HeaderUtils::unquote('"foo \"\b\a\r\""'));
|
||||
$this->assertEquals('foo \\ bar', HeaderUtils::unquote('"foo \\\\ bar"'));
|
||||
}
|
||||
}
|
@ -894,7 +894,7 @@ class RequestTest extends TestCase
|
||||
array(array('127.0.0.1'), '127.0.0.1', 'for="_gazonk"', array('127.0.0.1')),
|
||||
array(array('88.88.88.88'), '127.0.0.1', 'for="88.88.88.88:80"', array('127.0.0.1')),
|
||||
array(array('192.0.2.60'), '::1', 'for=192.0.2.60;proto=http;by=203.0.113.43', array('::1')),
|
||||
array(array('2620:0:1cfe:face:b00c::3', '192.0.2.43'), '::1', 'for=192.0.2.43, for=2620:0:1cfe:face:b00c::3', array('::1')),
|
||||
array(array('2620:0:1cfe:face:b00c::3', '192.0.2.43'), '::1', 'for=192.0.2.43, for="[2620:0:1cfe:face:b00c::3]"', array('::1')),
|
||||
array(array('2001:db8:cafe::17'), '::1', 'for="[2001:db8:cafe::17]:4711', array('::1')),
|
||||
);
|
||||
}
|
||||
|
@ -287,12 +287,12 @@ class ResponseHeaderBagTest extends TestCase
|
||||
public function provideMakeDisposition()
|
||||
{
|
||||
return array(
|
||||
array('attachment', 'foo.html', 'foo.html', 'attachment; filename="foo.html"'),
|
||||
array('attachment', 'foo.html', '', 'attachment; filename="foo.html"'),
|
||||
array('attachment', 'foo.html', 'foo.html', 'attachment; filename=foo.html'),
|
||||
array('attachment', 'foo.html', '', 'attachment; filename=foo.html'),
|
||||
array('attachment', 'foo bar.html', '', 'attachment; filename="foo bar.html"'),
|
||||
array('attachment', 'foo "bar".html', '', 'attachment; filename="foo \\"bar\\".html"'),
|
||||
array('attachment', 'foo%20bar.html', 'foo bar.html', 'attachment; filename="foo bar.html"; filename*=utf-8\'\'foo%2520bar.html'),
|
||||
array('attachment', 'föö.html', 'foo.html', 'attachment; filename="foo.html"; filename*=utf-8\'\'f%C3%B6%C3%B6.html'),
|
||||
array('attachment', 'föö.html', 'foo.html', 'attachment; filename=foo.html; filename*=utf-8\'\'f%C3%B6%C3%B6.html'),
|
||||
);
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user