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 URI is cross-origin.
  77.         if (Psr7\UriComparator::isCrossOrigin($request->getUri(), $nextRequest->getUri()) && defined('\CURLOPT_HTTPAUTH')) {
  78.             unset(
  79.                 $options['curl'][\CURLOPT_HTTPAUTH],
  80.                 $options['curl'][\CURLOPT_USERPWD]
  81.             );
  82.         }
  83.         if (isset($options['allow_redirects']['on_redirect'])) {
  84.             ($options['allow_redirects']['on_redirect'])(
  85.                 $request,
  86.                 $response,
  87.                 $nextRequest->getUri()
  88.             );
  89.         }
  90.         $promise $this($nextRequest$options);
  91.         // Add headers to be able to track history of redirects.
  92.         if (!empty($options['allow_redirects']['track_redirects'])) {
  93.             return $this->withTracking(
  94.                 $promise,
  95.                 (string) $nextRequest->getUri(),
  96.                 $response->getStatusCode()
  97.             );
  98.         }
  99.         return $promise;
  100.     }
  101.     /**
  102.      * Enable tracking on promise.
  103.      */
  104.     private function withTracking(PromiseInterface $promisestring $uriint $statusCode): PromiseInterface
  105.     {
  106.         return $promise->then(
  107.             static function (ResponseInterface $response) use ($uri$statusCode) {
  108.                 // Note that we are pushing to the front of the list as this
  109.                 // would be an earlier response than what is currently present
  110.                 // in the history header.
  111.                 $historyHeader $response->getHeader(self::HISTORY_HEADER);
  112.                 $statusHeader $response->getHeader(self::STATUS_HISTORY_HEADER);
  113.                 \array_unshift($historyHeader$uri);
  114.                 \array_unshift($statusHeader, (string) $statusCode);
  115.                 return $response->withHeader(self::HISTORY_HEADER$historyHeader)
  116.                                 ->withHeader(self::STATUS_HISTORY_HEADER$statusHeader);
  117.             }
  118.         );
  119.     }
  120.     /**
  121.      * Check for too many redirects.
  122.      *
  123.      * @throws TooManyRedirectsException Too many redirects.
  124.      */
  125.     private function guardMax(RequestInterface $requestResponseInterface $response, array &$options): void
  126.     {
  127.         $current $options['__redirect_count']
  128.             ?? 0;
  129.         $options['__redirect_count'] = $current 1;
  130.         $max $options['allow_redirects']['max'];
  131.         if ($options['__redirect_count'] > $max) {
  132.             throw new TooManyRedirectsException("Will not follow more than {$max} redirects"$request$response);
  133.         }
  134.     }
  135.     public function modifyRequest(RequestInterface $request, array $optionsResponseInterface $response): RequestInterface
  136.     {
  137.         // Request modifications to apply.
  138.         $modify = [];
  139.         $protocols $options['allow_redirects']['protocols'];
  140.         // Use a GET request if this is an entity enclosing request and we are
  141.         // not forcing RFC compliance, but rather emulating what all browsers
  142.         // would do.
  143.         $statusCode $response->getStatusCode();
  144.         if ($statusCode == 303 ||
  145.             ($statusCode <= 302 && !$options['allow_redirects']['strict'])
  146.         ) {
  147.             $safeMethods = ['GET''HEAD''OPTIONS'];
  148.             $requestMethod $request->getMethod();
  149.             $modify['method'] = in_array($requestMethod$safeMethods) ? $requestMethod 'GET';
  150.             $modify['body'] = '';
  151.         }
  152.         $uri self::redirectUri($request$response$protocols);
  153.         if (isset($options['idn_conversion']) && ($options['idn_conversion'] !== false)) {
  154.             $idnOptions = ($options['idn_conversion'] === true) ? \IDNA_DEFAULT $options['idn_conversion'];
  155.             $uri Utils::idnUriConvert($uri$idnOptions);
  156.         }
  157.         $modify['uri'] = $uri;
  158.         Psr7\Message::rewindBody($request);
  159.         // Add the Referer header if it is told to do so and only
  160.         // add the header if we are not redirecting from https to http.
  161.         if ($options['allow_redirects']['referer']
  162.             && $modify['uri']->getScheme() === $request->getUri()->getScheme()
  163.         ) {
  164.             $uri $request->getUri()->withUserInfo('');
  165.             $modify['set_headers']['Referer'] = (string) $uri;
  166.         } else {
  167.             $modify['remove_headers'][] = 'Referer';
  168.         }
  169.         // Remove Authorization and Cookie headers if URI is cross-origin.
  170.         if (Psr7\UriComparator::isCrossOrigin($request->getUri(), $modify['uri'])) {
  171.             $modify['remove_headers'][] = 'Authorization';
  172.             $modify['remove_headers'][] = 'Cookie';
  173.         }
  174.         return Psr7\Utils::modifyRequest($request$modify);
  175.     }
  176.     /**
  177.      * Set the appropriate URL on the request based on the location header.
  178.      */
  179.     private static function redirectUri(
  180.         RequestInterface $request,
  181.         ResponseInterface $response,
  182.         array $protocols
  183.     ): UriInterface {
  184.         $location Psr7\UriResolver::resolve(
  185.             $request->getUri(),
  186.             new Psr7\Uri($response->getHeaderLine('Location'))
  187.         );
  188.         // Ensure that the redirect URI is allowed based on the protocols.
  189.         if (!\in_array($location->getScheme(), $protocols)) {
  190.             throw new BadResponseException(\sprintf('Redirect URI, %s, does not use one of the allowed redirect protocols: %s'$location\implode(', '$protocols)), $request$response);
  191.         }
  192.         return $location;
  193.     }
  194. }