5613f6ec0e4a44f5ef081235fdaf6decba1ec017
[yaffs-website] / vendor / guzzlehttp / guzzle / src / RedirectMiddleware.php
1 <?php
2 namespace GuzzleHttp;
3
4 use GuzzleHttp\Exception\BadResponseException;
5 use GuzzleHttp\Exception\TooManyRedirectsException;
6 use GuzzleHttp\Promise\PromiseInterface;
7 use GuzzleHttp\Psr7;
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 class RedirectMiddleware
19 {
20     const HISTORY_HEADER = 'X-Guzzle-Redirect-History';
21
22     public static $defaultSettings = [
23         'max'             => 5,
24         'protocols'       => ['http', 'https'],
25         'strict'          => false,
26         'referer'         => false,
27         'track_redirects' => false,
28     ];
29
30     /** @var callable  */
31     private $nextHandler;
32
33     /**
34      * @param callable $nextHandler Next handler to invoke.
35      */
36     public function __construct(callable $nextHandler)
37     {
38         $this->nextHandler = $nextHandler;
39     }
40
41     /**
42      * @param RequestInterface $request
43      * @param array            $options
44      *
45      * @return PromiseInterface
46      */
47     public function __invoke(RequestInterface $request, array $options)
48     {
49         $fn = $this->nextHandler;
50
51         if (empty($options['allow_redirects'])) {
52             return $fn($request, $options);
53         }
54
55         if ($options['allow_redirects'] === true) {
56             $options['allow_redirects'] = self::$defaultSettings;
57         } elseif (!is_array($options['allow_redirects'])) {
58             throw new \InvalidArgumentException('allow_redirects must be true, false, or array');
59         } else {
60             // Merge the default settings with the provided settings
61             $options['allow_redirects'] += self::$defaultSettings;
62         }
63
64         if (empty($options['allow_redirects']['max'])) {
65             return $fn($request, $options);
66         }
67
68         return $fn($request, $options)
69             ->then(function (ResponseInterface $response) use ($request, $options) {
70                 return $this->checkRedirect($request, $options, $response);
71             });
72     }
73
74     /**
75      * @param RequestInterface  $request
76      * @param array             $options
77      * @param ResponseInterface|PromiseInterface $response
78      *
79      * @return ResponseInterface|PromiseInterface
80      */
81     public function checkRedirect(
82         RequestInterface $request,
83         array $options,
84         ResponseInterface $response
85     ) {
86         if (substr($response->getStatusCode(), 0, 1) != '3'
87             || !$response->hasHeader('Location')
88         ) {
89             return $response;
90         }
91
92         $this->guardMax($request, $options);
93         $nextRequest = $this->modifyRequest($request, $options, $response);
94
95         if (isset($options['allow_redirects']['on_redirect'])) {
96             call_user_func(
97                 $options['allow_redirects']['on_redirect'],
98                 $request,
99                 $response,
100                 $nextRequest->getUri()
101             );
102         }
103
104         /** @var PromiseInterface|ResponseInterface $promise */
105         $promise = $this($nextRequest, $options);
106
107         // Add headers to be able to track history of redirects.
108         if (!empty($options['allow_redirects']['track_redirects'])) {
109             return $this->withTracking(
110                 $promise,
111                 (string) $nextRequest->getUri()
112             );
113         }
114
115         return $promise;
116     }
117
118     private function withTracking(PromiseInterface $promise, $uri)
119     {
120         return $promise->then(
121             function (ResponseInterface $response) use ($uri) {
122                 // Note that we are pushing to the front of the list as this
123                 // would be an earlier response than what is currently present
124                 // in the history header.
125                 $header = $response->getHeader(self::HISTORY_HEADER);
126                 array_unshift($header, $uri);
127                 return $response->withHeader(self::HISTORY_HEADER, $header);
128             }
129         );
130     }
131
132     private function guardMax(RequestInterface $request, array &$options)
133     {
134         $current = isset($options['__redirect_count'])
135             ? $options['__redirect_count']
136             : 0;
137         $options['__redirect_count'] = $current + 1;
138         $max = $options['allow_redirects']['max'];
139
140         if ($options['__redirect_count'] > $max) {
141             throw new TooManyRedirectsException(
142                 "Will not follow more than {$max} redirects",
143                 $request
144             );
145         }
146     }
147
148     /**
149      * @param RequestInterface  $request
150      * @param array             $options
151      * @param ResponseInterface $response
152      *
153      * @return RequestInterface
154      */
155     public function modifyRequest(
156         RequestInterface $request,
157         array $options,
158         ResponseInterface $response
159     ) {
160         // Request modifications to apply.
161         $modify = [];
162         $protocols = $options['allow_redirects']['protocols'];
163
164         // Use a GET request if this is an entity enclosing request and we are
165         // not forcing RFC compliance, but rather emulating what all browsers
166         // would do.
167         $statusCode = $response->getStatusCode();
168         if ($statusCode == 303 ||
169             ($statusCode <= 302 && $request->getBody() && !$options['allow_redirects']['strict'])
170         ) {
171             $modify['method'] = 'GET';
172             $modify['body'] = '';
173         }
174
175         $modify['uri'] = $this->redirectUri($request, $response, $protocols);
176         Psr7\rewind_body($request);
177
178         // Add the Referer header if it is told to do so and only
179         // add the header if we are not redirecting from https to http.
180         if ($options['allow_redirects']['referer']
181             && $modify['uri']->getScheme() === $request->getUri()->getScheme()
182         ) {
183             $uri = $request->getUri()->withUserInfo('', '');
184             $modify['set_headers']['Referer'] = (string) $uri;
185         } else {
186             $modify['remove_headers'][] = 'Referer';
187         }
188
189         // Remove Authorization header if host is different.
190         if ($request->getUri()->getHost() !== $modify['uri']->getHost()) {
191             $modify['remove_headers'][] = 'Authorization';
192         }
193
194         return Psr7\modify_request($request, $modify);
195     }
196
197     /**
198      * Set the appropriate URL on the request based on the location header
199      *
200      * @param RequestInterface  $request
201      * @param ResponseInterface $response
202      * @param array             $protocols
203      *
204      * @return UriInterface
205      */
206     private function redirectUri(
207         RequestInterface $request,
208         ResponseInterface $response,
209         array $protocols
210     ) {
211         $location = Psr7\UriResolver::resolve(
212             $request->getUri(),
213             new Psr7\Uri($response->getHeaderLine('Location'))
214         );
215
216         // Ensure that the redirect URI is allowed based on the protocols.
217         if (!in_array($location->getScheme(), $protocols)) {
218             throw new BadResponseException(
219                 sprintf(
220                     'Redirect URI, %s, does not use one of the allowed redirect protocols: %s',
221                     $location,
222                     implode(', ', $protocols)
223                 ),
224                 $request,
225                 $response
226             );
227         }
228
229         return $location;
230     }
231 }