PluginProbe ʕ •ᴥ•ʔ
ShareThis Dashboard for Google Analytics / 3.2.3
ShareThis Dashboard for Google Analytics v3.2.3
3.3.2 trunk 1.0.7 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.1 2.1.2 2.1.3 2.1.4 2.1.5 2.2.5 2.3.5 2.3.6 2.3.7 2.3.8 2.4.0 2.4.1 2.5.0 2.5.1 2.5.2 2.5.3 2.5.4 2.5.5 3.0.0 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 3.1.5 3.1.6 3.1.7 3.2.0 3.2.1 3.2.2 3.2.3 3.2.4 3.3.0 3.3.1
googleanalytics / lib / analytics-admin / vendor / guzzlehttp / guzzle / src / Handler / StreamHandler.php
googleanalytics / lib / analytics-admin / vendor / guzzlehttp / guzzle / src / Handler Last commit date
CurlFactory.php 3 years ago CurlFactoryInterface.php 3 years ago CurlHandler.php 3 years ago CurlMultiHandler.php 2 years ago EasyHandle.php 3 years ago HeaderProcessor.php 3 years ago MockHandler.php 3 years ago Proxy.php 3 years ago StreamHandler.php 3 years ago
StreamHandler.php
594 lines
1 <?php
2
3 namespace GuzzleHttp\Handler;
4
5 use GuzzleHttp\Exception\ConnectException;
6 use GuzzleHttp\Exception\RequestException;
7 use GuzzleHttp\Promise as P;
8 use GuzzleHttp\Promise\FulfilledPromise;
9 use GuzzleHttp\Promise\PromiseInterface;
10 use GuzzleHttp\Psr7;
11 use GuzzleHttp\TransferStats;
12 use GuzzleHttp\Utils;
13 use Psr\Http\Message\RequestInterface;
14 use Psr\Http\Message\ResponseInterface;
15 use Psr\Http\Message\StreamInterface;
16 use Psr\Http\Message\UriInterface;
17
18 /**
19 * HTTP handler that uses PHP's HTTP stream wrapper.
20 *
21 * @final
22 */
23 class StreamHandler
24 {
25 /**
26 * @var array
27 */
28 private $lastHeaders = [];
29
30 /**
31 * Sends an HTTP request.
32 *
33 * @param RequestInterface $request Request to send.
34 * @param array $options Request transfer options.
35 */
36 public function __invoke(RequestInterface $request, array $options): PromiseInterface
37 {
38 // Sleep if there is a delay specified.
39 if (isset($options['delay'])) {
40 \usleep($options['delay'] * 1000);
41 }
42
43 $startTime = isset($options['on_stats']) ? Utils::currentTime() : null;
44
45 try {
46 // Does not support the expect header.
47 $request = $request->withoutHeader('Expect');
48
49 // Append a content-length header if body size is zero to match
50 // cURL's behavior.
51 if (0 === $request->getBody()->getSize()) {
52 $request = $request->withHeader('Content-Length', '0');
53 }
54
55 return $this->createResponse(
56 $request,
57 $options,
58 $this->createStream($request, $options),
59 $startTime
60 );
61 } catch (\InvalidArgumentException $e) {
62 throw $e;
63 } catch (\Exception $e) {
64 // Determine if the error was a networking error.
65 $message = $e->getMessage();
66 // This list can probably get more comprehensive.
67 if (false !== \strpos($message, 'getaddrinfo') // DNS lookup failed
68 || false !== \strpos($message, 'Connection refused')
69 || false !== \strpos($message, "couldn't connect to host") // error on HHVM
70 || false !== \strpos($message, "connection attempt failed")
71 ) {
72 $e = new ConnectException($e->getMessage(), $request, $e);
73 } else {
74 $e = RequestException::wrapException($request, $e);
75 }
76 $this->invokeStats($options, $request, $startTime, null, $e);
77
78 return P\Create::rejectionFor($e);
79 }
80 }
81
82 private function invokeStats(
83 array $options,
84 RequestInterface $request,
85 ?float $startTime,
86 ResponseInterface $response = null,
87 \Throwable $error = null
88 ): void {
89 if (isset($options['on_stats'])) {
90 $stats = new TransferStats($request, $response, Utils::currentTime() - $startTime, $error, []);
91 ($options['on_stats'])($stats);
92 }
93 }
94
95 /**
96 * @param resource $stream
97 */
98 private function createResponse(RequestInterface $request, array $options, $stream, ?float $startTime): PromiseInterface
99 {
100 $hdrs = $this->lastHeaders;
101 $this->lastHeaders = [];
102
103 try {
104 [$ver, $status, $reason, $headers] = HeaderProcessor::parseHeaders($hdrs);
105 } catch (\Exception $e) {
106 return P\Create::rejectionFor(
107 new RequestException('An error was encountered while creating the response', $request, null, $e)
108 );
109 }
110
111 [$stream, $headers] = $this->checkDecode($options, $headers, $stream);
112 $stream = Psr7\Utils::streamFor($stream);
113 $sink = $stream;
114
115 if (\strcasecmp('HEAD', $request->getMethod())) {
116 $sink = $this->createSink($stream, $options);
117 }
118
119 try {
120 $response = new Psr7\Response($status, $headers, $sink, $ver, $reason);
121 } catch (\Exception $e) {
122 return P\Create::rejectionFor(
123 new RequestException('An error was encountered while creating the response', $request, null, $e)
124 );
125 }
126
127 if (isset($options['on_headers'])) {
128 try {
129 $options['on_headers']($response);
130 } catch (\Exception $e) {
131 return P\Create::rejectionFor(
132 new RequestException('An error was encountered during the on_headers event', $request, $response, $e)
133 );
134 }
135 }
136
137 // Do not drain when the request is a HEAD request because they have
138 // no body.
139 if ($sink !== $stream) {
140 $this->drain($stream, $sink, $response->getHeaderLine('Content-Length'));
141 }
142
143 $this->invokeStats($options, $request, $startTime, $response, null);
144
145 return new FulfilledPromise($response);
146 }
147
148 private function createSink(StreamInterface $stream, array $options): StreamInterface
149 {
150 if (!empty($options['stream'])) {
151 return $stream;
152 }
153
154 $sink = $options['sink'] ?? Psr7\Utils::tryFopen('php://temp', 'r+');
155
156 return \is_string($sink) ? new Psr7\LazyOpenStream($sink, 'w+') : Psr7\Utils::streamFor($sink);
157 }
158
159 /**
160 * @param resource $stream
161 */
162 private function checkDecode(array $options, array $headers, $stream): array
163 {
164 // Automatically decode responses when instructed.
165 if (!empty($options['decode_content'])) {
166 $normalizedKeys = Utils::normalizeHeaderKeys($headers);
167 if (isset($normalizedKeys['content-encoding'])) {
168 $encoding = $headers[$normalizedKeys['content-encoding']];
169 if ($encoding[0] === 'gzip' || $encoding[0] === 'deflate') {
170 $stream = new Psr7\InflateStream(Psr7\Utils::streamFor($stream));
171 $headers['x-encoded-content-encoding'] = $headers[$normalizedKeys['content-encoding']];
172
173 // Remove content-encoding header
174 unset($headers[$normalizedKeys['content-encoding']]);
175
176 // Fix content-length header
177 if (isset($normalizedKeys['content-length'])) {
178 $headers['x-encoded-content-length'] = $headers[$normalizedKeys['content-length']];
179 $length = (int) $stream->getSize();
180 if ($length === 0) {
181 unset($headers[$normalizedKeys['content-length']]);
182 } else {
183 $headers[$normalizedKeys['content-length']] = [$length];
184 }
185 }
186 }
187 }
188 }
189
190 return [$stream, $headers];
191 }
192
193 /**
194 * Drains the source stream into the "sink" client option.
195 *
196 * @param string $contentLength Header specifying the amount of
197 * data to read.
198 *
199 * @throws \RuntimeException when the sink option is invalid.
200 */
201 private function drain(StreamInterface $source, StreamInterface $sink, string $contentLength): StreamInterface
202 {
203 // If a content-length header is provided, then stop reading once
204 // that number of bytes has been read. This can prevent infinitely
205 // reading from a stream when dealing with servers that do not honor
206 // Connection: Close headers.
207 Psr7\Utils::copyToStream(
208 $source,
209 $sink,
210 (\strlen($contentLength) > 0 && (int) $contentLength > 0) ? (int) $contentLength : -1
211 );
212
213 $sink->seek(0);
214 $source->close();
215
216 return $sink;
217 }
218
219 /**
220 * Create a resource and check to ensure it was created successfully
221 *
222 * @param callable $callback Callable that returns stream resource
223 *
224 * @return resource
225 *
226 * @throws \RuntimeException on error
227 */
228 private function createResource(callable $callback)
229 {
230 $errors = [];
231 \set_error_handler(static function ($_, $msg, $file, $line) use (&$errors): bool {
232 $errors[] = [
233 'message' => $msg,
234 'file' => $file,
235 'line' => $line
236 ];
237 return true;
238 });
239
240 try {
241 $resource = $callback();
242 } finally {
243 \restore_error_handler();
244 }
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 /**
260 * @return resource
261 */
262 private function createStream(RequestInterface $request, array $options)
263 {
264 static $methods;
265 if (!$methods) {
266 $methods = \array_flip(\get_class_methods(__CLASS__));
267 }
268
269 if (!\in_array($request->getUri()->getScheme(), ['http', 'https'])) {
270 throw new RequestException(\sprintf("The scheme '%s' is not supported.", $request->getUri()->getScheme()), $request);
271 }
272
273 // HTTP/1.1 streams using the PHP stream wrapper require a
274 // Connection: close header
275 if ($request->getProtocolVersion() == '1.1'
276 && !$request->hasHeader('Connection')
277 ) {
278 $request = $request->withHeader('Connection', 'close');
279 }
280
281 // Ensure SSL is verified by default
282 if (!isset($options['verify'])) {
283 $options['verify'] = true;
284 }
285
286 $params = [];
287 $context = $this->getDefaultContext($request);
288
289 if (isset($options['on_headers']) && !\is_callable($options['on_headers'])) {
290 throw new \InvalidArgumentException('on_headers must be callable');
291 }
292
293 if (!empty($options)) {
294 foreach ($options as $key => $value) {
295 $method = "add_{$key}";
296 if (isset($methods[$method])) {
297 $this->{$method}($request, $context, $value, $params);
298 }
299 }
300 }
301
302 if (isset($options['stream_context'])) {
303 if (!\is_array($options['stream_context'])) {
304 throw new \InvalidArgumentException('stream_context must be an array');
305 }
306 $context = \array_replace_recursive($context, $options['stream_context']);
307 }
308
309 // Microsoft NTLM authentication only supported with curl handler
310 if (isset($options['auth'][2]) && 'ntlm' === $options['auth'][2]) {
311 throw new \InvalidArgumentException('Microsoft NTLM authentication only supported with curl handler');
312 }
313
314 $uri = $this->resolveHost($request, $options);
315
316 $contextResource = $this->createResource(
317 static 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, $contextResource, $context, $options, $request) {
324 $resource = @\fopen((string) $uri, 'r', false, $contextResource);
325 $this->lastHeaders = $http_response_header ?? [];
326
327 if (false === $resource) {
328 throw new ConnectException(sprintf('Connection refused for URI %s', $uri), $request, null, $context);
329 }
330
331 if (isset($options['read_timeout'])) {
332 $readTimeout = $options['read_timeout'];
333 $sec = (int) $readTimeout;
334 $usec = ($readTimeout - $sec) * 100000;
335 \stream_set_timeout($resource, $sec, $usec);
336 }
337
338 return $resource;
339 }
340 );
341 }
342
343 private function resolveHost(RequestInterface $request, array $options): UriInterface
344 {
345 $uri = $request->getUri();
346
347 if (isset($options['force_ip_resolve']) && !\filter_var($uri->getHost(), \FILTER_VALIDATE_IP)) {
348 if ('v4' === $options['force_ip_resolve']) {
349 $records = \dns_get_record($uri->getHost(), \DNS_A);
350 if (false === $records || !isset($records[0]['ip'])) {
351 throw new ConnectException(\sprintf("Could not resolve IPv4 address for host '%s'", $uri->getHost()), $request);
352 }
353 return $uri->withHost($records[0]['ip']);
354 }
355 if ('v6' === $options['force_ip_resolve']) {
356 $records = \dns_get_record($uri->getHost(), \DNS_AAAA);
357 if (false === $records || !isset($records[0]['ipv6'])) {
358 throw new ConnectException(\sprintf("Could not resolve IPv6 address for host '%s'", $uri->getHost()), $request);
359 }
360 return $uri->withHost('[' . $records[0]['ipv6'] . ']');
361 }
362 }
363
364 return $uri;
365 }
366
367 private function getDefaultContext(RequestInterface $request): array
368 {
369 $headers = '';
370 foreach ($request->getHeaders() as $name => $value) {
371 foreach ($value as $val) {
372 $headers .= "$name: $val\r\n";
373 }
374 }
375
376 $context = [
377 'http' => [
378 'method' => $request->getMethod(),
379 'header' => $headers,
380 'protocol_version' => $request->getProtocolVersion(),
381 'ignore_errors' => true,
382 'follow_location' => 0,
383 ],
384 'ssl' => [
385 'peer_name' => $request->getUri()->getHost(),
386 ],
387 ];
388
389 $body = (string) $request->getBody();
390
391 if (!empty($body)) {
392 $context['http']['content'] = $body;
393 // Prevent the HTTP handler from adding a Content-Type header.
394 if (!$request->hasHeader('Content-Type')) {
395 $context['http']['header'] .= "Content-Type:\r\n";
396 }
397 }
398
399 $context['http']['header'] = \rtrim($context['http']['header']);
400
401 return $context;
402 }
403
404 /**
405 * @param mixed $value as passed via Request transfer options.
406 */
407 private function add_proxy(RequestInterface $request, array &$options, $value, array &$params): void
408 {
409 $uri = null;
410
411 if (!\is_array($value)) {
412 $uri = $value;
413 } else {
414 $scheme = $request->getUri()->getScheme();
415 if (isset($value[$scheme])) {
416 if (!isset($value['no']) || !Utils::isHostInNoProxy($request->getUri()->getHost(), $value['no'])) {
417 $uri = $value[$scheme];
418 }
419 }
420 }
421
422 if (!$uri) {
423 return;
424 }
425
426 $parsed = $this->parse_proxy($uri);
427 $options['http']['proxy'] = $parsed['proxy'];
428
429 if ($parsed['auth']) {
430 if (!isset($options['http']['header'])) {
431 $options['http']['header'] = [];
432 }
433 $options['http']['header'] .= "\r\nProxy-Authorization: {$parsed['auth']}";
434 }
435 }
436
437 /**
438 * Parses the given proxy URL to make it compatible with the format PHP's stream context expects.
439 */
440 private function parse_proxy(string $url): array
441 {
442 $parsed = \parse_url($url);
443
444 if ($parsed !== false && isset($parsed['scheme']) && $parsed['scheme'] === 'http') {
445 if (isset($parsed['host']) && isset($parsed['port'])) {
446 $auth = null;
447 if (isset($parsed['user']) && isset($parsed['pass'])) {
448 $auth = \base64_encode("{$parsed['user']}:{$parsed['pass']}");
449 }
450
451 return [
452 'proxy' => "tcp://{$parsed['host']}:{$parsed['port']}",
453 'auth' => $auth ? "Basic {$auth}" : null,
454 ];
455 }
456 }
457
458 // Return proxy as-is.
459 return [
460 'proxy' => $url,
461 'auth' => null,
462 ];
463 }
464
465 /**
466 * @param mixed $value as passed via Request transfer options.
467 */
468 private function add_timeout(RequestInterface $request, array &$options, $value, array &$params): void
469 {
470 if ($value > 0) {
471 $options['http']['timeout'] = $value;
472 }
473 }
474
475 /**
476 * @param mixed $value as passed via Request transfer options.
477 */
478 private function add_verify(RequestInterface $request, array &$options, $value, array &$params): void
479 {
480 if ($value === false) {
481 $options['ssl']['verify_peer'] = false;
482 $options['ssl']['verify_peer_name'] = false;
483
484 return;
485 }
486
487 if (\is_string($value)) {
488 $options['ssl']['cafile'] = $value;
489 if (!\file_exists($value)) {
490 throw new \RuntimeException("SSL CA bundle not found: $value");
491 }
492 } elseif ($value !== true) {
493 throw new \InvalidArgumentException('Invalid verify request option');
494 }
495
496 $options['ssl']['verify_peer'] = true;
497 $options['ssl']['verify_peer_name'] = true;
498 $options['ssl']['allow_self_signed'] = false;
499 }
500
501 /**
502 * @param mixed $value as passed via Request transfer options.
503 */
504 private function add_cert(RequestInterface $request, array &$options, $value, array &$params): void
505 {
506 if (\is_array($value)) {
507 $options['ssl']['passphrase'] = $value[1];
508 $value = $value[0];
509 }
510
511 if (!\file_exists($value)) {
512 throw new \RuntimeException("SSL certificate not found: {$value}");
513 }
514
515 $options['ssl']['local_cert'] = $value;
516 }
517
518 /**
519 * @param mixed $value as passed via Request transfer options.
520 */
521 private function add_progress(RequestInterface $request, array &$options, $value, array &$params): void
522 {
523 self::addNotification(
524 $params,
525 static function ($code, $a, $b, $c, $transferred, $total) use ($value) {
526 if ($code == \STREAM_NOTIFY_PROGRESS) {
527 // The upload progress cannot be determined. Use 0 for cURL compatibility:
528 // https://curl.se/libcurl/c/CURLOPT_PROGRESSFUNCTION.html
529 $value($total, $transferred, 0, 0);
530 }
531 }
532 );
533 }
534
535 /**
536 * @param mixed $value as passed via Request transfer options.
537 */
538 private function add_debug(RequestInterface $request, array &$options, $value, array &$params): void
539 {
540 if ($value === false) {
541 return;
542 }
543
544 static $map = [
545 \STREAM_NOTIFY_CONNECT => 'CONNECT',
546 \STREAM_NOTIFY_AUTH_REQUIRED => 'AUTH_REQUIRED',
547 \STREAM_NOTIFY_AUTH_RESULT => 'AUTH_RESULT',
548 \STREAM_NOTIFY_MIME_TYPE_IS => 'MIME_TYPE_IS',
549 \STREAM_NOTIFY_FILE_SIZE_IS => 'FILE_SIZE_IS',
550 \STREAM_NOTIFY_REDIRECTED => 'REDIRECTED',
551 \STREAM_NOTIFY_PROGRESS => 'PROGRESS',
552 \STREAM_NOTIFY_FAILURE => 'FAILURE',
553 \STREAM_NOTIFY_COMPLETED => 'COMPLETED',
554 \STREAM_NOTIFY_RESOLVE => 'RESOLVE',
555 ];
556 static $args = ['severity', 'message', 'message_code', 'bytes_transferred', 'bytes_max'];
557
558 $value = Utils::debugResource($value);
559 $ident = $request->getMethod() . ' ' . $request->getUri()->withFragment('');
560 self::addNotification(
561 $params,
562 static function (int $code, ...$passed) use ($ident, $value, $map, $args): void {
563 \fprintf($value, '<%s> [%s] ', $ident, $map[$code]);
564 foreach (\array_filter($passed) as $i => $v) {
565 \fwrite($value, $args[$i] . ': "' . $v . '" ');
566 }
567 \fwrite($value, "\n");
568 }
569 );
570 }
571
572 private static function addNotification(array &$params, callable $notify): void
573 {
574 // Wrap the existing function if needed.
575 if (!isset($params['notification'])) {
576 $params['notification'] = $notify;
577 } else {
578 $params['notification'] = self::callArray([
579 $params['notification'],
580 $notify
581 ]);
582 }
583 }
584
585 private static function callArray(array $functions): callable
586 {
587 return static function (...$args) use ($functions) {
588 foreach ($functions as $fn) {
589 $fn(...$args);
590 }
591 };
592 }
593 }
594