File: System\Net\Managed\HttpConnection.cs
Web Access
Project: src\src\libraries\System.Net.HttpListener\src\System.Net.HttpListener.csproj (System.Net.HttpListener)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
//
// System.Net.HttpConnection
//
// Author:
//  Gonzalo Paniagua Javier (gonzalo.mono@gmail.com)
//
// Copyright (c) 2005-2009 Novell, Inc. (http://www.novell.com)
// Copyright (c) 2012 Xamarin, Inc. (http://xamarin.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
 
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
 
namespace System.Net
{
    internal sealed class HttpConnection
    {
        private static readonly AsyncCallback s_onreadCallback = new AsyncCallback(OnRead);
        private const int BufferSize = 8192;
        private Socket? _socket;
        private readonly Stream _stream;
        private readonly HttpEndPointListener _epl;
        private MemoryStream? _memoryStream;
        private byte[]? _buffer;
        private HttpListenerContext _context;
        private StringBuilder? _currentLine;
        private ListenerPrefix? _prefix;
        private HttpRequestStream? _requestStream;
        private HttpResponseStream? _responseStream;
        private int _reuses;
        private bool _contextBound;
        private readonly bool _secure;
        private readonly X509Certificate _cert;
        private int _timeout = 90000; // 90k ms for first request, 15k ms from then on
        private readonly Timer _timer;
        private IPEndPoint? _localEndPoint;
        private HttpListener? _lastListener;
        private int[]? _clientCertErrors;
        private X509Certificate2? _clientCert;
        private readonly SslStream? _sslStream;
        private InputState _inputState = InputState.RequestLine;
        private LineState _lineState = LineState.None;
        private int _position;
 
        public HttpConnection(Socket sock, HttpEndPointListener epl, bool secure, X509Certificate cert)
        {
            _socket = sock;
            _epl = epl;
            _secure = secure;
            _cert = cert;
            if (secure == false)
            {
                _stream = new NetworkStream(sock, false);
            }
            else
            {
#pragma warning disable CA5359
                _sslStream = HttpListener.CreateSslStream(new NetworkStream(sock, false), false, (t, c, ch, e) =>
                {
                    if (c == null)
                    {
                        return true;
                    }
 
                    _clientCert = c as X509Certificate2 ?? X509CertificateLoader.LoadCertificate(c.GetRawCertData());
                    _clientCertErrors = new int[] { (int)e };
                    return true;
                });
#pragma warning restore CA5359
 
                _stream = _sslStream;
            }
 
            _timer = new Timer(OnTimeout, null, Timeout.Infinite, Timeout.Infinite);
 
#pragma warning disable SYSLIB0014 // ServicePointManager is obsolete
            _sslStream?.AuthenticateAsServer(_cert, true, (SslProtocols)ServicePointManager.SecurityProtocol, false);
#pragma warning restore SYSLIB0014
 
            Init();
        }
 
        internal int[]? ClientCertificateErrors
        {
            get { return _clientCertErrors; }
        }
 
        internal X509Certificate2? ClientCertificate
        {
            get { return _clientCert; }
        }
 
        internal SslStream? SslStream
        {
            get { return _sslStream; }
        }
 
        [MemberNotNull(nameof(_memoryStream))]
        [MemberNotNull(nameof(_context))]
        private void Init()
        {
            _contextBound = false;
            _requestStream = null;
            _responseStream = null;
            _prefix = null;
            _memoryStream = new MemoryStream();
            _position = 0;
            _inputState = InputState.RequestLine;
            _lineState = LineState.None;
            _context = new HttpListenerContext(this);
        }
 
        public Stream ConnectedStream => _stream;
 
        public bool IsClosed
        {
            get { return (_socket == null); }
        }
 
        public int Reuses
        {
            get { return _reuses; }
        }
 
        public IPEndPoint? LocalEndPoint
        {
            get
            {
                if (_localEndPoint != null)
                    return _localEndPoint;
 
                _localEndPoint = (IPEndPoint?)_socket!.LocalEndPoint;
                return _localEndPoint;
            }
        }
 
