Skip to content

Commit

Permalink
Merge pull request #119 from mapogolions/fix/headers-with-empty-array…
Browse files Browse the repository at this point in the history
…-value

Add header with empty array value must throw exception
  • Loading branch information
l0gicgate committed Aug 7, 2019
2 parents 0771c73 + af1b37b commit 3b8dc24
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 47 deletions.
101 changes: 59 additions & 42 deletions src/Headers.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,9 @@ public function __construct(array $headers = [], ?array $globals = null)
/**
* {@inheritdoc}
*/
public function addHeader(string $name, $values): HeadersInterface
public function addHeader(string $name, $value): HeadersInterface
{
$values = $this->validateAndTrimHeader($name, $values);
$originalName = $this->normalizeHeaderName($name, true);
$normalizedName = $this->normalizeHeaderName($name);
[$values, $originalName, $normalizedName] = $this->prepareHeader($name, $value);

if (isset($this->headers[$normalizedName])) {
$header = $this->headers[$normalizedName];
Expand All @@ -59,9 +57,7 @@ public function addHeader(string $name, $values): HeadersInterface
public function removeHeader(string $name): HeadersInterface
{
$name = $this->normalizeHeaderName($name);

unset($this->headers[$name]);

return $this;
}

Expand All @@ -77,25 +73,20 @@ public function getHeader(string $name, $default = []): array
return $header->getValues();
}

if (is_array($default)) {
return count(array_keys($default)) ? $this->validateAndTrimHeader($name, $default) : $default;
}

if (is_string($default) || is_numeric($default)) {
return $this->validateAndTrimHeader($name, $default);
if (empty($default)) {
return $default;
}

throw new InvalidArgumentException('Default parameter of Headers::getHeader() must be a string or an array.');
$this->validateHeader($name, $default);
return $this->trimHeaderValue($default);
}

/**
* {@inheritdoc}
*/
public function setHeader(string $name, $values): HeadersInterface
public function setHeader(string $name, $value): HeadersInterface
{
$values = $this->validateAndTrimHeader($name, $values);
$originalName = $this->normalizeHeaderName($name, true);
$normalizedName = $this->normalizeHeaderName($name);
[$values, $originalName, $normalizedName] = $this->prepareHeader($name, $value);

// Ensure we preserve original case if the header already exists in the stack
if (isset($this->headers[$normalizedName])) {
Expand Down Expand Up @@ -128,7 +119,6 @@ public function setHeaders(array $headers): HeadersInterface
public function hasHeader(string $name): bool
{
$name = $this->normalizeHeaderName($name);

return isset($this->headers[$name]);
}

Expand Down Expand Up @@ -189,6 +179,38 @@ protected function parseAuthorizationHeader(array $headers): array
return $headers;
}

/**
* @param array|string $value
*
* @return array
*/
protected function trimHeaderValue($value): array
{
$items = is_array($value) ? $value : [$value];
$result = [];
foreach ($items as $item) {
$result[] = trim((string) $item, " \t");
}
return $result;
}

/**
* @param string $name
* @param array|string $value
*
* @throws InvalidArgumentException
*
* @return array
*/
protected function prepareHeader(string $name, $value): array
{
$this->validateHeader($name, $value);
$values = $this->trimHeaderValue($value);
$originalName = $this->normalizeHeaderName($name, true);
$normalizedName = $this->normalizeHeaderName($name);
return [$values, $originalName, $normalizedName];
}

/**
* Make sure the header complies with RFC 7230.
*
Expand All @@ -208,31 +230,20 @@ protected function parseAuthorizationHeader(array $headers): array
* @see https://tools.ietf.org/html/rfc7230#section-3.2.4
*
* @param string $name
* @param array|string $values
*
* @return array
* @param array|string $value
*
* @throws InvalidArgumentException;
*/
protected function validateAndTrimHeader(string $name, $values): array
protected function validateHeader(string $name, $value)
{
self::validateHeaderName($name);

if (!is_array($values)) {
$values = [$values];
}

$returnValues = [];
foreach ($values as $value) {
self::validateHeaderValue($value);
$returnValues[] = trim((string) $value, " \t");
}

return $returnValues;
self::validateHeaderValue($value);
}

/**
* @param mixed $name
*
* @throws InvalidArgumentException
*/
public static function validateHeaderName($name)
{
Expand All @@ -243,22 +254,28 @@ public static function validateHeaderName($name)

/**
* @param mixed $value
*
* @throws InvalidArgumentException
*/
public static function validateHeaderValue($value)
{
if (is_array($value) && empty($value)) {
$items = is_array($value) ? $value : [$value];

if (empty($items)) {
throw new InvalidArgumentException(
'Header values must be a string or an array of strings, empty array given.'
);
}

if (!is_array($value)
&& (
(!is_numeric($value) && !is_string($value))
|| preg_match("@^[ \t\x21-\x7E\x80-\xFF]*$@", (string) $value) !== 1
)
) {
throw new InvalidArgumentException('Header values must be RFC 7230 compatible strings.');
$pattern = "@^[ \t\x21-\x7E\x80-\xFF]*$@";
foreach ($items as $item) {
$hasInvalidType = !is_numeric($item) && !is_string($item);
$rejected = $hasInvalidType || preg_match($pattern, (string) $item) !== 1;
if ($rejected) {
throw new InvalidArgumentException(
'Header values must be RFC 7230 compatible strings.'
);
}
}
}

Expand Down
8 changes: 4 additions & 4 deletions src/Interfaces/HeadersInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ interface HeadersInterface
* This method appends the value to the existing array of values
*
* @param string $name
* @param array|string $values
* @param array|string $value
*
* @return HeadersInterface
*
* @throws InvalidArgumentException
*/
public function addHeader(string $name, $values): HeadersInterface;
public function addHeader(string $name, $value): HeadersInterface;

/**
* Remove header value
Expand All @@ -51,13 +51,13 @@ public function getHeader(string $name, $default = []): array;
* Replaces the existing header value with the new value.
*
* @param string $name
* @param array|string $values
* @param array|string $value
*
* @return HeadersInterface
*
* @throws InvalidArgumentException
*/
public function setHeader(string $name, $values): HeadersInterface;
public function setHeader(string $name, $value): HeadersInterface;

/**
* Replaces all existing headers with the new values.
Expand Down
10 changes: 9 additions & 1 deletion tests/HeadersTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,15 @@ public function testAddHeader()
$this->assertEquals(['Accept' => ['application/json', 'text/html']], $headers->getHeaders(true));
}

/**
* @expectedException InvalidArgumentException
*/
public function testAddHeaderValueEmptyArray()
{
$headers = new Headers();
$headers->addHeader('Header', []);
}

public function testRemoveHeader()
{
$headers = new Headers([
Expand Down Expand Up @@ -86,7 +95,6 @@ public function testGetHeaderReturnsValidatedAndTrimedHeaderDefaultValue()

/**
* @expectedException InvalidArgumentException
* @expectedExceptionMessage Default parameter of Headers::getHeader() must be a string or an array.
*/
public function testGetHeaderThrowsExceptionWithInvalidDefaultArgument()
{
Expand Down

0 comments on commit 3b8dc24

Please sign in to comment.