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}