scratch – Blame information for rev 87

Subversion Repositories:
Rev:
Rev Author Line No. Line
87 office 1 <?php
2  
3 namespace GuzzleHttp\Subscriber;
4  
5 use GuzzleHttp\Event\CompleteEvent;
6 use GuzzleHttp\Event\RequestEvents;
7 use GuzzleHttp\Event\SubscriberInterface;
8 use GuzzleHttp\Exception\CouldNotRewindStreamException;
9 use GuzzleHttp\Exception\TooManyRedirectsException;
10 use GuzzleHttp\Message\RequestInterface;
11 use GuzzleHttp\Message\ResponseInterface;
12 use GuzzleHttp\Url;
13  
14 /**
15 * Subscriber used to implement HTTP redirects.
16 *
17 * **Request options**
18 *
19 * - redirect: Associative array containing the 'max', 'strict', and 'referer'
20 * keys.
21 *
22 * - max: Maximum number of redirects allowed per-request
23 * - strict: You can use strict redirects by setting this value to ``true``.
24 * Strict redirects adhere to strict RFC compliant redirection (e.g.,
25 * redirect POST with POST) vs doing what most clients do (e.g., redirect
26 * POST request with a GET request).
27 * - referer: Set to true to automatically add the "Referer" header when a
28 * redirect request is sent.
29 */
30 class Redirect implements SubscriberInterface
31 {
32 public function getEvents()
33 {
34 return ['complete' => ['onComplete', RequestEvents::REDIRECT_RESPONSE]];
35 }
36  
37 /**
38 * Rewind the entity body of the request if needed
39 *
40 * @param RequestInterface $redirectRequest
41 * @throws CouldNotRewindStreamException
42 */
43 public static function rewindEntityBody(RequestInterface $redirectRequest)
44 {
45 // Rewind the entity body of the request if needed
46 if ($redirectRequest->getBody()) {
47 $body = $redirectRequest->getBody();
48 // Only rewind the body if some of it has been read already, and
49 // throw an exception if the rewind fails
50 if ($body->tell() && !$body->seek(0)) {
51 throw new CouldNotRewindStreamException(
52 'Unable to rewind the non-seekable request body after redirecting',
53 $redirectRequest
54 );
55 }
56 }
57 }
58  
59 /**
60 * Called when a request receives a redirect response
61 *
62 * @param CompleteEvent $event Event emitted
63 * @throws TooManyRedirectsException
64 */
65 public function onComplete(CompleteEvent $event)
66 {
67 $response = $event->getResponse();
68  
69 if (substr($response->getStatusCode(), 0, 1) != '3' ||
70 !$response->hasHeader('Location')
71 ) {
72 return;
73 }
74  
75 $redirectCount = 0;
76 $redirectRequest = $event->getRequest();
77 $redirectResponse = $response;
78 $max = $redirectRequest->getConfig()->getPath('redirect/max') ?: 5;
79  
80 do {
81 if (++$redirectCount > $max) {
82 throw new TooManyRedirectsException(
83 "Will not follow more than {$redirectCount} redirects",
84 $redirectRequest
85 );
86 }
87 $redirectRequest = $this->createRedirectRequest($redirectRequest, $redirectResponse);
88 $redirectResponse = $event->getClient()->send($redirectRequest);
89 } while (substr($redirectResponse->getStatusCode(), 0, 1) == '3' &&
90 $redirectResponse->hasHeader('Location')
91 );
92  
93 if ($redirectResponse !== $response) {
94 $event->intercept($redirectResponse);
95 }
96 }
97  
98 /**
99 * Create a redirect request for a specific request object
100 *
101 * Takes into account strict RFC compliant redirection (e.g. redirect POST
102 * with POST) vs doing what most clients do (e.g. redirect POST with GET).
103 *
104 * @param RequestInterface $request
105 * @param ResponseInterface $response
106 *
107 * @return RequestInterface Returns a new redirect request
108 * @throws CouldNotRewindStreamException If the body cannot be rewound.
109 */
110 private function createRedirectRequest(
111 RequestInterface $request,
112 ResponseInterface $response
113 ) {
114 $config = $request->getConfig();
115  
116 // Use a GET request if this is an entity enclosing request and we are
117 // not forcing RFC compliance, but rather emulating what all browsers
118 // would do. Be sure to disable redirects on the clone.
119 $redirectRequest = clone $request;
120 $redirectRequest->getEmitter()->detach($this);
121 $statusCode = $response->getStatusCode();
122  
123 if ($statusCode == 303 ||
124 ($statusCode <= 302 && $request->getBody() &&
125 !$config->getPath('redirect/strict'))
126 ) {
127 $redirectRequest->setMethod('GET');
128 $redirectRequest->setBody(null);
129 }
130  
131 $this->setRedirectUrl($redirectRequest, $response);
132 $this->rewindEntityBody($redirectRequest);
133  
134 // Add the Referer header if it is told to do so and only
135 // add the header if we are not redirecting from https to http.
136 if ($config->getPath('redirect/referer') && (
137 $redirectRequest->getScheme() == 'https' ||
138 $redirectRequest->getScheme() == $request->getScheme()
139 )) {
140 $url = Url::fromString($request->getUrl());
141 $url->setUsername(null)->setPassword(null);
142 $redirectRequest->setHeader('Referer', (string) $url);
143 }
144  
145 return $redirectRequest;
146 }
147  
148 /**
149 * Set the appropriate URL on the request based on the location header
150 *
151 * @param RequestInterface $redirectRequest
152 * @param ResponseInterface $response
153 */
154 private function setRedirectUrl(
155 RequestInterface $redirectRequest,
156 ResponseInterface $response
157 ) {
158 $location = $response->getHeader('Location');
159 $location = Url::fromString($location);
160  
161 // Combine location with the original URL if it is not absolute.
162 if (!$location->isAbsolute()) {
163 $originalUrl = Url::fromString($redirectRequest->getUrl());
164 // Remove query string parameters and just take what is present on
165 // the redirect Location header
166 $originalUrl->getQuery()->clear();
167 $location = $originalUrl->combine($location);
168 }
169  
170 $redirectRequest->setUrl($location);
171 }
172 }