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.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", "TLSv1.3"};
76      static final Registry<ConnectionSocketFactory> SCHEME_REGISTRY;
77      static String WORKSTATION_NAME = "UNKNOWN";
78      static final int MAX_REDIRECTS = 10;
79  
80      static {
81          // disable Client-initiated TLS renegotiation
82          System.setProperty("jdk.tls.rejectClientInitiatedRenegotiation", "true");
83          // force strong ephemeral Diffie-Hellman parameter
84          System.setProperty("jdk.tls.ephemeralDHKeySize", "2048");
85  
86          Security.setProperty("ssl.SocketFactory.provider", "davmail.http.DavGatewaySSLSocketFactory");
87  
88          // DavMail is Kerberos configuration provider
89          Security.setProperty("login.configuration.provider", "davmail.http.KerberosLoginConfiguration");
90  
91          // reenable basic proxy authentication on Java >= 1.8.111
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 (Exception e) {
105             // ignore
106         }
107 
108         // set system property *before* calling ProxySelector.getDefault()
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      * Test if the response is gzip encoded
117      *
118      * @param response http response
119      * @return true if response is gzip encoded
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     // current URI
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         // init current uri
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                 // httpClient is not shared between clients, do not track connection state
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             // get proxy for url from system settings
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                 // we may still need authentication credentials
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() && (proxyUser != null && !proxyUser.isEmpty())) {
227 
228                 AuthScope authScope = new AuthScope(proxyHost, proxyPort, AuthScope.ANY_REALM);
229                 if (provider == null) {
230                     provider = new BasicCredentialsProvider();
231                 }
232 
233                 // detect ntlm authentication (windows domain name in username)
234                 int backslashIndex = proxyUser.indexOf('\\');
235                 if (backslashIndex > 0) {
236                     provider.setCredentials(authScope, new NTCredentials(proxyUser.substring(backslashIndex + 1),
237                             proxyPassword, WORKSTATION_NAME,
238                             proxyUser.substring(0, backslashIndex)));
239                 } else {
240                     provider.setCredentials(authScope, new NTCredentials(proxyUser, proxyPassword, WORKSTATION_NAME, ""));
241                 }
242 
243         }
244 
245         clientBuilder.setDefaultCredentialsProvider(provider);
246 
247         httpClient = clientBuilder.build();
248     }
249 
250     /**
251      * Get current uri host
252      *
253      * @return current host
254      */
255     public String getHost() {
256         return uri.getHost();
257     }
258 
259     /**
260      * Force current uri.
261      *
262      * @param uri new uri
263      */
264     public void setUri(URI uri) {
265         this.uri = uri;
266     }
267 
268     /**
269      * Current uri.
270      *
271      * @return current uri
272      */
273     public URI getUri() {
274         return uri;
275     }
276 
277     private Registry<AuthSchemeProvider> getAuthSchemeRegistry() {
278         final RegistryBuilder<AuthSchemeProvider> registryBuilder = RegistryBuilder.create();
279         registryBuilder.register(AuthSchemes.NTLM, new DavMailNTLMSchemeFactory())
280                 .register(AuthSchemes.BASIC, new BasicSchemeFactory())
281                 .register(AuthSchemes.DIGEST, new DigestSchemeFactory());
282         if (Settings.getBooleanProperty("davmail.enableKerberos")) {
283             registryBuilder.register(AuthSchemes.SPNEGO, new DavMailSPNegoSchemeFactory());
284         }
285 
286         return registryBuilder.build();
287     }
288 
289     private RequestConfig getRequestConfig() {
290         HashSet<String> authSchemes = new HashSet<>();
291         if (Settings.getBooleanProperty("davmail.enableKerberos")) {
292             authSchemes.add(AuthSchemes.SPNEGO);
293             authSchemes.add(AuthSchemes.KERBEROS);
294         } else {
295             authSchemes.add(AuthSchemes.NTLM);
296             authSchemes.add(AuthSchemes.BASIC);
297             authSchemes.add(AuthSchemes.DIGEST);
298         }
299         return RequestConfig.custom()
300                 // socket connect timeout
301                 .setConnectTimeout(Settings.getIntProperty("davmail.exchange.connectionTimeout", 10) * 1000)
302                 // inactivity timeout
303                 .setSocketTimeout(Settings.getIntProperty("davmail.exchange.soTimeout", 120) * 1000)
304                 .setTargetPreferredAuthSchemes(authSchemes)
305                 .build();
306     }
307 
308     private void parseUserName(String username) {
309         if (username != null) {
310             int pipeIndex = username.indexOf("|");
311             if (pipeIndex >= 0) {
312                 userid = username.substring(0, pipeIndex);
313                 userEmail = username.substring(pipeIndex + 1);
314             } else {
315                 userid = username;
316                 userEmail = username;
317             }
318             // separate domain name
319             int backSlashIndex = userid.indexOf('\\');
320             if (backSlashIndex >= 0) {
321                 // separate domain from username in credentials
322                 domain = userid.substring(0, backSlashIndex);
323                 userid = userid.substring(backSlashIndex + 1);
324             } else {
325                 domain = Settings.getProperty("davmail.defaultDomain", "");
326             }
327         }
328     }
329 
330     /**
331      * Retrieve Proxy Selector
332      *
333      * @param uri target uri
334      * @return proxy selector
335      */
336     private static List<Proxy> getProxyForURI(java.net.URI uri) {
337         LOGGER.debug("get Default proxy selector");
338         ProxySelector proxySelector = ProxySelector.getDefault();
339         LOGGER.debug("getProxyForURI(" + uri + ')');
340         List<Proxy> proxies = proxySelector.select(uri);
341         LOGGER.debug("got system proxies:" + proxies);
342         return proxies;
343     }
344 
345     protected static boolean isNoProxyFor(java.net.URI uri) {
346         final String noProxyFor = Settings.getProperty("davmail.noProxyFor");
347         if (noProxyFor != null) {
348             final String uriHost = uri.getHost().toLowerCase();
349             final String[] domains = noProxyFor.toLowerCase().split(",\\s*");
350             for (String domain : domains) {
351                 if (uriHost.endsWith(domain)) {
352                     return true;
353                 }
354             }
355         }
356         return false;
357     }
358 
359     public void startEvictorThread() {
360         DavMailIdleConnectionEvictor.addConnectionManager(connectionManager);
361     }
362 
363     @Override
364     public void close() {
365         DavMailIdleConnectionEvictor.removeConnectionManager(connectionManager);
366         try {
367             httpClient.close();
368         } catch (IOException e) {
369             LOGGER.warn("Exception closing http client", e);
370         }
371     }
372 
373     public static void close(HttpClientAdapter httpClientAdapter) {
374         if (httpClientAdapter != null) {
375             httpClientAdapter.close();
376         }
377     }
378 
379     /**
380      * Execute request, do not follow redirects.
381      * if request is an instance of ResponseHandler, process and close response
382      *
383      * @param request Http request
384      * @return Http response
385      * @throws IOException on error
386      */
387     public CloseableHttpResponse execute(HttpRequestBase request) throws IOException {
388         return execute(request, null);
389     }
390 
391     /**
392      * Execute request, do not follow redirects.
393      * if request is an instance of ResponseHandler, process and close response
394      *
395      * @param request Http request
396      * @param context Http request context
397      * @return Http response
398      * @throws IOException on error
399      */
400     public CloseableHttpResponse execute(HttpRequestBase request, HttpClientContext context) throws IOException {
401         // make sure request path is absolute
402         handleURI(request);
403         // execute request and return response
404         return httpClient.execute(request, context);
405     }
406 
407     /**
408      * fix relative uri and update current uri.
409      *
410      * @param request http request
411      */
412     private void handleURI(HttpRequestBase request) {
413         URI requestURI = request.getURI();
414         if (!requestURI.isAbsolute()) {
415             request.setURI(URIUtils.resolve(uri, requestURI));
416         }
417         uri = request.getURI();
418     }
419 
420     public ResponseWrapper executeFollowRedirect(PostRequest request) throws IOException {
421         ResponseWrapper responseWrapper = request;
422         LOGGER.debug(request.getMethod() + " " + request.getURI().toString());
423         LOGGER.debug(request.getParameters());
424 
425         int count = 0;
426         int maxRedirect = Settings.getIntProperty("davmail.httpMaxRedirects", MAX_REDIRECTS);
427 
428         executePostRequest(request);
429         URI redirectLocation = request.getRedirectLocation();
430 
431         while (count++ < maxRedirect && redirectLocation != null) {
432             LOGGER.debug("Redirect " + request.getURI() + " to " + redirectLocation);
433             // replace uri with target location
434             responseWrapper = new GetRequest(redirectLocation);
435             executeGetRequest((GetRequest) responseWrapper);
436             redirectLocation = ((GetRequest) responseWrapper).getRedirectLocation();
437         }
438 
439         return responseWrapper;
440     }
441 
442     public GetRequest executeFollowRedirect(GetRequest request) throws IOException {
443         GetRequest result = request;
444         LOGGER.debug(request.getMethod() + " " + request.getURI().toString());
445 
446         int count = 0;
447         int maxRedirect = Settings.getIntProperty("davmail.httpMaxRedirects", MAX_REDIRECTS);
448 
449         executeGetRequest(request);
450         URI redirectLocation = request.getRedirectLocation();
451 
452         while (count++ < maxRedirect && redirectLocation != null) {
453             LOGGER.debug("Redirect " + request.getURI() + " to " + redirectLocation);
454             // replace uri with target location
455             result = new GetRequest(redirectLocation);
456             executeGetRequest(result);
457             redirectLocation = result.getRedirectLocation();
458         }
459 
460         return result;
461     }
462 
463     /**
464      * Execute get request and return response body as string.
465      *
466      * @param getRequest get request
467      * @return response body
468      * @throws IOException on error
469      */
470     public String executeGetRequest(GetRequest getRequest) throws IOException {
471         handleURI(getRequest);
472         String responseBodyAsString;
473         try (CloseableHttpResponse response = execute(getRequest)) {
474             responseBodyAsString = getRequest.handleResponse(response);
475         }
476         return responseBodyAsString;
477     }
478 
479     /**
480      * Execute post request and return response body as string.
481      *
482      * @param postRequest post request
483      * @return response body
484      * @throws IOException on error
485      */
486     public String executePostRequest(PostRequest postRequest) throws IOException {
487         handleURI(postRequest);
488         String responseBodyAsString;
489         try (CloseableHttpResponse response = execute(postRequest)) {
490             responseBodyAsString = postRequest.handleResponse(response);
491         }
492         return responseBodyAsString;
493     }
494 
495     public JSONObject executeRestRequest(RestRequest restRequest) throws IOException {
496         handleURI(restRequest);
497         JSONObject responseBody;
498         try (CloseableHttpResponse response = execute(restRequest)) {
499             responseBody = restRequest.handleResponse(response);
500         }
501         return responseBody;
502     }
503 
504     /**
505      * Execute WebDav request
506      *
507      * @param request WebDav request
508      * @return multistatus response
509      * @throws IOException on error
510      */
511     public MultiStatus executeDavRequest(BaseDavRequest request) throws IOException {
512         handleURI(request);
513         MultiStatus multiStatus = null;
514         try (CloseableHttpResponse response = execute(request)) {
515             request.checkSuccess(response);
516             if (response.getStatusLine().getStatusCode() == HttpStatus.SC_MULTI_STATUS) {
517                 multiStatus = request.getResponseBodyAsMultiStatus(response);
518             }
519         } catch (DavException e) {
520             LOGGER.error(e.getMessage(), e);
521             throw new IOException(e.getErrorCode() + " " + e.getStatusPhrase(), e);
522         }
523         return multiStatus;
524     }
525 
526     /**
527      * Execute Exchange WebDav request
528      *
529      * @param request WebDav request
530      * @return multistatus response
531      * @throws IOException on error
532      */
533     public MultiStatusResponse[] executeDavRequest(ExchangeDavRequest request) throws IOException {
534         handleURI(request);
535         MultiStatusResponse[] responses;
536         try (CloseableHttpResponse response = execute(request)) {
537             List<MultiStatusResponse> responseList = request.handleResponse(response);
538             // TODO check error handling
539             //request.checkSuccess(response);
540             responses = responseList.toArray(new MultiStatusResponse[0]);
541         }
542         return responses;
543     }
544 
545 
546     /**
547      * Execute webdav search method.
548      *
549      * @param path            <i>encoded</i> searched folder path
550      * @param searchStatement (SQL like) search statement
551      * @param maxCount        max item count
552      * @return Responses enumeration
553      * @throws IOException on error
554      */
555     public MultiStatusResponse[] executeSearchRequest(String path, String searchStatement, int maxCount) throws IOException {
556         ExchangeSearchRequest searchRequest = new ExchangeSearchRequest(path, searchStatement);
557         if (maxCount > 0) {
558             searchRequest.setHeader("Range", "rows=0-" + (maxCount - 1));
559         }
560         return executeDavRequest(searchRequest);
561     }
562 
563     public static boolean isRedirect(HttpResponse response) {
564         return isRedirect(response.getStatusLine().getStatusCode());
565     }
566 
567     /**
568      * Check if status is a redirect (various 30x values).
569      *
570      * @param status Http status
571      * @return true if status is a redirect
572      */
573     public static boolean isRedirect(int status) {
574         return status == HttpStatus.SC_MOVED_PERMANENTLY
575                 || status == HttpStatus.SC_MOVED_TEMPORARILY
576                 || status == HttpStatus.SC_SEE_OTHER
577                 || status == HttpStatus.SC_TEMPORARY_REDIRECT;
578     }
579 
580     /**
581      * Get redirect location from header.
582      *
583      * @param response Http response
584      * @return URI target location
585      */
586     public static URI getRedirectLocation(HttpResponse response) {
587         Header location = response.getFirstHeader("Location");
588         if (isRedirect(response.getStatusLine().getStatusCode()) && location != null) {
589             return URI.create(location.getValue());
590         }
591         return null;
592     }
593 
594     public void setCredentials(String username, String password) {
595         parseUserName(username);
596         if (userid != null && password != null) {
597             LOGGER.debug("Creating NTCredentials for user " + userid + " workstation " + WORKSTATION_NAME + " domain " + domain);
598             NTCredentials credentials = new NTCredentials(userid, password, WORKSTATION_NAME, domain);
599             provider.setCredentials(AuthScope.ANY, credentials);
600         }
601     }
602 
603     public List<Cookie> getCookies() {
604         return cookieStore.getCookies();
605     }
606 
607     public void addCookie(Cookie cookie) {
608         cookieStore.addCookie(cookie);
609     }
610 
611     public String getUserAgent() {
612         return Settings.getUserAgent();
613     }
614 
615     public static HttpResponseException buildHttpResponseException(HttpRequestBase request, HttpResponse response) {
616         return buildHttpResponseException(request, response.getStatusLine());
617     }
618 
619     /**
620      * Build Http Exception from method status
621      *
622      * @param method Http Method
623      * @return Http Exception
624      */
625     public static HttpResponseException buildHttpResponseException(HttpRequestBase method, StatusLine statusLine) {
626         int status = statusLine.getStatusCode();
627         StringBuilder message = new StringBuilder();
628         message.append(status).append(' ').append(statusLine.getReasonPhrase());
629         message.append(" at ").append(method.getURI());
630         if (method instanceof HttpCopy || method instanceof HttpMove) {
631             message.append(" to ").append(method.getFirstHeader("Destination"));
632         }
633         // 440 means forbidden on Exchange
634         if (status == 440) {
635             return new LoginTimeoutException(message.toString());
636         } else if (status == HttpStatus.SC_FORBIDDEN) {
637             return new HttpForbiddenException(message.toString());
638         } else if (status == HttpStatus.SC_NOT_FOUND) {
639             return new HttpNotFoundException(message.toString());
640         } else if (status == HttpStatus.SC_PRECONDITION_FAILED) {
641             return new HttpPreconditionFailedException(message.toString());
642         } else if (status == HttpStatus.SC_INTERNAL_SERVER_ERROR) {
643             return new HttpServerErrorException(message.toString());
644         } else {
645             return new HttpResponseException(status, message.toString());
646         }
647     }
648 
649 }