vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php line 71

Open in your IDE?
  1. <?php
  2. namespace GuzzleHttp;
  3. use GuzzleHttp\Exception\BadResponseException;
  4. use GuzzleHttp\Exception\TooManyRedirectsException;
  5. use GuzzleHttp\Promise\PromiseInterface;
  6. use Psr\Http\Message\RequestInterface;
  7. use Psr\Http\Message\ResponseInterface;
  8. use Psr\Http\Message\UriInterface;
  9. /**
  10.  * Request redirect middleware.
  11.  *
  12.  * Apply this middleware like other middleware using
  13.  * {@see \GuzzleHttp\Middleware::redirect()}.
  14.  *
  15.  * @final
  16.  */
  17. class RedirectMiddleware
  18. {
  19.     public const HISTORY_HEADER 'X-Guzzle-Redirect-History';
  20.     public const STATUS_HISTORY_HEADER 'X-Guzzle-Redirect-Status-History';
  21.     /**
  22.      * @var array
  23.      */
  24.     public static $defaultSettings = [
  25.         'max'             => 5,
  26.         'protocols'       => ['http''https'],
  27.         'strict'          => false,
  28.         'referer'         => false,
  29.         'track_redirects' => false,
  30.     ];
  31.     /**
  32.      * @var callable(RequestInterface, array): PromiseInterface
  33.      */
  34.     private $nextHandler;
  35.     /**
  36.      * @param callable(RequestInterface, array): PromiseInterface $nextHandler Next handler to invoke.
  37.      */
  38.     public function __construct(callable $nextHandler)
  39.     {
  40.         $this->nextHandler $nextHandler;
  41.     }
  42.     public function __invoke(RequestInterface $request, array $options): PromiseInterface
  43.     {
  44.         $fn $this->nextHandler;
  45.         if (empty($options['allow_redirects'])) {
  46.             return $fn($request$options);
  47.         }
  48.         if ($options['allow_redirects'] === true) {
  49.             $options['allow_redirects'] = self::$defaultSettings;
  50.         } elseif (!\is_array($options['allow_redirects'])) {
  51.             throw new \InvalidArgumentException('allow_redirects must be true, false, or array');
  52.         } else {
  53.             // Merge the default settings with the provided settings
  54.             $options['allow_redirects'] += self::$defaultSettings;
  55.         }
  56.         if (empty($options['allow_redirects']['max'])) {
  57.             return $fn($request$options);
  58.         }
  59.         return $fn($request$options)
  60.             ->then(function (ResponseInterface $response) use ($request$options) {
  61.                 return $this->checkRedirect($request$options$response);
  62.             });
  63.     }
  64.     /**
  65.      * @return ResponseInterface|PromiseInterface
  66.      */
  67.     public function checkRedirect(RequestInterface $request, array $optionsResponseInterface $response)
  68.     {
  69.         if (\strpos((string) $response->getStatusCode(), '3') !== 0
  70.             || !$response->hasHeader('Location')
  71.         ) {
  72.             return $response;
  73.         }
  74.         $this->guardMax($request$response$options);
  75.         $nextRequest $this->modifyRequest($request$options$response);
  76.         // If authorization is handled by curl, unset it if host is different.
  77.         if ($request->getUri()->getHost() !== $nextRequest->getUri()->getHost()
  78.             && defined('\CURLOPT_HTTPAUTH')
  79.         ) {
  80.             unset(
  81.                 $options['curl'][\CURLOPT_HTTPAUTH],
  82.                 $options['curl'][\CURLOPT_USERPWD]
  83.             );
  84.         }
  85.         if (isset($options['allow_redirects']['on_redirect'])) {
  86.             ($options['allow_redirects']['on_redirect'])(
  87.                 $request,
  88.                 $response,
  89.                 $nextRequest->getUri()
  90.             );
  91.         }
  92.         $promise $this($nextRequest$options);
  93.         // Add headers to be able to track history of redirects.
  94.         if (!empty($options['allow_redirects']['track_redirects'])) {
  95.             return $this->withTracking(
  96.                 $promise,
  97.                 (string) $nextRequest->getUri(),
  98.                 $response->getStatusCode()
  99.             );
  100.         }
  101.         return $promise;
  102.     }
  103.     /**
  104.      * Enable tracking on promise.
  105.      */
  106.     private function withTracking(PromiseInterface $promisestring $uriint $statusCode): PromiseInterface
  107.     {
  108.         return $promise->then(
  109.             static function (ResponseInterface $response) use ($uri$statusCode) {
  110.                 // Note that we are pushing to the front of the list as this
  111.                 // would be an earlier response than what is currently present
  112.                 // in the history header.
  113.                 $historyHeader $response->getHeader(self::HISTORY_HEADER);
  114.                 $statusHeader $response->getHeader(self::STATUS_HISTORY_HEADER);
  115.                 \array_unshift($historyHeader$uri);
  116.                 \array_unshift($statusHeader, (string) $statusCode);
  117.                 return $response->withHeader(self::HISTORY_HEADER$historyHeader)
  118.                                 ->withHeader(self::STATUS_HISTORY_HEADER$statusHeader);
  119.             }
  120.         );
  121.     }
  122.     /**
  123.      * Check for too many redirects
  124.      *
  125.      * @throws TooManyRedirectsException Too many redirects.
  126.      */
  127.     private function guardMax(RequestInterface $requestResponseInterface $response, array &$options): void
  128.     {
  129.         $current $options['__redirect_count']
  130.             ?? 0;
  131.         $options['__redirect_count'] = $current 1;
  132.         $max $options['allow_redirects']['max'];
  133.         if ($options['__redirect_count'] > $max) {
  134.             throw new TooManyRedirectsException("Will not follow more than {$max} redirects"$request$response);
  135.         }
  136.     }
  137.     public function modifyRequest(RequestInterface $request, array $optionsResponseInterface $response): RequestInterface
  138.     {
  139.         // Request modifications to apply.
  140.         $modify = [];
  141.         $protocols $options['allow_redirects']['protocols'];
  142.         // Use a GET request if this is an entity enclosing request and we are
  143.         // not forcing RFC compliance, but rather emulating what all browsers
  144.         // would do.
  145.         $statusCode $response->getStatusCode();
  146.         if ($statusCode == 303 ||
  147.             ($statusCode <= 302 && !$options['allow_redirects']['strict'])
  148.         ) {
  149.             $safeMethods = ['GET''HEAD''OPTIONS'];
  150.             $requestMethod $request->getMethod();
  151.             $modify['method'] = in_array($requestMethod$safeMethods) ? $requestMethod 'GET';
  152.             $modify['body'] = '';
  153.         }
  154.         $uri $this->redirectUri($request$response$protocols);
  155.         if (isset($options['idn_conversion']) && ($options['idn_conversion'] !== false)) {
  156.             $idnOptions = ($options['idn_conversion'] === true) ? \IDNA_DEFAULT $options['idn_conversion'];
  157.             $uri Utils::idnUriConvert($uri$idnOptions);
  158.         }
  159.         $modify['uri'] = $uri;
  160.         Psr7\Message::rewindBody($request);
  161.         // Add the Referer header if it is told to do so and only
  162.         // add the header if we are not redirecting from https to http.
  163.         if ($options['allow_redirects']['referer']
  164.             && $modify['uri']->getScheme() === $request->getUri()->getScheme()
  165.         ) {
  166.             $uri $request->getUri()->withUserInfo('');
  167.             $modify['set_headers']['Referer'] = (string) $uri;
  168.         } else {
  169.             $modify['remove_headers'][] = 'Referer';
  170.         }
  171.         // Remove Authorization header if host is different.
  172.         if ($request->getUri()->getHost() !== $modify['uri']->getHost()) {
  173.             $modify['remove_headers'][] = 'Authorization';
  174.         }
  175.         return Psr7\Utils::modifyRequest($request$modify);
  176.     }
  177.     /**
  178.      * Set the appropriate URL on the request based on the location header
  179.      */
  180.     private function redirectUri(RequestInterface $requestResponseInterface $response, array $protocols): UriInterface
  181.     {
  182.         $location Psr7\UriResolver::resolve(
  183.             $request->getUri(),
  184.             new Psr7\Uri($response->getHeaderLine('Location'))
  185.         );
  186.         // Ensure that the redirect URI is allowed based on the protocols.
  187.         if (!\in_array($location->getScheme(), $protocols)) {
  188.             throw new BadResponseException(\sprintf('Redirect URI, %s, does not use one of the allowed redirect protocols: %s'$location, \implode(', '$protocols)), $request$response);
  189.         }
  190.         return $location;
  191.     }
  192. }