Index: class-http.php
===================================================================
--- class-http.php	(revision 15264)
+++ class-http.php	(working copy)
@@ -92,16 +92,24 @@
 			if ( true === WP_Http_ExtHttp::test($args) ) {
 				$working_transport['exthttp'] = new WP_Http_ExtHttp();
 				$blocking_transport[] = &$working_transport['exthttp'];
-			} else if ( true === WP_Http_Curl::test($args) ) {
+			}
+			if ( true === WP_Http_Curl::test($args) ) {
 				$working_transport['curl'] = new WP_Http_Curl();
 				$blocking_transport[] = &$working_transport['curl'];
-			} else if ( true === WP_Http_Streams::test($args) ) {
+			}
+			if ( true === WP_Http_Sockets::test($args) ) {
+				$working_transport['sockets'] = new WP_Http_Sockets();
+				$blocking_transport[] = &$working_transport['sockets'];
+			}
+			if ( true === WP_Http_Streams::test($args) ) {
 				$working_transport['streams'] = new WP_Http_Streams();
 				$blocking_transport[] = &$working_transport['streams'];
-			} else if ( true === WP_Http_Fopen::test($args) ) {
+			}
+			if ( true === WP_Http_Fopen::test($args) ) {
 				$working_transport['fopen'] = new WP_Http_Fopen();
 				$blocking_transport[] = &$working_transport['fopen'];
-			} else if ( true === WP_Http_Fsockopen::test($args) ) {
+			}
+			if ( true === WP_Http_Fsockopen::test($args) ) {
 				$working_transport['fsockopen'] = new WP_Http_Fsockopen();
 				$blocking_transport[] = &$working_transport['fsockopen'];
 			}
@@ -142,13 +150,20 @@
 			if ( true === WP_Http_ExtHttp::test($args) ) {
 				$working_transport['exthttp'] = new WP_Http_ExtHttp();
 				$blocking_transport[] = &$working_transport['exthttp'];
-			} else if ( true === WP_Http_Curl::test($args) ) {
+			}
+			if ( true === WP_Http_Curl::test($args) ) {
 				$working_transport['curl'] = new WP_Http_Curl();
 				$blocking_transport[] = &$working_transport['curl'];
-			} else if ( true === WP_Http_Streams::test($args) ) {
+			}
+			if ( true === WP_Http_Sockets::test($args) ) {
+				$working_transport['sockets'] = new WP_Http_Sockets();
+				$blocking_transport[] = &$working_transport['sockets'];
+			}
+			if ( true === WP_Http_Streams::test($args) ) {
 				$working_transport['streams'] = new WP_Http_Streams();
 				$blocking_transport[] = &$working_transport['streams'];
-			} else if ( true === WP_Http_Fsockopen::test($args) ) {
+			}
+			if ( true === WP_Http_Fsockopen::test($args) ) {
 				$working_transport['fsockopen'] = new WP_Http_Fsockopen();
 				$blocking_transport[] = &$working_transport['fsockopen'];
 			}
