diff --git a/src/MessageTrait.php b/src/MessageTrait.php index 1e4da649..831e20d7 100644 --- a/src/MessageTrait.php +++ b/src/MessageTrait.php @@ -66,11 +66,8 @@ public function getHeaderLine($header) public function withHeader($header, $value) { - if (!is_array($value)) { - $value = [$value]; - } - - $value = $this->trimHeaderValues($value); + $this->assertHeader($header); + $value = $this->normalizeHeaderValue($value); $normalized = strtolower($header); $new = clone $this; @@ -85,11 +82,8 @@ public function withHeader($header, $value) public function withAddedHeader($header, $value) { - if (!is_array($value)) { - $value = [$value]; - } - - $value = $this->trimHeaderValues($value); + $this->assertHeader($header); + $value = $this->normalizeHeaderValue($value); $normalized = strtolower($header); $new = clone $this; @@ -144,11 +138,8 @@ private function setHeaders(array $headers) { $this->headerNames = $this->headers = []; foreach ($headers as $header => $value) { - if (!is_array($value)) { - $value = [$value]; - } - - $value = $this->trimHeaderValues($value); + $this->assertHeader($header); + $value = $this->normalizeHeaderValue($value); $normalized = strtolower($header); if (isset($this->headerNames[$normalized])) { $header = $this->headerNames[$normalized]; @@ -160,6 +151,23 @@ private function setHeaders(array $headers) } } + private function normalizeHeaderValue($value) + { + if (!is_array($value)) { + if (!is_string($value)) { + throw new \InvalidArgumentException('Header value must be a string or an array of strings.'); + } + + return $this->trimHeaderValues([$value]); + } + + if (count($value) === 0) { + throw new \InvalidArgumentException('Header value can not be an empty array.'); + } + + return $this->trimHeaderValues($value); + } + /** * Trims whitespace from the header values. * @@ -177,7 +185,17 @@ private function setHeaders(array $headers) private function trimHeaderValues(array $values) { return array_map(function ($value) { + if (!is_string($value)) { + throw new \InvalidArgumentException('Header value must be a string.'); + } return trim($value, " \t"); }, $values); } + + private function assertHeader($header) + { + if (!is_string($header) || $header === '') { + throw new \InvalidArgumentException('Header must be a non empty string.'); + } + } } diff --git a/src/Request.php b/src/Request.php index 00066424..355e546b 100644 --- a/src/Request.php +++ b/src/Request.php @@ -36,6 +36,7 @@ public function __construct( $body = null, $version = '1.1' ) { + $this->assertMethod($method); if (!($uri instanceof UriInterface)) { $uri = new Uri($uri); } @@ -91,6 +92,7 @@ public function getMethod() public function withMethod($method) { + $this->assertMethod($method); $new = clone $this; $new->method = strtoupper($method); return $new; @@ -139,4 +141,11 @@ private function updateHostFromUri() // See: http://tools.ietf.org/html/rfc7230#section-5.4 $this->headers = [$header => [$host]] + $this->headers; } + + private function assertMethod($method) + { + if (!is_string($method) || $method === '') { + throw new \InvalidArgumentException('Method should be a non empty string.'); + } + } } diff --git a/src/Response.php b/src/Response.php index 6e72c06b..bea5f260 100644 --- a/src/Response.php +++ b/src/Response.php @@ -93,9 +93,9 @@ public function __construct( $version = '1.1', $reason = null ) { - if (filter_var($status, FILTER_VALIDATE_INT) === false) { - throw new \InvalidArgumentException('Status code must be an integer value.'); - } + $this->assertStatusCodeIsInteger($status); + $status = (int) $status; + $this->assertStatusCodeRange($status); $this->statusCode = (int) $status; @@ -125,12 +125,30 @@ public function getReasonPhrase() public function withStatus($code, $reasonPhrase = '') { + $this->assertStatusCodeIsInteger($code); + $code = (int) $code; + $this->assertStatusCodeRange($code); + $new = clone $this; - $new->statusCode = (int) $code; + $new->statusCode = $code; if ($reasonPhrase == '' && isset(self::$phrases[$new->statusCode])) { $reasonPhrase = self::$phrases[$new->statusCode]; } $new->reasonPhrase = $reasonPhrase; return $new; } + + private function assertStatusCodeIsInteger($statusCode) + { + if (filter_var($statusCode, FILTER_VALIDATE_INT) === false) { + throw new \InvalidArgumentException('Status code must be an integer value.'); + } + } + + private function assertStatusCodeRange($statusCode) + { + if ($statusCode < 100 || $statusCode >= 600) { + throw new \InvalidArgumentException('Status code must be an integer value between 1xx and 5xx.'); + } + } } diff --git a/tests/RequestTest.php b/tests/RequestTest.php index 2e3969ca..c9b5957b 100644 --- a/tests/RequestTest.php +++ b/tests/RequestTest.php @@ -6,6 +6,7 @@ use GuzzleHttp\Psr7\Uri; /** + * @covers GuzzleHttp\Psr7\MessageTrait * @covers GuzzleHttp\Psr7\Request */ class RequestTest extends BaseTest @@ -90,6 +91,35 @@ public function testWithUri() $this->assertSame($u1, $r1->getUri()); } + /** + * @dataProvider invalidMethodsProvider + */ + public function testConstructWithInvalidMethods($method) + { + $this->expectException('InvalidArgumentException'); + new Request($method, '/'); + } + + /** + * @dataProvider invalidMethodsProvider + */ + public function testWithInvalidMethods($method) + { + $r = new Request('get', '/'); + $this->expectException('InvalidArgumentException'); + $r->withMethod($method); + } + + public function invalidMethodsProvider() + { + return [ + [null], + [false], + [['foo']], + [new \stdClass()], + ]; + } + public function testSameInstanceWhenSameUri() { $r1 = new Request('GET', 'http://foo.com'); diff --git a/tests/ResponseTest.php b/tests/ResponseTest.php index 4e9a37e1..0fde5600 100644 --- a/tests/ResponseTest.php +++ b/tests/ResponseTest.php @@ -237,6 +237,51 @@ public function testSameInstanceWhenRemovingMissingHeader() $this->assertSame($r, $r->withoutHeader('foo')); } + /** + * @dataProvider invalidHeaderProvider + */ + public function testConstructResponseInvalidHeader($header, $headerValue, $expectedMessage) + { + $this->expectException('InvalidArgumentException', $expectedMessage); + new Response(200, [$header => $headerValue]); + } + + public function invalidHeaderProvider() + { + return [ + ['foo', [], 'Header value can not be an empty array.'], + ['', '', 'Header must be a non empty string.'], + ['foo', false, 'Header value must be a string or an array of strings.'], + [false, 'foo', 'Header must be a non empty string.'], + ['foo', new \stdClass(), 'Header value must be a string or an array of strings.'], + ['foo', new \ArrayObject(), 'Header value must be a string or an array of strings.'], + ]; + } + + /** + * @dataProvider invalidWithHeaderProvider + */ + public function testWithInvalidHeader($header, $headerValue, $expectedMessage) + { + $r = new Response(); + $this->expectException('InvalidArgumentException', $expectedMessage); + $r->withHeader($header, $headerValue); + } + + public function invalidWithHeaderProvider() + { + return [ + [[], 'foo', 'Header must be a non empty string.'], + ['foo', [], 'Header value can not be an empty array.'], + ['', '', 'Header must be a non empty string.'], + ['foo', false, 'Header value must be a string or an array of strings.'], + [false, 'foo', 'Header must be a non empty string.'], + ['foo', new \stdClass(), 'Header value must be a string or an array of strings.'], + ['foo', new \ArrayObject(), 'Header value must be a string or an array of strings.'], + [new \stdClass(), 'foo', 'Header must be a non empty string.'], + ]; + } + public function testHeaderValuesAreTrimmed() { $r1 = new Response(200, ['OWS' => " \t \tFoo\t \t "]); @@ -251,16 +296,27 @@ public function testHeaderValuesAreTrimmed() } /** - * @dataProvider responseInitializedWithNonIntegerStatusCodeProvider + * @dataProvider nonIntegerStatusCodeProvider * @param mixed $invalidValues */ - public function testResponseInitializedWithNonIntegerStatusCodeProvider($invalidValues) + public function testConstructResponseWithNonIntegerStatusCode($invalidValues) { $this->expectException('InvalidArgumentException', 'Status code must be an integer value.'); new Response($invalidValues); } - public function responseInitializedWithNonIntegerStatusCodeProvider() + /** + * @dataProvider nonIntegerStatusCodeProvider + * @param mixed $invalidValues + */ + public function testResponseChangeStatusCodeWithNonInteger($invalidValues) + { + $response = new Response(); + $this->expectException('InvalidArgumentException', 'Status code must be an integer value.'); + $response->withStatus($invalidValues); + } + + public function nonIntegerStatusCodeProvider() { return [ ['whatever'], @@ -269,4 +325,33 @@ public function responseInitializedWithNonIntegerStatusCodeProvider() [new \stdClass()], ]; } + + /** + * @dataProvider invalidStatusCodeRangeProvider + * @param mixed $invalidValues + */ + public function testConstructResponseWithInvalidRangeStatusCode($invalidValues) + { + $this->expectException('InvalidArgumentException', 'Status code must be an integer value between 1xx and 5xx.'); + new Response($invalidValues); + } + + /** + * @dataProvider invalidStatusCodeRangeProvider + * @param mixed $invalidValues + */ + public function testResponseChangeStatusCodeWithWithInvalidRange($invalidValues) + { + $response = new Response(); + $this->expectException('InvalidArgumentException', 'Status code must be an integer value between 1xx and 5xx.'); + $response->withStatus($invalidValues); + } + + public function invalidStatusCodeRangeProvider() + { + return [ + [600], + [99], + ]; + } }