mantis-matrix-integration – Blame information for rev 1
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | office | 1 | <?php |
2 | |||
3 | namespace GuzzleHttp; |
||
4 | |||
5 | use GuzzleHttp\Exception\BadResponseException; |
||
6 | use GuzzleHttp\Exception\TooManyRedirectsException; |
||
7 | use GuzzleHttp\Promise\PromiseInterface; |
||
8 | use Psr\Http\Message\RequestInterface; |
||
9 | use Psr\Http\Message\ResponseInterface; |
||
10 | use 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 | */ |
||
20 | class 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 | } |