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

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 GuzzleHttp\Psr7;
  7. use Psr\Http\Message\RequestInterface;
  8. use Psr\Http\Message\ResponseInterface;
  9. use Psr\Http\Message\UriInterface;
  10. /**
  11.  * Request redirect middleware.
  12.  *
  13.  * Apply this middleware like other middleware using
  14.  * {@see \GuzzleHttp\Middleware::redirect()}.
  15.  */
  16. class RedirectMiddleware
  17. {
  18.     const HISTORY_HEADER 'X-Guzzle-Redirect-History';
  19.     const STATUS_HISTORY_HEADER 'X-Guzzle-Redirect-Status-History';
  20.     public static $defaultSettings = [
  21.         'max'             => 5,
  22.         'protocols'       => ['http''https'],
  23.         'strict'          => false,
  24.         'referer'         => false,
  25.         'track_redirects' => false,
  26.     ];
  27.     /** @var callable  */
  28.     private $nextHandler;
  29.     /**
  30.      * @param callable $nextHandler Next handler to invoke.
  31.      */
  32.     public function __construct(callable $nextHandler)
  33.     {
  34.         $this->nextHandler $nextHandler;
  35.     }
  36.     /**
  37.      * @param RequestInterface $request
  38.      * @param array            $options
  39.      *
  40.      * @return PromiseInterface
  41.      */
  42.     public function __invoke(RequestInterface $request, array $options)
  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.      * @param RequestInterface  $request
  66.      * @param array             $options
  67.      * @param ResponseInterface $response
  68.      *
  69.      * @return ResponseInterface|PromiseInterface
  70.      */
  71.     public function checkRedirect(
  72.         RequestInterface $request,
  73.         array $options,
  74.         ResponseInterface $response
  75.     ) {
  76.         if (substr($response->getStatusCode(), 01) != '3'
  77.             || !$response->hasHeader('Location')
  78.         ) {
  79.             return $response;
  80.         }
  81.         $this->guardMax($request$options);
  82.         $nextRequest $this->modifyRequest($request$options$response);
  83.         if (isset($options['allow_redirects']['on_redirect'])) {
  84.             call_user_func(
  85.                 $options['allow_redirects']['on_redirect'],
  86.                 $request,
  87.                 $response,
  88.                 $nextRequest->getUri()
  89.             );
  90.         }
  91.         /** @var PromiseInterface|ResponseInterface $promise */
  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.      * @return PromiseInterface
  107.      */
  108.     private function withTracking(PromiseInterface $promise$uri$statusCode)
  109.     {
  110.         return $promise->then(
  111.             function (ResponseInterface $response) use ($uri$statusCode) {
  112.                 // Note that we are pushing to the front of the list as this
  113.                 // would be an earlier response than what is currently present
  114.                 // in the history header.
  115.                 $historyHeader $response->getHeader(self::HISTORY_HEADER);
  116.                 $statusHeader $response->getHeader(self::STATUS_HISTORY_HEADER);
  117.                 array_unshift($historyHeader$uri);
  118.                 array_unshift($statusHeader$statusCode);
  119.                 return $response->withHeader(self::HISTORY_HEADER$historyHeader)
  120.                                 ->withHeader(self::STATUS_HISTORY_HEADER$statusHeader);
  121.             }
  122.         );
  123.     }
  124.     /**
  125.      * Check for too many redirects
  126.      *
  127.      * @return void
  128.      *
  129.      * @throws TooManyRedirectsException Too many redirects.
  130.      */
  131.     private function guardMax(RequestInterface $request, array &$options)
  132.     {
  133.         $current = isset($options['__redirect_count'])
  134.             ? $options['__redirect_count']
  135.             : 0;
  136.         $options['__redirect_count'] = $current 1;
  137.         $max $options['allow_redirects']['max'];
  138.         if ($options['__redirect_count'] > $max) {
  139.             throw new TooManyRedirectsException(
  140.                 "Will not follow more than {$max} redirects",
  141.                 $request
  142.             );
  143.         }
  144.     }
  145.     /**
  146.      * @param RequestInterface  $request
  147.      * @param array             $options
  148.      * @param ResponseInterface $response
  149.      *
  150.      * @return RequestInterface
  151.      */
  152.     public function modifyRequest(
  153.         RequestInterface $request,
  154.         array $options,
  155.         ResponseInterface $response
  156.     ) {
  157.         // Request modifications to apply.
  158.         $modify = [];
  159.         $protocols $options['allow_redirects']['protocols'];
  160.         // Use a GET request if this is an entity enclosing request and we are
  161.         // not forcing RFC compliance, but rather emulating what all browsers
  162.         // would do.
  163.         $statusCode $response->getStatusCode();
  164.         if ($statusCode == 303 ||
  165.             ($statusCode <= 302 && !$options['allow_redirects']['strict'])
  166.         ) {
  167.             $modify['method'] = 'GET';
  168.             $modify['body'] = '';
  169.         }
  170.         $uri $this->redirectUri($request$response$protocols);
  171.         if (isset($options['idn_conversion']) && ($options['idn_conversion'] !== false)) {
  172.             $idnOptions = ($options['idn_conversion'] === true) ? IDNA_DEFAULT $options['idn_conversion'];
  173.             $uri Utils::idnUriConvert($uri$idnOptions);
  174.         }
  175.         $modify['uri'] = $uri;
  176.         Psr7\rewind_body($request);
  177.         // Add the Referer header if it is told to do so and only
  178.         // add the header if we are not redirecting from https to http.
  179.         if ($options['allow_redirects']['referer']
  180.             && $modify['uri']->getScheme() === $request->getUri()->getScheme()
  181.         ) {
  182.             $uri $request->getUri()->withUserInfo('');
  183.             $modify['set_headers']['Referer'] = (string) $uri;
  184.         } else {
  185.             $modify['remove_headers'][] = 'Referer';
  186.         }
  187.         // Remove Authorization header if host is different.
  188.         if ($request->getUri()->getHost() !== $modify['uri']->getHost()) {
  189.             $modify['remove_headers'][] = 'Authorization';
  190.         }
  191.         return Psr7\modify_request($request$modify);
  192.     }
  193.     /**
  194.      * Set the appropriate URL on the request based on the location header
  195.      *
  196.      * @param RequestInterface  $request
  197.      * @param ResponseInterface $response
  198.      * @param array             $protocols
  199.      *
  200.      * @return UriInterface
  201.      */
  202.     private function redirectUri(
  203.         RequestInterface $request,
  204.         ResponseInterface $response,
  205.         array $protocols
  206.     ) {
  207.         $location Psr7\UriResolver::resolve(
  208.             $request->getUri(),
  209.             new Psr7\Uri($response->getHeaderLine('Location'))
  210.         );
  211.         // Ensure that the redirect URI is allowed based on the protocols.
  212.         if (!in_array($location->getScheme(), $protocols)) {
  213.             throw new BadResponseException(
  214.                 sprintf(
  215.                     'Redirect URI, %s, does not use one of the allowed redirect protocols: %s',
  216.                     $location,
  217.                     implode(', '$protocols)
  218.                 ),
  219.                 $request,
  220.                 $response
  221.             );
  222.         }
  223.         return $location;
  224.     }
  225. }