/** * 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)) { throw new EHttpClientException(Yii::t('EHttpClient', "Error writing request to proxy server")); } // 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 (EHttpResponse::extractCode($response) != 200) { throw new EHttpClientException(Yii::t('EHttpClient', "Unable to connect to HTTPS proxy. Server response: " . $response)); } // 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) { throw new EHttpClientException(Yii::t('EHttpClient', "Unable to connect to" . " HTTPS server through proxy: could not negotiate secure connection.")); } }
/** * Read response from server * * @return string */ public function read() { // First, read headers only $response = ''; $gotStatus = false; while ($line = @fgets($this->socket)) { $gotStatus = $gotStatus || strpos($line, 'HTTP') !== false; if ($gotStatus) { $response .= $line; if (!chop($line)) { break; } } } $statusCode = EHttpResponse::extractCode($response); // Handle 100 and 101 responses internally by restarting the read again if ($statusCode == 100 || $statusCode == 101) { return $this->read(); } /** * 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 == EHttpClient::HEAD) { return $response; } // Check headers to see what kind of connection / transfer encoding we have $headers = EHttpResponse::extractHeaders($response); // if the connection is set to close, just read until socket closes if (isset($headers['connection']) && $headers['connection'] == 'close') { while ($buff = @fread($this->socket, 8192)) { $response .= $buff; } $this->close(); // Else, if we got a transfer-encoding header (chunked body) } elseif (isset($headers['transfer-encoding'])) { if ($headers['transfer-encoding'] == 'chunked') { do { $chunk = ''; $line = @fgets($this->socket); $chunk .= $line; $hexchunksize = ltrim(chop($line), '0'); $hexchunksize = strlen($hexchunksize) ? strtolower($hexchunksize) : 0; $chunksize = hexdec(chop($line)); if (dechex($chunksize) != $hexchunksize) { @fclose($this->socket); throw new EHttpClientException(Yii::t('EHttpClient', 'Invalid chunk size "' . $hexchunksize . '" unable to read chunked body')); } $left_to_read = $chunksize; while ($left_to_read > 0) { $line = @fread($this->socket, $left_to_read); $chunk .= $line; $left_to_read -= strlen($line); } $chunk .= @fgets($this->socket); $response .= $chunk; } while ($chunksize > 0); } else { throw new EHttpClientException(Yii::t('EHttpClient', 'Cannot handle "' . $headers['transfer-encoding'] . '" transfer encoding')); } // Else, if we got the content-length header, read this number of bytes } elseif (isset($headers['content-length'])) { $left_to_read = $headers['content-length']; $chunk = ''; while ($left_to_read > 0) { $chunk = @fread($this->socket, $left_to_read); $left_to_read -= strlen($chunk); $response .= $chunk; } // Fallback: just read the response (should not happen) } else { while ($buff = @fread($this->socket, 8192)) { $response .= $buff; } $this->close(); } return $response; }