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: 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: 29:
30: protected $defaultMaxRedirects = 5;
31:
32: 33: 34:
35: public static function getSubscribedEvents()
36: {
37: return array(
38: 'request.sent' => array('onRequestSent', 100),
39: 'request.clone' => 'onRequestClone'
40: );
41: }
42:
43: 44: 45: 46: 47:
48: public function onRequestClone(Event $event)
49: {
50: $event['request']->getParams()->remove(self::REDIRECT_COUNT)->remove(self::PARENT_REQUEST);
51: }
52:
53: 54: 55: 56: 57:
58: public function onRequestSent(Event $event)
59: {
60: $response = $event['response'];
61: $request = $event['request'];
62:
63:
64: if (!$response || !$response->isRedirect() || !$response->hasHeader('Location')
65: || $request->getParams()->get(self::DISABLE)
66: ) {
67: return;
68: }
69:
70:
71: $originalRequest = $this->prepareRedirection($request);
72:
73:
74: $redirectRequest = $this->createRedirectRequest(
75: $request,
76: $event['response']->getStatusCode(),
77: trim($response->getHeader('Location')),
78: $originalRequest
79: );
80:
81: try {
82:
83: $redirectResponse = $redirectRequest->send();
84: } catch (BadResponseException $e) {
85:
86: $redirectResponse = $e->getResponse();
87: if (!$e->getResponse()) {
88: throw $e;
89: }
90: }
91:
92: $request->setResponse($redirectResponse);
93: if (!$redirectResponse->getPreviousResponse()) {
94: $redirectResponse->setPreviousResponse($response);
95: }
96: }
97:
98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111:
112: protected function createRedirectRequest(
113: RequestInterface $request,
114: $statusCode,
115: $location,
116: RequestInterface $original
117: ) {
118: $redirectRequest = null;
119: $strict = $original->getParams()->get(self::STRICT_REDIRECTS);
120:
121:
122: if ($request instanceof EntityEnclosingRequestInterface && !$strict && $statusCode <= 302) {
123: $redirectRequest = $this->cloneRequestWithGetMethod($request);
124: } else {
125: $redirectRequest = clone $request;
126: }
127:
128: $redirectRequest->setIsRedirect(true);
129:
130: $redirectRequest->setResponseBody($request->getResponseBody());
131:
132: $location = Url::factory($location);
133:
134: if (!$location->isAbsolute()) {
135: $originalUrl = $redirectRequest->getUrl(true);
136:
137: $originalUrl->getQuery()->clear();
138: $location = $originalUrl->combine((string) $location);
139: }
140:
141: $redirectRequest->setUrl($location);
142: $redirectRequest->getParams()->set(self::PARENT_REQUEST, $request);
143:
144:
145: if ($redirectRequest instanceof EntityEnclosingRequestInterface && $redirectRequest->getBody()) {
146: $body = $redirectRequest->getBody();
147:
148: if ($body->ftell() && !$body->rewind()) {
149: throw new CouldNotRewindStreamException(
150: 'Unable to rewind the non-seekable entity body of the request after redirecting. cURL probably '
151: . 'sent part of body before the redirect occurred. Try adding acustom rewind function using on the '
152: . 'entity body of the request using setRewindFunction().'
153: );
154: }
155: }
156:
157: return $redirectRequest;
158: }
159:
160: 161: 162: 163: 164: 165: 166: 167:
168: protected function cloneRequestWithGetMethod(EntityEnclosingRequestInterface $request)
169: {
170: return RequestFactory::getInstance()->cloneRequestWithMethod($request, 'GET');
171: }
172:
173: 174: 175: 176: 177: 178: 179:
180: protected function prepareRedirection(RequestInterface $request)
181: {
182: $original = $request;
183:
184: while ($parent = $original->getParams()->get(self::PARENT_REQUEST)) {
185: $original = $parent;
186: }
187:
188:
189: if ($parent = $request->getParams()->get(self::PARENT_REQUEST)) {
190: $request->getResponse()->setPreviousResponse($parent->getResponse());
191: }
192:
193: $params = $original->getParams();
194:
195: $current = $params->get(self::REDIRECT_COUNT) + 1;
196: $params->set(self::REDIRECT_COUNT, $current);
197:
198:
199: $max = $params->hasKey(self::MAX_REDIRECTS)
200: ? $params->get(self::MAX_REDIRECTS)
201: : $this->defaultMaxRedirects;
202:
203:
204: if ($current > $max) {
205: return $this->throwTooManyRedirectsException($request);
206: }
207:
208: return $original;
209: }
210:
211: 212: 213: 214: 215: 216:
217: protected function throwTooManyRedirectsException(RequestInterface $request)
218: {
219: $lines = array();
220: $response = $request->getResponse();
221:
222: do {
223: $lines[] = '> ' . $response->getRequest()->getRawHeaders() . "\n\n< " . $response->getRawHeaders();
224: $response = $response->getPreviousResponse();
225: } while ($response);
226:
227: throw new TooManyRedirectsException(
228: "Too many redirects were issued for this transaction:\n"
229: . implode("* Sending redirect request\n", array_reverse($lines))
230: );
231: }
232: }
233: