View Javadoc
1   /*
2    * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
3    * Copyright (C) 2010  Mickael Guessant
4    *
5    * This program is free software; you can redistribute it and/or
6    * modify it under the terms of the GNU General Public License
7    * as published by the Free Software Foundation; either version 2
8    * of the License, or (at your option) any later version.
9    *
10   * This program is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with this program; if not, write to the Free Software
17   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
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          // disable Client-initiated TLS renegotiation
85          System.setProperty("jdk.tls.rejectClientInitiatedRenegotiation", "true");
86          // force strong ephemeral Diffie-Hellman parameter
87          System.setProperty("jdk.tls.ephemeralDHKeySize", "2048");
88  
89          Security.setProperty("ssl.SocketFactory.provider", "davmail.http.DavGatewaySSLSocketFactory");
90  
91          // DavMail is Kerberos configuration provider
92          Security.setProperty("login.configuration.provider", "davmail.http.KerberosLoginConfiguration");
93  
94          // re-enable basic proxy authentication on Java >= 1.8.111
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             // ignore
109         }
110 
111         // set system property *before* calling ProxySelector.getDefault()
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      * Test if the response is gzip encoded
120      *
121      * @param response http response
122      * @return true if response is gzip encoded
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             //LOGGER.debug("Add cookie " + cookie);
136             super.addCookie(cookie);
137         }
138     };
139     // current URI
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         // init current uri
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                 // httpClient is not shared between clients, do not track connection state
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             // get proxy for url from system settings
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                 // we may still need authentication credentials
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                 // detect ntlm authentication (windows domain name in username)
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      * Get current uri host
255      *
256      * @return current host
257      */
258     public String getHost() {
259         return uri.getHost();
260     }
261 
262     /**
263      * Force current uri.
264      *
265      * @param uri new uri
266      */
267     public void setUri(URI uri) {
268         this.uri = uri;
269     }
270 
271     /**
272      * Current uri.
273      *
274      * @return current uri
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                 // socket connect timeout
311                 .setConnectTimeout(Settings.getIntProperty("davmail.exchange.connectionTimeout", 10) * 1000)
312                 // inactivity timeout
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             // separate domain name
329             int backSlashIndex = userid.indexOf('\\');
330             if (backSlashIndex >= 0) {
331                 // separate domain from username in credentials
332                 domain = userid.substring(0, backSlashIndex);
333                 userid = userid.substring(backSlashIndex + 1);
334             } else if (userid.contains("@")) {
335                 // no need for domain name with userPrincipalName
336                 domain = "";
337             } else {
338                 domain = Settings.getProperty("davmail.defaultDomain", "");
339             }
340         }
341     }
342 
343     /**
344      * Retrieve Proxy Selector
345      *
346      * @param uri target uri
347      * @return proxy selector
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      * Execute request, do not follow redirects.
394      * if request is an instance of ResponseHandler, process and close response
395      *
396      * @param request Http request
397      * @return Http response
398      * @throws IOException on error
399      */
400     public CloseableHttpResponse execute(HttpRequestBase request) throws IOException {
401         return execute(request, null);
402     }
403 
404     /**
405      * Execute request, do not follow redirects.
406      * if request is an instance of ResponseHandler, process and close response
407      *
408      * @param request Http request
409      * @param context Http request context
410      * @return Http response
411      * @throws IOException on error
412      */
413     public CloseableHttpResponse execute(HttpRequestBase request, HttpClientContext context) throws IOException {
414         // make sure request path is absolute
415         handleURI(request);
416         // execute request and return response
417         return httpClient.execute(request, context);
418     }
419 
420     /**
421      * fix relative uri and update current uri.
422      *
423      * @param request http request
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             // replace uri with target location
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             // replace uri with target location
468             result = new GetRequest(redirectLocation);
469             executeGetRequest(result);
470             redirectLocation = result.getRedirectLocation();
471         }
472 
473         return result;
474     }
475 
476     /**
477      * Execute get request and return response body as string.
478      *
479      * @param getRequest get request
480      * @return response body
481      * @throws IOException on error
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      * Execute post request and return response body as string.
494      *
495      * @param postRequest post request
496      * @return response body
497      * @throws IOException on error
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      * Execute WebDav request
519      *
520      * @param request WebDav request
521      * @return multistatus response
522      * @throws IOException on error
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      * Execute Exchange WebDav request
541      *
542      * @param request WebDav request
543      * @return multistatus response
544      * @throws IOException on error
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             // TODO check error handling
552             //request.checkSuccess(response);
553             responses = responseList.toArray(new MultiStatusResponse[0]);
554         }
555         return responses;
556     }
557 
558 
559     /**
560      * Execute webdav search method.
561      *
562      * @param path            <i>encoded</i> searched folder path
563      * @param searchStatement (SQL like) search statement
564      * @param maxCount        max item count
565      * @return Responses enumeration
566      * @throws IOException on error
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      * Check if status is a redirect (various 30x values).
582      *
583      * @param status Http status
584      * @return true if status is a redirect
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      * Get redirect location from header.
595      *
596      * @param response Http response
597      * @return URI target location
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      * Build Http Exception from method status
647      *
648      * @param method Http Method
649      * @return Http Exception
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         // 440 means forbidden on Exchange
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 }