Файловый менеджер - Редактировать - /home/avadvi5/calendar.aeronextgen.com/http-client.zip
Ðазад
PK �n�\�2�N �N Response/error_lognu �[��� [10-Apr-2025 19:12:56 UTC] PHP Fatal error: Trait "Symfony\Component\HttpClient\Response\CommonResponseTrait" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/AsyncResponse.php on line 30 [10-Apr-2025 20:37:04 UTC] PHP Fatal error: Trait "Symfony\Component\HttpClient\Response\CommonResponseTrait" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/CurlResponse.php on line 28 [10-Apr-2025 21:11:32 UTC] PHP Fatal error: Uncaught Error: Interface "Http\Promise\Promise" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/HttplugPromise.php:24 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/HttplugPromise.php on line 24 [11-Apr-2025 01:28:49 UTC] PHP Fatal error: Uncaught Error: Interface "Symfony\Contracts\HttpClient\ResponseStreamInterface" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/ResponseStream.php:21 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/ResponseStream.php on line 21 [11-Apr-2025 03:34:17 UTC] PHP Fatal error: Trait "Symfony\Component\HttpClient\Response\CommonResponseTrait" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/AmpResponseV5.php on line 42 [11-Apr-2025 03:46:47 UTC] PHP Fatal error: Trait "Symfony\Component\HttpClient\Response\CommonResponseTrait" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/MockResponse.php on line 26 [11-Apr-2025 03:50:05 UTC] PHP Fatal error: Trait "Symfony\Component\HttpClient\Response\CommonResponseTrait" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/AmpResponseV4.php on line 41 [11-Apr-2025 04:15:09 UTC] PHP Fatal error: Trait "Symfony\Component\HttpClient\Response\CommonResponseTrait" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/NativeResponse.php on line 27 [11-Apr-2025 04:21:16 UTC] PHP Fatal error: Uncaught Error: Class "Symfony\Component\HttpClient\Response\MockResponse" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/JsonMockResponse.php:16 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/JsonMockResponse.php on line 16 [11-Apr-2025 07:06:34 UTC] PHP Fatal error: Uncaught Error: Interface "Symfony\Contracts\HttpClient\ResponseInterface" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/TraceableResponse.php:32 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/TraceableResponse.php on line 32 [16-Apr-2025 10:15:14 UTC] PHP Fatal error: Uncaught Error: Class "Symfony\Component\HttpClient\Response\MockResponse" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/JsonMockResponse.php:16 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/JsonMockResponse.php on line 16 [16-Apr-2025 11:30:04 UTC] PHP Fatal error: Trait "Symfony\Component\HttpClient\Response\CommonResponseTrait" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/MockResponse.php on line 26 [16-Apr-2025 11:49:42 UTC] PHP Fatal error: Uncaught Error: Interface "Symfony\Contracts\HttpClient\ResponseInterface" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/TraceableResponse.php:32 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/TraceableResponse.php on line 32 [16-Apr-2025 19:26:45 UTC] PHP Fatal error: Trait "Symfony\Component\HttpClient\Response\CommonResponseTrait" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/NativeResponse.php on line 27 [16-Apr-2025 22:23:36 UTC] PHP Fatal error: Uncaught Error: Interface "Symfony\Contracts\HttpClient\ResponseStreamInterface" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/ResponseStream.php:21 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/ResponseStream.php on line 21 [16-Apr-2025 23:14:28 UTC] PHP Fatal error: Trait "Symfony\Component\HttpClient\Response\CommonResponseTrait" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/AsyncResponse.php on line 30 [19-Apr-2025 16:31:52 UTC] PHP Fatal error: Trait "Symfony\Component\HttpClient\Response\CommonResponseTrait" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/AmpResponseV5.php on line 42 [19-Apr-2025 22:37:55 UTC] PHP Fatal error: Uncaught Error: Interface "Http\Promise\Promise" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/HttplugPromise.php:24 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/HttplugPromise.php on line 24 [19-Apr-2025 23:21:03 UTC] PHP Fatal error: Trait "Symfony\Component\HttpClient\Response\CommonResponseTrait" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/CurlResponse.php on line 28 [20-Apr-2025 00:30:14 UTC] PHP Fatal error: Trait "Symfony\Component\HttpClient\Response\CommonResponseTrait" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/AmpResponseV4.php on line 41 [21-Apr-2025 21:52:45 UTC] PHP Fatal error: Trait "Symfony\Component\HttpClient\Response\CommonResponseTrait" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/CurlResponse.php on line 28 [21-Apr-2025 22:00:13 UTC] PHP Fatal error: Uncaught Error: Interface "Http\Promise\Promise" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/HttplugPromise.php:24 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/HttplugPromise.php on line 24 [22-Apr-2025 00:37:16 UTC] PHP Fatal error: Trait "Symfony\Component\HttpClient\Response\CommonResponseTrait" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/AmpResponseV4.php on line 41 [22-Apr-2025 04:16:39 UTC] PHP Fatal error: Uncaught Error: Class "Symfony\Component\HttpClient\Response\MockResponse" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/JsonMockResponse.php:16 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/JsonMockResponse.php on line 16 [22-Apr-2025 05:07:59 UTC] PHP Fatal error: Trait "Symfony\Component\HttpClient\Response\CommonResponseTrait" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/AmpResponseV5.php on line 42 [22-Apr-2025 05:14:34 UTC] PHP Fatal error: Trait "Symfony\Component\HttpClient\Response\CommonResponseTrait" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/NativeResponse.php on line 27 [22-Apr-2025 05:51:37 UTC] PHP Fatal error: Uncaught Error: Interface "Symfony\Contracts\HttpClient\ResponseStreamInterface" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/ResponseStream.php:21 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/ResponseStream.php on line 21 [22-Apr-2025 06:25:29 UTC] PHP Fatal error: Uncaught Error: Interface "Symfony\Contracts\HttpClient\ResponseInterface" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/TraceableResponse.php:32 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/TraceableResponse.php on line 32 [22-Apr-2025 09:42:16 UTC] PHP Fatal error: Trait "Symfony\Component\HttpClient\Response\CommonResponseTrait" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/AsyncResponse.php on line 30 [22-Apr-2025 10:28:45 UTC] PHP Fatal error: Trait "Symfony\Component\HttpClient\Response\CommonResponseTrait" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/MockResponse.php on line 26 [27-Apr-2025 12:39:24 UTC] PHP Fatal error: Trait "Symfony\Component\HttpClient\Response\CommonResponseTrait" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/CurlResponse.php on line 28 [27-Apr-2025 12:41:55 UTC] PHP Fatal error: Trait "Symfony\Component\HttpClient\Response\CommonResponseTrait" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/AmpResponseV5.php on line 42 [27-Apr-2025 15:11:17 UTC] PHP Fatal error: Trait "Symfony\Component\HttpClient\Response\CommonResponseTrait" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/AsyncResponse.php on line 30 [27-Apr-2025 18:33:10 UTC] PHP Fatal error: Uncaught Error: Interface "Symfony\Contracts\HttpClient\ResponseStreamInterface" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/ResponseStream.php:21 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/ResponseStream.php on line 21 [27-Apr-2025 18:58:23 UTC] PHP Fatal error: Trait "Symfony\Component\HttpClient\Response\CommonResponseTrait" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/NativeResponse.php on line 27 [27-Apr-2025 19:05:43 UTC] PHP Fatal error: Trait "Symfony\Component\HttpClient\Response\CommonResponseTrait" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/AmpResponseV4.php on line 41 [27-Apr-2025 19:35:50 UTC] PHP Fatal error: Uncaught Error: Class "Symfony\Component\HttpClient\Response\MockResponse" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/JsonMockResponse.php:16 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/JsonMockResponse.php on line 16 [27-Apr-2025 22:58:09 UTC] PHP Fatal error: Uncaught Error: Interface "Http\Promise\Promise" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/HttplugPromise.php:24 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/HttplugPromise.php on line 24 [28-Apr-2025 00:55:11 UTC] PHP Fatal error: Uncaught Error: Interface "Symfony\Contracts\HttpClient\ResponseInterface" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/TraceableResponse.php:32 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/TraceableResponse.php on line 32 [28-Apr-2025 01:25:29 UTC] PHP Fatal error: Trait "Symfony\Component\HttpClient\Response\CommonResponseTrait" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/MockResponse.php on line 26 [30-Apr-2025 02:43:06 UTC] PHP Fatal error: Uncaught Error: Interface "Http\Promise\Promise" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/HttplugPromise.php:24 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/HttplugPromise.php on line 24 [30-Apr-2025 02:43:16 UTC] PHP Fatal error: Trait "Symfony\Component\HttpClient\Response\CommonResponseTrait" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/NativeResponse.php on line 27 [30-Apr-2025 02:45:40 UTC] PHP Fatal error: Uncaught Error: Interface "Symfony\Contracts\HttpClient\ResponseStreamInterface" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/ResponseStream.php:21 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/ResponseStream.php on line 21 [30-Apr-2025 02:48:00 UTC] PHP Fatal error: Trait "Symfony\Component\HttpClient\Response\CommonResponseTrait" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/MockResponse.php on line 26 [30-Apr-2025 02:58:33 UTC] PHP Fatal error: Trait "Symfony\Component\HttpClient\Response\CommonResponseTrait" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/AmpResponseV5.php on line 42 [30-Apr-2025 04:39:22 UTC] PHP Fatal error: Uncaught Error: Interface "Symfony\Contracts\HttpClient\ResponseInterface" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/TraceableResponse.php:32 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/TraceableResponse.php on line 32 [30-Apr-2025 04:42:49 UTC] PHP Fatal error: Trait "Symfony\Component\HttpClient\Response\CommonResponseTrait" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/CurlResponse.php on line 28 [30-Apr-2025 05:05:20 UTC] PHP Fatal error: Trait "Symfony\Component\HttpClient\Response\CommonResponseTrait" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/AmpResponseV4.php on line 41 [30-Apr-2025 05:53:46 UTC] PHP Fatal error: Uncaught Error: Class "Symfony\Component\HttpClient\Response\MockResponse" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/JsonMockResponse.php:16 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/JsonMockResponse.php on line 16 [13-May-2025 08:59:34 UTC] PHP Fatal error: Trait "Symfony\Component\HttpClient\Response\CommonResponseTrait" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/AmpResponseV5.php on line 42 [13-May-2025 09:15:41 UTC] PHP Fatal error: Trait "Symfony\Component\HttpClient\Response\CommonResponseTrait" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/AmpResponseV4.php on line 41 [13-May-2025 09:18:22 UTC] PHP Fatal error: Uncaught Error: Interface "Symfony\Contracts\HttpClient\ResponseStreamInterface" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/ResponseStream.php:21 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/ResponseStream.php on line 21 [13-May-2025 09:18:27 UTC] PHP Fatal error: Uncaught Error: Class "Symfony\Component\HttpClient\Response\MockResponse" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/JsonMockResponse.php:16 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/JsonMockResponse.php on line 16 [13-May-2025 09:18:56 UTC] PHP Fatal error: Uncaught Error: Interface "Symfony\Contracts\HttpClient\ResponseInterface" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/TraceableResponse.php:32 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/TraceableResponse.php on line 32 [13-May-2025 09:31:25 UTC] PHP Fatal error: Trait "Symfony\Component\HttpClient\Response\CommonResponseTrait" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/NativeResponse.php on line 27 [13-May-2025 09:31:37 UTC] PHP Fatal error: Trait "Symfony\Component\HttpClient\Response\CommonResponseTrait" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/MockResponse.php on line 26 [13-May-2025 09:33:15 UTC] PHP Fatal error: Trait "Symfony\Component\HttpClient\Response\CommonResponseTrait" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/AsyncResponse.php on line 30 [13-May-2025 09:48:51 UTC] PHP Fatal error: Uncaught Error: Interface "Http\Promise\Promise" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/HttplugPromise.php:24 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/HttplugPromise.php on line 24 [13-May-2025 09:49:49 UTC] PHP Fatal error: Trait "Symfony\Component\HttpClient\Response\CommonResponseTrait" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/CurlResponse.php on line 28 [13-May-2025 23:54:43 UTC] PHP Fatal error: Trait "Symfony\Component\HttpClient\Response\CommonResponseTrait" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/AsyncResponse.php on line 30 [14-May-2025 00:00:21 UTC] PHP Fatal error: Uncaught Error: Class "Symfony\Component\HttpClient\Response\MockResponse" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/JsonMockResponse.php:16 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/JsonMockResponse.php on line 16 [14-May-2025 00:24:52 UTC] PHP Fatal error: Trait "Symfony\Component\HttpClient\Response\CommonResponseTrait" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/NativeResponse.php on line 27 [14-May-2025 00:47:48 UTC] PHP Fatal error: Trait "Symfony\Component\HttpClient\Response\CommonResponseTrait" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/AmpResponseV5.php on line 42 [14-May-2025 00:53:09 UTC] PHP Fatal error: Trait "Symfony\Component\HttpClient\Response\CommonResponseTrait" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/AmpResponseV4.php on line 41 [14-May-2025 00:57:09 UTC] PHP Fatal error: Uncaught Error: Interface "Http\Promise\Promise" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/HttplugPromise.php:24 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/HttplugPromise.php on line 24 [14-May-2025 01:05:55 UTC] PHP Fatal error: Uncaught Error: Interface "Symfony\Contracts\HttpClient\ResponseStreamInterface" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/ResponseStream.php:21 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/ResponseStream.php on line 21 [14-May-2025 01:13:55 UTC] PHP Fatal error: Uncaught Error: Interface "Symfony\Contracts\HttpClient\ResponseInterface" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/TraceableResponse.php:32 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/TraceableResponse.php on line 32 [14-May-2025 01:14:46 UTC] PHP Fatal error: Trait "Symfony\Component\HttpClient\Response\CommonResponseTrait" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/CurlResponse.php on line 28 [14-May-2025 01:22:11 UTC] PHP Fatal error: Trait "Symfony\Component\HttpClient\Response\CommonResponseTrait" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/symfony/http-client/Response/MockResponse.php on line 26 PK �n�\���$ �$ Response/StreamWrapper.phpnu �[��� <?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; /** @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() { } } PK �n�\�� Response/CommonResponseTrait.phpnu �[��� <?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__); } public function __wakeup(): void { 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); } } } PK �n�\�z��I I Response/CurlResponse.phpnu �[��� <?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; /** * @var resource */ private $debugBuffer; /** * @internal */ public function __construct( private CurlClientState $multi, \CurlHandle|string $ch, ?array $options = null, ?LoggerInterface $logger = null, string $method = 'GET', ?callable $resolveRedirect = null, ?int $curlVersion = null, ?string $originalUrl = null, ) { 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) || ('C' === $waitFor[0] && 'OpenSSL SSL_read: SSL_ERROR_SYSCALL, errno 0' === curl_error($ch) && -1.0 === curl_getinfo($ch, \CURLINFO_CONTENT_LENGTH_DOWNLOAD) && \in_array('close', array_map('strtolower', $responses[$id]->headers['connection'] ?? []), true) ) ? 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']); } } 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); } } PK �n�\�ap׃5 �5 Response/NativeResponse.phpnu �[��� <?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; private \Closure $resolver; private ?\Closure $onProgress; private ?int $remaining = null; /** * @var resource|null */ private $buffer; private float $pauseExpiry = 0.0; /** * @internal * * @param $context resource */ public function __construct( private NativeClientState $multi, private $context, private string $url, array $options, array &$info, callable $resolver, ?callable $onProgress, ?LoggerInterface $logger, ) { $this->id = $id = (int) $context; $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) && isset($multi->hosts[$host]) && 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) && isset($multi->hosts[$host]) && 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))); } } PK �n�\����/ �/ Response/MockResponse.phpnu �[��� <?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); } public static function fromFile(string $path, array $info = []): static { if (!is_file($path)) { throw new \InvalidArgumentException(\sprintf('File not found: "%s".', $path)); } return new static(file_get_contents($path), $info); } /** * 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() ?: []; if (null !== $mock->getInfo('start_time')) { $response->info['start_time'] = $mock->getInfo('start_time'); } // 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)); } } } PK �n�\8��I� � Response/HttplugPromise.phpnu �[��� <?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 { public function __construct( private GuzzlePromiseInterface $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)); } } PK �n�\q��� Response/AsyncContext.phpnu �[��� <?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 ResponseInterface $response; private array $info = []; /** * @param resource|null $content */ public function __construct( ?callable &$passthru, private HttpClientInterface $client, ResponseInterface &$response, array &$info, private $content, private int $offset, ) { $this->passthru = &$passthru; $this->response = &$response; $this->info = &$info; } /** * 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; }; } } PK �n�\�Ѫ�P P Response/ResponseStream.phpnu �[��� <?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 { public function __construct( private \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(); } } PK �n�\�P! C C Response/AmpResponseV5.phpnu �[��� <?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\DeferredCancellation; use Amp\DeferredFuture; use Amp\Future; use Amp\Http\Client\HttpException; use Amp\Http\Client\Request; use Amp\Http\Client\Response; use Psr\Log\LoggerInterface; use Revolt\EventLoop; 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\AmpBodyV5; use Symfony\Component\HttpClient\Internal\AmpClientStateV5; use Symfony\Component\HttpClient\Internal\Canary; use Symfony\Component\HttpClient\Internal\ClientState; use Symfony\Contracts\HttpClient\ResponseInterface; use function Amp\delay; use function Amp\Future\awaitFirst; /** * @author Nicolas Grekas <p@tchwork.com> * * @internal */ final class AmpResponseV5 implements ResponseInterface, StreamableInterface { use CommonResponseTrait; use TransportResponseTrait; private static string $nextId = 'a'; private ?array $options; private \Closure $onProgress; /** * @internal */ public function __construct( private AmpClientStateV5 $multi, Request $request, array $options, ?LoggerInterface $logger, ) { $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 DeferredCancellation(); $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); }; $pause = 0.0; $this->id = $id = self::$nextId++; $info['pause_handler'] = static function (float $duration) use (&$pause) { $pause = $duration; }; $multi->lastTimeout = null; $multi->openHandles[$id] = new DeferredFuture(); ++$multi->responseCount; $this->canary = new Canary(static function () use ($canceller, $multi, $id) { $canceller->cancel(); $multi->openHandles[$id]?->isComplete() || $multi->openHandles[$id]?->complete(); unset($multi->openHandles[$id], $multi->handlesActivity[$id]); }); EventLoop::queue(static function () use ($request, $multi, $id, &$info, &$headers, $canceller, &$options, $onProgress, &$handle, $logger, &$pause) { self::generateResponse($request, $multi, $id, $info, $headers, $canceller, $options, $onProgress, $handle, $logger, $pause); }); } 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 AmpClientStateV5 $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 AmpClientStateV5 $multi */ private static function select(ClientState $multi, float $timeout): int { $delay = new DeferredFuture(); $id = EventLoop::delay($timeout, $delay->complete(...)); awaitFirst((function () use ($delay, $multi) { yield $delay->getFuture(); foreach ($multi->openHandles as $deferred) { yield $deferred->getFuture(); } })()); if ($delay->isComplete()) { return 0; } $delay->complete(); EventLoop::cancel($id); return 1; } private static function generateResponse(Request $request, AmpClientStateV5 $multi, string $id, array &$info, array &$headers, DeferredCancellation $canceller, array &$options, \Closure $onProgress, &$handle, ?LoggerInterface $logger, float &$pause): void { $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()); $multi->openHandles[$id]->complete(); $multi->openHandles[$id] = new DeferredFuture(); }); try { if (null === $response = self::getPushedResponse($request, $multi, $info, $headers, $canceller, $options, $logger)) { $logger?->info(\sprintf('Request: "%s %s"', $info['http_method'], $info['url'])); $response = 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; $multi->openHandles[$id]->complete(); return; } if ($response->hasHeader('content-length')) { $info['download_content_length'] = (float) $response->getHeader('content-length'); } $body = $response->getBody(); while (true) { $multi->openHandles[$id]->complete(); $multi->openHandles[$id] = new DeferredFuture(); if (0 < $pause) { delay($pause, true, $canceller->getCancellation()); } if (null === $data = $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']; } } private static function followRedirects(Request $originRequest, AmpClientStateV5 $multi, array &$info, array &$headers, DeferredCancellation $canceller, array $options, \Closure $onProgress, &$handle, ?LoggerInterface $logger, float &$pause): ?Response { if (0 < $pause) { delay($pause, true, $canceller->getCancellation()); } $originRequest->setBody(new AmpBodyV5($options['body'], $info, $onProgress)); $response = $multi->request($options, $originRequest, $canceller->getCancellation(), $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 $response->getBody()->close(); } 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(AmpBodyV5::rewind($response->getRequest()->getBody())); } foreach ($originRequest->getHeaderPairs() as [$name, $value]) { $request->addHeader($name, $value); } if ($request->getUri()->getAuthority() !== $originRequest->getUri()->getAuthority()) { $request->removeHeader('authorization'); $request->removeHeader('cookie'); $request->removeHeader('host'); } if (0 < $pause) { delay($pause, true, $canceller->getCancellation()); } $response = $multi->request($options, $request, $canceller->getCancellation(), $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->getHeaderPairs() 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, AmpClientStateV5 $multi, array &$info, array &$headers, DeferredCancellation $canceller, array $options, ?LoggerInterface $logger): ?Response { if ('' !== $options['body']) { return null; } $authority = $request->getUri()->getAuthority(); $cancellation = $canceller->getCancellation(); 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; } } /** @var DeferredFuture $pushDeferred */ $id = $cancellation->subscribe(static fn ($e) => $pushDeferred->error($e)); try { /** @var Future $pushedResponse */ $response = $pushedResponse->await($cancellation); } finally { $cancellation->unsubscribe($id); } foreach (['authorization', 'cookie', 'range', 'proxy-authorization'] as $k) { if ($response->getHeaderArray($k) !== $request->getHeaderArray($k)) { continue 2; } } 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; } } } $info += [ 'connect_time' => 0.0, 'pretransfer_time' => 0.0, 'starttransfer_time' => 0.0, 'total_time' => 0.0, 'namelookup_time' => 0.0, 'primary_ip' => '', 'primary_port' => 0, 'start_time' => microtime(true), ]; $pushDeferred->complete(); $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; } return null; } } PK �n�\�Mj�^D ^D Response/AsyncResponse.phpnu �[��� <?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 ('debug' === ($type ?? 'debug')) { $debug = implode('', array_column($this->info['previous_info'] ?? [], 'debug')); $debug .= $this->response->getInfo('debug'); if ('debug' === $type) { return $debug; } } if (null !== $type) { return $this->info[$type] ?? $this->response->getInfo($type); } return array_merge($this->info + $this->response->getInfo(), ['debug' => $debug]); } /** * @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; } $chunk = null; 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) { throw new \LogicException(\sprintf('"%s" is not compliant with HttpClientInterface: its "stream()" method didn\'t yield any chunks when it should have.', get_debug_type($client))); } 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(); } } PK �n�\���C C Response/AmpResponseV4.phpnu �[��� <?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\AmpBodyV4; use Symfony\Component\HttpClient\Internal\AmpClientStateV4; 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 AmpResponseV4 implements ResponseInterface, StreamableInterface { use CommonResponseTrait; use TransportResponseTrait; private static string $nextId = 'a'; private ?array $options; private \Closure $onProgress; private static ?string $delay = null; /** * @internal */ public function __construct( private AmpClientStateV4 $multi, Request $request, array $options, ?LoggerInterface $logger, ) { $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 AmpClientStateV4 $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 AmpClientStateV4 $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, AmpClientStateV4 $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, AmpClientStateV4 $multi, array &$info, array &$headers, CancellationTokenSource $canceller, array $options, \Closure $onProgress, &$handle, ?LoggerInterface $logger, Promise &$pause): \Generator { yield $pause; $originRequest->setBody(new AmpBodyV4($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 (303 === $status || \in_array($status, [301, 302], true) && 'POST' === $response->getRequest()->getMethod()) { // Do like curl and browsers: turn POST to GET on 301, 302 and 303 $originRequest->removeHeader('transfer-encoding'); $originRequest->removeHeader('content-length'); $originRequest->removeHeader('content-type'); $info['http_method'] = 'HEAD' === $response->getRequest()->getMethod() ? 'HEAD' : 'GET'; $request->setMethod($info['http_method']); } else { $request->setBody(AmpBodyV4::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, AmpClientStateV4 $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; } } } $info += [ 'connect_time' => 0.0, 'pretransfer_time' => 0.0, 'starttransfer_time' => 0.0, 'total_time' => 0.0, 'namelookup_time' => 0.0, 'primary_ip' => '', 'primary_port' => 0, 'start_time' => microtime(true), ]; $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(...)); } } PK �n�\�K�;� � Response/JsonMockResponse.phpnu �[��� <?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); } public static function fromFile(string $path, array $info = []): static { if (!is_file($path)) { throw new InvalidArgumentException(\sprintf('File not found: "%s".', $path)); } $json = file_get_contents($path); if (!json_validate($json)) { throw new \InvalidArgumentException(\sprintf('File "%s" does not contain valid JSON.', $path)); } return new static(json_decode($json, true, flags: \JSON_THROW_ON_ERROR), $info); } } PK �n�\��� � Response/StreamableInterface.phpnu �[��� <?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); } PK �n�\\?�{ { Response/TraceableResponse.phpnu �[��� <?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 { public function __construct( private HttpClientInterface $client, private ResponseInterface $response, private mixed &$content, private ?StopwatchEvent $event = null, ) { } 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); } } } PK �n�\�����, �, # Response/TransportResponseTrait.phpnu �[��� <?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('Response: "{http_code} {url}" {total_time} seconds', [ 'http_code' => $info['http_code'], 'url' => $info['url'], 'total_time' => $info['total_time'], ]); } $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; } } } PK �n�\�O�{&