primo commit
This commit is contained in:
		
							
								
								
									
										449
									
								
								libraries/vendor/symfony/http-client/Response/AmpResponse.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										449
									
								
								libraries/vendor/symfony/http-client/Response/AmpResponse.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,449 @@ | ||||
| <?php | ||||
|  | ||||
| /* | ||||
|  * This file is part of the Symfony package. | ||||
|  * | ||||
|  * (c) Fabien Potencier <fabien@symfony.com> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| namespace Symfony\Component\HttpClient\Response; | ||||
|  | ||||
| use Amp\ByteStream\StreamException; | ||||
| use Amp\CancellationTokenSource; | ||||
| use Amp\Coroutine; | ||||
| use Amp\Deferred; | ||||
| use Amp\Http\Client\HttpException; | ||||
| use Amp\Http\Client\Request; | ||||
| use Amp\Http\Client\Response; | ||||
| use Amp\Loop; | ||||
| use Amp\Promise; | ||||
| use Amp\Success; | ||||
| use Psr\Log\LoggerInterface; | ||||
| use Symfony\Component\HttpClient\Chunk\FirstChunk; | ||||
| use Symfony\Component\HttpClient\Chunk\InformationalChunk; | ||||
| use Symfony\Component\HttpClient\Exception\InvalidArgumentException; | ||||
| use Symfony\Component\HttpClient\Exception\TransportException; | ||||
| use Symfony\Component\HttpClient\HttpClientTrait; | ||||
| use Symfony\Component\HttpClient\Internal\AmpBody; | ||||
| use Symfony\Component\HttpClient\Internal\AmpClientState; | ||||
| use Symfony\Component\HttpClient\Internal\Canary; | ||||
| use Symfony\Component\HttpClient\Internal\ClientState; | ||||
| use Symfony\Contracts\HttpClient\ResponseInterface; | ||||
|  | ||||
| /** | ||||
|  * @author Nicolas Grekas <p@tchwork.com> | ||||
|  * | ||||
|  * @internal | ||||
|  */ | ||||
| final class AmpResponse implements ResponseInterface, StreamableInterface | ||||
| { | ||||
|     use CommonResponseTrait; | ||||
|     use TransportResponseTrait; | ||||
|  | ||||
|     private static string $nextId = 'a'; | ||||
|  | ||||
|     private AmpClientState $multi; | ||||
|     private ?array $options; | ||||
|     private \Closure $onProgress; | ||||
|  | ||||
|     private static ?string $delay = null; | ||||
|  | ||||
|     /** | ||||
|      * @internal | ||||
|      */ | ||||
|     public function __construct(AmpClientState $multi, Request $request, array $options, ?LoggerInterface $logger) | ||||
|     { | ||||
|         $this->multi = $multi; | ||||
|         $this->options = &$options; | ||||
|         $this->logger = $logger; | ||||
|         $this->timeout = $options['timeout']; | ||||
|         $this->shouldBuffer = $options['buffer']; | ||||
|  | ||||
|         if ($this->inflate = \extension_loaded('zlib') && !$request->hasHeader('accept-encoding')) { | ||||
|             $request->setHeader('Accept-Encoding', 'gzip'); | ||||
|         } | ||||
|  | ||||
|         $this->initializer = static fn (self $response) => null !== $response->options; | ||||
|  | ||||
|         $info = &$this->info; | ||||
|         $headers = &$this->headers; | ||||
|         $canceller = new CancellationTokenSource(); | ||||
|         $handle = &$this->handle; | ||||
|  | ||||
|         $info['url'] = (string) $request->getUri(); | ||||
|         $info['http_method'] = $request->getMethod(); | ||||
|         $info['start_time'] = null; | ||||
|         $info['redirect_url'] = null; | ||||
|         $info['original_url'] = $info['url']; | ||||
|         $info['redirect_time'] = 0.0; | ||||
|         $info['redirect_count'] = 0; | ||||
|         $info['size_upload'] = 0.0; | ||||
|         $info['size_download'] = 0.0; | ||||
|         $info['upload_content_length'] = -1.0; | ||||
|         $info['download_content_length'] = -1.0; | ||||
|         $info['user_data'] = $options['user_data']; | ||||
|         $info['max_duration'] = $options['max_duration']; | ||||
|         $info['debug'] = ''; | ||||
|  | ||||
|         $onProgress = $options['on_progress'] ?? static function () {}; | ||||
|         $onProgress = $this->onProgress = static function () use (&$info, $onProgress) { | ||||
|             $info['total_time'] = microtime(true) - $info['start_time']; | ||||
|             $onProgress((int) $info['size_download'], ((int) (1 + $info['download_content_length']) ?: 1) - 1, (array) $info); | ||||
|         }; | ||||
|  | ||||
|         $pauseDeferred = new Deferred(); | ||||
|         $pause = new Success(); | ||||
|  | ||||
|         $throttleWatcher = null; | ||||
|  | ||||
|         $this->id = $id = self::$nextId++; | ||||
|         Loop::defer(static function () use ($request, $multi, $id, &$info, &$headers, $canceller, &$options, $onProgress, &$handle, $logger, &$pause) { | ||||
|             return new Coroutine(self::generateResponse($request, $multi, $id, $info, $headers, $canceller, $options, $onProgress, $handle, $logger, $pause)); | ||||
|         }); | ||||
|  | ||||
|         $info['pause_handler'] = static function (float $duration) use (&$throttleWatcher, &$pauseDeferred, &$pause) { | ||||
|             if (null !== $throttleWatcher) { | ||||
|                 Loop::cancel($throttleWatcher); | ||||
|             } | ||||
|  | ||||
|             $pause = $pauseDeferred->promise(); | ||||
|  | ||||
|             if ($duration <= 0) { | ||||
|                 $deferred = $pauseDeferred; | ||||
|                 $pauseDeferred = new Deferred(); | ||||
|                 $deferred->resolve(); | ||||
|             } else { | ||||
|                 $throttleWatcher = Loop::delay(ceil(1000 * $duration), static function () use (&$pauseDeferred) { | ||||
|                     $deferred = $pauseDeferred; | ||||
|                     $pauseDeferred = new Deferred(); | ||||
|                     $deferred->resolve(); | ||||
|                 }); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         $multi->lastTimeout = null; | ||||
|         $multi->openHandles[$id] = $id; | ||||
|         ++$multi->responseCount; | ||||
|  | ||||
|         $this->canary = new Canary(static function () use ($canceller, $multi, $id) { | ||||
|             $canceller->cancel(); | ||||
|             unset($multi->openHandles[$id], $multi->handlesActivity[$id]); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     public function getInfo(?string $type = null): mixed | ||||
|     { | ||||
|         return null !== $type ? $this->info[$type] ?? null : $this->info; | ||||
|     } | ||||
|  | ||||
|     public function __sleep(): array | ||||
|     { | ||||
|         throw new \BadMethodCallException('Cannot serialize '.__CLASS__); | ||||
|     } | ||||
|  | ||||
|     public function __wakeup(): void | ||||
|     { | ||||
|         throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); | ||||
|     } | ||||
|  | ||||
|     public function __destruct() | ||||
|     { | ||||
|         try { | ||||
|             $this->doDestruct(); | ||||
|         } finally { | ||||
|             // Clear the DNS cache when all requests completed | ||||
|             if (0 >= --$this->multi->responseCount) { | ||||
|                 $this->multi->responseCount = 0; | ||||
|                 $this->multi->dnsCache = []; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private static function schedule(self $response, array &$runningResponses): void | ||||
|     { | ||||
|         if (isset($runningResponses[0])) { | ||||
|             $runningResponses[0][1][$response->id] = $response; | ||||
|         } else { | ||||
|             $runningResponses[0] = [$response->multi, [$response->id => $response]]; | ||||
|         } | ||||
|  | ||||
|         if (!isset($response->multi->openHandles[$response->id])) { | ||||
|             $response->multi->handlesActivity[$response->id][] = null; | ||||
|             $response->multi->handlesActivity[$response->id][] = null !== $response->info['error'] ? new TransportException($response->info['error']) : null; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param AmpClientState $multi | ||||
|      */ | ||||
|     private static function perform(ClientState $multi, ?array &$responses = null): void | ||||
|     { | ||||
|         if ($responses) { | ||||
|             foreach ($responses as $response) { | ||||
|                 try { | ||||
|                     if ($response->info['start_time']) { | ||||
|                         $response->info['total_time'] = microtime(true) - $response->info['start_time']; | ||||
|                         ($response->onProgress)(); | ||||
|                     } | ||||
|                 } catch (\Throwable $e) { | ||||
|                     $multi->handlesActivity[$response->id][] = null; | ||||
|                     $multi->handlesActivity[$response->id][] = $e; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param AmpClientState $multi | ||||
|      */ | ||||
|     private static function select(ClientState $multi, float $timeout): int | ||||
|     { | ||||
|         $timeout += hrtime(true) / 1E9; | ||||
|         self::$delay = Loop::defer(static function () use ($timeout) { | ||||
|             if (0 < $timeout -= hrtime(true) / 1E9) { | ||||
|                 self::$delay = Loop::delay(ceil(1000 * $timeout), Loop::stop(...)); | ||||
|             } else { | ||||
|                 Loop::stop(); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         Loop::run(); | ||||
|  | ||||
|         return null === self::$delay ? 1 : 0; | ||||
|     } | ||||
|  | ||||
|     private static function generateResponse(Request $request, AmpClientState $multi, string $id, array &$info, array &$headers, CancellationTokenSource $canceller, array &$options, \Closure $onProgress, &$handle, ?LoggerInterface $logger, Promise &$pause): \Generator | ||||
|     { | ||||
|         $request->setInformationalResponseHandler(static function (Response $response) use ($multi, $id, &$info, &$headers) { | ||||
|             self::addResponseHeaders($response, $info, $headers); | ||||
|             $multi->handlesActivity[$id][] = new InformationalChunk($response->getStatus(), $response->getHeaders()); | ||||
|             self::stopLoop(); | ||||
|         }); | ||||
|  | ||||
|         try { | ||||
|             /* @var Response $response */ | ||||
|             if (null === $response = yield from self::getPushedResponse($request, $multi, $info, $headers, $options, $logger)) { | ||||
|                 $logger?->info(sprintf('Request: "%s %s"', $info['http_method'], $info['url'])); | ||||
|  | ||||
|                 $response = yield from self::followRedirects($request, $multi, $info, $headers, $canceller, $options, $onProgress, $handle, $logger, $pause); | ||||
|             } | ||||
|  | ||||
|             $options = null; | ||||
|  | ||||
|             $multi->handlesActivity[$id][] = new FirstChunk(); | ||||
|  | ||||
|             if ('HEAD' === $response->getRequest()->getMethod() || \in_array($info['http_code'], [204, 304], true)) { | ||||
|                 $multi->handlesActivity[$id][] = null; | ||||
|                 $multi->handlesActivity[$id][] = null; | ||||
|                 self::stopLoop(); | ||||
|  | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             if ($response->hasHeader('content-length')) { | ||||
|                 $info['download_content_length'] = (float) $response->getHeader('content-length'); | ||||
|             } | ||||
|  | ||||
|             $body = $response->getBody(); | ||||
|  | ||||
|             while (true) { | ||||
|                 self::stopLoop(); | ||||
|  | ||||
|                 yield $pause; | ||||
|  | ||||
|                 if (null === $data = yield $body->read()) { | ||||
|                     break; | ||||
|                 } | ||||
|  | ||||
|                 $info['size_download'] += \strlen($data); | ||||
|                 $multi->handlesActivity[$id][] = $data; | ||||
|             } | ||||
|  | ||||
|             $multi->handlesActivity[$id][] = null; | ||||
|             $multi->handlesActivity[$id][] = null; | ||||
|         } catch (\Throwable $e) { | ||||
|             $multi->handlesActivity[$id][] = null; | ||||
|             $multi->handlesActivity[$id][] = $e; | ||||
|         } finally { | ||||
|             $info['download_content_length'] = $info['size_download']; | ||||
|         } | ||||
|  | ||||
|         self::stopLoop(); | ||||
|     } | ||||
|  | ||||
|     private static function followRedirects(Request $originRequest, AmpClientState $multi, array &$info, array &$headers, CancellationTokenSource $canceller, array $options, \Closure $onProgress, &$handle, ?LoggerInterface $logger, Promise &$pause): \Generator | ||||
|     { | ||||
|         yield $pause; | ||||
|  | ||||
|         $originRequest->setBody(new AmpBody($options['body'], $info, $onProgress)); | ||||
|         $response = yield $multi->request($options, $originRequest, $canceller->getToken(), $info, $onProgress, $handle); | ||||
|         $previousUrl = null; | ||||
|  | ||||
|         while (true) { | ||||
|             self::addResponseHeaders($response, $info, $headers); | ||||
|             $status = $response->getStatus(); | ||||
|  | ||||
|             if (!\in_array($status, [301, 302, 303, 307, 308], true) || null === $location = $response->getHeader('location')) { | ||||
|                 return $response; | ||||
|             } | ||||
|  | ||||
|             $urlResolver = new class() { | ||||
|                 use HttpClientTrait { | ||||
|                     parseUrl as public; | ||||
|                     resolveUrl as public; | ||||
|                 } | ||||
|             }; | ||||
|  | ||||
|             try { | ||||
|                 $previousUrl ??= $urlResolver::parseUrl($info['url']); | ||||
|                 $location = $urlResolver::parseUrl($location); | ||||
|                 $location = $urlResolver::resolveUrl($location, $previousUrl); | ||||
|                 $info['redirect_url'] = implode('', $location); | ||||
|             } catch (InvalidArgumentException) { | ||||
|                 return $response; | ||||
|             } | ||||
|  | ||||
|             if (0 >= $options['max_redirects'] || $info['redirect_count'] >= $options['max_redirects']) { | ||||
|                 return $response; | ||||
|             } | ||||
|  | ||||
|             $logger?->info(sprintf('Redirecting: "%s %s"', $status, $info['url'])); | ||||
|  | ||||
|             try { | ||||
|                 // Discard body of redirects | ||||
|                 while (null !== yield $response->getBody()->read()) { | ||||
|                 } | ||||
|             } catch (HttpException|StreamException) { | ||||
|                 // Ignore streaming errors on previous responses | ||||
|             } | ||||
|  | ||||
|             ++$info['redirect_count']; | ||||
|             $info['url'] = $info['redirect_url']; | ||||
|             $info['redirect_url'] = null; | ||||
|             $previousUrl = $location; | ||||
|  | ||||
|             $request = new Request($info['url'], $info['http_method']); | ||||
|             $request->setProtocolVersions($originRequest->getProtocolVersions()); | ||||
|             $request->setTcpConnectTimeout($originRequest->getTcpConnectTimeout()); | ||||
|             $request->setTlsHandshakeTimeout($originRequest->getTlsHandshakeTimeout()); | ||||
|             $request->setTransferTimeout($originRequest->getTransferTimeout()); | ||||
|  | ||||
|             if (\in_array($status, [301, 302, 303], true)) { | ||||
|                 $originRequest->removeHeader('transfer-encoding'); | ||||
|                 $originRequest->removeHeader('content-length'); | ||||
|                 $originRequest->removeHeader('content-type'); | ||||
|  | ||||
|                 // Do like curl and browsers: turn POST to GET on 301, 302 and 303 | ||||
|                 if ('POST' === $response->getRequest()->getMethod() || 303 === $status) { | ||||
|                     $info['http_method'] = 'HEAD' === $response->getRequest()->getMethod() ? 'HEAD' : 'GET'; | ||||
|                     $request->setMethod($info['http_method']); | ||||
|                 } | ||||
|             } else { | ||||
|                 $request->setBody(AmpBody::rewind($response->getRequest()->getBody())); | ||||
|             } | ||||
|  | ||||
|             foreach ($originRequest->getRawHeaders() as [$name, $value]) { | ||||
|                 $request->addHeader($name, $value); | ||||
|             } | ||||
|  | ||||
|             if ($request->getUri()->getAuthority() !== $originRequest->getUri()->getAuthority()) { | ||||
|                 $request->removeHeader('authorization'); | ||||
|                 $request->removeHeader('cookie'); | ||||
|                 $request->removeHeader('host'); | ||||
|             } | ||||
|  | ||||
|             yield $pause; | ||||
|  | ||||
|             $response = yield $multi->request($options, $request, $canceller->getToken(), $info, $onProgress, $handle); | ||||
|             $info['redirect_time'] = microtime(true) - $info['start_time']; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private static function addResponseHeaders(Response $response, array &$info, array &$headers): void | ||||
|     { | ||||
|         $info['http_code'] = $response->getStatus(); | ||||
|  | ||||
|         if ($headers) { | ||||
|             $info['debug'] .= "< \r\n"; | ||||
|             $headers = []; | ||||
|         } | ||||
|  | ||||
|         $h = sprintf('HTTP/%s %s %s', $response->getProtocolVersion(), $response->getStatus(), $response->getReason()); | ||||
|         $info['debug'] .= "< {$h}\r\n"; | ||||
|         $info['response_headers'][] = $h; | ||||
|  | ||||
|         foreach ($response->getRawHeaders() as [$name, $value]) { | ||||
|             $headers[strtolower($name)][] = $value; | ||||
|             $h = $name.': '.$value; | ||||
|             $info['debug'] .= "< {$h}\r\n"; | ||||
|             $info['response_headers'][] = $h; | ||||
|         } | ||||
|  | ||||
|         $info['debug'] .= "< \r\n"; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Accepts pushed responses only if their headers related to authentication match the request. | ||||
|      */ | ||||
|     private static function getPushedResponse(Request $request, AmpClientState $multi, array &$info, array &$headers, array $options, ?LoggerInterface $logger): \Generator | ||||
|     { | ||||
|         if ('' !== $options['body']) { | ||||
|             return null; | ||||
|         } | ||||
|  | ||||
|         $authority = $request->getUri()->getAuthority(); | ||||
|  | ||||
|         foreach ($multi->pushedResponses[$authority] ?? [] as $i => [$pushedUrl, $pushDeferred, $pushedRequest, $pushedResponse, $parentOptions]) { | ||||
|             if ($info['url'] !== $pushedUrl || $info['http_method'] !== $pushedRequest->getMethod()) { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             foreach ($parentOptions as $k => $v) { | ||||
|                 if ($options[$k] !== $v) { | ||||
|                     continue 2; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             foreach (['authorization', 'cookie', 'range', 'proxy-authorization'] as $k) { | ||||
|                 if ($pushedRequest->getHeaderArray($k) !== $request->getHeaderArray($k)) { | ||||
|                     continue 2; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             $response = yield $pushedResponse; | ||||
|  | ||||
|             foreach ($response->getHeaderArray('vary') as $vary) { | ||||
|                 foreach (preg_split('/\s*+,\s*+/', $vary) as $v) { | ||||
|                     if ('*' === $v || ($pushedRequest->getHeaderArray($v) !== $request->getHeaderArray($v) && 'accept-encoding' !== strtolower($v))) { | ||||
|                         $logger?->debug(sprintf('Skipping pushed response: "%s"', $info['url'])); | ||||
|                         continue 3; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             $pushDeferred->resolve(); | ||||
|             $logger?->debug(sprintf('Accepting pushed response: "%s %s"', $info['http_method'], $info['url'])); | ||||
|             self::addResponseHeaders($response, $info, $headers); | ||||
|             unset($multi->pushedResponses[$authority][$i]); | ||||
|  | ||||
|             if (!$multi->pushedResponses[$authority]) { | ||||
|                 unset($multi->pushedResponses[$authority]); | ||||
|             } | ||||
|  | ||||
|             return $response; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private static function stopLoop(): void | ||||
|     { | ||||
|         if (null !== self::$delay) { | ||||
|             Loop::cancel(self::$delay); | ||||
|             self::$delay = null; | ||||
|         } | ||||
|  | ||||
|         Loop::defer(Loop::stop(...)); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										200
									
								
								libraries/vendor/symfony/http-client/Response/AsyncContext.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										200
									
								
								libraries/vendor/symfony/http-client/Response/AsyncContext.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,200 @@ | ||||
| <?php | ||||
|  | ||||
| /* | ||||
|  * This file is part of the Symfony package. | ||||
|  * | ||||
|  * (c) Fabien Potencier <fabien@symfony.com> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| namespace Symfony\Component\HttpClient\Response; | ||||
|  | ||||
| use Symfony\Component\HttpClient\Chunk\DataChunk; | ||||
| use Symfony\Component\HttpClient\Chunk\LastChunk; | ||||
| use Symfony\Component\HttpClient\Exception\TransportException; | ||||
| use Symfony\Contracts\HttpClient\ChunkInterface; | ||||
| use Symfony\Contracts\HttpClient\HttpClientInterface; | ||||
| use Symfony\Contracts\HttpClient\ResponseInterface; | ||||
|  | ||||
| /** | ||||
|  * A DTO to work with AsyncResponse. | ||||
|  * | ||||
|  * @author Nicolas Grekas <p@tchwork.com> | ||||
|  */ | ||||
| final class AsyncContext | ||||
| { | ||||
|     /** @var callable|null */ | ||||
|     private $passthru; | ||||
|     private HttpClientInterface $client; | ||||
|     private ResponseInterface $response; | ||||
|     private array $info = []; | ||||
|     /** @var resource|null */ | ||||
|     private $content; | ||||
|     private int $offset; | ||||
|  | ||||
|     /** | ||||
|      * @param resource|null $content | ||||
|      */ | ||||
|     public function __construct(?callable &$passthru, HttpClientInterface $client, ResponseInterface &$response, array &$info, $content, int $offset) | ||||
|     { | ||||
|         $this->passthru = &$passthru; | ||||
|         $this->client = $client; | ||||
|         $this->response = &$response; | ||||
|         $this->info = &$info; | ||||
|         $this->content = $content; | ||||
|         $this->offset = $offset; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the HTTP status without consuming the response. | ||||
|      */ | ||||
|     public function getStatusCode(): int | ||||
|     { | ||||
|         return $this->response->getInfo('http_code'); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the headers without consuming the response. | ||||
|      */ | ||||
|     public function getHeaders(): array | ||||
|     { | ||||
|         $headers = []; | ||||
|  | ||||
|         foreach ($this->response->getInfo('response_headers') as $h) { | ||||
|             if (11 <= \strlen($h) && '/' === $h[4] && preg_match('#^HTTP/\d+(?:\.\d+)? ([123456789]\d\d)(?: |$)#', $h, $m)) { | ||||
|                 $headers = []; | ||||
|             } elseif (2 === \count($m = explode(':', $h, 2))) { | ||||
|                 $headers[strtolower($m[0])][] = ltrim($m[1]); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return $headers; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @return resource|null The PHP stream resource where the content is buffered, if it is | ||||
|      */ | ||||
|     public function getContent() | ||||
|     { | ||||
|         return $this->content; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Creates a new chunk of content. | ||||
|      */ | ||||
|     public function createChunk(string $data): ChunkInterface | ||||
|     { | ||||
|         return new DataChunk($this->offset, $data); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Pauses the request for the given number of seconds. | ||||
|      */ | ||||
|     public function pause(float $duration): void | ||||
|     { | ||||
|         if (\is_callable($pause = $this->response->getInfo('pause_handler'))) { | ||||
|             $pause($duration); | ||||
|         } elseif (0 < $duration) { | ||||
|             usleep((int) (1E6 * $duration)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Cancels the request and returns the last chunk to yield. | ||||
|      */ | ||||
|     public function cancel(): ChunkInterface | ||||
|     { | ||||
|         $this->info['canceled'] = true; | ||||
|         $this->info['error'] = 'Response has been canceled.'; | ||||
|         $this->response->cancel(); | ||||
|  | ||||
|         return new LastChunk(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the current info of the response. | ||||
|      */ | ||||
|     public function getInfo(?string $type = null): mixed | ||||
|     { | ||||
|         if (null !== $type) { | ||||
|             return $this->info[$type] ?? $this->response->getInfo($type); | ||||
|         } | ||||
|  | ||||
|         return $this->info + $this->response->getInfo(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Attaches an info to the response. | ||||
|      * | ||||
|      * @return $this | ||||
|      */ | ||||
|     public function setInfo(string $type, mixed $value): static | ||||
|     { | ||||
|         if ('canceled' === $type && $value !== $this->info['canceled']) { | ||||
|             throw new \LogicException('You cannot set the "canceled" info directly.'); | ||||
|         } | ||||
|  | ||||
|         if (null === $value) { | ||||
|             unset($this->info[$type]); | ||||
|         } else { | ||||
|             $this->info[$type] = $value; | ||||
|         } | ||||
|  | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the currently processed response. | ||||
|      */ | ||||
|     public function getResponse(): ResponseInterface | ||||
|     { | ||||
|         return $this->response; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Replaces the currently processed response by doing a new request. | ||||
|      */ | ||||
|     public function replaceRequest(string $method, string $url, array $options = []): ResponseInterface | ||||
|     { | ||||
|         $this->info['previous_info'][] = $info = $this->response->getInfo(); | ||||
|         if (null !== $onProgress = $options['on_progress'] ?? null) { | ||||
|             $thisInfo = &$this->info; | ||||
|             $options['on_progress'] = static function (int $dlNow, int $dlSize, array $info) use (&$thisInfo, $onProgress) { | ||||
|                 $onProgress($dlNow, $dlSize, $thisInfo + $info); | ||||
|             }; | ||||
|         } | ||||
|         if (0 < ($info['max_duration'] ?? 0) && 0 < ($info['total_time'] ?? 0)) { | ||||
|             if (0 >= $options['max_duration'] = $info['max_duration'] - $info['total_time']) { | ||||
|                 throw new TransportException(sprintf('Max duration was reached for "%s".', $info['url'])); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return $this->response = $this->client->request($method, $url, ['buffer' => false] + $options); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Replaces the currently processed response by another one. | ||||
|      */ | ||||
|     public function replaceResponse(ResponseInterface $response): ResponseInterface | ||||
|     { | ||||
|         $this->info['previous_info'][] = $this->response->getInfo(); | ||||
|  | ||||
|         return $this->response = $response; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Replaces or removes the chunk filter iterator. | ||||
|      * | ||||
|      * @param ?callable(ChunkInterface, self): ?\Iterator $passthru | ||||
|      */ | ||||
|     public function passthru(?callable $passthru = null): void | ||||
|     { | ||||
|         $this->passthru = $passthru ?? static function ($chunk, $context) { | ||||
|             $context->passthru = null; | ||||
|  | ||||
|             yield $chunk; | ||||
|         }; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										477
									
								
								libraries/vendor/symfony/http-client/Response/AsyncResponse.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										477
									
								
								libraries/vendor/symfony/http-client/Response/AsyncResponse.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,477 @@ | ||||
| <?php | ||||
|  | ||||
| /* | ||||
|  * This file is part of the Symfony package. | ||||
|  * | ||||
|  * (c) Fabien Potencier <fabien@symfony.com> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| namespace Symfony\Component\HttpClient\Response; | ||||
|  | ||||
| use Symfony\Component\HttpClient\Chunk\ErrorChunk; | ||||
| use Symfony\Component\HttpClient\Chunk\FirstChunk; | ||||
| use Symfony\Component\HttpClient\Chunk\LastChunk; | ||||
| use Symfony\Component\HttpClient\Exception\TransportException; | ||||
| use Symfony\Contracts\HttpClient\ChunkInterface; | ||||
| use Symfony\Contracts\HttpClient\Exception\ExceptionInterface; | ||||
| use Symfony\Contracts\HttpClient\Exception\HttpExceptionInterface; | ||||
| use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; | ||||
| use Symfony\Contracts\HttpClient\HttpClientInterface; | ||||
| use Symfony\Contracts\HttpClient\ResponseInterface; | ||||
|  | ||||
| /** | ||||
|  * Provides a single extension point to process a response's content stream. | ||||
|  * | ||||
|  * @author Nicolas Grekas <p@tchwork.com> | ||||
|  */ | ||||
| class AsyncResponse implements ResponseInterface, StreamableInterface | ||||
| { | ||||
|     use CommonResponseTrait; | ||||
|  | ||||
|     private const FIRST_CHUNK_YIELDED = 1; | ||||
|     private const LAST_CHUNK_YIELDED = 2; | ||||
|  | ||||
|     private ?HttpClientInterface $client; | ||||
|     private ResponseInterface $response; | ||||
|     private array $info = ['canceled' => false]; | ||||
|     /** @var callable|null */ | ||||
|     private $passthru; | ||||
|     private ?\Iterator $stream = null; | ||||
|     private ?int $yieldedState = null; | ||||
|  | ||||
|     /** | ||||
|      * @param ?callable(ChunkInterface, AsyncContext): ?\Iterator $passthru | ||||
|      */ | ||||
|     public function __construct(HttpClientInterface $client, string $method, string $url, array $options, ?callable $passthru = null) | ||||
|     { | ||||
|         $this->client = $client; | ||||
|         $this->shouldBuffer = $options['buffer'] ?? true; | ||||
|  | ||||
|         if (null !== $onProgress = $options['on_progress'] ?? null) { | ||||
|             $thisInfo = &$this->info; | ||||
|             $options['on_progress'] = static function (int $dlNow, int $dlSize, array $info) use (&$thisInfo, $onProgress) { | ||||
|                 $onProgress($dlNow, $dlSize, $thisInfo + $info); | ||||
|             }; | ||||
|         } | ||||
|         $this->response = $client->request($method, $url, ['buffer' => false] + $options); | ||||
|         $this->passthru = $passthru; | ||||
|         $this->initializer = static function (self $response, ?float $timeout = null) { | ||||
|             if (null === $response->shouldBuffer) { | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|             while (true) { | ||||
|                 foreach (self::stream([$response], $timeout) as $chunk) { | ||||
|                     if ($chunk->isTimeout() && $response->passthru) { | ||||
|                         // Timeouts thrown during initialization are transport errors | ||||
|                         foreach (self::passthru($response->client, $response, new ErrorChunk($response->offset, new TransportException($chunk->getError()))) as $chunk) { | ||||
|                             if ($chunk->isFirst()) { | ||||
|                                 return false; | ||||
|                             } | ||||
|                         } | ||||
|  | ||||
|                         continue 2; | ||||
|                     } | ||||
|  | ||||
|                     if ($chunk->isFirst()) { | ||||
|                         return false; | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 return false; | ||||
|             } | ||||
|         }; | ||||
|         if (\array_key_exists('user_data', $options)) { | ||||
|             $this->info['user_data'] = $options['user_data']; | ||||
|         } | ||||
|         if (\array_key_exists('max_duration', $options)) { | ||||
|             $this->info['max_duration'] = $options['max_duration']; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public function getStatusCode(): int | ||||
|     { | ||||
|         if ($this->initializer) { | ||||
|             self::initialize($this); | ||||
|         } | ||||
|  | ||||
|         return $this->response->getStatusCode(); | ||||
|     } | ||||
|  | ||||
|     public function getHeaders(bool $throw = true): array | ||||
|     { | ||||
|         if ($this->initializer) { | ||||
|             self::initialize($this); | ||||
|         } | ||||
|  | ||||
|         $headers = $this->response->getHeaders(false); | ||||
|  | ||||
|         if ($throw) { | ||||
|             $this->checkStatusCode(); | ||||
|         } | ||||
|  | ||||
|         return $headers; | ||||
|     } | ||||
|  | ||||
|     public function getInfo(?string $type = null): mixed | ||||
|     { | ||||
|         if (null !== $type) { | ||||
|             return $this->info[$type] ?? $this->response->getInfo($type); | ||||
|         } | ||||
|  | ||||
|         return $this->info + $this->response->getInfo(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @return resource | ||||
|      */ | ||||
|     public function toStream(bool $throw = true) | ||||
|     { | ||||
|         if ($throw) { | ||||
|             // Ensure headers arrived | ||||
|             $this->getHeaders(true); | ||||
|         } | ||||
|  | ||||
|         $handle = function () { | ||||
|             $stream = $this->response instanceof StreamableInterface ? $this->response->toStream(false) : StreamWrapper::createResource($this->response); | ||||
|  | ||||
|             return stream_get_meta_data($stream)['wrapper_data']->stream_cast(\STREAM_CAST_FOR_SELECT); | ||||
|         }; | ||||
|  | ||||
|         $stream = StreamWrapper::createResource($this); | ||||
|         stream_get_meta_data($stream)['wrapper_data'] | ||||
|             ->bindHandles($handle, $this->content); | ||||
|  | ||||
|         return $stream; | ||||
|     } | ||||
|  | ||||
|     public function cancel(): void | ||||
|     { | ||||
|         if ($this->info['canceled']) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         $this->info['canceled'] = true; | ||||
|         $this->info['error'] = 'Response has been canceled.'; | ||||
|         $this->close(); | ||||
|         $client = $this->client; | ||||
|         $this->client = null; | ||||
|  | ||||
|         if (!$this->passthru) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         try { | ||||
|             foreach (self::passthru($client, $this, new LastChunk()) as $chunk) { | ||||
|                 // no-op | ||||
|             } | ||||
|  | ||||
|             $this->passthru = null; | ||||
|         } catch (ExceptionInterface) { | ||||
|             // ignore any errors when canceling | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public function __destruct() | ||||
|     { | ||||
|         $httpException = null; | ||||
|  | ||||
|         if ($this->initializer && null === $this->getInfo('error')) { | ||||
|             try { | ||||
|                 self::initialize($this, -0.0); | ||||
|                 $this->getHeaders(true); | ||||
|             } catch (HttpExceptionInterface $httpException) { | ||||
|                 // no-op | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if ($this->passthru && null === $this->getInfo('error')) { | ||||
|             $this->info['canceled'] = true; | ||||
|  | ||||
|             try { | ||||
|                 foreach (self::passthru($this->client, $this, new LastChunk()) as $chunk) { | ||||
|                     // no-op | ||||
|                 } | ||||
|             } catch (ExceptionInterface) { | ||||
|                 // ignore any errors when destructing | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (null !== $httpException) { | ||||
|             throw $httpException; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @internal | ||||
|      */ | ||||
|     public static function stream(iterable $responses, ?float $timeout = null, ?string $class = null): \Generator | ||||
|     { | ||||
|         while ($responses) { | ||||
|             $wrappedResponses = []; | ||||
|             $asyncMap = new \SplObjectStorage(); | ||||
|             $client = null; | ||||
|  | ||||
|             foreach ($responses as $r) { | ||||
|                 if (!$r instanceof self) { | ||||
|                     throw new \TypeError(sprintf('"%s::stream()" expects parameter 1 to be an iterable of AsyncResponse objects, "%s" given.', $class ?? static::class, get_debug_type($r))); | ||||
|                 } | ||||
|  | ||||
|                 if (null !== $e = $r->info['error'] ?? null) { | ||||
|                     yield $r => $chunk = new ErrorChunk($r->offset, new TransportException($e)); | ||||
|                     $chunk->didThrow() ?: $chunk->getContent(); | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 if (null === $client) { | ||||
|                     $client = $r->client; | ||||
|                 } elseif ($r->client !== $client) { | ||||
|                     throw new TransportException('Cannot stream AsyncResponse objects with many clients.'); | ||||
|                 } | ||||
|  | ||||
|                 $asyncMap[$r->response] = $r; | ||||
|                 $wrappedResponses[] = $r->response; | ||||
|  | ||||
|                 if ($r->stream) { | ||||
|                     yield from self::passthruStream($response = $r->response, $r, new FirstChunk(), $asyncMap); | ||||
|  | ||||
|                     if (!isset($asyncMap[$response])) { | ||||
|                         array_pop($wrappedResponses); | ||||
|                     } | ||||
|  | ||||
|                     if ($r->response !== $response && !isset($asyncMap[$r->response])) { | ||||
|                         $asyncMap[$r->response] = $r; | ||||
|                         $wrappedResponses[] = $r->response; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (!$client || !$wrappedResponses) { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             foreach ($client->stream($wrappedResponses, $timeout) as $response => $chunk) { | ||||
|                 $r = $asyncMap[$response]; | ||||
|  | ||||
|                 if (null === $chunk->getError()) { | ||||
|                     if ($chunk->isFirst()) { | ||||
|                         // Ensure no exception is thrown on destruct for the wrapped response | ||||
|                         $r->response->getStatusCode(); | ||||
|                     } elseif (0 === $r->offset && null === $r->content && $chunk->isLast()) { | ||||
|                         $r->content = fopen('php://memory', 'w+'); | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 if (!$r->passthru) { | ||||
|                     if (null !== $chunk->getError() || $chunk->isLast()) { | ||||
|                         unset($asyncMap[$response]); | ||||
|                     } elseif (null !== $r->content && '' !== ($content = $chunk->getContent()) && \strlen($content) !== fwrite($r->content, $content)) { | ||||
|                         $chunk = new ErrorChunk($r->offset, new TransportException(sprintf('Failed writing %d bytes to the response buffer.', \strlen($content)))); | ||||
|                         $r->info['error'] = $chunk->getError(); | ||||
|                         $r->response->cancel(); | ||||
|                     } | ||||
|  | ||||
|                     yield $r => $chunk; | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 if (null !== $chunk->getError()) { | ||||
|                     // no-op | ||||
|                 } elseif ($chunk->isFirst()) { | ||||
|                     $r->yieldedState = self::FIRST_CHUNK_YIELDED; | ||||
|                 } elseif (self::FIRST_CHUNK_YIELDED !== $r->yieldedState && null === $chunk->getInformationalStatus()) { | ||||
|                     throw new \LogicException(sprintf('Instance of "%s" is already consumed and cannot be managed by "%s". A decorated client should not call any of the response\'s methods in its "request()" method.', get_debug_type($response), $class ?? static::class)); | ||||
|                 } | ||||
|  | ||||
|                 foreach (self::passthru($r->client, $r, $chunk, $asyncMap) as $chunk) { | ||||
|                     yield $r => $chunk; | ||||
|                 } | ||||
|  | ||||
|                 if ($r->response !== $response && isset($asyncMap[$response])) { | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (null === $chunk->getError() && $chunk->isLast()) { | ||||
|                 $r->yieldedState = self::LAST_CHUNK_YIELDED; | ||||
|             } | ||||
|             if (null === $chunk->getError() && self::LAST_CHUNK_YIELDED !== $r->yieldedState && $r->response === $response && null !== $r->client) { | ||||
|                 throw new \LogicException('A chunk passthru must yield an "isLast()" chunk before ending a stream.'); | ||||
|             } | ||||
|  | ||||
|             $responses = []; | ||||
|             foreach ($asyncMap as $response) { | ||||
|                 $r = $asyncMap[$response]; | ||||
|  | ||||
|                 if (null !== $r->client) { | ||||
|                     $responses[] = $asyncMap[$response]; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param \SplObjectStorage<ResponseInterface, AsyncResponse>|null $asyncMap | ||||
|      */ | ||||
|     private static function passthru(HttpClientInterface $client, self $r, ChunkInterface $chunk, ?\SplObjectStorage $asyncMap = null): \Generator | ||||
|     { | ||||
|         $r->stream = null; | ||||
|         $response = $r->response; | ||||
|         $context = new AsyncContext($r->passthru, $client, $r->response, $r->info, $r->content, $r->offset); | ||||
|         if (null === $stream = ($r->passthru)($chunk, $context)) { | ||||
|             if ($r->response === $response && (null !== $chunk->getError() || $chunk->isLast())) { | ||||
|                 throw new \LogicException('A chunk passthru cannot swallow the last chunk.'); | ||||
|             } | ||||
|  | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         if (!$stream instanceof \Iterator) { | ||||
|             throw new \LogicException(sprintf('A chunk passthru must return an "Iterator", "%s" returned.', get_debug_type($stream))); | ||||
|         } | ||||
|         $r->stream = $stream; | ||||
|  | ||||
|         yield from self::passthruStream($response, $r, null, $asyncMap); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param \SplObjectStorage<ResponseInterface, AsyncResponse>|null $asyncMap | ||||
|      */ | ||||
|     private static function passthruStream(ResponseInterface $response, self $r, ?ChunkInterface $chunk, ?\SplObjectStorage $asyncMap): \Generator | ||||
|     { | ||||
|         while (true) { | ||||
|             try { | ||||
|                 if (null !== $chunk && $r->stream) { | ||||
|                     $r->stream->next(); | ||||
|                 } | ||||
|  | ||||
|                 if (!$r->stream || !$r->stream->valid() || !$r->stream) { | ||||
|                     $r->stream = null; | ||||
|                     break; | ||||
|                 } | ||||
|             } catch (\Throwable $e) { | ||||
|                 unset($asyncMap[$response]); | ||||
|                 $r->stream = null; | ||||
|                 $r->info['error'] = $e->getMessage(); | ||||
|                 $r->response->cancel(); | ||||
|  | ||||
|                 yield $r => $chunk = new ErrorChunk($r->offset, $e); | ||||
|                 $chunk->didThrow() ?: $chunk->getContent(); | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
|             $chunk = $r->stream->current(); | ||||
|  | ||||
|             if (!$chunk instanceof ChunkInterface) { | ||||
|                 throw new \LogicException(sprintf('A chunk passthru must yield instances of "%s", "%s" yielded.', ChunkInterface::class, get_debug_type($chunk))); | ||||
|             } | ||||
|  | ||||
|             if (null !== $chunk->getError()) { | ||||
|                 // no-op | ||||
|             } elseif ($chunk->isFirst()) { | ||||
|                 $e = $r->openBuffer(); | ||||
|  | ||||
|                 yield $r => $chunk; | ||||
|  | ||||
|                 if ($r->initializer && null === $r->getInfo('error')) { | ||||
|                     // Ensure the HTTP status code is always checked | ||||
|                     $r->getHeaders(true); | ||||
|                 } | ||||
|  | ||||
|                 if (null === $e) { | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 $r->response->cancel(); | ||||
|                 $chunk = new ErrorChunk($r->offset, $e); | ||||
|             } elseif ('' !== $content = $chunk->getContent()) { | ||||
|                 if (null !== $r->shouldBuffer) { | ||||
|                     throw new \LogicException('A chunk passthru must yield an "isFirst()" chunk before any content chunk.'); | ||||
|                 } | ||||
|  | ||||
|                 if (null !== $r->content && \strlen($content) !== fwrite($r->content, $content)) { | ||||
|                     $chunk = new ErrorChunk($r->offset, new TransportException(sprintf('Failed writing %d bytes to the response buffer.', \strlen($content)))); | ||||
|                     $r->info['error'] = $chunk->getError(); | ||||
|                     $r->response->cancel(); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (null !== $chunk->getError() || $chunk->isLast()) { | ||||
|                 $stream = $r->stream; | ||||
|                 $r->stream = null; | ||||
|                 unset($asyncMap[$response]); | ||||
|             } | ||||
|  | ||||
|             if (null === $chunk->getError()) { | ||||
|                 $r->offset += \strlen($content); | ||||
|  | ||||
|                 yield $r => $chunk; | ||||
|  | ||||
|                 if (!$chunk->isLast()) { | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 $stream->next(); | ||||
|  | ||||
|                 if ($stream->valid()) { | ||||
|                     throw new \LogicException('A chunk passthru cannot yield after an "isLast()" chunk.'); | ||||
|                 } | ||||
|  | ||||
|                 $r->passthru = null; | ||||
|             } else { | ||||
|                 if ($chunk instanceof ErrorChunk) { | ||||
|                     $chunk->didThrow(false); | ||||
|                 } else { | ||||
|                     try { | ||||
|                         $chunk = new ErrorChunk($chunk->getOffset(), !$chunk->isTimeout() ?: $chunk->getError()); | ||||
|                     } catch (TransportExceptionInterface $e) { | ||||
|                         $chunk = new ErrorChunk($chunk->getOffset(), $e); | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 yield $r => $chunk; | ||||
|                 $chunk->didThrow() ?: $chunk->getContent(); | ||||
|             } | ||||
|  | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private function openBuffer(): ?\Throwable | ||||
|     { | ||||
|         if (null === $shouldBuffer = $this->shouldBuffer) { | ||||
|             throw new \LogicException('A chunk passthru cannot yield more than one "isFirst()" chunk.'); | ||||
|         } | ||||
|  | ||||
|         $e = $this->shouldBuffer = null; | ||||
|  | ||||
|         if ($shouldBuffer instanceof \Closure) { | ||||
|             try { | ||||
|                 $shouldBuffer = $shouldBuffer($this->getHeaders(false)); | ||||
|  | ||||
|                 if (null !== $e = $this->response->getInfo('error')) { | ||||
|                     throw new TransportException($e); | ||||
|                 } | ||||
|             } catch (\Throwable $e) { | ||||
|                 $this->info['error'] = $e->getMessage(); | ||||
|                 $this->response->cancel(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (true === $shouldBuffer) { | ||||
|             $this->content = fopen('php://temp', 'w+'); | ||||
|         } elseif (\is_resource($shouldBuffer)) { | ||||
|             $this->content = $shouldBuffer; | ||||
|         } | ||||
|  | ||||
|         return $e; | ||||
|     } | ||||
|  | ||||
|     private function close(): void | ||||
|     { | ||||
|         $this->response->cancel(); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										180
									
								
								libraries/vendor/symfony/http-client/Response/CommonResponseTrait.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										180
									
								
								libraries/vendor/symfony/http-client/Response/CommonResponseTrait.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,180 @@ | ||||
| <?php | ||||
|  | ||||
| /* | ||||
|  * This file is part of the Symfony package. | ||||
|  * | ||||
|  * (c) Fabien Potencier <fabien@symfony.com> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| namespace Symfony\Component\HttpClient\Response; | ||||
|  | ||||
| use Symfony\Component\HttpClient\Exception\ClientException; | ||||
| use Symfony\Component\HttpClient\Exception\JsonException; | ||||
| use Symfony\Component\HttpClient\Exception\RedirectionException; | ||||
| use Symfony\Component\HttpClient\Exception\ServerException; | ||||
| use Symfony\Component\HttpClient\Exception\TransportException; | ||||
|  | ||||
| /** | ||||
|  * Implements common logic for response classes. | ||||
|  * | ||||
|  * @author Nicolas Grekas <p@tchwork.com> | ||||
|  * | ||||
|  * @internal | ||||
|  */ | ||||
| trait CommonResponseTrait | ||||
| { | ||||
|     /** | ||||
|      * @var callable|null A callback that tells whether we're waiting for response headers | ||||
|      */ | ||||
|     private $initializer; | ||||
|     /** @var bool|\Closure|resource|null */ | ||||
|     private $shouldBuffer; | ||||
|     /** @var resource|null */ | ||||
|     private $content; | ||||
|     private int $offset = 0; | ||||
|     private ?array $jsonData = null; | ||||
|  | ||||
|     public function getContent(bool $throw = true): string | ||||
|     { | ||||
|         if ($this->initializer) { | ||||
|             self::initialize($this); | ||||
|         } | ||||
|  | ||||
|         if ($throw) { | ||||
|             $this->checkStatusCode(); | ||||
|         } | ||||
|  | ||||
|         if (null === $this->content) { | ||||
|             $content = null; | ||||
|  | ||||
|             foreach (self::stream([$this]) as $chunk) { | ||||
|                 if (!$chunk->isLast()) { | ||||
|                     $content .= $chunk->getContent(); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (null !== $content) { | ||||
|                 return $content; | ||||
|             } | ||||
|  | ||||
|             if (null === $this->content) { | ||||
|                 throw new TransportException('Cannot get the content of the response twice: buffering is disabled.'); | ||||
|             } | ||||
|         } else { | ||||
|             foreach (self::stream([$this]) as $chunk) { | ||||
|                 // Chunks are buffered in $this->content already | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         rewind($this->content); | ||||
|  | ||||
|         return stream_get_contents($this->content); | ||||
|     } | ||||
|  | ||||
|     public function toArray(bool $throw = true): array | ||||
|     { | ||||
|         if ('' === $content = $this->getContent($throw)) { | ||||
|             throw new JsonException('Response body is empty.'); | ||||
|         } | ||||
|  | ||||
|         if (null !== $this->jsonData) { | ||||
|             return $this->jsonData; | ||||
|         } | ||||
|  | ||||
|         try { | ||||
|             $content = json_decode($content, true, 512, \JSON_BIGINT_AS_STRING | \JSON_THROW_ON_ERROR); | ||||
|         } catch (\JsonException $e) { | ||||
|             throw new JsonException($e->getMessage().sprintf(' for "%s".', $this->getInfo('url')), $e->getCode()); | ||||
|         } | ||||
|  | ||||
|         if (!\is_array($content)) { | ||||
|             throw new JsonException(sprintf('JSON content was expected to decode to an array, "%s" returned for "%s".', get_debug_type($content), $this->getInfo('url'))); | ||||
|         } | ||||
|  | ||||
|         if (null !== $this->content) { | ||||
|             // Option "buffer" is true | ||||
|             return $this->jsonData = $content; | ||||
|         } | ||||
|  | ||||
|         return $content; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @return resource | ||||
|      */ | ||||
|     public function toStream(bool $throw = true) | ||||
|     { | ||||
|         if ($throw) { | ||||
|             // Ensure headers arrived | ||||
|             $this->getHeaders($throw); | ||||
|         } | ||||
|  | ||||
|         $stream = StreamWrapper::createResource($this); | ||||
|         stream_get_meta_data($stream)['wrapper_data'] | ||||
|             ->bindHandles($this->handle, $this->content); | ||||
|  | ||||
|         return $stream; | ||||
|     } | ||||
|  | ||||
|     public function __sleep(): array | ||||
|     { | ||||
|         throw new \BadMethodCallException('Cannot serialize '.__CLASS__); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @return void | ||||
|      */ | ||||
|     public function __wakeup() | ||||
|     { | ||||
|         throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Closes the response and all its network handles. | ||||
|      */ | ||||
|     abstract protected function close(): void; | ||||
|  | ||||
|     private static function initialize(self $response): void | ||||
|     { | ||||
|         if (null !== $response->getInfo('error')) { | ||||
|             throw new TransportException($response->getInfo('error')); | ||||
|         } | ||||
|  | ||||
|         try { | ||||
|             if (($response->initializer)($response, -0.0)) { | ||||
|                 foreach (self::stream([$response], -0.0) as $chunk) { | ||||
|                     if ($chunk->isFirst()) { | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } catch (\Throwable $e) { | ||||
|             // Persist timeouts thrown during initialization | ||||
|             $response->info['error'] = $e->getMessage(); | ||||
|             $response->close(); | ||||
|             throw $e; | ||||
|         } | ||||
|  | ||||
|         $response->initializer = null; | ||||
|     } | ||||
|  | ||||
|     private function checkStatusCode(): void | ||||
|     { | ||||
|         $code = $this->getInfo('http_code'); | ||||
|  | ||||
|         if (500 <= $code) { | ||||
|             throw new ServerException($this); | ||||
|         } | ||||
|  | ||||
|         if (400 <= $code) { | ||||
|             throw new ClientException($this); | ||||
|         } | ||||
|  | ||||
|         if (300 <= $code) { | ||||
|             throw new RedirectionException($this); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										457
									
								
								libraries/vendor/symfony/http-client/Response/CurlResponse.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										457
									
								
								libraries/vendor/symfony/http-client/Response/CurlResponse.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,457 @@ | ||||
| <?php | ||||
|  | ||||
| /* | ||||
|  * This file is part of the Symfony package. | ||||
|  * | ||||
|  * (c) Fabien Potencier <fabien@symfony.com> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| namespace Symfony\Component\HttpClient\Response; | ||||
|  | ||||
| use Psr\Log\LoggerInterface; | ||||
| use Symfony\Component\HttpClient\Chunk\FirstChunk; | ||||
| use Symfony\Component\HttpClient\Chunk\InformationalChunk; | ||||
| use Symfony\Component\HttpClient\Exception\TransportException; | ||||
| use Symfony\Component\HttpClient\Internal\Canary; | ||||
| use Symfony\Component\HttpClient\Internal\ClientState; | ||||
| use Symfony\Component\HttpClient\Internal\CurlClientState; | ||||
| use Symfony\Contracts\HttpClient\ResponseInterface; | ||||
|  | ||||
| /** | ||||
|  * @author Nicolas Grekas <p@tchwork.com> | ||||
|  * | ||||
|  * @internal | ||||
|  */ | ||||
| final class CurlResponse implements ResponseInterface, StreamableInterface | ||||
| { | ||||
|     use CommonResponseTrait { | ||||
|         getContent as private doGetContent; | ||||
|     } | ||||
|     use TransportResponseTrait; | ||||
|  | ||||
|     private CurlClientState $multi; | ||||
|  | ||||
|     /** | ||||
|      * @var resource | ||||
|      */ | ||||
|     private $debugBuffer; | ||||
|  | ||||
|     /** | ||||
|      * @internal | ||||
|      */ | ||||
|     public function __construct(CurlClientState $multi, \CurlHandle|string $ch, ?array $options = null, ?LoggerInterface $logger = null, string $method = 'GET', ?callable $resolveRedirect = null, ?int $curlVersion = null, ?string $originalUrl = null) | ||||
|     { | ||||
|         $this->multi = $multi; | ||||
|  | ||||
|         if ($ch instanceof \CurlHandle) { | ||||
|             $this->handle = $ch; | ||||
|             $this->debugBuffer = fopen('php://temp', 'w+'); | ||||
|             if (0x074000 === $curlVersion) { | ||||
|                 fwrite($this->debugBuffer, 'Due to a bug in curl 7.64.0, the debug log is disabled; use another version to work around the issue.'); | ||||
|             } else { | ||||
|                 curl_setopt($ch, \CURLOPT_VERBOSE, true); | ||||
|                 curl_setopt($ch, \CURLOPT_STDERR, $this->debugBuffer); | ||||
|             } | ||||
|         } else { | ||||
|             $this->info['url'] = $ch; | ||||
|             $ch = $this->handle; | ||||
|         } | ||||
|  | ||||
|         $this->id = $id = (int) $ch; | ||||
|         $this->logger = $logger; | ||||
|         $this->shouldBuffer = $options['buffer'] ?? true; | ||||
|         $this->timeout = $options['timeout'] ?? null; | ||||
|         $this->info['http_method'] = $method; | ||||
|         $this->info['user_data'] = $options['user_data'] ?? null; | ||||
|         $this->info['max_duration'] = $options['max_duration'] ?? null; | ||||
|         $this->info['start_time'] ??= microtime(true); | ||||
|         $this->info['original_url'] = $originalUrl ?? $this->info['url'] ?? curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL); | ||||
|         $info = &$this->info; | ||||
|         $headers = &$this->headers; | ||||
|         $debugBuffer = $this->debugBuffer; | ||||
|  | ||||
|         if (!$info['response_headers']) { | ||||
|             // Used to keep track of what we're waiting for | ||||
|             curl_setopt($ch, \CURLOPT_PRIVATE, \in_array($method, ['GET', 'HEAD', 'OPTIONS', 'TRACE'], true) && 1.0 < (float) ($options['http_version'] ?? 1.1) ? 'H2' : 'H0'); // H = headers + retry counter | ||||
|         } | ||||
|  | ||||
|         curl_setopt($ch, \CURLOPT_HEADERFUNCTION, static function ($ch, string $data) use (&$info, &$headers, $options, $multi, $id, &$location, $resolveRedirect, $logger): int { | ||||
|             return self::parseHeaderLine($ch, $data, $info, $headers, $options, $multi, $id, $location, $resolveRedirect, $logger); | ||||
|         }); | ||||
|  | ||||
|         if (null === $options) { | ||||
|             // Pushed response: buffer until requested | ||||
|             curl_setopt($ch, \CURLOPT_WRITEFUNCTION, static function ($ch, string $data) use ($multi, $id): int { | ||||
|                 $multi->handlesActivity[$id][] = $data; | ||||
|                 curl_pause($ch, \CURLPAUSE_RECV); | ||||
|  | ||||
|                 return \strlen($data); | ||||
|             }); | ||||
|  | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         $execCounter = $multi->execCounter; | ||||
|         $this->info['pause_handler'] = static function (float $duration) use ($ch, $multi, $execCounter) { | ||||
|             if (0 < $duration) { | ||||
|                 if ($execCounter === $multi->execCounter) { | ||||
|                     curl_multi_remove_handle($multi->handle, $ch); | ||||
|                 } | ||||
|  | ||||
|                 $lastExpiry = end($multi->pauseExpiries); | ||||
|                 $multi->pauseExpiries[(int) $ch] = $duration += hrtime(true) / 1E9; | ||||
|                 if (false !== $lastExpiry && $lastExpiry > $duration) { | ||||
|                     asort($multi->pauseExpiries); | ||||
|                 } | ||||
|                 curl_pause($ch, \CURLPAUSE_ALL); | ||||
|             } else { | ||||
|                 unset($multi->pauseExpiries[(int) $ch]); | ||||
|                 curl_pause($ch, \CURLPAUSE_CONT); | ||||
|                 curl_multi_add_handle($multi->handle, $ch); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         $this->inflate = !isset($options['normalized_headers']['accept-encoding']); | ||||
|         curl_pause($ch, \CURLPAUSE_CONT); | ||||
|  | ||||
|         if ($onProgress = $options['on_progress']) { | ||||
|             $url = isset($info['url']) ? ['url' => $info['url']] : []; | ||||
|             curl_setopt($ch, \CURLOPT_NOPROGRESS, false); | ||||
|             curl_setopt($ch, \CURLOPT_PROGRESSFUNCTION, static function ($ch, $dlSize, $dlNow) use ($onProgress, &$info, $url, $multi, $debugBuffer) { | ||||
|                 try { | ||||
|                     rewind($debugBuffer); | ||||
|                     $debug = ['debug' => stream_get_contents($debugBuffer)]; | ||||
|                     $onProgress($dlNow, $dlSize, $url + curl_getinfo($ch) + $info + $debug); | ||||
|                 } catch (\Throwable $e) { | ||||
|                     $multi->handlesActivity[(int) $ch][] = null; | ||||
|                     $multi->handlesActivity[(int) $ch][] = $e; | ||||
|  | ||||
|                     return 1; // Abort the request | ||||
|                 } | ||||
|  | ||||
|                 return null; | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         curl_setopt($ch, \CURLOPT_WRITEFUNCTION, static function ($ch, string $data) use ($multi, $id): int { | ||||
|             if ('H' === (curl_getinfo($ch, \CURLINFO_PRIVATE)[0] ?? null)) { | ||||
|                 $multi->handlesActivity[$id][] = null; | ||||
|                 $multi->handlesActivity[$id][] = new TransportException(sprintf('Unsupported protocol for "%s"', curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL))); | ||||
|  | ||||
|                 return 0; | ||||
|             } | ||||
|  | ||||
|             curl_setopt($ch, \CURLOPT_WRITEFUNCTION, static function ($ch, string $data) use ($multi, $id): int { | ||||
|                 $multi->handlesActivity[$id][] = $data; | ||||
|  | ||||
|                 return \strlen($data); | ||||
|             }); | ||||
|  | ||||
|             $multi->handlesActivity[$id][] = $data; | ||||
|  | ||||
|             return \strlen($data); | ||||
|         }); | ||||
|  | ||||
|         $this->initializer = static function (self $response) { | ||||
|             $waitFor = curl_getinfo($response->handle, \CURLINFO_PRIVATE); | ||||
|  | ||||
|             return 'H' === $waitFor[0]; | ||||
|         }; | ||||
|  | ||||
|         // Schedule the request in a non-blocking way | ||||
|         $multi->lastTimeout = null; | ||||
|         $multi->openHandles[$id] = [$ch, $options]; | ||||
|         curl_multi_add_handle($multi->handle, $ch); | ||||
|  | ||||
|         $this->canary = new Canary(static function () use ($ch, $multi, $id) { | ||||
|             unset($multi->pauseExpiries[$id], $multi->openHandles[$id], $multi->handlesActivity[$id]); | ||||
|             curl_setopt($ch, \CURLOPT_PRIVATE, '_0'); | ||||
|  | ||||
|             if ($multi->performing) { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             curl_multi_remove_handle($multi->handle, $ch); | ||||
|             curl_setopt_array($ch, [ | ||||
|                 \CURLOPT_NOPROGRESS => true, | ||||
|                 \CURLOPT_PROGRESSFUNCTION => null, | ||||
|                 \CURLOPT_HEADERFUNCTION => null, | ||||
|                 \CURLOPT_WRITEFUNCTION => null, | ||||
|                 \CURLOPT_READFUNCTION => null, | ||||
|                 \CURLOPT_INFILE => null, | ||||
|             ]); | ||||
|  | ||||
|             if (!$multi->openHandles) { | ||||
|                 // Schedule DNS cache eviction for the next request | ||||
|                 $multi->dnsCache->evictions = $multi->dnsCache->evictions ?: $multi->dnsCache->removals; | ||||
|                 $multi->dnsCache->removals = $multi->dnsCache->hostnames = []; | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     public function getInfo(?string $type = null): mixed | ||||
|     { | ||||
|         if (!$info = $this->finalInfo) { | ||||
|             $info = array_merge($this->info, curl_getinfo($this->handle)); | ||||
|             $info['url'] = $this->info['url'] ?? $info['url']; | ||||
|             $info['redirect_url'] = $this->info['redirect_url'] ?? null; | ||||
|  | ||||
|             // workaround curl not subtracting the time offset for pushed responses | ||||
|             if (isset($this->info['url']) && $info['start_time'] / 1000 < $info['total_time']) { | ||||
|                 $info['total_time'] -= $info['starttransfer_time'] ?: $info['total_time']; | ||||
|                 $info['starttransfer_time'] = 0.0; | ||||
|             } | ||||
|  | ||||
|             rewind($this->debugBuffer); | ||||
|             $info['debug'] = stream_get_contents($this->debugBuffer); | ||||
|             $waitFor = curl_getinfo($this->handle, \CURLINFO_PRIVATE); | ||||
|  | ||||
|             if ('H' !== $waitFor[0] && 'C' !== $waitFor[0]) { | ||||
|                 curl_setopt($this->handle, \CURLOPT_VERBOSE, false); | ||||
|                 rewind($this->debugBuffer); | ||||
|                 ftruncate($this->debugBuffer, 0); | ||||
|                 $this->finalInfo = $info; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return null !== $type ? $info[$type] ?? null : $info; | ||||
|     } | ||||
|  | ||||
|     public function getContent(bool $throw = true): string | ||||
|     { | ||||
|         $performing = $this->multi->performing; | ||||
|         $this->multi->performing = $performing || '_0' === curl_getinfo($this->handle, \CURLINFO_PRIVATE); | ||||
|  | ||||
|         try { | ||||
|             return $this->doGetContent($throw); | ||||
|         } finally { | ||||
|             $this->multi->performing = $performing; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public function __destruct() | ||||
|     { | ||||
|         try { | ||||
|             if (null === $this->timeout) { | ||||
|                 return; // Unused pushed response | ||||
|             } | ||||
|  | ||||
|             $this->doDestruct(); | ||||
|         } finally { | ||||
|             if ($this->handle instanceof \CurlHandle) { | ||||
|                 curl_setopt($this->handle, \CURLOPT_VERBOSE, false); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private static function schedule(self $response, array &$runningResponses): void | ||||
|     { | ||||
|         if (isset($runningResponses[$i = (int) $response->multi->handle])) { | ||||
|             $runningResponses[$i][1][$response->id] = $response; | ||||
|         } else { | ||||
|             $runningResponses[$i] = [$response->multi, [$response->id => $response]]; | ||||
|         } | ||||
|  | ||||
|         if ('_0' === curl_getinfo($response->handle, \CURLINFO_PRIVATE)) { | ||||
|             // Response already completed | ||||
|             $response->multi->handlesActivity[$response->id][] = null; | ||||
|             $response->multi->handlesActivity[$response->id][] = null !== $response->info['error'] ? new TransportException($response->info['error']) : null; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param CurlClientState $multi | ||||
|      */ | ||||
|     private static function perform(ClientState $multi, ?array &$responses = null): void | ||||
|     { | ||||
|         if ($multi->performing) { | ||||
|             if ($responses) { | ||||
|                 $response = current($responses); | ||||
|                 $multi->handlesActivity[(int) $response->handle][] = null; | ||||
|                 $multi->handlesActivity[(int) $response->handle][] = new TransportException(sprintf('Userland callback cannot use the client nor the response while processing "%s".', curl_getinfo($response->handle, \CURLINFO_EFFECTIVE_URL))); | ||||
|             } | ||||
|  | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         try { | ||||
|             $multi->performing = true; | ||||
|             ++$multi->execCounter; | ||||
|             $active = 0; | ||||
|             while (\CURLM_CALL_MULTI_PERFORM === ($err = curl_multi_exec($multi->handle, $active))) { | ||||
|             } | ||||
|  | ||||
|             if (\CURLM_OK !== $err) { | ||||
|                 throw new TransportException(curl_multi_strerror($err)); | ||||
|             } | ||||
|  | ||||
|             while ($info = curl_multi_info_read($multi->handle)) { | ||||
|                 if (\CURLMSG_DONE !== $info['msg']) { | ||||
|                     continue; | ||||
|                 } | ||||
|                 $result = $info['result']; | ||||
|                 $id = (int) $ch = $info['handle']; | ||||
|                 $waitFor = @curl_getinfo($ch, \CURLINFO_PRIVATE) ?: '_0'; | ||||
|  | ||||
|                 if (\in_array($result, [\CURLE_SEND_ERROR, \CURLE_RECV_ERROR, /* CURLE_HTTP2 */ 16, /* CURLE_HTTP2_STREAM */ 92], true) && $waitFor[1] && 'C' !== $waitFor[0]) { | ||||
|                     curl_multi_remove_handle($multi->handle, $ch); | ||||
|                     $waitFor[1] = (string) ((int) $waitFor[1] - 1); // decrement the retry counter | ||||
|                     curl_setopt($ch, \CURLOPT_PRIVATE, $waitFor); | ||||
|                     curl_setopt($ch, \CURLOPT_FORBID_REUSE, true); | ||||
|  | ||||
|                     if (0 === curl_multi_add_handle($multi->handle, $ch)) { | ||||
|                         continue; | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 if (\CURLE_RECV_ERROR === $result && 'H' === $waitFor[0] && 400 <= ($responses[(int) $ch]->info['http_code'] ?? 0)) { | ||||
|                     $multi->handlesActivity[$id][] = new FirstChunk(); | ||||
|                 } | ||||
|  | ||||
|                 $multi->handlesActivity[$id][] = null; | ||||
|                 $multi->handlesActivity[$id][] = \in_array($result, [\CURLE_OK, \CURLE_TOO_MANY_REDIRECTS], true) || '_0' === $waitFor || curl_getinfo($ch, \CURLINFO_SIZE_DOWNLOAD) === curl_getinfo($ch, \CURLINFO_CONTENT_LENGTH_DOWNLOAD) ? null : new TransportException(ucfirst(curl_error($ch) ?: curl_strerror($result)).sprintf(' for "%s".', curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL))); | ||||
|             } | ||||
|         } finally { | ||||
|             $multi->performing = false; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param CurlClientState $multi | ||||
|      */ | ||||
|     private static function select(ClientState $multi, float $timeout): int | ||||
|     { | ||||
|         if ($multi->pauseExpiries) { | ||||
|             $now = hrtime(true) / 1E9; | ||||
|  | ||||
|             foreach ($multi->pauseExpiries as $id => $pauseExpiry) { | ||||
|                 if ($now < $pauseExpiry) { | ||||
|                     $timeout = min($timeout, $pauseExpiry - $now); | ||||
|                     break; | ||||
|                 } | ||||
|  | ||||
|                 unset($multi->pauseExpiries[$id]); | ||||
|                 curl_pause($multi->openHandles[$id][0], \CURLPAUSE_CONT); | ||||
|                 curl_multi_add_handle($multi->handle, $multi->openHandles[$id][0]); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (0 !== $selected = curl_multi_select($multi->handle, $timeout)) { | ||||
|             return $selected; | ||||
|         } | ||||
|  | ||||
|         if ($multi->pauseExpiries && 0 < $timeout -= hrtime(true) / 1E9 - $now) { | ||||
|             usleep((int) (1E6 * $timeout)); | ||||
|         } | ||||
|  | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Parses header lines as curl yields them to us. | ||||
|      */ | ||||
|     private static function parseHeaderLine($ch, string $data, array &$info, array &$headers, ?array $options, CurlClientState $multi, int $id, ?string &$location, ?callable $resolveRedirect, ?LoggerInterface $logger): int | ||||
|     { | ||||
|         if (!str_ends_with($data, "\r\n")) { | ||||
|             return 0; | ||||
|         } | ||||
|  | ||||
|         $waitFor = @curl_getinfo($ch, \CURLINFO_PRIVATE) ?: '_0'; | ||||
|  | ||||
|         if ('H' !== $waitFor[0]) { | ||||
|             return \strlen($data); // Ignore HTTP trailers | ||||
|         } | ||||
|  | ||||
|         $statusCode = curl_getinfo($ch, \CURLINFO_RESPONSE_CODE); | ||||
|  | ||||
|         if ($statusCode !== $info['http_code'] && !preg_match("#^HTTP/\d+(?:\.\d+)? {$statusCode}(?: |\r\n$)#", $data)) { | ||||
|             return \strlen($data); // Ignore headers from responses to CONNECT requests | ||||
|         } | ||||
|  | ||||
|         if ("\r\n" !== $data) { | ||||
|             // Regular header line: add it to the list | ||||
|             self::addResponseHeaders([substr($data, 0, -2)], $info, $headers); | ||||
|  | ||||
|             if (!str_starts_with($data, 'HTTP/')) { | ||||
|                 if (0 === stripos($data, 'Location:')) { | ||||
|                     $location = trim(substr($data, 9, -2)); | ||||
|                 } | ||||
|  | ||||
|                 return \strlen($data); | ||||
|             } | ||||
|  | ||||
|             if (\function_exists('openssl_x509_read') && $certinfo = curl_getinfo($ch, \CURLINFO_CERTINFO)) { | ||||
|                 $info['peer_certificate_chain'] = array_map('openssl_x509_read', array_column($certinfo, 'Cert')); | ||||
|             } | ||||
|  | ||||
|             if (300 <= $info['http_code'] && $info['http_code'] < 400) { | ||||
|                 if (curl_getinfo($ch, \CURLINFO_REDIRECT_COUNT) === $options['max_redirects']) { | ||||
|                     curl_setopt($ch, \CURLOPT_FOLLOWLOCATION, false); | ||||
|                 } elseif (303 === $info['http_code'] || ('POST' === $info['http_method'] && \in_array($info['http_code'], [301, 302], true))) { | ||||
|                     curl_setopt($ch, \CURLOPT_POSTFIELDS, ''); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return \strlen($data); | ||||
|         } | ||||
|  | ||||
|         // End of headers: handle informational responses, redirects, etc. | ||||
|  | ||||
|         if (200 > $statusCode) { | ||||
|             $multi->handlesActivity[$id][] = new InformationalChunk($statusCode, $headers); | ||||
|             $location = null; | ||||
|  | ||||
|             return \strlen($data); | ||||
|         } | ||||
|  | ||||
|         $info['redirect_url'] = null; | ||||
|  | ||||
|         if (300 <= $statusCode && $statusCode < 400 && null !== $location) { | ||||
|             if ($noContent = 303 === $statusCode || ('POST' === $info['http_method'] && \in_array($statusCode, [301, 302], true))) { | ||||
|                 $info['http_method'] = 'HEAD' === $info['http_method'] ? 'HEAD' : 'GET'; | ||||
|                 curl_setopt($ch, \CURLOPT_CUSTOMREQUEST, $info['http_method']); | ||||
|             } | ||||
|  | ||||
|             if (null === $info['redirect_url'] = $resolveRedirect($ch, $location, $noContent)) { | ||||
|                 $options['max_redirects'] = curl_getinfo($ch, \CURLINFO_REDIRECT_COUNT); | ||||
|                 curl_setopt($ch, \CURLOPT_FOLLOWLOCATION, false); | ||||
|                 curl_setopt($ch, \CURLOPT_MAXREDIRS, $options['max_redirects']); | ||||
|             } else { | ||||
|                 $url = parse_url($location ?? ':'); | ||||
|  | ||||
|                 if (isset($url['host']) && null !== $ip = $multi->dnsCache->hostnames[$url['host'] = strtolower($url['host'])] ?? null) { | ||||
|                     // Populate DNS cache for redirects if needed | ||||
|                     $port = $url['port'] ?? ('http' === ($url['scheme'] ?? parse_url(curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL), \PHP_URL_SCHEME)) ? 80 : 443); | ||||
|                     curl_setopt($ch, \CURLOPT_RESOLVE, ["{$url['host']}:$port:$ip"]); | ||||
|                     $multi->dnsCache->removals["-{$url['host']}:$port"] = "-{$url['host']}:$port"; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (401 === $statusCode && isset($options['auth_ntlm']) && 0 === strncasecmp($headers['www-authenticate'][0] ?? '', 'NTLM ', 5)) { | ||||
|             // Continue with NTLM auth | ||||
|         } elseif ($statusCode < 300 || 400 <= $statusCode || null === $location || curl_getinfo($ch, \CURLINFO_REDIRECT_COUNT) === $options['max_redirects']) { | ||||
|             // Headers and redirects completed, time to get the response's content | ||||
|             $multi->handlesActivity[$id][] = new FirstChunk(); | ||||
|  | ||||
|             if ('HEAD' === $info['http_method'] || \in_array($statusCode, [204, 304], true)) { | ||||
|                 $waitFor = '_0'; // no content expected | ||||
|                 $multi->handlesActivity[$id][] = null; | ||||
|                 $multi->handlesActivity[$id][] = null; | ||||
|             } else { | ||||
|                 $waitFor[0] = 'C'; // C = content | ||||
|             } | ||||
|  | ||||
|             curl_setopt($ch, \CURLOPT_PRIVATE, $waitFor); | ||||
|         } elseif (null !== $info['redirect_url'] && $logger) { | ||||
|             $logger->info(sprintf('Redirecting: "%s %s"', $info['http_code'], $info['redirect_url'])); | ||||
|         } | ||||
|  | ||||
|         $location = null; | ||||
|  | ||||
|         return \strlen($data); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										73
									
								
								libraries/vendor/symfony/http-client/Response/HttplugPromise.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								libraries/vendor/symfony/http-client/Response/HttplugPromise.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,73 @@ | ||||
| <?php | ||||
|  | ||||
| /* | ||||
|  * This file is part of the Symfony package. | ||||
|  * | ||||
|  * (c) Fabien Potencier <fabien@symfony.com> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| namespace Symfony\Component\HttpClient\Response; | ||||
|  | ||||
| use GuzzleHttp\Promise\Create; | ||||
| use GuzzleHttp\Promise\PromiseInterface as GuzzlePromiseInterface; | ||||
| use Http\Promise\Promise as HttplugPromiseInterface; | ||||
| use Psr\Http\Message\ResponseInterface as Psr7ResponseInterface; | ||||
|  | ||||
| /** | ||||
|  * @author Tobias Nyholm <tobias.nyholm@gmail.com> | ||||
|  * | ||||
|  * @internal | ||||
|  */ | ||||
| final class HttplugPromise implements HttplugPromiseInterface | ||||
| { | ||||
|     private GuzzlePromiseInterface $promise; | ||||
|  | ||||
|     public function __construct(GuzzlePromiseInterface $promise) | ||||
|     { | ||||
|         $this->promise = $promise; | ||||
|     } | ||||
|  | ||||
|     public function then(?callable $onFulfilled = null, ?callable $onRejected = null): self | ||||
|     { | ||||
|         return new self($this->promise->then( | ||||
|             $this->wrapThenCallback($onFulfilled), | ||||
|             $this->wrapThenCallback($onRejected) | ||||
|         )); | ||||
|     } | ||||
|  | ||||
|     public function cancel(): void | ||||
|     { | ||||
|         $this->promise->cancel(); | ||||
|     } | ||||
|  | ||||
|     public function getState(): string | ||||
|     { | ||||
|         return $this->promise->getState(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @return Psr7ResponseInterface|mixed | ||||
|      */ | ||||
|     public function wait($unwrap = true): mixed | ||||
|     { | ||||
|         $result = $this->promise->wait($unwrap); | ||||
|  | ||||
|         while ($result instanceof HttplugPromiseInterface || $result instanceof GuzzlePromiseInterface) { | ||||
|             $result = $result->wait($unwrap); | ||||
|         } | ||||
|  | ||||
|         return $result; | ||||
|     } | ||||
|  | ||||
|     private function wrapThenCallback(?callable $callback): ?callable | ||||
|     { | ||||
|         if (null === $callback) { | ||||
|             return null; | ||||
|         } | ||||
|  | ||||
|         return static fn ($value) => Create::promiseFor($callback($value)); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										33
									
								
								libraries/vendor/symfony/http-client/Response/JsonMockResponse.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								libraries/vendor/symfony/http-client/Response/JsonMockResponse.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,33 @@ | ||||
| <?php | ||||
|  | ||||
| /* | ||||
|  * This file is part of the Symfony package. | ||||
|  * | ||||
|  * (c) Fabien Potencier <fabien@symfony.com> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| namespace Symfony\Component\HttpClient\Response; | ||||
|  | ||||
| use Symfony\Component\HttpClient\Exception\InvalidArgumentException; | ||||
|  | ||||
| class JsonMockResponse extends MockResponse | ||||
| { | ||||
|     /** | ||||
|      * @param mixed $body Any value that `json_encode()` can serialize | ||||
|      */ | ||||
|     public function __construct(mixed $body = [], array $info = []) | ||||
|     { | ||||
|         try { | ||||
|             $json = json_encode($body, \JSON_THROW_ON_ERROR | \JSON_PRESERVE_ZERO_FRACTION); | ||||
|         } catch (\JsonException $e) { | ||||
|             throw new InvalidArgumentException('JSON encoding failed: '.$e->getMessage(), $e->getCode(), $e); | ||||
|         } | ||||
|  | ||||
|         $info['response_headers']['content-type'] ??= 'application/json'; | ||||
|  | ||||
|         parent::__construct($json, $info); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										326
									
								
								libraries/vendor/symfony/http-client/Response/MockResponse.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										326
									
								
								libraries/vendor/symfony/http-client/Response/MockResponse.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,326 @@ | ||||
| <?php | ||||
|  | ||||
| /* | ||||
|  * This file is part of the Symfony package. | ||||
|  * | ||||
|  * (c) Fabien Potencier <fabien@symfony.com> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| namespace Symfony\Component\HttpClient\Response; | ||||
|  | ||||
| use Symfony\Component\HttpClient\Chunk\ErrorChunk; | ||||
| use Symfony\Component\HttpClient\Chunk\FirstChunk; | ||||
| use Symfony\Component\HttpClient\Exception\InvalidArgumentException; | ||||
| use Symfony\Component\HttpClient\Exception\TransportException; | ||||
| use Symfony\Component\HttpClient\Internal\ClientState; | ||||
| use Symfony\Contracts\HttpClient\ResponseInterface; | ||||
|  | ||||
| /** | ||||
|  * A test-friendly response. | ||||
|  * | ||||
|  * @author Nicolas Grekas <p@tchwork.com> | ||||
|  */ | ||||
| class MockResponse implements ResponseInterface, StreamableInterface | ||||
| { | ||||
|     use CommonResponseTrait; | ||||
|     use TransportResponseTrait; | ||||
|  | ||||
|     private string|iterable|null $body; | ||||
|     private array $requestOptions = []; | ||||
|     private string $requestUrl; | ||||
|     private string $requestMethod; | ||||
|  | ||||
|     private static ClientState $mainMulti; | ||||
|     private static int $idSequence = 0; | ||||
|  | ||||
|     /** | ||||
|      * @param string|iterable<string|\Throwable> $body The response body as a string or an iterable of strings, | ||||
|      *                                                 yielding an empty string simulates an idle timeout, | ||||
|      *                                                 throwing or yielding an exception yields an ErrorChunk | ||||
|      * | ||||
|      * @see ResponseInterface::getInfo() for possible info, e.g. "response_headers" | ||||
|      */ | ||||
|     public function __construct(string|iterable $body = '', array $info = []) | ||||
|     { | ||||
|         $this->body = $body; | ||||
|         $this->info = $info + ['http_code' => 200] + $this->info; | ||||
|  | ||||
|         if (!isset($info['response_headers'])) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         $responseHeaders = []; | ||||
|  | ||||
|         foreach ($info['response_headers'] as $k => $v) { | ||||
|             foreach ((array) $v as $v) { | ||||
|                 $responseHeaders[] = (\is_string($k) ? $k.': ' : '').$v; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         $this->info['response_headers'] = []; | ||||
|         self::addResponseHeaders($responseHeaders, $this->info, $this->headers); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the options used when doing the request. | ||||
|      */ | ||||
|     public function getRequestOptions(): array | ||||
|     { | ||||
|         return $this->requestOptions; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the URL used when doing the request. | ||||
|      */ | ||||
|     public function getRequestUrl(): string | ||||
|     { | ||||
|         return $this->requestUrl; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the method used when doing the request. | ||||
|      */ | ||||
|     public function getRequestMethod(): string | ||||
|     { | ||||
|         return $this->requestMethod; | ||||
|     } | ||||
|  | ||||
|     public function getInfo(?string $type = null): mixed | ||||
|     { | ||||
|         return null !== $type ? $this->info[$type] ?? null : $this->info; | ||||
|     } | ||||
|  | ||||
|     public function cancel(): void | ||||
|     { | ||||
|         $this->info['canceled'] = true; | ||||
|         $this->info['error'] = 'Response has been canceled.'; | ||||
|         try { | ||||
|             $this->body = null; | ||||
|         } catch (TransportException $e) { | ||||
|             // ignore errors when canceling | ||||
|         } | ||||
|  | ||||
|         $onProgress = $this->requestOptions['on_progress'] ?? static function () {}; | ||||
|         $dlSize = isset($this->headers['content-encoding']) || 'HEAD' === ($this->info['http_method'] ?? null) || \in_array($this->info['http_code'], [204, 304], true) ? 0 : (int) ($this->headers['content-length'][0] ?? 0); | ||||
|         $onProgress($this->offset, $dlSize, $this->info); | ||||
|     } | ||||
|  | ||||
|     public function __destruct() | ||||
|     { | ||||
|         $this->doDestruct(); | ||||
|     } | ||||
|  | ||||
|     protected function close(): void | ||||
|     { | ||||
|         $this->inflate = null; | ||||
|         $this->body = []; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @internal | ||||
|      */ | ||||
|     public static function fromRequest(string $method, string $url, array $options, ResponseInterface $mock): self | ||||
|     { | ||||
|         $response = new self([]); | ||||
|         $response->requestOptions = $options; | ||||
|         $response->id = ++self::$idSequence; | ||||
|         $response->shouldBuffer = $options['buffer'] ?? true; | ||||
|         $response->initializer = static fn (self $response) => \is_array($response->body[0] ?? null); | ||||
|  | ||||
|         $response->info['redirect_count'] = 0; | ||||
|         $response->info['redirect_url'] = null; | ||||
|         $response->info['start_time'] = microtime(true); | ||||
|         $response->info['http_method'] = $method; | ||||
|         $response->info['http_code'] = 0; | ||||
|         $response->info['user_data'] = $options['user_data'] ?? null; | ||||
|         $response->info['max_duration'] = $options['max_duration'] ?? null; | ||||
|         $response->info['url'] = $url; | ||||
|         $response->info['original_url'] = $url; | ||||
|  | ||||
|         if ($mock instanceof self) { | ||||
|             $mock->requestOptions = $response->requestOptions; | ||||
|             $mock->requestMethod = $method; | ||||
|             $mock->requestUrl = $url; | ||||
|         } | ||||
|  | ||||
|         self::writeRequest($response, $options, $mock); | ||||
|         $response->body[] = [$options, $mock]; | ||||
|  | ||||
|         return $response; | ||||
|     } | ||||
|  | ||||
|     protected static function schedule(self $response, array &$runningResponses): void | ||||
|     { | ||||
|         if (!isset($response->id)) { | ||||
|             throw new InvalidArgumentException('MockResponse instances must be issued by MockHttpClient before processing.'); | ||||
|         } | ||||
|  | ||||
|         $multi = self::$mainMulti ??= new ClientState(); | ||||
|  | ||||
|         if (!isset($runningResponses[0])) { | ||||
|             $runningResponses[0] = [$multi, []]; | ||||
|         } | ||||
|  | ||||
|         $runningResponses[0][1][$response->id] = $response; | ||||
|     } | ||||
|  | ||||
|     protected static function perform(ClientState $multi, array &$responses): void | ||||
|     { | ||||
|         foreach ($responses as $response) { | ||||
|             $id = $response->id; | ||||
|  | ||||
|             if (null === $response->body) { | ||||
|                 // Canceled response | ||||
|                 $response->body = []; | ||||
|             } elseif ([] === $response->body) { | ||||
|                 // Error chunk | ||||
|                 $multi->handlesActivity[$id][] = null; | ||||
|                 $multi->handlesActivity[$id][] = null !== $response->info['error'] ? new TransportException($response->info['error']) : null; | ||||
|             } elseif (null === $chunk = array_shift($response->body)) { | ||||
|                 // Last chunk | ||||
|                 $multi->handlesActivity[$id][] = null; | ||||
|                 $multi->handlesActivity[$id][] = array_shift($response->body); | ||||
|             } elseif (\is_array($chunk)) { | ||||
|                 // First chunk | ||||
|                 try { | ||||
|                     $offset = 0; | ||||
|                     $chunk[1]->getStatusCode(); | ||||
|                     $chunk[1]->getHeaders(false); | ||||
|                     self::readResponse($response, $chunk[0], $chunk[1], $offset); | ||||
|                     $multi->handlesActivity[$id][] = new FirstChunk(); | ||||
|                 } catch (\Throwable $e) { | ||||
|                     $multi->handlesActivity[$id][] = null; | ||||
|                     $multi->handlesActivity[$id][] = $e; | ||||
|                 } | ||||
|             } elseif ($chunk instanceof \Throwable) { | ||||
|                 $multi->handlesActivity[$id][] = null; | ||||
|                 $multi->handlesActivity[$id][] = $chunk; | ||||
|             } else { | ||||
|                 // Data or timeout chunk | ||||
|                 $multi->handlesActivity[$id][] = $chunk; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     protected static function select(ClientState $multi, float $timeout): int | ||||
|     { | ||||
|         return 42; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Simulates sending the request. | ||||
|      */ | ||||
|     private static function writeRequest(self $response, array $options, ResponseInterface $mock): void | ||||
|     { | ||||
|         $onProgress = $options['on_progress'] ?? static function () {}; | ||||
|         $response->info += $mock->getInfo() ?: []; | ||||
|  | ||||
|         // simulate "size_upload" if it is set | ||||
|         if (isset($response->info['size_upload'])) { | ||||
|             $response->info['size_upload'] = 0.0; | ||||
|         } | ||||
|  | ||||
|         // simulate "total_time" if it is not set | ||||
|         if (!isset($response->info['total_time'])) { | ||||
|             $response->info['total_time'] = microtime(true) - $response->info['start_time']; | ||||
|         } | ||||
|  | ||||
|         // "notify" DNS resolution | ||||
|         $onProgress(0, 0, $response->info); | ||||
|  | ||||
|         // consume the request body | ||||
|         if (\is_resource($body = $options['body'] ?? '')) { | ||||
|             $data = stream_get_contents($body); | ||||
|             if (isset($response->info['size_upload'])) { | ||||
|                 $response->info['size_upload'] += \strlen($data); | ||||
|             } | ||||
|         } elseif ($body instanceof \Closure) { | ||||
|             while ('' !== $data = $body(16372)) { | ||||
|                 if (!\is_string($data)) { | ||||
|                     throw new TransportException(sprintf('Return value of the "body" option callback must be string, "%s" returned.', get_debug_type($data))); | ||||
|                 } | ||||
|  | ||||
|                 // "notify" upload progress | ||||
|                 if (isset($response->info['size_upload'])) { | ||||
|                     $response->info['size_upload'] += \strlen($data); | ||||
|                 } | ||||
|  | ||||
|                 $onProgress(0, 0, $response->info); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Simulates reading the response. | ||||
|      */ | ||||
|     private static function readResponse(self $response, array $options, ResponseInterface $mock, int &$offset): void | ||||
|     { | ||||
|         $onProgress = $options['on_progress'] ?? static function () {}; | ||||
|  | ||||
|         // populate info related to headers | ||||
|         $info = $mock->getInfo() ?: []; | ||||
|         $response->info['http_code'] = ($info['http_code'] ?? 0) ?: $mock->getStatusCode() ?: 200; | ||||
|         $response->addResponseHeaders($info['response_headers'] ?? [], $response->info, $response->headers); | ||||
|         $dlSize = isset($response->headers['content-encoding']) || 'HEAD' === $response->info['http_method'] || \in_array($response->info['http_code'], [204, 304], true) ? 0 : (int) ($response->headers['content-length'][0] ?? 0); | ||||
|  | ||||
|         $response->info = [ | ||||
|             'start_time' => $response->info['start_time'], | ||||
|             'user_data' => $response->info['user_data'], | ||||
|             'max_duration' => $response->info['max_duration'], | ||||
|             'http_code' => $response->info['http_code'], | ||||
|         ] + $info + $response->info; | ||||
|  | ||||
|         if (null !== $response->info['error']) { | ||||
|             throw new TransportException($response->info['error']); | ||||
|         } | ||||
|  | ||||
|         if (!isset($response->info['total_time'])) { | ||||
|             $response->info['total_time'] = microtime(true) - $response->info['start_time']; | ||||
|         } | ||||
|  | ||||
|         // "notify" headers arrival | ||||
|         $onProgress(0, $dlSize, $response->info); | ||||
|  | ||||
|         // cast response body to activity list | ||||
|         $body = $mock instanceof self ? $mock->body : $mock->getContent(false); | ||||
|  | ||||
|         if (!\is_string($body)) { | ||||
|             try { | ||||
|                 foreach ($body as $chunk) { | ||||
|                     if ($chunk instanceof \Throwable) { | ||||
|                         throw $chunk; | ||||
|                     } | ||||
|  | ||||
|                     if ('' === $chunk = (string) $chunk) { | ||||
|                         // simulate an idle timeout | ||||
|                         $response->body[] = new ErrorChunk($offset, sprintf('Idle timeout reached for "%s".', $response->info['url'])); | ||||
|                     } else { | ||||
|                         $response->body[] = $chunk; | ||||
|                         $offset += \strlen($chunk); | ||||
|                         // "notify" download progress | ||||
|                         $onProgress($offset, $dlSize, $response->info); | ||||
|                     } | ||||
|                 } | ||||
|             } catch (\Throwable $e) { | ||||
|                 $response->body[] = $e; | ||||
|             } | ||||
|         } elseif ('' !== $body) { | ||||
|             $response->body[] = $body; | ||||
|             $offset = \strlen($body); | ||||
|         } | ||||
|  | ||||
|         if (!isset($response->info['total_time'])) { | ||||
|             $response->info['total_time'] = microtime(true) - $response->info['start_time']; | ||||
|         } | ||||
|  | ||||
|         // "notify" completion | ||||
|         $onProgress($offset, $dlSize, $response->info); | ||||
|  | ||||
|         if ($dlSize && $offset !== $dlSize) { | ||||
|             throw new TransportException(sprintf('Transfer closed with %d bytes remaining to read.', $dlSize - $offset)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										370
									
								
								libraries/vendor/symfony/http-client/Response/NativeResponse.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										370
									
								
								libraries/vendor/symfony/http-client/Response/NativeResponse.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,370 @@ | ||||
| <?php | ||||
|  | ||||
| /* | ||||
|  * This file is part of the Symfony package. | ||||
|  * | ||||
|  * (c) Fabien Potencier <fabien@symfony.com> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| namespace Symfony\Component\HttpClient\Response; | ||||
|  | ||||
| use Psr\Log\LoggerInterface; | ||||
| use Symfony\Component\HttpClient\Chunk\FirstChunk; | ||||
| use Symfony\Component\HttpClient\Exception\TransportException; | ||||
| use Symfony\Component\HttpClient\Internal\Canary; | ||||
| use Symfony\Component\HttpClient\Internal\ClientState; | ||||
| use Symfony\Component\HttpClient\Internal\NativeClientState; | ||||
| use Symfony\Contracts\HttpClient\ResponseInterface; | ||||
|  | ||||
| /** | ||||
|  * @author Nicolas Grekas <p@tchwork.com> | ||||
|  * | ||||
|  * @internal | ||||
|  */ | ||||
| final class NativeResponse implements ResponseInterface, StreamableInterface | ||||
| { | ||||
|     use CommonResponseTrait; | ||||
|     use TransportResponseTrait; | ||||
|  | ||||
|     /** | ||||
|      * @var resource | ||||
|      */ | ||||
|     private $context; | ||||
|     private string $url; | ||||
|     private \Closure $resolver; | ||||
|     private ?\Closure $onProgress; | ||||
|     private ?int $remaining = null; | ||||
|  | ||||
|     /** | ||||
|      * @var resource|null | ||||
|      */ | ||||
|     private $buffer; | ||||
|  | ||||
|     private NativeClientState $multi; | ||||
|     private float $pauseExpiry = 0.0; | ||||
|  | ||||
|     /** | ||||
|      * @internal | ||||
|      */ | ||||
|     public function __construct(NativeClientState $multi, $context, string $url, array $options, array &$info, callable $resolver, ?callable $onProgress, ?LoggerInterface $logger) | ||||
|     { | ||||
|         $this->multi = $multi; | ||||
|         $this->id = $id = (int) $context; | ||||
|         $this->context = $context; | ||||
|         $this->url = $url; | ||||
|         $this->logger = $logger; | ||||
|         $this->timeout = $options['timeout']; | ||||
|         $this->info = &$info; | ||||
|         $this->resolver = $resolver(...); | ||||
|         $this->onProgress = $onProgress ? $onProgress(...) : null; | ||||
|         $this->inflate = !isset($options['normalized_headers']['accept-encoding']); | ||||
|         $this->shouldBuffer = $options['buffer'] ?? true; | ||||
|  | ||||
|         // Temporary resource to dechunk the response stream | ||||
|         $this->buffer = fopen('php://temp', 'w+'); | ||||
|  | ||||
|         $info['original_url'] = implode('', $info['url']); | ||||
|         $info['user_data'] = $options['user_data']; | ||||
|         $info['max_duration'] = $options['max_duration']; | ||||
|         ++$multi->responseCount; | ||||
|  | ||||
|         $this->initializer = static fn (self $response) => null === $response->remaining; | ||||
|  | ||||
|         $pauseExpiry = &$this->pauseExpiry; | ||||
|         $info['pause_handler'] = static function (float $duration) use (&$pauseExpiry) { | ||||
|             $pauseExpiry = 0 < $duration ? hrtime(true) / 1E9 + $duration : 0; | ||||
|         }; | ||||
|  | ||||
|         $this->canary = new Canary(static function () use ($multi, $id) { | ||||
|             if (null !== ($host = $multi->openHandles[$id][6] ?? null) && 0 >= --$multi->hosts[$host]) { | ||||
|                 unset($multi->hosts[$host]); | ||||
|             } | ||||
|             unset($multi->openHandles[$id], $multi->handlesActivity[$id]); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     public function getInfo(?string $type = null): mixed | ||||
|     { | ||||
|         if (!$info = $this->finalInfo) { | ||||
|             $info = $this->info; | ||||
|             $info['url'] = implode('', $info['url']); | ||||
|             unset($info['size_body'], $info['request_header']); | ||||
|  | ||||
|             if (null === $this->buffer) { | ||||
|                 $this->finalInfo = $info; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return null !== $type ? $info[$type] ?? null : $info; | ||||
|     } | ||||
|  | ||||
|     public function __destruct() | ||||
|     { | ||||
|         try { | ||||
|             $this->doDestruct(); | ||||
|         } finally { | ||||
|             // Clear the DNS cache when all requests completed | ||||
|             if (0 >= --$this->multi->responseCount) { | ||||
|                 $this->multi->responseCount = 0; | ||||
|                 $this->multi->dnsCache = []; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private function open(): void | ||||
|     { | ||||
|         $url = $this->url; | ||||
|  | ||||
|         set_error_handler(function ($type, $msg) use (&$url) { | ||||
|             if (\E_NOTICE !== $type || 'fopen(): Content-type not specified assuming application/x-www-form-urlencoded' !== $msg) { | ||||
|                 throw new TransportException($msg); | ||||
|             } | ||||
|  | ||||
|             $this->logger?->info(sprintf('%s for "%s".', $msg, $url ?? $this->url)); | ||||
|         }); | ||||
|  | ||||
|         try { | ||||
|             $this->info['start_time'] = microtime(true); | ||||
|  | ||||
|             [$resolver, $url] = ($this->resolver)($this->multi); | ||||
|  | ||||
|             while (true) { | ||||
|                 $context = stream_context_get_options($this->context); | ||||
|  | ||||
|                 if ($proxy = $context['http']['proxy'] ?? null) { | ||||
|                     $this->info['debug'] .= "* Establish HTTP proxy tunnel to {$proxy}\n"; | ||||
|                     $this->info['request_header'] = $url; | ||||
|                 } else { | ||||
|                     $this->info['debug'] .= "*   Trying {$this->info['primary_ip']}...\n"; | ||||
|                     $this->info['request_header'] = $this->info['url']['path'].$this->info['url']['query']; | ||||
|                 } | ||||
|  | ||||
|                 $this->info['request_header'] = sprintf("> %s %s HTTP/%s \r\n", $context['http']['method'], $this->info['request_header'], $context['http']['protocol_version']); | ||||
|                 $this->info['request_header'] .= implode("\r\n", $context['http']['header'])."\r\n\r\n"; | ||||
|  | ||||
|                 if (\array_key_exists('peer_name', $context['ssl']) && null === $context['ssl']['peer_name']) { | ||||
|                     unset($context['ssl']['peer_name']); | ||||
|                     $this->context = stream_context_create([], ['options' => $context] + stream_context_get_params($this->context)); | ||||
|                 } | ||||
|  | ||||
|                 // Send request and follow redirects when needed | ||||
|                 $this->handle = $h = fopen($url, 'r', false, $this->context); | ||||
|                 self::addResponseHeaders(stream_get_meta_data($h)['wrapper_data'], $this->info, $this->headers, $this->info['debug']); | ||||
|                 $url = $resolver($this->multi, $this->headers['location'][0] ?? null, $this->context); | ||||
|  | ||||
|                 if (null === $url) { | ||||
|                     break; | ||||
|                 } | ||||
|  | ||||
|                 $this->logger?->info(sprintf('Redirecting: "%s %s"', $this->info['http_code'], $url ?? $this->url)); | ||||
|             } | ||||
|         } catch (\Throwable $e) { | ||||
|             $this->close(); | ||||
|             $this->multi->handlesActivity[$this->id][] = null; | ||||
|             $this->multi->handlesActivity[$this->id][] = $e; | ||||
|  | ||||
|             return; | ||||
|         } finally { | ||||
|             $this->info['pretransfer_time'] = $this->info['total_time'] = microtime(true) - $this->info['start_time']; | ||||
|             restore_error_handler(); | ||||
|         } | ||||
|  | ||||
|         if (isset($context['ssl']['capture_peer_cert_chain']) && isset(($context = stream_context_get_options($this->context))['ssl']['peer_certificate_chain'])) { | ||||
|             $this->info['peer_certificate_chain'] = $context['ssl']['peer_certificate_chain']; | ||||
|         } | ||||
|  | ||||
|         stream_set_blocking($h, false); | ||||
|         unset($this->context, $this->resolver); | ||||
|  | ||||
|         // Create dechunk buffers | ||||
|         if (isset($this->headers['content-length'])) { | ||||
|             $this->remaining = (int) $this->headers['content-length'][0]; | ||||
|         } elseif ('chunked' === ($this->headers['transfer-encoding'][0] ?? null)) { | ||||
|             stream_filter_append($this->buffer, 'dechunk', \STREAM_FILTER_WRITE); | ||||
|             $this->remaining = -1; | ||||
|         } else { | ||||
|             $this->remaining = -2; | ||||
|         } | ||||
|  | ||||
|         $this->multi->handlesActivity[$this->id] = [new FirstChunk()]; | ||||
|  | ||||
|         if ('HEAD' === $context['http']['method'] || \in_array($this->info['http_code'], [204, 304], true)) { | ||||
|             $this->multi->handlesActivity[$this->id][] = null; | ||||
|             $this->multi->handlesActivity[$this->id][] = null; | ||||
|  | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         $host = parse_url($this->info['redirect_url'] ?? $this->url, \PHP_URL_HOST); | ||||
|         $this->multi->lastTimeout = null; | ||||
|         $this->multi->openHandles[$this->id] = [&$this->pauseExpiry, $h, $this->buffer, $this->onProgress, &$this->remaining, &$this->info, $host]; | ||||
|         $this->multi->hosts[$host] = 1 + ($this->multi->hosts[$host] ?? 0); | ||||
|     } | ||||
|  | ||||
|     private function close(): void | ||||
|     { | ||||
|         $this->canary->cancel(); | ||||
|         $this->handle = $this->buffer = $this->inflate = $this->onProgress = null; | ||||
|     } | ||||
|  | ||||
|     private static function schedule(self $response, array &$runningResponses): void | ||||
|     { | ||||
|         if (!isset($runningResponses[$i = $response->multi->id])) { | ||||
|             $runningResponses[$i] = [$response->multi, []]; | ||||
|         } | ||||
|  | ||||
|         $runningResponses[$i][1][$response->id] = $response; | ||||
|  | ||||
|         if (null === $response->buffer) { | ||||
|             // Response already completed | ||||
|             $response->multi->handlesActivity[$response->id][] = null; | ||||
|             $response->multi->handlesActivity[$response->id][] = null !== $response->info['error'] ? new TransportException($response->info['error']) : null; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param NativeClientState $multi | ||||
|      */ | ||||
|     private static function perform(ClientState $multi, ?array &$responses = null): void | ||||
|     { | ||||
|         foreach ($multi->openHandles as $i => [$pauseExpiry, $h, $buffer, $onProgress]) { | ||||
|             if ($pauseExpiry) { | ||||
|                 if (hrtime(true) / 1E9 < $pauseExpiry) { | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 $multi->openHandles[$i][0] = 0; | ||||
|             } | ||||
|  | ||||
|             $hasActivity = false; | ||||
|             $remaining = &$multi->openHandles[$i][4]; | ||||
|             $info = &$multi->openHandles[$i][5]; | ||||
|             $e = null; | ||||
|  | ||||
|             // Read incoming buffer and write it to the dechunk one | ||||
|             try { | ||||
|                 if ($remaining && '' !== $data = (string) fread($h, 0 > $remaining ? 16372 : $remaining)) { | ||||
|                     fwrite($buffer, $data); | ||||
|                     $hasActivity = true; | ||||
|                     $multi->sleep = false; | ||||
|  | ||||
|                     if (-1 !== $remaining) { | ||||
|                         $remaining -= \strlen($data); | ||||
|                     } | ||||
|                 } | ||||
|             } catch (\Throwable $e) { | ||||
|                 $hasActivity = $onProgress = false; | ||||
|             } | ||||
|  | ||||
|             if (!$hasActivity) { | ||||
|                 if ($onProgress) { | ||||
|                     try { | ||||
|                         // Notify the progress callback so that it can e.g. cancel | ||||
|                         // the request if the stream is inactive for too long | ||||
|                         $info['total_time'] = microtime(true) - $info['start_time']; | ||||
|                         $onProgress(); | ||||
|                     } catch (\Throwable $e) { | ||||
|                         // no-op | ||||
|                     } | ||||
|                 } | ||||
|             } elseif ('' !== $data = stream_get_contents($buffer, -1, 0)) { | ||||
|                 rewind($buffer); | ||||
|                 ftruncate($buffer, 0); | ||||
|  | ||||
|                 if (null === $e) { | ||||
|                     $multi->handlesActivity[$i][] = $data; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (null !== $e || !$remaining || feof($h)) { | ||||
|                 // Stream completed | ||||
|                 $info['total_time'] = microtime(true) - $info['start_time']; | ||||
|                 $info['starttransfer_time'] = $info['starttransfer_time'] ?: $info['total_time']; | ||||
|  | ||||
|                 if ($onProgress) { | ||||
|                     try { | ||||
|                         $onProgress(-1); | ||||
|                     } catch (\Throwable $e) { | ||||
|                         // no-op | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 if (null === $e) { | ||||
|                     if (0 < $remaining) { | ||||
|                         $e = new TransportException(sprintf('Transfer closed with %s bytes remaining to read.', $remaining)); | ||||
|                     } elseif (-1 === $remaining && fwrite($buffer, '-') && '' !== stream_get_contents($buffer, -1, 0)) { | ||||
|                         $e = new TransportException('Transfer closed with outstanding data remaining from chunked response.'); | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 $multi->handlesActivity[$i][] = null; | ||||
|                 $multi->handlesActivity[$i][] = $e; | ||||
|                 if (null !== ($host = $multi->openHandles[$i][6] ?? null) && 0 >= --$multi->hosts[$host]) { | ||||
|                     unset($multi->hosts[$host]); | ||||
|                 } | ||||
|                 unset($multi->openHandles[$i]); | ||||
|                 $multi->sleep = false; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (null === $responses) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         $maxHosts = $multi->maxHostConnections; | ||||
|  | ||||
|         foreach ($responses as $i => $response) { | ||||
|             if (null !== $response->remaining || null === $response->buffer) { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             if ($response->pauseExpiry && hrtime(true) / 1E9 < $response->pauseExpiry) { | ||||
|                 // Create empty open handles to tell we still have pending requests | ||||
|                 $multi->openHandles[$i] = [\INF, null, null, null]; | ||||
|             } elseif ($maxHosts && $maxHosts > ($multi->hosts[parse_url($response->url, \PHP_URL_HOST)] ?? 0)) { | ||||
|                 // Open the next pending request - this is a blocking operation so we do only one of them | ||||
|                 $response->open(); | ||||
|                 $multi->sleep = false; | ||||
|                 self::perform($multi); | ||||
|                 $maxHosts = 0; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param NativeClientState $multi | ||||
|      */ | ||||
|     private static function select(ClientState $multi, float $timeout): int | ||||
|     { | ||||
|         if (!$multi->sleep = !$multi->sleep) { | ||||
|             return -1; | ||||
|         } | ||||
|  | ||||
|         $_ = $handles = []; | ||||
|         $now = null; | ||||
|  | ||||
|         foreach ($multi->openHandles as [$pauseExpiry, $h]) { | ||||
|             if (null === $h) { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             if ($pauseExpiry && ($now ??= hrtime(true) / 1E9) < $pauseExpiry) { | ||||
|                 $timeout = min($timeout, $pauseExpiry - $now); | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             $handles[] = $h; | ||||
|         } | ||||
|  | ||||
|         if (!$handles) { | ||||
|             usleep((int) (1E6 * $timeout)); | ||||
|  | ||||
|             return 0; | ||||
|         } | ||||
|  | ||||
|         return stream_select($handles, $_, $_, (int) $timeout, (int) (1E6 * ($timeout - (int) $timeout))); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										54
									
								
								libraries/vendor/symfony/http-client/Response/ResponseStream.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								libraries/vendor/symfony/http-client/Response/ResponseStream.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,54 @@ | ||||
| <?php | ||||
|  | ||||
| /* | ||||
|  * This file is part of the Symfony package. | ||||
|  * | ||||
|  * (c) Fabien Potencier <fabien@symfony.com> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| namespace Symfony\Component\HttpClient\Response; | ||||
|  | ||||
| use Symfony\Contracts\HttpClient\ChunkInterface; | ||||
| use Symfony\Contracts\HttpClient\ResponseInterface; | ||||
| use Symfony\Contracts\HttpClient\ResponseStreamInterface; | ||||
|  | ||||
| /** | ||||
|  * @author Nicolas Grekas <p@tchwork.com> | ||||
|  */ | ||||
| final class ResponseStream implements ResponseStreamInterface | ||||
| { | ||||
|     private \Generator $generator; | ||||
|  | ||||
|     public function __construct(\Generator $generator) | ||||
|     { | ||||
|         $this->generator = $generator; | ||||
|     } | ||||
|  | ||||
|     public function key(): ResponseInterface | ||||
|     { | ||||
|         return $this->generator->key(); | ||||
|     } | ||||
|  | ||||
|     public function current(): ChunkInterface | ||||
|     { | ||||
|         return $this->generator->current(); | ||||
|     } | ||||
|  | ||||
|     public function next(): void | ||||
|     { | ||||
|         $this->generator->next(); | ||||
|     } | ||||
|  | ||||
|     public function rewind(): void | ||||
|     { | ||||
|         $this->generator->rewind(); | ||||
|     } | ||||
|  | ||||
|     public function valid(): bool | ||||
|     { | ||||
|         return $this->generator->valid(); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										312
									
								
								libraries/vendor/symfony/http-client/Response/StreamWrapper.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										312
									
								
								libraries/vendor/symfony/http-client/Response/StreamWrapper.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,312 @@ | ||||
| <?php | ||||
|  | ||||
| /* | ||||
|  * This file is part of the Symfony package. | ||||
|  * | ||||
|  * (c) Fabien Potencier <fabien@symfony.com> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| namespace Symfony\Component\HttpClient\Response; | ||||
|  | ||||
| use Symfony\Contracts\HttpClient\Exception\ExceptionInterface; | ||||
| use Symfony\Contracts\HttpClient\HttpClientInterface; | ||||
| use Symfony\Contracts\HttpClient\ResponseInterface; | ||||
|  | ||||
| /** | ||||
|  * Allows turning ResponseInterface instances to PHP streams. | ||||
|  * | ||||
|  * @author Nicolas Grekas <p@tchwork.com> | ||||
|  */ | ||||
| class StreamWrapper | ||||
| { | ||||
|     /** @var resource|null */ | ||||
|     public $context; | ||||
|  | ||||
|     private HttpClientInterface|ResponseInterface $client; | ||||
|  | ||||
|     private ResponseInterface $response; | ||||
|  | ||||
|     /** @var resource|string|null */ | ||||
|     private $content = null; | ||||
|  | ||||
|     /** @var resource|callable|null */ | ||||
|     private $handle; | ||||
|  | ||||
|     private bool $blocking = true; | ||||
|     private ?float $timeout = null; | ||||
|     private bool $eof = false; | ||||
|     private ?int $offset = 0; | ||||
|  | ||||
|     /** | ||||
|      * Creates a PHP stream resource from a ResponseInterface. | ||||
|      * | ||||
|      * @return resource | ||||
|      */ | ||||
|     public static function createResource(ResponseInterface $response, ?HttpClientInterface $client = null) | ||||
|     { | ||||
|         if ($response instanceof StreamableInterface) { | ||||
|             $stack = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS, 2); | ||||
|  | ||||
|             if ($response !== ($stack[1]['object'] ?? null)) { | ||||
|                 return $response->toStream(false); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (null === $client && !method_exists($response, 'stream')) { | ||||
|             throw new \InvalidArgumentException(sprintf('Providing a client to "%s()" is required when the response doesn\'t have any "stream()" method.', __CLASS__)); | ||||
|         } | ||||
|  | ||||
|         static $registered = false; | ||||
|  | ||||
|         if (!$registered = $registered || stream_wrapper_register(strtr(__CLASS__, '\\', '-'), __CLASS__)) { | ||||
|             throw new \RuntimeException(error_get_last()['message'] ?? 'Registering the "symfony" stream wrapper failed.'); | ||||
|         } | ||||
|  | ||||
|         $context = [ | ||||
|             'client' => $client ?? $response, | ||||
|             'response' => $response, | ||||
|         ]; | ||||
|  | ||||
|         return fopen(strtr(__CLASS__, '\\', '-').'://'.$response->getInfo('url'), 'r', false, stream_context_create(['symfony' => $context])); | ||||
|     } | ||||
|  | ||||
|     public function getResponse(): ResponseInterface | ||||
|     { | ||||
|         return $this->response; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param resource|callable|null $handle  The resource handle that should be monitored when | ||||
|      *                                        stream_select() is used on the created stream | ||||
|      * @param resource|null          $content The seekable resource where the response body is buffered | ||||
|      */ | ||||
|     public function bindHandles(&$handle, &$content): void | ||||
|     { | ||||
|         $this->handle = &$handle; | ||||
|         $this->content = &$content; | ||||
|         $this->offset = null; | ||||
|     } | ||||
|  | ||||
|     public function stream_open(string $path, string $mode, int $options): bool | ||||
|     { | ||||
|         if ('r' !== $mode) { | ||||
|             if ($options & \STREAM_REPORT_ERRORS) { | ||||
|                 trigger_error(sprintf('Invalid mode "%s": only "r" is supported.', $mode), \E_USER_WARNING); | ||||
|             } | ||||
|  | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         $context = stream_context_get_options($this->context)['symfony'] ?? null; | ||||
|         $this->client = $context['client'] ?? null; | ||||
|         $this->response = $context['response'] ?? null; | ||||
|         $this->context = null; | ||||
|  | ||||
|         if (null !== $this->client && null !== $this->response) { | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         if ($options & \STREAM_REPORT_ERRORS) { | ||||
|             trigger_error('Missing options "client" or "response" in "symfony" stream context.', \E_USER_WARNING); | ||||
|         } | ||||
|  | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     public function stream_read(int $count): string|false | ||||
|     { | ||||
|         if (\is_resource($this->content)) { | ||||
|             // Empty the internal activity list | ||||
|             foreach ($this->client->stream([$this->response], 0) as $chunk) { | ||||
|                 try { | ||||
|                     if (!$chunk->isTimeout() && $chunk->isFirst()) { | ||||
|                         $this->response->getStatusCode(); // ignore 3/4/5xx | ||||
|                     } | ||||
|                 } catch (ExceptionInterface $e) { | ||||
|                     trigger_error($e->getMessage(), \E_USER_WARNING); | ||||
|  | ||||
|                     return false; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (0 !== fseek($this->content, $this->offset ?? 0)) { | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|             if ('' !== $data = fread($this->content, $count)) { | ||||
|                 fseek($this->content, 0, \SEEK_END); | ||||
|                 $this->offset += \strlen($data); | ||||
|  | ||||
|                 return $data; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (\is_string($this->content)) { | ||||
|             if (\strlen($this->content) <= $count) { | ||||
|                 $data = $this->content; | ||||
|                 $this->content = null; | ||||
|             } else { | ||||
|                 $data = substr($this->content, 0, $count); | ||||
|                 $this->content = substr($this->content, $count); | ||||
|             } | ||||
|             $this->offset += \strlen($data); | ||||
|  | ||||
|             return $data; | ||||
|         } | ||||
|  | ||||
|         foreach ($this->client->stream([$this->response], $this->blocking ? $this->timeout : 0) as $chunk) { | ||||
|             try { | ||||
|                 $this->eof = true; | ||||
|                 $this->eof = !$chunk->isTimeout(); | ||||
|  | ||||
|                 if (!$this->eof && !$this->blocking) { | ||||
|                     return ''; | ||||
|                 } | ||||
|  | ||||
|                 $this->eof = $chunk->isLast(); | ||||
|  | ||||
|                 if ($chunk->isFirst()) { | ||||
|                     $this->response->getStatusCode(); // ignore 3/4/5xx | ||||
|                 } | ||||
|  | ||||
|                 if ('' !== $data = $chunk->getContent()) { | ||||
|                     if (\strlen($data) > $count) { | ||||
|                         $this->content ??= substr($data, $count); | ||||
|                         $data = substr($data, 0, $count); | ||||
|                     } | ||||
|                     $this->offset += \strlen($data); | ||||
|  | ||||
|                     return $data; | ||||
|                 } | ||||
|             } catch (ExceptionInterface $e) { | ||||
|                 trigger_error($e->getMessage(), \E_USER_WARNING); | ||||
|  | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return ''; | ||||
|     } | ||||
|  | ||||
|     public function stream_set_option(int $option, int $arg1, ?int $arg2): bool | ||||
|     { | ||||
|         if (\STREAM_OPTION_BLOCKING === $option) { | ||||
|             $this->blocking = (bool) $arg1; | ||||
|         } elseif (\STREAM_OPTION_READ_TIMEOUT === $option) { | ||||
|             $this->timeout = $arg1 + $arg2 / 1e6; | ||||
|         } else { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     public function stream_tell(): int | ||||
|     { | ||||
|         return $this->offset ?? 0; | ||||
|     } | ||||
|  | ||||
|     public function stream_eof(): bool | ||||
|     { | ||||
|         return $this->eof && !\is_string($this->content); | ||||
|     } | ||||
|  | ||||
|     public function stream_seek(int $offset, int $whence = \SEEK_SET): bool | ||||
|     { | ||||
|         if (null === $this->content && null === $this->offset) { | ||||
|             $this->response->getStatusCode(); | ||||
|             $this->offset = 0; | ||||
|         } | ||||
|  | ||||
|         if (!\is_resource($this->content) || 0 !== fseek($this->content, 0, \SEEK_END)) { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         $size = ftell($this->content); | ||||
|  | ||||
|         if (\SEEK_CUR === $whence) { | ||||
|             $offset += $this->offset ?? 0; | ||||
|         } | ||||
|  | ||||
|         if (\SEEK_END === $whence || $size < $offset) { | ||||
|             foreach ($this->client->stream([$this->response]) as $chunk) { | ||||
|                 try { | ||||
|                     if ($chunk->isFirst()) { | ||||
|                         $this->response->getStatusCode(); // ignore 3/4/5xx | ||||
|                     } | ||||
|  | ||||
|                     // Chunks are buffered in $this->content already | ||||
|                     $size += \strlen($chunk->getContent()); | ||||
|  | ||||
|                     if (\SEEK_END !== $whence && $offset <= $size) { | ||||
|                         break; | ||||
|                     } | ||||
|                 } catch (ExceptionInterface $e) { | ||||
|                     trigger_error($e->getMessage(), \E_USER_WARNING); | ||||
|  | ||||
|                     return false; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (\SEEK_END === $whence) { | ||||
|                 $offset += $size; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (0 <= $offset && $offset <= $size) { | ||||
|             $this->eof = false; | ||||
|             $this->offset = $offset; | ||||
|  | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @return resource|false | ||||
|      */ | ||||
|     public function stream_cast(int $castAs) | ||||
|     { | ||||
|         if (\STREAM_CAST_FOR_SELECT === $castAs) { | ||||
|             $this->response->getHeaders(false); | ||||
|  | ||||
|             return (\is_callable($this->handle) ? ($this->handle)() : $this->handle) ?? false; | ||||
|         } | ||||
|  | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     public function stream_stat(): array | ||||
|     { | ||||
|         try { | ||||
|             $headers = $this->response->getHeaders(false); | ||||
|         } catch (ExceptionInterface $e) { | ||||
|             trigger_error($e->getMessage(), \E_USER_WARNING); | ||||
|             $headers = []; | ||||
|         } | ||||
|  | ||||
|         return [ | ||||
|             'dev' => 0, | ||||
|             'ino' => 0, | ||||
|             'mode' => 33060, | ||||
|             'nlink' => 0, | ||||
|             'uid' => 0, | ||||
|             'gid' => 0, | ||||
|             'rdev' => 0, | ||||
|             'size' => (int) ($headers['content-length'][0] ?? -1), | ||||
|             'atime' => 0, | ||||
|             'mtime' => strtotime($headers['last-modified'][0] ?? '') ?: 0, | ||||
|             'ctime' => 0, | ||||
|             'blksize' => 0, | ||||
|             'blocks' => 0, | ||||
|         ]; | ||||
|     } | ||||
|  | ||||
|     private function __construct() | ||||
|     { | ||||
|     } | ||||
| } | ||||
							
								
								
									
										35
									
								
								libraries/vendor/symfony/http-client/Response/StreamableInterface.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								libraries/vendor/symfony/http-client/Response/StreamableInterface.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,35 @@ | ||||
| <?php | ||||
|  | ||||
| /* | ||||
|  * This file is part of the Symfony package. | ||||
|  * | ||||
|  * (c) Fabien Potencier <fabien@symfony.com> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| namespace Symfony\Component\HttpClient\Response; | ||||
|  | ||||
| use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; | ||||
| use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface; | ||||
| use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface; | ||||
| use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; | ||||
|  | ||||
| /** | ||||
|  * @author Nicolas Grekas <p@tchwork.com> | ||||
|  */ | ||||
| interface StreamableInterface | ||||
| { | ||||
|     /** | ||||
|      * Casts the response to a PHP stream resource. | ||||
|      * | ||||
|      * @return resource | ||||
|      * | ||||
|      * @throws TransportExceptionInterface   When a network error occurs | ||||
|      * @throws RedirectionExceptionInterface On a 3xx when $throw is true and the "max_redirects" option has been reached | ||||
|      * @throws ClientExceptionInterface      On a 4xx when $throw is true | ||||
|      * @throws ServerExceptionInterface      On a 5xx when $throw is true | ||||
|      */ | ||||
|     public function toStream(bool $throw = true); | ||||
| } | ||||
							
								
								
									
										221
									
								
								libraries/vendor/symfony/http-client/Response/TraceableResponse.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										221
									
								
								libraries/vendor/symfony/http-client/Response/TraceableResponse.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,221 @@ | ||||
| <?php | ||||
|  | ||||
| /* | ||||
|  * This file is part of the Symfony package. | ||||
|  * | ||||
|  * (c) Fabien Potencier <fabien@symfony.com> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| namespace Symfony\Component\HttpClient\Response; | ||||
|  | ||||
| use Symfony\Component\HttpClient\Chunk\ErrorChunk; | ||||
| use Symfony\Component\HttpClient\Exception\ClientException; | ||||
| use Symfony\Component\HttpClient\Exception\RedirectionException; | ||||
| use Symfony\Component\HttpClient\Exception\ServerException; | ||||
| use Symfony\Component\HttpClient\TraceableHttpClient; | ||||
| use Symfony\Component\Stopwatch\StopwatchEvent; | ||||
| use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; | ||||
| use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface; | ||||
| use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface; | ||||
| use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; | ||||
| use Symfony\Contracts\HttpClient\HttpClientInterface; | ||||
| use Symfony\Contracts\HttpClient\ResponseInterface; | ||||
|  | ||||
| /** | ||||
|  * @author Nicolas Grekas <p@tchwork.com> | ||||
|  * | ||||
|  * @internal | ||||
|  */ | ||||
| class TraceableResponse implements ResponseInterface, StreamableInterface | ||||
| { | ||||
|     private HttpClientInterface $client; | ||||
|     private ResponseInterface $response; | ||||
|     private mixed $content; | ||||
|     private ?StopwatchEvent $event; | ||||
|  | ||||
|     public function __construct(HttpClientInterface $client, ResponseInterface $response, &$content, ?StopwatchEvent $event = null) | ||||
|     { | ||||
|         $this->client = $client; | ||||
|         $this->response = $response; | ||||
|         $this->content = &$content; | ||||
|         $this->event = $event; | ||||
|     } | ||||
|  | ||||
|     public function __sleep(): array | ||||
|     { | ||||
|         throw new \BadMethodCallException('Cannot serialize '.__CLASS__); | ||||
|     } | ||||
|  | ||||
|     public function __wakeup(): void | ||||
|     { | ||||
|         throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); | ||||
|     } | ||||
|  | ||||
|     public function __destruct() | ||||
|     { | ||||
|         try { | ||||
|             if (method_exists($this->response, '__destruct')) { | ||||
|                 $this->response->__destruct(); | ||||
|             } | ||||
|         } finally { | ||||
|             if ($this->event?->isStarted()) { | ||||
|                 $this->event->stop(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public function getStatusCode(): int | ||||
|     { | ||||
|         try { | ||||
|             return $this->response->getStatusCode(); | ||||
|         } finally { | ||||
|             if ($this->event?->isStarted()) { | ||||
|                 $this->event->lap(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public function getHeaders(bool $throw = true): array | ||||
|     { | ||||
|         try { | ||||
|             return $this->response->getHeaders($throw); | ||||
|         } finally { | ||||
|             if ($this->event?->isStarted()) { | ||||
|                 $this->event->lap(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public function getContent(bool $throw = true): string | ||||
|     { | ||||
|         try { | ||||
|             if (false === $this->content) { | ||||
|                 return $this->response->getContent($throw); | ||||
|             } | ||||
|  | ||||
|             return $this->content = $this->response->getContent(false); | ||||
|         } finally { | ||||
|             if ($this->event?->isStarted()) { | ||||
|                 $this->event->stop(); | ||||
|             } | ||||
|             if ($throw) { | ||||
|                 $this->checkStatusCode($this->response->getStatusCode()); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public function toArray(bool $throw = true): array | ||||
|     { | ||||
|         try { | ||||
|             if (false === $this->content) { | ||||
|                 return $this->response->toArray($throw); | ||||
|             } | ||||
|  | ||||
|             return $this->content = $this->response->toArray(false); | ||||
|         } finally { | ||||
|             if ($this->event?->isStarted()) { | ||||
|                 $this->event->stop(); | ||||
|             } | ||||
|             if ($throw) { | ||||
|                 $this->checkStatusCode($this->response->getStatusCode()); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public function cancel(): void | ||||
|     { | ||||
|         $this->response->cancel(); | ||||
|  | ||||
|         if ($this->event?->isStarted()) { | ||||
|             $this->event->stop(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public function getInfo(?string $type = null): mixed | ||||
|     { | ||||
|         return $this->response->getInfo($type); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Casts the response to a PHP stream resource. | ||||
|      * | ||||
|      * @return resource | ||||
|      * | ||||
|      * @throws TransportExceptionInterface   When a network error occurs | ||||
|      * @throws RedirectionExceptionInterface On a 3xx when $throw is true and the "max_redirects" option has been reached | ||||
|      * @throws ClientExceptionInterface      On a 4xx when $throw is true | ||||
|      * @throws ServerExceptionInterface      On a 5xx when $throw is true | ||||
|      */ | ||||
|     public function toStream(bool $throw = true) | ||||
|     { | ||||
|         if ($throw) { | ||||
|             // Ensure headers arrived | ||||
|             $this->response->getHeaders(true); | ||||
|         } | ||||
|  | ||||
|         if ($this->response instanceof StreamableInterface) { | ||||
|             return $this->response->toStream(false); | ||||
|         } | ||||
|  | ||||
|         return StreamWrapper::createResource($this->response, $this->client); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @internal | ||||
|      */ | ||||
|     public static function stream(HttpClientInterface $client, iterable $responses, ?float $timeout): \Generator | ||||
|     { | ||||
|         $wrappedResponses = []; | ||||
|         $traceableMap = new \SplObjectStorage(); | ||||
|  | ||||
|         foreach ($responses as $r) { | ||||
|             if (!$r instanceof self) { | ||||
|                 throw new \TypeError(sprintf('"%s::stream()" expects parameter 1 to be an iterable of TraceableResponse objects, "%s" given.', TraceableHttpClient::class, get_debug_type($r))); | ||||
|             } | ||||
|  | ||||
|             $traceableMap[$r->response] = $r; | ||||
|             $wrappedResponses[] = $r->response; | ||||
|             if ($r->event && !$r->event->isStarted()) { | ||||
|                 $r->event->start(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         foreach ($client->stream($wrappedResponses, $timeout) as $r => $chunk) { | ||||
|             if ($traceableMap[$r]->event && $traceableMap[$r]->event->isStarted()) { | ||||
|                 try { | ||||
|                     if ($chunk->isTimeout() || !$chunk->isLast()) { | ||||
|                         $traceableMap[$r]->event->lap(); | ||||
|                     } else { | ||||
|                         $traceableMap[$r]->event->stop(); | ||||
|                     } | ||||
|                 } catch (TransportExceptionInterface $e) { | ||||
|                     $traceableMap[$r]->event->stop(); | ||||
|                     if ($chunk instanceof ErrorChunk) { | ||||
|                         $chunk->didThrow(false); | ||||
|                     } else { | ||||
|                         $chunk = new ErrorChunk($chunk->getOffset(), $e); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             yield $traceableMap[$r] => $chunk; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private function checkStatusCode(int $code): void | ||||
|     { | ||||
|         if (500 <= $code) { | ||||
|             throw new ServerException($this); | ||||
|         } | ||||
|  | ||||
|         if (400 <= $code) { | ||||
|             throw new ClientException($this); | ||||
|         } | ||||
|  | ||||
|         if (300 <= $code) { | ||||
|             throw new RedirectionException($this); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										305
									
								
								libraries/vendor/symfony/http-client/Response/TransportResponseTrait.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										305
									
								
								libraries/vendor/symfony/http-client/Response/TransportResponseTrait.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,305 @@ | ||||
| <?php | ||||
|  | ||||
| /* | ||||
|  * This file is part of the Symfony package. | ||||
|  * | ||||
|  * (c) Fabien Potencier <fabien@symfony.com> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| namespace Symfony\Component\HttpClient\Response; | ||||
|  | ||||
| use Psr\Log\LoggerInterface; | ||||
| use Symfony\Component\HttpClient\Chunk\DataChunk; | ||||
| use Symfony\Component\HttpClient\Chunk\ErrorChunk; | ||||
| use Symfony\Component\HttpClient\Chunk\FirstChunk; | ||||
| use Symfony\Component\HttpClient\Chunk\LastChunk; | ||||
| use Symfony\Component\HttpClient\Exception\TransportException; | ||||
| use Symfony\Component\HttpClient\Internal\Canary; | ||||
| use Symfony\Component\HttpClient\Internal\ClientState; | ||||
|  | ||||
| /** | ||||
|  * Implements common logic for transport-level response classes. | ||||
|  * | ||||
|  * @author Nicolas Grekas <p@tchwork.com> | ||||
|  * | ||||
|  * @internal | ||||
|  */ | ||||
| trait TransportResponseTrait | ||||
| { | ||||
|     private Canary $canary; | ||||
|     private array $headers = []; | ||||
|     private array $info = [ | ||||
|         'response_headers' => [], | ||||
|         'http_code' => 0, | ||||
|         'error' => null, | ||||
|         'canceled' => false, | ||||
|     ]; | ||||
|  | ||||
|     /** @var object|resource|null */ | ||||
|     private $handle; | ||||
|     private int|string $id; | ||||
|     private ?float $timeout = 0; | ||||
|     private \InflateContext|bool|null $inflate = null; | ||||
|     private ?array $finalInfo = null; | ||||
|     private ?LoggerInterface $logger = null; | ||||
|  | ||||
|     public function getStatusCode(): int | ||||
|     { | ||||
|         if ($this->initializer) { | ||||
|             self::initialize($this); | ||||
|         } | ||||
|  | ||||
|         return $this->info['http_code']; | ||||
|     } | ||||
|  | ||||
|     public function getHeaders(bool $throw = true): array | ||||
|     { | ||||
|         if ($this->initializer) { | ||||
|             self::initialize($this); | ||||
|         } | ||||
|  | ||||
|         if ($throw) { | ||||
|             $this->checkStatusCode(); | ||||
|         } | ||||
|  | ||||
|         return $this->headers; | ||||
|     } | ||||
|  | ||||
|     public function cancel(): void | ||||
|     { | ||||
|         $this->info['canceled'] = true; | ||||
|         $this->info['error'] = 'Response has been canceled.'; | ||||
|         $this->close(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Closes the response and all its network handles. | ||||
|      */ | ||||
|     protected function close(): void | ||||
|     { | ||||
|         $this->canary->cancel(); | ||||
|         $this->inflate = null; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Adds pending responses to the activity list. | ||||
|      */ | ||||
|     abstract protected static function schedule(self $response, array &$runningResponses): void; | ||||
|  | ||||
|     /** | ||||
|      * Performs all pending non-blocking operations. | ||||
|      */ | ||||
|     abstract protected static function perform(ClientState $multi, array &$responses): void; | ||||
|  | ||||
|     /** | ||||
|      * Waits for network activity. | ||||
|      */ | ||||
|     abstract protected static function select(ClientState $multi, float $timeout): int; | ||||
|  | ||||
|     private static function addResponseHeaders(array $responseHeaders, array &$info, array &$headers, string &$debug = ''): void | ||||
|     { | ||||
|         foreach ($responseHeaders as $h) { | ||||
|             if (11 <= \strlen($h) && '/' === $h[4] && preg_match('#^HTTP/\d+(?:\.\d+)? (\d\d\d)(?: |$)#', $h, $m)) { | ||||
|                 if ($headers) { | ||||
|                     $debug .= "< \r\n"; | ||||
|                     $headers = []; | ||||
|                 } | ||||
|                 $info['http_code'] = (int) $m[1]; | ||||
|             } elseif (2 === \count($m = explode(':', $h, 2))) { | ||||
|                 $headers[strtolower($m[0])][] = ltrim($m[1]); | ||||
|             } | ||||
|  | ||||
|             $debug .= "< {$h}\r\n"; | ||||
|             $info['response_headers'][] = $h; | ||||
|         } | ||||
|  | ||||
|         $debug .= "< \r\n"; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Ensures the request is always sent and that the response code was checked. | ||||
|      */ | ||||
|     private function doDestruct(): void | ||||
|     { | ||||
|         $this->shouldBuffer = true; | ||||
|  | ||||
|         if ($this->initializer && null === $this->info['error']) { | ||||
|             self::initialize($this); | ||||
|             $this->checkStatusCode(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Implements an event loop based on a buffer activity queue. | ||||
|      * | ||||
|      * @param iterable<array-key, self> $responses | ||||
|      * | ||||
|      * @internal | ||||
|      */ | ||||
|     public static function stream(iterable $responses, ?float $timeout = null): \Generator | ||||
|     { | ||||
|         $runningResponses = []; | ||||
|  | ||||
|         foreach ($responses as $response) { | ||||
|             self::schedule($response, $runningResponses); | ||||
|         } | ||||
|  | ||||
|         $lastActivity = hrtime(true) / 1E9; | ||||
|         $elapsedTimeout = 0; | ||||
|  | ||||
|         if ($fromLastTimeout = 0.0 === $timeout && '-0' === (string) $timeout) { | ||||
|             $timeout = null; | ||||
|         } elseif ($fromLastTimeout = 0 > $timeout) { | ||||
|             $timeout = -$timeout; | ||||
|         } | ||||
|  | ||||
|         while (true) { | ||||
|             $hasActivity = false; | ||||
|             $timeoutMax = 0; | ||||
|             $timeoutMin = $timeout ?? \INF; | ||||
|  | ||||
|             /** @var ClientState $multi */ | ||||
|             foreach ($runningResponses as $i => [$multi]) { | ||||
|                 $responses = &$runningResponses[$i][1]; | ||||
|                 self::perform($multi, $responses); | ||||
|  | ||||
|                 foreach ($responses as $j => $response) { | ||||
|                     $timeoutMax = $timeout ?? max($timeoutMax, $response->timeout); | ||||
|                     $timeoutMin = min($timeoutMin, $response->timeout, 1); | ||||
|                     $chunk = false; | ||||
|  | ||||
|                     if ($fromLastTimeout && null !== $multi->lastTimeout) { | ||||
|                         $elapsedTimeout = hrtime(true) / 1E9 - $multi->lastTimeout; | ||||
|                     } | ||||
|  | ||||
|                     if (isset($multi->handlesActivity[$j])) { | ||||
|                         $multi->lastTimeout = null; | ||||
|                     } elseif (!isset($multi->openHandles[$j])) { | ||||
|                         unset($responses[$j]); | ||||
|                         continue; | ||||
|                     } elseif ($elapsedTimeout >= $timeoutMax) { | ||||
|                         $multi->handlesActivity[$j] = [new ErrorChunk($response->offset, sprintf('Idle timeout reached for "%s".', $response->getInfo('url')))]; | ||||
|                         $multi->lastTimeout ??= $lastActivity; | ||||
|                     } else { | ||||
|                         continue; | ||||
|                     } | ||||
|  | ||||
|                     while ($multi->handlesActivity[$j] ?? false) { | ||||
|                         $hasActivity = true; | ||||
|                         $elapsedTimeout = 0; | ||||
|  | ||||
|                         if (\is_string($chunk = array_shift($multi->handlesActivity[$j]))) { | ||||
|                             if (null !== $response->inflate && false === $chunk = @inflate_add($response->inflate, $chunk)) { | ||||
|                                 $multi->handlesActivity[$j] = [null, new TransportException(sprintf('Error while processing content unencoding for "%s".', $response->getInfo('url')))]; | ||||
|                                 continue; | ||||
|                             } | ||||
|  | ||||
|                             if ('' !== $chunk && null !== $response->content && \strlen($chunk) !== fwrite($response->content, $chunk)) { | ||||
|                                 $multi->handlesActivity[$j] = [null, new TransportException(sprintf('Failed writing %d bytes to the response buffer.', \strlen($chunk)))]; | ||||
|                                 continue; | ||||
|                             } | ||||
|  | ||||
|                             $chunkLen = \strlen($chunk); | ||||
|                             $chunk = new DataChunk($response->offset, $chunk); | ||||
|                             $response->offset += $chunkLen; | ||||
|                         } elseif (null === $chunk) { | ||||
|                             $e = $multi->handlesActivity[$j][0]; | ||||
|                             unset($responses[$j], $multi->handlesActivity[$j]); | ||||
|                             $response->close(); | ||||
|  | ||||
|                             if (null !== $e) { | ||||
|                                 $response->info['error'] = $e->getMessage(); | ||||
|  | ||||
|                                 if ($e instanceof \Error) { | ||||
|                                     throw $e; | ||||
|                                 } | ||||
|  | ||||
|                                 $chunk = new ErrorChunk($response->offset, $e); | ||||
|                             } else { | ||||
|                                 if (0 === $response->offset && null === $response->content) { | ||||
|                                     $response->content = fopen('php://memory', 'w+'); | ||||
|                                 } | ||||
|  | ||||
|                                 $chunk = new LastChunk($response->offset); | ||||
|                             } | ||||
|                         } elseif ($chunk instanceof ErrorChunk) { | ||||
|                             unset($responses[$j]); | ||||
|                             $elapsedTimeout = $timeoutMax; | ||||
|                         } elseif ($chunk instanceof FirstChunk) { | ||||
|                             if ($response->logger) { | ||||
|                                 $info = $response->getInfo(); | ||||
|                                 $response->logger->info(sprintf('Response: "%s %s"', $info['http_code'], $info['url'])); | ||||
|                             } | ||||
|  | ||||
|                             $response->inflate = \extension_loaded('zlib') && $response->inflate && 'gzip' === ($response->headers['content-encoding'][0] ?? null) ? inflate_init(\ZLIB_ENCODING_GZIP) : null; | ||||
|  | ||||
|                             if ($response->shouldBuffer instanceof \Closure) { | ||||
|                                 try { | ||||
|                                     $response->shouldBuffer = ($response->shouldBuffer)($response->headers); | ||||
|  | ||||
|                                     if (null !== $response->info['error']) { | ||||
|                                         throw new TransportException($response->info['error']); | ||||
|                                     } | ||||
|                                 } catch (\Throwable $e) { | ||||
|                                     $response->close(); | ||||
|                                     $multi->handlesActivity[$j] = [null, $e]; | ||||
|                                 } | ||||
|                             } | ||||
|  | ||||
|                             if (true === $response->shouldBuffer) { | ||||
|                                 $response->content = fopen('php://temp', 'w+'); | ||||
|                             } elseif (\is_resource($response->shouldBuffer)) { | ||||
|                                 $response->content = $response->shouldBuffer; | ||||
|                             } | ||||
|                             $response->shouldBuffer = null; | ||||
|  | ||||
|                             yield $response => $chunk; | ||||
|  | ||||
|                             if ($response->initializer && null === $response->info['error']) { | ||||
|                                 // Ensure the HTTP status code is always checked | ||||
|                                 $response->getHeaders(true); | ||||
|                             } | ||||
|  | ||||
|                             continue; | ||||
|                         } | ||||
|  | ||||
|                         yield $response => $chunk; | ||||
|                     } | ||||
|  | ||||
|                     unset($multi->handlesActivity[$j]); | ||||
|  | ||||
|                     if ($chunk instanceof ErrorChunk && !$chunk->didThrow()) { | ||||
|                         // Ensure transport exceptions are always thrown | ||||
|                         $chunk->getContent(); | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 if (!$responses) { | ||||
|                     unset($runningResponses[$i]); | ||||
|                 } | ||||
|  | ||||
|                 // Prevent memory leaks | ||||
|                 $multi->handlesActivity = $multi->handlesActivity ?: []; | ||||
|                 $multi->openHandles = $multi->openHandles ?: []; | ||||
|             } | ||||
|  | ||||
|             if (!$runningResponses) { | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
|             if ($hasActivity) { | ||||
|                 $lastActivity = hrtime(true) / 1E9; | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             if (-1 === self::select($multi, min($timeoutMin, $timeoutMax - $elapsedTimeout))) { | ||||
|                 usleep((int) min(500, 1E6 * $timeoutMin)); | ||||
|             } | ||||
|  | ||||
|             $elapsedTimeout = hrtime(true) / 1E9 - $lastActivity; | ||||
|         } | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user