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"};
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 (Throwable t) {
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 LOGGER.debug("Add cookie " + cookie);
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()) {
227 if (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 registryBuilder.register(AuthSchemes.NTLM, new JCIFSNTLMSchemeFactory())
281 .register(AuthSchemes.BASIC, new BasicSchemeFactory())
282 .register(AuthSchemes.DIGEST, new DigestSchemeFactory());
283 if (Settings.getBooleanProperty("davmail.enableKerberos")) {
284 registryBuilder.register(AuthSchemes.SPNEGO, new DavMailSPNegoSchemeFactory());
285 }
286
287 return registryBuilder.build();
288 }
289
290 private RequestConfig getRequestConfig() {
291 HashSet<String> authSchemes = new HashSet<>();
292 if (Settings.getBooleanProperty("davmail.enableKerberos")) {
293 authSchemes.add(AuthSchemes.SPNEGO);
294 authSchemes.add(AuthSchemes.KERBEROS);
295 } else {
296 authSchemes.add(AuthSchemes.NTLM);
297 authSchemes.add(AuthSchemes.BASIC);
298 authSchemes.add(AuthSchemes.DIGEST);
299 }
300 return RequestConfig.custom()
301
302 .setConnectTimeout(Settings.getIntProperty("davmail.exchange.connectionTimeout", 10) * 1000)
303
304 .setSocketTimeout(Settings.getIntProperty("davmail.exchange.soTimeout", 120) * 1000)
305 .setTargetPreferredAuthSchemes(authSchemes)
306 .build();
307 }
308
309 private void parseUserName(String username) {
310 if (username != null) {
311 int pipeIndex = username.indexOf("|");
312 if (pipeIndex >= 0) {
313 userid = username.substring(0, pipeIndex);
314 userEmail = username.substring(pipeIndex + 1);
315 } else {
316 userid = username;
317 userEmail = username;
318 }
319
320 int backSlashIndex = userid.indexOf('\\');
321 if (backSlashIndex >= 0) {
322
323 domain = userid.substring(0, backSlashIndex);
324 userid = userid.substring(backSlashIndex + 1);
325 } else {
326 domain = Settings.getProperty("davmail.defaultDomain", "");
327 }
328 }
329 }
330
331
332
333
334
335
336
337 private static List<Proxy> getProxyForURI(java.net.URI uri) {
338 LOGGER.debug("get Default proxy selector");
339 ProxySelector proxySelector = ProxySelector.getDefault();
340 LOGGER.debug("getProxyForURI(" + uri + ')');
341 List<Proxy> proxies = proxySelector.select(uri);
342 LOGGER.debug("got system proxies:" + proxies);
343 return proxies;
344 }
345
346 protected static boolean isNoProxyFor(java.net.URI uri) {
347 final String noProxyFor = Settings.getProperty("davmail.noProxyFor");
348 if (noProxyFor != null) {
349 final String urihost = uri.getHost().toLowerCase();
350 final String[] domains = noProxyFor.toLowerCase().split(",\\s*");
351 for (String domain : domains) {
352 if (urihost.endsWith(domain)) {
353 return true;
354 }
355 }
356 }
357 return false;
358 }
359
360 public void startEvictorThread() {
361 DavMailIdleConnectionEvictor.addConnectionManager(connectionManager);
362 }
363
364 @Override
365 public void close() {
366 DavMailIdleConnectionEvictor.removeConnectionManager(connectionManager);
367 try {
368 httpClient.close();
369 } catch (IOException e) {
370 LOGGER.warn("Exception closing http client", e);
371 }
372 }
373
374 public static void close(HttpClientAdapter httpClientAdapter) {
375 if (httpClientAdapter != null) {
376 httpClientAdapter.close();
377 }
378 }
379
380
381
382
383
384
385
386
387
388 public CloseableHttpResponse execute(HttpRequestBase request) throws IOException {
389 return execute(request, null);
390 }
391
392
393
394
395
396
397
398
399
400
401 public CloseableHttpResponse execute(HttpRequestBase request, HttpClientContext context) throws IOException {
402
403 handleURI(request);
404
405 return httpClient.execute(request, context);
406 }
407
408
409
410
411
412
413 private void handleURI(HttpRequestBase request) {
414 URI requestURI = request.getURI();
415 if (!requestURI.isAbsolute()) {
416 request.setURI(URIUtils.resolve(uri, requestURI));
417 }
418 uri = request.getURI();
419 }
420
421 public ResponseWrapper executeFollowRedirect(PostRequest request) throws IOException {
422 ResponseWrapper responseWrapper = request;
423 LOGGER.debug(request.getMethod() + " " + request.getURI().toString());
424 LOGGER.debug(request.getParameters());
425
426 int count = 0;
427 int maxRedirect = Settings.getIntProperty("davmail.httpMaxRedirects", MAX_REDIRECTS);
428
429 executePostRequest(request);
430 URI redirectLocation = request.getRedirectLocation();
431
432 while (count++ < maxRedirect && redirectLocation != null) {
433 LOGGER.debug("Redirect " + request.getURI() + " to " + redirectLocation);
434
435 responseWrapper = new GetRequest(redirectLocation);
436 executeGetRequest((GetRequest) responseWrapper);
437 redirectLocation = ((GetRequest) responseWrapper).getRedirectLocation();
438 }
439
440 return responseWrapper;
441 }
442
443 public GetRequest executeFollowRedirect(GetRequest request) throws IOException {
444 GetRequest result = request;
445 LOGGER.debug(request.getMethod() + " " + request.getURI().toString());
446
447 int count = 0;
448 int maxRedirect = Settings.getIntProperty("davmail.httpMaxRedirects", MAX_REDIRECTS);
449
450 executeGetRequest(request);
451 URI redirectLocation = request.getRedirectLocation();
452
453 while (count++ < maxRedirect && redirectLocation != null) {
454 LOGGER.debug("Redirect " + request.getURI() + " to " + redirectLocation);
455
456 result = new GetRequest(redirectLocation);
457 executeGetRequest(result);
458 redirectLocation = result.getRedirectLocation();
459 }
460
461 return result;
462 }
463
464
465
466
467
468
469
470
471 public String executeGetRequest(GetRequest getRequest) throws IOException {
472 handleURI(getRequest);
473 String responseBodyAsString;
474 try (CloseableHttpResponse response = execute(getRequest)) {
475 responseBodyAsString = getRequest.handleResponse(response);
476 }
477 return responseBodyAsString;
478 }
479
480
481
482
483
484
485
486
487 public String executePostRequest(PostRequest postRequest) throws IOException {
488 handleURI(postRequest);
489 String responseBodyAsString;
490 try (CloseableHttpResponse response = execute(postRequest)) {
491 responseBodyAsString = postRequest.handleResponse(response);
492 }
493 return responseBodyAsString;
494 }
495
496 public JSONObject executeRestRequest(RestRequest restRequest) throws IOException {
497 handleURI(restRequest);
498 JSONObject responseBody;
499 try (CloseableHttpResponse response = execute(restRequest)) {
500 responseBody = restRequest.handleResponse(response);
501 }
502 return responseBody;
503 }
504
505
506
507
508
509
510
511
512 public MultiStatus executeDavRequest(BaseDavRequest request) throws IOException {
513 handleURI(request);
514 MultiStatus multiStatus = null;
515 try (CloseableHttpResponse response = execute(request)) {
516 request.checkSuccess(response);
517 if (response.getStatusLine().getStatusCode() == HttpStatus.SC_MULTI_STATUS) {
518 multiStatus = request.getResponseBodyAsMultiStatus(response);
519 }
520 } catch (DavException e) {
521 LOGGER.error(e.getMessage(), e);
522 throw new IOException(e.getErrorCode() + " " + e.getStatusPhrase(), e);
523 }
524 return multiStatus;
525 }
526
527
528
529
530
531
532
533
534 public MultiStatusResponse[] executeDavRequest(ExchangeDavRequest request) throws IOException {
535 handleURI(request);
536 MultiStatusResponse[] responses;
537 try (CloseableHttpResponse response = execute(request)) {
538 List<MultiStatusResponse> responseList = request.handleResponse(response);
539
540
541 responses = responseList.toArray(new MultiStatusResponse[0]);
542 }
543 return responses;
544 }
545
546
547
548
549
550
551
552
553
554
555
556 public MultiStatusResponse[] executeSearchRequest(String path, String searchStatement, int maxCount) throws IOException {
557 ExchangeSearchRequest searchRequest = new ExchangeSearchRequest(path, searchStatement);
558 if (maxCount > 0) {
559 searchRequest.setHeader("Range", "rows=0-" + (maxCount - 1));
560 }
561 return executeDavRequest(searchRequest);
562 }
563
564 public static boolean isRedirect(HttpResponse response) {
565 return isRedirect(response.getStatusLine().getStatusCode());
566 }
567
568
569
570
571
572
573
574 public static boolean isRedirect(int status) {
575 return status == HttpStatus.SC_MOVED_PERMANENTLY
576 || status == HttpStatus.SC_MOVED_TEMPORARILY
577 || status == HttpStatus.SC_SEE_OTHER
578 || status == HttpStatus.SC_TEMPORARY_REDIRECT;
579 }
580
581
582
583
584
585
586
587 public static URI getRedirectLocation(HttpResponse response) {
588 Header location = response.getFirstHeader("Location");
589 if (isRedirect(response.getStatusLine().getStatusCode()) && location != null) {
590 return URI.create(location.getValue());
591 }
592 return null;
593 }
594
595 public void setCredentials(String username, String password) {
596 parseUserName(username);
597 if (userid != null && password != null) {
598 LOGGER.debug("Creating NTCredentials for user " + userid + " workstation " + WORKSTATION_NAME + " domain " + domain);
599 NTCredentials credentials = new NTCredentials(userid, password, WORKSTATION_NAME, domain);
600 provider.setCredentials(AuthScope.ANY, credentials);
601 }
602 }
603
604 public List<Cookie> getCookies() {
605 return cookieStore.getCookies();
606 }
607
608 public void addCookie(Cookie cookie) {
609 cookieStore.addCookie(cookie);
610 }
611
612 public String getUserAgent() {
613 return Settings.getUserAgent();
614 }
615
616 public static HttpResponseException buildHttpResponseException(HttpRequestBase request, HttpResponse response) {
617 return buildHttpResponseException(request, response.getStatusLine());
618 }
619
620
621
622
623
624
625
626 public static HttpResponseException buildHttpResponseException(HttpRequestBase method, StatusLine statusLine) {
627 int status = statusLine.getStatusCode();
628 StringBuilder message = new StringBuilder();
629 message.append(status).append(' ').append(statusLine.getReasonPhrase());
630 message.append(" at ").append(method.getURI());
631 if (method instanceof HttpCopy || method instanceof HttpMove) {
632 message.append(" to ").append(method.getFirstHeader("Destination"));
633 }
634
635 if (status == 440) {
636 return new LoginTimeoutException(message.toString());
637 } else if (status == HttpStatus.SC_FORBIDDEN) {
638 return new HttpForbiddenException(message.toString());
639 } else if (status == HttpStatus.SC_NOT_FOUND) {
640 return new HttpNotFoundException(message.toString());
641 } else if (status == HttpStatus.SC_PRECONDITION_FAILED) {
642 return new HttpPreconditionFailedException(message.toString());
643 } else if (status == HttpStatus.SC_INTERNAL_SERVER_ERROR) {
644 return new HttpServerErrorException(message.toString());
645 } else {
646 return new HttpResponseException(status, message.toString());
647 }
648 }
649
650 }