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