View Javadoc
1   /*
2     HttpClient.java
3   
4     (C) Copyright IBM Corp. 2005, 2013
5   
6     THIS FILE IS PROVIDED UNDER THE TERMS OF THE ECLIPSE PUBLIC LICENSE
7     ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THIS FILE
8     CONSTITUTES RECIPIENTS ACCEPTANCE OF THE AGREEMENT.
9   
10    You can obtain a current copy of the Eclipse Public License from
11    http://www.opensource.org/licenses/eclipse-1.0.php
12  
13    @author : Roberto Pineiro, IBM, roberto.pineiro@us.ibm.com
14   * @author : Chung-hao Tan, IBM, chungtan@us.ibm.com
15   * 
16   * 
17   * Change History
18   * Flag       Date        Prog         Description
19   *------------------------------------------------------------------------------- 
20   * 13799      2004-12-07  thschaef     Fix chunking support 
21   * 17620      2005-06-29  thschaef     eliminate ASCIIPrintStream1 in import statement
22   * 17970      2005-08-11  pineiro5     Logon from z/OS not possible
23   * 1353168    2005-11-24  fiuczy       Possible NullPointerExcection in HttpClient.streamFinished()
24   * 1422316    2006-05-15  lupusalex    Disable delayed acknowledgement
25   * 1486379    2006-05-29  lupusalex    CIM client retries twice when HTTP/1.1 401 is returned
26   * 1498130    2006-05-31  lupusalex    Selection of xml parser on a per connection basis
27   * 1516244    2006-07-10  ebak         GCJ support
28   * 1536711    2006-08-15  lupusalex    NullPointerException causes client call to never return
29   * 1535756    2006-08-07  lupusalex    Make code warning free
30   * 1565091    2006-10-17  ebak         ssl handshake exception
31   * 1516242    2006-11-27  lupusalex    Support of OpenPegasus local authentication
32   * 1565892    2006-11-28  lupusalex    Make SBLIM client JSR48 compliant
33   * 1604329    2006-12-18  lupusalex    Fix OpenPegasus auth module
34   * 1620526    2007-01-08  lupusalex    Socket Leak in HTTPClient.getResponseCode()
35   * 1627832    2007-01-08  lupuslaex    Incorrect retry behaviour on HTTP 401
36   * 1637546    2007-01-27  lupusalex    CIMEnumerationImpl has faulty close function
37   * 1647148    2007-01-29  lupusalex    HttpClient.resetSocket() doesn't set socket timeout
38   * 1647159    2007-01-29  lupusalex    HttpClientPool runs out of HttpClients
39   * 1649595    2007-02-01  lupusalex    No chunkig requested
40   * 1660743    2007-02-15  lupusalex    SSLContext is static
41   * 1702832    2007-04-18  lupusalex    WBEMClientCIMXL.setCustomSocketFactory() not implemented
42   * 1715511    2007-05-09  lupusalex    FVT: Wrong HTTP header values 
43   * 1892046    2008-02-13  blaschke-oss Basic/digest authentication problem for Japanese users
44   * 1931216    2008-04-01  blaschke-oss In HTTPClient need to get status before closing connection
45   * 2003590    2008-06-30  blaschke-oss Change licensing from CPL to EPL
46   * 2204488 	  2008-10-28  raman_arora  Fix code to remove compiler warnings
47   * 2372030    2008-12-01  blaschke-oss Add property to control synchronized SSL handshaking
48   * 2524131    2009-01-21  raman_arora  Upgrade client to JDK 1.5 (Phase 1)
49   * 2531371    2009-02-10  raman_arora  Upgrade client to JDK 1.5 (Phase 2)
50   * 2714989    2009-03-26  blaschke-oss Code cleanup from redundant null check et al
51   * 2750520    2009-04-10  blaschke-oss Code cleanup from empty statement et al
52   * 2763216    2009-04-14  blaschke-oss Code cleanup: visible spelling/grammar errors
53   * 2817962    2009-08-05  blaschke-oss socket creation connects w/o a timeout
54   * 2994776    2010-05-05  blaschke-oss http 401 gives CIM_ERR_FAILED instead of CIM_ERR_ACCESS_DENIED
55   * 2997865    2010-05-07  blaschke-oss Infinite loop in HttpClient
56   * 3004762    2010-06-16  blaschke-oss HTTPClient infinite loop for HTTP 407
57   * 3022554    2010-06-30  blaschke-oss Flushing socket ignores skip() return code
58   * 3046073    2010-09-07  blaschke-oss Performance hit due to socket conn. creation with timeout
59   * 3195069    2011-02-28  blaschke-oss Need support to disable SSL Handshake
60   * 3235440    2011-03-22  blaschke-oss NullPointerException when socket factory returns null
61   * 3323310    2011-06-20  blaschke-oss Need the ability to override certain Global Properties
62   * 3400209    2011-08-31  blaschke-oss Highlighted Static Analysis (PMD) issues
63   * 3444912    2011-11-29  blaschke-oss Client delay during SSL handshake
64   * 3492224    2012-02-23  blaschke-oss Need two different timeouts for Socket connections
65   * 3504304    2012-03-14  blaschke-oss Rename socket timeout variables
66   * 3523918    2012-05-06  blaschke-oss "java.io.IOException: Unexpected EOF" returned as HTTP 401
67   * 3524050    2012-06-06  blaschke-oss Improve WWW-Authenticate in HTTPClient.java
68   * 3557283    2012-11-05  blaschke-oss Print full response when get EOF from CIMOM
69   * 3601894    2013-01-23  blaschke-oss Enhance HTTP and CIM-XML tracing
70   *    2619    2013-02-22  blaschke-oss Host should contain port when not 5988/5989
71   *    2621    2013-02-23  blaschke-oss Not all chunked input has trailers
72   *    2618    2013-02-27  blaschke-oss Need to add property to disable weak cipher suites for the secure indication
73   *    2642    2013-05-21  blaschke-oss Seperate properties needed for cim client and listener to filter out ciphers
74   *    2654    2013-07-29  blaschke-oss Check jcc idle time with CIMOM keepalive timeout to avoid EOF
75   *    2655    2013-08-14  blaschke-oss Content-length must be ignored when Transfer-encoding present
76   *    2151    2013-08-20  blaschke-oss gzip compression not supported
77   *    2709    2013-11-13  blaschke-oss Lower the level of the EOF message to FINE
78   */
79  package org.metricshub.wbem.sblim.cimclient.internal.http;
80  
81  /*-
82   * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
83   * WBEM Java Client
84   * ჻჻჻჻჻჻
85   * Copyright 2023 - 2025 MetricsHub
86   * ჻჻჻჻჻჻
87   * Licensed under the Apache License, Version 2.0 (the "License");
88   * you may not use this file except in compliance with the License.
89   * You may obtain a copy of the License at
90   *
91   *      http://www.apache.org/licenses/LICENSE-2.0
92   *
93   * Unless required by applicable law or agreed to in writing, software
94   * distributed under the License is distributed on an "AS IS" BASIS,
95   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
96   * See the License for the specific language governing permissions and
97   * limitations under the License.
98   * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
99   */
100 
101 import java.io.BufferedInputStream;
102 import java.io.BufferedOutputStream;
103 import java.io.ByteArrayOutputStream;
104 import java.io.IOException;
105 import java.io.InputStream;
106 import java.io.OutputStream;
107 import java.net.HttpURLConnection;
108 import java.net.InetSocketAddress;
109 import java.net.Socket;
110 import java.net.SocketTimeoutException;
111 import java.net.URI;
112 import java.security.AccessController;
113 import java.security.MessageDigest;
114 import java.security.NoSuchAlgorithmException;
115 import java.security.PrivilegedAction;
116 import java.util.Arrays;
117 import java.util.Iterator;
118 import java.util.Map.Entry;
119 import java.util.StringTokenizer;
120 import java.util.Vector;
121 import java.util.logging.Level;
122 import java.util.zip.GZIPInputStream;
123 import javax.net.SocketFactory;
124 import javax.net.ssl.HandshakeCompletedEvent;
125 import javax.net.ssl.HandshakeCompletedListener;
126 import javax.net.ssl.SSLSession;
127 import javax.net.ssl.SSLSocket;
128 import javax.net.ssl.SSLSocketFactory;
129 import org.metricshub.wbem.sblim.cimclient.WBEMConfigurationProperties;
130 import org.metricshub.wbem.sblim.cimclient.internal.http.HttpHeader.HeaderEntry;
131 import org.metricshub.wbem.sblim.cimclient.internal.http.io.ASCIIPrintStream;
132 import org.metricshub.wbem.sblim.cimclient.internal.http.io.BoundedInputStream;
133 import org.metricshub.wbem.sblim.cimclient.internal.http.io.ChunkedInputStream;
134 import org.metricshub.wbem.sblim.cimclient.internal.http.io.KeepAliveInputStream;
135 import org.metricshub.wbem.sblim.cimclient.internal.http.io.PersistentInputStream;
136 import org.metricshub.wbem.sblim.cimclient.internal.logging.LogAndTraceBroker;
137 import org.metricshub.wbem.sblim.cimclient.internal.logging.Messages;
138 import org.metricshub.wbem.sblim.cimclient.internal.util.WBEMConstants;
139 
140 /**
141  * Class HttpClient implements a HTTP client
142  *
143  */
144 public class HttpClient implements HandshakeCompletedListener {
145 
146 	private static class HostPortPair {
147 		String iHost;
148 
149 		/**
150 		 * Ctor.
151 		 *
152 		 * @param url
153 		 *            The url
154 		 */
155 		public HostPortPair(URI url) {
156 			this.iHost = url.getScheme().toLowerCase() + ':' + url.getHost().toLowerCase() + ':' + url.getPort();
157 		}
158 
159 		@Override
160 		public boolean equals(Object o) {
161 			if (!(o instanceof HostPortPair)) return false;
162 
163 			return this.iHost.equals(((HostPortPair) o).iHost);
164 		}
165 
166 		@Override
167 		public int hashCode() {
168 			return this.iHost.hashCode();
169 		}
170 
171 		@Override
172 		public String toString() {
173 			return "HostPortPair=[+" + this.iHost + "]";
174 		}
175 	}
176 
177 	private static class GetProperty implements PrivilegedAction<Object> {
178 		String iPropertyName;
179 
180 		GetProperty(String propertyName) {
181 			this.iPropertyName = propertyName;
182 		}
183 
184 		public Object run() {
185 			return System.getProperty(this.iPropertyName);
186 		}
187 	}
188 
189 	private static String iEncoding;
190 
191 	static {
192 		try {
193 			iEncoding =
194 				(String) AccessController.doPrivileged(
195 					new PrivilegedAction<Object>() {
196 
197 						public Object run() {
198 							return System.getProperty("file.encoding", "ISO8859_1");
199 						}
200 					}
201 				);
202 			if (!isASCIISuperset(iEncoding)) iEncoding = "ISO8859_1";
203 		} catch (Exception exception) {
204 			iEncoding = "ISO8859_1";
205 		}
206 	}
207 
208 	/**
209 	 * Converts a byte array to a String of hex digits
210 	 *
211 	 * @param digest
212 	 *            The byte array
213 	 * @return The hex string
214 	 */
215 	public static String convertToHexString(byte[] digest) {
216 		char hexDigit[] = "0123456789abcdef".toCharArray();
217 
218 		StringBuffer buf = new StringBuffer();
219 		for (int i = 0; i < digest.length; i++) {
220 			int b = digest[i];
221 			buf.append(hexDigit[(b >> 4) & 0xF]);
222 			buf.append(hexDigit[(b) & 0xF]);
223 		}
224 		return buf.toString();
225 	}
226 
227 	/**
228 	 * Returns a client from a http client pool
229 	 *
230 	 * @param url
231 	 *            The url to connect to
232 	 * @param clientPool
233 	 *            The client pool
234 	 * @param auth_handler
235 	 *            The authentication handler to use
236 	 * @return A http client from the pool
237 	 */
238 	public static HttpClient getClient(URI url, HttpClientPool clientPool, AuthorizationHandler auth_handler) {
239 		return clientPool.retrieveAvailableConnectionFromPool(url, auth_handler);
240 	}
241 
242 	protected static String dequote(String str) {
243 		int len = str.length();
244 		if (len > 1 && str.charAt(0) == '\"' && str.charAt(len - 1) == '\"') return str.substring(1, len - 1);
245 		return str;
246 	}
247 
248 	protected static void handleRsp(String authInfo, AuthorizationInfo prevAuthInfo) throws IOException {
249 		if (authInfo != null) {
250 			HttpHeader params = HttpHeader.parse(authInfo);
251 
252 			String nonce = params.getField("nextnonce");
253 			if (nonce != null) {
254 				prevAuthInfo.setNonce(nonce);
255 				prevAuthInfo.setNc(0);
256 			} else {
257 				nonce = prevAuthInfo.getNonce();
258 			}
259 			String qop = params.getField("qop");
260 			if (qop != null) {
261 				if (!"auth".equalsIgnoreCase(qop) && !"auth-int".equalsIgnoreCase(qop)) {
262 					// TODO
263 					throw new IOException("Authentication Digest with integrity check not supported");
264 				}
265 				byte[] rspauth;
266 				String rspauthStr = dequote(params.getField("rspauth"));
267 				if (rspauthStr != null) {
268 					rspauth = parseHex(rspauthStr);
269 
270 					String cnonce = dequote(params.getField("cnonce"));
271 					if (cnonce != null && !cnonce.equals(prevAuthInfo.getCnonce())) {
272 						throw new IOException("Digest authentication: Invalid nonce counter");
273 					}
274 					String ncStr = params.getField("nc");
275 					if (ncStr != null) {
276 						try {
277 							long nc = Long.parseLong(ncStr, 16);
278 							if (nc != prevAuthInfo.getNc()) {
279 								throw new IOException();
280 							}
281 						} catch (Exception e) {
282 							throw new IOException("Digest authentication: Invalid nonce counter");
283 						}
284 					}
285 
286 					String HA1, HA2;
287 					MessageDigest md5;
288 					try {
289 						md5 = MessageDigest.getInstance("MD5");
290 						md5.reset();
291 						byte[] bytes = prevAuthInfo.getA1().getBytes("UTF-8");
292 						md5.update(bytes);
293 						HA1 = convertToHexString(md5.digest());
294 						if ("MD5-sess".equalsIgnoreCase(params.getField("algorithm"))) {
295 							md5.reset();
296 							md5.update((HA1 + ":" + nonce + ":" + cnonce).getBytes("UTF-8"));
297 							HA1 = convertToHexString(md5.digest());
298 						}
299 
300 						HA2 = ":" + prevAuthInfo.getURI();
301 						if ("auth-int".equalsIgnoreCase(qop)) {
302 							md5.reset();
303 							md5.update(new byte[] {});
304 							HA2 += ":" + convertToHexString(md5.digest());
305 						}
306 						md5.reset();
307 						md5.update(HA2.getBytes("UTF-8"));
308 						HA2 = convertToHexString(md5.digest());
309 
310 						md5.reset();
311 						md5.update((HA1 + ":" + nonce + ":" + ncStr + ":" + cnonce + ":" + qop + ":" + HA2).getBytes("UTF-8"));
312 						String hsh = convertToHexString(md5.digest());
313 						byte[] hash = parseHex(hsh);
314 
315 						if (!Arrays.equals(hash, rspauth)) throw new IOException("Digest Authentication failed!");
316 					} catch (NoSuchAlgorithmException e1) {
317 						throw new IOException("Unable to validate Authentication response: NoSuchAlgorithmException");
318 					}
319 				}
320 			} else {
321 				// TODO compute md5 of the entity-body
322 			}
323 		}
324 	}
325 
326 	protected static byte[] parseHex(String hex) {
327 		byte[] value = new byte[hex.length() >> 1];
328 		int n = 0;
329 		for (int i = 0; i < value.length; i++) {
330 			value[i] = (byte) (0xff & Integer.parseInt(hex.substring(n, n + 1), 16));
331 			n += 2;
332 		}
333 		return value;
334 	}
335 
336 	private static boolean isASCIISuperset(String charset) throws Exception {
337 		String asciiSuperSet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_.!~*'();/?:@&=+$,";
338 		byte abyte0[] = {
339 			48,
340 			49,
341 			50,
342 			51,
343 			52,
344 			53,
345 			54,
346 			55,
347 			56,
348 			57,
349 			65,
350 			66,
351 			67,
352 			68,
353 			69,
354 			70,
355 			71,
356 			72,
357 			73,
358 			74,
359 			75,
360 			76,
361 			77,
362 			78,
363 			79,
364 			80,
365 			81,
366 			82,
367 			83,
368 			84,
369 			85,
370 			86,
371 			87,
372 			88,
373 			89,
374 			90,
375 			97,
376 			98,
377 			99,
378 			100,
379 			101,
380 			102,
381 			103,
382 			104,
383 			105,
384 			106,
385 			107,
386 			108,
387 			109,
388 			110,
389 			111,
390 			112,
391 			113,
392 			114,
393 			115,
394 			116,
395 			117,
396 			118,
397 			119,
398 			120,
399 			121,
400 			122,
401 			45,
402 			95,
403 			46,
404 			33,
405 			126,
406 			42,
407 			39,
408 			40,
409 			41,
410 			59,
411 			47,
412 			63,
413 			58,
414 			64,
415 			38,
416 			61,
417 			43,
418 			36,
419 			44
420 		};
421 		byte convertedArray[] = asciiSuperSet.getBytes(charset);
422 		return Arrays.equals(convertedArray, abyte0);
423 	}
424 
425 	private boolean iConnected = false;
426 
427 	private HttpClientPool iHttpClientPool;
428 
429 	private AuthorizationHandler iAuth_handler;
430 
431 	private SSLSession iSession;
432 
433 	private InputStream iIStream;
434 
435 	private boolean iUseHttp11 = true;
436 
437 	private boolean iKeepAlive = true;
438 
439 	private HttpClientMethod iMethod;
440 
441 	private OutputStream iOStream;
442 
443 	private AuthorizationInfo iPrevAuthInfo;
444 
445 	private AuthorizationInfo iPrevProxy;
446 
447 	private HttpHeader iRequestHeaders = new HttpHeader();
448 
449 	private String iRequestMethod = "POST";
450 
451 	private boolean iReset = true;
452 
453 	private HttpClientMethod iResponse;
454 
455 	private HttpHeader iResponseHeaders = new HttpHeader();
456 
457 	private InputStream iServerInput;
458 
459 	private ByteArrayOutputStream iServerOutput;
460 
461 	private Socket iSocket;
462 
463 	private URI iUrl;
464 
465 	private long iPreviousResponseTime = -1;
466 
467 	/**
468 	 * Ctor.
469 	 *
470 	 * @param url
471 	 *            The url to connect to
472 	 * @param clientPool
473 	 *            The associated client pool
474 	 * @param auth_handler
475 	 *            The authentication handler
476 	 */
477 	public HttpClient(URI url, HttpClientPool clientPool, AuthorizationHandler auth_handler) {
478 		this.iUrl = url;
479 		this.iAuth_handler = auth_handler;
480 		this.iHttpClientPool = clientPool;
481 	}
482 
483 	/**
484 	 * Connects to the http server
485 	 *
486 	 * @throws IOException
487 	 */
488 	public void connect() throws IOException {
489 		LogAndTraceBroker logger = LogAndTraceBroker.getBroker();
490 		logger.entry();
491 		try {
492 			this.iReset = true;
493 			this.iResponse = null;
494 			this.iConnected = true;
495 			this.iServerOutput = null;
496 			resetSocket();
497 		} finally {
498 			logger.exit();
499 		}
500 	}
501 
502 	/**
503 	 * Disconnects the session
504 	 */
505 	public synchronized void disconnect() {
506 		LogAndTraceBroker logger = LogAndTraceBroker.getBroker();
507 		logger.entry();
508 		this.iConnected = false;
509 		if (this.iSocket != null) {
510 			try {
511 				this.iSocket.close();
512 			} catch (IOException e) {
513 				logger.trace(Level.FINE, "Unexpected problem closing http socket", e);
514 			}
515 			this.iSocket = null;
516 			this.iServerInput = null;
517 			this.iReset = true;
518 			this.iResponse = null;
519 		}
520 		logger.exit();
521 	}
522 
523 	@Override
524 	protected void finalize() throws Throwable {
525 		try {
526 			this.iSocket.close();
527 		} catch (IOException e) {
528 			// bad luck
529 		} finally {
530 			super.finalize();
531 		}
532 	}
533 
534 	/**
535 	 * Returns the http header field value for a given index
536 	 *
537 	 * @param index
538 	 *            The index
539 	 * @return The header field value
540 	 */
541 	public synchronized String getHeaderFieldValue(int index) {
542 		if (index < 0) throw new IllegalArgumentException();
543 		if (index == 0) return this.iResponse.toString();
544 
545 		Iterator<Entry<HeaderEntry, String>> iterator = this.iResponseHeaders.iterator();
546 		while (iterator.hasNext() && --index >= 0) {
547 			Entry<HeaderEntry, String> entry = iterator.next();
548 			if (index == 0) return entry.getValue().toString();
549 		}
550 		return null;
551 	}
552 
553 	/**
554 	 * Returns the http header field for a given name
555 	 *
556 	 * @param name
557 	 *            The name
558 	 * @return The header field
559 	 */
560 	public synchronized String getHeaderField(String name) {
561 		return this.iResponseHeaders.getField(name);
562 	}
563 
564 	/**
565 	 * Return the http header field name for a given index
566 	 *
567 	 * @param index
568 	 *            The index
569 	 * @return The name
570 	 */
571 	public synchronized String getHeaderFieldName(int index) {
572 		if (index < 0) throw new IllegalArgumentException();
573 		if (index == 0) return null;
574 
575 		Iterator<Entry<HeaderEntry, String>> iterator = this.iResponseHeaders.iterator();
576 		while (iterator.hasNext() && --index >= 0) {
577 			Entry<HeaderEntry, String> entry = iterator.next();
578 			if (index == 0) return entry.getKey().toString();
579 		}
580 		return null;
581 	}
582 
583 	/**
584 	 * Returns the input stream of this http connection
585 	 *
586 	 * @return The input stream
587 	 * @throws IOException
588 	 */
589 	public synchronized InputStream getInputStream() throws IOException {
590 		if (getResponseCode() < 500 && this.iResponse != null && this.iServerInput != null) return this.iServerInput;
591 
592 		throw new IOException("Failed to open an input stream from server: HTTPResponse " + getResponseCode());
593 	}
594 
595 	/**
596 	 * Returns the output stream of this http connection
597 	 *
598 	 * @return The output stream
599 	 */
600 	public synchronized OutputStream getOutputStream() {
601 		if (this.iServerOutput == null) {
602 			this.iServerOutput = new ByteArrayOutputStream();
603 		}
604 		return this.iServerOutput;
605 	}
606 
607 	/**
608 	 * Returns the request method
609 	 *
610 	 * @return The request method
611 	 */
612 	public String getRequestMethod() {
613 		return this.iRequestMethod;
614 	}
615 
616 	/**
617 	 * Returns the request property for a given key
618 	 *
619 	 * @param key
620 	 *            The key
621 	 * @return The property
622 	 */
623 	public String getRequestProperty(String key) {
624 		return this.iRequestHeaders.getField(key);
625 	}
626 
627 	/**
628 	 * Returns the response code
629 	 *
630 	 * @return The response code
631 	 * @throws IOException
632 	 */
633 	public synchronized int getResponseCode() throws IOException {
634 		LogAndTraceBroker logger = LogAndTraceBroker.getBroker();
635 		logger.entry();
636 		try {
637 			long ResponseTime = -1;
638 			Exception delayedException = null;
639 			if (this.iReset && this.iResponse == null) {
640 				boolean authFailed = false;
641 				int IoRetry = 1;
642 				int AuthentificationRetry = 1;
643 				do {
644 					logger.trace(
645 						Level.FINER,
646 						"Attempting http request (retry counters:" + IoRetry + "/" + AuthentificationRetry + ")"
647 					);
648 					long RequestTime = System.currentTimeMillis();
649 
650 					if (this.iPreviousResponseTime != -1) {
651 						long time = RequestTime - this.iPreviousResponseTime;
652 						long maxTime = this.iHttpClientPool.getConfigurationContext().getSocketIdleTimeout();
653 						if (maxTime > 0 && time > maxTime) {
654 							logger.trace(Level.FINER, "Closing socket after " + time + "ms of idle time");
655 
656 							if (this.iSocket != null && !this.iSocket.isClosed()) {
657 								try {
658 									this.iSocket.close();
659 								} catch (IOException e) {
660 									logger.trace(Level.FINER, "Exception caught while closing socket", e);
661 								}
662 							}
663 							this.iSocket = null;
664 							this.iReset = true;
665 							this.iResponse = null;
666 						}
667 					}
668 
669 					ResponseTime = -1;
670 					resetSocket();
671 					this.iReset = false;
672 					try {
673 						ASCIIPrintStream out = (ASCIIPrintStream) this.iOStream;
674 						if (out == null) throw new IOException("could not open output stream");
675 
676 						String file = this.iUrl.getPath();
677 						if (file == null || file.length() == 0) file = "/";
678 						String query = this.iUrl.getQuery();
679 						if (query != null) file = file + '?' + query;
680 
681 						this.iMethod = new HttpClientMethod(this.iRequestMethod, this.iUrl.getPath(), 1, this.iUseHttp11 ? 1 : 0);
682 						logger.trace(Level.FINER, "HTTP Operation= " + this.iMethod);
683 
684 						this.iMethod.write(out);
685 
686 						StringBuilder hostField = new StringBuilder(this.iUrl.getHost());
687 						if (
688 							this.iUrl.getPort() > 0 &&
689 							(
690 								(
691 									WBEMConstants.HTTPS.equalsIgnoreCase(this.iUrl.getScheme()) &&
692 									this.iUrl.getPort() != WBEMConstants.DEFAULT_WBEM_SECURE_PORT
693 								) ||
694 								(
695 									WBEMConstants.HTTP.equalsIgnoreCase(this.iUrl.getScheme()) &&
696 									this.iUrl.getPort() != WBEMConstants.DEFAULT_WBEM_PORT
697 								)
698 							)
699 						) {
700 							hostField.append(':');
701 							hostField.append(this.iUrl.getPort());
702 						}
703 						this.iRequestHeaders.addField("Host", hostField.toString());
704 
705 						if (this.iServerOutput != null) this.iRequestHeaders.addField(
706 								"Content-length",
707 								"" + this.iServerOutput.size()
708 							); else this.iRequestHeaders.addField("Content-length", "0");
709 						if (this.iHttpClientPool.getConfigurationContext().isHttpChunked()) {
710 							this.iRequestHeaders.addField("TE", "trailers");
711 						}
712 
713 						if (iUseHttp11 && !iHttpClientPool.getConfigurationContext().useKeepAliveStrictMode()) {
714 							// AMMO-863 Systematic use of the Keep-alive
715 							// header is not recommended with HTTP/1.1
716 							// And may cause issues with EMC's VAPP appliances
717 							// so we made the header available only when
718 							// new strict mode is disabled
719 							logger.trace(
720 								Level.INFO,
721 								"HTTP 1.1 protocol and 'Connection=Keep-alive' strict mode disabled, we add the header"
722 							);
723 							iRequestHeaders.addField("Connection", "Keep-alive");
724 						}
725 
726 						if (this.iPrevAuthInfo == null) {
727 							AuthorizationInfo authInfo = this.iAuth_handler.getAuthorizationInfo(0);
728 							String authenticate = this.iHttpClientPool.getConfigurationContext().getHttpWwwAuthenticateInfo();
729 
730 							if (authInfo.isSentOnFirstRequest()) {
731 								this.iRequestHeaders.addField(authInfo.getHeaderFieldName(), authInfo.toString());
732 							} else if (authenticate != null) {
733 								try {
734 									this.iPrevAuthInfo = getAuthentication(false, this.iPrevAuthInfo, authenticate);
735 									if (this.iPrevAuthInfo != null) {
736 										this.iRequestHeaders.addField(
737 												this.iPrevAuthInfo.getHeaderFieldName(),
738 												this.iPrevAuthInfo.toString()
739 											);
740 									}
741 								} catch (NoSuchAlgorithmException e) {
742 									logger.trace(Level.FINER, "Unable to find digest algorithm", e);
743 								} catch (IllegalArgumentException e) {
744 									logger.trace(
745 										Level.FINER,
746 										WBEMConfigurationProperties.HTTP_WWW_AUTHENTICATE_INFO +
747 										" did not contain WWW-Authenticate information",
748 										e
749 									);
750 								} catch (HttpParseException e) {
751 									logger.trace(
752 										Level.FINER,
753 										WBEMConfigurationProperties.HTTP_WWW_AUTHENTICATE_INFO +
754 										" did not contain valid WWW-Authenticate information",
755 										e
756 									);
757 								}
758 							}
759 						} else {
760 							this.iRequestHeaders.addField(this.iPrevAuthInfo.getHeaderFieldName(), this.iPrevAuthInfo.toString());
761 						}
762 
763 						if (this.iPrevProxy != null) this.iRequestHeaders.addField(
764 								"Proxy-authorization",
765 								this.iPrevProxy.toString()
766 							);
767 
768 						boolean isGzipped = false;
769 						if (this.iHttpClientPool.getConfigurationContext().isGzipEncodingEnabled()) {
770 							isGzipped = true;
771 							this.iRequestHeaders.addField("Accept-Encoding", "gzip,identity;q=0.5");
772 						}
773 
774 						this.iRequestHeaders.write(out);
775 
776 						logger.trace(Level.FINER, "Request HTTP Headers= " + this.iRequestHeaders);
777 
778 						if (out.checkError() != null) {
779 							delayedException = out.checkError();
780 							logger.trace(Level.FINER, "Exception caught while writing to the http output stream.", delayedException);
781 							if (this.iSocket != null && !this.iSocket.isClosed()) {
782 								try {
783 									this.iSocket.close();
784 								} catch (IOException e) {
785 									logger.trace(Level.FINER, "Exception caught while closing socket", e);
786 								}
787 							}
788 							this.iSocket = null;
789 							this.iReset = true;
790 							this.iResponse = null;
791 							--IoRetry;
792 							continue;
793 						}
794 						if (this.iServerOutput != null) {
795 							this.iServerOutput.writeTo(out);
796 						}
797 						out.flush();
798 
799 						// byte[] header = new byte[8];
800 						// istream.mark(8);
801 						// int totalRead;
802 						// int k;
803 						// for (totalRead = 0; totalRead < 8; totalRead += k) {
804 						// k = istream.read(header, totalRead, 8 - totalRead);
805 						// if (k < 0)
806 						// break;
807 						// }
808 						//
809 						// if (header[0] != 72 // HTTP1.
810 						// || header[1] != 84
811 						// || header[2] != 84
812 						// || header[3] != 80
813 						// || header[4] != 47
814 						// || header[5] != 49
815 						// || header[6] != 46
816 						// || totalRead != 8) {
817 						// retry = 0;
818 						// throw new IOException("Unexpected end of file from
819 						// server. Header does not match HTTP header: "+new
820 						// String(header));
821 						// }
822 						//
823 						// istream.reset();
824 
825 						this.iResponse = new HttpClientMethod(this.iIStream);
826 						logger.trace(Level.FINER, "HTTP Response= " + this.iResponse);
827 						ResponseTime = System.currentTimeMillis();
828 
829 						this.iResponseHeaders = new HttpHeader(this.iIStream);
830 						logger.trace(Level.FINER, "Response HTTP Headers= " + this.iResponseHeaders.toString());
831 						this.iKeepAlive = false;
832 						if (
833 							"Keep-alive".equalsIgnoreCase(this.iResponseHeaders.getField("Connection")) ||
834 							(this.iResponse.getMajorVersion() == 1 && this.iResponse.getMinorVersion() == 1) ||
835 							this.iResponseHeaders.getField("Keep-alive") != null
836 						) {
837 							this.iKeepAlive = true;
838 						}
839 
840 						this.iServerInput = new PersistentInputStream(this.iIStream);
841 						// String keepAliveHdr =
842 						// iResponseHeaders.getField("Keep-Alive");
843 
844 						String contentLength = this.iResponseHeaders.getField("Content-length");
845 						long length = -1;
846 						try {
847 							if (contentLength != null && contentLength.length() > 0) length = Long.parseLong(contentLength);
848 						} catch (Exception e) {
849 							logger.trace(Level.FINER, "Exception while parsing the content length of http response", e);
850 						}
851 						this.iKeepAlive = (length >= 0 || this.iResponse.getStatus() == 304 || this.iResponse.getStatus() == 204);
852 
853 						if (isGzipped) {
854 							String contentEncoding = this.iResponseHeaders.getField("Content-Encoding");
855 							if (contentEncoding != null && contentEncoding.toLowerCase().contains("gzip")) {
856 								length = -1; // ignore Content-length
857 								this.iServerInput = new GZIPInputStream(this.iServerInput);
858 							}
859 						}
860 
861 						String transferEncoding = this.iResponseHeaders.getField("Transfer-encoding");
862 						if (transferEncoding != null) {
863 							length = -1; // ignore Content-length
864 							if (transferEncoding.toLowerCase().endsWith("chunked")) {
865 								this.iServerInput =
866 									new ChunkedInputStream(this.iServerInput, this.iResponseHeaders.getField("Trailer"), "Response");
867 								this.iKeepAlive = true;
868 							}
869 						}
870 
871 						if (length >= 0) this.iServerInput = new BoundedInputStream(this.iServerInput, length);
872 
873 						logger.trace(Level.FINER, "KeepAlive=" + (this.iKeepAlive ? "true" : "false"));
874 
875 						if (this.iKeepAlive) {
876 							this.iServerInput = new KeepAliveInputStream(this.iServerInput, this);
877 						}
878 
879 						switch (this.iResponse.getStatus()) {
880 							case 100: {
881 									continue;
882 								}
883 							case HttpURLConnection.HTTP_OK:
884 								String authInfo = this.iResponseHeaders.getField("Authentication-Info");
885 								handleRsp(authInfo, this.iPrevAuthInfo);
886 
887 								authInfo = this.iResponseHeaders.getField("Authentication-Proxy");
888 								handleRsp(authInfo, this.iPrevProxy);
889 
890 								if (this.iServerOutput != null) this.iServerOutput = null;
891 
892 								this.iPreviousResponseTime = ResponseTime;
893 								return HttpURLConnection.HTTP_OK;
894 							case HttpURLConnection.HTTP_UNAUTHORIZED:
895 								--AuthentificationRetry;
896 								String authenticate = this.iResponseHeaders.getField("WWW-Authenticate");
897 								try {
898 									this.iPrevAuthInfo = getAuthentication(false, this.iPrevAuthInfo, authenticate);
899 									if (this.iPrevAuthInfo != null) {
900 										this.iRequestHeaders.addField(
901 												this.iPrevAuthInfo.getHeaderFieldName(),
902 												this.iPrevAuthInfo.toString()
903 											);
904 									}
905 								} catch (NoSuchAlgorithmException e) {
906 									logger.trace(Level.FINER, "Unable to find digest algorithm", e);
907 								} catch (IllegalArgumentException e) {
908 									logger.trace(Level.FINER, "HTTP 401 response did not contain WWW-Authenticate information", e);
909 								} catch (HttpParseException e) {
910 									logger.trace(Level.FINER, "HTTP 401 response did not contain valid WWW-Authenticate information", e);
911 								}
912 
913 								if (!authFailed) {
914 									authFailed = true;
915 									logger.trace(Level.FINER, "Authorization failed, retrying with authorization info.");
916 								}
917 								if (this.iPrevAuthInfo != null && this.iPrevAuthInfo.isKeptAlive()) {
918 									this.iKeepAlive = true;
919 								}
920 								break;
921 							case HttpURLConnection.HTTP_PROXY_AUTH:
922 								--AuthentificationRetry;
923 								logger.message(Messages.HTTP_PROXY_AUTH_UNSUPPORTED, this.iUrl);
924 
925 								// TODO implement http proxy authentication
926 								/*
927 								 * authenticate =
928 								 * responseHeaders.getField("Proxy-Authenticate"
929 								 * ); prevProxy = getAuthentication(true,
930 								 * prevProxy, authenticate, this); if
931 								 * (prevAuthInfo != null)
932 								 * requestHeaders.addField
933 								 * ("Proxy-Authorization",
934 								 * prevProxy.toString());
935 								 *
936 								 * while ((total = serverInput.available()) > 0)
937 								 * { serverInput.skip(total); } break;
938 								 */
939 								break;
940 							default:
941 								int status = this.iResponse.getStatus();
942 								if (!this.iKeepAlive) closeConnection(); else this.iServerInput.close();
943 								this.iPreviousResponseTime = ResponseTime;
944 								return status;
945 						}
946 					} catch (SocketTimeoutException e) {
947 						throw e;
948 					} catch (IOException e) {
949 						logger.message(Messages.HTTP_CONNECTION_FAILED, new Object[] { this.iUrl, e.getMessage() });
950 						StringBuilder msg = new StringBuilder("Http connection failed ");
951 						if (ResponseTime != -1) {
952 							msg.append("after");
953 							msg.append(ResponseTime - RequestTime);
954 							msg.append(" milliseconds");
955 						} else {
956 							msg.append("before response received");
957 						}
958 						if (this.iPreviousResponseTime != -1) {
959 							msg.append(", ");
960 							msg.append(System.currentTimeMillis() - this.iPreviousResponseTime);
961 							msg.append(" milliseconds after previous response on same socket");
962 						}
963 						logger.trace(Level.FINE, msg.toString(), e);
964 						delayedException = e;
965 						if (this.iSocket != null && !this.iSocket.isClosed()) {
966 							try {
967 								this.iSocket.close();
968 							} catch (IOException e2) {
969 								logger.trace(Level.FINER, "Exception caught while closing socket", e2);
970 							}
971 						}
972 						this.iSocket = null;
973 						this.iReset = true;
974 						this.iResponse = null;
975 						--IoRetry;
976 					}
977 				} while (AuthentificationRetry >= 0 && IoRetry >= 0);
978 			}
979 
980 			if (this.iResponse != null) {
981 				logger.trace(Level.FINER, "http response code=" + this.iResponse.getStatus());
982 				if (ResponseTime != -1) this.iPreviousResponseTime = ResponseTime;
983 				return this.iResponse.getStatus();
984 			}
985 			throw (IOException) (
986 				delayedException != null ? delayedException : new Exception("Unable to get response after maximum retries")
987 			);
988 		} finally {
989 			logger.exit();
990 		}
991 	}
992 
993 	/**
994 	 * Returns the response message
995 	 *
996 	 * @return The response message
997 	 */
998 	public String getResponseMessage() {
999 		if (this.iResponse != null) return this.iResponse.getResponseMessage();
1000 		return null;
1001 	}
1002 
1003 	public void handshakeCompleted(HandshakeCompletedEvent event) {
1004 		LogAndTraceBroker.getBroker().trace(Level.FINER, "Http handshake completed.");
1005 		this.iSession = event.getSession();
1006 	}
1007 
1008 	/**
1009 	 * Resets state
1010 	 */
1011 	public void reset() {
1012 		this.iRequestHeaders.clear();
1013 		this.iResponseHeaders.clear();
1014 		this.iResponse = null;
1015 		this.iReset = true;
1016 	}
1017 
1018 	/**
1019 	 * Sets the request method
1020 	 *
1021 	 * @param method
1022 	 *            The request method
1023 	 */
1024 	public void setRequestMethod(String method) {
1025 		this.iRequestMethod = method;
1026 	}
1027 
1028 	/**
1029 	 * Sets the request property
1030 	 *
1031 	 * @param key
1032 	 *            The property name
1033 	 * @param value
1034 	 *            The value
1035 	 */
1036 	public void setRequestProperty(String key, String value) {
1037 		this.iRequestHeaders.addField(key, value);
1038 	}
1039 
1040 	/**
1041 	 * Releases the client and returns it to the pool
1042 	 */
1043 	public void streamFinished() {
1044 		streamFinished(true);
1045 	}
1046 
1047 	/**
1048 	 * Releases the client and returns it to the pool
1049 	 *
1050 	 * @param keep
1051 	 *            if <code>true</code> return to the pool, if <code>false</code>
1052 	 *            drop.
1053 	 */
1054 	public void streamFinished(boolean keep) {
1055 		LogAndTraceBroker logger = LogAndTraceBroker.getBroker();
1056 		logger.entry();
1057 
1058 		HostPortPair hpp = new HostPortPair(this.iUrl);
1059 		if (keep) { // TODO configurable from property file
1060 			logger.trace(Level.FINER, "Adding http client to pool (" + hpp + ")");
1061 			this.iHttpClientPool.returnAvailableConnectionToPool(this);
1062 		} else {
1063 			logger.trace(Level.FINER, "Disconnectiong http client (" + hpp + ")");
1064 			this.iHttpClientPool.removeConnectionFromPool(this);
1065 			disconnect();
1066 		}
1067 		logger.exit();
1068 	}
1069 
1070 	/**
1071 	 * Enables/Disables use of http 1.1
1072 	 *
1073 	 * @param bool
1074 	 *            if <code>true</code> http 1.1 is enabled.
1075 	 */
1076 	public void useHttp11(boolean bool) {
1077 		this.iUseHttp11 = bool;
1078 	}
1079 
1080 	/**
1081 	 * Returns if a proxy is used
1082 	 *
1083 	 * @return <code>true</code> if a proxy is used
1084 	 */
1085 	public boolean usingProxy() {
1086 		// TODO Auto-generated method stub
1087 		return false;
1088 	}
1089 
1090 	protected AuthorizationInfo getAuthentication(boolean proxy, AuthorizationInfo prevAuthInfo, String authenticate)
1091 		throws HttpParseException, NoSuchAlgorithmException {
1092 		Challenge[] challenges = Challenge.parseChallenge(authenticate);
1093 
1094 		// AuthorizationHandler auth_handler =
1095 		// AuthorizationHandler.getInstance();
1096 		int cntr = 0;
1097 		prevAuthInfo = null;
1098 		while (cntr < challenges.length) {
1099 			Challenge challenge = challenges[cntr];
1100 			cntr++;
1101 			// if (challenge.getScheme().equalsIgnoreCase("Digest")) {
1102 			// HttpHeader headers = challenge.getParams();
1103 			// String stale = headers.getField("stale");
1104 			// }
1105 			prevAuthInfo =
1106 				this.iAuth_handler.getAuthorizationInfo(
1107 						this.iHttpClientPool.getConfigurationContext().getHttpAuthenticationModule(),
1108 						proxy ? Boolean.TRUE : Boolean.FALSE,
1109 						this.iUrl.getHost(),
1110 						this.iUrl.getPort(),
1111 						this.iUrl.getScheme(),
1112 						challenge.getRealm(),
1113 						challenge.getScheme()
1114 					);
1115 
1116 			if (prevAuthInfo != null) {
1117 				prevAuthInfo.updateAuthenticationInfo(challenge, authenticate, this.iUrl, this.iRequestMethod);
1118 				return prevAuthInfo;
1119 			}
1120 		}
1121 		return null;
1122 	}
1123 
1124 	private void closeConnection() {
1125 		LogAndTraceBroker logger = LogAndTraceBroker.getBroker();
1126 		logger.entry();
1127 		if (this.iSocket != null) {
1128 			try {
1129 				this.iSocket.close();
1130 			} catch (IOException e) {
1131 				logger.trace(Level.FINER, "Exception while closing the socket", e);
1132 			}
1133 			this.iSocket = null;
1134 			this.iServerInput = null;
1135 			// response = null;
1136 		}
1137 		logger.exit();
1138 	}
1139 
1140 	private String[] parseProperty(String propertyName) {
1141 		String s = (String) AccessController.doPrivileged(new GetProperty(propertyName));
1142 		String as[];
1143 		if (s == null || s.length() == 0) {
1144 			as = null;
1145 		} else {
1146 			Vector<Object> vector = new Vector<Object>();
1147 			for (
1148 				StringTokenizer stringtokenizer = new StringTokenizer(s, ",");
1149 				stringtokenizer.hasMoreElements();
1150 				vector.addElement(stringtokenizer.nextElement())
1151 			) {
1152 				// add each token to vector
1153 			}
1154 			as = new String[vector.size()];
1155 			for (int i1 = 0; i1 < as.length; i1++) as[i1] = (String) vector.elementAt(i1);
1156 		}
1157 		return as;
1158 	}
1159 
1160 	private void resetSocket() throws IOException {
1161 		LogAndTraceBroker logger = LogAndTraceBroker.getBroker();
1162 		logger.entry();
1163 		if (!this.iKeepAlive) {
1164 			logger.trace(Level.FINER, "KeepAlive=false, closing http connection...");
1165 			closeConnection();
1166 		}
1167 
1168 		int httpTimeout = this.iHttpClientPool.getConfigurationContext().getHttpTimeout();
1169 		logger.trace(Level.FINER, "Setting http timeout=" + httpTimeout);
1170 
1171 		if (this.iSocket == null) {
1172 			// Determine whether we need to connect with a timeout or not
1173 			boolean socketConnectWithTimeout = this.iHttpClientPool.getConfigurationContext().socketConnectWithTimeout();
1174 			logger.trace(
1175 				Level.FINER,
1176 				"Socket=null, creating http socket " + (socketConnectWithTimeout ? "with" : "without") + " timeout."
1177 			);
1178 
1179 			// On Java 5+ InetSocketAddress(String,int) constructor will call
1180 			// any security manager's checkConnect method
1181 			if (!socketConnectWithTimeout) {
1182 				SecurityManager sm = System.getSecurityManager();
1183 				if (sm != null) {
1184 					sm.checkConnect(this.iUrl.getHost(), this.iUrl.getPort());
1185 				}
1186 			}
1187 
1188 			SocketFactory factory = this.iHttpClientPool.getConfigurationContext().getCustomSocketFactory();
1189 			if (factory == null) {
1190 				factory =
1191 					HttpSocketFactory
1192 						.getInstance()
1193 						.getClientSocketFactory(
1194 							WBEMConstants.HTTPS.equalsIgnoreCase(this.iUrl.getScheme()) ? this.iHttpClientPool.getSslContext() : null
1195 						);
1196 				if (factory == null) {
1197 					logger.message(Messages.HTTP_NO_SOCKET_FACTORY, this.iUrl.getScheme());
1198 					throw new IllegalStateException("Unable to load socket factory:" + this.iUrl.getScheme());
1199 				}
1200 			}
1201 			logger.trace(Level.FINER, "Creating new http for url " + this.iUrl.toString());
1202 			if (socketConnectWithTimeout) {
1203 				int connectTimeout = this.iHttpClientPool.getConfigurationContext().getSocketConnectTimeout();
1204 				logger.trace(Level.FINER, "Setting socket connect timeout=" + connectTimeout);
1205 
1206 				if (factory instanceof SSLSocketFactory) {
1207 					Socket sock = new Socket();
1208 					sock.connect(new InetSocketAddress(this.iUrl.getHost(), this.iUrl.getPort()), connectTimeout);
1209 					this.iSocket =
1210 						((SSLSocketFactory) factory).createSocket(sock, this.iUrl.getHost(), this.iUrl.getPort(), true);
1211 				} else {
1212 					this.iSocket = factory.createSocket();
1213 					if (this.iSocket != null) this.iSocket.connect(
1214 							new InetSocketAddress(this.iUrl.getHost(), this.iUrl.getPort()),
1215 							connectTimeout
1216 						);
1217 				}
1218 			} else {
1219 				this.iSocket = factory.createSocket(this.iUrl.getHost(), this.iUrl.getPort());
1220 			}
1221 			if (this.iSocket == null) {
1222 				logger.trace(Level.WARNING, "Socket factory " + factory.getClass().getName() + " returned null socket");
1223 				throw new IOException("Socket factory did not create socket");
1224 			}
1225 
1226 			this.iPreviousResponseTime = -1;
1227 			this.iSocket.setTcpNoDelay(true);
1228 			this.iSocket.setKeepAlive(true);
1229 			this.iSocket.setSoTimeout(httpTimeout);
1230 
1231 			if (this.iSocket instanceof SSLSocket) {
1232 				// Determine whether we need to perform SSL handshake or not
1233 				boolean performHandshake = this.iHttpClientPool.getConfigurationContext().performSslHandshake();
1234 				logger.trace(
1235 					Level.FINER,
1236 					"SSL socket created, handshake " + (performHandshake ? "will" : "will not") + " be performed."
1237 				);
1238 
1239 				if (performHandshake) {
1240 					SSLSocket sk = (SSLSocket) this.iSocket;
1241 
1242 					String protocols[] = parseProperty("https.protocols");
1243 					if (protocols != null) {
1244 						logger.trace(
1245 							Level.FINER,
1246 							"Setting SSLSocket.setEnabledProtocols() from \"https.protocols\"=" +
1247 							String.valueOf(Arrays.asList(protocols))
1248 						);
1249 						sk.setEnabledProtocols(protocols);
1250 					}
1251 
1252 					String ciphersuites[] = parseProperty("https.cipherSuites");
1253 					if (ciphersuites != null) {
1254 						logger.trace(
1255 							Level.FINER,
1256 							"Setting SSLSocket.setEnableCipheSuites() from \"https.cipherSuites\"=" +
1257 							String.valueOf(Arrays.asList(ciphersuites))
1258 						);
1259 						sk.setEnabledCipherSuites(ciphersuites);
1260 					}
1261 
1262 					String disableCipherSuites =
1263 						this.iHttpClientPool.getConfigurationContext().getSslClientCipherSuitesToDisable();
1264 					if (disableCipherSuites != null) {
1265 						sk.setEnabledCipherSuites(
1266 							this.iHttpClientPool.getUpdatedCipherSuites(sk.getEnabledCipherSuites(), disableCipherSuites)
1267 						);
1268 					}
1269 
1270 					// Determine whether we need to perform synchronized SSL
1271 					// handshake or not
1272 					boolean synchronizedHandshake = this.iHttpClientPool.getConfigurationContext().synchronizedSslHandshake();
1273 					logger.trace(
1274 						Level.FINER,
1275 						"Starting " + (synchronizedHandshake ? "synchronized" : "unsynchronized") + " http handshake."
1276 					);
1277 
1278 					sk.addHandshakeCompletedListener(this);
1279 					if (synchronizedHandshake) {
1280 						synchronized (SSLSocket.class) {
1281 							sk.startHandshake();
1282 						}
1283 					} else {
1284 						sk.startHandshake();
1285 					}
1286 				}
1287 			}
1288 
1289 			this.iIStream = new BufferedInputStream(this.iSocket.getInputStream());
1290 			this.iOStream =
1291 				new ASCIIPrintStream(new BufferedOutputStream(this.iSocket.getOutputStream(), 1024), false, iEncoding);
1292 			this.iServerInput = null;
1293 		} else {
1294 			if (this.iServerInput != null && !(this.iServerInput instanceof KeepAliveInputStream)) {
1295 				logger.trace(Level.FINER, "Socket!=null, flushing the stream...");
1296 				long totalBytes = 0;
1297 				long total;
1298 				while ((total = this.iServerInput.available()) > 0) {
1299 					total = this.iServerInput.skip(total);
1300 					if (total >= 0) totalBytes += total;
1301 				}
1302 				logger.trace(Level.FINER, "total bytes on the stream=" + totalBytes);
1303 			}
1304 			this.iSocket.setSoTimeout(httpTimeout);
1305 		}
1306 	}
1307 
1308 	/**
1309 	 * Returns connected
1310 	 *
1311 	 * @return The value of connected.
1312 	 */
1313 	public boolean isConnected() {
1314 		return this.iConnected;
1315 	}
1316 
1317 	@Override
1318 	public String toString() {
1319 		StringBuffer result = new StringBuffer();
1320 		result.append('[');
1321 		result.append("URI=");
1322 		result.append(this.iUrl);
1323 		result.append(", ");
1324 		result.append(this.iConnected ? "connected" : "not connected");
1325 		result.append(", ");
1326 		result.append(this.iKeepAlive ? "kept alive" : "not kept alive");
1327 		result.append(", ");
1328 		result.append(this.iUseHttp11 ? "HTTP 1.1" : "HTTP 1.0");
1329 		result.append(", ");
1330 		result.append("Method=");
1331 		result.append(this.iMethod);
1332 		result.append(", ");
1333 		result.append("Request Method=");
1334 		result.append(this.iRequestMethod);
1335 		result.append(", ");
1336 		result.append("Protocol=");
1337 		result.append(this.iSession != null ? this.iSession.getProtocol() : "http");
1338 		result.append(", ");
1339 		result.append("CipherSuite=");
1340 		result.append(this.iSession != null ? this.iSession.getCipherSuite() : "n/a");
1341 		result.append(']');
1342 		return result.toString();
1343 	}
1344 }