Verzeichnisstruktur phpBB-3.3.15


Veröffentlicht
28.08.2024

So funktioniert es


Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück

Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

(Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

CurlFactory.php

Zuletzt modifiziert: 02.04.2025, 15:03 - Dateigröße: 21.04 KiB


001  <?php
002  namespace GuzzleHttp\Handler;
003   
004  use GuzzleHttp\Exception\ConnectException;
005  use GuzzleHttp\Exception\RequestException;
006  use GuzzleHttp\Promise\FulfilledPromise;
007  use GuzzleHttp\Psr7;
008  use GuzzleHttp\Psr7\LazyOpenStream;
009  use GuzzleHttp\TransferStats;
010  use Psr\Http\Message\RequestInterface;
011   
012  /**
013   * Creates curl resources from a request
014   */
015  class CurlFactory implements CurlFactoryInterface
016  {
017      const CURL_VERSION_STR = 'curl_version';
018      const LOW_CURL_VERSION_NUMBER = '7.21.2';
019   
020      /** @var array */
021      private $handles = [];
022   
023      /** @var int Total number of idle handles to keep in cache */
024      private $maxHandles;
025   
026      /**
027       * @param int $maxHandles Maximum number of idle handles.
028       */
029      public function __construct($maxHandles)
030      {
031          $this->maxHandles = $maxHandles;
032      }
033   
034      public function create(RequestInterface $request, array $options)
035      {
036          if (isset($options['curl']['body_as_string'])) {
037              $options['_body_as_string'] = $options['curl']['body_as_string'];
038              unset($options['curl']['body_as_string']);
039          }
040   
041          $easy = new EasyHandle;
042          $easy->request = $request;
043          $easy->options = $options;
044          $conf = $this->getDefaultConf($easy);
045          $this->applyMethod($easy, $conf);
046          $this->applyHandlerOptions($easy, $conf);
047          $this->applyHeaders($easy, $conf);
048          unset($conf['_headers']);
049   
050          // Add handler options from the request configuration options
051          if (isset($options['curl'])) {
052              $conf = array_replace($conf, $options['curl']);
053          }
054   
055          $conf[CURLOPT_HEADERFUNCTION] = $this->createHeaderFn($easy);
056          $easy->handle = $this->handles
057              ? array_pop($this->handles)
058              : curl_init();
059          curl_setopt_array($easy->handle, $conf);
060   
061          return $easy;
062      }
063   
064      public function release(EasyHandle $easy)
065      {
066          $resource = $easy->handle;
067          unset($easy->handle);
068   
069          if (count($this->handles) >= $this->maxHandles) {
070              curl_close($resource);
071          } else {
072              // Remove all callback functions as they can hold onto references
073              // and are not cleaned up by curl_reset. Using curl_setopt_array
074              // does not work for some reason, so removing each one
075              // individually.
076              curl_setopt($resource, CURLOPT_HEADERFUNCTION, null);
077              curl_setopt($resource, CURLOPT_READFUNCTION, null);
078              curl_setopt($resource, CURLOPT_WRITEFUNCTION, null);
079              curl_setopt($resource, CURLOPT_PROGRESSFUNCTION, null);
080              curl_reset($resource);
081              $this->handles[] = $resource;
082          }
083      }
084   
085      /**
086       * Completes a cURL transaction, either returning a response promise or a
087       * rejected promise.
088       *
089       * @param callable             $handler
090       * @param EasyHandle           $easy
091       * @param CurlFactoryInterface $factory Dictates how the handle is released
092       *
093       * @return \GuzzleHttp\Promise\PromiseInterface
094       */
095      public static function finish(
096          callable $handler,
097          EasyHandle $easy,
098          CurlFactoryInterface $factory
099      ) {
100          if (isset($easy->options['on_stats'])) {
101              self::invokeStats($easy);
102          }
103   
104          if (!$easy->response || $easy->errno) {
105              return self::finishError($handler, $easy, $factory);
106          }
107   
108          // Return the response if it is present and there is no error.
109          $factory->release($easy);
110   
111          // Rewind the body of the response if possible.
112          $body = $easy->response->getBody();
113          if ($body->isSeekable()) {
114              $body->rewind();
115          }
116   
117          return new FulfilledPromise($easy->response);
118      }
119   
120      private static function invokeStats(EasyHandle $easy)
121      {
122          $curlStats = curl_getinfo($easy->handle);
123          $curlStats['appconnect_time'] = curl_getinfo($easy->handle, CURLINFO_APPCONNECT_TIME);
124          $stats = new TransferStats(
125              $easy->request,
126              $easy->response,
127              $curlStats['total_time'],
128              $easy->errno,
129              $curlStats
130          );
131          call_user_func($easy->options['on_stats'], $stats);
132      }
133   
134      private static function finishError(
135          callable $handler,
136          EasyHandle $easy,
137          CurlFactoryInterface $factory
138      ) {
139          // Get error information and release the handle to the factory.
140          $ctx = [
141              'errno' => $easy->errno,
142              'error' => curl_error($easy->handle),
143              'appconnect_time' => curl_getinfo($easy->handle, CURLINFO_APPCONNECT_TIME),
144          ] + curl_getinfo($easy->handle);
145          $ctx[self::CURL_VERSION_STR] = curl_version()['version'];
146          $factory->release($easy);
147   
148          // Retry when nothing is present or when curl failed to rewind.
149          if (empty($easy->options['_err_message'])
150              && (!$easy->errno || $easy->errno == 65)
151          ) {
152              return self::retryFailedRewind($handler, $easy, $ctx);
153          }
154   
155          return self::createRejection($easy, $ctx);
156      }
157   
158      private static function createRejection(EasyHandle $easy, array $ctx)
159      {
160          static $connectionErrors = [
161              CURLE_OPERATION_TIMEOUTED  => true,
162              CURLE_COULDNT_RESOLVE_HOST => true,
163              CURLE_COULDNT_CONNECT      => true,
164              CURLE_SSL_CONNECT_ERROR    => true,
165              CURLE_GOT_NOTHING          => true,
166          ];
167   
168          // If an exception was encountered during the onHeaders event, then
169          // return a rejected promise that wraps that exception.
170          if ($easy->onHeadersException) {
171              return \GuzzleHttp\Promise\rejection_for(
172                  new RequestException(
173                      'An error was encountered during the on_headers event',
174                      $easy->request,
175                      $easy->response,
176                      $easy->onHeadersException,
177                      $ctx
178                  )
179              );
180          }
181          if (version_compare($ctx[self::CURL_VERSION_STR], self::LOW_CURL_VERSION_NUMBER)) {
182              $message = sprintf(
183                  'cURL error %s: %s (%s)',
184                  $ctx['errno'],
185                  $ctx['error'],
186                  'see https://curl.haxx.se/libcurl/c/libcurl-errors.html'
187              );
188          } else {
189              $message = sprintf(
190                  'cURL error %s: %s (%s) for %s',
191                  $ctx['errno'],
192                  $ctx['error'],
193                  'see https://curl.haxx.se/libcurl/c/libcurl-errors.html',
194                  $easy->request->getUri()
195              );
196          }
197   
198          // Create a connection exception if it was a specific error code.
199          $error = isset($connectionErrors[$easy->errno])
200              ? new ConnectException($message, $easy->request, null, $ctx)
201              : new RequestException($message, $easy->request, $easy->response, null, $ctx);
202   
203          return \GuzzleHttp\Promise\rejection_for($error);
204      }
205   
206      private function getDefaultConf(EasyHandle $easy)
207      {
208          $conf = [
209              '_headers'             => $easy->request->getHeaders(),
210              CURLOPT_CUSTOMREQUEST  => $easy->request->getMethod(),
211              CURLOPT_URL            => (string) $easy->request->getUri()->withFragment(''),
212              CURLOPT_RETURNTRANSFER => false,
213              CURLOPT_HEADER         => false,
214              CURLOPT_CONNECTTIMEOUT => 150,
215          ];
216   
217          if (defined('CURLOPT_PROTOCOLS')) {
218              $conf[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
219          }
220   
221          $version = $easy->request->getProtocolVersion();
222          if ($version == 1.1) {
223              $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_1;
224          } elseif ($version == 2.0) {
225              $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2_0;
226          } else {
227              $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0;
228          }
229   
230          return $conf;
231      }
232   
233      private function applyMethod(EasyHandle $easy, array &$conf)
234      {
235          $body = $easy->request->getBody();
236          $size = $body->getSize();
237   
238          if ($size === null || $size > 0) {
239              $this->applyBody($easy->request, $easy->options, $conf);
240              return;
241          }
242   
243          $method = $easy->request->getMethod();
244          if ($method === 'PUT' || $method === 'POST') {
245              // See http://tools.ietf.org/html/rfc7230#section-3.3.2
246              if (!$easy->request->hasHeader('Content-Length')) {
247                  $conf[CURLOPT_HTTPHEADER][] = 'Content-Length: 0';
248              }
249          } elseif ($method === 'HEAD') {
250              $conf[CURLOPT_NOBODY] = true;
251              unset(
252                  $conf[CURLOPT_WRITEFUNCTION],
253                  $conf[CURLOPT_READFUNCTION],
254                  $conf[CURLOPT_FILE],
255                  $conf[CURLOPT_INFILE]
256              );
257          }
258      }
259   
260      private function applyBody(RequestInterface $request, array $options, array &$conf)
261      {
262          $size = $request->hasHeader('Content-Length')
263              ? (int) $request->getHeaderLine('Content-Length')
264              : null;
265   
266          // Send the body as a string if the size is less than 1MB OR if the
267          // [curl][body_as_string] request value is set.
268          if (($size !== null && $size < 1000000) ||
269              !empty($options['_body_as_string'])
270          ) {
271              $conf[CURLOPT_POSTFIELDS] = (string) $request->getBody();
272              // Don't duplicate the Content-Length header
273              $this->removeHeader('Content-Length', $conf);
274              $this->removeHeader('Transfer-Encoding', $conf);
275          } else {
276              $conf[CURLOPT_UPLOAD] = true;
277              if ($size !== null) {
278                  $conf[CURLOPT_INFILESIZE] = $size;
279                  $this->removeHeader('Content-Length', $conf);
280              }
281              $body = $request->getBody();
282              if ($body->isSeekable()) {
283                  $body->rewind();
284              }
285              $conf[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body) {
286                  return $body->read($length);
287              };
288          }
289   
290          // If the Expect header is not present, prevent curl from adding it
291          if (!$request->hasHeader('Expect')) {
292              $conf[CURLOPT_HTTPHEADER][] = 'Expect:';
293          }
294   
295          // cURL sometimes adds a content-type by default. Prevent this.
296          if (!$request->hasHeader('Content-Type')) {
297              $conf[CURLOPT_HTTPHEADER][] = 'Content-Type:';
298          }
299      }
300   
301      private function applyHeaders(EasyHandle $easy, array &$conf)
302      {
303          foreach ($conf['_headers'] as $name => $values) {
304              foreach ($values as $value) {
305                  $value = (string) $value;
306                  if ($value === '') {
307                      // cURL requires a special format for empty headers.
308                      // See https://github.com/guzzle/guzzle/issues/1882 for more details.
309                      $conf[CURLOPT_HTTPHEADER][] = "$name;";
310                  } else {
311                      $conf[CURLOPT_HTTPHEADER][] = "$name$value";
312                  }
313              }
314          }
315   
316          // Remove the Accept header if one was not set
317          if (!$easy->request->hasHeader('Accept')) {
318              $conf[CURLOPT_HTTPHEADER][] = 'Accept:';
319          }
320      }
321   
322      /**
323       * Remove a header from the options array.
324       *
325       * @param string $name    Case-insensitive header to remove
326       * @param array  $options Array of options to modify
327       */
328      private function removeHeader($name, array &$options)
329      {
330          foreach (array_keys($options['_headers']) as $key) {
331              if (!strcasecmp($key, $name)) {
332                  unset($options['_headers'][$key]);
333                  return;
334              }
335          }
336      }
337   
338      private function applyHandlerOptions(EasyHandle $easy, array &$conf)
339      {
340          $options = $easy->options;
341          if (isset($options['verify'])) {
342              if ($options['verify'] === false) {
343                  unset($conf[CURLOPT_CAINFO]);
344                  $conf[CURLOPT_SSL_VERIFYHOST] = 0;
345                  $conf[CURLOPT_SSL_VERIFYPEER] = false;
346              } else {
347                  $conf[CURLOPT_SSL_VERIFYHOST] = 2;
348                  $conf[CURLOPT_SSL_VERIFYPEER] = true;
349                  if (is_string($options['verify'])) {
350                      // Throw an error if the file/folder/link path is not valid or doesn't exist.
351                      if (!file_exists($options['verify'])) {
352                          throw new \InvalidArgumentException(
353                              "SSL CA bundle not found: {$options['verify']}"
354                          );
355                      }
356                      // If it's a directory or a link to a directory use CURLOPT_CAPATH.
357                      // If not, it's probably a file, or a link to a file, so use CURLOPT_CAINFO.
358                      if (is_dir($options['verify']) ||
359                          (is_link($options['verify']) && is_dir(readlink($options['verify'])))) {
360                          $conf[CURLOPT_CAPATH] = $options['verify'];
361                      } else {
362                          $conf[CURLOPT_CAINFO] = $options['verify'];
363                      }
364                  }
365              }
366          }
367   
368          if (!empty($options['decode_content'])) {
369              $accept = $easy->request->getHeaderLine('Accept-Encoding');
370              if ($accept) {
371                  $conf[CURLOPT_ENCODING] = $accept;
372              } else {
373                  $conf[CURLOPT_ENCODING] = '';
374                  // Don't let curl send the header over the wire
375                  $conf[CURLOPT_HTTPHEADER][] = 'Accept-Encoding:';
376              }
377          }
378   
379          if (isset($options['sink'])) {
380              $sink = $options['sink'];
381              if (!is_string($sink)) {
382                  $sink = \GuzzleHttp\Psr7\stream_for($sink);
383              } elseif (!is_dir(dirname($sink))) {
384                  // Ensure that the directory exists before failing in curl.
385                  throw new \RuntimeException(sprintf(
386                      'Directory %s does not exist for sink value of %s',
387                      dirname($sink),
388                      $sink
389                  ));
390              } else {
391                  $sink = new LazyOpenStream($sink, 'w+');
392              }
393              $easy->sink = $sink;
394              $conf[CURLOPT_WRITEFUNCTION] = function ($ch, $write) use ($sink) {
395                  return $sink->write($write);
396              };
397          } else {
398              // Use a default temp stream if no sink was set.
399              $conf[CURLOPT_FILE] = fopen('php://temp', 'w+');
400              $easy->sink = Psr7\stream_for($conf[CURLOPT_FILE]);
401          }
402          $timeoutRequiresNoSignal = false;
403          if (isset($options['timeout'])) {
404              $timeoutRequiresNoSignal |= $options['timeout'] < 1;
405              $conf[CURLOPT_TIMEOUT_MS] = $options['timeout'] * 1000;
406          }
407   
408          // CURL default value is CURL_IPRESOLVE_WHATEVER
409          if (isset($options['force_ip_resolve'])) {
410              if ('v4' === $options['force_ip_resolve']) {
411                  $conf[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V4;
412              } elseif ('v6' === $options['force_ip_resolve']) {
413                  $conf[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V6;
414              }
415          }
416   
417          if (isset($options['connect_timeout'])) {
418              $timeoutRequiresNoSignal |= $options['connect_timeout'] < 1;
419              $conf[CURLOPT_CONNECTTIMEOUT_MS] = $options['connect_timeout'] * 1000;
420          }
421   
422          if ($timeoutRequiresNoSignal && strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
423              $conf[CURLOPT_NOSIGNAL] = true;
424          }
425   
426          if (isset($options['proxy'])) {
427              if (!is_array($options['proxy'])) {
428                  $conf[CURLOPT_PROXY] = $options['proxy'];
429              } else {
430                  $scheme = $easy->request->getUri()->getScheme();
431                  if (isset($options['proxy'][$scheme])) {
432                      $host = $easy->request->getUri()->getHost();
433                      if (!isset($options['proxy']['no']) ||
434                          !\GuzzleHttp\is_host_in_noproxy($host, $options['proxy']['no'])
435                      ) {
436                          $conf[CURLOPT_PROXY] = $options['proxy'][$scheme];
437                      }
438                  }
439              }
440          }
441   
442          if (isset($options['cert'])) {
443              $cert = $options['cert'];
444              if (is_array($cert)) {
445                  $conf[CURLOPT_SSLCERTPASSWD] = $cert[1];
446                  $cert = $cert[0];
447              }
448              if (!file_exists($cert)) {
449                  throw new \InvalidArgumentException(
450                      "SSL certificate not found: {$cert}"
451                  );
452              }
453              $conf[CURLOPT_SSLCERT] = $cert;
454          }
455   
456          if (isset($options['ssl_key'])) {
457              if (is_array($options['ssl_key'])) {
458                  if (count($options['ssl_key']) === 2) {
459                      list($sslKey, $conf[CURLOPT_SSLKEYPASSWD]) = $options['ssl_key'];
460                  } else {
461                      list($sslKey) = $options['ssl_key'];
462                  }
463              }
464   
465              $sslKey = isset($sslKey) ? $sslKey: $options['ssl_key'];
466   
467              if (!file_exists($sslKey)) {
468                  throw new \InvalidArgumentException(
469                      "SSL private key not found: {$sslKey}"
470                  );
471              }
472              $conf[CURLOPT_SSLKEY] = $sslKey;
473          }
474   
475          if (isset($options['progress'])) {
476              $progress = $options['progress'];
477              if (!is_callable($progress)) {
478                  throw new \InvalidArgumentException(
479                      'progress client option must be callable'
480                  );
481              }
482              $conf[CURLOPT_NOPROGRESS] = false;
483              $conf[CURLOPT_PROGRESSFUNCTION] = function () use ($progress) {
484                  $args = func_get_args();
485                  // PHP 5.5 pushed the handle onto the start of the args
486                  if (is_resource($args[0])) {
487                      array_shift($args);
488                  }
489                  call_user_func_array($progress, $args);
490              };
491          }
492   
493          if (!empty($options['debug'])) {
494              $conf[CURLOPT_STDERR] = \GuzzleHttp\debug_resource($options['debug']);
495              $conf[CURLOPT_VERBOSE] = true;
496          }
497      }
498   
499      /**
500       * This function ensures that a response was set on a transaction. If one
501       * was not set, then the request is retried if possible. This error
502       * typically means you are sending a payload, curl encountered a
503       * "Connection died, retrying a fresh connect" error, tried to rewind the
504       * stream, and then encountered a "necessary data rewind wasn't possible"
505       * error, causing the request to be sent through curl_multi_info_read()
506       * without an error status.
507       */
508      private static function retryFailedRewind(
509          callable $handler,
510          EasyHandle $easy,
511          array $ctx
512      ) {
513          try {
514              // Only rewind if the body has been read from.
515              $body = $easy->request->getBody();
516              if ($body->tell() > 0) {
517                  $body->rewind();
518              }
519          } catch (\RuntimeException $e) {
520              $ctx['error'] = 'The connection unexpectedly failed without '
521                  . 'providing an error. The request would have been retried, '
522                  . 'but attempting to rewind the request body failed. '
523                  . 'Exception: ' . $e;
524              return self::createRejection($easy, $ctx);
525          }
526   
527          // Retry no more than 3 times before giving up.
528          if (!isset($easy->options['_curl_retries'])) {
529              $easy->options['_curl_retries'] = 1;
530          } elseif ($easy->options['_curl_retries'] == 2) {
531              $ctx['error'] = 'The cURL request was retried 3 times '
532                  . 'and did not succeed. The most likely reason for the failure '
533                  . 'is that cURL was unable to rewind the body of the request '
534                  . 'and subsequent retries resulted in the same error. Turn on '
535                  . 'the debug option to see what went wrong. See '
536                  . 'https://bugs.php.net/bug.php?id=47204 for more information.';
537              return self::createRejection($easy, $ctx);
538          } else {
539              $easy->options['_curl_retries']++;
540          }
541   
542          return $handler($easy->request, $easy->options);
543      }
544   
545      private function createHeaderFn(EasyHandle $easy)
546      {
547          if (isset($easy->options['on_headers'])) {
548              $onHeaders = $easy->options['on_headers'];
549   
550              if (!is_callable($onHeaders)) {
551                  throw new \InvalidArgumentException('on_headers must be callable');
552              }
553          } else {
554              $onHeaders = null;
555          }
556   
557          return function ($ch, $h) use (
558              $onHeaders,
559              $easy,
560              &$startingResponse
561          ) {
562              $value = trim($h);
563              if ($value === '') {
564                  $startingResponse = true;
565                  $easy->createResponse();
566                  if ($onHeaders !== null) {
567                      try {
568                          $onHeaders($easy->response);
569                      } catch (\Exception $e) {
570                          // Associate the exception with the handle and trigger
571                          // a curl header write error by returning 0.
572                          $easy->onHeadersException = $e;
573                          return -1;
574                      }
575                  }
576              } elseif ($startingResponse) {
577                  $startingResponse = false;
578                  $easy->headers = [$value];
579              } else {
580                  $easy->headers[] = $value;
581              }
582              return strlen($h);
583          };
584      }
585  }
586