Open Journal Systems  3.3.0
RedirectMiddleware.php
1 <?php
2 namespace GuzzleHttp;
3 
11 
19 {
20  const HISTORY_HEADER = 'X-Guzzle-Redirect-History';
21 
22  const STATUS_HISTORY_HEADER = 'X-Guzzle-Redirect-Status-History';
23 
24  public static $defaultSettings = [
25  'max' => 5,
26  'protocols' => ['http', 'https'],
27  'strict' => false,
28  'referer' => false,
29  'track_redirects' => false,
30  ];
31 
33  private $nextHandler;
34 
38  public function __construct(callable $nextHandler)
39  {
40  $this->nextHandler = $nextHandler;
41  }
42 
49  public function __invoke(RequestInterface $request, array $options)
50  {
51  $fn = $this->nextHandler;
52 
53  if (empty($options['allow_redirects'])) {
54  return $fn($request, $options);
55  }
56 
57  if ($options['allow_redirects'] === true) {
58  $options['allow_redirects'] = self::$defaultSettings;
59  } elseif (!is_array($options['allow_redirects'])) {
60  throw new \InvalidArgumentException('allow_redirects must be true, false, or array');
61  } else {
62  // Merge the default settings with the provided settings
63  $options['allow_redirects'] += self::$defaultSettings;
64  }
65 
66  if (empty($options['allow_redirects']['max'])) {
67  return $fn($request, $options);
68  }
69 
70  return $fn($request, $options)
71  ->then(function (ResponseInterface $response) use ($request, $options) {
72  return $this->checkRedirect($request, $options, $response);
73  });
74  }
75 
83  public function checkRedirect(
84  RequestInterface $request,
85  array $options,
87  ) {
88  if (substr($response->getStatusCode(), 0, 1) != '3'
89  || !$response->hasHeader('Location')
90  ) {
91  return $response;
92  }
93 
94  $this->guardMax($request, $options);
95  $nextRequest = $this->modifyRequest($request, $options, $response);
96 
97  if (isset($options['allow_redirects']['on_redirect'])) {
98  call_user_func(
99  $options['allow_redirects']['on_redirect'],
100  $request,
101  $response,
102  $nextRequest->getUri()
103  );
104  }
105 
107  $promise = $this($nextRequest, $options);
108 
109  // Add headers to be able to track history of redirects.
110  if (!empty($options['allow_redirects']['track_redirects'])) {
111  return $this->withTracking(
112  $promise,
113  (string) $nextRequest->getUri(),
114  $response->getStatusCode()
115  );
116  }
117 
118  return $promise;
119  }
120 
126  private function withTracking(PromiseInterface $promise, $uri, $statusCode)
127  {
128  return $promise->then(
129  function (ResponseInterface $response) use ($uri, $statusCode) {
130  // Note that we are pushing to the front of the list as this
131  // would be an earlier response than what is currently present
132  // in the history header.
133  $historyHeader = $response->getHeader(self::HISTORY_HEADER);
134  $statusHeader = $response->getHeader(self::STATUS_HISTORY_HEADER);
135  array_unshift($historyHeader, $uri);
136  array_unshift($statusHeader, $statusCode);
137  return $response->withHeader(self::HISTORY_HEADER, $historyHeader)
138  ->withHeader(self::STATUS_HISTORY_HEADER, $statusHeader);
139  }
140  );
141  }
142 
150  private function guardMax(RequestInterface $request, array &$options)
151  {
152  $current = isset($options['__redirect_count'])
153  ? $options['__redirect_count']
154  : 0;
155  $options['__redirect_count'] = $current + 1;
156  $max = $options['allow_redirects']['max'];
157 
158  if ($options['__redirect_count'] > $max) {
159  throw new TooManyRedirectsException(
160  "Will not follow more than {$max} redirects",
161  $request
162  );
163  }
164  }
165 
173  public function modifyRequest(
174  RequestInterface $request,
175  array $options,
177  ) {
178  // Request modifications to apply.
179  $modify = [];
180  $protocols = $options['allow_redirects']['protocols'];
181 
182  // Use a GET request if this is an entity enclosing request and we are
183  // not forcing RFC compliance, but rather emulating what all browsers
184  // would do.
185  $statusCode = $response->getStatusCode();
186  if ($statusCode == 303 ||
187  ($statusCode <= 302 && !$options['allow_redirects']['strict'])
188  ) {
189  $modify['method'] = 'GET';
190  $modify['body'] = '';
191  }
192 
193  $uri = $this->redirectUri($request, $response, $protocols);
194  if (isset($options['idn_conversion']) && ($options['idn_conversion'] !== false)) {
195  $idnOptions = ($options['idn_conversion'] === true) ? IDNA_DEFAULT : $options['idn_conversion'];
196  $uri = Utils::idnUriConvert($uri, $idnOptions);
197  }
198 
199  $modify['uri'] = $uri;
200  Psr7\rewind_body($request);
201 
202  // Add the Referer header if it is told to do so and only
203  // add the header if we are not redirecting from https to http.
204  if ($options['allow_redirects']['referer']
205  && $modify['uri']->getScheme() === $request->getUri()->getScheme()
206  ) {
207  $uri = $request->getUri()->withUserInfo('');
208  $modify['set_headers']['Referer'] = (string) $uri;
209  } else {
210  $modify['remove_headers'][] = 'Referer';
211  }
212 
213  // Remove Authorization header if host is different.
214  if ($request->getUri()->getHost() !== $modify['uri']->getHost()) {
215  $modify['remove_headers'][] = 'Authorization';
216  }
217 
218  return Psr7\modify_request($request, $modify);
219  }
220 
230  private function redirectUri(
231  RequestInterface $request,
232  ResponseInterface $response,
233  array $protocols
234  ) {
235  $location = Psr7\UriResolver::resolve(
236  $request->getUri(),
237  new Psr7\Uri($response->getHeaderLine('Location'))
238  );
239 
240  // Ensure that the redirect URI is allowed based on the protocols.
241  if (!in_array($location->getScheme(), $protocols)) {
242  throw new BadResponseException(
243  sprintf(
244  'Redirect URI, %s, does not use one of the allowed redirect protocols: %s',
245  $location,
246  implode(', ', $protocols)
247  ),
248  $request,
249  $response
250  );
251  }
252 
253  return $location;
254  }
255 }
GuzzleHttp
Definition: vendor/guzzlehttp/guzzle/src/Client.php:2
GuzzleHttp\Promise\PromiseInterface\then
then(callable $onFulfilled=null, callable $onRejected=null)
GuzzleHttp\Promise\PromiseInterface
Definition: PromiseInterface.php:13
Psr\Http\Message\RequestInterface
Definition: vendor/psr/http-message/src/RequestInterface.php:24
GuzzleHttp\Exception\TooManyRedirectsException
Definition: vendor/guzzlehttp/guzzle/src/Exception/TooManyRedirectsException.php:4
GuzzleHttp\Exception\BadResponseException
Definition: vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php:10
GuzzleHttp\RedirectMiddleware\__invoke
__invoke(RequestInterface $request, array $options)
Definition: RedirectMiddleware.php:52
GuzzleHttp\Psr7\Uri
Definition: Uri.php:13
GuzzleHttp\Psr7
Definition: AppendStream.php:2
Psr\Http\Message\UriInterface
Definition: UriInterface.php:24
Psr\Http\Message\ResponseInterface
Definition: vendor/psr/http-message/src/ResponseInterface.php:20
GuzzleHttp\RedirectMiddleware\__construct
__construct(callable $nextHandler)
Definition: RedirectMiddleware.php:41
GuzzleHttp\RedirectMiddleware\modifyRequest
modifyRequest(RequestInterface $request, array $options, ResponseInterface $response)
Definition: RedirectMiddleware.php:176
GuzzleHttp\RedirectMiddleware
Definition: RedirectMiddleware.php:18
Psr\Http\Message\RequestInterface\getUri
getUri()
GuzzleHttp\RedirectMiddleware\checkRedirect
checkRedirect(RequestInterface $request, array $options, ResponseInterface $response)
Definition: RedirectMiddleware.php:86