Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
}