1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 package org.apache.hc.client5.http.impl.classic;
28
29 import java.io.ByteArrayInputStream;
30 import java.io.InputStream;
31 import java.net.URI;
32 import java.net.URISyntaxException;
33 import java.util.Arrays;
34 import java.util.List;
35
36 import org.apache.hc.client5.http.CircularRedirectException;
37 import org.apache.hc.client5.http.HttpRoute;
38 import org.apache.hc.client5.http.RedirectException;
39 import org.apache.hc.client5.http.auth.AuthExchange;
40 import org.apache.hc.client5.http.classic.ExecChain;
41 import org.apache.hc.client5.http.classic.ExecRuntime;
42 import org.apache.hc.client5.http.classic.methods.HttpGet;
43 import org.apache.hc.client5.http.config.RequestConfig;
44 import org.apache.hc.client5.http.entity.EntityBuilder;
45 import org.apache.hc.client5.http.impl.DefaultRedirectStrategy;
46 import org.apache.hc.client5.http.impl.auth.BasicScheme;
47 import org.apache.hc.client5.http.impl.auth.NTLMScheme;
48 import org.apache.hc.client5.http.protocol.HttpClientContext;
49 import org.apache.hc.client5.http.protocol.RedirectLocations;
50 import org.apache.hc.client5.http.protocol.RedirectStrategy;
51 import org.apache.hc.client5.http.routing.HttpRoutePlanner;
52 import org.apache.hc.core5.http.ClassicHttpRequest;
53 import org.apache.hc.core5.http.ClassicHttpResponse;
54 import org.apache.hc.core5.http.HttpEntity;
55 import org.apache.hc.core5.http.HttpException;
56 import org.apache.hc.core5.http.HttpHeaders;
57 import org.apache.hc.core5.http.HttpHost;
58 import org.apache.hc.core5.http.HttpStatus;
59 import org.apache.hc.core5.http.ProtocolException;
60 import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
61 import org.junit.Assert;
62 import org.junit.Before;
63 import org.junit.Test;
64 import org.junit.runner.RunWith;
65 import org.mockito.ArgumentCaptor;
66 import org.mockito.ArgumentMatcher;
67 import org.mockito.ArgumentMatchers;
68 import org.mockito.Mock;
69 import org.mockito.Mockito;
70 import org.mockito.junit.MockitoJUnitRunner;
71
72 @RunWith(MockitoJUnitRunner.class)
73 public class TestRedirectExec {
74
75 @Mock
76 private HttpRoutePlanner httpRoutePlanner;
77 @Mock
78 private ExecChain chain;
79 @Mock
80 private ExecRuntime endpoint;
81
82 private RedirectStrategy redirectStrategy;
83 private RedirectExec redirectExec;
84 private HttpHost target;
85
86 @Before
87 public void setup() throws Exception {
88 target = new HttpHost("localhost", 80);
89 redirectStrategy = Mockito.spy(new DefaultRedirectStrategy());
90 redirectExec = new RedirectExec(httpRoutePlanner, redirectStrategy);
91 }
92
93 @Test
94 public void testFundamentals() throws Exception {
95 final HttpRoute route = new HttpRoute(target);
96 final HttpGet request = new HttpGet("/test");
97 final HttpClientContext context = HttpClientContext.create();
98
99 final ClassicHttpResponse response1 = Mockito.spy(new BasicClassicHttpResponse(HttpStatus.SC_MOVED_TEMPORARILY));
100 final URI redirect = new URI("http://localhost:80/redirect");
101 response1.setHeader(HttpHeaders.LOCATION, redirect.toASCIIString());
102 final InputStream inStream1 = Mockito.spy(new ByteArrayInputStream(new byte[] {1, 2, 3}));
103 final HttpEntity entity1 = EntityBuilder.create()
104 .setStream(inStream1)
105 .build();
106 response1.setEntity(entity1);
107 final ClassicHttpResponse response2 = Mockito.spy(new BasicClassicHttpResponse(HttpStatus.SC_OK));
108 final InputStream inStream2 = Mockito.spy(new ByteArrayInputStream(new byte[] {1, 2, 3}));
109 final HttpEntity entity2 = EntityBuilder.create()
110 .setStream(inStream2)
111 .build();
112 response2.setEntity(entity2);
113
114 Mockito.when(chain.proceed(
115 ArgumentMatchers.same(request),
116 ArgumentMatchers.<ExecChain.Scope>any())).thenReturn(response1);
117 Mockito.when(chain.proceed(
118 HttpRequestMatcher.matchesRequestUri(redirect),
119 ArgumentMatchers.<ExecChain.Scope>any())).thenReturn(response2);
120
121 final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, endpoint, context);
122 redirectExec.execute(request, scope, chain);
123
124 final ArgumentCaptor<ClassicHttpRequest> reqCaptor = ArgumentCaptor.forClass(ClassicHttpRequest.class);
125 Mockito.verify(chain, Mockito.times(2)).proceed(reqCaptor.capture(), ArgumentMatchers.same(scope));
126
127 final List<ClassicHttpRequest> allValues = reqCaptor.getAllValues();
128 Assert.assertNotNull(allValues);
129 Assert.assertEquals(2, allValues.size());
130 Assert.assertSame(request, allValues.get(0));
131
132 Mockito.verify(response1, Mockito.times(1)).close();
133 Mockito.verify(inStream1, Mockito.times(2)).close();
134 Mockito.verify(response2, Mockito.never()).close();
135 Mockito.verify(inStream2, Mockito.never()).close();
136 }
137
138 @Test(expected = RedirectException.class)
139 public void testMaxRedirect() throws Exception {
140 final HttpRoute route = new HttpRoute(target);
141 final HttpGet request = new HttpGet("/test");
142 final HttpClientContext context = HttpClientContext.create();
143 final RequestConfig config = RequestConfig.custom()
144 .setRedirectsEnabled(true)
145 .setMaxRedirects(3)
146 .build();
147 context.setRequestConfig(config);
148
149 final ClassicHttpResponse response1 = Mockito.spy(new BasicClassicHttpResponse(HttpStatus.SC_MOVED_TEMPORARILY));
150 final URI redirect = new URI("http://localhost:80/redirect");
151 response1.setHeader(HttpHeaders.LOCATION, redirect.toASCIIString());
152
153 Mockito.when(chain.proceed(ArgumentMatchers.<ClassicHttpRequest>any(), ArgumentMatchers.<ExecChain.Scope>any())).thenReturn(response1);
154
155 final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, endpoint, context);
156 redirectExec.execute(request, scope, chain);
157 }
158
159 @Test(expected = HttpException.class)
160 public void testRelativeRedirect() throws Exception {
161 final HttpRoute route = new HttpRoute(target);
162 final HttpGet request = new HttpGet("/test");
163 final HttpClientContext context = HttpClientContext.create();
164
165 final ClassicHttpResponse response1 = Mockito.spy(new BasicClassicHttpResponse(HttpStatus.SC_MOVED_TEMPORARILY));
166 final URI redirect = new URI("/redirect");
167 response1.setHeader(HttpHeaders.LOCATION, redirect.toASCIIString());
168 Mockito.when(chain.proceed(
169 ArgumentMatchers.same(request),
170 ArgumentMatchers.<ExecChain.Scope>any())).thenReturn(response1);
171
172 final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, endpoint, context);
173 redirectExec.execute(request, scope, chain);
174 }
175
176 @Test
177 public void testCrossSiteRedirect() throws Exception {
178
179 final HttpHost proxy = new HttpHost("proxy");
180 final HttpRoute route = new HttpRoute(target, proxy);
181 final HttpGet request = new HttpGet("/test");
182 final HttpClientContext context = HttpClientContext.create();
183
184 final AuthExchange targetAuthExchange = new AuthExchange();
185 targetAuthExchange.setState(AuthExchange.State.SUCCESS);
186 targetAuthExchange.select(new BasicScheme());
187 final AuthExchange proxyAuthExchange = new AuthExchange();
188 proxyAuthExchange.setState(AuthExchange.State.SUCCESS);
189 proxyAuthExchange.select(new NTLMScheme());
190 context.setAuthExchange(target, targetAuthExchange);
191 context.setAuthExchange(proxy, proxyAuthExchange);
192
193 final ClassicHttpResponse response1 = Mockito.spy(new BasicClassicHttpResponse(HttpStatus.SC_MOVED_TEMPORARILY));
194 final URI redirect = new URI("http://otherhost:8888/redirect");
195 response1.setHeader(HttpHeaders.LOCATION, redirect.toASCIIString());
196 final ClassicHttpResponse response2 = Mockito.spy(new BasicClassicHttpResponse(HttpStatus.SC_OK));
197 final HttpHost otherHost = new HttpHost("otherhost", 8888);
198 Mockito.when(chain.proceed(
199 ArgumentMatchers.same(request),
200 ArgumentMatchers.<ExecChain.Scope>any())).thenReturn(response1);
201 Mockito.when(chain.proceed(
202 HttpRequestMatcher.matchesRequestUri(redirect),
203 ArgumentMatchers.<ExecChain.Scope>any())).thenReturn(response2);
204 Mockito.when(httpRoutePlanner.determineRoute(
205 ArgumentMatchers.eq(otherHost),
206 ArgumentMatchers.<HttpClientContext>any())).thenReturn(new HttpRoute(otherHost));
207
208 final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, endpoint, context);
209 redirectExec.execute(request, scope, chain);
210
211 final AuthExchange authExchange1 = context.getAuthExchange(target);
212 Assert.assertNotNull(authExchange1);
213 Assert.assertEquals(AuthExchange.State.UNCHALLENGED, authExchange1.getState());
214 Assert.assertEquals(null, authExchange1.getAuthScheme());
215 final AuthExchange authExchange2 = context.getAuthExchange(proxy);
216 Assert.assertNotNull(authExchange2);
217 Assert.assertEquals(AuthExchange.State.UNCHALLENGED, authExchange2.getState());
218 Assert.assertEquals(null, authExchange2.getAuthScheme());
219 }
220
221 @Test
222 public void testAllowCircularRedirects() throws Exception {
223 final HttpRoute route = new HttpRoute(target);
224 final HttpClientContext context = HttpClientContext.create();
225 context.setRequestConfig(RequestConfig.custom()
226 .setCircularRedirectsAllowed(true)
227 .build());
228
229 final URI uri = URI.create("http://localhost/");
230 final HttpGet request = new HttpGet(uri);
231
232 final URI uri1 = URI.create("http://localhost/stuff1");
233 final URI uri2 = URI.create("http://localhost/stuff2");
234 final ClassicHttpResponse response1 = new BasicClassicHttpResponse(HttpStatus.SC_MOVED_TEMPORARILY);
235 response1.addHeader("Location", uri1.toASCIIString());
236 final ClassicHttpResponse response2 = new BasicClassicHttpResponse(HttpStatus.SC_MOVED_TEMPORARILY);
237 response2.addHeader("Location", uri2.toASCIIString());
238 final ClassicHttpResponse response3 = new BasicClassicHttpResponse(HttpStatus.SC_MOVED_TEMPORARILY);
239 response3.addHeader("Location", uri1.toASCIIString());
240 final ClassicHttpResponse response4 = new BasicClassicHttpResponse(HttpStatus.SC_OK);
241
242 Mockito.when(chain.proceed(
243 HttpRequestMatcher.matchesRequestUri(uri),
244 ArgumentMatchers.<ExecChain.Scope>any())).thenReturn(response1);
245 Mockito.when(chain.proceed(
246 HttpRequestMatcher.matchesRequestUri(uri1),
247 ArgumentMatchers.<ExecChain.Scope>any())).thenReturn(response2, response4);
248 Mockito.when(chain.proceed(
249 HttpRequestMatcher.matchesRequestUri(uri2),
250 ArgumentMatchers.<ExecChain.Scope>any())).thenReturn(response3);
251 Mockito.when(httpRoutePlanner.determineRoute(
252 ArgumentMatchers.eq(new HttpHost("localhost")),
253 ArgumentMatchers.<HttpClientContext>any())).thenReturn(route);
254
255 final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, endpoint, context);
256 redirectExec.execute(request, scope, chain);
257
258 final RedirectLocations uris = context.getRedirectLocations();
259 Assert.assertNotNull(uris);
260 Assert.assertEquals(Arrays.asList(uri1, uri2, uri1), uris.getAll());
261 }
262
263 @Test(expected=CircularRedirectException.class)
264 public void testGetLocationUriDisallowCircularRedirects() throws Exception {
265 final HttpRoute route = new HttpRoute(target);
266 final HttpClientContext context = HttpClientContext.create();
267 context.setRequestConfig(RequestConfig.custom()
268 .setCircularRedirectsAllowed(false)
269 .build());
270
271 final URI uri = URI.create("http://localhost/");
272 final HttpGet request = new HttpGet(uri);
273
274 final URI uri1 = URI.create("http://localhost/stuff1");
275 final URI uri2 = URI.create("http://localhost/stuff2");
276 final ClassicHttpResponse response1 = new BasicClassicHttpResponse(HttpStatus.SC_MOVED_TEMPORARILY);
277 response1.addHeader("Location", uri1.toASCIIString());
278 final ClassicHttpResponse response2 = new BasicClassicHttpResponse(HttpStatus.SC_MOVED_TEMPORARILY);
279 response2.addHeader("Location", uri2.toASCIIString());
280 final ClassicHttpResponse response3 = new BasicClassicHttpResponse(HttpStatus.SC_MOVED_TEMPORARILY);
281 response3.addHeader("Location", uri1.toASCIIString());
282 Mockito.when(httpRoutePlanner.determineRoute(
283 ArgumentMatchers.eq(new HttpHost("localhost")),
284 ArgumentMatchers.<HttpClientContext>any())).thenReturn(route);
285
286 Mockito.when(chain.proceed(
287 HttpRequestMatcher.matchesRequestUri(uri),
288 ArgumentMatchers.<ExecChain.Scope>any())).thenReturn(response1);
289 Mockito.when(chain.proceed(
290 HttpRequestMatcher.matchesRequestUri(uri1),
291 ArgumentMatchers.<ExecChain.Scope>any())).thenReturn(response2);
292 Mockito.when(chain.proceed(
293 HttpRequestMatcher.matchesRequestUri(uri2),
294 ArgumentMatchers.<ExecChain.Scope>any())).thenReturn(response3);
295
296 final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, endpoint, context);
297 redirectExec.execute(request, scope, chain);
298 }
299
300 @Test(expected = RuntimeException.class)
301 public void testRedirectRuntimeException() throws Exception {
302 final HttpRoute route = new HttpRoute(target);
303 final HttpGet request = new HttpGet("/test");
304 final HttpClientContext context = HttpClientContext.create();
305
306 final ClassicHttpResponse response1 = Mockito.spy(new BasicClassicHttpResponse(HttpStatus.SC_MOVED_TEMPORARILY));
307 final URI redirect = new URI("http://localhost:80/redirect");
308 response1.setHeader(HttpHeaders.LOCATION, redirect.toASCIIString());
309 Mockito.when(chain.proceed(
310 ArgumentMatchers.same(request),
311 ArgumentMatchers.<ExecChain.Scope>any())).thenReturn(response1);
312 Mockito.doThrow(new RuntimeException("Oppsie")).when(redirectStrategy).getLocationURI(
313 ArgumentMatchers.<ClassicHttpRequest>any(),
314 ArgumentMatchers.<ClassicHttpResponse>any(),
315 ArgumentMatchers.<HttpClientContext>any());
316
317 final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, endpoint, context);
318 try {
319 redirectExec.execute(request, scope, chain);
320 } catch (final Exception ex) {
321 Mockito.verify(response1).close();
322 throw ex;
323 }
324 }
325
326 @Test(expected = ProtocolException.class)
327 public void testRedirectProtocolException() throws Exception {
328 final HttpRoute route = new HttpRoute(target);
329 final HttpGet request = new HttpGet("/test");
330 final HttpClientContext context = HttpClientContext.create();
331
332 final ClassicHttpResponse response1 = Mockito.spy(new BasicClassicHttpResponse(HttpStatus.SC_MOVED_TEMPORARILY));
333 final URI redirect = new URI("http://localhost:80/redirect");
334 response1.setHeader(HttpHeaders.LOCATION, redirect.toASCIIString());
335 final InputStream inStream1 = Mockito.spy(new ByteArrayInputStream(new byte[] {1, 2, 3}));
336 final HttpEntity entity1 = EntityBuilder.create()
337 .setStream(inStream1)
338 .build();
339 response1.setEntity(entity1);
340 Mockito.when(chain.proceed(
341 ArgumentMatchers.same(request),
342 ArgumentMatchers.<ExecChain.Scope>any())).thenReturn(response1);
343 Mockito.doThrow(new ProtocolException("Oppsie")).when(redirectStrategy).getLocationURI(
344 ArgumentMatchers.<ClassicHttpRequest>any(),
345 ArgumentMatchers.<ClassicHttpResponse>any(),
346 ArgumentMatchers.<HttpClientContext>any());
347
348 final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, endpoint, context);
349 try {
350 redirectExec.execute(request, scope, chain);
351 } catch (final Exception ex) {
352 Mockito.verify(inStream1, Mockito.times(2)).close();
353 Mockito.verify(response1).close();
354 throw ex;
355 }
356 }
357
358 private static class HttpRequestMatcher implements ArgumentMatcher<ClassicHttpRequest> {
359
360 private final URI expectedRequestUri;
361
362 HttpRequestMatcher(final URI requestUri) {
363 super();
364 this.expectedRequestUri = requestUri;
365 }
366
367 @Override
368 public boolean matches(final ClassicHttpRequest argument) {
369 if (argument == null) {
370 return false;
371 }
372 try {
373 final URI requestUri = argument.getUri();
374 return this.expectedRequestUri.equals(requestUri);
375 } catch (final URISyntaxException ex) {
376 return false;
377 }
378 }
379
380 static ClassicHttpRequest matchesRequestUri(final URI requestUri) {
381 return ArgumentMatchers.argThat(new HttpRequestMatcher(requestUri));
382 }
383
384 }
385
386 }