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

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