Skip to content

Commit fcac7ba

Browse files
[7.9] Improve protocol version checks to provide useful feedback to users when trying to use an unsupported protocol version for their system (#3175)
* Improve protocol version checks to provide useful feedback to users when trying to use an unsupported protocol version for their system * Fixes * Update CurlFactory.php * Update phpstan-baseline.neon * Update phpstan-baseline.neon
1 parent 53f5a4e commit fcac7ba

File tree

4 files changed

+96
-15
lines changed

4 files changed

+96
-15
lines changed

phpstan-baseline.neon

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ parameters:
4040
count: 1
4141
path: src/Exception/RequestException.php
4242

43+
-
44+
message: "#^Cannot access offset 'features' on array\\|false\\.$#"
45+
count: 3
46+
path: src/Handler/CurlFactory.php
47+
4348
-
4449
message: "#^Cannot access offset 'version' on array\\|false\\.$#"
4550
count: 1

src/Handler/CurlFactory.php

Lines changed: 82 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,16 @@ public function __construct(int $maxHandles)
4646

4747
public function create(RequestInterface $request, array $options): EasyHandle
4848
{
49+
$protocolVersion = $request->getProtocolVersion();
50+
51+
if ('2' === $protocolVersion || '2.0' === $protocolVersion) {
52+
if (!self::supportsHttp2()) {
53+
throw new ConnectException('HTTP/2 is supported by the cURL handler, however libcurl is built without HTTP/2 support.', $request);
54+
}
55+
} elseif ('1.0' !== $protocolVersion && '1.1' !== $protocolVersion) {
56+
throw new ConnectException(sprintf('HTTP/%s is not supported by the cURL handler.', $protocolVersion), $request);
57+
}
58+
4959
if (isset($options['curl']['body_as_string'])) {
5060
$options['_body_as_string'] = $options['curl']['body_as_string'];
5161
unset($options['curl']['body_as_string']);
@@ -72,6 +82,42 @@ public function create(RequestInterface $request, array $options): EasyHandle
7282
return $easy;
7383
}
7484

85+
private static function supportsHttp2(): bool
86+
{
87+
static $supportsHttp2 = null;
88+
89+
if (null === $supportsHttp2) {
90+
$supportsHttp2 = self::supportsTls12()
91+
&& defined('CURL_VERSION_HTTP2')
92+
&& (\CURL_VERSION_HTTP2 & \curl_version()['features']);
93+
}
94+
95+
return $supportsHttp2;
96+
}
97+
98+
private static function supportsTls12(): bool
99+
{
100+
static $supportsTls12 = null;
101+
102+
if (null === $supportsTls12) {
103+
$supportsTls12 = \CURL_SSLVERSION_TLSv1_2 & \curl_version()['features'];
104+
}
105+
106+
return $supportsTls12;
107+
}
108+
109+
private static function supportsTls13(): bool
110+
{
111+
static $supportsTls13 = null;
112+
113+
if (null === $supportsTls13) {
114+
$supportsTls13 = defined('CURL_SSLVERSION_TLSv1_3')
115+
&& (\CURL_SSLVERSION_TLSv1_3 & \curl_version()['features']);
116+
}
117+
118+
return $supportsTls13;
119+
}
120+
75121
public function release(EasyHandle $easy): void
76122
{
77123
$resource = $easy->handle;
@@ -147,7 +193,7 @@ private static function finishError(callable $handler, EasyHandle $easy, CurlFac
147193
'error' => \curl_error($easy->handle),
148194
'appconnect_time' => \curl_getinfo($easy->handle, \CURLINFO_APPCONNECT_TIME),
149195
] + \curl_getinfo($easy->handle);
150-
$ctx[self::CURL_VERSION_STR] = \curl_version()['version'];
196+
$ctx[self::CURL_VERSION_STR] = self::getCurlVersion();
151197
$factory->release($easy);
152198

153199
// Retry when nothing is present or when curl failed to rewind.
@@ -158,6 +204,17 @@ private static function finishError(callable $handler, EasyHandle $easy, CurlFac
158204
return self::createRejection($easy, $ctx);
159205
}
160206

207+
private static function getCurlVersion(): string
208+
{
209+
static $curlVersion = null;
210+
211+
if (null === $curlVersion) {
212+
$curlVersion = \curl_version()['version'];
213+
}
214+
215+
return $curlVersion;
216+
}
217+
161218
private static function createRejection(EasyHandle $easy, array $ctx): PromiseInterface
162219
{
163220
static $connectionErrors = [
@@ -232,10 +289,11 @@ private function getDefaultConf(EasyHandle $easy): array
232289
}
233290

234291
$version = $easy->request->getProtocolVersion();
235-
if ($version == 1.1) {
236-
$conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_1;
237-
} elseif ($version == 2.0) {
292+
293+
if ('2' === $version || '2.0' === $version) {
238294
$conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_2_0;
295+
} elseif ('1.1' === $version) {
296+
$conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_1;
239297
} else {
240298
$conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_0;
241299
}
@@ -455,23 +513,35 @@ private function applyHandlerOptions(EasyHandle $easy, array &$conf): void
455513
}
456514

457515
if (isset($options['crypto_method'])) {
458-
if (\STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT === $options['crypto_method']) {
459-
if (!defined('CURL_SSLVERSION_TLSv1_0')) {
460-
throw new \InvalidArgumentException('Invalid crypto_method request option: TLS 1.0 not supported by your version of cURL');
516+
$protocolVersion = $easy->request->getProtocolVersion();
517+
518+
// If HTTP/2, upgrade TLS 1.0 and 1.1 to 1.2
519+
if ('2' === $protocolVersion || '2.0' === $protocolVersion) {
520+
if (
521+
\STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT === $options['crypto_method']
522+
|| \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT === $options['crypto_method']
523+
|| \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT === $options['crypto_method']
524+
) {
525+
$conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_2;
526+
} elseif (defined('STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT') && \STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT === $options['crypto_method']) {
527+
if (!self::supportsTls12()) {
528+
throw new \InvalidArgumentException('Invalid crypto_method request option: TLS 1.3 not supported by your version of cURL');
529+
}
530+
$conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_3;
531+
} else {
532+
throw new \InvalidArgumentException('Invalid crypto_method request option: unknown version provided');
461533
}
534+
} elseif (\STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT === $options['crypto_method']) {
462535
$conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_0;
463536
} elseif (\STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT === $options['crypto_method']) {
464-
if (!defined('CURL_SSLVERSION_TLSv1_1')) {
465-
throw new \InvalidArgumentException('Invalid crypto_method request option: TLS 1.1 not supported by your version of cURL');
466-
}
467537
$conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_1;
468538
} elseif (\STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT === $options['crypto_method']) {
469-
if (!defined('CURL_SSLVERSION_TLSv1_2')) {
539+
if (!self::supportsTls12()) {
470540
throw new \InvalidArgumentException('Invalid crypto_method request option: TLS 1.2 not supported by your version of cURL');
471541
}
472542
$conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_2;
473543
} elseif (defined('STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT') && \STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT === $options['crypto_method']) {
474-
if (!defined('CURL_SSLVERSION_TLSv1_3')) {
544+
if (!self::supportsTls13()) {
475545
throw new \InvalidArgumentException('Invalid crypto_method request option: TLS 1.3 not supported by your version of cURL');
476546
}
477547
$conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_3;

src/Handler/StreamHandler.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ public function __invoke(RequestInterface $request, array $options): PromiseInte
4040
\usleep($options['delay'] * 1000);
4141
}
4242

43+
$protocolVersion = $request->getProtocolVersion();
44+
45+
if ('1.0' !== $protocolVersion && '1.1' !== $protocolVersion) {
46+
throw new ConnectException(sprintf('HTTP/%s is not supported by the stream handler.', $protocolVersion), $request);
47+
}
48+
4349
$startTime = isset($options['on_stats']) ? Utils::currentTime() : null;
4450

4551
try {
@@ -273,7 +279,7 @@ private function createStream(RequestInterface $request, array $options)
273279

274280
// HTTP/1.1 streams using the PHP stream wrapper require a
275281
// Connection: close header
276-
if ($request->getProtocolVersion() == '1.1'
282+
if ($request->getProtocolVersion() === '1.1'
277283
&& !$request->hasHeader('Connection')
278284
) {
279285
$request = $request->withHeader('Connection', 'close');

src/PrepareBodyMiddleware.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ private function addExpectHeader(RequestInterface $request, array $options, arra
7676

7777
$expect = $options['expect'] ?? null;
7878

79-
// Return if disabled or if you're not using HTTP/1.1 or HTTP/2.0
80-
if ($expect === false || $request->getProtocolVersion() < 1.1) {
79+
// Return if disabled or using HTTP/1.0
80+
if ($expect === false || $request->getProtocolVersion() === '1.0') {
8181
return;
8282
}
8383

0 commit comments

Comments
 (0)