浏览代码

Skip headers (and message body if Content-Length headers is set) returned by HTTP proxy. Fail fast when proxy does not return HTTP 200.
Added corresponding regression tests.
Fixes the following issues: 1601, 1890 and 1905.

Gert Driesen 11 年之前
父节点
当前提交
8bea7cb388

+ 13 - 1
Renci.SshClient/Renci.SshNet.Tests.NET35/Renci.SshNet.Tests.NET35.csproj

@@ -529,6 +529,9 @@
     <Compile Include="..\Renci.SshNet.Tests\Classes\SessionTest.cs">
       <Link>Classes\SessionTest.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet.Tests\Classes\SessionTest.HttpProxy.cs">
+      <Link>Classes\SessionTest.HttpProxy.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet.Tests\Classes\SftpClientTest.ChangeDirectory.cs">
       <Link>Classes\SftpClientTest.ChangeDirectory.cs</Link>
     </Compile>
@@ -700,6 +703,15 @@
     <Compile Include="..\Renci.SshNet.Tests\Classes\SshCommandTest.cs">
       <Link>Classes\SshCommandTest.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet.Tests\Common\AsyncSocketListener.cs">
+      <Link>Common\AsyncSocketListener.cs</Link>
+    </Compile>
+    <Compile Include="..\Renci.SshNet.Tests\Common\HttpProxyStub.cs">
+      <Link>Common\HttpProxyStub.cs</Link>
+    </Compile>
+    <Compile Include="..\Renci.SshNet.Tests\Common\HttpRequest.cs">
+      <Link>Common\HttpRequest.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet.Tests\Common\TestBase.cs">
       <Link>Common\TestBase.cs</Link>
     </Compile>
@@ -752,7 +764,7 @@
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
   <ProjectExtensions>
     <VisualStudio>
-      <UserProperties ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" ProjectLinkReference="c45379b9-17b1-4e89-bc2e-6d41726413e8" />
+      <UserProperties ProjectLinkReference="c45379b9-17b1-4e89-bc2e-6d41726413e8" ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" />
     </VisualStudio>
   </ProjectExtensions>
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 

+ 236 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/SessionTest.HttpProxy.cs

@@ -0,0 +1,236 @@
+using System;
+using System.Net;
+using System.Linq;
+using System.Text;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Renci.SshNet.Common;
+using Renci.SshNet.Tests.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    public partial class SessionTest
+    {
+        [TestMethod]
+        public void ConnectShouldThrowProxyExceptionWhenHttpProxyReturnsHttpStatusOtherThan200()
+        {
+            var proxyEndPoint = new IPEndPoint(IPAddress.Loopback, 8123);
+            var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
+
+            using (var proxyStub = new HttpProxyStub(proxyEndPoint, "A"))
+            {
+                proxyStub.Responses.Add(Encoding.ASCII.GetBytes("HTTP/1.0 501 Custom\r\n"));
+                proxyStub.Start();
+
+                using (var session = new Session(CreateConnectionInfoWithProxy(proxyEndPoint, serverEndPoint, "anon")))
+                {
+                    try
+                    {
+                        session.Connect();
+                        Assert.Fail();
+                    }
+                    catch (ProxyException ex)
+                    {
+                        Assert.IsNull(ex.InnerException);
+                        Assert.AreEqual("HTTP: Status code 501, \"Custom\"", ex.Message);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void ConnectShouldSkipHeadersWhenHttpProxyReturnsHttpStatus200()
+        {
+            var proxyEndPoint = new IPEndPoint(IPAddress.Loopback, 8123);
+            var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
+
+            using (var proxyStub = new HttpProxyStub(proxyEndPoint, "B"))
+            {
+                proxyStub.Responses.Add(Encoding.ASCII.GetBytes("HTTP/1.0 200 OK\r\n"));
+                proxyStub.Responses.Add(Encoding.ASCII.GetBytes("Content-Type: application/octet-stream\r\n"));
+                proxyStub.Responses.Add(Encoding.ASCII.GetBytes("\r\n"));
+                proxyStub.Responses.Add(Encoding.ASCII.GetBytes("SSH-666-SshStub"));
+                proxyStub.Start();
+
+                using (var session = new Session(CreateConnectionInfoWithProxy(proxyEndPoint, serverEndPoint, "anon")))
+                {
+                    try
+                    {
+                        session.Connect();
+                        Assert.Fail();
+                    }
+                    catch (SshConnectionException ex)
+                    {
+                        Assert.IsNull(ex.InnerException);
+                        Assert.AreEqual("Server version '666' is not supported.", ex.Message);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void ConnectShouldSkipContentWhenHttpProxyReturnsHttpStatus200()
+        {
+            var proxyEndPoint = new IPEndPoint(IPAddress.Loopback, 8123);
+            var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
+
+            using (var proxyStub = new HttpProxyStub(proxyEndPoint, "B"))
+            {
+                proxyStub.Responses.Add(Encoding.ASCII.GetBytes("HTTP/1.0 200 OK\r\n"));
+                proxyStub.Responses.Add(Encoding.ASCII.GetBytes("Content-Length: 13\r\n"));
+                proxyStub.Responses.Add(Encoding.ASCII.GetBytes("Content-Type: application/octet-stream\r\n"));
+                proxyStub.Responses.Add(Encoding.ASCII.GetBytes("\r\n"));
+                proxyStub.Responses.Add(Encoding.ASCII.GetBytes("DUMMY_CONTENT"));
+                proxyStub.Responses.Add(Encoding.ASCII.GetBytes("SSH-666-SshStub"));
+                proxyStub.Start();
+
+                using (var session = new Session(CreateConnectionInfoWithProxy(proxyEndPoint, serverEndPoint, "anon")))
+                {
+                    try
+                    {
+                        session.Connect();
+                        Assert.Fail();
+                    }
+                    catch (SshConnectionException ex)
+                    {
+                        Assert.IsNull(ex.InnerException);
+                        Assert.AreEqual("Server version '666' is not supported.", ex.Message);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void ConnectShouldWriteConnectMethodToHttpProxy()
+        {
+            var proxyEndPoint = new IPEndPoint(IPAddress.Loopback, 8123);
+            var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
+
+            using (var proxyStub = new HttpProxyStub(proxyEndPoint, "A"))
+            {
+                proxyStub.Responses.Add(Encoding.ASCII.GetBytes("HTTP/1.0 501 Custom\r\n"));
+                proxyStub.Start();
+
+                using (var session = new Session(CreateConnectionInfoWithProxy(proxyEndPoint, serverEndPoint, "anon")))
+                {
+                    try
+                    {
+                        session.Connect();
+                        Assert.Fail();
+                    }
+                    catch (ProxyException)
+                    {
+                    }
+                }
+
+                Assert.AreEqual(string.Format("CONNECT {0} HTTP/1.0", serverEndPoint), proxyStub.HttpRequest.RequestLine);
+            }
+        }
+
+        [TestMethod]
+        public void ConnectShouldWriteProxyAuthorizationToHttpProxyWhenProxyUserNameIsNotNullAndNotEmpty()
+        {
+            var proxyEndPoint = new IPEndPoint(IPAddress.Loopback, 8123);
+            var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
+
+            using (var proxyStub = new HttpProxyStub(proxyEndPoint, "A"))
+            {
+                proxyStub.Responses.Add(Encoding.ASCII.GetBytes("HTTP/1.0 501 Custom\r\n"));
+                proxyStub.Start();
+
+                var connectionInfo = CreateConnectionInfoWithProxy(proxyEndPoint, serverEndPoint, "anon");
+                using (var session = new Session(connectionInfo))
+                {
+                    try
+                    {
+                        session.Connect();
+                        Assert.Fail();
+                    }
+                    catch (ProxyException)
+                    {
+                    }
+                }
+
+                var expectedProxyAuthorizationHeader = CreateProxyAuthorizationHeader(connectionInfo);
+                Assert.IsNotNull(proxyStub.HttpRequest.Headers.SingleOrDefault(p => p == expectedProxyAuthorizationHeader));
+            }
+        }
+
+        [TestMethod]
+        public void ConnectShouldNotWriteProxyAuthorizationToHttpProxyWhenProxyUserNameIsEmpty()
+        {
+            var proxyEndPoint = new IPEndPoint(IPAddress.Loopback, 8123);
+            var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
+
+            using (var proxyStub = new HttpProxyStub(proxyEndPoint, "A"))
+            {
+                proxyStub.Responses.Add(Encoding.ASCII.GetBytes("HTTP/1.0 501 Custom\r\n"));
+                proxyStub.Start();
+
+                var connectionInfo = CreateConnectionInfoWithProxy(proxyEndPoint, serverEndPoint, string.Empty);
+                using (var session = new Session(connectionInfo))
+                {
+                    try
+                    {
+                        session.Connect();
+                        Assert.Fail();
+                    }
+                    catch (ProxyException)
+                    {
+                    }
+                }
+
+                Assert.IsFalse(proxyStub.HttpRequest.Headers.Any(p => p.StartsWith("Proxy-Authorization:")));
+            }
+        }
+
+        [TestMethod]
+        public void ConnectShouldNotWriteProxyAuthorizationToHttpProxyWhenProxyUserNameIsNull()
+        {
+            var proxyEndPoint = new IPEndPoint(IPAddress.Loopback, 8123);
+            var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
+
+            using (var proxyStub = new HttpProxyStub(proxyEndPoint, "A"))
+            {
+                proxyStub.Responses.Add(Encoding.ASCII.GetBytes("HTTP/1.0 501 Custom\r\n"));
+                proxyStub.Start();
+
+                var connectionInfo = CreateConnectionInfoWithProxy(proxyEndPoint, serverEndPoint, null);
+                using (var session = new Session(connectionInfo))
+                {
+                    try
+                    {
+                        session.Connect();
+                        Assert.Fail();
+                    }
+                    catch (ProxyException)
+                    {
+                    }
+                }
+
+                Assert.IsFalse(proxyStub.HttpRequest.Headers.Any(p => p.StartsWith("Proxy-Authorization:")));
+            }
+        }
+
+        private static ConnectionInfo CreateConnectionInfoWithProxy(IPEndPoint proxyEndPoint, IPEndPoint serverEndPoint, string proxyUserName)
+        {
+            return new ConnectionInfo(
+                serverEndPoint.Address.ToString(),
+                serverEndPoint.Port,
+                "eric",
+                ProxyTypes.Http,
+                proxyEndPoint.Address.ToString(),
+                proxyEndPoint.Port,
+                proxyUserName,
+                "proxypwd",
+                new NoneAuthenticationMethod("eric"));
+        }
+
+        private static string CreateProxyAuthorizationHeader(ConnectionInfo connectionInfo)
+        {
+            return string.Format("Proxy-Authorization: Basic {0}",
+                Convert.ToBase64String(
+                    Encoding.ASCII.GetBytes(string.Format("{0}:{1}", connectionInfo.ProxyUsername,
+                        connectionInfo.ProxyPassword))));
+        }
+    }
+}

+ 139 - 0
Renci.SshClient/Renci.SshNet.Tests/Common/AsyncSocketListener.cs

@@ -0,0 +1,139 @@
+using System;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+
+namespace Renci.SshNet.Tests.Common
+{
+    public class AsyncSocketListener : IDisposable
+    {
+        private readonly IPEndPoint _endPoint;
+        private readonly string _id;
+        private readonly ManualResetEvent _acceptCallbackDone;
+        private Socket _listener;
+        private Thread _receiveThread;
+        private bool _started;
+
+        public delegate void BytesReceivedHandler(byte[] bytesReceived, Socket socket);
+
+        public event BytesReceivedHandler BytesReceived;
+
+        public AsyncSocketListener(IPEndPoint endPoint, string id)
+        {
+            _endPoint = endPoint;
+            _id = id;
+            _acceptCallbackDone = new ManualResetEvent(false);
+        }
+
+        public void Start()
+        {
+            _listener = new Socket(_endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
+            _listener.Bind(_endPoint);
+            _listener.Listen(1);
+
+            _started = true;
+
+            _receiveThread = new Thread(StartListener);
+            _receiveThread.Start(_listener);
+        }
+
+        public void Stop()
+        {
+            _started = false;
+            if (_listener != null)
+            {
+                _listener.Dispose();
+                _listener = null;
+            }
+            if (_receiveThread != null)
+            {
+                _receiveThread.Join();
+                _receiveThread = null;
+            }
+        }
+
+        public void Dispose()
+        {
+            Stop();
+            GC.SuppressFinalize(this);
+        }
+
+        private void StartListener(object state)
+        {
+            var listener = (Socket)state;
+            while (_started)
+            {
+                _acceptCallbackDone.Reset();
+                listener.BeginAccept(AcceptCallback, listener);
+                _acceptCallbackDone.WaitOne();
+            }
+        }
+
+        private void AcceptCallback(IAsyncResult ar)
+        {
+            // Signal the main thread to continue.
+            _acceptCallbackDone.Set();
+
+            // Get the socket that handles the client request.
+            var listener = (Socket)ar.AsyncState;
+            try
+            {
+                var handler = listener.EndAccept(ar);
+                var state = new SocketStateObject(handler);
+                handler.BeginReceive(state.Buffer, 0, state.Buffer.Length, 0, ReadCallback, state);
+            }
+            catch (ObjectDisposedException)
+            {
+                // when the socket is closed, an ObjectDisposedException is thrown
+                // by Socket.EndAccept(IAsyncResult)
+            }
+        }
+
+        private void ReadCallback(IAsyncResult ar)
+        {
+            // Retrieve the state object and the handler socket
+            // from the asynchronous state object.
+            var state = (SocketStateObject)ar.AsyncState;
+            var handler = state.Socket;
+
+            // Read data from the client socket.
+            var bytesRead = handler.EndReceive(ar);
+
+            if (bytesRead > 0)
+            {
+                var bytesReceived = new byte[bytesRead];
+                Array.Copy(state.Buffer, bytesReceived, bytesRead);
+                SignalBytesReceived(bytesReceived, handler);
+
+                // prepare to receive more bytes
+                try
+                {
+                    handler.BeginReceive(state.Buffer, 0, state.Buffer.Length, 0, ReadCallback, state);
+                }
+                catch (ObjectDisposedException)
+                {
+                    // when the socket is closed, an ObjectDisposedException is thrown
+                }
+            }
+        }
+
+        private void SignalBytesReceived(byte[] bytesReceived, Socket client)
+        {
+            var subscribers = BytesReceived;
+            if (subscribers != null)
+                subscribers(bytesReceived, client);
+        }
+
+        private class SocketStateObject
+        {
+            public Socket Socket { get; private set; }
+
+            public readonly byte[] Buffer = new byte[1024];
+
+            public SocketStateObject(Socket handler)
+            {
+                Socket = handler;
+            }
+        }
+    }
+}

+ 171 - 0
Renci.SshClient/Renci.SshNet.Tests/Common/HttpProxyStub.cs

@@ -0,0 +1,171 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+
+namespace Renci.SshNet.Tests.Common
+{
+    public class HttpProxyStub : IDisposable
+    {
+        private readonly IPEndPoint _endPoint;
+        private readonly string _id;
+        private AsyncSocketListener _listener;
+        private HttpRequestParser _httpRequestParser;
+        private readonly IList<byte[]> _responses;
+
+        public HttpProxyStub(IPEndPoint endPoint, string id)
+        {
+            _endPoint = endPoint;
+            _id = id;
+            _responses = new List<byte[]>();
+        }
+
+        public HttpRequest HttpRequest
+        {
+            get
+            {
+                if (_httpRequestParser == null)
+                    throw new InvalidOperationException("The proxy is not started.");
+                return _httpRequestParser.HttpRequest;
+            }
+        }
+
+        public IList<byte[]> Responses
+        {
+            get { return _responses; }
+        }
+
+        public void Start()
+        {
+            _httpRequestParser = new HttpRequestParser();
+
+            _listener = new AsyncSocketListener(_endPoint, _id);
+            _listener.BytesReceived += OnBytesReceived;
+            _listener.Start();
+        }
+
+        public void Stop()
+        {
+            if (_listener != null)
+                _listener.Stop();
+        }
+
+        public void Dispose()
+        {
+            Stop();
+            GC.SuppressFinalize(this);
+        }
+
+        private void OnBytesReceived(byte[] bytesReceived, Socket socket)
+        {
+            _httpRequestParser.ProcessData(bytesReceived);
+
+            if (_httpRequestParser.CurrentState == HttpRequestParser.State.Content)
+            {
+                foreach (var response in Responses)
+                    socket.Send(response);
+                socket.Shutdown(SocketShutdown.Send);
+            }
+        }
+
+        private class HttpRequestParser
+        {
+            private readonly List<byte> _buffer;
+            private readonly HttpRequest _httpRequest;
+
+            public enum State
+            {
+                RequestLine,
+                Headers,
+                Content
+            }
+
+            public HttpRequestParser()
+            {
+                CurrentState = State.RequestLine;
+                _buffer = new List<byte>();
+                _httpRequest = new HttpRequest();
+            }
+
+            public HttpRequest HttpRequest
+            {
+                get { return _httpRequest; }
+            }
+
+            public State CurrentState { get; private set; }
+
+            public void ProcessData(byte[] data)
+            {
+                var position = 0;
+
+                while (position != data.Length)
+                {
+                    if (CurrentState == State.RequestLine)
+                    {
+                        var requestLine = ReadLine(data, ref position);
+                        if (requestLine != null)
+                        {
+                            _httpRequest.RequestLine = requestLine;
+                            CurrentState = State.Headers;
+                        }
+                    }
+
+                    if (CurrentState == State.Headers)
+                    {
+                        var line = ReadLine(data, ref position);
+                        if (line != null)
+                        {
+                            if (line.Length == 0)
+                            {
+                                CurrentState = State.Content;
+                            }
+                            else
+                            {
+                                _httpRequest.Headers.Add(line);
+                            }
+                        }
+                    }
+
+                    if (CurrentState == State.Content)
+                    {
+                        if (position < data.Length)
+                        {
+                            var currentContent = _httpRequest.MessageBody;
+                            var newBufferSize = currentContent.Length + (data.Length - position);
+                            var copyBuffer = new byte[newBufferSize];
+                            Array.Copy(currentContent, copyBuffer, currentContent.Length);
+                            Array.Copy(data, position, copyBuffer, currentContent.Length, data.Length - position);
+                            _httpRequest.MessageBody = copyBuffer;
+                            break;
+                        }
+                    }
+                }
+            }
+
+            private string ReadLine(byte[] data, ref int position)
+            {
+                for (; position < data.Length; position++)
+                {
+                    var b = data[position];
+                    if (b == '\n')
+                    {
+                        var buffer = _buffer.ToArray();
+                        var bytesInLine = buffer.Length;
+                        // when the previous byte was a CR, then do not include it in line
+                        if (buffer.Length > 0 && buffer[buffer.Length - 1] == '\r')
+                            bytesInLine -= 1;
+                        // clear the buffer
+                        _buffer.Clear();
+                        // move position up one position as we've processed the current byte
+                        position++;
+                        return Encoding.ASCII.GetString(buffer, 0, bytesInLine);
+                    }
+                    _buffer.Add(b);
+                }
+
+                return null;
+            }
+        }
+    }
+}

+ 17 - 0
Renci.SshClient/Renci.SshNet.Tests/Common/HttpRequest.cs

@@ -0,0 +1,17 @@
+using System.Collections.Generic;
+
+namespace Renci.SshNet.Tests.Common
+{
+    public class HttpRequest
+    {
+        public HttpRequest()
+        {
+            Headers = new List<string>();
+            MessageBody = new byte[0];
+        }
+
+        public string RequestLine { get; set; }
+        public IList<string> Headers { get; set; }
+        public byte[] MessageBody { get; set; }
+    }
+}

+ 6 - 0
Renci.SshClient/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj

@@ -224,6 +224,9 @@
     <Compile Include="Classes\ForwardedPortLocalTest.NET40.cs" />
     <Compile Include="Classes\SshCommandTest.NET40.cs" />
     <Compile Include="Classes\ScpClientTest.NET40.cs" />
+    <Compile Include="Common\AsyncSocketListener.cs" />
+    <Compile Include="Common\HttpProxyStub.cs" />
+    <Compile Include="Common\HttpRequest.cs" />
     <Compile Include="Common\TestBase.cs" />
     <Compile Include="Classes\ForwardedPortTest.cs" />
     <Compile Include="Classes\Compression\CompressorTest.cs" />
@@ -322,6 +325,9 @@
     <EmbeddedResource Include="Data\Key.RSA.Encrypted.Des.CBC.12345.txt" />
     <EmbeddedResource Include="Data\Key.RSA.txt" />
   </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Classes\SessionTest.HttpProxy.cs" />
+  </ItemGroup>
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.

+ 33 - 31
Renci.SshClient/Renci.SshNet/Session.cs

@@ -1,6 +1,5 @@
 using System;
 using System.Collections.Generic;
-using System.IO;
 using System.Linq;
 using System.Net;
 using System.Net.Sockets;
@@ -17,7 +16,6 @@ using Renci.SshNet.Messages.Connection;
 using Renci.SshNet.Messages.Transport;
 using Renci.SshNet.Security;
 using System.Globalization;
-using Renci.SshNet.Security.Cryptography.Ciphers;
 using Renci.SshNet.Security.Cryptography;
 
 namespace Renci.SshNet
@@ -407,8 +405,12 @@ namespace Renci.SshNet
         /// Initializes a new instance of the <see cref="Session"/> class.
         /// </summary>
         /// <param name="connectionInfo">The connection info.</param>
+        /// <exception cref="ArgumentNullException"><paramref name="connectionInfo"/> is <c>null</c>.</exception>
         internal Session(ConnectionInfo connectionInfo)
         {
+            if (connectionInfo == null)
+                throw new ArgumentNullException("connectionInfo");
+
             this.ConnectionInfo = connectionInfo;
             //this.ClientVersion = string.Format(CultureInfo.CurrentCulture, "SSH-2.0-Renci.SshNet.SshClient.{0}", this.GetType().Assembly.GetName().Version);
             this.ClientVersion = string.Format(CultureInfo.CurrentCulture, "SSH-2.0-Renci.SshNet.SshClient.0.0.1");
@@ -419,12 +421,6 @@ namespace Renci.SshNet
         /// </summary>
         public void Connect()
         {
-            //   TODO: Add exception documentation for Proxy.
-            if (this.ConnectionInfo == null)
-            {
-                throw new ArgumentNullException("connectionInfo");
-            }
-
             if (this.IsConnected)
                 return;
 
@@ -1842,47 +1838,53 @@ namespace Renci.SshNet
 
             this.SocketWrite(encoding.GetBytes("\r\n"));
 
-            HttpStatusCode statusCode = (HttpStatusCode)0;
+            HttpStatusCode? statusCode = null;
             var response = string.Empty;
             var contentLength = 0;
 
-            while (statusCode != HttpStatusCode.OK)
+            while (true)
             {
                 this.SocketReadLine(ref response);
 
-                var match = httpResponseRe.Match(response);
-
-                if (match.Success)
+                if (statusCode == null)
                 {
-                    statusCode = (HttpStatusCode)int.Parse(match.Result("${statusCode}"));
-                    continue;
+                    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
-                match = httpHeaderRe.Match(response);
-                if (match.Success)
+                var headerMatch = httpHeaderRe.Match(response);
+                if (headerMatch.Success)
                 {
-                    var fieldName = match.Result("${fieldName}");
+                    var fieldName = headerMatch.Result("${fieldName}");
                     if (fieldName.Equals("Content-Length", StringComparison.InvariantCultureIgnoreCase))
                     {
-                        contentLength = int.Parse(match.Result("${fieldValue}"));
+                        contentLength = int.Parse(headerMatch.Result("${fieldValue}"));
                     }
                     continue;
                 }
 
-                //  Read response body if specified
-                if (string.IsNullOrEmpty(response) && contentLength > 0)
+                // check if we've reached the CRLF which separates request line and headers from the message body
+                if (response.Length == 0)
                 {
-                    var contentBody = new byte[contentLength];
-                    this.SocketRead(contentLength, ref contentBody);
-                }
-
-                switch (statusCode)
-                {
-                    case HttpStatusCode.OK:
-                        break;
-                    default:
-                        throw new ProxyException(string.Format("HTTP: Status code {0}, \"{1}\"", statusCode, statusCode));
+                    //  read response body if specified
+                    if (contentLength > 0)
+                    {
+                        var contentBody = new byte[contentLength];
+                        SocketRead(contentLength, ref contentBody);
+                    }
+                    break;
                 }
             }
         }