Subversion Repositories php-qbpwcf

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
3 liveuser 1
<?php
2
 
3
namespace Guzzle\Http;
4
 
5
use Guzzle\Common\Event;
6
use Guzzle\Http\Exception\BadResponseException;
7
use Guzzle\Http\Url;
8
use Guzzle\Http\Message\Response;
9
use Guzzle\Http\Message\RequestInterface;
10
use Guzzle\Http\Message\RequestFactory;
11
use Guzzle\Http\Message\EntityEnclosingRequestInterface;
12
use Guzzle\Http\Exception\TooManyRedirectsException;
13
use Guzzle\Http\Exception\CouldNotRewindStreamException;
14
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
15
 
16
/**
17
 * Plugin to implement HTTP redirects. Can redirect like a web browser or using strict RFC 2616 compliance
18
 */
19
class RedirectPlugin implements EventSubscriberInterface
20
{
21
    const REDIRECT_COUNT = 'redirect.count';
22
    const MAX_REDIRECTS = 'redirect.max';
23
    const STRICT_REDIRECTS = 'redirect.strict';
24
    const PARENT_REQUEST = 'redirect.parent_request';
25
    const DISABLE = 'redirect.disable';
26
 
27
    /**
28
     * @var int Default number of redirects allowed when no setting is supplied by a request
29
     */
30
    protected $defaultMaxRedirects = 5;
31
 
32
    public static function getSubscribedEvents()
33
    {
34
        return array(
35
            'request.sent'        => array('onRequestSent', 100),
36
            'request.clone'       => 'cleanupRequest',
37
            'request.before_send' => 'cleanupRequest'
38
        );
39
    }
40
 
41
    /**
42
     * Clean up the parameters of a request when it is cloned
43
     *
44
     * @param Event $event Event emitted
45
     */
46
    public function cleanupRequest(Event $event)
47
    {
48
        $params = $event['request']->getParams();
49
        unset($params[self::REDIRECT_COUNT]);
50
        unset($params[self::PARENT_REQUEST]);
51
    }
52
 
53
    /**
54
     * Called when a request receives a redirect response
55
     *
56
     * @param Event $event Event emitted
57
     */
58
    public function onRequestSent(Event $event)
59
    {
60
        $response = $event['response'];
61
        $request = $event['request'];
62
 
63
        // Only act on redirect requests with Location headers
64
        if (!$response || $request->getParams()->get(self::DISABLE)) {
65
            return;
66
        }
67
 
68
        // Trace the original request based on parameter history
69
        $original = $this->getOriginalRequest($request);
70
 
71
        // Terminating condition to set the effective response on the original request
72
        if (!$response->isRedirect() || !$response->hasHeader('Location')) {
73
            if ($request !== $original) {
74
                // This is a terminating redirect response, so set it on the original request
75
                $response->getParams()->set(self::REDIRECT_COUNT, $original->getParams()->get(self::REDIRECT_COUNT));
76
                $original->setResponse($response);
77
                $response->setEffectiveUrl($request->getUrl());
78
            }
79
            return;
80
        }
81
 
82
        $this->sendRedirectRequest($original, $request, $response);
83
    }
84
 
85
    /**
86
     * Get the original request that initiated a series of redirects
87
     *
88
     * @param RequestInterface $request Request to get the original request from
89
     *
90
     * @return RequestInterface
91
     */
92
    protected function getOriginalRequest(RequestInterface $request)
93
    {
94
        $original = $request;
95
        // The number of redirects is held on the original request, so determine which request that is
96
        while ($parent = $original->getParams()->get(self::PARENT_REQUEST)) {
97
            $original = $parent;
98
        }
99
 
100
        return $original;
101
    }
102
 
103
    /**
104
     * Create a redirect request for a specific request object
105
     *
106
     * Takes into account strict RFC compliant redirection (e.g. redirect POST with POST) vs doing what most clients do
107
     * (e.g. redirect POST with GET).
108
     *
109
     * @param RequestInterface $request    Request being redirected
110
     * @param RequestInterface $original   Original request
111
     * @param int              $statusCode Status code of the redirect
112
     * @param string           $location   Location header of the redirect
113
     *
114
     * @return RequestInterface Returns a new redirect request
115
     * @throws CouldNotRewindStreamException If the body needs to be rewound but cannot
116
     */
117
    protected function createRedirectRequest(
118
        RequestInterface $request,
119
        $statusCode,
120
        $location,
121
        RequestInterface $original
122
    ) {
123
        $redirectRequest = null;
124
        $strict = $original->getParams()->get(self::STRICT_REDIRECTS);
125
 
126
        // Switch method to GET for 303 redirects.  301 and 302 redirects also switch to GET unless we are forcing RFC
127
        // compliance to emulate what most browsers do.  NOTE: IE only switches methods on 301/302 when coming from a POST.
128
        if ($request instanceof EntityEnclosingRequestInterface && ($statusCode == 303 || (!$strict && $statusCode <= 302))) {
129
            $redirectRequest = RequestFactory::getInstance()->cloneRequestWithMethod($request, 'GET');
130
        } else {
131
            $redirectRequest = clone $request;
132
        }
133
 
134
        $redirectRequest->setIsRedirect(true);
135
        // Always use the same response body when redirecting
136
        $redirectRequest->setResponseBody($request->getResponseBody());
137
 
138
        $location = Url::factory($location);
139
        // If the location is not absolute, then combine it with the original URL
140
        if (!$location->isAbsolute()) {
141
            $originalUrl = $redirectRequest->getUrl(true);
142
            // Remove query string parameters and just take what is present on the redirect Location header
143
            $originalUrl->getQuery()->clear();
144
            $location = $originalUrl->combine((string) $location, true);
145
        }
146
 
147
        $redirectRequest->setUrl($location);
148
 
149
        // Add the parent request to the request before it sends (make sure it's before the onRequestClone event too)
150
        $redirectRequest->getEventDispatcher()->addListener(
151
            'request.before_send',
152
            $func = function ($e) use (&$func, $request, $redirectRequest) {
153
                $redirectRequest->getEventDispatcher()->removeListener('request.before_send', $func);
154
                $e['request']->getParams()->set(RedirectPlugin::PARENT_REQUEST, $request);
155
            }
156
        );
157
 
158
        // Rewind the entity body of the request if needed
159
        if ($redirectRequest instanceof EntityEnclosingRequestInterface && $redirectRequest->getBody()) {
160
            $body = $redirectRequest->getBody();
161
            // Only rewind the body if some of it has been read already, and throw an exception if the rewind fails
162
            if ($body->ftell() && !$body->rewind()) {
163
                throw new CouldNotRewindStreamException(
164
                    'Unable to rewind the non-seekable entity body of the request after redirecting. cURL probably '
165
                    . 'sent part of body before the redirect occurred. Try adding acustom rewind function using on the '
166
                    . 'entity body of the request using setRewindFunction().'
167
                );
168
            }
169
        }
170
 
171
        return $redirectRequest;
172
    }
173
 
174
    /**
175
     * Prepare the request for redirection and enforce the maximum number of allowed redirects per client
176
     *
177
     * @param RequestInterface $original  Original request
178
     * @param RequestInterface $request   Request to prepare and validate
179
     * @param Response         $response  The current response
180
     *
181
     * @return RequestInterface
182
     */
183
    protected function prepareRedirection(RequestInterface $original, RequestInterface $request, Response $response)
184
    {
185
        $params = $original->getParams();
186
        // This is a new redirect, so increment the redirect counter
187
        $current = $params[self::REDIRECT_COUNT] + 1;
188
        $params[self::REDIRECT_COUNT] = $current;
189
        // Use a provided maximum value or default to a max redirect count of 5
190
        $max = isset($params[self::MAX_REDIRECTS]) ? $params[self::MAX_REDIRECTS] : $this->defaultMaxRedirects;
191
 
192
        // Throw an exception if the redirect count is exceeded
193
        if ($current > $max) {
194
            $this->throwTooManyRedirectsException($original, $max);
195
            return false;
196
        } else {
197
            // Create a redirect request based on the redirect rules set on the request
198
            return $this->createRedirectRequest(
199
                $request,
200
                $response->getStatusCode(),
201
                trim($response->getLocation()),
202
                $original
203
            );
204
        }
205
    }
206
 
207
    /**
208
     * Send a redirect request and handle any errors
209
     *
210
     * @param RequestInterface $original The originating request
211
     * @param RequestInterface $request  The current request being redirected
212
     * @param Response         $response The response of the current request
213
     *
214
     * @throws BadResponseException|\Exception
215
     */
216
    protected function sendRedirectRequest(RequestInterface $original, RequestInterface $request, Response $response)
217
    {
218
        // Validate and create a redirect request based on the original request and current response
219
        if ($redirectRequest = $this->prepareRedirection($original, $request, $response)) {
220
            try {
221
                $redirectRequest->send();
222
            } catch (BadResponseException $e) {
223
                $e->getResponse();
224
                if (!$e->getResponse()) {
225
                    throw $e;
226
                }
227
            }
228
        }
229
    }
230
 
231
    /**
232
     * Throw a too many redirects exception for a request
233
     *
234
     * @param RequestInterface $original Request
235
     * @param int              $max      Max allowed redirects
236
     *
237
     * @throws TooManyRedirectsException when too many redirects have been issued
238
     */
239
    protected function throwTooManyRedirectsException(RequestInterface $original, $max)
240
    {
241
        $original->getEventDispatcher()->addListener(
242
            'request.complete',
243
            $func = function ($e) use (&$func, $original, $max) {
244
                $original->getEventDispatcher()->removeListener('request.complete', $func);
245
                $str = "{$max} redirects were issued for this request:\n" . $e['request']->getRawHeaders();
246
                throw new TooManyRedirectsException($str);
247
            }
248
        );
249
    }
250
}