[ Index ]

PHP Cross Reference of phpBB-3.3.2-deutsch

title

Body

[close]

/vendor/guzzlehttp/guzzle/src/Handler/ -> StreamHandler.php (source)

   1  <?php
   2  namespace GuzzleHttp\Handler;
   3  
   4  use GuzzleHttp\Exception\ConnectException;
   5  use GuzzleHttp\Exception\RequestException;
   6  use GuzzleHttp\Promise\FulfilledPromise;
   7  use GuzzleHttp\Promise\PromiseInterface;
   8  use GuzzleHttp\Psr7;
   9  use GuzzleHttp\TransferStats;
  10  use GuzzleHttp\Utils;
  11  use Psr\Http\Message\RequestInterface;
  12  use Psr\Http\Message\ResponseInterface;
  13  use Psr\Http\Message\StreamInterface;
  14  
  15  /**
  16   * HTTP handler that uses PHP's HTTP stream wrapper.
  17   */
  18  class StreamHandler
  19  {
  20      private $lastHeaders = [];
  21  
  22      /**
  23       * Sends an HTTP request.
  24       *
  25       * @param RequestInterface $request Request to send.
  26       * @param array            $options Request transfer options.
  27       *
  28       * @return PromiseInterface
  29       */
  30      public function __invoke(RequestInterface $request, array $options)
  31      {
  32          // Sleep if there is a delay specified.
  33          if (isset($options['delay'])) {
  34              usleep($options['delay'] * 1000);
  35          }
  36  
  37          $startTime = isset($options['on_stats']) ? Utils::currentTime() : null;
  38  
  39          try {
  40              // Does not support the expect header.
  41              $request = $request->withoutHeader('Expect');
  42  
  43              // Append a content-length header if body size is zero to match
  44              // cURL's behavior.
  45              if (0 === $request->getBody()->getSize()) {
  46                  $request = $request->withHeader('Content-Length', '0');
  47              }
  48  
  49              return $this->createResponse(
  50                  $request,
  51                  $options,
  52                  $this->createStream($request, $options),
  53                  $startTime
  54              );
  55          } catch (\InvalidArgumentException $e) {
  56              throw $e;
  57          } catch (\Exception $e) {
  58              // Determine if the error was a networking error.
  59              $message = $e->getMessage();
  60              // This list can probably get more comprehensive.
  61              if (strpos($message, 'getaddrinfo') // DNS lookup failed
  62                  || strpos($message, 'Connection refused')
  63                  || strpos($message, "couldn't connect to host") // error on HHVM
  64                  || strpos($message, "connection attempt failed")
  65              ) {
  66                  $e = new ConnectException($e->getMessage(), $request, $e);
  67              }
  68              $e = RequestException::wrapException($request, $e);
  69              $this->invokeStats($options, $request, $startTime, null, $e);
  70  
  71              return \GuzzleHttp\Promise\rejection_for($e);
  72          }
  73      }
  74  
  75      private function invokeStats(
  76          array $options,
  77          RequestInterface $request,
  78          $startTime,
  79          ResponseInterface $response = null,
  80          $error = null
  81      ) {
  82          if (isset($options['on_stats'])) {
  83              $stats = new TransferStats(
  84                  $request,
  85                  $response,
  86                  Utils::currentTime() - $startTime,
  87                  $error,
  88                  []
  89              );
  90              call_user_func($options['on_stats'], $stats);
  91          }
  92      }
  93  
  94      private function createResponse(
  95          RequestInterface $request,
  96          array $options,
  97          $stream,
  98          $startTime
  99      ) {
 100          $hdrs = $this->lastHeaders;
 101          $this->lastHeaders = [];
 102          $parts = explode(' ', array_shift($hdrs), 3);
 103          $ver = explode('/', $parts[0])[1];
 104          $status = $parts[1];
 105          $reason = isset($parts[2]) ? $parts[2] : null;
 106          $headers = \GuzzleHttp\headers_from_lines($hdrs);
 107          list($stream, $headers) = $this->checkDecode($options, $headers, $stream);
 108          $stream = Psr7\stream_for($stream);
 109          $sink = $stream;
 110  
 111          if (strcasecmp('HEAD', $request->getMethod())) {
 112              $sink = $this->createSink($stream, $options);
 113          }
 114  
 115          $response = new Psr7\Response($status, $headers, $sink, $ver, $reason);
 116  
 117          if (isset($options['on_headers'])) {
 118              try {
 119                  $options['on_headers']($response);
 120              } catch (\Exception $e) {
 121                  $msg = 'An error was encountered during the on_headers event';
 122                  $ex = new RequestException($msg, $request, $response, $e);
 123                  return \GuzzleHttp\Promise\rejection_for($ex);
 124              }
 125          }
 126  
 127          // Do not drain when the request is a HEAD request because they have
 128          // no body.
 129          if ($sink !== $stream) {
 130              $this->drain(
 131                  $stream,
 132                  $sink,
 133                  $response->getHeaderLine('Content-Length')
 134              );
 135          }
 136  
 137          $this->invokeStats($options, $request, $startTime, $response, null);
 138  
 139          return new FulfilledPromise($response);
 140      }
 141  
 142      private function createSink(StreamInterface $stream, array $options)
 143      {
 144          if (!empty($options['stream'])) {
 145              return $stream;
 146          }
 147  
 148          $sink = isset($options['sink'])
 149              ? $options['sink']
 150              : fopen('php://temp', 'r+');
 151  
 152          return is_string($sink)
 153              ? new Psr7\LazyOpenStream($sink, 'w+')
 154              : Psr7\stream_for($sink);
 155      }
 156  
 157      private function checkDecode(array $options, array $headers, $stream)
 158      {
 159          // Automatically decode responses when instructed.
 160          if (!empty($options['decode_content'])) {
 161              $normalizedKeys = \GuzzleHttp\normalize_header_keys($headers);
 162              if (isset($normalizedKeys['content-encoding'])) {
 163                  $encoding = $headers[$normalizedKeys['content-encoding']];
 164                  if ($encoding[0] === 'gzip' || $encoding[0] === 'deflate') {
 165                      $stream = new Psr7\InflateStream(
 166                          Psr7\stream_for($stream)
 167                      );
 168                      $headers['x-encoded-content-encoding']
 169                          = $headers[$normalizedKeys['content-encoding']];
 170                      // Remove content-encoding header
 171                      unset($headers[$normalizedKeys['content-encoding']]);
 172                      // Fix content-length header
 173                      if (isset($normalizedKeys['content-length'])) {
 174                          $headers['x-encoded-content-length']
 175                              = $headers[$normalizedKeys['content-length']];
 176  
 177                          $length = (int) $stream->getSize();
 178                          if ($length === 0) {
 179                              unset($headers[$normalizedKeys['content-length']]);
 180                          } else {
 181                              $headers[$normalizedKeys['content-length']] = [$length];
 182                          }
 183                      }
 184                  }
 185              }
 186          }
 187  
 188          return [$stream, $headers];
 189      }
 190  
 191      /**
 192       * Drains the source stream into the "sink" client option.
 193       *
 194       * @param StreamInterface $source
 195       * @param StreamInterface $sink
 196       * @param string          $contentLength Header specifying the amount of
 197       *                                       data to read.
 198       *
 199       * @return StreamInterface
 200       * @throws \RuntimeException when the sink option is invalid.
 201       */
 202      private function drain(
 203          StreamInterface $source,
 204          StreamInterface $sink,
 205          $contentLength
 206      ) {
 207          // If a content-length header is provided, then stop reading once
 208          // that number of bytes has been read. This can prevent infinitely
 209          // reading from a stream when dealing with servers that do not honor
 210          // Connection: Close headers.
 211          Psr7\copy_to_stream(
 212              $source,
 213              $sink,
 214              (strlen($contentLength) > 0 && (int) $contentLength > 0) ? (int) $contentLength : -1
 215          );
 216  
 217          $sink->seek(0);
 218          $source->close();
 219  
 220          return $sink;
 221      }
 222  
 223      /**
 224       * Create a resource and check to ensure it was created successfully
 225       *
 226       * @param callable $callback Callable that returns stream resource
 227       *
 228       * @return resource
 229       * @throws \RuntimeException on error
 230       */
 231      private function createResource(callable $callback)
 232      {
 233          $errors = null;
 234          set_error_handler(function ($_, $msg, $file, $line) use (&$errors) {
 235              $errors[] = [
 236                  'message' => $msg,
 237                  'file'    => $file,
 238                  'line'    => $line
 239              ];
 240              return true;
 241          });
 242  
 243          $resource = $callback();
 244          restore_error_handler();
 245  
 246          if (!$resource) {
 247              $message = 'Error creating resource: ';
 248              foreach ($errors as $err) {
 249                  foreach ($err as $key => $value) {
 250                      $message .= "[$key] $value" . PHP_EOL;
 251                  }
 252              }
 253              throw new \RuntimeException(trim($message));
 254          }
 255  
 256          return $resource;
 257      }
 258  
 259      private function createStream(RequestInterface $request, array $options)
 260      {
 261          static $methods;
 262          if (!$methods) {
 263              $methods = array_flip(get_class_methods(__CLASS__));
 264          }
 265  
 266          // HTTP/1.1 streams using the PHP stream wrapper require a
 267          // Connection: close header
 268          if ($request->getProtocolVersion() == '1.1'
 269              && !$request->hasHeader('Connection')
 270          ) {
 271              $request = $request->withHeader('Connection', 'close');
 272          }
 273  
 274          // Ensure SSL is verified by default
 275          if (!isset($options['verify'])) {
 276              $options['verify'] = true;
 277          }
 278  
 279          $params = [];
 280          $context = $this->getDefaultContext($request);
 281  
 282          if (isset($options['on_headers']) && !is_callable($options['on_headers'])) {
 283              throw new \InvalidArgumentException('on_headers must be callable');
 284          }
 285  
 286          if (!empty($options)) {
 287              foreach ($options as $key => $value) {
 288                  $method = "add_{$key}";
 289                  if (isset($methods[$method])) {
 290                      $this->{$method}($request, $context, $value, $params);
 291                  }
 292              }
 293          }
 294  
 295          if (isset($options['stream_context'])) {
 296              if (!is_array($options['stream_context'])) {
 297                  throw new \InvalidArgumentException('stream_context must be an array');
 298              }
 299              $context = array_replace_recursive(
 300                  $context,
 301                  $options['stream_context']
 302              );
 303          }
 304  
 305          // Microsoft NTLM authentication only supported with curl handler
 306          if (isset($options['auth'])
 307              && is_array($options['auth'])
 308              && isset($options['auth'][2])
 309              && 'ntlm' == $options['auth'][2]
 310          ) {
 311              throw new \InvalidArgumentException('Microsoft NTLM authentication only supported with curl handler');
 312          }
 313  
 314          $uri = $this->resolveHost($request, $options);
 315  
 316          $context = $this->createResource(
 317              function () use ($context, $params) {
 318                  return stream_context_create($context, $params);
 319              }
 320          );
 321  
 322          return $this->createResource(
 323              function () use ($uri, &$http_response_header, $context, $options) {
 324                  $resource = fopen((string) $uri, 'r', null, $context);
 325                  $this->lastHeaders = $http_response_header;
 326  
 327                  if (isset($options['read_timeout'])) {
 328                      $readTimeout = $options['read_timeout'];
 329                      $sec = (int) $readTimeout;
 330                      $usec = ($readTimeout - $sec) * 100000;
 331                      stream_set_timeout($resource, $sec, $usec);
 332                  }
 333  
 334                  return $resource;
 335              }
 336          );
 337      }
 338  
 339      private function resolveHost(RequestInterface $request, array $options)
 340      {
 341          $uri = $request->getUri();
 342  
 343          if (isset($options['force_ip_resolve']) && !filter_var($uri->getHost(), FILTER_VALIDATE_IP)) {
 344              if ('v4' === $options['force_ip_resolve']) {
 345                  $records = dns_get_record($uri->getHost(), DNS_A);
 346                  if (!isset($records[0]['ip'])) {
 347                      throw new ConnectException(
 348                          sprintf(
 349                              "Could not resolve IPv4 address for host '%s'",
 350                              $uri->getHost()
 351                          ),
 352                          $request
 353                      );
 354                  }
 355                  $uri = $uri->withHost($records[0]['ip']);
 356              } elseif ('v6' === $options['force_ip_resolve']) {
 357                  $records = dns_get_record($uri->getHost(), DNS_AAAA);
 358                  if (!isset($records[0]['ipv6'])) {
 359                      throw new ConnectException(
 360                          sprintf(
 361                              "Could not resolve IPv6 address for host '%s'",
 362                              $uri->getHost()
 363                          ),
 364                          $request
 365                      );
 366                  }
 367                  $uri = $uri->withHost('[' . $records[0]['ipv6'] . ']');
 368              }
 369          }
 370  
 371          return $uri;
 372      }
 373  
 374      private function getDefaultContext(RequestInterface $request)
 375      {
 376          $headers = '';
 377          foreach ($request->getHeaders() as $name => $value) {
 378              foreach ($value as $val) {
 379                  $headers .= "$name: $val\r\n";
 380              }
 381          }
 382  
 383          $context = [
 384              'http' => [
 385                  'method'           => $request->getMethod(),
 386                  'header'           => $headers,
 387                  'protocol_version' => $request->getProtocolVersion(),
 388                  'ignore_errors'    => true,
 389                  'follow_location'  => 0,
 390              ],
 391          ];
 392  
 393          $body = (string) $request->getBody();
 394  
 395          if (!empty($body)) {
 396              $context['http']['content'] = $body;
 397              // Prevent the HTTP handler from adding a Content-Type header.
 398              if (!$request->hasHeader('Content-Type')) {
 399                  $context['http']['header'] .= "Content-Type:\r\n";
 400              }
 401          }
 402  
 403          $context['http']['header'] = rtrim($context['http']['header']);
 404  
 405          return $context;
 406      }
 407  
 408      private function add_proxy(RequestInterface $request, &$options, $value, &$params)
 409      {
 410          if (!is_array($value)) {
 411              $options['http']['proxy'] = $value;
 412          } else {
 413              $scheme = $request->getUri()->getScheme();
 414              if (isset($value[$scheme])) {
 415                  if (!isset($value['no'])
 416                      || !\GuzzleHttp\is_host_in_noproxy(
 417                          $request->getUri()->getHost(),
 418                          $value['no']
 419                      )
 420                  ) {
 421                      $options['http']['proxy'] = $value[$scheme];
 422                  }
 423              }
 424          }
 425      }
 426  
 427      private function add_timeout(RequestInterface $request, &$options, $value, &$params)
 428      {
 429          if ($value > 0) {
 430              $options['http']['timeout'] = $value;
 431          }
 432      }
 433  
 434      private function add_verify(RequestInterface $request, &$options, $value, &$params)
 435      {
 436          if ($value === true) {
 437              // PHP 5.6 or greater will find the system cert by default. When
 438              // < 5.6, use the Guzzle bundled cacert.
 439              if (PHP_VERSION_ID < 50600) {
 440                  $options['ssl']['cafile'] = \GuzzleHttp\default_ca_bundle();
 441              }
 442          } elseif (is_string($value)) {
 443              $options['ssl']['cafile'] = $value;
 444              if (!file_exists($value)) {
 445                  throw new \RuntimeException("SSL CA bundle not found: $value");
 446              }
 447          } elseif ($value === false) {
 448              $options['ssl']['verify_peer'] = false;
 449              $options['ssl']['verify_peer_name'] = false;
 450              return;
 451          } else {
 452              throw new \InvalidArgumentException('Invalid verify request option');
 453          }
 454  
 455          $options['ssl']['verify_peer'] = true;
 456          $options['ssl']['verify_peer_name'] = true;
 457          $options['ssl']['allow_self_signed'] = false;
 458      }
 459  
 460      private function add_cert(RequestInterface $request, &$options, $value, &$params)
 461      {
 462          if (is_array($value)) {
 463              $options['ssl']['passphrase'] = $value[1];
 464              $value = $value[0];
 465          }
 466  
 467          if (!file_exists($value)) {
 468              throw new \RuntimeException("SSL certificate not found: {$value}");
 469          }
 470  
 471          $options['ssl']['local_cert'] = $value;
 472      }
 473  
 474      private function add_progress(RequestInterface $request, &$options, $value, &$params)
 475      {
 476          $this->addNotification(
 477              $params,
 478              function ($code, $a, $b, $c, $transferred, $total) use ($value) {
 479                  if ($code == STREAM_NOTIFY_PROGRESS) {
 480                      $value($total, $transferred, null, null);
 481                  }
 482              }
 483          );
 484      }
 485  
 486      private function add_debug(RequestInterface $request, &$options, $value, &$params)
 487      {
 488          if ($value === false) {
 489              return;
 490          }
 491  
 492          static $map = [
 493              STREAM_NOTIFY_CONNECT       => 'CONNECT',
 494              STREAM_NOTIFY_AUTH_REQUIRED => 'AUTH_REQUIRED',
 495              STREAM_NOTIFY_AUTH_RESULT   => 'AUTH_RESULT',
 496              STREAM_NOTIFY_MIME_TYPE_IS  => 'MIME_TYPE_IS',
 497              STREAM_NOTIFY_FILE_SIZE_IS  => 'FILE_SIZE_IS',
 498              STREAM_NOTIFY_REDIRECTED    => 'REDIRECTED',
 499              STREAM_NOTIFY_PROGRESS      => 'PROGRESS',
 500              STREAM_NOTIFY_FAILURE       => 'FAILURE',
 501              STREAM_NOTIFY_COMPLETED     => 'COMPLETED',
 502              STREAM_NOTIFY_RESOLVE       => 'RESOLVE',
 503          ];
 504          static $args = ['severity', 'message', 'message_code',
 505              'bytes_transferred', 'bytes_max'];
 506  
 507          $value = \GuzzleHttp\debug_resource($value);
 508          $ident = $request->getMethod() . ' ' . $request->getUri()->withFragment('');
 509          $this->addNotification(
 510              $params,
 511              function () use ($ident, $value, $map, $args) {
 512                  $passed = func_get_args();
 513                  $code = array_shift($passed);
 514                  fprintf($value, '<%s> [%s] ', $ident, $map[$code]);
 515                  foreach (array_filter($passed) as $i => $v) {
 516                      fwrite($value, $args[$i] . ': "' . $v . '" ');
 517                  }
 518                  fwrite($value, "\n");
 519              }
 520          );
 521      }
 522  
 523      private function addNotification(array &$params, callable $notify)
 524      {
 525          // Wrap the existing function if needed.
 526          if (!isset($params['notification'])) {
 527              $params['notification'] = $notify;
 528          } else {
 529              $params['notification'] = $this->callArray([
 530                  $params['notification'],
 531                  $notify
 532              ]);
 533          }
 534      }
 535  
 536      private function callArray(array $functions)
 537      {
 538          return function () use ($functions) {
 539              $args = func_get_args();
 540              foreach ($functions as $fn) {
 541                  call_user_func_array($fn, $args);
 542              }
 543          };
 544      }
 545  }


Generated: Wed Nov 11 20:28:18 2020 Cross-referenced by PHPXref 0.7.1