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