|
|
@@ -1,6 +1,4 @@
|
|
|
using System;
|
|
|
-using System.Collections.Generic;
|
|
|
-using System.Net;
|
|
|
using System.Net.Sockets;
|
|
|
using System.Security.Cryptography;
|
|
|
using System.Text;
|
|
|
@@ -18,6 +16,7 @@ using System.Globalization;
|
|
|
using System.Linq;
|
|
|
using Renci.SshNet.Abstractions;
|
|
|
using Renci.SshNet.Security.Cryptography;
|
|
|
+using System.Collections.Generic;
|
|
|
|
|
|
namespace Renci.SshNet
|
|
|
{
|
|
|
@@ -27,7 +26,7 @@ namespace Renci.SshNet
|
|
|
public class Session : ISession
|
|
|
{
|
|
|
private const byte Null = 0x00;
|
|
|
- private const byte CarriageReturn = 0x0d;
|
|
|
+ internal const byte CarriageReturn = 0x0d;
|
|
|
internal const byte LineFeed = 0x0a;
|
|
|
|
|
|
/// <summary>
|
|
|
@@ -52,7 +51,7 @@ namespace Renci.SshNet
|
|
|
/// <value>
|
|
|
/// 68536 (64 KB + 3000 bytes).
|
|
|
/// </value>
|
|
|
- private const int MaximumSshPacketSize = LocalChannelDataPacketSize + 3000;
|
|
|
+ internal const int MaximumSshPacketSize = LocalChannelDataPacketSize + 3000;
|
|
|
|
|
|
/// <summary>
|
|
|
/// Holds the initial local window size for the channels.
|
|
|
@@ -587,24 +586,8 @@ namespace Renci.SshNet
|
|
|
// Build list of available messages while connecting
|
|
|
_sshMessageFactory = new SshMessageFactory();
|
|
|
|
|
|
- switch (ConnectionInfo.ProxyType)
|
|
|
- {
|
|
|
- case ProxyTypes.None:
|
|
|
- SocketConnect(ConnectionInfo.Host, ConnectionInfo.Port);
|
|
|
- break;
|
|
|
- case ProxyTypes.Socks4:
|
|
|
- SocketConnect(ConnectionInfo.ProxyHost, ConnectionInfo.ProxyPort);
|
|
|
- ConnectSocks4(_socket, ConnectionInfo);
|
|
|
- break;
|
|
|
- case ProxyTypes.Socks5:
|
|
|
- SocketConnect(ConnectionInfo.ProxyHost, ConnectionInfo.ProxyPort);
|
|
|
- ConnectSocks5(_socket, ConnectionInfo);
|
|
|
- break;
|
|
|
- case ProxyTypes.Http:
|
|
|
- SocketConnect(ConnectionInfo.ProxyHost, ConnectionInfo.ProxyPort);
|
|
|
- ConnectHttp(_socket, ConnectionInfo);
|
|
|
- break;
|
|
|
- }
|
|
|
+ _socket = _serviceFactory.CreateConnector(ConnectionInfo)
|
|
|
+ .Connect(ConnectionInfo);
|
|
|
|
|
|
// Immediately send the identification string since the spec states both sides MUST send an identification string
|
|
|
// when the connection has been established
|
|
|
@@ -1698,54 +1681,6 @@ namespace Renci.SshNet
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// Establishes a socket connection to the specified host and port.
|
|
|
- /// </summary>
|
|
|
- /// <param name="host">The host name of the server to connect to.</param>
|
|
|
- /// <param name="port">The port to connect to.</param>
|
|
|
- /// <exception cref="SshOperationTimeoutException">The connection failed to establish within the configured <see cref="Renci.SshNet.ConnectionInfo.Timeout"/>.</exception>
|
|
|
- /// <exception cref="SocketException">An error occurred trying to establish the connection.</exception>
|
|
|
- private void SocketConnect(string host, int port)
|
|
|
- {
|
|
|
- var ipAddress = DnsAbstraction.GetHostAddresses(host)[0];
|
|
|
- var ep = new IPEndPoint(ipAddress, port);
|
|
|
-
|
|
|
- DiagnosticAbstraction.Log(string.Format("Initiating connection to '{0}:{1}'.", host, port));
|
|
|
-
|
|
|
- _socket = SocketAbstraction.Connect(ep, ConnectionInfo.Timeout);
|
|
|
-
|
|
|
- const int socketBufferSize = 2 * MaximumSshPacketSize;
|
|
|
- _socket.SendBufferSize = socketBufferSize;
|
|
|
- _socket.ReceiveBufferSize = socketBufferSize;
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Performs a blocking read on the socket until <paramref name="length"/> bytes are received.
|
|
|
- /// </summary>
|
|
|
- /// <param name="socket">The <see cref="Socket"/> to read from.</param>
|
|
|
- /// <param name="buffer">An array of type <see cref="byte"/> that is the storage location for the received data.</param>
|
|
|
- /// <param name="offset">The position in <paramref name="buffer"/> parameter to store the received data.</param>
|
|
|
- /// <param name="length">The number of bytes to read.</param>
|
|
|
- /// <returns>
|
|
|
- /// The number of bytes read.
|
|
|
- /// </returns>
|
|
|
- /// <exception cref="SshConnectionException">The socket is closed.</exception>
|
|
|
- /// <exception cref="SshOperationTimeoutException">The read has timed-out.</exception>
|
|
|
- /// <exception cref="SocketException">The read failed.</exception>
|
|
|
- private static int SocketRead(Socket socket, byte[] buffer, int offset, int length)
|
|
|
- {
|
|
|
- var bytesRead = SocketAbstraction.Read(socket, buffer, offset, length, InfiniteTimeSpan);
|
|
|
- if (bytesRead == 0)
|
|
|
- {
|
|
|
- // when we're in the disconnecting state (either triggered by client or server), then the
|
|
|
- // SshConnectionException will interrupt the message listener loop (if not already interrupted)
|
|
|
- // and the exception itself will be ignored (in RaiseError)
|
|
|
- throw new SshConnectionException("An established connection was aborted by the server.",
|
|
|
- DisconnectReason.ConnectionLost);
|
|
|
- }
|
|
|
- return bytesRead;
|
|
|
- }
|
|
|
-
|
|
|
#if FEATURE_SOCKET_POLL
|
|
|
/// <summary>
|
|
|
/// Gets a value indicating whether the socket is connected.
|
|
|
@@ -1896,13 +1831,16 @@ namespace Renci.SshNet
|
|
|
{
|
|
|
DiagnosticAbstraction.Log(string.Format("[{0}] Shutting down socket.", ToHex(SessionId)));
|
|
|
|
|
|
- // interrupt any pending reads; should be done outside of socket read lock as we
|
|
|
- // actually want shutdown the socket to make sure blocking reads are interrupted
|
|
|
+ // Interrupt any pending reads; should be done outside of socket read lock as we
|
|
|
+ // actually want shutdown the socket to make sure blocking reads are interrupted.
|
|
|
//
|
|
|
- // this may result in a SocketException (eg. An existing connection was forcibly
|
|
|
+ // This may result in a SocketException (eg. An existing connection was forcibly
|
|
|
// closed by the remote host) which we'll log and ignore as it means the socket
|
|
|
- // was already shut down
|
|
|
- _socket.Shutdown(SocketShutdown.Send);
|
|
|
+ // was already shut down.
|
|
|
+ //
|
|
|
+ // We use SocketShutdown.Both instead of SocketShutdown.Send as a workaround to a
|
|
|
+ // .NET Core issue on Linux & Mac OS X.
|
|
|
+ _socket.Shutdown(SocketShutdown.Both);
|
|
|
}
|
|
|
catch (SocketException ex)
|
|
|
{
|
|
|
@@ -2026,392 +1964,6 @@ namespace Renci.SshNet
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private static byte SocketReadByte(Socket socket)
|
|
|
- {
|
|
|
- var buffer = new byte[1];
|
|
|
- SocketRead(socket, buffer, 0, 1);
|
|
|
- return buffer[0];
|
|
|
- }
|
|
|
-
|
|
|
- private static void ConnectSocks4(Socket socket, ConnectionInfo connectionInfo)
|
|
|
- {
|
|
|
- var connectionRequest = CreateSocks4ConnectionRequest(connectionInfo.Host, (ushort)connectionInfo.Port, connectionInfo.ProxyUsername);
|
|
|
- SocketAbstraction.Send(socket, connectionRequest);
|
|
|
-
|
|
|
- // Read null byte
|
|
|
- if (SocketReadByte(socket) != 0)
|
|
|
- {
|
|
|
- throw new ProxyException("SOCKS4: Null is expected.");
|
|
|
- }
|
|
|
-
|
|
|
- // Read response code
|
|
|
- var code = SocketReadByte(socket);
|
|
|
-
|
|
|
- switch (code)
|
|
|
- {
|
|
|
- case 0x5a:
|
|
|
- break;
|
|
|
- case 0x5b:
|
|
|
- throw new ProxyException("SOCKS4: Connection rejected.");
|
|
|
- case 0x5c:
|
|
|
- throw new ProxyException("SOCKS4: Client is not running identd or not reachable from the server.");
|
|
|
- case 0x5d:
|
|
|
- throw new ProxyException("SOCKS4: Client's identd could not confirm the user ID string in the request.");
|
|
|
- default:
|
|
|
- throw new ProxyException("SOCKS4: Not valid response.");
|
|
|
- }
|
|
|
-
|
|
|
- var dummyBuffer = new byte[6]; // field 3 (2 bytes) and field 4 (4) should be ignored
|
|
|
- SocketRead(socket, dummyBuffer, 0, 6);
|
|
|
- }
|
|
|
-
|
|
|
- private static void ConnectSocks5(Socket socket, ConnectionInfo connectionInfo)
|
|
|
- {
|
|
|
- var greeting = new byte[]
|
|
|
- {
|
|
|
- // SOCKS version number
|
|
|
- 0x05,
|
|
|
- // Number of supported authentication methods
|
|
|
- 0x02,
|
|
|
- // No authentication
|
|
|
- 0x00,
|
|
|
- // Username/Password authentication
|
|
|
- 0x02
|
|
|
- };
|
|
|
- SocketAbstraction.Send(socket, greeting);
|
|
|
-
|
|
|
- var socksVersion = SocketReadByte(socket);
|
|
|
- if (socksVersion != 0x05)
|
|
|
- throw new ProxyException(string.Format("SOCKS Version '{0}' is not supported.", socksVersion));
|
|
|
-
|
|
|
- var authenticationMethod = SocketReadByte(socket);
|
|
|
- switch (authenticationMethod)
|
|
|
- {
|
|
|
- case 0x00:
|
|
|
- break;
|
|
|
- case 0x02:
|
|
|
- // Create username/password authentication request
|
|
|
- var authenticationRequest = CreateSocks5UserNameAndPasswordAuthenticationRequest(connectionInfo.ProxyUsername, connectionInfo.ProxyPassword);
|
|
|
- // Send authentication request
|
|
|
- SocketAbstraction.Send(socket, authenticationRequest);
|
|
|
- // Read authentication result
|
|
|
- var authenticationResult = SocketAbstraction.Read(socket, 2, connectionInfo.Timeout);
|
|
|
-
|
|
|
- if (authenticationResult[0] != 0x01)
|
|
|
- throw new ProxyException("SOCKS5: Server authentication version is not valid.");
|
|
|
- if (authenticationResult[1] != 0x00)
|
|
|
- throw new ProxyException("SOCKS5: Username/Password authentication failed.");
|
|
|
- break;
|
|
|
- case 0xFF:
|
|
|
- throw new ProxyException("SOCKS5: No acceptable authentication methods were offered.");
|
|
|
- }
|
|
|
-
|
|
|
- var connectionRequest = CreateSocks5ConnectionRequest(connectionInfo.Host, (ushort)connectionInfo.Port);
|
|
|
- SocketAbstraction.Send(socket, connectionRequest);
|
|
|
-
|
|
|
- // Read Server SOCKS5 version
|
|
|
- if (SocketReadByte(socket) != 5)
|
|
|
- {
|
|
|
- throw new ProxyException("SOCKS5: Version 5 is expected.");
|
|
|
- }
|
|
|
-
|
|
|
- // Read response code
|
|
|
- var status = SocketReadByte(socket);
|
|
|
-
|
|
|
- switch (status)
|
|
|
- {
|
|
|
- case 0x00:
|
|
|
- break;
|
|
|
- case 0x01:
|
|
|
- throw new ProxyException("SOCKS5: General failure.");
|
|
|
- case 0x02:
|
|
|
- throw new ProxyException("SOCKS5: Connection not allowed by ruleset.");
|
|
|
- case 0x03:
|
|
|
- throw new ProxyException("SOCKS5: Network unreachable.");
|
|
|
- case 0x04:
|
|
|
- throw new ProxyException("SOCKS5: Host unreachable.");
|
|
|
- case 0x05:
|
|
|
- throw new ProxyException("SOCKS5: Connection refused by destination host.");
|
|
|
- case 0x06:
|
|
|
- throw new ProxyException("SOCKS5: TTL expired.");
|
|
|
- case 0x07:
|
|
|
- throw new ProxyException("SOCKS5: Command not supported or protocol error.");
|
|
|
- case 0x08:
|
|
|
- throw new ProxyException("SOCKS5: Address type not supported.");
|
|
|
- default:
|
|
|
- throw new ProxyException("SOCKS5: Not valid response.");
|
|
|
- }
|
|
|
-
|
|
|
- // Read reserved byte
|
|
|
- if (SocketReadByte(socket) != 0)
|
|
|
- {
|
|
|
- throw new ProxyException("SOCKS5: 0 byte is expected.");
|
|
|
- }
|
|
|
-
|
|
|
- var addressType = SocketReadByte(socket);
|
|
|
- switch (addressType)
|
|
|
- {
|
|
|
- case 0x01:
|
|
|
- var ipv4 = new byte[4];
|
|
|
- SocketRead(socket, ipv4, 0, 4);
|
|
|
- break;
|
|
|
- case 0x04:
|
|
|
- var ipv6 = new byte[16];
|
|
|
- SocketRead(socket, ipv6, 0, 16);
|
|
|
- break;
|
|
|
- default:
|
|
|
- throw new ProxyException(string.Format("Address type '{0}' is not supported.", addressType));
|
|
|
- }
|
|
|
-
|
|
|
- var port = new byte[2];
|
|
|
-
|
|
|
- // Read 2 bytes to be ignored
|
|
|
- SocketRead(socket, port, 0, 2);
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// https://tools.ietf.org/html/rfc1929
|
|
|
- /// </summary>
|
|
|
- private static byte[] CreateSocks5UserNameAndPasswordAuthenticationRequest(string username, string password)
|
|
|
- {
|
|
|
- if (username.Length > byte.MaxValue)
|
|
|
- throw new ProxyException("Proxy username is too long.");
|
|
|
- if (password.Length > byte.MaxValue)
|
|
|
- throw new ProxyException("Proxy password is too long.");
|
|
|
-
|
|
|
- var authenticationRequest = new byte
|
|
|
- [
|
|
|
- // Version of the negotiation
|
|
|
- 1 +
|
|
|
- // Length of the username
|
|
|
- 1 +
|
|
|
- // Username
|
|
|
- username.Length +
|
|
|
- // Length of the password
|
|
|
- 1 +
|
|
|
- // Password
|
|
|
- password.Length
|
|
|
- ];
|
|
|
-
|
|
|
- var index = 0;
|
|
|
-
|
|
|
- // Version of the negiotiation
|
|
|
- authenticationRequest[index++] = 0x01;
|
|
|
-
|
|
|
- // Length of the username
|
|
|
- authenticationRequest[index++] = (byte) username.Length;
|
|
|
-
|
|
|
- // Username
|
|
|
- SshData.Ascii.GetBytes(username, 0, username.Length, authenticationRequest, index);
|
|
|
- index += username.Length;
|
|
|
-
|
|
|
- // Length of the password
|
|
|
- authenticationRequest[index++] = (byte) password.Length;
|
|
|
-
|
|
|
- // Password
|
|
|
- SshData.Ascii.GetBytes(password, 0, password.Length, authenticationRequest, index);
|
|
|
-
|
|
|
- return authenticationRequest;
|
|
|
- }
|
|
|
-
|
|
|
- private static byte[] CreateSocks4ConnectionRequest(string hostname, ushort port, string username)
|
|
|
- {
|
|
|
- var addressBytes = GetSocks4DestinationAddress(hostname);
|
|
|
-
|
|
|
- var connectionRequest = new byte
|
|
|
- [
|
|
|
- // SOCKS version number
|
|
|
- 1 +
|
|
|
- // Command code
|
|
|
- 1 +
|
|
|
- // Port number
|
|
|
- 2 +
|
|
|
- // IP address
|
|
|
- addressBytes.Length +
|
|
|
- // Username
|
|
|
- username.Length +
|
|
|
- // Null terminator
|
|
|
- 1
|
|
|
- ];
|
|
|
-
|
|
|
- var index = 0;
|
|
|
-
|
|
|
- // SOCKS version number
|
|
|
- connectionRequest[index++] = 0x04;
|
|
|
-
|
|
|
- // Command code
|
|
|
- connectionRequest[index++] = 0x01; // establish a TCP/IP stream connection
|
|
|
-
|
|
|
- // Port number
|
|
|
- Pack.UInt16ToBigEndian(port, connectionRequest, index);
|
|
|
- index += 2;
|
|
|
-
|
|
|
- // Address
|
|
|
- Buffer.BlockCopy(addressBytes, 0, connectionRequest, index, addressBytes.Length);
|
|
|
- index += addressBytes.Length;
|
|
|
-
|
|
|
- connectionRequest[index] = 0x00;
|
|
|
-
|
|
|
- return connectionRequest;
|
|
|
- }
|
|
|
-
|
|
|
- private static byte[] CreateSocks5ConnectionRequest(string hostname, ushort port)
|
|
|
- {
|
|
|
- byte addressType;
|
|
|
- var addressBytes = GetSocks5DestinationAddress(hostname, out addressType);
|
|
|
-
|
|
|
- var connectionRequest = new byte
|
|
|
- [
|
|
|
- // SOCKS version number
|
|
|
- 1 +
|
|
|
- // Command code
|
|
|
- 1 +
|
|
|
- // Reserved
|
|
|
- 1 +
|
|
|
- // Address type
|
|
|
- 1 +
|
|
|
- // Address
|
|
|
- addressBytes.Length +
|
|
|
- // Port number
|
|
|
- 2
|
|
|
- ];
|
|
|
-
|
|
|
- var index = 0;
|
|
|
-
|
|
|
- // SOCKS version number
|
|
|
- connectionRequest[index++] = 0x05;
|
|
|
-
|
|
|
- // Command code
|
|
|
- connectionRequest[index++] = 0x01; // establish a TCP/IP stream connection
|
|
|
-
|
|
|
- // Reserved
|
|
|
- connectionRequest[index++] = 0x00;
|
|
|
-
|
|
|
- // Address type
|
|
|
- connectionRequest[index++] = addressType;
|
|
|
-
|
|
|
- // Address
|
|
|
- Buffer.BlockCopy(addressBytes, 0, connectionRequest, index, addressBytes.Length);
|
|
|
- index += addressBytes.Length;
|
|
|
-
|
|
|
- // Port number
|
|
|
- Pack.UInt16ToBigEndian(port, connectionRequest, index);
|
|
|
-
|
|
|
- return connectionRequest;
|
|
|
- }
|
|
|
-
|
|
|
- private static byte[] GetSocks4DestinationAddress(string hostname)
|
|
|
- {
|
|
|
- var addresses = DnsAbstraction.GetHostAddresses(hostname);
|
|
|
-
|
|
|
- for (var i = 0; i < addresses.Length; i++)
|
|
|
- {
|
|
|
- var address = addresses[i];
|
|
|
- if (address.AddressFamily == AddressFamily.InterNetwork)
|
|
|
- return address.GetAddressBytes();
|
|
|
- }
|
|
|
-
|
|
|
- throw new ProxyException(string.Format("SOCKS4 only supports IPv4. No such address found for '{0}'.", hostname));
|
|
|
- }
|
|
|
-
|
|
|
- private static byte[] GetSocks5DestinationAddress(string hostname, out byte addressType)
|
|
|
- {
|
|
|
- var ip = DnsAbstraction.GetHostAddresses(hostname)[0];
|
|
|
-
|
|
|
- byte[] address;
|
|
|
-
|
|
|
- switch (ip.AddressFamily)
|
|
|
- {
|
|
|
- case AddressFamily.InterNetwork:
|
|
|
- addressType = 0x01; // IPv4
|
|
|
- address = ip.GetAddressBytes();
|
|
|
- break;
|
|
|
- case AddressFamily.InterNetworkV6:
|
|
|
- addressType = 0x04; // IPv6
|
|
|
- address = ip.GetAddressBytes();
|
|
|
- break;
|
|
|
- default:
|
|
|
- throw new ProxyException(string.Format("SOCKS5: IP address '{0}' is not supported.", ip));
|
|
|
- }
|
|
|
-
|
|
|
- return address;
|
|
|
- }
|
|
|
-
|
|
|
- private static void ConnectHttp(Socket socket, ConnectionInfo connectionInfo)
|
|
|
- {
|
|
|
- var httpResponseRe = new Regex(@"HTTP/(?<version>\d[.]\d) (?<statusCode>\d{3}) (?<reasonPhrase>.+)$");
|
|
|
- var httpHeaderRe = new Regex(@"(?<fieldName>[^\[\]()<>@,;:\""/?={} \t]+):(?<fieldValue>.+)?");
|
|
|
-
|
|
|
- SocketAbstraction.Send(socket, SshData.Ascii.GetBytes(string.Format("CONNECT {0}:{1} HTTP/1.0\r\n", connectionInfo.Host, connectionInfo.Port)));
|
|
|
-
|
|
|
- // Sent proxy authorization is specified
|
|
|
- if (!string.IsNullOrEmpty(connectionInfo.ProxyUsername))
|
|
|
- {
|
|
|
- var authorization = string.Format("Proxy-Authorization: Basic {0}\r\n",
|
|
|
- Convert.ToBase64String(SshData.Ascii.GetBytes(string.Format("{0}:{1}", connectionInfo.ProxyUsername, connectionInfo.ProxyPassword)))
|
|
|
- );
|
|
|
- SocketAbstraction.Send(socket, SshData.Ascii.GetBytes(authorization));
|
|
|
- }
|
|
|
-
|
|
|
- SocketAbstraction.Send(socket, SshData.Ascii.GetBytes("\r\n"));
|
|
|
-
|
|
|
- HttpStatusCode? statusCode = null;
|
|
|
- var contentLength = 0;
|
|
|
-
|
|
|
- while (true)
|
|
|
- {
|
|
|
- var response = SocketReadLine(socket, connectionInfo.Timeout);
|
|
|
- if (response == null)
|
|
|
- // server shut down socket
|
|
|
- break;
|
|
|
-
|
|
|
- if (statusCode == null)
|
|
|
- {
|
|
|
- var statusMatch = httpResponseRe.Match(response);
|
|
|
- if (statusMatch.Success)
|
|
|
- {
|
|
|
- var httpStatusCode = statusMatch.Result("${statusCode}");
|
|
|
- statusCode = (HttpStatusCode) int.Parse(httpStatusCode);
|
|
|
- if (statusCode != HttpStatusCode.OK)
|
|
|
- {
|
|
|
- var reasonPhrase = statusMatch.Result("${reasonPhrase}");
|
|
|
- throw new ProxyException(string.Format("HTTP: Status code {0}, \"{1}\"", httpStatusCode,
|
|
|
- reasonPhrase));
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- // continue on parsing message headers coming from the server
|
|
|
- var headerMatch = httpHeaderRe.Match(response);
|
|
|
- if (headerMatch.Success)
|
|
|
- {
|
|
|
- var fieldName = headerMatch.Result("${fieldName}");
|
|
|
- if (fieldName.Equals("Content-Length", StringComparison.OrdinalIgnoreCase))
|
|
|
- {
|
|
|
- contentLength = int.Parse(headerMatch.Result("${fieldValue}"));
|
|
|
- }
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- // check if we've reached the CRLF which separates request line and headers from the message body
|
|
|
- if (response.Length == 0)
|
|
|
- {
|
|
|
- // read response body if specified
|
|
|
- if (contentLength > 0)
|
|
|
- {
|
|
|
- var contentBody = new byte[contentLength];
|
|
|
- SocketRead(socket, contentBody, 0, contentLength);
|
|
|
- }
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (statusCode == null)
|
|
|
- throw new ProxyException("HTTP response does not contain status line.");
|
|
|
- }
|
|
|
-
|
|
|
/// <summary>
|
|
|
/// Raises the <see cref="ErrorOccured"/> event.
|
|
|
/// </summary>
|