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