123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528 |
- using System;
- using System.IO;
- using System.Net;
- using System.Net.Security;
- using System.Net.Sockets;
- using System.Security.Authentication;
- using System.Security.Cryptography.X509Certificates;
- using System.Text;
- using System.Threading;
- using System.Threading.Tasks;
- using MediaBrowser.Model.Cryptography;
- using MediaBrowser.Model.IO;
- using MediaBrowser.Model.System;
- using Microsoft.Extensions.Logging;
- namespace SocketHttpListener.Net
- {
- sealed class HttpConnection
- {
- private static AsyncCallback s_onreadCallback = new AsyncCallback(OnRead);
- const int BufferSize = 8192;
- Socket _socket;
- Stream _stream;
- HttpEndPointListener _epl;
- MemoryStream _memoryStream;
- byte[] _buffer;
- HttpListenerContext _context;
- StringBuilder _currentLine;
- ListenerPrefix _prefix;
- HttpRequestStream _requestStream;
- HttpResponseStream _responseStream;
- bool _chunked;
- int _reuses;
- bool _contextBound;
- bool secure;
- IPEndPoint local_ep;
- HttpListener _lastListener;
- X509Certificate cert;
- SslStream ssl_stream;
- private readonly ILogger _logger;
- private readonly ICryptoProvider _cryptoProvider;
- private readonly IStreamHelper _streamHelper;
- private readonly IFileSystem _fileSystem;
- private readonly IEnvironmentInfo _environment;
- public HttpConnection(ILogger logger, Socket socket, HttpEndPointListener epl, bool secure,
- X509Certificate cert, ICryptoProvider cryptoProvider, IStreamHelper streamHelper, IFileSystem fileSystem,
- IEnvironmentInfo environment)
- {
- _logger = logger;
- this._socket = socket;
- this._epl = epl;
- this.secure = secure;
- this.cert = cert;
- _cryptoProvider = cryptoProvider;
- _streamHelper = streamHelper;
- _fileSystem = fileSystem;
- _environment = environment;
- if (secure == false)
- {
- _stream = new SocketStream(_socket, false);
- }
- else
- {
- ssl_stream = new SslStream(new SocketStream(_socket, false), false, (t, c, ch, e) =>
- {
- if (c == null)
- {
- return true;
- }
- //var c2 = c as X509Certificate2;
- //if (c2 == null)
- //{
- // c2 = new X509Certificate2(c.GetRawCertData());
- //}
- //_clientCert = c2;
- //_clientCertErrors = new int[] { (int)e };
- return true;
- });
- _stream = ssl_stream;
- }
- }
- public Stream Stream => _stream;
- public async Task Init()
- {
- if (ssl_stream != null)
- {
- var enableAsync = true;
- if (enableAsync)
- {
- await ssl_stream.AuthenticateAsServerAsync(cert, false, (SslProtocols)ServicePointManager.SecurityProtocol, false).ConfigureAwait(false);
- }
- else
- {
- ssl_stream.AuthenticateAsServer(cert, false, (SslProtocols)ServicePointManager.SecurityProtocol, false);
- }
- }
- InitInternal();
- }
- private void InitInternal()
- {
- _contextBound = false;
- _requestStream = null;
- _responseStream = null;
- _prefix = null;
- _chunked = false;
- _memoryStream = new MemoryStream();
- _position = 0;
- _inputState = InputState.RequestLine;
- _lineState = LineState.None;
- _context = new HttpListenerContext(this);
- }
- public bool IsClosed => (_socket == null);
- public int Reuses => _reuses;
- public IPEndPoint LocalEndPoint
- {
- get
- {
- if (local_ep != null)
- return local_ep;
- local_ep = (IPEndPoint)_socket.LocalEndPoint;
- return local_ep;
- }
- }
- public IPEndPoint RemoteEndPoint => _socket.RemoteEndPoint as IPEndPoint;
- public bool IsSecure => secure;
- public ListenerPrefix Prefix
- {
- get => _prefix;
- set => _prefix = value;
- }
- private void OnTimeout(object unused)
- {
- //_logger.LogInformation("HttpConnection timer fired");
- CloseSocket();
- Unbind();
- }
- public void BeginReadRequest()
- {
- if (_buffer == null)
- _buffer = new byte[BufferSize];
- try
- {
- _stream.BeginRead(_buffer, 0, BufferSize, s_onreadCallback, this);
- }
- catch
- {
- 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)
- {
- _chunked = true;
- //_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(bool isExpect100Continue = false)
- {
- // TODO: can we get this _stream before reading the input?
- if (_responseStream == null)
- {
- var supportsDirectSocketAccess = !_context.Response.SendChunked && !isExpect100Continue && !secure;
- _responseStream = new HttpResponseStream(_stream, _context.Response, false, _streamHelper, _socket, supportsDirectSocketAccess, _environment, _fileSystem, _logger);
- }
- return _responseStream;
- }
- private static void OnRead(IAsyncResult ares)
- {
- var cnc = (HttpConnection)ares.AsyncState;
- cnc.OnReadInternal(ares);
- }
- private void OnReadInternal(IAsyncResult ares)
- {
- int nread = -1;
- try
- {
- nread = _stream.EndRead(ares);
- _memoryStream.Write(_buffer, 0, nread);
- if (_memoryStream.Length > 32768)
- {
- SendError("Bad Request", 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 = _epl.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
- }
- InputState _inputState = InputState.RequestLine;
- LineState _lineState = LineState.None;
- int _position;
- // 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 = "Bad request";
- _context.ErrorStatus = 400;
- return true;
- }
- if (line == null)
- break;
- if (line == "")
- {
- if (_inputState == InputState.RequestLine)
- continue;
- _currentLine = null;
- ms = 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)
- {
- if (_currentLine == null)
- _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;
- if (msg != null)
- str = string.Format("<h1>{0} ({1})</h1>", description, msg);
- else
- str = string.Format("<h1>{0}</h1>", description);
- byte[] error = Encoding.UTF8.GetBytes(str);
- response.Close(error, false);
- }
- catch
- {
- // response was already closed
- }
- }
- public void SendError()
- {
- SendError(_context.ErrorMessage, _context.ErrorStatus);
- }
- private void Unbind()
- {
- if (_contextBound)
- {
- _epl.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)
- {
- Stream st = GetResponseStream();
- if (st != null)
- st.Close();
- _responseStream = null;
- }
- if (_socket != null)
- {
- force |= !_context.Request.KeepAlive;
- if (!force)
- force = (string.Equals(_context.Response.Headers["connection"], "close", StringComparison.OrdinalIgnoreCase));
- if (!force && _context.Request.FlushInput())
- {
- if (_chunked && _context.Response.ForceCloseChunked == false)
- {
- // Don't close. Keep working.
- _reuses++;
- Unbind();
- InitInternal();
- BeginReadRequest();
- return;
- }
- _reuses++;
- Unbind();
- InitInternal();
- BeginReadRequest();
- return;
- }
- Socket s = _socket;
- _socket = null;
- try
- {
- if (s != null)
- s.Shutdown(SocketShutdown.Both);
- }
- catch
- {
- }
- finally
- {
- if (s != null)
- {
- try
- {
- s.Close();
- }
- catch { }
- }
- }
- Unbind();
- RemoveConnection();
- return;
- }
- }
- }
- }
|