PluginProbe
Media Cloud Sync / 1.4.1
Media Cloud Sync v1.4.1
1.4.1 1.4.0 1.3.12 1.3.11 1.3.10 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.2.0 1.2.10 1.2.11 1.2.12 1.2.13 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 All 35 releases
← All changes | includes/sdk/s3/GuzzleHttp/Handler/CurlFactory.php +206 -75 1.2.41.4.1 View file →
@@ -3,33 +3,53 @@
3 3 namespace Dudlewebs\WPMCS\s3\GuzzleHttp\Handler;
4 4
5 5 use Dudlewebs\WPMCS\s3\GuzzleHttp\Exception\ConnectException;
6 6 use Dudlewebs\WPMCS\s3\GuzzleHttp\Exception\RequestException;
7 +use Dudlewebs\WPMCS\s3\GuzzleHttp\Promise as P;
7 8 use Dudlewebs\WPMCS\s3\GuzzleHttp\Promise\FulfilledPromise;
8 -use Dudlewebs\WPMCS\s3\GuzzleHttp\Psr7;
9 +use Dudlewebs\WPMCS\s3\GuzzleHttp\Promise\PromiseInterface;
9 10 use Dudlewebs\WPMCS\s3\GuzzleHttp\Psr7\LazyOpenStream;
10 11 use Dudlewebs\WPMCS\s3\GuzzleHttp\TransferStats;
12 +use Dudlewebs\WPMCS\s3\GuzzleHttp\Utils;
11 13 use Dudlewebs\WPMCS\s3\Psr\Http\Message\RequestInterface;
14 +use Dudlewebs\WPMCS\s3\Psr\Http\Message\UriInterface;
12 15 /**
13 16 * Creates curl resources from a request
17 + *
18 + * @final
14 19 */
15 20 class CurlFactory implements CurlFactoryInterface
16 21 {
17 - const CURL_VERSION_STR = 'curl_version';
18 - const LOW_CURL_VERSION_NUMBER = '7.21.2';
19 - /** @var array */
22 + public const CURL_VERSION_STR = 'curl_version';
23 + /**
24 + * @deprecated
25 + */
26 + public const LOW_CURL_VERSION_NUMBER = '7.21.2';
27 + /**
28 + * @var resource[]|\CurlHandle[]
29 + */
20 30 private $handles = [];
21 - /** @var int Total number of idle handles to keep in cache */
31 + /**
32 + * @var int Total number of idle handles to keep in cache
33 + */
22 34 private $maxHandles;
23 35 /**
24 36 * @param int $maxHandles Maximum number of idle handles.
25 37 */
26 - public function __construct($maxHandles)
38 + public function __construct(int $maxHandles)
27 39 {
28 40 $this->maxHandles = $maxHandles;
29 41 }
30 - public function create(RequestInterface $request, array $options)
42 + public function create(RequestInterface $request, array $options) : EasyHandle
31 43 {
44 + $protocolVersion = $request->getProtocolVersion();
45 + if ('2' === $protocolVersion || '2.0' === $protocolVersion) {
46 + if (!self::supportsHttp2()) {
47 + throw new ConnectException('HTTP/2 is supported by the cURL handler, however libcurl is built without HTTP/2 support.', $request);
48 + }
49 + } elseif ('1.0' !== $protocolVersion && '1.1' !== $protocolVersion) {
50 + throw new ConnectException(\sprintf('HTTP/%s is not supported by the cURL handler.', $protocolVersion), $request);
51 + }
32 52 if (isset($options['curl']['body_as_string'])) {
33 53 $options['_body_as_string'] = $options['curl']['body_as_string'];
34 54 unset($options['curl']['body_as_string']);
35 55 }
@@ -49,14 +69,40 @@
49 69 $easy->handle = $this->handles ? \array_pop($this->handles) : \curl_init();
50 70 \curl_setopt_array($easy->handle, $conf);
51 71 return $easy;
52 72 }
53 - public function release(EasyHandle $easy)
73 + private static function supportsHttp2() : bool
54 74 {
75 + static $supportsHttp2 = null;
76 + if (null === $supportsHttp2) {
77 + $supportsHttp2 = self::supportsTls12() && \defined('CURL_VERSION_HTTP2') && \CURL_VERSION_HTTP2 & \curl_version()['features'];
78 + }
79 + return $supportsHttp2;
80 + }
81 + private static function supportsTls12() : bool
82 + {
83 + static $supportsTls12 = null;
84 + if (null === $supportsTls12) {
85 + $supportsTls12 = \CURL_SSLVERSION_TLSv1_2 & \curl_version()['features'];
86 + }
87 + return $supportsTls12;
88 + }
89 + private static function supportsTls13() : bool
90 + {
91 + static $supportsTls13 = null;
92 + if (null === $supportsTls13) {
93 + $supportsTls13 = \defined('CURL_SSLVERSION_TLSv1_3') && \CURL_SSLVERSION_TLSv1_3 & \curl_version()['features'];
94 + }
95 + return $supportsTls13;
96 + }
97 + public function release(EasyHandle $easy) : void
98 + {
55 99 $resource = $easy->handle;
56 100 unset($easy->handle);
57 101 if (\count($this->handles) >= $this->maxHandles) {
58 - \curl_close($resource);
102 + if (\PHP_VERSION_ID < 80000) {
103 + \curl_close($resource);
104 + }
59 105 } else {
60 106 // Remove all callback functions as they can hold onto references
61 107 // and are not cleaned up by curl_reset. Using curl_setopt_array
62 108 // does not work for some reason, so removing each one
@@ -72,15 +118,12 @@
72 118 /**
73 119 * Completes a cURL transaction, either returning a response promise or a
74 120 * rejected promise.
75 121 *
76 - * @param callable $handler
77 - * @param EasyHandle $easy
78 - * @param CurlFactoryInterface $factory Dictates how the handle is released
79 - *
80 - * @return \GuzzleHttp\Promise\PromiseInterface
122 + * @param callable(RequestInterface, array): PromiseInterface $handler
123 + * @param CurlFactoryInterface $factory Dictates how the handle is released
81 124 */
82 - public static function finish(callable $handler, EasyHandle $easy, CurlFactoryInterface $factory)
125 + public static function finish(callable $handler, EasyHandle $easy, CurlFactoryInterface $factory) : PromiseInterface
83 126 {
84 127 if (isset($easy->options['on_stats'])) {
85 128 self::invokeStats($easy);
86 129 }
@@ -95,20 +138,23 @@
95 138 $body->rewind();
96 139 }
97 140 return new FulfilledPromise($easy->response);
98 141 }
99 - private static function invokeStats(EasyHandle $easy)
142 + private static function invokeStats(EasyHandle $easy) : void
100 143 {
101 144 $curlStats = \curl_getinfo($easy->handle);
102 145 $curlStats['appconnect_time'] = \curl_getinfo($easy->handle, \CURLINFO_APPCONNECT_TIME);
103 146 $stats = new TransferStats($easy->request, $easy->response, $curlStats['total_time'], $easy->errno, $curlStats);
104 - \call_user_func($easy->options['on_stats'], $stats);
147 + $easy->options['on_stats']($stats);
105 148 }
106 - private static function finishError(callable $handler, EasyHandle $easy, CurlFactoryInterface $factory)
149 + /**
150 + * @param callable(RequestInterface, array): PromiseInterface $handler
151 + */
152 + private static function finishError(callable $handler, EasyHandle $easy, CurlFactoryInterface $factory) : PromiseInterface
107 153 {
108 154 // Get error information and release the handle to the factory.
109 155 $ctx = ['errno' => $easy->errno, 'error' => \curl_error($easy->handle), 'appconnect_time' => \curl_getinfo($easy->handle, \CURLINFO_APPCONNECT_TIME)] + \curl_getinfo($easy->handle);
110 - $ctx[self::CURL_VERSION_STR] = \curl_version()['version'];
156 + $ctx[self::CURL_VERSION_STR] = self::getCurlVersion();
111 157 $factory->release($easy);
112 158 // Retry when nothing is present or when curl failed to rewind.
113 159 if (empty($easy->options['_err_message']) && (!$easy->errno || $easy->errno == 65)) {
114 160 return self::retryFailedRewind($handler, $easy, $ctx);
@@ -114,42 +160,73 @@
114 160 return self::retryFailedRewind($handler, $easy, $ctx);
115 161 }
116 162 return self::createRejection($easy, $ctx);
117 163 }
118 - private static function createRejection(EasyHandle $easy, array $ctx)
164 + private static function getCurlVersion() : string
119 165 {
166 + static $curlVersion = null;
167 + if (null === $curlVersion) {
168 + $curlVersion = \curl_version()['version'];
169 + }
170 + return $curlVersion;
171 + }
172 + private static function createRejection(EasyHandle $easy, array $ctx) : PromiseInterface
173 + {
120 174 static $connectionErrors = [\CURLE_OPERATION_TIMEOUTED => \true, \CURLE_COULDNT_RESOLVE_HOST => \true, \CURLE_COULDNT_CONNECT => \true, \CURLE_SSL_CONNECT_ERROR => \true, \CURLE_GOT_NOTHING => \true];
175 + if ($easy->createResponseException) {
176 + return P\Create::rejectionFor(new RequestException('An error was encountered while creating the response', $easy->request, $easy->response, $easy->createResponseException, $ctx));
177 + }
121 178 // If an exception was encountered during the onHeaders event, then
122 179 // return a rejected promise that wraps that exception.
123 180 if ($easy->onHeadersException) {
124 - return \Dudlewebs\WPMCS\s3\GuzzleHttp\Promise\rejection_for(new RequestException('An error was encountered during the on_headers event', $easy->request, $easy->response, $easy->onHeadersException, $ctx));
181 + return P\Create::rejectionFor(new RequestException('An error was encountered during the on_headers event', $easy->request, $easy->response, $easy->onHeadersException, $ctx));
125 182 }
126 - if (\version_compare($ctx[self::CURL_VERSION_STR], self::LOW_CURL_VERSION_NUMBER)) {
127 - $message = \sprintf('cURL error %s: %s (%s)', $ctx['errno'], $ctx['error'], 'see https://curl.haxx.se/libcurl/c/libcurl-errors.html');
128 - } else {
129 - $message = \sprintf('cURL error %s: %s (%s) for %s', $ctx['errno'], $ctx['error'], 'see https://curl.haxx.se/libcurl/c/libcurl-errors.html', $easy->request->getUri());
183 + $uri = $easy->request->getUri();
184 + $sanitizedError = self::sanitizeCurlError($ctx['error'] ?? '', $uri);
185 + $message = \sprintf('cURL error %s: %s (%s)', $ctx['errno'], $sanitizedError, 'see https://curl.haxx.se/libcurl/c/libcurl-errors.html');
186 + if ('' !== $sanitizedError) {
187 + $redactedUriString = \Dudlewebs\WPMCS\s3\GuzzleHttp\Psr7\Utils::redactUserInfo($uri)->__toString();
188 + if ($redactedUriString !== '' && \false === \strpos($sanitizedError, $redactedUriString)) {
189 + $message .= \sprintf(' for %s', $redactedUriString);
190 + }
130 191 }
131 192 // Create a connection exception if it was a specific error code.
132 193 $error = isset($connectionErrors[$easy->errno]) ? new ConnectException($message, $easy->request, null, $ctx) : new RequestException($message, $easy->request, $easy->response, null, $ctx);
133 - return \Dudlewebs\WPMCS\s3\GuzzleHttp\Promise\rejection_for($error);
194 + return P\Create::rejectionFor($error);
134 195 }
135 - private function getDefaultConf(EasyHandle $easy)
196 + private static function sanitizeCurlError(string $error, UriInterface $uri) : string
136 197 {
137 - $conf = ['_headers' => $easy->request->getHeaders(), \CURLOPT_CUSTOMREQUEST => $easy->request->getMethod(), \CURLOPT_URL => (string) $easy->request->getUri()->withFragment(''), \CURLOPT_RETURNTRANSFER => \false, \CURLOPT_HEADER => \false, \CURLOPT_CONNECTTIMEOUT => 150];
198 + if ('' === $error) {
199 + return $error;
200 + }
201 + $baseUri = $uri->withQuery('')->withFragment('');
202 + $baseUriString = $baseUri->__toString();
203 + if ('' === $baseUriString) {
204 + return $error;
205 + }
206 + $redactedUriString = \Dudlewebs\WPMCS\s3\GuzzleHttp\Psr7\Utils::redactUserInfo($baseUri)->__toString();
207 + return \str_replace($baseUriString, $redactedUriString, $error);
208 + }
209 + /**
210 + * @return array<int|string, mixed>
211 + */
212 + private function getDefaultConf(EasyHandle $easy) : array
213 + {
214 + $conf = ['_headers' => $easy->request->getHeaders(), \CURLOPT_CUSTOMREQUEST => $easy->request->getMethod(), \CURLOPT_URL => (string) $easy->request->getUri()->withFragment(''), \CURLOPT_RETURNTRANSFER => \false, \CURLOPT_HEADER => \false, \CURLOPT_CONNECTTIMEOUT => 300];
138 215 if (\defined('CURLOPT_PROTOCOLS')) {
139 216 $conf[\CURLOPT_PROTOCOLS] = \CURLPROTO_HTTP | \CURLPROTO_HTTPS;
140 217 }
141 218 $version = $easy->request->getProtocolVersion();
142 - if ($version == 1.1) {
219 + if ('2' === $version || '2.0' === $version) {
220 + $conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_2_0;
221 + } elseif ('1.1' === $version) {
143 222 $conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_1;
144 - } elseif ($version == 2.0) {
145 - $conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_2_0;
146 223 } else {
147 224 $conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_0;
148 225 }
149 226 return $conf;
150 227 }
151 - private function applyMethod(EasyHandle $easy, array &$conf)
228 + private function applyMethod(EasyHandle $easy, array &$conf) : void
152 229 {
153 230 $body = $easy->request->getBody();
154 231 $size = $body->getSize();
155 232 if ($size === null || $size > 0) {
@@ -157,9 +234,9 @@
157 234 return;
158 235 }
159 236 $method = $easy->request->getMethod();
160 237 if ($method === 'PUT' || $method === 'POST') {
161 - // See http://tools.ietf.org/html/rfc7230#section-3.3.2
238 + // See https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.2
162 239 if (!$easy->request->hasHeader('Content-Length')) {
163 240 $conf[\CURLOPT_HTTPHEADER][] = 'Content-Length: 0';
164 241 }
165 242 } elseif ($method === 'HEAD') {
@@ -166,9 +243,9 @@
166 243 $conf[\CURLOPT_NOBODY] = \true;
167 244 unset($conf[\CURLOPT_WRITEFUNCTION], $conf[\CURLOPT_READFUNCTION], $conf[\CURLOPT_FILE], $conf[\CURLOPT_INFILE]);
168 245 }
169 246 }
170 - private function applyBody(RequestInterface $request, array $options, array &$conf)
247 + private function applyBody(RequestInterface $request, array $options, array &$conf) : void
171 248 {
172 249 $size = $request->hasHeader('Content-Length') ? (int) $request->getHeaderLine('Content-Length') : null;
173 250 // Send the body as a string if the size is less than 1MB OR if the
174 251 // [curl][body_as_string] request value is set.
@@ -186,9 +263,9 @@
186 263 $body = $request->getBody();
187 264 if ($body->isSeekable()) {
188 265 $body->rewind();
189 266 }
190 - $conf[\CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use($body) {
267 + $conf[\CURLOPT_READFUNCTION] = static function ($ch, $fd, $length) use($body) {
191 268 return $body->read($length);
192 269 };
193 270 }
194 271 // If the Expect header is not present, prevent curl from adding it
@@ -199,9 +276,9 @@
199 276 if (!$request->hasHeader('Content-Type')) {
200 277 $conf[\CURLOPT_HTTPHEADER][] = 'Content-Type:';
201 278 }
202 279 }
203 - private function applyHeaders(EasyHandle $easy, array &$conf)
280 + private function applyHeaders(EasyHandle $easy, array &$conf) : void
204 281 {
205 282 foreach ($conf['_headers'] as $name => $values) {
206 283 foreach ($values as $value) {
207 284 $value = (string) $value;
@@ -224,9 +301,9 @@
224 301 *
225 302 * @param string $name Case-insensitive header to remove
226 303 * @param array $options Array of options to modify
227 304 */
228 - private function removeHeader($name, array &$options)
305 + private function removeHeader(string $name, array &$options) : void
229 306 {
230 307 foreach (\array_keys($options['_headers']) as $key) {
231 308 if (!\strcasecmp($key, $name)) {
232 309 unset($options['_headers'][$key]);
@@ -233,9 +310,9 @@
233 310 return;
234 311 }
235 312 }
236 313 }
237 - private function applyHandlerOptions(EasyHandle $easy, array &$conf)
314 + private function applyHandlerOptions(EasyHandle $easy, array &$conf) : void
238 315 {
239 316 $options = $easy->options;
240 317 if (isset($options['verify'])) {
241 318 if ($options['verify'] === \false) {
@@ -251,9 +328,9 @@
251 328 throw new \InvalidArgumentException("SSL CA bundle not found: {$options['verify']}");
252 329 }
253 330 // If it's a directory or a link to a directory use CURLOPT_CAPATH.
254 331 // If not, it's probably a file, or a link to a file, so use CURLOPT_CAINFO.
255 - if (\is_dir($options['verify']) || \is_link($options['verify']) && \is_dir(\readlink($options['verify']))) {
332 + if (\is_dir($options['verify']) || \is_link($options['verify']) === \true && ($verifyLink = \readlink($options['verify'])) !== \false && \is_dir($verifyLink)) {
256 333 $conf[\CURLOPT_CAPATH] = $options['verify'];
257 334 } else {
258 335 $conf[\CURLOPT_CAINFO] = $options['verify'];
259 336 }
@@ -259,37 +336,40 @@
259 336 }
260 337 }
261 338 }
262 339 }
263 - if (!empty($options['decode_content'])) {
340 + if (!isset($options['curl'][\CURLOPT_ENCODING]) && !empty($options['decode_content'])) {
264 341 $accept = $easy->request->getHeaderLine('Accept-Encoding');
265 342 if ($accept) {
266 343 $conf[\CURLOPT_ENCODING] = $accept;
267 344 } else {
345 + // The empty string enables all available decoders and implicitly
346 + // sets a matching 'Accept-Encoding' header.
268 347 $conf[\CURLOPT_ENCODING] = '';
269 - // Don't let curl send the header over the wire
348 + // But as the user did not specify any encoding preference,
349 + // let's leave it up to server by preventing curl from sending
350 + // the header, which will be interpreted as 'Accept-Encoding: *'.
351 + // https://www.rfc-editor.org/rfc/rfc9110#field.accept-encoding
270 352 $conf[\CURLOPT_HTTPHEADER][] = 'Accept-Encoding:';
271 353 }
272 354 }
273 - if (isset($options['sink'])) {
274 - $sink = $options['sink'];
275 - if (!\is_string($sink)) {
276 - $sink = \Dudlewebs\WPMCS\s3\GuzzleHttp\Psr7\stream_for($sink);
277 - } elseif (!\is_dir(\dirname($sink))) {
278 - // Ensure that the directory exists before failing in curl.
279 - throw new \RuntimeException(\sprintf('Directory %s does not exist for sink value of %s', \dirname($sink), $sink));
280 - } else {
281 - $sink = new LazyOpenStream($sink, 'w+');
282 - }
283 - $easy->sink = $sink;
284 - $conf[\CURLOPT_WRITEFUNCTION] = function ($ch, $write) use($sink) {
285 - return $sink->write($write);
286 - };
355 + if (!isset($options['sink'])) {
356 + // Use a default temp stream if no sink was set.
357 + $options['sink'] = \Dudlewebs\WPMCS\s3\GuzzleHttp\Psr7\Utils::tryFopen('php://temp', 'w+');
358 + }
359 + $sink = $options['sink'];
360 + if (!\is_string($sink)) {
361 + $sink = \Dudlewebs\WPMCS\s3\GuzzleHttp\Psr7\Utils::streamFor($sink);
362 + } elseif (!\is_dir(\dirname($sink))) {
363 + // Ensure that the directory exists before failing in curl.
364 + throw new \RuntimeException(\sprintf('Directory %s does not exist for sink value of %s', \dirname($sink), $sink));
287 365 } else {
288 - // Use a default temp stream if no sink was set.
289 - $conf[\CURLOPT_FILE] = \fopen('php://temp', 'w+');
290 - $easy->sink = Psr7\stream_for($conf[\CURLOPT_FILE]);
366 + $sink = new LazyOpenStream($sink, 'w+');
291 367 }
368 + $easy->sink = $sink;
369 + $conf[\CURLOPT_WRITEFUNCTION] = static function ($ch, $write) use($sink) : int {
370 + return $sink->write($write);
371 + };
292 372 $timeoutRequiresNoSignal = \false;
293 373 if (isset($options['timeout'])) {
294 374 $timeoutRequiresNoSignal |= $options['timeout'] < 1;
295 375 $conf[\CURLOPT_TIMEOUT_MS] = $options['timeout'] * 1000;
@@ -315,14 +395,48 @@
315 395 } else {
316 396 $scheme = $easy->request->getUri()->getScheme();
317 397 if (isset($options['proxy'][$scheme])) {
318 398 $host = $easy->request->getUri()->getHost();
319 - if (!isset($options['proxy']['no']) || !\Dudlewebs\WPMCS\s3\GuzzleHttp\is_host_in_noproxy($host, $options['proxy']['no'])) {
399 + if (isset($options['proxy']['no']) && Utils::isHostInNoProxy($host, $options['proxy']['no'])) {
400 + unset($conf[\CURLOPT_PROXY]);
401 + } else {
320 402 $conf[\CURLOPT_PROXY] = $options['proxy'][$scheme];
321 403 }
322 404 }
323 405 }
324 406 }
407 + if (isset($options['crypto_method'])) {
408 + $protocolVersion = $easy->request->getProtocolVersion();
409 + // If HTTP/2, upgrade TLS 1.0 and 1.1 to 1.2
410 + if ('2' === $protocolVersion || '2.0' === $protocolVersion) {
411 + if (\STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT === $options['crypto_method'] || \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT === $options['crypto_method'] || \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT === $options['crypto_method']) {
412 + $conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_2;
413 + } elseif (\defined('STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT') && \STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT === $options['crypto_method']) {
414 + if (!self::supportsTls13()) {
415 + throw new \InvalidArgumentException('Invalid crypto_method request option: TLS 1.3 not supported by your version of cURL');
416 + }
417 + $conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_3;
418 + } else {
419 + throw new \InvalidArgumentException('Invalid crypto_method request option: unknown version provided');
420 + }
421 + } elseif (\STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT === $options['crypto_method']) {
422 + $conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_0;
423 + } elseif (\STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT === $options['crypto_method']) {
424 + $conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_1;
425 + } elseif (\STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT === $options['crypto_method']) {
426 + if (!self::supportsTls12()) {
427 + throw new \InvalidArgumentException('Invalid crypto_method request option: TLS 1.2 not supported by your version of cURL');
428 + }
429 + $conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_2;
430 + } elseif (\defined('STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT') && \STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT === $options['crypto_method']) {
431 + if (!self::supportsTls13()) {
432 + throw new \InvalidArgumentException('Invalid crypto_method request option: TLS 1.3 not supported by your version of cURL');
433 + }
434 + $conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_3;
435 + } else {
436 + throw new \InvalidArgumentException('Invalid crypto_method request option: unknown version provided');
437 + }
438 + }
325 439 if (isset($options['cert'])) {
326 440 $cert = $options['cert'];
327 441 if (\is_array($cert)) {
328 442 $conf[\CURLOPT_SSLCERTPASSWD] = $cert[1];
@@ -330,19 +444,25 @@
330 444 }
331 445 if (!\file_exists($cert)) {
332 446 throw new \InvalidArgumentException("SSL certificate not found: {$cert}");
333 447 }
448 + // OpenSSL (versions 0.9.3 and later) also support "P12" for PKCS#12-encoded files.
449 + // see https://curl.se/libcurl/c/CURLOPT_SSLCERTTYPE.html
450 + $ext = \pathinfo($cert, \PATHINFO_EXTENSION);
451 + if (\preg_match('#^(der|p12)$#i', $ext)) {
452 + $conf[\CURLOPT_SSLCERTTYPE] = \strtoupper($ext);
453 + }
334 454 $conf[\CURLOPT_SSLCERT] = $cert;
335 455 }
336 456 if (isset($options['ssl_key'])) {
337 457 if (\is_array($options['ssl_key'])) {
338 458 if (\count($options['ssl_key']) === 2) {
339 - list($sslKey, $conf[\CURLOPT_SSLKEYPASSWD]) = $options['ssl_key'];
459 + [$sslKey, $conf[\CURLOPT_SSLKEYPASSWD]] = $options['ssl_key'];
340 460 } else {
341 - list($sslKey) = $options['ssl_key'];
461 + [$sslKey] = $options['ssl_key'];
342 462 }
343 463 }
344 - $sslKey = isset($sslKey) ? $sslKey : $options['ssl_key'];
464 + $sslKey = $sslKey ?? $options['ssl_key'];
345 465 if (!\file_exists($sslKey)) {
346 466 throw new \InvalidArgumentException("SSL private key not found: {$sslKey}");
347 467 }
348 468 $conf[\CURLOPT_SSLKEY] = $sslKey;
@@ -352,19 +472,14 @@
352 472 if (!\is_callable($progress)) {
353 473 throw new \InvalidArgumentException('progress client option must be callable');
354 474 }
355 475 $conf[\CURLOPT_NOPROGRESS] = \false;
356 - $conf[\CURLOPT_PROGRESSFUNCTION] = function () use($progress) {
357 - $args = \func_get_args();
358 - // PHP 5.5 pushed the handle onto the start of the args
359 - if (\is_resource($args[0])) {
360 - \array_shift($args);
361 - }
362 - \call_user_func_array($progress, $args);
476 + $conf[\CURLOPT_PROGRESSFUNCTION] = static function ($resource, int $downloadSize, int $downloaded, int $uploadSize, int $uploaded) use($progress) {
477 + $progress($downloadSize, $downloaded, $uploadSize, $uploaded);
363 478 };
364 479 }
365 480 if (!empty($options['debug'])) {
366 - $conf[\CURLOPT_STDERR] = \Dudlewebs\WPMCS\s3\GuzzleHttp\debug_resource($options['debug']);
481 + $conf[\CURLOPT_STDERR] = Utils::debugResource($options['debug']);
367 482 $conf[\CURLOPT_VERBOSE] = \true;
368 483 }
369 484 }
370 485 /**
@@ -374,10 +489,12 @@
374 489 * "Connection died, retrying a fresh connect" error, tried to rewind the
375 490 * stream, and then encountered a "necessary data rewind wasn't possible"
376 491 * error, causing the request to be sent through curl_multi_info_read()
377 492 * without an error status.
493 + *
494 + * @param callable(RequestInterface, array): PromiseInterface $handler
378 495 */
379 - private static function retryFailedRewind(callable $handler, EasyHandle $easy, array $ctx)
496 + private static function retryFailedRewind(callable $handler, EasyHandle $easy, array $ctx) : PromiseInterface
380 497 {
381 498 try {
382 499 // Only rewind if the body has been read from.
383 500 $body = $easy->request->getBody();
@@ -394,13 +511,13 @@
394 511 } elseif ($easy->options['_curl_retries'] == 2) {
395 512 $ctx['error'] = 'The cURL request was retried 3 times ' . 'and did not succeed. The most likely reason for the failure ' . 'is that cURL was unable to rewind the body of the request ' . 'and subsequent retries resulted in the same error. Turn on ' . 'the debug option to see what went wrong. See ' . 'https://bugs.php.net/bug.php?id=47204 for more information.';
396 513 return self::createRejection($easy, $ctx);
397 514 } else {
398 - $easy->options['_curl_retries']++;
515 + ++$easy->options['_curl_retries'];
399 516 }
400 517 return $handler($easy->request, $easy->options);
401 518 }
402 - private function createHeaderFn(EasyHandle $easy)
519 + private function createHeaderFn(EasyHandle $easy) : callable
403 520 {
404 521 if (isset($easy->options['on_headers'])) {
405 522 $onHeaders = $easy->options['on_headers'];
406 523 if (!\is_callable($onHeaders)) {
@@ -408,13 +525,18 @@
408 525 }
409 526 } else {
410 527 $onHeaders = null;
411 528 }
412 - return function ($ch, $h) use($onHeaders, $easy, &$startingResponse) {
529 + return static function ($ch, $h) use($onHeaders, $easy, &$startingResponse) {
413 530 $value = \trim($h);
414 531 if ($value === '') {
415 532 $startingResponse = \true;
416 - $easy->createResponse();
533 + try {
534 + $easy->createResponse();
535 + } catch (\Exception $e) {
536 + $easy->createResponseException = $e;
537 + return -1;
538 + }
417 539 if ($onHeaders !== null) {
418 540 try {
419 541 $onHeaders($easy->response);
420 542 } catch (\Exception $e) {
@@ -431,6 +553,15 @@
431 553 $easy->headers[] = $value;
432 554 }
433 555 return \strlen($h);
434 556 };
557 + }
558 + public function __destruct()
559 + {
560 + foreach ($this->handles as $id => $handle) {
561 + if (\PHP_VERSION_ID < 80000) {
562 + \curl_close($handle);
563 + }
564 + unset($this->handles[$id]);
565 + }
435 566 }
436 567 }