diff --git a/src/Driver/Http2Driver.php b/src/Driver/Http2Driver.php index 97b48589c..7964672e1 100644 --- a/src/Driver/Http2Driver.php +++ b/src/Driver/Http2Driver.php @@ -467,12 +467,7 @@ private function dispatchInternalRequest(Request $request, int $streamId, PsrUri $path .= "?" . $query; } - $headers = \array_merge([ - ":authority" => [$uri->getAuthority()], - ":scheme" => [$uri->getScheme()], - ":path" => [$path], - ":method" => ["GET"], - ], \array_intersect_key($request->getHeaders(), self::PUSH_PROMISE_INTERSECT), $headers); + $headers = \array_intersect_key($request->getHeaders(), self::PUSH_PROMISE_INTERSECT); $id = $this->localStreamId += 2; // Server initiated stream IDs must be even. $this->remoteStreamId = \max($id, $this->remoteStreamId); @@ -485,6 +480,13 @@ private function dispatchInternalRequest(Request $request, int $streamId, PsrUri Http2Stream::RESERVED | Http2Stream::REMOTE_CLOSED ); + $headers = \array_merge([ + ":authority" => [$uri->getAuthority()], + ":scheme" => [$uri->getScheme()], + ":path" => [$path], + ":method" => ["GET"], + ], $headers); + $headers = \pack("N", $id) . $this->table->encode($headers); if (\strlen($headers) >= $this->maxFrameSize) { @@ -1245,7 +1247,7 @@ private function parser(string $settings = null): \Generator throw new Http2StreamException("Shutting down", $id, self::REFUSED_STREAM); } - if (!isset($pseudo[":method"], $pseudo[":path"], $pseudo[":scheme"]) + if (!isset($pseudo[":method"], $pseudo[":path"], $pseudo[":scheme"], $pseudo[":authority"]) || isset($headers["connection"]) || $pseudo[":path"] === '' || (isset($headers["te"]) && \implode($headers["te"]) !== "trailers") @@ -1255,8 +1257,8 @@ private function parser(string $settings = null): \Generator $method = $pseudo[":method"]; $target = $pseudo[":path"]; - $scheme = $pseudo[":scheme"] ?? ($this->client->isEncrypted() ? "https" : "http"); - $host = $pseudo[":authority"] ?? ""; + $scheme = $pseudo[":scheme"]; + $host = $pseudo[":authority"]; $query = null; if (!\preg_match("#^([A-Z\d\.\-]+|\[[\d:]+\])(?::([1-9]\d*))?$#i", $host, $matches)) { diff --git a/src/Request.php b/src/Request.php index 3e6faf2a4..99a4bf4a7 100644 --- a/src/Request.php +++ b/src/Request.php @@ -169,6 +169,10 @@ public function setHeaders(array $headers): void */ public function setHeader(string $name, $value): void { + if (($name[0] ?? ":") === ":") { + throw new \Error("Header name cannot be empty or start with a colon (:)"); + } + parent::setHeader($name, $value); if (\stripos($name, "cookie") === 0) { @@ -186,6 +190,10 @@ public function setHeader(string $name, $value): void */ public function addHeader(string $name, $value): void { + if (($name[0] ?? ":") === ":") { + throw new \Error("Header name cannot be empty or start with a colon (:)"); + } + parent::addHeader($name, $value); if (\stripos($name, "cookie") === 0) { diff --git a/src/Response.php b/src/Response.php index d580e22bf..d88fedf41 100644 --- a/src/Response.php +++ b/src/Response.php @@ -13,8 +13,6 @@ final class Response extends Message { - - /** @var InputStream */ private $body; @@ -144,6 +142,10 @@ public function setHeaders(array $headers): void */ public function setHeader(string $name, $value): void { + if (($name[0] ?? ":") === ":") { + throw new \Error("Header name cannot be empty or start with a colon (:)"); + } + parent::setHeader($name, $value); if (\stripos($name, "set-cookie") === 0) { @@ -161,6 +163,10 @@ public function setHeader(string $name, $value): void */ public function addHeader(string $name, $value): void { + if (($name[0] ?? ":") === ":") { + throw new \Error("Header name cannot be empty or start with a colon (:)"); + } + parent::addHeader($name, $value); if (\stripos($name, "set-cookie") === 0) { @@ -354,7 +360,7 @@ public function push(string $url, array $headers = []): void { \assert((function (array $headers) { foreach ($headers as $name => $header) { - if ($name[0] === ":" || !\strncasecmp("host", $name, 4)) { + if (($name[0] ?? ":") === ":" || !\strncasecmp("host", $name, 4)) { return false; } } diff --git a/test/Driver/Http2DriverTest.php b/test/Driver/Http2DriverTest.php index 83a0049e5..8f40f53d8 100644 --- a/test/Driver/Http2DriverTest.php +++ b/test/Driver/Http2DriverTest.php @@ -6,6 +6,7 @@ use Amp\Delayed; use Amp\Emitter; use Amp\Http\HPack; +use Amp\Http\Message; use Amp\Http\Server\Driver\Client; use Amp\Http\Server\Driver\Http2Driver; use Amp\Http\Server\Driver\HttpDriver; @@ -84,11 +85,17 @@ public function testSimpleCases(string $msg, array $expectations) } } + /** @var Request $request */ $this->assertInstanceOf(Request::class, $request); - /** @var \Amp\Http\Server\Request $request */ - $body = Promise\wait($request->getBody()->buffer()); - $trailers = Promise\wait($request->getTrailers()); + $body = yield $request->getBody()->buffer(); + $trailers = $request->getTrailers(); + + if ($trailers !== null) { + $trailers = yield $trailers->awaitMessage(); + /** @var $trailers Message */ + $this->assertInstanceOf(Message::class, $trailers); + } $headers = $request->getHeaders(); foreach ($headers as $header => $value) { @@ -105,7 +112,7 @@ public function testSimpleCases(string $msg, array $expectations) $this->assertSame($expectations["port"] ?? 80, $request->getUri()->getPort() ?: $defaultPort, "uriPort mismatch"); $this->assertSame($expectations["host"], $request->getUri()->getHost(), "uriHost mismatch"); $this->assertSame($expectations["body"], $body, "body mismatch"); - $this->assertSame($expectations["trailers"] ?? [], $trailers->getHeaders()); + $this->assertSame($expectations["trailers"] ?? [], $trailers ? $trailers->getHeaders() : []); } } @@ -164,7 +171,7 @@ public function provideSimpleCases(): array ":scheme" => ["http"], ":method" => ["GET"], "te" => ["trailers"], - "trailers" => ["expires"], + "trailer" => ["expires"], ]; $msg = self::packFrame(\pack("N", 100), Http2Driver::WINDOW_UPDATE, Http2Driver::NOFLAG); @@ -179,7 +186,7 @@ public function provideSimpleCases(): array "method" => "GET", "uri" => "/foo", "host" => "localhost", - "headers" => ["trailers" => ["expires"]], + "headers" => ["te" => ["trailers"], "trailer" => ["expires"]], "body" => "ab", "trailers" => ["expires" => ["date"]], ]; @@ -579,7 +586,7 @@ function () { $emitter->emit("{data}"); - Promise\wait($writer); // Will throw if the writer is not complete. + yield $writer; // Will throw if the writer is not complete. } public function testPush() @@ -616,9 +623,7 @@ function () { $this->assertInstanceOf(Request::class, $requests[0]); - $writer = $driver->write($requests[0], $response); - - Promise\wait($writer); + yield $driver->write($requests[0], $response); $paths = ["/base", "/absolute/path", "/base/relative/path", "/base/path/with/query"];