        public IPEndPoint? RemoteEndPoint
        {
            get { return (IPEndPoint?)_socket!.RemoteEndPoint; }
        }
 
        public bool IsSecure
        {
            get { return _secure; }
        }
 
        public ListenerPrefix? Prefix
        {
            get { return _prefix; }
            set { _prefix = value; }
        }
 
        private void OnTimeout(object? unused)
        {
            CloseSocket();
            Unbind();
        }
 
        public void BeginReadRequest()
        {
            _buffer ??= new byte[BufferSize];
            try
            {
                if (_reuses == 1)
                    _timeout = 15000;
                _timer.Change(_timeout, Timeout.Infinite);
                _stream.BeginRead(_buffer, 0, BufferSize, s_onreadCallback, this);
            }
            catch
            {
                _timer.Change(Timeout.Infinite, Timeout.Infinite);
                CloseSocket();
                Unbind();
            }
        }
 
        public HttpRequestStream GetRequestStream(bool chunked, long contentlength)
        {
            if (_requestStream == null)
            {
                byte[] buffer = _memoryStream!.GetBuffer();
                int length = (int)_memoryStream!.Length;
                _memoryStream = null;
                if (chunked)
                {
                    _context.Response.SendChunked = true;
                    _requestStream = new ChunkedInputStream(_context, _stream, buffer, _position, length - _position);
                }
                else
                {
                    _requestStream = new HttpRequestStream(_stream, buffer, _position, length - _position, contentlength);
                }
            }
            return _requestStream;
        }
 
        public HttpResponseStream GetResponseStream()
        {
            if (_responseStream == null)
            {
                HttpListener? listener = _context._listener;
 
                if (listener == null)
                    return new HttpResponseStream(_stream, _context.Response, true);
 
                _responseStream = new HttpResponseStream(_stream, _context.Response, listener.IgnoreWriteExceptions);
            }
            return _responseStream;
        }
 
        private static void OnRead(IAsyncResult ares)
        {
            HttpConnection? cnc = null;
            try
            {
                cnc = (HttpConnection)ares.AsyncState!;
                cnc.OnReadInternal(ares);
            }
            catch
            {
                cnc?.Close(true);
                return;
            }
        }
 
        private void OnReadInternal(IAsyncResult ares)
        {
            _timer.Change(Timeout.Infinite, Timeout.Infinite);
            int nread;
            try
            {
                nread = _stream.EndRead(ares);
                _memoryStream!.Write(_buffer!, 0, nread);
                if (_memoryStream.Length > 32768)
                {
                    SendError(HttpStatusDescription.Get(400), 400);
                    Close(true);
                    return;
                }
            }
            catch
            {
                if (_memoryStream != null && _memoryStream.Length > 0)
                    SendError();
                if (_socket != null)
                {
                    CloseSocket();
                    Unbind();
                }
                return;
            }
 
            if (nread == 0)
            {
                CloseSocket();
                Unbind();
                return;
            }
 
            if (ProcessInput(_memoryStream))
            {
                if (!_context.HaveError)
                    _context.Request.FinishInitialization();
 
                if (_context.HaveError)
                {
                    SendError();
                    Close(true);
                    return;
                }
 
                if (!_epl.BindContext(_context))
                {
                    const int NotFoundErrorCode = 404;
                    SendError(HttpStatusDescription.Get(NotFoundErrorCode), NotFoundErrorCode);
                    Close(true);
                    return;
                }
                HttpListener listener = _context._listener!;
                if (_lastListener != listener)
                {
                    RemoveConnection();
                    listener.AddConnection(this);
                    _lastListener = listener;
                }
 
                _contextBound = true;
                listener.RegisterContext(_context);
                return;
            }
            _stream.BeginRead(_buffer!, 0, BufferSize, s_onreadCallback, this);
        }
 
        private void RemoveConnection()
        {
            if (_lastListener == null)
                _epl.RemoveConnection(this);
            else
                _lastListener.RemoveConnection(this);
        }
 
        private enum InputState
        {
            RequestLine,
            Headers
        }
 
