1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.client;
20
21 import java.net.URI;
22 import java.net.URISyntaxException;
23 import java.util.List;
24 import java.util.regex.Matcher;
25 import java.util.regex.Pattern;
26
27 import org.eclipse.jetty.client.api.Request;
28 import org.eclipse.jetty.client.api.Response;
29 import org.eclipse.jetty.client.api.Result;
30 import org.eclipse.jetty.http.HttpMethod;
31 import org.eclipse.jetty.util.log.Log;
32 import org.eclipse.jetty.util.log.Logger;
33
34 public class RedirectProtocolHandler extends Response.Listener.Empty implements ProtocolHandler
35 {
36 private static final Logger LOG = Log.getLogger(RedirectProtocolHandler.class);
37 private static final String SCHEME_REGEXP = "(^https?)";
38 private static final String AUTHORITY_REGEXP = "([^/\\?#]+)";
39
40 private static final String DESTINATION_REGEXP = "(" + SCHEME_REGEXP + "://" + AUTHORITY_REGEXP + ")?";
41 private static final String PATH_REGEXP = "([^\\?#]*)";
42 private static final String QUERY_REGEXP = "([^#]*)";
43 private static final String FRAGMENT_REGEXP = "(.*)";
44 private static final Pattern URI_PATTERN = Pattern.compile(DESTINATION_REGEXP + PATH_REGEXP + QUERY_REGEXP + FRAGMENT_REGEXP);
45 private static final String ATTRIBUTE = RedirectProtocolHandler.class.getName() + ".redirects";
46
47 private final HttpClient client;
48 private final ResponseNotifier notifier;
49
50 public RedirectProtocolHandler(HttpClient client)
51 {
52 this.client = client;
53 this.notifier = new ResponseNotifier(client);
54 }
55
56 @Override
57 public boolean accept(Request request, Response response)
58 {
59 switch (response.getStatus())
60 {
61 case 301:
62 case 302:
63 case 303:
64 case 307:
65 return request.isFollowRedirects();
66 }
67 return false;
68 }
69
70 @Override
71 public Response.Listener getResponseListener()
72 {
73 return this;
74 }
75
76 @Override
77 public void onComplete(Result result)
78 {
79 if (!result.isFailed())
80 {
81 Request request = result.getRequest();
82 Response response = result.getResponse();
83 String location = response.getHeaders().get("location");
84 if (location != null)
85 {
86 URI newURI = sanitize(location);
87 LOG.debug("Redirecting to {} (Location: {})", newURI, location);
88 if (newURI != null)
89 {
90 if (!newURI.isAbsolute())
91 newURI = request.getURI().resolve(newURI);
92
93 int status = response.getStatus();
94 switch (status)
95 {
96 case 301:
97 {
98 String method = request.method();
99 if (HttpMethod.GET.is(method) || HttpMethod.HEAD.is(method) || HttpMethod.PUT.is(method))
100 redirect(result, method, newURI);
101 else if (HttpMethod.POST.is(method))
102 redirect(result, HttpMethod.GET.asString(), newURI);
103 else
104 fail(result, new HttpResponseException("HTTP protocol violation: received 301 for non GET/HEAD/POST/PUT request", response));
105 break;
106 }
107 case 302:
108 {
109 String method = request.method();
110 if (HttpMethod.HEAD.is(method) || HttpMethod.PUT.is(method))
111 redirect(result, method, newURI);
112 else
113 redirect(result, HttpMethod.GET.asString(), newURI);
114 break;
115 }
116 case 303:
117 {
118 String method = request.method();
119 if (HttpMethod.HEAD.is(method))
120 redirect(result, method, newURI);
121 else
122 redirect(result, HttpMethod.GET.asString(), newURI);
123 break;
124 }
125 case 307:
126 {
127
128 redirect(result, request.method(), newURI);
129 break;
130 }
131 default:
132 {
133 fail(result, new HttpResponseException("Unhandled HTTP status code " + status, response));
134 break;
135 }
136 }
137 }
138 else
139 {
140 fail(result, new HttpResponseException("Malformed Location header " + location, response));
141 }
142 }
143 else
144 {
145 fail(result, new HttpResponseException("Missing Location header " + location, response));
146 }
147 }
148 else
149 {
150 fail(result, result.getFailure());
151 }
152 }
153
154 private URI sanitize(String location)
155 {
156
157
158
159 try
160 {
161
162 return new URI(location);
163 }
164 catch (URISyntaxException x)
165 {
166 Matcher matcher = URI_PATTERN.matcher(location);
167 if (matcher.matches())
168 {
169 String scheme = matcher.group(2);
170 String authority = matcher.group(3);
171 String path = matcher.group(4);
172 String query = matcher.group(5);
173 if (query.length() == 0)
174 query = null;
175 String fragment = matcher.group(6);
176 if (fragment.length() == 0)
177 fragment = null;
178 try
179 {
180 return new URI(scheme, authority, path, query, fragment);
181 }
182 catch (URISyntaxException xx)
183 {
184
185 }
186 }
187 return null;
188 }
189 }
190
191 private void redirect(Result result, String method, URI location)
192 {
193 final Request request = result.getRequest();
194 HttpConversation conversation = client.getConversation(request.getConversationID(), false);
195 Integer redirects = (Integer)conversation.getAttribute(ATTRIBUTE);
196 if (redirects == null)
197 redirects = 0;
198
199 if (redirects < client.getMaxRedirects())
200 {
201 ++redirects;
202 conversation.setAttribute(ATTRIBUTE, redirects);
203
204 Request redirect = client.copyRequest(request, location);
205
206
207 redirect.method(method);
208
209 redirect.onRequestBegin(new Request.BeginListener()
210 {
211 @Override
212 public void onBegin(Request redirect)
213 {
214 Throwable cause = request.getAbortCause();
215 if (cause != null)
216 redirect.abort(cause);
217 }
218 });
219
220 redirect.send(null);
221 }
222 else
223 {
224 fail(result, new HttpResponseException("Max redirects exceeded " + redirects, result.getResponse()));
225 }
226 }
227
228 private void fail(Result result, Throwable failure)
229 {
230 Request request = result.getRequest();
231 Response response = result.getResponse();
232 HttpConversation conversation = client.getConversation(request.getConversationID(), false);
233 conversation.updateResponseListeners(null);
234 List<Response.ResponseListener> listeners = conversation.getResponseListeners();
235 notifier.notifyFailure(listeners, response, failure);
236 notifier.notifyComplete(listeners, new Result(request, response, failure));
237 }
238 }