1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package davmail.http;
21
22 import davmail.Settings;
23 import davmail.exception.*;
24 import davmail.http.request.*;
25 import org.apache.http.Header;
26 import org.apache.http.HttpResponse;
27 import org.apache.http.HttpStatus;
28 import org.apache.http.StatusLine;
29 import org.apache.http.auth.AuthSchemeProvider;
30 import org.apache.http.auth.AuthScope;
31 import org.apache.http.auth.NTCredentials;
32 import org.apache.http.client.CredentialsProvider;
33 import org.apache.http.client.HttpResponseException;
34 import org.apache.http.client.config.AuthSchemes;
35 import org.apache.http.client.config.RequestConfig;
36 import org.apache.http.client.methods.CloseableHttpResponse;
37 import org.apache.http.client.methods.HttpRequestBase;
38 import org.apache.http.client.protocol.HttpClientContext;
39 import org.apache.http.client.utils.URIUtils;
40 import org.apache.http.config.Registry;
41 import org.apache.http.config.RegistryBuilder;
42 import org.apache.http.conn.HttpClientConnectionManager;
43 import org.apache.http.conn.socket.ConnectionSocketFactory;
44 import org.apache.http.conn.socket.PlainConnectionSocketFactory;
45 import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
46 import org.apache.http.cookie.Cookie;
47 import org.apache.http.impl.auth.BasicSchemeFactory;
48 import org.apache.http.impl.auth.DigestSchemeFactory;
49 import org.apache.http.impl.client.BasicCookieStore;
50 import org.apache.http.impl.client.BasicCredentialsProvider;
51 import org.apache.http.impl.client.CloseableHttpClient;
52 import org.apache.http.impl.client.HttpClientBuilder;
53 import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
54 import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
55 import org.apache.http.impl.conn.SystemDefaultRoutePlanner;
56 import org.apache.jackrabbit.webdav.DavException;
57 import org.apache.jackrabbit.webdav.MultiStatus;
58 import org.apache.jackrabbit.webdav.MultiStatusResponse;
59 import org.apache.jackrabbit.webdav.client.methods.BaseDavRequest;
60 import org.apache.jackrabbit.webdav.client.methods.HttpCopy;
61 import org.apache.jackrabbit.webdav.client.methods.HttpMove;
62 import org.apache.log4j.Logger;
63 import org.codehaus.jettison.json.JSONObject;
64
65 import java.io.Closeable;
66 import java.io.IOException;
67 import java.net.*;
68 import java.security.Security;
69 import java.util.HashSet;
70 import java.util.List;
71
72 public class HttpClientAdapter implements Closeable {
73 static final Logger LOGGER = Logger.getLogger("davmail.http.HttpClientAdapter");
74
75 static final String[] SUPPORTED_PROTOCOLS = new String[]{"TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3"};
76 static final Registry<ConnectionSocketFactory> SCHEME_REGISTRY;
77 static String WORKSTATION_NAME = "UNKNOWN";
78 static final int MAX_REDIRECTS = 10;
79
80 static {
81
82 System.setProperty("jdk.tls.rejectClientInitiatedRenegotiation", "true");
83
84 System.setProperty("jdk.tls.ephemeralDHKeySize", "2048");
85
86 Security.setProperty("ssl.SocketFactory.provider", "davmail.http.DavGatewaySSLSocketFactory");
87
88
89 Security.setProperty("login.configuration.provider", "davmail.http.KerberosLoginConfiguration");
90
91
92 System.setProperty("jdk.http.auth.tunneling.disabledSchemes", "");
93
94 RegistryBuilder<ConnectionSocketFactory> schemeRegistry = RegistryBuilder.create();
95 schemeRegistry.register("http", new PlainConnectionSocketFactory());
96 schemeRegistry.register("https", new SSLConnectionSocketFactory(new DavGatewaySSLSocketFactory(),
97 SUPPORTED_PROTOCOLS, null,
98 SSLConnectionSocketFactory.getDefaultHostnameVerifier()));
99
100 SCHEME_REGISTRY = schemeRegistry.build();
101
102 try {
103 WORKSTATION_NAME = InetAddress.getLocalHost().getHostName();
104 } catch (Exception e) {
105
106 }
107
108
109 if (Settings.getBooleanProperty("davmail.useSystemProxies", Boolean.FALSE)) {
110 System.setProperty("java.net.useSystemProxies", "true");
111 }
112 ProxySelector.setDefault(new DavGatewayProxySelector(ProxySelector.getDefault()));
113 }
114
115
116
117
118
119
120
121 public static boolean isGzipEncoded(HttpResponse response) {
122 Header header = response.getFirstHeader("Content-Encoding");
123 return header != null && "gzip".equals(header.getValue());
124 }
125
126 HttpClientConnectionManager connectionManager;
127 CloseableHttpClient httpClient;
128 CredentialsProvider provider = new BasicCredentialsProvider();
129 BasicCookieStore cookieStore = new BasicCookieStore() {
130 @Override
131 public void addCookie(final Cookie cookie) {
132
133 super.addCookie(cookie);
134 }
135 };
136
137 URI uri;
138 String domain;
139 String userid;
140 String userEmail;
141
142 public HttpClientAdapter(String url) {
143 this(URI.create(url));
144 }
145
146 public HttpClientAdapter(String url, String username, String password) {
147 this(URI.create(url), username, password, false);
148 }
149
150 public HttpClientAdapter(String url, boolean enablePool) {
151 this(URI.create(url), null, null, enablePool);
152 }
153
154 public HttpClientAdapter(String url, String username, String password, boolean enablePool) {
155 this(URI.create(url), username, password, enablePool);
156 }
157
158 public HttpClientAdapter(URI uri) {
159 this(uri, null, null, false);
160 }
161
162 public HttpClientAdapter(URI uri, boolean enablePool) {
163 this(uri, null, null, enablePool);
164 }
165
166 public HttpClientAdapter(URI uri, String username, String password) {
167 this(uri, username, password, false);
168 }
169
170 public HttpClientAdapter(URI uri, String username, String password, boolean enablePool) {
171
172 this.uri = uri;
173
174 if (enablePool) {
175 connectionManager = new PoolingHttpClientConnectionManager(SCHEME_REGISTRY);
176 ((PoolingHttpClientConnectionManager) connectionManager).setDefaultMaxPerRoute(5);
177 startEvictorThread();
178 } else {
179 connectionManager = new BasicHttpClientConnectionManager(SCHEME_REGISTRY);
180 }
181 HttpClientBuilder clientBuilder = HttpClientBuilder.create()
182 .disableRedirectHandling()
183 .setDefaultRequestConfig(getRequestConfig())
184 .setUserAgent(getUserAgent())
185 .setDefaultAuthSchemeRegistry(getAuthSchemeRegistry())
186
187 .disableConnectionState()
188 .setConnectionManager(connectionManager);
189
190 SystemDefaultRoutePlanner routePlanner = new SystemDefaultRoutePlanner(ProxySelector.getDefault());
191 clientBuilder.setRoutePlanner(routePlanner);
192
193 clientBuilder.setDefaultCookieStore(cookieStore);
194
195 setCredentials(username, password);
196
197 boolean enableProxy = Settings.getBooleanProperty("davmail.enableProxy");
198 boolean useSystemProxies = Settings.getBooleanProperty("davmail.useSystemProxies", Boolean.FALSE);
199 String proxyHost = null;
200 int proxyPort = 0;
201 String proxyUser = null;
202 String proxyPassword = null;
203
204 if (useSystemProxies) {
205
206 System.setProperty("java.net.useSystemProxies", "true");
207 List<Proxy> proxyList = getProxyForURI(uri);
208 if (!proxyList.isEmpty() && proxyList.get(0).address() != null) {
209 InetSocketAddress inetSocketAddress = (InetSocketAddress) proxyList.get(0).address();
210 proxyHost = inetSocketAddress.getHostName();
211 proxyPort = inetSocketAddress.getPort();
212
213
214 proxyUser = Settings.getProperty("davmail.proxyUser");
215 proxyPassword = Settings.getProperty("davmail.proxyPassword");
216 }
217 } else if (isNoProxyFor(uri)) {
218 LOGGER.debug("no proxy for " + uri.getHost());
219 } else if (enableProxy) {
220 proxyHost = Settings.getProperty("davmail.proxyHost");
221 proxyPort = Settings.getIntProperty("davmail.proxyPort");
222 proxyUser = Settings.getProperty("davmail.proxyUser");
223 proxyPassword = Settings.getProperty("davmail.proxyPassword");
224 }
225
226 if (proxyHost != null && !proxyHost.isEmpty() && (proxyUser != null && !proxyUser.isEmpty())) {
227
228 AuthScope authScope = new AuthScope(proxyHost, proxyPort, AuthScope.ANY_REALM);
229 if (provider == null) {
230 provider = new BasicCredentialsProvider();
231 }
232
233
234 int backslashIndex = proxyUser.indexOf('\\');
235 if (backslashIndex > 0) {
236 provider.setCredentials(authScope, new NTCredentials(proxyUser.substring(backslashIndex + 1),
237 proxyPassword, WORKSTATION_NAME,
238 proxyUser.substring(0, backslashIndex)));
239 } else {
240 provider.setCredentials(authScope, new NTCredentials(proxyUser, proxyPassword, WORKSTATION_NAME, ""));
241 }
242
243 }
244
245 clientBuilder.setDefaultCredentialsProvider(provider);
246
247 httpClient = clientBuilder.build();
248 }
249
250
251
252
253
254
255 public String getHost() {
256 return uri.getHost();
257 }
258
259
260
261
262
263
264 public void setUri(URI uri) {
265 this.uri = uri;
266 }
267
268
269
270
271
272
273 public URI getUri() {
274 return uri;
275 }
276
277 private Registry<AuthSchemeProvider> getAuthSchemeRegistry() {
278 final RegistryBuilder<AuthSchemeProvider> registryBuilder = RegistryBuilder.create();
279 registryBuilder.register(AuthSchemes.NTLM, new DavMailNTLMSchemeFactory())
280 .register(AuthSchemes.BASIC, new BasicSchemeFactory())
281 .register(AuthSchemes.DIGEST, new DigestSchemeFactory());
282 if (Settings.getBooleanProperty("davmail.enableKerberos")) {
283 registryBuilder.register(AuthSchemes.SPNEGO, new DavMailSPNegoSchemeFactory());
284 }
285
286 return registryBuilder.build();
287 }
288
289 private RequestConfig getRequestConfig() {
290 HashSet<String> authSchemes = new HashSet<>();
291 if (Settings.getBooleanProperty("davmail.enableKerberos")) {
292 authSchemes.add(AuthSchemes.SPNEGO);
293 authSchemes.add(AuthSchemes.KERBEROS);
294 } else {
295 authSchemes.add(AuthSchemes.NTLM);
296 authSchemes.add(AuthSchemes.BASIC);
297 authSchemes.add(AuthSchemes.DIGEST);
298 }
299 return RequestConfig.custom()
300
301 .setConnectTimeout(Settings.getIntProperty("davmail.exchange.connectionTimeout", 10) * 1000)
302
303 .setSocketTimeout(Settings.getIntProperty("davmail.exchange.soTimeout", 120) * 1000)
304 .setTargetPreferredAuthSchemes(authSchemes)
305 .build();
306 }
307
308 private void parseUserName(String username) {
309 if (username != null) {
310 int pipeIndex = username.indexOf("|");
311 if (pipeIndex >= 0) {
312 userid = username.substring(0, pipeIndex);
313 userEmail = username.substring(pipeIndex + 1);
314 } else {
315 userid = username;
316 userEmail = username;
317 }
318
319 int backSlashIndex = userid.indexOf('\\');
320 if (backSlashIndex >= 0) {
321
322 domain = userid.substring(0, backSlashIndex);
323 userid = userid.substring(backSlashIndex + 1);
324 } else {
325 domain = Settings.getProperty("davmail.defaultDomain", "");
326 }
327 }
328 }
329
330
331
332
333
334
335
336 private static List<Proxy> getProxyForURI(java.net.URI uri) {
337 LOGGER.debug("get Default proxy selector");
338 ProxySelector proxySelector = ProxySelector.getDefault();
339 LOGGER.debug("getProxyForURI(" + uri + ')');
340 List<Proxy> proxies = proxySelector.select(uri);
341 LOGGER.debug("got system proxies:" + proxies);
342 return proxies;
343 }
344
345 protected static boolean isNoProxyFor(java.net.URI uri) {
346 final String noProxyFor = Settings.getProperty("davmail.noProxyFor");
347 if (noProxyFor != null) {
348 final String uriHost = uri.getHost().toLowerCase();
349 final String[] domains = noProxyFor.toLowerCase().split(",\\s*");
350 for (String domain : domains) {
351 if (uriHost.endsWith(domain)) {
352 return true;
353 }
354 }
355 }
356 return false;
357 }
358
359 public void startEvictorThread() {
360 DavMailIdleConnectionEvictor.addConnectionManager(connectionManager);
361 }
362
363 @Override
364 public void close() {
365 DavMailIdleConnectionEvictor.removeConnectionManager(connectionManager);
366 try {
367 httpClient.close();
368 } catch (IOException e) {
369 LOGGER.warn("Exception closing http client", e);
370 }
371 }
372
373 public static void close(HttpClientAdapter httpClientAdapter) {
374 if (httpClientAdapter != null) {
375 httpClientAdapter.close();
376 }
377 }
378
379
380
381
382
383
384
385
386
387 public CloseableHttpResponse execute(HttpRequestBase request) throws IOException {
388 return execute(request, null);
389 }
390
391
392
393
394
395
396
397
398
399
400 public CloseableHttpResponse execute(HttpRequestBase request, HttpClientContext context) throws IOException {
401
402 handleURI(request);
403
404 return httpClient.execute(request, context);
405 }
406
407
408
409
410
411
412 private void handleURI(HttpRequestBase request) {
413 URI requestURI = request.getURI();
414 if (!requestURI.isAbsolute()) {
415 request.setURI(URIUtils.resolve(uri, requestURI));
416 }
417 uri = request.getURI();
418 }
419
420 public ResponseWrapper executeFollowRedirect(PostRequest request) throws IOException {
421 ResponseWrapper responseWrapper = request;
422 LOGGER.debug(request.getMethod() + " " + request.getURI().toString());
423 LOGGER.debug(request.getParameters());
424
425 int count = 0;
426 int maxRedirect = Settings.getIntProperty("davmail.httpMaxRedirects", MAX_REDIRECTS);
427
428 executePostRequest(request);
429 URI redirectLocation = request.getRedirectLocation();
430
431 while (count++ < maxRedirect && redirectLocation != null) {
432 LOGGER.debug("Redirect " + request.getURI() + " to " + redirectLocation);
433
434 responseWrapper = new GetRequest(redirectLocation);
435 executeGetRequest((GetRequest) responseWrapper);
436 redirectLocation = ((GetRequest) responseWrapper).getRedirectLocation();
437 }
438
439 return responseWrapper;
440 }
441
442 public GetRequest executeFollowRedirect(GetRequest request) throws IOException {
443 GetRequest result = request;
444 LOGGER.debug(request.getMethod() + " " + request.getURI().toString());
445
446 int count = 0;
447 int maxRedirect = Settings.getIntProperty("davmail.httpMaxRedirects", MAX_REDIRECTS);
448
449 executeGetRequest(request);
450 URI redirectLocation = request.getRedirectLocation();
451
452 while (count++ < maxRedirect && redirectLocation != null) {
453 LOGGER.debug("Redirect " + request.getURI() + " to " + redirectLocation);
454
455 result = new GetRequest(redirectLocation);
456 executeGetRequest(result);
457 redirectLocation = result.getRedirectLocation();
458 }
459
460 return result;
461 }
462
463
464
465
466
467
468
469
470 public String executeGetRequest(GetRequest getRequest) throws IOException {
471 handleURI(getRequest);
472 String responseBodyAsString;
473 try (CloseableHttpResponse response = execute(getRequest)) {
474 responseBodyAsString = getRequest.handleResponse(response);
475 }
476 return responseBodyAsString;
477 }
478
479
480
481
482
483
484
485
486 public String executePostRequest(PostRequest postRequest) throws IOException {
487 handleURI(postRequest);
488 String responseBodyAsString;
489 try (CloseableHttpResponse response = execute(postRequest)) {
490 responseBodyAsString = postRequest.handleResponse(response);
491 }
492 return responseBodyAsString;
493 }
494
495 public JSONObject executeRestRequest(RestRequest restRequest) throws IOException {
496 handleURI(restRequest);
497 JSONObject responseBody;
498 try (CloseableHttpResponse response = execute(restRequest)) {
499 responseBody = restRequest.handleResponse(response);
500 }
501 return responseBody;
502 }
503
504
505
506
507
508
509
510
511 public MultiStatus executeDavRequest(BaseDavRequest request) throws IOException {
512 handleURI(request);
513 MultiStatus multiStatus = null;
514 try (CloseableHttpResponse response = execute(request)) {
515 request.checkSuccess(response);
516 if (response.getStatusLine().getStatusCode() == HttpStatus.SC_MULTI_STATUS) {
517 multiStatus = request.getResponseBodyAsMultiStatus(response);
518 }
519 } catch (DavException e) {
520 LOGGER.error(e.getMessage(), e);
521 throw new IOException(e.getErrorCode() + " " + e.getStatusPhrase(), e);
522 }
523 return multiStatus;
524 }
525
526
527
528
529
530
531
532
533 public MultiStatusResponse[] executeDavRequest(ExchangeDavRequest request) throws IOException {
534 handleURI(request);
535 MultiStatusResponse[] responses;
536 try (CloseableHttpResponse response = execute(request)) {
537 List<MultiStatusResponse> responseList = request.handleResponse(response);
538
539
540 responses = responseList.toArray(new MultiStatusResponse[0]);
541 }
542 return responses;
543 }
544
545
546
547
548
549
550
551
552
553
554
555 public MultiStatusResponse[] executeSearchRequest(String path, String searchStatement, int maxCount) throws IOException {
556 ExchangeSearchRequest searchRequest = new ExchangeSearchRequest(path, searchStatement);
557 if (maxCount > 0) {
558 searchRequest.setHeader("Range", "rows=0-" + (maxCount - 1));
559 }
560 return executeDavRequest(searchRequest);
561 }
562
563 public static boolean isRedirect(HttpResponse response) {
564 return isRedirect(response.getStatusLine().getStatusCode());
565 }
566
567
568
569
570
571
572
573 public static boolean isRedirect(int status) {
574 return status == HttpStatus.SC_MOVED_PERMANENTLY
575 || status == HttpStatus.SC_MOVED_TEMPORARILY
576 || status == HttpStatus.SC_SEE_OTHER
577 || status == HttpStatus.SC_TEMPORARY_REDIRECT;
578 }
579
580
581
582
583
584
585
586 public static URI getRedirectLocation(HttpResponse response) {
587 Header location = response.getFirstHeader("Location");
588 if (isRedirect(response.getStatusLine().getStatusCode()) && location != null) {
589 return URI.create(location.getValue());
590 }
591 return null;
592 }
593
594 public void setCredentials(String username, String password) {
595 parseUserName(username);
596 if (userid != null && password != null) {
597 LOGGER.debug("Creating NTCredentials for user " + userid + " workstation " + WORKSTATION_NAME + " domain " + domain);
598 NTCredentials credentials = new NTCredentials(userid, password, WORKSTATION_NAME, domain);
599 provider.setCredentials(AuthScope.ANY, credentials);
600 }
601 }
602
603 public List<Cookie> getCookies() {
604 return cookieStore.getCookies();
605 }
606
607 public void addCookie(Cookie cookie) {
608 cookieStore.addCookie(cookie);
609 }
610
611 public String getUserAgent() {
612 return Settings.getUserAgent();
613 }
614
615 public static HttpResponseException buildHttpResponseException(HttpRequestBase request, HttpResponse response) {
616 return buildHttpResponseException(request, response.getStatusLine());
617 }
618
619
620
621
622
623
624
625 public static HttpResponseException buildHttpResponseException(HttpRequestBase method, StatusLine statusLine) {
626 int status = statusLine.getStatusCode();
627 StringBuilder message = new StringBuilder();
628 message.append(status).append(' ').append(statusLine.getReasonPhrase());
629 message.append(" at ").append(method.getURI());
630 if (method instanceof HttpCopy || method instanceof HttpMove) {
631 message.append(" to ").append(method.getFirstHeader("Destination"));
632 }
633
634 if (status == 440) {
635 return new LoginTimeoutException(message.toString());
636 } else if (status == HttpStatus.SC_FORBIDDEN) {
637 return new HttpForbiddenException(message.toString());
638 } else if (status == HttpStatus.SC_NOT_FOUND) {
639 return new HttpNotFoundException(message.toString());
640 } else if (status == HttpStatus.SC_PRECONDITION_FAILED) {
641 return new HttpPreconditionFailedException(message.toString());
642 } else if (status == HttpStatus.SC_INTERNAL_SERVER_ERROR) {
643 return new HttpServerErrorException(message.toString());
644 } else {
645 return new HttpResponseException(status, message.toString());
646 }
647 }
648
649 }