source: vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php

Last change on this file was e3d4e0a, checked in by Vlado 222039 <vlado.popovski@…>, 7 days ago

Upload project files

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