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
28 package org.apache.hc.client5.http.impl.classic;
29
30 import java.io.IOException;
31 import java.util.Iterator;
32
33 import org.apache.hc.client5.http.AuthenticationStrategy;
34 import org.apache.hc.client5.http.HttpRoute;
35 import org.apache.hc.client5.http.SchemePortResolver;
36 import org.apache.hc.client5.http.auth.AuthExchange;
37 import org.apache.hc.client5.http.auth.AuthenticationException;
38 import org.apache.hc.client5.http.auth.ChallengeType;
39 import org.apache.hc.client5.http.auth.MalformedChallengeException;
40 import org.apache.hc.client5.http.classic.ExecChain;
41 import org.apache.hc.client5.http.classic.ExecChainHandler;
42 import org.apache.hc.client5.http.classic.ExecRuntime;
43 import org.apache.hc.client5.http.config.RequestConfig;
44 import org.apache.hc.client5.http.impl.DefaultSchemePortResolver;
45 import org.apache.hc.client5.http.impl.RequestSupport;
46 import org.apache.hc.client5.http.impl.auth.AuthCacheKeeper;
47 import org.apache.hc.client5.http.impl.auth.AuthenticationHandler;
48 import org.apache.hc.client5.http.protocol.HttpClientContext;
49 import org.apache.hc.core5.annotation.Contract;
50 import org.apache.hc.core5.annotation.Internal;
51 import org.apache.hc.core5.annotation.ThreadingBehavior;
52 import org.apache.hc.core5.http.ClassicHttpRequest;
53 import org.apache.hc.core5.http.ClassicHttpResponse;
54 import org.apache.hc.core5.http.Header;
55 import org.apache.hc.core5.http.HttpEntity;
56 import org.apache.hc.core5.http.HttpException;
57 import org.apache.hc.core5.http.HttpHeaders;
58 import org.apache.hc.core5.http.HttpHost;
59 import org.apache.hc.core5.http.HttpResponse;
60 import org.apache.hc.core5.http.Method;
61 import org.apache.hc.core5.http.ProtocolException;
62 import org.apache.hc.core5.http.io.entity.EntityUtils;
63 import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
64 import org.apache.hc.core5.net.URIAuthority;
65 import org.apache.hc.core5.util.Args;
66 import org.slf4j.Logger;
67 import org.slf4j.LoggerFactory;
68
69
70
71
72
73
74
75
76
77
78
79
80 @Contract(threading = ThreadingBehavior.STATELESS)
81 @Internal
82 public final class ProtocolExec implements ExecChainHandler {
83
84 private static final Logger LOG = LoggerFactory.getLogger(ProtocolExec.class);
85
86 private final AuthenticationStrategy targetAuthStrategy;
87 private final AuthenticationStrategy proxyAuthStrategy;
88 private final AuthenticationHandler authenticator;
89 private final SchemePortResolver schemePortResolver;
90 private final AuthCacheKeeper authCacheKeeper;
91
92 public ProtocolExec(
93 final AuthenticationStrategy targetAuthStrategy,
94 final AuthenticationStrategy proxyAuthStrategy,
95 final SchemePortResolver schemePortResolver,
96 final boolean authCachingDisabled) {
97 this.targetAuthStrategy = Args.notNull(targetAuthStrategy, "Target authentication strategy");
98 this.proxyAuthStrategy = Args.notNull(proxyAuthStrategy, "Proxy authentication strategy");
99 this.authenticator = new AuthenticationHandler();
100 this.schemePortResolver = schemePortResolver != null ? schemePortResolver : DefaultSchemePortResolver.INSTANCE;
101 this.authCacheKeeper = authCachingDisabled ? null : new AuthCacheKeeper(this.schemePortResolver);
102 }
103
104 @Override
105 public ClassicHttpResponse execute(
106 final ClassicHttpRequest userRequest,
107 final ExecChain.Scope scope,
108 final ExecChain chain) throws IOException, HttpException {
109 Args.notNull(userRequest, "HTTP request");
110 Args.notNull(scope, "Scope");
111
112 if (Method.CONNECT.isSame(userRequest.getMethod())) {
113 throw new ProtocolException("Direct execution of CONNECT is not allowed");
114 }
115
116 final String exchangeId = scope.exchangeId;
117 final HttpRoute route = scope.route;
118 final HttpClientContext context = scope.clientContext;
119 final ExecRuntime execRuntime = scope.execRuntime;
120
121 final HttpHost routeTarget = route.getTargetHost();
122 final HttpHost proxy = route.getProxyHost();
123
124 try {
125 final ClassicHttpRequest request;
126 if (proxy != null && !route.isTunnelled()) {
127 final ClassicRequestBuilder requestBuilder = ClassicRequestBuilder.copy(userRequest);
128 if (requestBuilder.getAuthority() == null) {
129 requestBuilder.setAuthority(new URIAuthority(routeTarget));
130 }
131 requestBuilder.setAbsoluteRequestUri(true);
132 request = requestBuilder.build();
133 } else {
134 request = userRequest;
135 }
136
137
138 if (request.getScheme() == null) {
139 request.setScheme(routeTarget.getSchemeName());
140 }
141 if (request.getAuthority() == null) {
142 request.setAuthority(new URIAuthority(routeTarget));
143 }
144
145 final URIAuthority authority = request.getAuthority();
146 if (authority.getUserInfo() != null) {
147 throw new ProtocolException("Request URI authority contains deprecated userinfo component");
148 }
149
150 final HttpHost target = new HttpHost(
151 request.getScheme(),
152 authority.getHostName(),
153 schemePortResolver.resolve(request.getScheme(), authority));
154 final String pathPrefix = RequestSupport.extractPathPrefix(request);
155
156 final AuthExchange targetAuthExchange = context.getAuthExchange(target);
157 final AuthExchange proxyAuthExchange = proxy != null ? context.getAuthExchange(proxy) : new AuthExchange();
158
159 if (!targetAuthExchange.isConnectionBased() &&
160 targetAuthExchange.getPathPrefix() != null &&
161 !pathPrefix.startsWith(targetAuthExchange.getPathPrefix())) {
162
163
164 targetAuthExchange.reset();
165 }
166 if (targetAuthExchange.getPathPrefix() == null) {
167 targetAuthExchange.setPathPrefix(pathPrefix);
168 }
169
170 if (authCacheKeeper != null) {
171 authCacheKeeper.loadPreemptively(target, pathPrefix, targetAuthExchange, context);
172 if (proxy != null) {
173 authCacheKeeper.loadPreemptively(proxy, null, proxyAuthExchange, context);
174 }
175 }
176
177 RequestEntityProxy.enhance(request);
178
179 for (;;) {
180
181 if (!request.containsHeader(HttpHeaders.AUTHORIZATION)) {
182 if (LOG.isDebugEnabled()) {
183 LOG.debug("{} target auth state: {}", exchangeId, targetAuthExchange.getState());
184 }
185 authenticator.addAuthResponse(target, ChallengeType.TARGET, request, targetAuthExchange, context);
186 }
187 if (!request.containsHeader(HttpHeaders.PROXY_AUTHORIZATION) && !route.isTunnelled()) {
188 if (LOG.isDebugEnabled()) {
189 LOG.debug("{} proxy auth state: {}", exchangeId, proxyAuthExchange.getState());
190 }
191 authenticator.addAuthResponse(proxy, ChallengeType.PROXY, request, proxyAuthExchange, context);
192 }
193
194
195 final ClassicHttpResponse response = chain.proceed(request, scope);
196
197 if (Method.TRACE.isSame(request.getMethod())) {
198
199 ResponseEntityProxy.enhance(response, execRuntime);
200 return response;
201 }
202 final HttpEntity requestEntity = request.getEntity();
203 if (requestEntity != null && !requestEntity.isRepeatable()) {
204 if (LOG.isDebugEnabled()) {
205 LOG.debug("{} Cannot retry non-repeatable request", exchangeId);
206 }
207 ResponseEntityProxy.enhance(response, execRuntime);
208 return response;
209 }
210 if (needAuthentication(
211 targetAuthExchange,
212 proxyAuthExchange,
213 proxy != null ? proxy : target,
214 target,
215 pathPrefix,
216 response,
217 context)) {
218
219 final HttpEntity responseEntity = response.getEntity();
220 if (execRuntime.isConnectionReusable()) {
221 EntityUtils.consume(responseEntity);
222 } else {
223 execRuntime.disconnectEndpoint();
224 if (proxyAuthExchange.getState() == AuthExchange.State.SUCCESS
225 && proxyAuthExchange.isConnectionBased()) {
226 if (LOG.isDebugEnabled()) {
227 LOG.debug("{} resetting proxy auth state", exchangeId);
228 }
229 proxyAuthExchange.reset();
230 }
231 if (targetAuthExchange.getState() == AuthExchange.State.SUCCESS
232 && targetAuthExchange.isConnectionBased()) {
233 if (LOG.isDebugEnabled()) {
234 LOG.debug("{} resetting target auth state", exchangeId);
235 }
236 targetAuthExchange.reset();
237 }
238 }
239
240 final ClassicHttpRequest original = scope.originalRequest;
241 request.setHeaders();
242 for (final Iterator<Header> it = original.headerIterator(); it.hasNext(); ) {
243 request.addHeader(it.next());
244 }
245 } else {
246 ResponseEntityProxy.enhance(response, execRuntime);
247 return response;
248 }
249 }
250 } catch (final HttpException ex) {
251 execRuntime.discardEndpoint();
252 throw ex;
253 } catch (final RuntimeException | IOException ex) {
254 execRuntime.discardEndpoint();
255 for (final AuthExchange authExchange : context.getAuthExchanges().values()) {
256 if (authExchange.isConnectionBased()) {
257 authExchange.reset();
258 }
259 }
260 throw ex;
261 }
262 }
263
264 private boolean needAuthentication(
265 final AuthExchange targetAuthExchange,
266 final AuthExchange proxyAuthExchange,
267 final HttpHost proxy,
268 final HttpHost target,
269 final String pathPrefix,
270 final HttpResponse response,
271 final HttpClientContext context) throws AuthenticationException, MalformedChallengeException {
272 final RequestConfig config = context.getRequestConfigOrDefault();
273 if (config.isAuthenticationEnabled()) {
274 final boolean targetAuthRequested = authenticator.isChallenged(
275 target, ChallengeType.TARGET, response, targetAuthExchange, context);
276 final boolean targetMutualAuthRequired = authenticator.isChallengeExpected(targetAuthExchange);
277
278 if (authCacheKeeper != null) {
279 if (targetAuthRequested) {
280 authCacheKeeper.updateOnChallenge(target, pathPrefix, targetAuthExchange, context);
281 } else {
282 authCacheKeeper.updateOnNoChallenge(target, pathPrefix, targetAuthExchange, context);
283 }
284 }
285
286 final boolean proxyAuthRequested = authenticator.isChallenged(
287 proxy, ChallengeType.PROXY, response, proxyAuthExchange, context);
288 final boolean proxyMutualAuthRequired = authenticator.isChallengeExpected(proxyAuthExchange);
289
290 if (authCacheKeeper != null) {
291 if (proxyAuthRequested) {
292 authCacheKeeper.updateOnChallenge(proxy, null, proxyAuthExchange, context);
293 } else {
294 authCacheKeeper.updateOnNoChallenge(proxy, null, proxyAuthExchange, context);
295 }
296 }
297
298 if (targetAuthRequested || targetMutualAuthRequired) {
299 final boolean updated = authenticator.handleResponse(target, ChallengeType.TARGET, response,
300 targetAuthStrategy, targetAuthExchange, context);
301
302 if (authCacheKeeper != null) {
303 authCacheKeeper.updateOnResponse(target, pathPrefix, targetAuthExchange, context);
304 }
305
306 return updated;
307 }
308 if (proxyAuthRequested || proxyMutualAuthRequired) {
309 final boolean updated = authenticator.handleResponse(proxy, ChallengeType.PROXY, response,
310 proxyAuthStrategy, proxyAuthExchange, context);
311
312 if (authCacheKeeper != null) {
313 authCacheKeeper.updateOnResponse(proxy, null, proxyAuthExchange, context);
314 }
315
316 return updated;
317 }
318 }
319 return false;
320 }
321
322 }