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 }