vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php line 603

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