@@ -1110,6 +1125,248 @@
 }
 
 /**
+ * HTTP request method uses PHP5 sockets to retrieve the url.
+ *
+ * Actually, this works pretty well. It supports SSL, nonblocking, quick, handles redirection and,
+ * if cURL is not available and on PHP5, this will be preferred.
+ *
+ * @package WordPress
+ * @subpackage HTTP
+ * @since {unknown}
+ */
+class WP_Http_Sockets {
+	/**
+	 * Send a HTTP request to a URI using streams with stream_socket_client().
+	 *
+	 * @access public
+	 * @since {unknown}
+	 *
+	 * @param string $url
+	 * @param str|array $args Optional. Override the defaults.
+	 * @return array 'headers', 'body', 'cookies' and 'response' keys.
+	 */
+	function request($url, $args = array()) {
+		$defaults = array(
+			'method' => 'GET', 'timeout' => 5,
+			'redirection' => 5, 'httpversion' => '1.0',
+			'blocking' => true,
+			'headers' => array(), 'body' => null, 'cookies' => array()
+		);
+
+		$r = wp_parse_args( $args, $defaults );
+
+		// Construct Cookie: header if any cookies are set
+		WP_Http::buildCookieHeader( $r );
+
+		if ( isset($r['headers']['User-Agent']) ) {
+			$r['user-agent'] = $r['headers']['User-Agent'];
+			unset($r['headers']['User-Agent']);
+		} else if( isset($r['headers']['user-agent']) ) {
+			$r['user-agent'] = $r['headers']['user-agent'];
+			unset($r['headers']['user-agent']);
+		}
+
+		$arrURL = parse_url($url);
+
+		// Not the best check for URLs, but works for the purposes of what we are using it.
+		if ( false === $arrURL )
+			return new WP_Error('http_request_failed', sprintf(__('Malformed URL: %s'), $url));
+
+		// Only do this check once.
+		if ( ! isset($r['ssl']) )
+			$r['ssl'] = $arrURL['scheme'] == 'https' || $arrURL['scheme'] == 'ssl';
+
+		// Nonblocking only works for SSL PHP 5.2.11+, might not work on Windows.
+		// http://bugs.php.net/48182
+		if( $r['ssl'] && ! $r['blocking'] && version_compare(PHP_VERSION, '5.2.11', '<') )
+			return new WP_Error('http_request_failed', __('Not possible to do nonblocking request with SSL.'));
+
+		// Create socket context. You only have ssl and socket transports. Socket has few options,
+		// none that matter for HTTP requests. SSL has quite a few and we set those later.
+		$context = stream_context_create();
+
+		$host = "tcp://".$arrURL['host'].':'.( isset($arrURL['port']) ? $arrURL['port'] : '80' );
+
+		if ( $r['ssl'] && extension_loaded('openssl') && in_array( 'ssl', stream_get_transports() ) ) {
+			$host = "ssl://".$arrURL['host'].':'.( isset($arrURL['port']) ? $arrURL['port'] : '443' );
+
+			$is_local = isset($r['local']) && $r['local'];
+			$ssl_verify = isset($r['sslverify']) && $r['sslverify'];
+			if ( $is_local )
+				$ssl_verify = apply_filters('https_local_ssl_verify', $ssl_verify);
+			elseif ( ! $is_local )
+				$ssl_verify = apply_filters('https_ssl_verify', $ssl_verify);
+
+			stream_context_set_option($context, 'ssl', 'verify_peer', $ssl_verify);
+			stream_context_set_option($context, 'ssl', 'verify_host', $ssl_verify);
+		}
+
+		// This is the message body for the request, it includes the header and the body if available.
+		// It works similar to Fsockopen, in that it doesn't handle HTTP protocol directly.
+		$request_message = $this->_build_request_message($r, $url, $arrURL);
+
+		// Holds the error number.
+		$error_num = 0;
+
+		// Holds the error string.
+		$error_string = '';
+
+		$proxy = new WP_HTTP_Proxy();
+
+		$flags = STREAM_CLIENT_CONNECT;
+		if ( ! $r['blocking'] )
+			$flags = STREAM_CLIENT_ASYNC_CONNECT | STREAM_CLIENT_CONNECT;
+
+		// stream_socket_client only supports tcp and udp, change to tcp for HTTP protocol.
+		if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) )
+			$handle = stream_socket_client( $proxy->host().':'.$proxy->port(), $error_num, $error_string, $r['timeout'], $flags, $context );
+		else
+			$handle = stream_socket_client( $host, $error_num, $error_string, $r['timeout'], $flags, $context );
+
+		if ( ! $handle )
+			return new WP_Error('http_request_failed', sprintf(__('Could not open handle for stream_socket_client() to %s'), $url));
+
+		$timeout = (int) floor( $r['timeout'] );
+		$utimeout = $timeout == $r['timeout'] ? 0 : 1000000 * $r['timeout'] % 1000000;
+
+		if ( ! $r['blocking'] ) {
+			stream_set_blocking($handle, 0);
+			$read = $except = null;
+			$write = array($handle);
+			while ( ! empty( $request_message ) ) {
+				$nonblocking = stream_select($read, $write, $except, $timeout, $utimeout);
+
+				if ( $nonblocking === false )
+					return new WP_Error('http_request_failed', sprintf(__('Could not handle nonblocking request to %s'), $url));
+
+				// Short requests should complete after the first try, but longer ones will only
+				// send so much before kicking it back out.
+				$bytes_written = fwrite( $handle, $request_message, strlen($request_message) );
+
+				// Dare god I hope this works. May mess up with UTF8 strings, but the execution after will catch it.
+				if ( $bytes_written == strlen($request_message) )
+					break;
+
+				$request_message = substr($request_message, $bytes_written);
+			}
+
+			fclose($handle);
+			return array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() );
+		} else {
+			// We don't use stream_set_timeout with stream_select();
+			stream_set_timeout( $handle, $timeout, $utimeout );
+		}
+		
+		while ( ! empty( $request_message ) ) {
+			// Short requests should complete after the first try, but longer ones will only send so
+			// much before kicking it back out.
+			$bytes_written = fwrite( $handle, $request_message, strlen($request_message) );
+
+			if ( $bytes_written == strlen($request_message) )
+				break;
+
+			$request_message = substr($request_message, $bytes_written);
+		}
+
+		$response = stream_get_contents($handle);
+
+		fclose($handle);
+
+		$process = WP_Http::processResponse($response);
+		$arrHeaders = WP_Http::processHeaders($process['headers']);
+
+		// Is the response code within the 400 range?
+		if ( (int) $arrHeaders['response']['code'] >= 400 && (int) $arrHeaders['response']['code'] < 500 )
+			return new WP_Error('http_request_failed', $arrHeaders['response']['code'] . ': ' . $arrHeaders['response']['message']);
+
+		// If location is found, then assume redirect and redirect to location.
+		if ( 'HEAD' != $r['method'] && isset($arrHeaders['headers']['location']) ) {
+			if ( $r['redirection']-- > 0 ) {
+				return $this->request($arrHeaders['headers']['location'], $r);
+			} else {
+				return new WP_Error('http_request_failed', __('Too many redirects.'));
+			}
+		}
+
+		// If the body was chunk encoded, then decode it.
+		if ( ! empty( $process['body'] ) && isset( $arrHeaders['headers']['transfer-encoding'] ) && 'chunked' == $arrHeaders['headers']['transfer-encoding'] )
+			$process['body'] = WP_Http::chunkTransferDecode($process['body']);
+
+		if ( true === $r['decompress'] && true === WP_Http_Encoding::should_decode($arrHeaders['headers']) )
+			$process['body'] = WP_Http_Encoding::decompress( $process['body'] );
+
+		return array('headers' => $arrHeaders['headers'], 'body' => $process['body'], 'response' => $arrHeaders['response'], 'cookies' => $arrHeaders['cookies']);
+	}
+
+	/**
+	 * Builds the request message for sending to the URL.
+	 *
+	 * @access private
+	 * @since {unknown}
+	 *
+	 * @param array $r Request arguments.
+	 * @param string $url URL string.
+	 * @param array $arrURL Processed URL string.
+	 * @return string Full request message.
+	 */
+	function _build_request_message($r, $url, &$arrURL) {
+		$proxy = new WP_HTTP_Proxy();
+
+		// Create request headers and combine body.
+		if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) //Some proxies require full URL in this field.
+			$requestPath = $url;
+		else
+			$requestPath = $arrURL['path'] . ( isset($arrURL['query']) ? '?' . $arrURL['query'] : '' );
+
+		if ( empty($requestPath) )
+			$requestPath .= '/';
+
+		$strHeaders = strtoupper($r['method']) . ' ' . $requestPath . ' HTTP/' . $r['httpversion'] . "\r\n";
+
+		if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) )
+			$strHeaders .= 'Host: ' . $arrURL['host'] . ':' . $arrURL['port'] . "\r\n";
+		else
+			$strHeaders .= 'Host: ' . $arrURL['host'] . "\r\n";
+
+		if ( isset($r['user-agent']) )
+			$strHeaders .= 'User-agent: ' . $r['user-agent'] . "\r\n";
+
+		if ( is_array($r['headers']) ) {
+			foreach ( (array) $r['headers'] as $header => $headerValue )
+				$strHeaders .= $header . ': ' . $headerValue . "\r\n";
+		} else {
+			$strHeaders .= $r['headers'];
+		}
+
+		if ( $proxy->use_authentication() )
+			$strHeaders .= $proxy->authentication_header() . "\r\n";
+
+		$strHeaders .= "\r\n";
+
+		if ( ! is_null($r['body']) )
+			$strHeaders .= $r['body'];
+
+		return $strHeaders;
+	}
+
+	/**
+	 * Whether this class can be used for retrieving an URL.
+	 *
+	 * @static
+	 * @access public
+	 * @since {unknown}
+	 *
+	 * @return boolean False means this class can not be used, true means it can.
+	 */
+	function test($args = array()) {
+		if ( ! function_exists('stream_socket_client') )
+			return false;
+
+		return apply_filters('use_socket_transport', true, $args);
+	}
+}
+
+/**
  * HTTP request method uses HTTP extension to retrieve the url.
  *
  * Requires the HTTP extension to be installed. This would be the preferred transport since it can
