From 5eaf3df2008c5d1aaad3ab661efc0e6f586bb46e Mon Sep 17 00:00:00 2001 From: Aaron Parecki Date: Sat, 3 Oct 2026 14:37:43 +0000 Subject: [PATCH] Only use HTTP/2 over TLS, and HTTP/1.1 for plain http Requesting CURL_HTTP_VERSION_2 makes curl send an h2c Upgrade request on plain http URLs, which some servers mishandle. PHP 7.3's built-in server drops the connection, which failed the curl tests on PHP 7.3. Use CURL_HTTP_VERSION_2TLS instead, curl's own default since 7.62: https still negotiates HTTP/2. Co-Authored-By: Claude Opus 5.5 (1M context) --- src/p3k/HTTP/Curl.php | 11 ++++++----- tests/CurlPinningTest.php | 9 +++++++++ tests/server/index.php | 4 ++++ 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/p3k/HTTP/Curl.php b/src/p3k/HTTP/Curl.php index 75cfbf2..7da1433 100644 --- a/src/p3k/HTTP/Curl.php +++ b/src/p3k/HTTP/Curl.php @@ -124,12 +124,13 @@ class Curl implements Transport, Pinnable { private function _http_version() { if (static::$_http_version !== null) return static::$_http_version; - if (defined('CURL_HTTP_VERSION_2')) { // PHP 7.0.7 - static::$_http_version = CURL_HTTP_VERSION_2; - } else if (defined('CURL_HTTP_VERSION_2_0')) { // Recommended in online articles - static::$_http_version = CURL_HTTP_VERSION_2_0; + // HTTP/2 over TLS only, and HTTP/1.1 for plain http, which is curl's own + // default since 7.62. Asking for HTTP/2 on plain http makes curl send an + // h2c Upgrade request, which some servers mishandle. + if (defined('CURL_HTTP_VERSION_2TLS')) { // PHP 7.0.7 + static::$_http_version = CURL_HTTP_VERSION_2TLS; } else { // Linked curl might be newer than PHP, send (current) INT value anyway. - static::$_http_version = 3; + static::$_http_version = 4; } return static::$_http_version; } diff --git a/tests/CurlPinningTest.php b/tests/CurlPinningTest.php index 15d65d6..9e025f1 100644 --- a/tests/CurlPinningTest.php +++ b/tests/CurlPinningTest.php @@ -78,4 +78,13 @@ class CurlPinningTest extends TestCase { $this->assertSame(0, $response['code']); $this->assertNotSame('', $response['error_description']); } + + // HTTP/2 is only negotiated over TLS. Plain http stays on HTTP/1.1 rather + // than sending an h2c Upgrade request, which some servers mishandle (PHP + // 7.3's built-in server drops the connection). + public function testPlainHttpDoesNotAttemptHttp2Upgrade() { + $response = (new HTTP('test'))->get('http://127.0.0.1:' . self::$port . '/?upgrade=1'); + $this->assertSame(200, $response['code']); + $this->assertSame('upgrade=', $response['body']); + } } diff --git a/tests/server/index.php b/tests/server/index.php index f8477ec..7e5cd46 100644 --- a/tests/server/index.php +++ b/tests/server/index.php @@ -4,4 +4,8 @@ if(($_GET['to'] ?? '') !== '') { header('Location: ' . $_GET['to'], true, 302); exit; } +if(isset($_GET['upgrade'])) { + echo 'upgrade=' . ($_SERVER['HTTP_UPGRADE'] ?? ''); + exit; +} echo 'host=' . ($_SERVER['HTTP_HOST'] ?? '');