        private enum LineState
        {
            None,
            CR,
            LF
        }
 
        // true -> done processing
        // false -> need more input
        private bool ProcessInput(MemoryStream ms)
        {
            byte[] buffer = ms.GetBuffer();
            int len = (int)ms.Length;
            int used = 0;
            string? line;
 
            while (true)
            {
                if (_context.HaveError)
                    return true;
 
                if (_position >= len)
                    break;
 
                try
                {
                    line = ReadLine(buffer, _position, len - _position, ref used);
                    _position += used;
                }
                catch
                {
                    _context.ErrorMessage = HttpStatusDescription.Get(400)!;
                    _context.ErrorStatus = 400;
                    return true;
                }
 
                if (line == null)
                    break;
 
                if (line == "")
                {
                    if (_inputState == InputState.RequestLine)
                        continue;
                    _currentLine = null;
                    return true;
                }
 
                if (_inputState == InputState.RequestLine)
                {
                    _context.Request.SetRequestLine(line);
                    _inputState = InputState.Headers;
                }
                else
                {
                    try
                    {
                        _context.Request.AddHeader(line);
                    }
                    catch (Exception e)
                    {
                        _context.ErrorMessage = e.Message;
                        _context.ErrorStatus = 400;
                        return true;
                    }
                }
            }
 
            if (used == len)
            {
                ms.SetLength(0);
                _position = 0;
            }
            return false;
        }
 
        private string? ReadLine(byte[] buffer, int offset, int len, ref int used)
        {
            _currentLine ??= new StringBuilder(128);
            int last = offset + len;
            used = 0;
            for (int i = offset; i < last && _lineState != LineState.LF; i++)
            {
                used++;
                byte b = buffer[i];
                if (b == 13)
                {
                    _lineState = LineState.CR;
                }
                else if (b == 10)
                {
                    _lineState = LineState.LF;
                }
                else
                {
                    _currentLine.Append((char)b);
                }
            }
 
            string? result = null;
            if (_lineState == LineState.LF)
            {
                _lineState = LineState.None;
                result = _currentLine.ToString();
                _currentLine.Length = 0;
            }
 
            return result;
        }
 
        public void SendError(string? msg, int status)
        {
            try
            {
                HttpListenerResponse response = _context.Response;
                response.StatusCode = status;
                response.ContentType = "text/html";
                string? description = HttpStatusDescription.Get(status);
                string str = msg != null ?
                    $"<h1>{description} ({msg})</h1>" :
                    $"<h1>{description}</h1>";
 
                byte[] error = Encoding.Default.GetBytes(str);
                response.Close(error, false);
            }
            catch
            {
                // response was already closed
            }
        }
 
        public void SendError()
        {
            SendError(_context.ErrorMessage, _context.ErrorStatus);
        }
 
        private void Unbind()
        {
            if (_contextBound)
            {
                HttpEndPointListener.UnbindContext(_context);
                _contextBound = false;
            }
        }
 
        public void Close()
        {
            Close(false);
        }
 
        private void CloseSocket()
        {
            if (_socket == null)
                return;
 
            try
            {
                _socket.Close();
            }
            catch { }
            finally
            {
                _socket = null;
            }
 
            RemoveConnection();
        }
 
        internal void Close(bool force)
        {
            if (_socket != null)
            {
                GetResponseStream()?.Close();
 
                _responseStream = null;
            }
 
            if (_socket != null)
            {
                force |= !_context.Request.KeepAlive;
                if (!force)
                    force = (_context.Response.Headers[HttpKnownHeaderNames.Connection] == HttpHeaderStrings.Close);
 
                if (!force && _context.Request.FlushInput())
                {
                    _reuses++;
                    Unbind();
                    Init();
                    BeginReadRequest();
                    return;
                }
 
                Socket s = _socket;
                _socket = null;
                try
                {
                    s?.Shutdown(SocketShutdown.Both);
                }
                catch
                {
                }
                finally
                {
                    s?.Close();
                }
                Unbind();
                RemoveConnection();
                return;
            }
        }
    }
}