vendor/php-http/client-common/src/Plugin/DecoderPlugin.php line 72

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace Http\Client\Common\Plugin;
  4. use Http\Client\Common\Plugin;
  5. use Http\Message\Encoding;
  6. use Http\Promise\Promise;
  7. use Psr\Http\Message\RequestInterface;
  8. use Psr\Http\Message\ResponseInterface;
  9. use Psr\Http\Message\StreamInterface;
  10. use Symfony\Component\OptionsResolver\OptionsResolver;
  11. /**
  12.  * Allow to decode response body with a chunk, deflate, compress or gzip encoding.
  13.  *
  14.  * If zlib is not installed, only chunked encoding can be handled.
  15.  *
  16.  * If Content-Encoding is not disabled, the plugin will add an Accept-Encoding header for the encoding methods it supports.
  17.  *
  18.  * @author Joel Wurtz <joel.wurtz@gmail.com>
  19.  */
  20. final class DecoderPlugin implements Plugin
  21. {
  22.     /**
  23.      * @var bool Whether this plugin decode stream with value in the Content-Encoding header (default to true).
  24.      *
  25.      * If set to false only the Transfer-Encoding header will be used
  26.      */
  27.     private $useContentEncoding;
  28.     /**
  29.      * @param array{'use_content_encoding'?: bool} $config
  30.      *
  31.      * Configuration options:
  32.      *   - use_content_encoding: Whether this plugin should look at the Content-Encoding header first or only at the Transfer-Encoding (defaults to true)
  33.      */
  34.     public function __construct(array $config = [])
  35.     {
  36.         $resolver = new OptionsResolver();
  37.         $resolver->setDefaults([
  38.             'use_content_encoding' => true,
  39.         ]);
  40.         $resolver->setAllowedTypes('use_content_encoding''bool');
  41.         $options $resolver->resolve($config);
  42.         $this->useContentEncoding $options['use_content_encoding'];
  43.     }
  44.     /**
  45.      * {@inheritdoc}
  46.      */
  47.     public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise
  48.     {
  49.         $encodings extension_loaded('zlib') ? ['gzip''deflate'] : ['identity'];
  50.         if ($this->useContentEncoding) {
  51.             $request $request->withHeader('Accept-Encoding'$encodings);
  52.         }
  53.         $encodings[] = 'chunked';
  54.         $request $request->withHeader('TE'$encodings);
  55.         return $next($request)->then(function (ResponseInterface $response) {
  56.             return $this->decodeResponse($response);
  57.         });
  58.     }
  59.     /**
  60.      * Decode a response body given its Transfer-Encoding or Content-Encoding value.
  61.      */
  62.     private function decodeResponse(ResponseInterface $response): ResponseInterface
  63.     {
  64.         $response $this->decodeOnEncodingHeader('Transfer-Encoding'$response);
  65.         if ($this->useContentEncoding) {
  66.             $response $this->decodeOnEncodingHeader('Content-Encoding'$response);
  67.         }
  68.         return $response;
  69.     }
  70.     /**
  71.      * Decode a response on a specific header (content encoding or transfer encoding mainly).
  72.      */
  73.     private function decodeOnEncodingHeader(string $headerNameResponseInterface $response): ResponseInterface
  74.     {
  75.         if ($response->hasHeader($headerName)) {
  76.             $encodings $response->getHeader($headerName);
  77.             $newEncodings = [];
  78.             while ($encoding array_pop($encodings)) {
  79.                 $stream $this->decorateStream($encoding$response->getBody());
  80.                 if (false === $stream) {
  81.                     array_unshift($newEncodings$encoding);
  82.                     continue;
  83.                 }
  84.                 $response $response->withBody($stream);
  85.             }
  86.             if (\count($newEncodings) > 0) {
  87.                 $response $response->withHeader($headerName$newEncodings);
  88.             } else {
  89.                 $response $response->withoutHeader($headerName);
  90.             }
  91.         }
  92.         return $response;
  93.     }
  94.     /**
  95.      * Decorate a stream given an encoding.
  96.      *
  97.      * @return StreamInterface|false A new stream interface or false if encoding is not supported
  98.      */
  99.     private function decorateStream(string $encodingStreamInterface $stream)
  100.     {
  101.         if ('chunked' === strtolower($encoding)) {
  102.             return new Encoding\DechunkStream($stream);
  103.         }
  104.         if ('deflate' === strtolower($encoding)) {
  105.             return new Encoding\DecompressStream($stream);
  106.         }
  107.         if ('gzip' === strtolower($encoding)) {
  108.             return new Encoding\GzipDecodeStream($stream);
  109.         }
  110.         return false;
  111.     }
  112. }