001    /*
002     * The MIT License
003     * Copyright (c) 2012 Microsoft Corporation
004     *
005     * Permission is hereby granted, free of charge, to any person obtaining a copy
006     * of this software and associated documentation files (the "Software"), to deal
007     * in the Software without restriction, including without limitation the rights
008     * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
009     * copies of the Software, and to permit persons to whom the Software is
010     * furnished to do so, subject to the following conditions:
011     *
012     * The above copyright notice and this permission notice shall be included in
013     * all copies or substantial portions of the Software.
014     *
015     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
016     * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
017     * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
018     * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
019     * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
020     * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
021     * THE SOFTWARE.
022     */
023    
024    package microsoft.exchange.webservices.data.core.request;
025    
026    import microsoft.exchange.webservices.data.core.WebProxy;
027    import microsoft.exchange.webservices.data.core.exception.http.EWSHttpException;
028    import org.apache.http.Header;
029    import org.apache.http.HttpHost;
030    import org.apache.http.auth.AuthScope;
031    import org.apache.http.auth.NTCredentials;
032    import org.apache.http.client.CredentialsProvider;
033    import org.apache.http.client.config.AuthSchemes;
034    import org.apache.http.client.config.RequestConfig;
035    import org.apache.http.client.methods.CloseableHttpResponse;
036    import org.apache.http.client.methods.HttpPost;
037    import org.apache.http.client.protocol.HttpClientContext;
038    import org.apache.http.impl.client.BasicCredentialsProvider;
039    import org.apache.http.impl.client.CloseableHttpClient;
040    import org.apache.http.util.EntityUtils;
041    
042    import java.io.BufferedInputStream;
043    import java.io.ByteArrayOutputStream;
044    import java.io.IOException;
045    import java.io.InputStream;
046    import java.io.OutputStream;
047    import java.util.Arrays;
048    import java.util.HashMap;
049    import java.util.Map;
050    
051    
052    /**
053     * HttpClientWebRequest is used for making request to the server through NTLM Authentication by using Apache
054     * HttpClient 3.1 and JCIFS Library.
055     */
056    public class HttpClientWebRequest extends HttpWebRequest {
057    
058      /**
059       * The Http Method.
060       */
061      private HttpPost httpPost = null;
062      private CloseableHttpResponse response = null;
063    
064      private final CloseableHttpClient httpClient;
065      private final HttpClientContext httpContext;
066    
067    
068      /**
069       * Instantiates a new http native web request.
070       */
071      public HttpClientWebRequest(CloseableHttpClient httpClient, HttpClientContext httpContext) {
072        this.httpClient = httpClient;
073        this.httpContext = httpContext;
074      }
075    
076      /**
077       * Releases the connection by Closing.
078       */
079      @Override
080      public void close() throws IOException {
081        // First check if we can close the response, by consuming the complete response
082        // This releases the connection but keeps it alive for future request
083        // If that is not possible, we simply cleanup the whole connection
084        if (response != null && response.getEntity() != null) {
085          EntityUtils.consume(response.getEntity());
086        } else if (httpPost != null) {
087          httpPost.releaseConnection();
088        }
089    
090        // We set httpPost to null to prevent the connection from being closed again by an accidental
091        // second call to close()
092        // The response is kept, in case something in the library still wants to read something from it,
093        // like response code or headers
094        httpPost = null;
095      }
096    
097      /**
098       * Prepares the request by setting appropriate headers, authentication, timeouts, etc.
099       */
100      @Override
101      public void prepareConnection() {
102        httpPost = new HttpPost(getUrl().toString());
103    
104        // Populate headers.
105        httpPost.addHeader("Content-type", getContentType());
106        httpPost.addHeader("User-Agent", getUserAgent());
107        httpPost.addHeader("Accept", getAccept());
108        httpPost.addHeader("Keep-Alive", "300");
109        httpPost.addHeader("Connection", "Keep-Alive");
110    
111        if (isAcceptGzipEncoding()) {
112          httpPost.addHeader("Accept-Encoding", "gzip,deflate");
113        }
114    
115        if (getHeaders() != null) {
116          for (Map.Entry<String, String> httpHeader : getHeaders().entrySet()) {
117            httpPost.addHeader(httpHeader.getKey(), httpHeader.getValue());
118          }
119        }
120    
121        // Build request configuration.
122        // Disable Kerberos in the preferred auth schemes - EWS should usually allow NTLM or Basic auth
123        RequestConfig.Builder
124            requestConfigBuilder =
125            RequestConfig.custom().setAuthenticationEnabled(true).setConnectionRequestTimeout(getTimeout())
126                .setConnectTimeout(getTimeout()).setRedirectsEnabled(isAllowAutoRedirect())
127                .setSocketTimeout(getTimeout())
128                .setTargetPreferredAuthSchemes(Arrays.asList(AuthSchemes.NTLM, AuthSchemes.BASIC))
129                .setProxyPreferredAuthSchemes(Arrays.asList(AuthSchemes.NTLM, AuthSchemes.BASIC));
130    
131        CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
132    
133        // Add proxy credential if necessary.
134        WebProxy proxy = getProxy();
135        if (proxy != null) {
136          HttpHost proxyHost = new HttpHost(proxy.getHost(), proxy.getPort());
137          requestConfigBuilder.setProxy(proxyHost);
138    
139          if (proxy.hasCredentials()) {
140            NTCredentials
141                proxyCredentials =
142                new NTCredentials(proxy.getCredentials().getUsername(), proxy.getCredentials().getPassword(), "",
143                                  proxy.getCredentials().getDomain());
144    
145            credentialsProvider.setCredentials(new AuthScope(proxyHost), proxyCredentials);
146          }
147        }
148    
149        // Add web service credential if necessary.
150        if (isAllowAuthentication() && getUsername() != null) {
151          NTCredentials webServiceCredentials = new NTCredentials(getUsername(), getPassword(), "", getDomain());
152          credentialsProvider.setCredentials(new AuthScope(AuthScope.ANY), webServiceCredentials);
153        }
154    
155        httpContext.setCredentialsProvider(credentialsProvider);
156    
157        httpPost.setConfig(requestConfigBuilder.build());
158      }
159    
160      /**
161       * Gets the input stream.
162       *
163       * @return the input stream
164       * @throws EWSHttpException the EWS http exception
165       */
166      @Override
167      public InputStream getInputStream() throws EWSHttpException, IOException {
168        throwIfResponseIsNull();
169        BufferedInputStream bufferedInputStream = null;
170        try {
171          bufferedInputStream = new BufferedInputStream(response.getEntity().getContent());
172        } catch (IOException e) {
173          throw new EWSHttpException("Connection Error " + e);
174        }
175        return bufferedInputStream;
176      }
177    
178      /**
179       * Gets the error stream.
180       *
181       * @return the error stream
182       * @throws EWSHttpException the EWS http exception
183       */
184      @Override
185      public InputStream getErrorStream() throws EWSHttpException {
186        throwIfResponseIsNull();
187        BufferedInputStream bufferedInputStream = null;
188        try {
189          bufferedInputStream = new BufferedInputStream(response.getEntity().getContent());
190        } catch (Exception e) {
191          throw new EWSHttpException("Connection Error " + e);
192        }
193        return bufferedInputStream;
194      }
195    
196      /**
197       * Gets the output stream.
198       *
199       * @return the output stream
200       * @throws EWSHttpException the EWS http exception
201       */
202      @Override
203      public OutputStream getOutputStream() throws EWSHttpException {
204        OutputStream os = null;
205        throwIfRequestIsNull();
206        os = new ByteArrayOutputStream();
207    
208        httpPost.setEntity(new ByteArrayOSRequestEntity(os));
209        return os;
210      }
211    
212      /**
213       * Gets the response headers.
214       *
215       * @return the response headers
216       * @throws EWSHttpException the EWS http exception
217       */
218      @Override
219      public Map<String, String> getResponseHeaders() throws EWSHttpException {
220        throwIfResponseIsNull();
221        Map<String, String> map = new HashMap<String, String>();
222    
223        Header[] hM = response.getAllHeaders();
224        for (Header header : hM) {
225          // RFC2109: Servers may return multiple Set-Cookie headers
226          // Need to append the cookies before they are added to the map
227          if (header.getName().equals("Set-Cookie")) {
228            String cookieValue = "";
229            if (map.containsKey("Set-Cookie")) {
230              cookieValue += map.get("Set-Cookie");
231              cookieValue += ",";
232            }
233            cookieValue += header.getValue();
234            map.put("Set-Cookie", cookieValue);
235          } else {
236            map.put(header.getName(), header.getValue());
237          }
238        }
239    
240        return map;
241      }
242    
243      /*
244       * (non-Javadoc)
245       *
246       * @see
247       * microsoft.exchange.webservices.HttpWebRequest#getResponseHeaderField(
248       * java.lang.String)
249       */
250      @Override
251      public String getResponseHeaderField(String headerName) throws EWSHttpException {
252        throwIfResponseIsNull();
253        Header hM = response.getFirstHeader(headerName);
254        return hM != null ? hM.getValue() : null;
255      }
256    
257      /**
258       * Gets the content encoding.
259       *
260       * @return the content encoding
261       * @throws EWSHttpException the EWS http exception
262       */
263      @Override
264      public String getContentEncoding() throws EWSHttpException {
265        throwIfResponseIsNull();
266        return response.getFirstHeader("content-encoding") != null ? response.getFirstHeader("content-encoding")
267            .getValue() : null;
268      }
269    
270      /**
271       * Gets the response content type.
272       *
273       * @return the response content type
274       * @throws EWSHttpException the EWS http exception
275       */
276      @Override
277      public String getResponseContentType() throws EWSHttpException {
278        throwIfResponseIsNull();
279        return response.getFirstHeader("Content-type") != null ? response.getFirstHeader("Content-type")
280            .getValue() : null;
281      }
282    
283      /**
284       * Executes Request by sending request xml data to server.
285       *
286       * @throws EWSHttpException    the EWS http exception
287       * @throws java.io.IOException the IO Exception
288       */
289      @Override
290      public int executeRequest() throws EWSHttpException, IOException {
291        throwIfRequestIsNull();
292        response = httpClient.execute(httpPost, httpContext);
293        return response.getStatusLine().getStatusCode(); // ?? don't know what is wanted in return
294      }
295    
296      /**
297       * Gets the response code.
298       *
299       * @return the response code
300       * @throws EWSHttpException the EWS http exception
301       */
302      @Override
303      public int getResponseCode() throws EWSHttpException {
304        throwIfResponseIsNull();
305        return response.getStatusLine().getStatusCode();
306      }
307    
308      /**
309       * Gets the response message.
310       *
311       * @return the response message
312       * @throws EWSHttpException the EWS http exception
313       */
314      public String getResponseText() throws EWSHttpException {
315        throwIfResponseIsNull();
316        return response.getStatusLine().getReasonPhrase();
317      }
318    
319      /**
320       * Throw if conn is null.
321       *
322       * @throws EWSHttpException the EWS http exception
323       */
324      private void throwIfRequestIsNull() throws EWSHttpException {
325        if (null == httpPost) {
326          throw new EWSHttpException("Connection not established");
327        }
328      }
329    
330      private void throwIfResponseIsNull() throws EWSHttpException {
331        if (null == response) {
332          throw new EWSHttpException("Connection not established");
333        }
334      }
335    
336      /**
337       * Gets the request property.
338       *
339       * @return the request property
340       * @throws EWSHttpException the EWS http exception
341       */
342      public Map<String, String> getRequestProperty() throws EWSHttpException {
343        throwIfRequestIsNull();
344        Map<String, String> map = new HashMap<String, String>();
345    
346        Header[] hM = httpPost.getAllHeaders();
347        for (Header header : hM) {
348          map.put(header.getName(), header.getValue());
349        }
350        return map;
351      }
352    }