PluginProbe
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell / 2.8.16
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell v2.8.16
3.13.3 3.13.2 3.13.1 3.13.0 3.12.13 3.12.12 3.12.11 3.12.10 3.12.9 3.12.8 3.12.7 3.12.6 3.12.5 3.12.4 3.12.3 3.12.1 3.12.2 3.12.0 3.11.1 3.11.0 3.10.9 3.10.8 3.10.7 3.10.6 2.8.16 All 261 releases
wpfunnels / vendor / symfony / http-client / CurlHttpClient.php

CurlHttpClient.php in WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell 2.8.16, at vendor/symfony/http-client/CurlHttpClient.php

552 lines 23.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <[email protected]>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12 namespace Symfony\Component\HttpClient;
13
14 use Psr\Log\LoggerAwareInterface;
15 use Psr\Log\LoggerInterface;
16 use Symfony\Component\HttpClient\Exception\InvalidArgumentException;
17 use Symfony\Component\HttpClient\Exception\TransportException;
18 use Symfony\Component\HttpClient\Internal\CurlClientState;
19 use Symfony\Component\HttpClient\Internal\PushedResponse;
20 use Symfony\Component\HttpClient\Response\CurlResponse;
21 use Symfony\Component\HttpClient\Response\ResponseStream;
22 use Symfony\Contracts\HttpClient\HttpClientInterface;
23 use Symfony\Contracts\HttpClient\ResponseInterface;
24 use Symfony\Contracts\HttpClient\ResponseStreamInterface;
25 use Symfony\Contracts\Service\ResetInterface;
26
27 /**
28 * A performant implementation of the HttpClientInterface contracts based on the curl extension.
29 *
30 * This provides fully concurrent HTTP requests, with transparent
31 * HTTP/2 push when a curl version that supports it is installed.
32 *
33 * @author Nicolas Grekas <[email protected]>
34 */
35 final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface, ResetInterface
36 {
37 use HttpClientTrait;
38
39 private $defaultOptions = self::OPTIONS_DEFAULTS + [
40 'auth_ntlm' => null, // array|string - an array containing the username as first value, and optionally the
41 // password as the second one; or string like username:password - enabling NTLM auth
42 'extra' => [
43 'curl' => [], // A list of extra curl options indexed by their corresponding CURLOPT_*
44 ],
45 ];
46 private static $emptyDefaults = self::OPTIONS_DEFAULTS + ['auth_ntlm' => null];
47
48 /**
49 * @var LoggerInterface|null
50 */
51 private $logger;
52
53 /**
54 * An internal object to share state between the client and its responses.
55 *
56 * @var CurlClientState
57 */
58 private $multi;
59
60 /**
61 * @param array $defaultOptions Default request's options
62 * @param int $maxHostConnections The maximum number of connections to a single host
63 * @param int $maxPendingPushes The maximum number of pushed responses to accept in the queue
64 *
65 * @see HttpClientInterface::OPTIONS_DEFAULTS for available options
66 */
67 public function __construct(array $defaultOptions = [], int $maxHostConnections = 6, int $maxPendingPushes = 50)
68 {
69 if (!\extension_loaded('curl')) {
70 throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\CurlHttpClient" as the "curl" extension is not installed.');
71 }
72
73 $this->defaultOptions['buffer'] = $this->defaultOptions['buffer'] ?? \Closure::fromCallable([__CLASS__, 'shouldBuffer']);
74
75 if ($defaultOptions) {
76 [, $this->defaultOptions] = self::prepareRequest(null, null, $defaultOptions, $this->defaultOptions);
77 }
78
79 $this->multi = new CurlClientState($maxHostConnections, $maxPendingPushes);
80 }
81
82 public function setLogger(LoggerInterface $logger): void
83 {
84 $this->logger = $this->multi->logger = $logger;
85 }
86
87 /**
88 * @see HttpClientInterface::OPTIONS_DEFAULTS for available options
89 *
90 * {@inheritdoc}
91 */
92 public function request(string $method, string $url, array $options = []): ResponseInterface
93 {
94 [$url, $options] = self::prepareRequest($method, $url, $options, $this->defaultOptions);
95 $scheme = $url['scheme'];
96 $authority = $url['authority'];
97 $host = parse_url($authority, \PHP_URL_HOST);
98 $proxy = self::getProxyUrl($options['proxy'], $url);
99 $url = implode('', $url);
100
101 if (!isset($options['normalized_headers']['user-agent'])) {
102 $options['headers'][] = 'User-Agent: Symfony HttpClient/Curl';
103 }
104
105 $curlopts = [
106 \CURLOPT_URL => $url,
107 \CURLOPT_TCP_NODELAY => true,
108 \CURLOPT_PROTOCOLS => \CURLPROTO_HTTP | \CURLPROTO_HTTPS,
109 \CURLOPT_REDIR_PROTOCOLS => \CURLPROTO_HTTP | \CURLPROTO_HTTPS,
110 \CURLOPT_FOLLOWLOCATION => true,
111 \CURLOPT_MAXREDIRS => 0 < $options['max_redirects'] ? $options['max_redirects'] : 0,
112 \CURLOPT_COOKIEFILE => '', // Keep track of cookies during redirects
113 \CURLOPT_TIMEOUT => 0,
114 \CURLOPT_PROXY => $proxy,
115 \CURLOPT_NOPROXY => $options['no_proxy'] ?? $_SERVER['no_proxy'] ?? $_SERVER['NO_PROXY'] ?? '',
116 \CURLOPT_SSL_VERIFYPEER => $options['verify_peer'],
117 \CURLOPT_SSL_VERIFYHOST => $options['verify_host'] ? 2 : 0,
118 \CURLOPT_CAINFO => $options['cafile'],
119 \CURLOPT_CAPATH => $options['capath'],
120 \CURLOPT_SSL_CIPHER_LIST => $options['ciphers'],
121 \CURLOPT_SSLCERT => $options['local_cert'],
122 \CURLOPT_SSLKEY => $options['local_pk'],
123 \CURLOPT_KEYPASSWD => $options['passphrase'],
124 \CURLOPT_CERTINFO => $options['capture_peer_cert_chain'],
125 ];
126
127 if (1.0 === (float) $options['http_version']) {
128 $curlopts[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_0;
129 } elseif (1.1 === (float) $options['http_version']) {
130 $curlopts[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_1;
131 } elseif (\defined('CURL_VERSION_HTTP2') && (\CURL_VERSION_HTTP2 & CurlClientState::$curlVersion['features']) && ('https:' === $scheme || 2.0 === (float) $options['http_version'])) {
132 $curlopts[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_2_0;
133 }
134
135 if (isset($options['auth_ntlm'])) {
136 $curlopts[\CURLOPT_HTTPAUTH] = \CURLAUTH_NTLM;
137 $curlopts[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_1;
138
139 if (\is_array($options['auth_ntlm'])) {
140 $count = \count($options['auth_ntlm']);
141 if ($count <= 0 || $count > 2) {
142 throw new InvalidArgumentException(sprintf('Option "auth_ntlm" must contain 1 or 2 elements, %d given.', $count));
143 }
144
145 $options['auth_ntlm'] = implode(':', $options['auth_ntlm']);
146 }
147
148 if (!\is_string($options['auth_ntlm'])) {
149 throw new InvalidArgumentException(sprintf('Option "auth_ntlm" must be a string or an array, "%s" given.', get_debug_type($options['auth_ntlm'])));
150 }
151
152 $curlopts[\CURLOPT_USERPWD] = $options['auth_ntlm'];
153 }
154
155 if (!\ZEND_THREAD_SAFE) {
156 $curlopts[\CURLOPT_DNS_USE_GLOBAL_CACHE] = false;
157 }
158
159 if (\defined('CURLOPT_HEADEROPT') && \defined('CURLHEADER_SEPARATE')) {
160 $curlopts[\CURLOPT_HEADEROPT] = \CURLHEADER_SEPARATE;
161 }
162
163 // curl's resolve feature varies by host:port but ours varies by host only, let's handle this with our own DNS map
164 if (isset($this->multi->dnsCache->hostnames[$host])) {
165 $options['resolve'] += [$host => $this->multi->dnsCache->hostnames[$host]];
166 }
167
168 if ($options['resolve'] || $this->multi->dnsCache->evictions) {
169 // First reset any old DNS cache entries then add the new ones
170 $resolve = $this->multi->dnsCache->evictions;
171 $this->multi->dnsCache->evictions = [];
172 $port = parse_url($authority, \PHP_URL_PORT) ?: ('http:' === $scheme ? 80 : 443);
173
174 if ($resolve && 0x072A00 > CurlClientState::$curlVersion['version_number']) {
175 // DNS cache removals require curl 7.42 or higher
176 $this->multi->reset();
177 }
178
179 foreach ($options['resolve'] as $host => $ip) {
180 $resolve[] = null === $ip ? "-$host:$port" : "$host:$port:$ip";
181 $this->multi->dnsCache->hostnames[$host] = $ip;
182 $this->multi->dnsCache->removals["-$host:$port"] = "-$host:$port";
183 }
184
185 $curlopts[\CURLOPT_RESOLVE] = $resolve;
186 }
187
188 if ('POST' === $method) {
189 // Use CURLOPT_POST to have browser-like POST-to-GET redirects for 301, 302 and 303
190 $curlopts[\CURLOPT_POST] = true;
191 } elseif ('HEAD' === $method) {
192 $curlopts[\CURLOPT_NOBODY] = true;
193 } else {
194 $curlopts[\CURLOPT_CUSTOMREQUEST] = $method;
195 }
196
197 if ('\\' !== \DIRECTORY_SEPARATOR && $options['timeout'] < 1) {
198 $curlopts[\CURLOPT_NOSIGNAL] = true;
199 }
200
201 if (\extension_loaded('zlib') && !isset($options['normalized_headers']['accept-encoding'])) {
202 $options['headers'][] = 'Accept-Encoding: gzip'; // Expose only one encoding, some servers mess up when more are provided
203 }
204 $body = $options['body'];
205
206 foreach ($options['headers'] as $i => $header) {
207 if (\is_string($body) && '' !== $body && 0 === stripos($header, 'Content-Length: ')) {
208 // Let curl handle Content-Length headers
209 unset($options['headers'][$i]);
210 continue;
211 }
212 if (':' === $header[-2] && \strlen($header) - 2 === strpos($header, ': ')) {
213 // curl requires a special syntax to send empty headers
214 $curlopts[\CURLOPT_HTTPHEADER][] = substr_replace($header, ';', -2);
215 } else {
216 $curlopts[\CURLOPT_HTTPHEADER][] = $header;
217 }
218 }
219
220 // Prevent curl from sending its default Accept and Expect headers
221 foreach (['accept', 'expect'] as $header) {
222 if (!isset($options['normalized_headers'][$header][0])) {
223 $curlopts[\CURLOPT_HTTPHEADER][] = $header.':';
224 }
225 }
226
227 if (!\is_string($body)) {
228 if (\is_resource($body)) {
229 $curlopts[\CURLOPT_INFILE] = $body;
230 } else {
231 $eof = false;
232 $buffer = '';
233 $curlopts[\CURLOPT_READFUNCTION] = static function ($ch, $fd, $length) use ($body, &$buffer, &$eof) {
234 return self::readRequestBody($length, $body, $buffer, $eof);
235 };
236 }
237
238 if (isset($options['normalized_headers']['content-length'][0])) {
239 $curlopts[\CURLOPT_INFILESIZE] = (int) substr($options['normalized_headers']['content-length'][0], \strlen('Content-Length: '));
240 }
241 if (!isset($options['normalized_headers']['transfer-encoding'])) {
242 $curlopts[\CURLOPT_HTTPHEADER][] = 'Transfer-Encoding:'.(isset($curlopts[\CURLOPT_INFILESIZE]) ? '' : ' chunked');
243 }
244
245 if ('POST' !== $method) {
246 $curlopts[\CURLOPT_UPLOAD] = true;
247
248 if (!isset($options['normalized_headers']['content-type']) && 0 !== ($curlopts[\CURLOPT_INFILESIZE] ?? null)) {
249 $curlopts[\CURLOPT_HTTPHEADER][] = 'Content-Type: application/x-www-form-urlencoded';
250 }
251 }
252 } elseif ('' !== $body || 'POST' === $method) {
253 $curlopts[\CURLOPT_POSTFIELDS] = $body;
254 }
255
256 if ($options['peer_fingerprint']) {
257 if (!isset($options['peer_fingerprint']['pin-sha256'])) {
258 throw new TransportException(__CLASS__.' supports only "pin-sha256" fingerprints.');
259 }
260
261 $curlopts[\CURLOPT_PINNEDPUBLICKEY] = 'sha256//'.implode(';sha256//', $options['peer_fingerprint']['pin-sha256']);
262 }
263
264 if ($options['bindto']) {
265 if (file_exists($options['bindto'])) {
266 $curlopts[\CURLOPT_UNIX_SOCKET_PATH] = $options['bindto'];
267 } elseif (!str_starts_with($options['bindto'], 'if!') && preg_match('/^(.*):(\d+)$/', $options['bindto'], $matches)) {
268 $curlopts[\CURLOPT_INTERFACE] = $matches[1];
269 $curlopts[\CURLOPT_LOCALPORT] = $matches[2];
270 } else {
271 $curlopts[\CURLOPT_INTERFACE] = $options['bindto'];
272 }
273 }
274
275 if (0 < $options['max_duration']) {
276 $curlopts[\CURLOPT_TIMEOUT_MS] = 1000 * $options['max_duration'];
277 }
278
279 if (!empty($options['extra']['curl']) && \is_array($options['extra']['curl'])) {
280 $this->validateExtraCurlOptions($options['extra']['curl']);
281 $curlopts += $options['extra']['curl'];
282 }
283
284 if ($pushedResponse = $this->multi->pushedResponses[$url] ?? null) {
285 unset($this->multi->pushedResponses[$url]);
286
287 if (self::acceptPushForRequest($method, $options, $pushedResponse)) {
288 $this->logger && $this->logger->debug(sprintf('Accepting pushed response: "%s %s"', $method, $url));
289
290 // Reinitialize the pushed response with request's options
291 $ch = $pushedResponse->handle;
292 $pushedResponse = $pushedResponse->response;
293 $pushedResponse->__construct($this->multi, $url, $options, $this->logger);
294 } else {
295 $this->logger && $this->logger->debug(sprintf('Rejecting pushed response: "%s"', $url));
296 $pushedResponse = null;
297 }
298 }
299
300 if (!$pushedResponse) {
301 $ch = curl_init();
302 $this->logger && $this->logger->info(sprintf('Request: "%s %s"', $method, $url));
303 $curlopts += [\CURLOPT_SHARE => $this->multi->share];
304 }
305
306 foreach ($curlopts as $opt => $value) {
307 if (null !== $value && !curl_setopt($ch, $opt, $value) && \CURLOPT_CERTINFO !== $opt && (!\defined('CURLOPT_HEADEROPT') || \CURLOPT_HEADEROPT !== $opt)) {
308 $constantName = $this->findConstantName($opt);
309 throw new TransportException(sprintf('Curl option "%s" is not supported.', $constantName ?? $opt));
310 }
311 }
312
313 return $pushedResponse ?? new CurlResponse($this->multi, $ch, $options, $this->logger, $method, self::createRedirectResolver($options, $host), CurlClientState::$curlVersion['version_number']);
314 }
315
316 /**
317 * {@inheritdoc}
318 */
319 public function stream($responses, float $timeout = null): ResponseStreamInterface
320 {
321 if ($responses instanceof CurlResponse) {
322 $responses = [$responses];
323 } elseif (!is_iterable($responses)) {
324 throw new \TypeError(sprintf('"%s()" expects parameter 1 to be an iterable of CurlResponse objects, "%s" given.', __METHOD__, get_debug_type($responses)));
325 }
326
327 if (\is_resource($this->multi->handle) || $this->multi->handle instanceof \CurlMultiHandle) {
328 $active = 0;
329 while (\CURLM_CALL_MULTI_PERFORM === curl_multi_exec($this->multi->handle, $active)) {
330 }
331 }
332
333 return new ResponseStream(CurlResponse::stream($responses, $timeout));
334 }
335
336 public function reset()
337 {
338 $this->multi->reset();
339 }
340
341 /**
342 * Accepts pushed responses only if their headers related to authentication match the request.
343 */
344 private static function acceptPushForRequest(string $method, array $options, PushedResponse $pushedResponse): bool
345 {
346 if ('' !== $options['body'] || $method !== $pushedResponse->requestHeaders[':method'][0]) {
347 return false;
348 }
349
350 foreach (['proxy', 'no_proxy', 'bindto', 'local_cert', 'local_pk'] as $k) {
351 if ($options[$k] !== $pushedResponse->parentOptions[$k]) {
352 return false;
353 }
354 }
355
356 foreach (['authorization', 'cookie', 'range', 'proxy-authorization'] as $k) {
357 $normalizedHeaders = $options['normalized_headers'][$k] ?? [];
358 foreach ($normalizedHeaders as $i => $v) {
359 $normalizedHeaders[$i] = substr($v, \strlen($k) + 2);
360 }
361
362 if (($pushedResponse->requestHeaders[$k] ?? []) !== $normalizedHeaders) {
363 return false;
364 }
365 }
366
367 return true;
368 }
369
370 /**
371 * Wraps the request's body callback to allow it to return strings longer than curl requested.
372 */
373 private static function readRequestBody(int $length, \Closure $body, string &$buffer, bool &$eof): string
374 {
375 if (!$eof && \strlen($buffer) < $length) {
376 if (!\is_string($data = $body($length))) {
377 throw new TransportException(sprintf('The return value of the "body" option callback must be a string, "%s" returned.', get_debug_type($data)));
378 }
379
380 $buffer .= $data;
381 $eof = '' === $data;
382 }
383
384 $data = substr($buffer, 0, $length);
385 $buffer = substr($buffer, $length);
386
387 return $data;
388 }
389
390 /**
391 * Resolves relative URLs on redirects and deals with authentication headers.
392 *
393 * Work around CVE-2018-1000007: Authorization and Cookie headers should not follow redirects - fixed in Curl 7.64
394 */
395 private static function createRedirectResolver(array $options, string $host): \Closure
396 {
397 $redirectHeaders = [];
398 if (0 < $options['max_redirects']) {
399 $redirectHeaders['host'] = $host;
400 $redirectHeaders['with_auth'] = $redirectHeaders['no_auth'] = array_filter($options['headers'], static function ($h) {
401 return 0 !== stripos($h, 'Host:');
402 });
403
404 if (isset($options['normalized_headers']['authorization'][0]) || isset($options['normalized_headers']['cookie'][0])) {
405 $redirectHeaders['no_auth'] = array_filter($options['headers'], static function ($h) {
406 return 0 !== stripos($h, 'Authorization:') && 0 !== stripos($h, 'Cookie:');
407 });
408 }
409 }
410
411 return static function ($ch, string $location, bool $noContent) use (&$redirectHeaders, $options) {
412 try {
413 $location = self::parseUrl($location);
414 } catch (InvalidArgumentException $e) {
415 return null;
416 }
417
418 if ($noContent && $redirectHeaders) {
419 $filterContentHeaders = static function ($h) {
420 return 0 !== stripos($h, 'Content-Length:') && 0 !== stripos($h, 'Content-Type:') && 0 !== stripos($h, 'Transfer-Encoding:');
421 };
422 $redirectHeaders['no_auth'] = array_filter($redirectHeaders['no_auth'], $filterContentHeaders);
423 $redirectHeaders['with_auth'] = array_filter($redirectHeaders['with_auth'], $filterContentHeaders);
424 }
425
426 if ($redirectHeaders && $host = parse_url('http:'.$location['authority'], \PHP_URL_HOST)) {
427 $requestHeaders = $redirectHeaders['host'] === $host ? $redirectHeaders['with_auth'] : $redirectHeaders['no_auth'];
428 curl_setopt($ch, \CURLOPT_HTTPHEADER, $requestHeaders);
429 } elseif ($noContent && $redirectHeaders) {
430 curl_setopt($ch, \CURLOPT_HTTPHEADER, $redirectHeaders['with_auth']);
431 }
432
433 $url = self::parseUrl(curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL));
434 $url = self::resolveUrl($location, $url);
435
436 curl_setopt($ch, \CURLOPT_PROXY, self::getProxyUrl($options['proxy'], $url));
437
438 return implode('', $url);
439 };
440 }
441
442 private function findConstantName(int $opt): ?string
443 {
444 $constants = array_filter(get_defined_constants(), static function ($v, $k) use ($opt) {
445 return $v === $opt && 'C' === $k[0] && (str_starts_with($k, 'CURLOPT_') || str_starts_with($k, 'CURLINFO_'));
446 }, \ARRAY_FILTER_USE_BOTH);
447
448 return key($constants);
449 }
450
451 /**
452 * Prevents overriding options that are set internally throughout the request.
453 */
454 private function validateExtraCurlOptions(array $options): void
455 {
456 $curloptsToConfig = [
457 // options used in CurlHttpClient
458 \CURLOPT_HTTPAUTH => 'auth_ntlm',
459 \CURLOPT_USERPWD => 'auth_ntlm',
460 \CURLOPT_RESOLVE => 'resolve',
461 \CURLOPT_NOSIGNAL => 'timeout',
462 \CURLOPT_HTTPHEADER => 'headers',
463 \CURLOPT_INFILE => 'body',
464 \CURLOPT_READFUNCTION => 'body',
465 \CURLOPT_INFILESIZE => 'body',
466 \CURLOPT_POSTFIELDS => 'body',
467 \CURLOPT_UPLOAD => 'body',
468 \CURLOPT_INTERFACE => 'bindto',
469 \CURLOPT_TIMEOUT_MS => 'max_duration',
470 \CURLOPT_TIMEOUT => 'max_duration',
471 \CURLOPT_MAXREDIRS => 'max_redirects',
472 \CURLOPT_PROXY => 'proxy',
473 \CURLOPT_NOPROXY => 'no_proxy',
474 \CURLOPT_SSL_VERIFYPEER => 'verify_peer',
475 \CURLOPT_SSL_VERIFYHOST => 'verify_host',
476 \CURLOPT_CAINFO => 'cafile',
477 \CURLOPT_CAPATH => 'capath',
478 \CURLOPT_SSL_CIPHER_LIST => 'ciphers',
479 \CURLOPT_SSLCERT => 'local_cert',
480 \CURLOPT_SSLKEY => 'local_pk',
481 \CURLOPT_KEYPASSWD => 'passphrase',
482 \CURLOPT_CERTINFO => 'capture_peer_cert_chain',
483 \CURLOPT_USERAGENT => 'normalized_headers',
484 \CURLOPT_REFERER => 'headers',
485 // options used in CurlResponse
486 \CURLOPT_NOPROGRESS => 'on_progress',
487 \CURLOPT_PROGRESSFUNCTION => 'on_progress',
488 ];
489
490 if (\defined('CURLOPT_UNIX_SOCKET_PATH')) {
491 $curloptsToConfig[\CURLOPT_UNIX_SOCKET_PATH] = 'bindto';
492 }
493
494 if (\defined('CURLOPT_PINNEDPUBLICKEY')) {
495 $curloptsToConfig[\CURLOPT_PINNEDPUBLICKEY] = 'peer_fingerprint';
496 }
497
498 $curloptsToCheck = [
499 \CURLOPT_PRIVATE,
500 \CURLOPT_HEADERFUNCTION,
501 \CURLOPT_WRITEFUNCTION,
502 \CURLOPT_VERBOSE,
503 \CURLOPT_STDERR,
504 \CURLOPT_RETURNTRANSFER,
505 \CURLOPT_URL,
506 \CURLOPT_FOLLOWLOCATION,
507 \CURLOPT_HEADER,
508 \CURLOPT_CONNECTTIMEOUT,
509 \CURLOPT_CONNECTTIMEOUT_MS,
510 \CURLOPT_HTTP_VERSION,
511 \CURLOPT_PORT,
512 \CURLOPT_DNS_USE_GLOBAL_CACHE,
513 \CURLOPT_PROTOCOLS,
514 \CURLOPT_REDIR_PROTOCOLS,
515 \CURLOPT_COOKIEFILE,
516 \CURLINFO_REDIRECT_COUNT,
517 ];
518
519 if (\defined('CURLOPT_HTTP09_ALLOWED')) {
520 $curloptsToCheck[] = \CURLOPT_HTTP09_ALLOWED;
521 }
522
523 if (\defined('CURLOPT_HEADEROPT')) {
524 $curloptsToCheck[] = \CURLOPT_HEADEROPT;
525 }
526
527 $methodOpts = [
528 \CURLOPT_POST,
529 \CURLOPT_PUT,
530 \CURLOPT_CUSTOMREQUEST,
531 \CURLOPT_HTTPGET,
532 \CURLOPT_NOBODY,
533 ];
534
535 foreach ($options as $opt => $optValue) {
536 if (isset($curloptsToConfig[$opt])) {
537 $constName = $this->findConstantName($opt) ?? $opt;
538 throw new InvalidArgumentException(sprintf('Cannot set "%s" with "extra.curl", use option "%s" instead.', $constName, $curloptsToConfig[$opt]));
539 }
540
541 if (\in_array($opt, $methodOpts)) {
542 throw new InvalidArgumentException('The HTTP method cannot be overridden using "extra.curl".');
543 }
544
545 if (\in_array($opt, $curloptsToCheck)) {
546 $constName = $this->findConstantName($opt) ?? $opt;
547 throw new InvalidArgumentException(sprintf('Cannot set "%s" with "extra.curl".', $constName));
548 }
549 }
550 }
551 }
552