Verzeichnisstruktur phpBB-3.2.0


Veröffentlicht
06.01.2017

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: 09.10.2024, 12:57 - Dateigröße: 19.47 KiB


001  <?php
002  namespace GuzzleHttp\Ring\Client;
003   
004  use GuzzleHttp\Ring\Core;
005  use GuzzleHttp\Ring\Exception\ConnectException;
006  use GuzzleHttp\Ring\Exception\RingException;
007  use GuzzleHttp\Stream\LazyOpenStream;
008  use GuzzleHttp\Stream\StreamInterface;
009   
010  /**
011   * Creates curl resources from a request
012   */
013  class CurlFactory
014  {
015      /**
016       * Creates a cURL handle, header resource, and body resource based on a
017       * transaction.
018       *
019       * @param array         $request Request hash
020       * @param null|resource $handle  Optionally provide a curl handle to modify
021       *
022       * @return array Returns an array of the curl handle, headers array, and
023       *               response body handle.
024       * @throws \RuntimeException when an option cannot be applied
025       */
026      public function __invoke(array $request, $handle = null)
027      {
028          $headers = [];
029          $options = $this->getDefaultOptions($request, $headers);
030          $this->applyMethod($request, $options);
031   
032          if (isset($request['client'])) {
033              $this->applyHandlerOptions($request, $options);
034          }
035   
036          $this->applyHeaders($request, $options);
037          unset($options['_headers']);
038   
039          // Add handler options from the request's configuration options
040          if (isset($request['client']['curl'])) {
041              $options = $this->applyCustomCurlOptions(
042                  $request['client']['curl'],
043                  $options
044              );
045          }
046   
047          if (!$handle) {
048              $handle = curl_init();
049          }
050   
051          $body = $this->getOutputBody($request, $options);
052          curl_setopt_array($handle, $options);
053   
054          return [$handle, &$headers, $body];
055      }
056   
057      /**
058       * Creates a response hash from a cURL result.
059       *
060       * @param callable $handler  Handler that was used.
061       * @param array    $request  Request that sent.
062       * @param array    $response Response hash to update.
063       * @param array    $headers  Headers received during transfer.
064       * @param resource $body     Body fopen response.
065       *
066       * @return array
067       */
068      public static function createResponse(
069          callable $handler,
070          array $request,
071          array $response,
072          array $headers,
073          $body
074      ) {
075          if (isset($response['transfer_stats']['url'])) {
076              $response['effective_url'] = $response['transfer_stats']['url'];
077          }
078   
079          if (!empty($headers)) {
080              $startLine = explode(' ', array_shift($headers), 3);
081              $headerList = Core::headersFromLines($headers);
082              $response['headers'] = $headerList;
083              $response['version'] = isset($startLine[0]) ? substr($startLine[0], 5) : null;
084              $response['status'] = isset($startLine[1]) ? (int) $startLine[1] : null;
085              $response['reason'] = isset($startLine[2]) ? $startLine[2] : null;
086              $response['body'] = $body;
087              Core::rewindBody($response);
088          }
089   
090          return !empty($response['curl']['errno']) || !isset($response['status'])
091              ? self::createErrorResponse($handler, $request, $response)
092              : $response;
093      }
094   
095      private static function createErrorResponse(
096          callable $handler,
097          array $request,
098          array $response
099      ) {
100          static $connectionErrors = [
101              CURLE_OPERATION_TIMEOUTED  => true,
102              CURLE_COULDNT_RESOLVE_HOST => true,
103              CURLE_COULDNT_CONNECT      => true,
104              CURLE_SSL_CONNECT_ERROR    => true,
105              CURLE_GOT_NOTHING          => true,
106          ];
107   
108          // Retry when nothing is present or when curl failed to rewind.
109          if (!isset($response['err_message'])
110              && (empty($response['curl']['errno'])
111                  || $response['curl']['errno'] == 65)
112          ) {
113              return self::retryFailedRewind($handler, $request, $response);
114          }
115   
116          $message = isset($response['err_message'])
117              ? $response['err_message']
118              : sprintf('cURL error %s: %s',
119                  $response['curl']['errno'],
120                  isset($response['curl']['error'])
121                      ? $response['curl']['error']
122                      : 'See http://curl.haxx.se/libcurl/c/libcurl-errors.html');
123   
124          $error = isset($response['curl']['errno'])
125              && isset($connectionErrors[$response['curl']['errno']])
126              ? new ConnectException($message)
127              : new RingException($message);
128   
129          return $response + [
130              'status'  => null,
131              'reason'  => null,
132              'body'    => null,
133              'headers' => [],
134              'error'   => $error,
135          ];
136      }
137   
138      private function getOutputBody(array $request, array &$options)
139      {
140          // Determine where the body of the response (if any) will be streamed.
141          if (isset($options[CURLOPT_WRITEFUNCTION])) {
142              return $request['client']['save_to'];
143          }
144   
145          if (isset($options[CURLOPT_FILE])) {
146              return $options[CURLOPT_FILE];
147          }
148   
149          if ($request['http_method'] != 'HEAD') {
150              // Create a default body if one was not provided
151              return $options[CURLOPT_FILE] = fopen('php://temp', 'w+');
152          }
153   
154          return null;
155      }
156   
157      private function getDefaultOptions(array $request, array &$headers)
158      {
159          $url = Core::url($request);
160          $startingResponse = false;
161   
162          $options = [
163              '_headers'             => $request['headers'],
164              CURLOPT_CUSTOMREQUEST  => $request['http_method'],
165              CURLOPT_URL            => $url,
166              CURLOPT_RETURNTRANSFER => false,
167              CURLOPT_HEADER         => false,
168              CURLOPT_CONNECTTIMEOUT => 150,
169              CURLOPT_HEADERFUNCTION => function ($ch, $h) use (&$headers, &$startingResponse) {
170                  $value = trim($h);
171                  if ($value === '') {
172                      $startingResponse = true;
173                  } elseif ($startingResponse) {
174                      $startingResponse = false;
175                      $headers = [$value];
176                  } else {
177                      $headers[] = $value;
178                  }
179                  return strlen($h);
180              },
181          ];
182   
183          if (isset($request['version'])) {
184              if ($request['version'] == 2.0) {
185                  $options[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2_0;
186              } else if ($request['version'] == 1.1) {
187                  $options[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_1;
188              } else {
189                  $options[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0;
190              }
191          }
192   
193          if (defined('CURLOPT_PROTOCOLS')) {
194              $options[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
195          }
196   
197          return $options;
198      }
199   
200      private function applyMethod(array $request, array &$options)
201      {
202          if (isset($request['body'])) {
203              $this->applyBody($request, $options);
204              return;
205          }
206   
207          switch ($request['http_method']) {
208              case 'PUT':
209              case 'POST':
210                  // See http://tools.ietf.org/html/rfc7230#section-3.3.2
211                  if (!Core::hasHeader($request, 'Content-Length')) {
212                      $options[CURLOPT_HTTPHEADER][] = 'Content-Length: 0';
213                  }
214                  break;
215              case 'HEAD':
216                  $options[CURLOPT_NOBODY] = true;
217                  unset(
218                      $options[CURLOPT_WRITEFUNCTION],
219                      $options[CURLOPT_READFUNCTION],
220                      $options[CURLOPT_FILE],
221                      $options[CURLOPT_INFILE]
222                  );
223          }
224      }
225   
226      private function applyBody(array $request, array &$options)
227      {
228          $contentLength = Core::firstHeader($request, 'Content-Length');
229          $size = $contentLength !== null ? (int) $contentLength : null;
230   
231          // Send the body as a string if the size is less than 1MB OR if the
232          // [client][curl][body_as_string] request value is set.
233          if (($size !== null && $size < 1000000) ||
234              isset($request['client']['curl']['body_as_string']) ||
235              is_string($request['body'])
236          ) {
237              $options[CURLOPT_POSTFIELDS] = Core::body($request);
238              // Don't duplicate the Content-Length header
239              $this->removeHeader('Content-Length', $options);
240              $this->removeHeader('Transfer-Encoding', $options);
241          } else {
242              $options[CURLOPT_UPLOAD] = true;
243              if ($size !== null) {
244                  // Let cURL handle setting the Content-Length header
245                  $options[CURLOPT_INFILESIZE] = $size;
246                  $this->removeHeader('Content-Length', $options);
247              }
248              $this->addStreamingBody($request, $options);
249          }
250   
251          // If the Expect header is not present, prevent curl from adding it
252          if (!Core::hasHeader($request, 'Expect')) {
253              $options[CURLOPT_HTTPHEADER][] = 'Expect:';
254          }
255   
256          // cURL sometimes adds a content-type by default. Prevent this.
257          if (!Core::hasHeader($request, 'Content-Type')) {
258              $options[CURLOPT_HTTPHEADER][] = 'Content-Type:';
259          }
260      }
261   
262      private function addStreamingBody(array $request, array &$options)
263      {
264          $body = $request['body'];
265   
266          if ($body instanceof StreamInterface) {
267              $options[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body) {
268                  return (string) $body->read($length);
269              };
270              if (!isset($options[CURLOPT_INFILESIZE])) {
271                  if ($size = $body->getSize()) {
272                      $options[CURLOPT_INFILESIZE] = $size;
273                  }
274              }
275          } elseif (is_resource($body)) {
276              $options[CURLOPT_INFILE] = $body;
277          } elseif ($body instanceof \Iterator) {
278              $buf = '';
279              $options[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body, &$buf) {
280                  if ($body->valid()) {
281                      $buf .= $body->current();
282                      $body->next();
283                  }
284                  $result = (string) substr($buf, 0, $length);
285                  $buf = substr($buf, $length);
286                  return $result;
287              };
288          } else {
289              throw new \InvalidArgumentException('Invalid request body provided');
290          }
291      }
292   
293      private function applyHeaders(array $request, array &$options)
294      {
295          foreach ($options['_headers'] as $name => $values) {
296              foreach ($values as $value) {
297                  $options[CURLOPT_HTTPHEADER][] = "$name$value";
298              }
299          }
300   
301          // Remove the Accept header if one was not set
302          if (!Core::hasHeader($request, 'Accept')) {
303              $options[CURLOPT_HTTPHEADER][] = 'Accept:';
304          }
305      }
306   
307      /**
308       * Takes an array of curl options specified in the 'curl' option of a
309       * request's configuration array and maps them to CURLOPT_* options.
310       *
311       * This method is only called when a  request has a 'curl' config setting.
312       *
313       * @param array $config  Configuration array of custom curl option
314       * @param array $options Array of existing curl options
315       *
316       * @return array Returns a new array of curl options
317       */
318      private function applyCustomCurlOptions(array $config, array $options)
319      {
320          $curlOptions = [];
321          foreach ($config as $key => $value) {
322              if (is_int($key)) {
323                  $curlOptions[$key] = $value;
324              }
325          }
326   
327          return $curlOptions + $options;
328      }
329   
330      /**
331       * Remove a header from the options array.
332       *
333       * @param string $name    Case-insensitive header to remove
334       * @param array  $options Array of options to modify
335       */
336      private function removeHeader($name, array &$options)
337      {
338          foreach (array_keys($options['_headers']) as $key) {
339              if (!strcasecmp($key, $name)) {
340                  unset($options['_headers'][$key]);
341                  return;
342              }
343          }
344      }
345   
346      /**
347       * Applies an array of request client options to a the options array.
348       *
349       * This method uses a large switch rather than double-dispatch to save on
350       * high overhead of calling functions in PHP.
351       */
352      private function applyHandlerOptions(array $request, array &$options)
353      {
354          foreach ($request['client'] as $key => $value) {
355              switch ($key) {
356              // Violating PSR-4 to provide more room.
357              case 'verify':
358   
359                  if ($value === false) {
360                      unset($options[CURLOPT_CAINFO]);
361                      $options[CURLOPT_SSL_VERIFYHOST] = 0;
362                      $options[CURLOPT_SSL_VERIFYPEER] = false;
363                      continue;
364                  }
365   
366                  $options[CURLOPT_SSL_VERIFYHOST] = 2;
367                  $options[CURLOPT_SSL_VERIFYPEER] = true;
368   
369                  if (is_string($value)) {
370                      $options[CURLOPT_CAINFO] = $value;
371                      if (!file_exists($value)) {
372                          throw new \InvalidArgumentException(
373                              "SSL CA bundle not found: $value"
374                          );
375                      }
376                  }
377                  break;
378   
379              case 'decode_content':
380   
381                  if ($value === false) {
382                      continue;
383                  }
384   
385                  $accept = Core::firstHeader($request, 'Accept-Encoding');
386                  if ($accept) {
387                      $options[CURLOPT_ENCODING] = $accept;
388                  } else {
389                      $options[CURLOPT_ENCODING] = '';
390                      // Don't let curl send the header over the wire
391                      $options[CURLOPT_HTTPHEADER][] = 'Accept-Encoding:';
392                  }
393                  break;
394   
395              case 'save_to':
396   
397                  if (is_string($value)) {
398                      if (!is_dir(dirname($value))) {
399                          throw new \RuntimeException(sprintf(
400                              'Directory %s does not exist for save_to value of %s',
401                              dirname($value),
402                              $value
403                          ));
404                      }
405                      $value = new LazyOpenStream($value, 'w+');
406                  }
407   
408                  if ($value instanceof StreamInterface) {
409                      $options[CURLOPT_WRITEFUNCTION] =
410                          function ($ch, $write) use ($value) {
411                              return $value->write($write);
412                          };
413                  } elseif (is_resource($value)) {
414                      $options[CURLOPT_FILE] = $value;
415                  } else {
416                      throw new \InvalidArgumentException('save_to must be a '
417                          . 'GuzzleHttp\Stream\StreamInterface or resource');
418                  }
419                  break;
420   
421              case 'timeout':
422   
423                  if (defined('CURLOPT_TIMEOUT_MS')) {
424                      $options[CURLOPT_TIMEOUT_MS] = $value * 1000;
425                  } else {
426                      $options[CURLOPT_TIMEOUT] = $value;
427                  }
428                  break;
429   
430              case 'connect_timeout':
431   
432                  if (defined('CURLOPT_CONNECTTIMEOUT_MS')) {
433                      $options[CURLOPT_CONNECTTIMEOUT_MS] = $value * 1000;
434                  } else {
435                      $options[CURLOPT_CONNECTTIMEOUT] = $value;
436                  }
437                  break;
438   
439              case 'proxy':
440   
441                  if (!is_array($value)) {
442                      $options[CURLOPT_PROXY] = $value;
443                  } elseif (isset($request['scheme'])) {
444                      $scheme = $request['scheme'];
445                      if (isset($value[$scheme])) {
446                          $options[CURLOPT_PROXY] = $value[$scheme];
447                      }
448                  }
449                  break;
450   
451              case 'cert':
452   
453                  if (is_array($value)) {
454                      $options[CURLOPT_SSLCERTPASSWD] = $value[1];
455                      $value = $value[0];
456                  }
457   
458                  if (!file_exists($value)) {
459                      throw new \InvalidArgumentException(
460                          "SSL certificate not found: {$value}"
461                      );
462                  }
463   
464                  $options[CURLOPT_SSLCERT] = $value;
465                  break;
466   
467              case 'ssl_key':
468   
469                  if (is_array($value)) {
470                      $options[CURLOPT_SSLKEYPASSWD] = $value[1];
471                      $value = $value[0];
472                  }
473   
474                  if (!file_exists($value)) {
475                      throw new \InvalidArgumentException(
476                          "SSL private key not found: {$value}"
477                      );
478                  }
479   
480                  $options[CURLOPT_SSLKEY] = $value;
481                  break;
482   
483              case 'progress':
484   
485                  if (!is_callable($value)) {
486                      throw new \InvalidArgumentException(
487                          'progress client option must be callable'
488                      );
489                  }
490   
491                  $options[CURLOPT_NOPROGRESS] = false;
492                  $options[CURLOPT_PROGRESSFUNCTION] =
493                      function () use ($value) {
494                          $args = func_get_args();
495                          // PHP 5.5 pushed the handle onto the start of the args
496                          if (is_resource($args[0])) {
497                              array_shift($args);
498                          }
499                          call_user_func_array($value, $args);
500                      };
501                  break;
502   
503              case 'debug':
504   
505                  if ($value) {
506                      $options[CURLOPT_STDERR] = Core::getDebugResource($value);
507                      $options[CURLOPT_VERBOSE] = true;
508                  }
509                  break;
510              }
511          }
512      }
513   
514      /**
515       * This function ensures that a response was set on a transaction. If one
516       * was not set, then the request is retried if possible. This error
517       * typically means you are sending a payload, curl encountered a
518       * "Connection died, retrying a fresh connect" error, tried to rewind the
519       * stream, and then encountered a "necessary data rewind wasn't possible"
520       * error, causing the request to be sent through curl_multi_info_read()
521       * without an error status.
522       */
523      private static function retryFailedRewind(
524          callable $handler,
525          array $request,
526          array $response
527      ) {
528          // If there is no body, then there is some other kind of issue. This
529          // is weird and should probably never happen.
530          if (!isset($request['body'])) {
531              $response['err_message'] = 'No response was received for a request '
532                  . 'with no body. This could mean that you are saturating your '
533                  . 'network.';
534              return self::createErrorResponse($handler, $request, $response);
535          }
536   
537          if (!Core::rewindBody($request)) {
538              $response['err_message'] = 'The connection unexpectedly failed '
539                  . 'without providing an error. The request would have been '
540                  . 'retried, but attempting to rewind the request body failed.';
541              return self::createErrorResponse($handler, $request, $response);
542          }
543   
544          // Retry no more than 3 times before giving up.
545          if (!isset($request['curl']['retries'])) {
546              $request['curl']['retries'] = 1;
547          } elseif ($request['curl']['retries'] == 2) {
548              $response['err_message'] = 'The cURL request was retried 3 times '
549                  . 'and did no succeed. cURL was unable to rewind the body of '
550                  . 'the request and subsequent retries resulted in the same '
551                  . 'error. Turn on the debug option to see what went wrong. '
552                  . 'See https://bugs.php.net/bug.php?id=47204 for more information.';
553              return self::createErrorResponse($handler, $request, $response);
554          } else {
555              $request['curl']['retries']++;
556          }
557   
558          return $handler($request);
559      }
560  }
561