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