jellyfin/SocketHttpListener/Net/HttpConnection.cs

529 lines
15 KiB
C#
Raw Normal View History

2016-11-11 19:55:12 +00:00
using System;
using System.IO;
2017-09-03 02:42:13 +00:00
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
2017-09-03 02:42:13 +00:00
using System.Security.Cryptography.X509Certificates;
2016-11-11 19:55:12 +00:00
using System.Text;
using System.Threading;
2016-11-11 19:55:12 +00:00
using System.Threading.Tasks;
using MediaBrowser.Model.Cryptography;
using MediaBrowser.Model.IO;
2017-05-09 18:51:26 +00:00
using MediaBrowser.Model.System;
using Microsoft.Extensions.Logging;
2016-11-11 19:55:12 +00:00
namespace SocketHttpListener.Net
{
sealed class HttpConnection
{
2017-05-25 13:00:14 +00:00
private static AsyncCallback s_onreadCallback = new AsyncCallback(OnRead);
2016-11-11 19:55:12 +00:00
const int BufferSize = 8192;
2017-09-03 02:42:13 +00:00
Socket _socket;
2017-05-25 13:00:14 +00:00
Stream _stream;
2018-09-12 17:26:21 +00:00
HttpEndPointListener _epl;
2017-05-25 13:00:14 +00:00
MemoryStream _memoryStream;
byte[] _buffer;
HttpListenerContext _context;
StringBuilder _currentLine;
ListenerPrefix _prefix;
HttpRequestStream _requestStream;
2017-06-15 17:22:05 +00:00
HttpResponseStream _responseStream;
2017-05-25 13:00:14 +00:00
bool _chunked;
int _reuses;
bool _contextBound;
2016-11-11 19:55:12 +00:00
bool secure;
2017-09-03 02:42:13 +00:00
IPEndPoint local_ep;
2017-05-25 13:00:14 +00:00
HttpListener _lastListener;
2017-09-03 02:42:13 +00:00
X509Certificate cert;
SslStream ssl_stream;
2016-11-11 19:55:12 +00:00
2017-03-12 19:27:26 +00:00
private readonly ILogger _logger;
2016-11-11 19:55:12 +00:00
private readonly ICryptoProvider _cryptoProvider;
2018-09-12 17:26:21 +00:00
private readonly IStreamHelper _streamHelper;
2017-03-12 19:27:26 +00:00
private readonly IFileSystem _fileSystem;
2017-05-09 18:51:26 +00:00
private readonly IEnvironmentInfo _environment;
2016-11-11 19:55:12 +00:00
public HttpConnection(ILogger logger, Socket socket, HttpEndPointListener epl, bool secure,
X509Certificate cert, ICryptoProvider cryptoProvider, IStreamHelper streamHelper, IFileSystem fileSystem,
IEnvironmentInfo environment)
2016-11-11 19:55:12 +00:00
{
_logger = logger;
2017-05-25 13:00:14 +00:00
this._socket = socket;
this._epl = epl;
2016-11-11 19:55:12 +00:00
this.secure = secure;
this.cert = cert;
_cryptoProvider = cryptoProvider;
2018-09-12 17:26:21 +00:00
_streamHelper = streamHelper;
2017-03-12 19:27:26 +00:00
_fileSystem = fileSystem;
2017-05-09 18:51:26 +00:00
_environment = environment;
2016-11-11 19:55:12 +00:00
if (secure == false)
{
2017-09-03 02:42:13 +00:00
_stream = new SocketStream(_socket, false);
2016-11-11 19:55:12 +00:00
}
else
{
2018-09-12 17:26:21 +00:00
ssl_stream = new SslStream(new SocketStream(_socket, false), false, (t, c, ch, e) =>
{
if (c == null)
{
return true;
}
2016-11-11 19:55:12 +00:00
2018-09-12 17:26:21 +00:00
//var c2 = c as X509Certificate2;
//if (c2 == null)
//{
// c2 = new X509Certificate2(c.GetRawCertData());
//}
2016-11-11 19:55:12 +00:00
2018-09-12 17:26:21 +00:00
//_clientCert = c2;
//_clientCertErrors = new int[] { (int)e };
return true;
});
2016-11-11 19:55:12 +00:00
2018-09-12 17:26:21 +00:00
_stream = ssl_stream;
}
2016-11-11 19:55:12 +00:00
}
public Stream Stream => _stream;
2016-11-11 19:55:12 +00:00
2018-09-12 17:26:21 +00:00
public async Task Init()
2016-11-11 19:55:12 +00:00
{
2018-09-12 17:26:21 +00:00
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();
2016-11-11 19:55:12 +00:00
}
2018-09-12 17:26:21 +00:00
private void InitInternal()
2016-11-11 19:55:12 +00:00
{
2017-05-25 13:00:14 +00:00
_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);
2016-11-11 19:55:12 +00:00
}
public bool IsClosed => (_socket == null);
2016-11-11 19:55:12 +00:00
public int Reuses => _reuses;
2016-11-11 19:55:12 +00:00
2017-09-03 02:42:13 +00:00
public IPEndPoint LocalEndPoint
2016-11-11 19:55:12 +00:00
{
get
{
if (local_ep != null)
return local_ep;
2017-09-03 02:42:13 +00:00
local_ep = (IPEndPoint)_socket.LocalEndPoint;
2016-11-11 19:55:12 +00:00
return local_ep;
}
}
public IPEndPoint RemoteEndPoint => _socket.RemoteEndPoint as IPEndPoint;
2016-11-11 19:55:12 +00:00
public bool IsSecure => secure;
2016-11-11 19:55:12 +00:00
public ListenerPrefix Prefix
{
get => _prefix;
set => _prefix = value;
2016-11-11 19:55:12 +00:00
}
2018-09-12 17:26:21 +00:00
private void OnTimeout(object unused)
{
//_logger.LogInformation("HttpConnection timer fired");
2018-09-12 17:26:21 +00:00
CloseSocket();
Unbind();
}
2017-05-25 13:00:14 +00:00
public void BeginReadRequest()
2016-11-11 19:55:12 +00:00
{
2017-05-25 13:00:14 +00:00
if (_buffer == null)
_buffer = new byte[BufferSize];
2016-11-11 19:55:12 +00:00
try
{
2017-05-25 13:00:14 +00:00
_stream.BeginRead(_buffer, 0, BufferSize, s_onreadCallback, this);
2016-11-11 19:55:12 +00:00
}
2017-05-25 13:00:14 +00:00
catch
2016-11-11 19:55:12 +00:00
{
2017-05-25 13:00:14 +00:00
CloseSocket();
Unbind();
2016-11-11 19:55:12 +00:00
}
}
2017-05-24 19:12:55 +00:00
public HttpRequestStream GetRequestStream(bool chunked, long contentlength)
2016-11-11 19:55:12 +00:00
{
2017-05-25 13:00:14 +00:00
if (_requestStream == null)
2016-11-11 19:55:12 +00:00
{
2017-05-25 13:00:14 +00:00
byte[] buffer = _memoryStream.GetBuffer();
int length = (int)_memoryStream.Length;
_memoryStream = null;
2016-11-11 19:55:12 +00:00
if (chunked)
{
2017-05-25 13:00:14 +00:00
_chunked = true;
//_context.Response.SendChunked = true;
_requestStream = new ChunkedInputStream(_context, _stream, buffer, _position, length - _position);
2016-11-11 19:55:12 +00:00
}
else
{
2017-05-25 13:00:14 +00:00
_requestStream = new HttpRequestStream(_stream, buffer, _position, length - _position, contentlength);
2016-11-11 19:55:12 +00:00
}
}
2017-05-25 13:00:14 +00:00
return _requestStream;
2016-11-11 19:55:12 +00:00
}
2017-06-15 17:22:05 +00:00
public HttpResponseStream GetResponseStream(bool isExpect100Continue = false)
2016-11-11 19:55:12 +00:00
{
2017-05-25 13:00:14 +00:00
// TODO: can we get this _stream before reading the input?
if (_responseStream == null)
2016-11-11 19:55:12 +00:00
{
2017-05-25 13:00:14 +00:00
var supportsDirectSocketAccess = !_context.Response.SendChunked && !isExpect100Continue && !secure;
2017-03-12 19:27:26 +00:00
2018-09-12 17:26:21 +00:00
_responseStream = new HttpResponseStream(_stream, _context.Response, false, _streamHelper, _socket, supportsDirectSocketAccess, _environment, _fileSystem, _logger);
2016-11-11 19:55:12 +00:00
}
2017-05-25 13:00:14 +00:00
return _responseStream;
2016-11-11 19:55:12 +00:00
}
2017-05-25 13:00:14 +00:00
private static void OnRead(IAsyncResult ares)
2016-11-11 19:55:12 +00:00
{
2019-01-13 20:37:13 +00:00
var cnc = (HttpConnection)ares.AsyncState;
2017-05-25 13:00:14 +00:00
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
2016-11-11 19:55:12 +00:00
{
2017-05-25 13:00:14 +00:00
if (_memoryStream != null && _memoryStream.Length > 0)
SendError();
if (_socket != null)
{
CloseSocket();
Unbind();
}
2016-11-11 19:55:12 +00:00
return;
}
if (nread == 0)
{
CloseSocket();
Unbind();
return;
}
2017-05-25 13:00:14 +00:00
if (ProcessInput(_memoryStream))
2016-11-11 19:55:12 +00:00
{
2017-05-25 13:00:14 +00:00
if (!_context.HaveError)
_context.Request.FinishInitialization();
2016-11-11 19:55:12 +00:00
2017-05-25 13:00:14 +00:00
if (_context.HaveError)
2016-11-11 19:55:12 +00:00
{
SendError();
Close(true);
return;
}
2017-05-25 13:00:14 +00:00
if (!_epl.BindContext(_context))
2016-11-11 19:55:12 +00:00
{
2017-06-21 14:50:54 +00:00
const int NotFoundErrorCode = 404;
SendError(HttpStatusDescription.Get(NotFoundErrorCode), NotFoundErrorCode);
2016-11-11 19:55:12 +00:00
Close(true);
return;
}
2019-01-18 15:48:01 +00:00
HttpListener listener = _epl.Listener;
2017-05-25 13:00:14 +00:00
if (_lastListener != listener)
2016-11-11 19:55:12 +00:00
{
RemoveConnection();
listener.AddConnection(this);
2017-05-25 13:00:14 +00:00
_lastListener = listener;
2016-11-11 19:55:12 +00:00
}
2017-05-25 13:00:14 +00:00
_contextBound = true;
listener.RegisterContext(_context);
2016-11-11 19:55:12 +00:00
return;
}
2017-05-25 13:00:14 +00:00
_stream.BeginRead(_buffer, 0, BufferSize, s_onreadCallback, this);
2016-11-11 19:55:12 +00:00
}
2017-05-25 13:00:14 +00:00
private void RemoveConnection()
2016-11-11 19:55:12 +00:00
{
2017-05-25 13:00:14 +00:00
if (_lastListener == null)
_epl.RemoveConnection(this);
2016-11-11 19:55:12 +00:00
else
2017-05-25 13:00:14 +00:00
_lastListener.RemoveConnection(this);
2016-11-11 19:55:12 +00:00
}
2017-05-25 13:00:14 +00:00
private enum InputState
2016-11-11 19:55:12 +00:00
{
RequestLine,
Headers
}
2017-05-25 13:00:14 +00:00
private enum LineState
2016-11-11 19:55:12 +00:00
{
None,
CR,
LF
}
2017-05-25 13:00:14 +00:00
InputState _inputState = InputState.RequestLine;
LineState _lineState = LineState.None;
int _position;
2016-11-11 19:55:12 +00:00
// true -> done processing
// false -> need more input
2017-05-25 13:00:14 +00:00
private bool ProcessInput(MemoryStream ms)
2016-11-11 19:55:12 +00:00
{
2017-05-25 13:00:14 +00:00
byte[] buffer = ms.GetBuffer();
2016-11-11 19:55:12 +00:00
int len = (int)ms.Length;
int used = 0;
string line;
while (true)
{
2017-05-25 13:00:14 +00:00
if (_context.HaveError)
2016-11-11 19:55:12 +00:00
return true;
2017-05-25 13:00:14 +00:00
if (_position >= len)
2016-11-11 19:55:12 +00:00
break;
try
{
2017-05-25 13:00:14 +00:00
line = ReadLine(buffer, _position, len - _position, ref used);
_position += used;
2016-11-11 19:55:12 +00:00
}
catch
{
2017-05-25 13:00:14 +00:00
_context.ErrorMessage = "Bad request";
_context.ErrorStatus = 400;
2016-11-11 19:55:12 +00:00
return true;
}
if (line == null)
break;
if (line == "")
{
2017-05-25 13:00:14 +00:00
if (_inputState == InputState.RequestLine)
2016-11-11 19:55:12 +00:00
continue;
2017-05-25 13:00:14 +00:00
_currentLine = null;
2016-11-11 19:55:12 +00:00
ms = null;
return true;
}
2017-05-25 13:00:14 +00:00
if (_inputState == InputState.RequestLine)
2016-11-11 19:55:12 +00:00
{
2017-05-25 13:00:14 +00:00
_context.Request.SetRequestLine(line);
_inputState = InputState.Headers;
2016-11-11 19:55:12 +00:00
}
else
{
try
{
2017-05-25 13:00:14 +00:00
_context.Request.AddHeader(line);
2016-11-11 19:55:12 +00:00
}
catch (Exception e)
{
2017-05-25 13:00:14 +00:00
_context.ErrorMessage = e.Message;
_context.ErrorStatus = 400;
2016-11-11 19:55:12 +00:00
return true;
}
}
}
if (used == len)
{
ms.SetLength(0);
2017-05-25 13:00:14 +00:00
_position = 0;
2016-11-11 19:55:12 +00:00
}
return false;
}
2017-05-25 13:00:14 +00:00
private string ReadLine(byte[] buffer, int offset, int len, ref int used)
2016-11-11 19:55:12 +00:00
{
2017-05-25 13:00:14 +00:00
if (_currentLine == null)
_currentLine = new StringBuilder(128);
2016-11-11 19:55:12 +00:00
int last = offset + len;
used = 0;
2017-05-25 13:00:14 +00:00
for (int i = offset; i < last && _lineState != LineState.LF; i++)
2016-11-11 19:55:12 +00:00
{
used++;
byte b = buffer[i];
if (b == 13)
{
2017-05-25 13:00:14 +00:00
_lineState = LineState.CR;
2016-11-11 19:55:12 +00:00
}
else if (b == 10)
{
2017-05-25 13:00:14 +00:00
_lineState = LineState.LF;
2016-11-11 19:55:12 +00:00
}
else
{
2017-05-25 13:00:14 +00:00
_currentLine.Append((char)b);
2016-11-11 19:55:12 +00:00
}
}
string result = null;
2017-05-25 13:00:14 +00:00
if (_lineState == LineState.LF)
2016-11-11 19:55:12 +00:00
{
2017-05-25 13:00:14 +00:00
_lineState = LineState.None;
result = _currentLine.ToString();
_currentLine.Length = 0;
2016-11-11 19:55:12 +00:00
}
return result;
}
public void SendError(string msg, int status)
{
try
{
2019-01-18 15:48:01 +00:00
HttpListenerResponse response = _context.Response;
2016-11-11 19:55:12 +00:00
response.StatusCode = status;
response.ContentType = "text/html";
2017-06-15 17:22:05 +00:00
string description = HttpStatusDescription.Get(status);
2016-11-11 19:55:12 +00:00
string str;
if (msg != null)
2017-05-25 13:00:14 +00:00
str = string.Format("<h1>{0} ({1})</h1>", description, msg);
2016-11-11 19:55:12 +00:00
else
2017-05-25 13:00:14 +00:00
str = string.Format("<h1>{0}</h1>", description);
2016-11-11 19:55:12 +00:00
byte[] error = Encoding.UTF8.GetBytes(str);
2017-05-25 13:00:14 +00:00
response.Close(error, false);
2016-11-11 19:55:12 +00:00
}
catch
{
// response was already closed
}
}
public void SendError()
{
2017-05-25 13:00:14 +00:00
SendError(_context.ErrorMessage, _context.ErrorStatus);
2016-11-11 19:55:12 +00:00
}
2017-05-25 13:00:14 +00:00
private void Unbind()
2016-11-11 19:55:12 +00:00
{
2017-05-25 13:00:14 +00:00
if (_contextBound)
2016-11-11 19:55:12 +00:00
{
2017-05-25 13:00:14 +00:00
_epl.UnbindContext(_context);
_contextBound = false;
2016-11-11 19:55:12 +00:00
}
}
public void Close()
{
Close(false);
}
private void CloseSocket()
{
2017-05-25 13:00:14 +00:00
if (_socket == null)
2016-11-11 19:55:12 +00:00
return;
try
{
2017-05-25 13:00:14 +00:00
_socket.Close();
2016-11-11 19:55:12 +00:00
}
2017-05-25 13:00:14 +00:00
catch { }
2016-11-11 19:55:12 +00:00
finally
{
2017-05-25 13:00:14 +00:00
_socket = null;
2016-11-11 19:55:12 +00:00
}
2017-05-25 13:00:14 +00:00
2016-11-11 19:55:12 +00:00
RemoveConnection();
}
2017-05-25 13:00:14 +00:00
internal void Close(bool force)
2016-11-11 19:55:12 +00:00
{
2017-05-25 13:00:14 +00:00
if (_socket != null)
2016-11-11 19:55:12 +00:00
{
2017-05-25 13:00:14 +00:00
Stream st = GetResponseStream();
if (st != null)
st.Close();
2016-11-11 19:55:12 +00:00
2017-05-25 13:00:14 +00:00
_responseStream = null;
2016-11-11 19:55:12 +00:00
}
2017-05-25 13:00:14 +00:00
if (_socket != null)
2016-11-11 19:55:12 +00:00
{
2017-05-25 13:00:14 +00:00
force |= !_context.Request.KeepAlive;
if (!force)
force = (string.Equals(_context.Response.Headers["connection"], "close", StringComparison.OrdinalIgnoreCase));
if (!force && _context.Request.FlushInput())
2016-11-11 19:55:12 +00:00
{
2017-05-25 13:00:14 +00:00
if (_chunked && _context.Response.ForceCloseChunked == false)
{
// Don't close. Keep working.
_reuses++;
Unbind();
2018-09-12 17:26:21 +00:00
InitInternal();
2017-05-25 13:00:14 +00:00
BeginReadRequest();
return;
}
_reuses++;
2016-11-11 19:55:12 +00:00
Unbind();
2018-09-12 17:26:21 +00:00
InitInternal();
2016-11-11 19:55:12 +00:00
BeginReadRequest();
return;
}
2019-01-18 15:48:01 +00:00
Socket s = _socket;
2017-05-25 13:00:14 +00:00
_socket = null;
2016-11-11 19:55:12 +00:00
try
{
if (s != null)
2017-09-03 02:42:13 +00:00
s.Shutdown(SocketShutdown.Both);
2016-11-11 19:55:12 +00:00
}
catch
{
}
finally
{
if (s != null)
2017-10-17 19:49:39 +00:00
{
try
{
s.Close();
}
catch { }
}
2016-11-11 19:55:12 +00:00
}
Unbind();
RemoveConnection();
return;
}
}
}
}