< Summary

Information
Class: Renci.SshNet.Connection.HttpConnector
Assembly: Renci.SshNet
File(s): \home\appveyor\projects\ssh-net\src\Renci.SshNet\Connection\HttpConnector.cs
Line coverage
97%
Covered lines: 79
Uncovered lines: 2
Coverable lines: 81
Total lines: 167
Line coverage: 97.5%
Branch coverage
93%
Covered branches: 28
Total branches: 30
Branch coverage: 93.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity Line coverage
.ctor(...)100%1100%
HandleProxyConnect(...)95%2098.24%
SocketReadLine(...)90%1095.23%

File(s)

\home\appveyor\projects\ssh-net\src\Renci.SshNet\Connection\HttpConnector.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Globalization;
 4using System.Net;
 5using System.Net.Sockets;
 6using System.Text.RegularExpressions;
 7
 8using Renci.SshNet.Abstractions;
 9using Renci.SshNet.Common;
 10
 11namespace Renci.SshNet.Connection
 12{
 13    /// <summary>
 14    /// Establishes a tunnel via an HTTP proxy server.
 15    /// </summary>
 16    /// <remarks>
 17    /// <list type="table">
 18    ///   <listheader>
 19    ///     <term>Specification</term>
 20    ///     <description>URL</description>
 21    ///   </listheader>
 22    ///   <item>
 23    ///     <term>HTTP CONNECT method</term>
 24    ///     <description>https://tools.ietf.org/html/rfc7231#section-4.3.6</description>
 25    ///   </item>
 26    ///   <item>
 27    ///     <term>HTTP Authentication: Basic and Digest Access Authentication</term>
 28    ///     <description>https://tools.ietf.org/html/rfc2617</description>
 29    ///   </item>
 30    /// </list>
 31    /// </remarks>
 32    internal sealed class HttpConnector : ProxyConnector
 33    {
 34        public HttpConnector(ISocketFactory socketFactory)
 22235            : base(socketFactory)
 22236        {
 22237        }
 38
 39        protected override void HandleProxyConnect(IConnectionInfo connectionInfo, Socket socket)
 18940        {
 18941            var httpResponseRe = new Regex(@"HTTP/(?<version>\d[.]\d) (?<statusCode>\d{3}) (?<reasonPhrase>.+)$");
 18942            var httpHeaderRe = new Regex(@"(?<fieldName>[^\[\]()<>@,;:\""/?={} \t]+):(?<fieldValue>.+)?");
 43
 18944            SocketAbstraction.Send(socket, SshData.Ascii.GetBytes(string.Format(CultureInfo.InvariantCulture,
 18945                                                                                "CONNECT {0}:{1} HTTP/1.0\r\n",
 18946                                                                                connectionInfo.Host,
 18947                                                                                connectionInfo.Port)));
 48
 49            // Send proxy authorization if specified
 18950            if (!string.IsNullOrEmpty(connectionInfo.ProxyUsername))
 15951            {
 15952                var authorization = string.Format(CultureInfo.InvariantCulture,
 15953                                                  "Proxy-Authorization: Basic {0}\r\n",
 15954                                                  Convert.ToBase64String(SshData.Ascii.GetBytes($"{connectionInfo.ProxyU
 15955                SocketAbstraction.Send(socket, SshData.Ascii.GetBytes(authorization));
 15956            }
 57
 18958            SocketAbstraction.Send(socket, SshData.Ascii.GetBytes("\r\n"));
 59
 18960            HttpStatusCode? statusCode = null;
 18961            var contentLength = 0;
 62
 66063            while (true)
 66064            {
 66065                var response = SocketReadLine(socket, connectionInfo.Timeout);
 64566                if (response is null)
 2467                {
 68                    // server shut down socket
 2469                    break;
 70                }
 71
 62172                if (statusCode is null)
 31273                {
 31274                    var statusMatch = httpResponseRe.Match(response);
 31275                    if (statusMatch.Success)
 15076                    {
 15077                        var httpStatusCode = statusMatch.Result("${statusCode}");
 15078                        statusCode = (HttpStatusCode) int.Parse(httpStatusCode, CultureInfo.InvariantCulture);
 15079                        if (statusCode != HttpStatusCode.OK)
 1280                        {
 1281                            throw new ProxyException($"HTTP: Status code {httpStatusCode}, \"{statusMatch.Result("${reas
 82                        }
 13883                    }
 84
 30085                    continue;
 86                }
 87
 88                // continue on parsing message headers coming from the server
 30989                var headerMatch = httpHeaderRe.Match(response);
 30990                if (headerMatch.Success)
 17191                {
 17192                    var fieldName = headerMatch.Result("${fieldName}");
 17193                    if (fieldName.Equals("Content-Length", StringComparison.OrdinalIgnoreCase))
 3394                    {
 3395                        contentLength = int.Parse(headerMatch.Result("${fieldValue}"), CultureInfo.InvariantCulture);
 3396                    }
 97
 17198                    continue;
 99                }
 100
 101                // check if we've reached the CRLF which separates request line and headers from the message body
 138102                if (response.Length == 0)
 138103                {
 104                    // read response body if specified
 138105                    if (contentLength > 0)
 33106                    {
 33107                        var contentBody = new byte[contentLength];
 33108                        _ = SocketRead(socket, contentBody, 0, contentLength, connectionInfo.Timeout);
 15109                    }
 110
 120111                    break;
 112                }
 0113            }
 114
 144115            if (statusCode is null)
 24116            {
 24117                throw new ProxyException("HTTP response does not contain status line.");
 118            }
 120119        }
 120
 121        /// <summary>
 122        /// Performs a blocking read on the socket until a line is read.
 123        /// </summary>
 124        /// <param name="socket">The <see cref="Socket"/> to read from.</param>
 125        /// <param name="readTimeout">A <see cref="TimeSpan"/> that represents the time to wait until a line is read.</p
 126        /// <exception cref="SshOperationTimeoutException">The read has timed-out.</exception>
 127        /// <exception cref="SocketException">An error occurred when trying to access the socket.</exception>
 128        /// <returns>
 129        /// The line read from the socket, or <see langword="null"/> when the remote server has shutdown and all data ha
 130        /// </returns>
 131        private static string SocketReadLine(Socket socket, TimeSpan readTimeout)
 660132        {
 660133            var encoding = SshData.Ascii;
 660134            var buffer = new List<byte>();
 660135            var data = new byte[1];
 136
 137            // read data one byte at a time to find end of line and leave any unhandled information in the buffer
 138            // to be processed by subsequent invocations
 139            do
 10350140            {
 10350141                var bytesRead = SocketAbstraction.Read(socket, data, 0, data.Length, readTimeout);
 10335142                if (bytesRead == 0)
 24143                {
 144                    // the remote server shut down the socket
 24145                    break;
 146                }
 147
 10311148                var b = data[0];
 10311149                buffer.Add(b);
 150
 10311151                if (b == Session.LineFeed && buffer.Count > 1 && buffer[buffer.Count - 2] == Session.CarriageReturn)
 621152                {
 153                    // Return line without CRLF
 621154                    return encoding.GetString(buffer.ToArray(), 0, buffer.Count - 2);
 155                }
 9690156            }
 9690157            while (true);
 158
 24159            if (buffer.Count == 0)
 24160            {
 24161                return null;
 162            }
 163
 0164            return encoding.GetString(buffer.ToArray(), 0, buffer.Count);
 645165        }
 166    }
 167}