From 39313f008caf942fa79495e5a5e246e006bd88c9 Mon Sep 17 00:00:00 2001 From: joanhey Date: Thu, 7 May 2026 19:44:38 +0200 Subject: [PATCH 1/5] Disabled methods --- src/Protocols/Http.php | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/Protocols/Http.php b/src/Protocols/Http.php index 453bd8a3..1f1d692d 100644 --- a/src/Protocols/Http.php +++ b/src/Protocols/Http.php @@ -77,6 +77,13 @@ class Http */ protected const HTTP_413 = "HTTP/1.1 413 Payload Too Large\r\nConnection: close\r\n\r\n"; + /** + * Method Not Allowed. + * + * @var string + */ + protected const HTTP_405 = "HTTP/1.1 405 Method Not Allowed\r\nConnection: close\r\n\r\n"; + /** * Request Header Fields Too Large. * @@ -90,6 +97,13 @@ class Http protected const MAX_HEADER_LENGTH = 16384; + /** + * Disabled methods. + * + * @var string[] + */ + public static array $disabledMethods = ['TRACE', 'OPTIONS']; + /** * Get or set the request class name. * @@ -137,7 +151,7 @@ public static function input(string $buffer, TcpConnection $connection): int // Validate request line: METHOD SP origin-form SP HTTP/1.x $firstLineEnd = strpos($header, "\r\n"); if (!preg_match( - '~^(?-i:GET|POST|OPTIONS|HEAD|DELETE|PUT|PATCH) /[^\x00-\x20\x7f]* (?-i:HTTP)/1\.(?[0-9])$~', + '~^(?-i:GET|POST|OPTIONS|HEAD|DELETE|PUT|PATCH|TRACE) /[^\x00-\x20\x7f]* (?-i:HTTP)/1\.(?[0-9])$~', substr($header, 0, $firstLineEnd), $matches )) { @@ -145,6 +159,12 @@ public static function input(string $buffer, TcpConnection $connection): int return 0; } + // Check if the method is disabled + if (in_array($matches[0], static::$disabledMethods, true)) { + $connection->end(static::HTTP_405, true); + return 0; + } + // Parse headers $headers = []; $headerBody = substr($header, $firstLineEnd + 2, $crlfPos - $firstLineEnd - 2); From 7ba6e9b6edfc4cedf372f346fb56909b0c1cdb30 Mon Sep 17 00:00:00 2001 From: joanhey Date: Thu, 7 May 2026 19:58:56 +0200 Subject: [PATCH 2/5] Add tests for disabled http methods --- tests/Unit/Protocols/HttpTest.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/Unit/Protocols/HttpTest.php b/tests/Unit/Protocols/HttpTest.php index ca139f19..31f4773d 100644 --- a/tests/Unit/Protocols/HttpTest.php +++ b/tests/Unit/Protocols/HttpTest.php @@ -213,6 +213,21 @@ ]); }); +describe('Disabled Http methods return 405', function () { + it('rejects bad method', function (string $buffer) { + testWithConnectionEnd(function (TcpConnection $tcpConnection) use ($buffer) { + expect(Http::input($buffer, $tcpConnection))->toBe(0); + }, '405 Method Not Allowed'); + })->with([ + 'Method OPTIONS' => [ + "OPTIONS * HTTP/1.1\r\n\r\n", + ], + 'Method TRACE' => [ + "TRACE / HTTP/1.1\r\n\r\n", + ], + ]); +}); + describe('HTTP/1.0', function () { it('accepts minimal GET without Host header', function () { /** @var TcpConnection&\Mockery\MockInterface $tcpConnection */ From 80db4a79bccc3841842be2dbf0310fc35bd386d8 Mon Sep 17 00:00:00 2001 From: joanhey Date: Thu, 7 May 2026 20:07:25 +0200 Subject: [PATCH 3/5] Update tests --- tests/Unit/Protocols/HttpTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Unit/Protocols/HttpTest.php b/tests/Unit/Protocols/HttpTest.php index 31f4773d..9832ca18 100644 --- a/tests/Unit/Protocols/HttpTest.php +++ b/tests/Unit/Protocols/HttpTest.php @@ -214,15 +214,15 @@ }); describe('Disabled Http methods return 405', function () { - it('rejects bad method', function (string $buffer) { + it('rejects method', function (string $buffer) { testWithConnectionEnd(function (TcpConnection $tcpConnection) use ($buffer) { expect(Http::input($buffer, $tcpConnection))->toBe(0); }, '405 Method Not Allowed'); })->with([ - 'Method OPTIONS' => [ - "OPTIONS * HTTP/1.1\r\n\r\n", + 'OPTIONS' => [ + "OPTIONS / HTTP/1.1\r\n\r\n", ], - 'Method TRACE' => [ + 'TRACE' => [ "TRACE / HTTP/1.1\r\n\r\n", ], ]); From e3da354fd73f93155a7830a00cb661ede0330db8 Mon Sep 17 00:00:00 2001 From: joanhey Date: Thu, 7 May 2026 20:40:00 +0200 Subject: [PATCH 4/5] More tests --- src/Protocols/Http.php | 2 +- tests/Unit/Protocols/HttpTest.php | 19 +++++++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/Protocols/Http.php b/src/Protocols/Http.php index 1f1d692d..390c36a1 100644 --- a/src/Protocols/Http.php +++ b/src/Protocols/Http.php @@ -160,7 +160,7 @@ public static function input(string $buffer, TcpConnection $connection): int } // Check if the method is disabled - if (in_array($matches[0], static::$disabledMethods, true)) { + if (in_array($matches[0], static::$disabledMethods)) { $connection->end(static::HTTP_405, true); return 0; } diff --git a/tests/Unit/Protocols/HttpTest.php b/tests/Unit/Protocols/HttpTest.php index 9832ca18..10e698e7 100644 --- a/tests/Unit/Protocols/HttpTest.php +++ b/tests/Unit/Protocols/HttpTest.php @@ -219,12 +219,19 @@ expect(Http::input($buffer, $tcpConnection))->toBe(0); }, '405 Method Not Allowed'); })->with([ - 'OPTIONS' => [ - "OPTIONS / HTTP/1.1\r\n\r\n", - ], - 'TRACE' => [ - "TRACE / HTTP/1.1\r\n\r\n", - ], + 'OPTIONS' => ["OPTIONS / HTTP/1.1\r\n\r\n"], + 'TRACE' => ["TRACE / HTTP/1.1\r\n\r\n"], + ]); + + it('rejects manual method', function (string $buffer) { + testWithConnectionEnd(function (TcpConnection $tcpConnection) use ($buffer) { + Http::$disabledMethods = [...Http::$disabledMethods, 'DELETE', 'POST']; + expect(Http::input($buffer, $tcpConnection))->toBe(0); + }, '405 Method Not Allowed'); + })->with([ + 'DELETE' => ["DELETE / HTTP/1.1\r\n\r\n"], + 'POST' => ["POST / HTTP/1.1\r\n\r\n"], + 'TRACE' => ["TRACE / HTTP/1.1\r\n\r\n"], ]); }); From fafea8e6eaaf70e18f65f0c9ec90c21ae256a620 Mon Sep 17 00:00:00 2001 From: joanhey Date: Thu, 7 May 2026 20:47:55 +0200 Subject: [PATCH 5/5] Update tests --- tests/Unit/Protocols/HttpTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Unit/Protocols/HttpTest.php b/tests/Unit/Protocols/HttpTest.php index 10e698e7..32a218ab 100644 --- a/tests/Unit/Protocols/HttpTest.php +++ b/tests/Unit/Protocols/HttpTest.php @@ -225,7 +225,8 @@ it('rejects manual method', function (string $buffer) { testWithConnectionEnd(function (TcpConnection $tcpConnection) use ($buffer) { - Http::$disabledMethods = [...Http::$disabledMethods, 'DELETE', 'POST']; + Http::$disabledMethods[] = 'DELETE'; + Http::$disabledMethods[] = 'POST'; expect(Http::input($buffer, $tcpConnection))->toBe(0); }, '405 Method Not Allowed'); })->with([