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