PluginProbe
Depicter — Popup & Slider Builder / 2.0.0
Depicter — Popup & Slider Builder v2.0.0
4.8.1 trunk 1.0.0 1.1.0 1.1.2 1.1.4 1.1.6 1.1.7 1.1.8 1.1.9 1.2.0 1.3.0 1.3.1 1.3.2 1.3.3 1.3.5 1.3.8 1.5.0 1.5.1 1.5.2 1.5.5 1.6.0 1.6.1 1.6.2 1.7.0 All 76 releases
depicter / modules / GuzzleHttp / RedirectMiddleware.php

RedirectMiddleware.php in Depicter — Popup & Slider Builder 2.0.0, at modules/GuzzleHttp/RedirectMiddleware.php

217 lines 7.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Depicter\GuzzleHttp;
4
5 use Depicter\GuzzleHttp\Exception\BadResponseException;
6 use Depicter\GuzzleHttp\Exception\TooManyRedirectsException;
7 use Depicter\GuzzleHttp\Promise\PromiseInterface;
8 use Depicter\Psr\Http\Message\RequestInterface;
9 use Depicter\Psr\Http\Message\ResponseInterface;
10 use Depicter\Psr\Http\Message\UriInterface;
11
12 /**
13 * Request redirect middleware.
14 *
15 * Apply this middleware like other middleware using
16 * {@see \Depicter\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 (isset($options['allow_redirects']['on_redirect'])) {
92 ($options['allow_redirects']['on_redirect'])(
93 $request,
94 $response,
95 $nextRequest->getUri()
96 );
97 }
98
99 $promise = $this($nextRequest, $options);
100
101 // Add headers to be able to track history of redirects.
102 if (!empty($options['allow_redirects']['track_redirects'])) {
103 return $this->withTracking(
104 $promise,
105 (string) $nextRequest->getUri(),
106 $response->getStatusCode()
107 );
108 }
109
110 return $promise;
111 }
112
113 /**
114 * Enable tracking on promise.
115 */
116 private function withTracking(PromiseInterface $promise, string $uri, int $statusCode): PromiseInterface
117 {
118 return $promise->then(
119 static function (ResponseInterface $response) use ($uri, $statusCode) {
120 // Note that we are pushing to the front of the list as this
121 // would be an earlier response than what is currently present
122 // in the history header.
123 $historyHeader = $response->getHeader(self::HISTORY_HEADER);
124 $statusHeader = $response->getHeader(self::STATUS_HISTORY_HEADER);
125 \array_unshift($historyHeader, $uri);
126 \array_unshift($statusHeader, (string) $statusCode);
127
128 return $response->withHeader(self::HISTORY_HEADER, $historyHeader)
129 ->withHeader(self::STATUS_HISTORY_HEADER, $statusHeader);
130 }
131 );
132 }
133
134 /**
135 * Check for too many redirects
136 *
137 * @throws TooManyRedirectsException Too many redirects.
138 */
139 private function guardMax(RequestInterface $request, ResponseInterface $response, array &$options): void
140 {
141 $current = $options['__redirect_count']
142 ?? 0;
143 $options['__redirect_count'] = $current + 1;
144 $max = $options['allow_redirects']['max'];
145
146 if ($options['__redirect_count'] > $max) {
147 throw new TooManyRedirectsException("Will not follow more than {$max} redirects", $request, $response);
148 }
149 }
150
151 public function modifyRequest(RequestInterface $request, array $options, ResponseInterface $response): RequestInterface
152 {
153 // Request modifications to apply.
154 $modify = [];
155 $protocols = $options['allow_redirects']['protocols'];
156
157 // Use a GET request if this is an entity enclosing request and we are
158 // not forcing RFC compliance, but rather emulating what all browsers
159 // would do.
160 $statusCode = $response->getStatusCode();
161 if ($statusCode == 303 ||
162 ($statusCode <= 302 && !$options['allow_redirects']['strict'])
163 ) {
164 $safeMethods = ['GET', 'HEAD', 'OPTIONS'];
165 $requestMethod = $request->getMethod();
166
167 $modify['method'] = in_array($requestMethod, $safeMethods) ? $requestMethod : 'GET';
168 $modify['body'] = '';
169 }
170
171 $uri = $this->redirectUri($request, $response, $protocols);
172 if (isset($options['idn_conversion']) && ($options['idn_conversion'] !== false)) {
173 $idnOptions = ($options['idn_conversion'] === true) ? \IDNA_DEFAULT : $options['idn_conversion'];
174 $uri = Utils::idnUriConvert($uri, $idnOptions);
175 }
176
177 $modify['uri'] = $uri;
178 Psr7\Message::rewindBody($request);
179
180 // Add the Referer header if it is told to do so and only
181 // add the header if we are not redirecting from https to http.
182 if ($options['allow_redirects']['referer']
183 && $modify['uri']->getScheme() === $request->getUri()->getScheme()
184 ) {
185 $uri = $request->getUri()->withUserInfo('');
186 $modify['set_headers']['Referer'] = (string) $uri;
187 } else {
188 $modify['remove_headers'][] = 'Referer';
189 }
190
191 // Remove Authorization header if host is different.
192 if ($request->getUri()->getHost() !== $modify['uri']->getHost()) {
193 $modify['remove_headers'][] = 'Authorization';
194 }
195
196 return Psr7\Utils::modifyRequest($request, $modify);
197 }
198
199 /**
200 * Set the appropriate URL on the request based on the location header
201 */
202 private function redirectUri(RequestInterface $request, ResponseInterface $response, array $protocols): UriInterface
203 {
204 $location = Psr7\UriResolver::resolve(
205 $request->getUri(),
206 new Psr7\Uri($response->getHeaderLine('Location'))
207 );
208
209 // Ensure that the redirect URI is allowed based on the protocols.
210 if (!\in_array($location->getScheme(), $protocols)) {
211 throw new BadResponseException(\sprintf('Redirect URI, %s, does not use one of the allowed redirect protocols: %s', $location, \implode(', ', $protocols)), $request, $response);
212 }
213
214 return $location;
215 }
216 }
217