From ed93337f5012ebc35114ef545df4d0c101d360ce Mon Sep 17 00:00:00 2001 From: Jake Booher Date: Fri, 3 Sep 2021 12:39:05 -0500 Subject: [PATCH 1/4] Adds support for web servers that send HTTP2 request protocol as HTTP/2.0. Fixes #71 Signed-off-by: Jake Booher --- src/MessageTrait.php | 2 +- test/MessageTraitTest.php | 22 +++++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/MessageTrait.php b/src/MessageTrait.php index e490bd3e..1c240d10 100644 --- a/src/MessageTrait.php +++ b/src/MessageTrait.php @@ -370,7 +370,7 @@ private function validateProtocolVersion($version) : void // HTTP/1 uses a "." numbering scheme to indicate // versions of the protocol, while HTTP/2 does not. - if (! preg_match('#^(1\.[01]|2)$#', $version)) { + if (! preg_match('#^(1\.[01]|2(\.0)?)$#', $version)) { throw new Exception\InvalidArgumentException(sprintf( 'Unsupported HTTP protocol version "%s" provided', $version diff --git a/test/MessageTraitTest.php b/test/MessageTraitTest.php index 38bc57ea..b480d6a4 100644 --- a/test/MessageTraitTest.php +++ b/test/MessageTraitTest.php @@ -50,7 +50,6 @@ public function invalidProtocolVersionProvider() '1-without-minor' => [ '1' ], '1-with-invalid-minor' => [ '1.2' ], '1-with-hotfix' => [ '1.2.3' ], - '2-with-minor' => [ '2.0' ], ]; } @@ -66,6 +65,27 @@ public function testWithProtocolVersionRaisesExceptionForInvalidVersion($version $request->withProtocolVersion($version); } + public function validProtocolVersionProvider() + { + return [ + '1.0' => [ '1.0' ], + '1.1' => [ '1.1' ], + '2' => [ '2' ], + '2.0' => [ '2.0' ], + ]; + } + + /** + * @dataProvider validProtocolVersionProvider + */ + public function testWithProtocolVersionDoesntRaiseExceptionForValidVersion($version) + { + $request = new Request(); + + $request->withProtocolVersion($version); + $this->addToAssertionCount(1); + } + public function testUsesStreamProvidedInConstructorAsBody() { $stream = $this->createMock(StreamInterface::class); From de26ca092b109d9a90d498b0fda93a0564aa120d Mon Sep 17 00:00:00 2001 From: Jake Booher Date: Fri, 17 Sep 2021 10:45:50 -0500 Subject: [PATCH 2/4] Add return and argument type hinting where possible --- test/MessageTraitTest.php | 76 +++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/test/MessageTraitTest.php b/test/MessageTraitTest.php index b480d6a4..b04a51cd 100644 --- a/test/MessageTraitTest.php +++ b/test/MessageTraitTest.php @@ -24,12 +24,12 @@ protected function setUp() : void $this->message = new Request(null, null, $this->createMock(StreamInterface::class)); } - public function testProtocolHasAcceptableDefault() + public function testProtocolHasAcceptableDefault(): void { $this->assertSame('1.1', $this->message->getProtocolVersion()); } - public function testProtocolMutatorReturnsCloneWithChanges() + public function testProtocolMutatorReturnsCloneWithChanges(): void { $message = $this->message->withProtocolVersion('1.0'); $this->assertNotSame($this->message, $message); @@ -37,7 +37,7 @@ public function testProtocolMutatorReturnsCloneWithChanges() } - public function invalidProtocolVersionProvider() + public function invalidProtocolVersionProvider(): array { return [ 'null' => [ null ], @@ -56,7 +56,7 @@ public function invalidProtocolVersionProvider() /** * @dataProvider invalidProtocolVersionProvider */ - public function testWithProtocolVersionRaisesExceptionForInvalidVersion($version) + public function testWithProtocolVersionRaisesExceptionForInvalidVersion($version): void { $request = new Request(); @@ -65,7 +65,7 @@ public function testWithProtocolVersionRaisesExceptionForInvalidVersion($version $request->withProtocolVersion($version); } - public function validProtocolVersionProvider() + public function validProtocolVersionProvider(): array { return [ '1.0' => [ '1.0' ], @@ -78,7 +78,7 @@ public function validProtocolVersionProvider() /** * @dataProvider validProtocolVersionProvider */ - public function testWithProtocolVersionDoesntRaiseExceptionForValidVersion($version) + public function testWithProtocolVersionDoesntRaiseExceptionForValidVersion(string $version): void { $request = new Request(); @@ -86,14 +86,14 @@ public function testWithProtocolVersionDoesntRaiseExceptionForValidVersion($vers $this->addToAssertionCount(1); } - public function testUsesStreamProvidedInConstructorAsBody() + public function testUsesStreamProvidedInConstructorAsBody(): void { $stream = $this->createMock(StreamInterface::class); $message = new Request(null, null, $stream); $this->assertSame($stream, $message->getBody()); } - public function testBodyMutatorReturnsCloneWithChanges() + public function testBodyMutatorReturnsCloneWithChanges(): void { $stream = $this->createMock(StreamInterface::class); $message = $this->message->withBody($stream); @@ -101,28 +101,28 @@ public function testBodyMutatorReturnsCloneWithChanges() $this->assertSame($stream, $message->getBody()); } - public function testGetHeaderReturnsHeaderValueAsArray() + public function testGetHeaderReturnsHeaderValueAsArray(): void { $message = $this->message->withHeader('X-Foo', ['Foo', 'Bar']); $this->assertNotSame($this->message, $message); $this->assertSame(['Foo', 'Bar'], $message->getHeader('X-Foo')); } - public function testGetHeaderLineReturnsHeaderValueAsCommaConcatenatedString() + public function testGetHeaderLineReturnsHeaderValueAsCommaConcatenatedString(): void { $message = $this->message->withHeader('X-Foo', ['Foo', 'Bar']); $this->assertNotSame($this->message, $message); $this->assertSame('Foo,Bar', $message->getHeaderLine('X-Foo')); } - public function testGetHeadersKeepsHeaderCaseSensitivity() + public function testGetHeadersKeepsHeaderCaseSensitivity(): void { $message = $this->message->withHeader('X-Foo', ['Foo', 'Bar']); $this->assertNotSame($this->message, $message); $this->assertSame([ 'X-Foo' => [ 'Foo', 'Bar' ] ], $message->getHeaders()); } - public function testGetHeadersReturnsCaseWithWhichHeaderFirstRegistered() + public function testGetHeadersReturnsCaseWithWhichHeaderFirstRegistered(): void { $message = $this->message ->withHeader('X-Foo', 'Foo') @@ -131,19 +131,19 @@ public function testGetHeadersReturnsCaseWithWhichHeaderFirstRegistered() $this->assertSame([ 'X-Foo' => [ 'Foo', 'Bar' ] ], $message->getHeaders()); } - public function testHasHeaderReturnsFalseIfHeaderIsNotPresent() + public function testHasHeaderReturnsFalseIfHeaderIsNotPresent(): void { $this->assertFalse($this->message->hasHeader('X-Foo')); } - public function testHasHeaderReturnsTrueIfHeaderIsPresent() + public function testHasHeaderReturnsTrueIfHeaderIsPresent(): void { $message = $this->message->withHeader('X-Foo', 'Foo'); $this->assertNotSame($this->message, $message); $this->assertTrue($message->hasHeader('X-Foo')); } - public function testAddHeaderAppendsToExistingHeader() + public function testAddHeaderAppendsToExistingHeader(): void { $message = $this->message->withHeader('X-Foo', 'Foo'); $this->assertNotSame($this->message, $message); @@ -152,7 +152,7 @@ public function testAddHeaderAppendsToExistingHeader() $this->assertSame('Foo,Bar', $message2->getHeaderLine('X-Foo')); } - public function testCanRemoveHeaders() + public function testCanRemoveHeaders(): void { $message = $this->message->withHeader('X-Foo', 'Foo'); $this->assertNotSame($this->message, $message); @@ -163,7 +163,7 @@ public function testCanRemoveHeaders() $this->assertFalse($message2->hasHeader('X-Foo')); } - public function testHeaderRemovalIsCaseInsensitive() + public function testHeaderRemovalIsCaseInsensitive(): void { $message = $this->message ->withHeader('X-Foo', 'Foo') @@ -181,7 +181,7 @@ public function testHeaderRemovalIsCaseInsensitive() $this->assertSame(0, count($headers)); } - public function invalidGeneralHeaderValues() + public function invalidGeneralHeaderValues(): array { return [ 'null' => [null], @@ -195,7 +195,7 @@ public function invalidGeneralHeaderValues() /** * @dataProvider invalidGeneralHeaderValues */ - public function testWithHeaderRaisesExceptionForInvalidNestedHeaderValue($value) + public function testWithHeaderRaisesExceptionForInvalidNestedHeaderValue($value): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Invalid header value'); @@ -203,7 +203,7 @@ public function testWithHeaderRaisesExceptionForInvalidNestedHeaderValue($value) $this->message->withHeader('X-Foo', [ $value ]); } - public function invalidHeaderValues() + public function invalidHeaderValues(): array { return [ 'null' => [null], @@ -216,7 +216,7 @@ public function invalidHeaderValues() /** * @dataProvider invalidHeaderValues */ - public function testWithHeaderRaisesExceptionForInvalidValueType($value) + public function testWithHeaderRaisesExceptionForInvalidValueType($value): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Invalid header value'); @@ -224,7 +224,7 @@ public function testWithHeaderRaisesExceptionForInvalidValueType($value) $this->message->withHeader('X-Foo', $value); } - public function testWithHeaderReplacesDifferentCapitalization() + public function testWithHeaderReplacesDifferentCapitalization(): void { $this->message = $this->message->withHeader('X-Foo', ['foo']); $new = $this->message->withHeader('X-foo', ['bar']); @@ -235,7 +235,7 @@ public function testWithHeaderReplacesDifferentCapitalization() /** * @dataProvider invalidGeneralHeaderValues */ - public function testWithAddedHeaderRaisesExceptionForNonStringNonArrayValue($value) + public function testWithAddedHeaderRaisesExceptionForNonStringNonArrayValue($value): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('must be a string'); @@ -243,7 +243,7 @@ public function testWithAddedHeaderRaisesExceptionForNonStringNonArrayValue($val $this->message->withAddedHeader('X-Foo', $value); } - public function testWithoutHeaderDoesNothingIfHeaderDoesNotExist() + public function testWithoutHeaderDoesNothingIfHeaderDoesNotExist(): void { $this->assertFalse($this->message->hasHeader('X-Foo')); $message = $this->message->withoutHeader('X-Foo'); @@ -251,24 +251,24 @@ public function testWithoutHeaderDoesNothingIfHeaderDoesNotExist() $this->assertFalse($message->hasHeader('X-Foo')); } - public function testHeadersInitialization() + public function testHeadersInitialization(): void { $headers = ['X-Foo' => ['bar']]; $message = new Request(null, null, 'php://temp', $headers); $this->assertSame($headers, $message->getHeaders()); } - public function testGetHeaderReturnsAnEmptyArrayWhenHeaderDoesNotExist() + public function testGetHeaderReturnsAnEmptyArrayWhenHeaderDoesNotExist(): void { $this->assertSame([], $this->message->getHeader('X-Foo-Bar')); } - public function testGetHeaderLineReturnsEmptyStringWhenHeaderDoesNotExist() + public function testGetHeaderLineReturnsEmptyStringWhenHeaderDoesNotExist(): void { $this->assertEmpty($this->message->getHeaderLine('X-Foo-Bar')); } - public function headersWithInjectionVectors() + public function headersWithInjectionVectors(): array { return [ 'name-with-cr' => ["X-Foo\r-Bar", 'value'], @@ -290,7 +290,7 @@ public function headersWithInjectionVectors() * @dataProvider headersWithInjectionVectors * @group ZF2015-04 */ - public function testDoesNotAllowCRLFInjectionWhenCallingWithHeader($name, $value) + public function testDoesNotAllowCRLFInjectionWhenCallingWithHeader($name, $value): void { $this->expectException(InvalidArgumentException::class); @@ -301,26 +301,26 @@ public function testDoesNotAllowCRLFInjectionWhenCallingWithHeader($name, $value * @dataProvider headersWithInjectionVectors * @group ZF2015-04 */ - public function testDoesNotAllowCRLFInjectionWhenCallingWithAddedHeader($name, $value) + public function testDoesNotAllowCRLFInjectionWhenCallingWithAddedHeader($name, $value): void { $this->expectException(InvalidArgumentException::class); $this->message->withAddedHeader($name, $value); } - public function testWithHeaderAllowsHeaderContinuations() + public function testWithHeaderAllowsHeaderContinuations(): void { $message = $this->message->withHeader('X-Foo-Bar', "value,\r\n second value"); $this->assertSame("value,\r\n second value", $message->getHeaderLine('X-Foo-Bar')); } - public function testWithAddedHeaderAllowsHeaderContinuations() + public function testWithAddedHeaderAllowsHeaderContinuations(): void { $message = $this->message->withAddedHeader('X-Foo-Bar', "value,\r\n second value"); $this->assertSame("value,\r\n second value", $message->getHeaderLine('X-Foo-Bar')); } - public function numericHeaderValuesProvider() + public function numericHeaderValuesProvider(): array { return [ 'integer' => [ 123 ], @@ -332,7 +332,7 @@ public function numericHeaderValuesProvider() * @dataProvider numericHeaderValuesProvider * @group 99 */ - public function testWithHeaderShouldAllowIntegersAndFloats($value) + public function testWithHeaderShouldAllowIntegersAndFloats(float $value): void { $message = $this->message ->withHeader('X-Test-Array', [ $value ]) @@ -344,7 +344,7 @@ public function testWithHeaderShouldAllowIntegersAndFloats($value) ], $message->getHeaders()); } - public function invalidHeaderValueTypes() + public function invalidHeaderValueTypes(): array { return [ 'null' => [null], @@ -354,7 +354,7 @@ public function invalidHeaderValueTypes() ]; } - public function invalidArrayHeaderValues() + public function invalidArrayHeaderValues(): array { $values = $this->invalidHeaderValueTypes(); $values['array'] = [['INVALID']]; @@ -365,7 +365,7 @@ public function invalidArrayHeaderValues() * @dataProvider invalidArrayHeaderValues * @group 99 */ - public function testWithHeaderShouldRaiseExceptionForInvalidHeaderValuesInArrays($value) + public function testWithHeaderShouldRaiseExceptionForInvalidHeaderValuesInArrays($value): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('header value type'); @@ -377,7 +377,7 @@ public function testWithHeaderShouldRaiseExceptionForInvalidHeaderValuesInArrays * @dataProvider invalidHeaderValueTypes * @group 99 */ - public function testWithHeaderShouldRaiseExceptionForInvalidHeaderScalarValues($value) + public function testWithHeaderShouldRaiseExceptionForInvalidHeaderScalarValues($value): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('header value type'); From 5e5cd22c22b052de2fd5fce3fa870eeda0367441 Mon Sep 17 00:00:00 2001 From: Jake Booher Date: Fri, 17 Sep 2021 10:51:25 -0500 Subject: [PATCH 3/4] Use assertEquals to verify protocol version on testWithProtocolVersionDoesntRaiseExceptionForValidVersion --- test/MessageTraitTest.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/MessageTraitTest.php b/test/MessageTraitTest.php index b04a51cd..3f4ca002 100644 --- a/test/MessageTraitTest.php +++ b/test/MessageTraitTest.php @@ -80,10 +80,8 @@ public function validProtocolVersionProvider(): array */ public function testWithProtocolVersionDoesntRaiseExceptionForValidVersion(string $version): void { - $request = new Request(); - - $request->withProtocolVersion($version); - $this->addToAssertionCount(1); + $request = (new Request())->withProtocolVersion($version); + $this->assertEquals($version, $request->getProtocolVersion()); } public function testUsesStreamProvidedInConstructorAsBody(): void From 899e5f51ae1d13d3526e65f9821e75878bab8add Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Wed, 22 Sep 2021 05:43:40 +0200 Subject: [PATCH 4/4] Completed type information for data-providers of `MessageTraitTest` Coincidentally, this reduces the psalm baseline for that file too. --- psalm-baseline.xml | 65 ++------------------------------------- test/MessageTraitTest.php | 30 +++++++++++++++++- 2 files changed, 32 insertions(+), 63 deletions(-) diff --git a/psalm-baseline.xml b/psalm-baseline.xml index cfa7df05..1c9bade4 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,5 +1,5 @@ - + $contents @@ -515,60 +515,7 @@ - - $name - $name - $value - $value - $value - $value - $value - $value - $value - $value - $version - - - headersWithInjectionVectors - invalidArrayHeaderValues - invalidGeneralHeaderValues - invalidHeaderValueTypes - invalidHeaderValues - invalidProtocolVersionProvider - numericHeaderValuesProvider - testAddHeaderAppendsToExistingHeader - testBodyMutatorReturnsCloneWithChanges - testCanRemoveHeaders - testDoesNotAllowCRLFInjectionWhenCallingWithAddedHeader - testDoesNotAllowCRLFInjectionWhenCallingWithHeader - testGetHeaderLineReturnsEmptyStringWhenHeaderDoesNotExist - testGetHeaderLineReturnsHeaderValueAsCommaConcatenatedString - testGetHeaderReturnsAnEmptyArrayWhenHeaderDoesNotExist - testGetHeaderReturnsHeaderValueAsArray - testGetHeadersKeepsHeaderCaseSensitivity - testGetHeadersReturnsCaseWithWhichHeaderFirstRegistered - testHasHeaderReturnsFalseIfHeaderIsNotPresent - testHasHeaderReturnsTrueIfHeaderIsPresent - testHeaderRemovalIsCaseInsensitive - testHeadersInitialization - testProtocolHasAcceptableDefault - testProtocolMutatorReturnsCloneWithChanges - testUsesStreamProvidedInConstructorAsBody - testWithAddedHeaderAllowsHeaderContinuations - testWithAddedHeaderRaisesExceptionForNonStringNonArrayValue - testWithHeaderAllowsHeaderContinuations - testWithHeaderRaisesExceptionForInvalidNestedHeaderValue - testWithHeaderRaisesExceptionForInvalidValueType - testWithHeaderReplacesDifferentCapitalization - testWithHeaderShouldAllowIntegersAndFloats - testWithHeaderShouldRaiseExceptionForInvalidHeaderScalarValues - testWithHeaderShouldRaiseExceptionForInvalidHeaderValuesInArrays - testWithProtocolVersionRaisesExceptionForInvalidVersion - testWithoutHeaderDoesNothingIfHeaderDoesNotExist - - - $name - $name + $value $value $value @@ -577,17 +524,11 @@ $value $version - + [ $value ] [ $value ] [ $value ] - - $values['array'] - - - $values - diff --git a/test/MessageTraitTest.php b/test/MessageTraitTest.php index 3f4ca002..bbb39cef 100644 --- a/test/MessageTraitTest.php +++ b/test/MessageTraitTest.php @@ -36,7 +36,7 @@ public function testProtocolMutatorReturnsCloneWithChanges(): void $this->assertSame('1.0', $message->getProtocolVersion()); } - + /** @return non-empty-array */ public function invalidProtocolVersionProvider(): array { return [ @@ -55,6 +55,8 @@ public function invalidProtocolVersionProvider(): array /** * @dataProvider invalidProtocolVersionProvider + * + * @param mixed $version */ public function testWithProtocolVersionRaisesExceptionForInvalidVersion($version): void { @@ -65,6 +67,7 @@ public function testWithProtocolVersionRaisesExceptionForInvalidVersion($version $request->withProtocolVersion($version); } + /** @return non-empty-array */ public function validProtocolVersionProvider(): array { return [ @@ -179,6 +182,7 @@ public function testHeaderRemovalIsCaseInsensitive(): void $this->assertSame(0, count($headers)); } + /** @return non-empty-array */ public function invalidGeneralHeaderValues(): array { return [ @@ -192,6 +196,8 @@ public function invalidGeneralHeaderValues(): array /** * @dataProvider invalidGeneralHeaderValues + * + * @param mixed $value */ public function testWithHeaderRaisesExceptionForInvalidNestedHeaderValue($value): void { @@ -201,6 +207,7 @@ public function testWithHeaderRaisesExceptionForInvalidNestedHeaderValue($value) $this->message->withHeader('X-Foo', [ $value ]); } + /** @return non-empty-array */ public function invalidHeaderValues(): array { return [ @@ -213,6 +220,8 @@ public function invalidHeaderValues(): array /** * @dataProvider invalidHeaderValues + * + * @param mixed $value */ public function testWithHeaderRaisesExceptionForInvalidValueType($value): void { @@ -232,6 +241,8 @@ public function testWithHeaderReplacesDifferentCapitalization(): void /** * @dataProvider invalidGeneralHeaderValues + * + * @param mixed $value */ public function testWithAddedHeaderRaisesExceptionForNonStringNonArrayValue($value): void { @@ -266,6 +277,7 @@ public function testGetHeaderLineReturnsEmptyStringWhenHeaderDoesNotExist(): voi $this->assertEmpty($this->message->getHeaderLine('X-Foo-Bar')); } + /** @return non-empty-array */ public function headersWithInjectionVectors(): array { return [ @@ -287,6 +299,9 @@ public function headersWithInjectionVectors(): array /** * @dataProvider headersWithInjectionVectors * @group ZF2015-04 + * + * @param string $name + * @param string|array{string} $value */ public function testDoesNotAllowCRLFInjectionWhenCallingWithHeader($name, $value): void { @@ -298,6 +313,9 @@ public function testDoesNotAllowCRLFInjectionWhenCallingWithHeader($name, $value /** * @dataProvider headersWithInjectionVectors * @group ZF2015-04 + * + * @param string $name + * @param string|array{string} $value */ public function testDoesNotAllowCRLFInjectionWhenCallingWithAddedHeader($name, $value): void { @@ -318,6 +336,7 @@ public function testWithAddedHeaderAllowsHeaderContinuations(): void $this->assertSame("value,\r\n second value", $message->getHeaderLine('X-Foo-Bar')); } + /** @return non-empty-array */ public function numericHeaderValuesProvider(): array { return [ @@ -329,6 +348,9 @@ public function numericHeaderValuesProvider(): array /** * @dataProvider numericHeaderValuesProvider * @group 99 + * + * @psalm-suppress InvalidScalarArgument this test explicitly verifies that pre-type-declaration + * implicit type conversion semantics still apply, for BC Compliance */ public function testWithHeaderShouldAllowIntegersAndFloats(float $value): void { @@ -342,6 +364,7 @@ public function testWithHeaderShouldAllowIntegersAndFloats(float $value): void ], $message->getHeaders()); } + /** @return non-empty-array */ public function invalidHeaderValueTypes(): array { return [ @@ -352,6 +375,7 @@ public function invalidHeaderValueTypes(): array ]; } + /** @return non-empty-array */ public function invalidArrayHeaderValues(): array { $values = $this->invalidHeaderValueTypes(); @@ -362,6 +386,8 @@ public function invalidArrayHeaderValues(): array /** * @dataProvider invalidArrayHeaderValues * @group 99 + * + * @param mixed $value */ public function testWithHeaderShouldRaiseExceptionForInvalidHeaderValuesInArrays($value): void { @@ -374,6 +400,8 @@ public function testWithHeaderShouldRaiseExceptionForInvalidHeaderValuesInArrays /** * @dataProvider invalidHeaderValueTypes * @group 99 + * + * @param mixed $value */ public function testWithHeaderShouldRaiseExceptionForInvalidHeaderScalarValues($value): void {