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
024package microsoft.exchange.webservices.data.core.request;
025
026import microsoft.exchange.webservices.data.core.WebProxy;
027import microsoft.exchange.webservices.data.core.exception.http.EWSHttpException;
028import org.apache.http.Header;
029import org.apache.http.HttpHost;
030import org.apache.http.auth.AuthScope;
031import org.apache.http.auth.NTCredentials;
032import org.apache.http.client.CredentialsProvider;
033import org.apache.http.client.config.AuthSchemes;
034import org.apache.http.client.config.RequestConfig;
035import org.apache.http.client.methods.CloseableHttpResponse;
036import org.apache.http.client.methods.HttpPost;
037import org.apache.http.client.protocol.HttpClientContext;
038import org.apache.http.impl.client.BasicCredentialsProvider;
039import org.apache.http.impl.client.CloseableHttpClient;
040import org.apache.http.util.EntityUtils;
041
042import java.io.BufferedInputStream;
043import java.io.ByteArrayOutputStream;
044import java.io.IOException;
045import java.io.InputStream;
046import java.io.OutputStream;
047import java.util.Arrays;
048import java.util.HashMap;
049import 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 */
056public 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    if (httpContext.getCredentialsProvider() == null) {
132      CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
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
158
159    httpPost.setConfig(requestConfigBuilder.build());
160  }
161
162  /**
163   * Gets the input stream.
164   *
165   * @return the input stream
166   * @throws EWSHttpException the EWS http exception
167   */
168  @Override
169  public InputStream getInputStream() throws EWSHttpException, IOException {
170    throwIfResponseIsNull();
171    BufferedInputStream bufferedInputStream = null;
172    try {
173      bufferedInputStream = new BufferedInputStream(response.getEntity().getContent());
174    } catch (IOException e) {
175      throw new EWSHttpException("Connection Error " + e);
176    }
177    return bufferedInputStream;
178  }
179
180  /**
181   * Gets the error stream.
182   *
183   * @return the error stream
184   * @throws EWSHttpException the EWS http exception
185   */
186  @Override
187  public InputStream getErrorStream() throws EWSHttpException {
188    throwIfResponseIsNull();
189    BufferedInputStream bufferedInputStream = null;
190    try {
191      bufferedInputStream = new BufferedInputStream(response.getEntity().getContent());
192    } catch (Exception e) {
193      throw new EWSHttpException("Connection Error " + e);
194    }
195    return bufferedInputStream;
196  }
197
198  /**
199   * Gets the output stream.
200   *
201   * @return the output stream
202   * @throws EWSHttpException the EWS http exception
203   */
204  @Override
205  public OutputStream getOutputStream() throws EWSHttpException {
206    OutputStream os = null;
207    throwIfRequestIsNull();
208    os = new ByteArrayOutputStream();
209
210    httpPost.setEntity(new ByteArrayOSRequestEntity(os));
211    return os;
212  }
213
214  /**
215   * Gets the response headers.
216   *
217   * @return the response headers
218   * @throws EWSHttpException the EWS http exception
219   */
220  @Override
221  public Map<String, String> getResponseHeaders() throws EWSHttpException {
222    throwIfResponseIsNull();
223    Map<String, String> map = new HashMap<String, String>();
224
225    Header[] hM = response.getAllHeaders();
226    for (Header header : hM) {
227      // RFC2109: Servers may return multiple Set-Cookie headers
228      // Need to append the cookies before they are added to the map
229      if (header.getName().equals("Set-Cookie")) {
230        String cookieValue = "";
231        if (map.containsKey("Set-Cookie")) {
232          cookieValue += map.get("Set-Cookie");
233          cookieValue += ",";
234        }
235        cookieValue += header.getValue();
236        map.put("Set-Cookie", cookieValue);
237      } else {
238        map.put(header.getName(), header.getValue());
239      }
240    }
241
242    return map;
243  }
244
245  /*
246   * (non-Javadoc)
247   *
248   * @see
249   * microsoft.exchange.webservices.HttpWebRequest#getResponseHeaderField(
250   * java.lang.String)
251   */
252  @Override
253  public String getResponseHeaderField(String headerName) throws EWSHttpException {
254    throwIfResponseIsNull();
255    Header hM = response.getFirstHeader(headerName);
256    return hM != null ? hM.getValue() : null;
257  }
258
259  /**
260   * Gets the content encoding.
261   *
262   * @return the content encoding
263   * @throws EWSHttpException the EWS http exception
264   */
265  @Override
266  public String getContentEncoding() throws EWSHttpException {
267    throwIfResponseIsNull();
268    return response.getFirstHeader("content-encoding") != null ? response.getFirstHeader("content-encoding")
269        .getValue() : null;
270  }
271
272  /**
273   * Gets the response content type.
274   *
275   * @return the response content type
276   * @throws EWSHttpException the EWS http exception
277   */
278  @Override
279  public String getResponseContentType() throws EWSHttpException {
280    throwIfResponseIsNull();
281    return response.getFirstHeader("Content-type") != null ? response.getFirstHeader("Content-type")
282        .getValue() : null;
283  }
284
285  /**
286   * Executes Request by sending request xml data to server.
287   *
288   * @throws EWSHttpException    the EWS http exception
289   * @throws java.io.IOException the IO Exception
290   */
291  @Override
292  public int executeRequest() throws EWSHttpException, IOException {
293    throwIfRequestIsNull();
294    response = httpClient.execute(httpPost, httpContext);
295    return response.getStatusLine().getStatusCode(); // ?? don't know what is wanted in return
296  }
297
298  /**
299   * Gets the response code.
300   *
301   * @return the response code
302   * @throws EWSHttpException the EWS http exception
303   */
304  @Override
305  public int getResponseCode() throws EWSHttpException {
306    throwIfResponseIsNull();
307    return response.getStatusLine().getStatusCode();
308  }
309
310  /**
311   * Gets the response message.
312   *
313   * @return the response message
314   * @throws EWSHttpException the EWS http exception
315   */
316  public String getResponseText() throws EWSHttpException {
317    throwIfResponseIsNull();
318    return response.getStatusLine().getReasonPhrase();
319  }
320
321  /**
322   * Throw if conn is null.
323   *
324   * @throws EWSHttpException the EWS http exception
325   */
326  private void throwIfRequestIsNull() throws EWSHttpException {
327    if (null == httpPost) {
328      throw new EWSHttpException("Connection not established");
329    }
330  }
331
332  private void throwIfResponseIsNull() throws EWSHttpException {
333    if (null == response) {
334      throw new EWSHttpException("Connection not established");
335    }
336  }
337
338  /**
339   * Gets the request property.
340   *
341   * @return the request property
342   * @throws EWSHttpException the EWS http exception
343   */
344  public Map<String, String> getRequestProperty() throws EWSHttpException {
345    throwIfRequestIsNull();
346    Map<String, String> map = new HashMap<String, String>();
347
348    Header[] hM = httpPost.getAllHeaders();
349    for (Header header : hM) {
350      map.put(header.getName(), header.getValue());
351    }
352    return map;
353  }
354}