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