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