|
@@ -1,532 +0,0 @@
|
|
-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
|
|
|
|
- {
|
|
|
|
- s?.Shutdown(SocketShutdown.Both);
|
|
|
|
- }
|
|
|
|
- catch
|
|
|
|
- {
|
|
|
|
- }
|
|
|
|
- finally
|
|
|
|
- {
|
|
|
|
- try
|
|
|
|
- {
|
|
|
|
- s?.Close();
|
|
|
|
- }
|
|
|
|
- catch { }
|
|
|
|
- }
|
|
|
|
- Unbind();
|
|
|
|
- RemoveConnection();
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-}
|
|
|