/** * Read response from server * * @return string */ public function read() { // First, read headers only $response = ""; $gotStatus = false; while (($line = @fgets($this->socket)) !== false) { $gotStatus = $gotStatus || strpos($line, "HTTP") !== false; if ($gotStatus) { $response .= $line; if (rtrim($line) === "") { break; } } } $this->_checkSocketReadTimeout(); $statusCode = Sabel_Http_Response::extractCode($response); // Handle 100 and 101 responses internally by restarting the read again if ($statusCode == 100 || $statusCode == 101) { return $this->read(); } // Check headers to see what kind of connection / transfer encoding we have $headers = Sabel_Http_Response::extractHeaders($response); /** * Responses to HEAD requests and 204 or 304 responses are not expected * to have a body - stop reading here */ if ($statusCode == 304 || $statusCode == 204 || $this->method === Sabel_Http_Client::HEAD) { // Close the connection if requested to do so by the server if (isset($headers["connection"]) && $headers["connection"] === "close") { $this->close(); } return $response; } // If we got a 'transfer-encoding: chunked' header if (isset($headers["transfer-encoding"])) { if (strtolower($headers["transfer-encoding"]) === "chunked") { do { $line = @fgets($this->socket); $this->_checkSocketReadTimeout(); $chunk = $line; // Figure out the next chunk size $chunksize = trim($line); if (!ctype_xdigit($chunksize)) { $this->close(); $message = __METHOD__ . "() Unable to read chunk body."; throw new Sabel_Exception_Runtime($message); } // Convert the hexadecimal value to plain integer $chunksize = hexdec($chunksize); // Read next chunk $read_to = ftell($this->socket) + $chunksize; do { $current_pos = ftell($this->socket); if ($current_pos >= $read_to) { break; } $line = @fread($this->socket, $read_to - $current_pos); if ($line === false || strlen($line) === 0) { $this->_checkSocketReadTimeout(); break; } else { $chunk .= $line; } } while (!feof($this->socket)); $chunk .= @fgets($this->socket); $this->_checkSocketReadTimeout(); $response .= $chunk; } while ($chunksize > 0); } else { $this->close(); $message = __METHOD__ . "() Cannot handle '{$headers['transfer-encoding']}' transfer encoding. "; throw new Sabel_Exception_Runtime($message); } } elseif (isset($headers["content-length"])) { $current_pos = ftell($this->socket); $chunk = ""; for ($read_to = $current_pos + $headers["content-length"]; $read_to > $current_pos; $current_pos = ftell($this->socket)) { $chunk = @fread($this->socket, $read_to - $current_pos); if ($chunk === false || strlen($chunk) === 0) { $this->_checkSocketReadTimeout(); break; } $response .= $chunk; // Break if the connection ended prematurely if (feof($this->socket)) { break; } } } else { do { $buff = @fread($this->socket, 8192); if ($buff === false || strlen($buff) === 0) { $this->_checkSocketReadTimeout(); break; } else { $response .= $buff; } } while (feof($this->socket) === false); $this->close(); } // Close the connection if requested to do so by the server if (isset($headers["connection"]) && $headers["connection"] === "close") { $this->close(); } return $response; }
/** * Preform handshaking with HTTPS proxy using CONNECT method * * @param string $host * @param integer $port * @param string $http_ver * @param array $headers */ protected function connectHandshake($host, $port = 443, $http_ver = "1.1", array &$headers = array()) { $request = "CONNECT {$host}:{$port} HTTP/{$http_ver}\r\n" . "Host: " . $this->config["proxy_host"] . "\r\n"; // Add the user-agent header if (isset($this->config["useragent"])) { $request .= "User-agent: " . $this->config["useragent"] . "\r\n"; } // If the proxy-authorization header is set, send it to proxy but remove // it from headers sent to target host if (isset($headers["proxy-authorization"])) { $request .= "Proxy-authorization: " . $headers["proxy-authorization"] . "\r\n"; unset($headers["proxy-authorization"]); } $request .= "\r\n"; // Send the request if (!@fwrite($this->socket, $request)) { $message = __METHOD__ . "() Error writing request to proxy server."; throw new Sabel_Exception_Runtime($message); } // Read response headers only $response = ""; $gotStatus = false; while ($line = @fgets($this->socket)) { $gotStatus = $gotStatus || strpos($line, "HTTP") !== false; if ($gotStatus) { $response .= $line; if (!chop($line)) { break; } } } // Check that the response from the proxy is 200 if (Sabel_Http_Response::extractCode($response) != 200) { $message = __METHOD__ . "() Unable to connect to HTTPS proxy. Server response: {$response}."; throw new Sabel_Exception_Runtime($message); } // If all is good, switch socket to secure mode. We have to fall back // through the different modes $modes = array(STREAM_CRYPTO_METHOD_TLS_CLIENT, STREAM_CRYPTO_METHOD_SSLv3_CLIENT, STREAM_CRYPTO_METHOD_SSLv23_CLIENT, STREAM_CRYPTO_METHOD_SSLv2_CLIENT); $success = false; foreach ($modes as $mode) { $success = stream_socket_enable_crypto($this->socket, true, $mode); if ($success) { break; } } if (!$success) { $message = __METHOD__ . "() Unable to connect to HTTPS server through " . "proxy: could not negotiate secure connection."; throw new Sabel_Exception_Runtime($message); } }