using System; using System.IO; using System.Text; using System.Threading.Tasks; using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Net; using MediaBrowser.Model.System; using MediaBrowser.Model.Text; using SocketHttpListener.Primitives; namespace SocketHttpListener.Net { sealed class HttpConnection { const int BufferSize = 8192; IAcceptSocket sock; Stream stream; EndPointListener epl; MemoryStream ms; byte[] buffer; HttpListenerContext context; StringBuilder current_line; ListenerPrefix prefix; HttpRequestStream i_stream; Stream o_stream; bool chunked; int reuses; bool context_bound; bool secure; int s_timeout = 300000; // 90k ms for first request, 15k ms from then on IpEndPointInfo local_ep; HttpListener last_listener; int[] client_cert_errors; ICertificate cert; Stream ssl_stream; private readonly ILogger _logger; private readonly ICryptoProvider _cryptoProvider; private readonly IMemoryStreamFactory _memoryStreamFactory; private readonly ITextEncoding _textEncoding; private readonly IStreamFactory _streamFactory; private readonly IFileSystem _fileSystem; private readonly IEnvironmentInfo _environment; private HttpConnection(ILogger logger, IAcceptSocket sock, EndPointListener epl, bool secure, ICertificate cert, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem, IEnvironmentInfo environment) { _logger = logger; this.sock = sock; this.epl = epl; this.secure = secure; this.cert = cert; _cryptoProvider = cryptoProvider; _memoryStreamFactory = memoryStreamFactory; _textEncoding = textEncoding; _fileSystem = fileSystem; _environment = environment; _streamFactory = streamFactory; } private async Task InitStream() { if (secure == false) { stream = _streamFactory.CreateNetworkStream(sock, false); } else { //ssl_stream = epl.Listener.CreateSslStream(new NetworkStream(sock, false), false, (t, c, ch, e) => //{ // if (c == null) // return true; // var c2 = c as X509Certificate2; // if (c2 == null) // c2 = new X509Certificate2(c.GetRawCertData()); // client_cert = c2; // client_cert_errors = new int[] { (int)e }; // return true; //}); //stream = ssl_stream.AuthenticatedStream; ssl_stream = _streamFactory.CreateSslStream(_streamFactory.CreateNetworkStream(sock, false), false); await _streamFactory.AuthenticateSslStreamAsServer(ssl_stream, cert).ConfigureAwait(false); stream = ssl_stream; } Init(); } public static async Task Create(ILogger logger, IAcceptSocket sock, EndPointListener epl, bool secure, ICertificate cert, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem, IEnvironmentInfo environment) { var connection = new HttpConnection(logger, sock, epl, secure, cert, cryptoProvider, streamFactory, memoryStreamFactory, textEncoding, fileSystem, environment); await connection.InitStream().ConfigureAwait(false); return connection; } public Stream Stream { get { return stream; } } internal int[] ClientCertificateErrors { get { return client_cert_errors; } } void Init() { if (ssl_stream != null) { //ssl_stream.AuthenticateAsServer(client_cert, true, (SslProtocols)ServicePointManager.SecurityProtocol, false); //_streamFactory.AuthenticateSslStreamAsServer(ssl_stream, cert); } context_bound = false; i_stream = null; o_stream = null; prefix = null; chunked = false; ms = _memoryStreamFactory.CreateNew(); position = 0; input_state = InputState.RequestLine; line_state = LineState.None; context = new HttpListenerContext(this, _logger, _cryptoProvider, _memoryStreamFactory, _textEncoding, _fileSystem); } public bool IsClosed { get { return (sock == null); } } public int Reuses { get { return reuses; } } public IpEndPointInfo LocalEndPoint { get { if (local_ep != null) return local_ep; local_ep = (IpEndPointInfo)sock.LocalEndPoint; return local_ep; } } public IpEndPointInfo RemoteEndPoint { get { return (IpEndPointInfo)sock.RemoteEndPoint; } } public bool IsSecure { get { return secure; } } public ListenerPrefix Prefix { get { return prefix; } set { prefix = value; } } public async Task BeginReadRequest() { if (buffer == null) buffer = new byte[BufferSize]; try { //if (reuses == 1) // s_timeout = 15000; var nRead = await stream.ReadAsync(buffer, 0, BufferSize).ConfigureAwait(false); OnReadInternal(nRead); } catch (Exception ex) { OnReadInternalException(ms, ex); } } public HttpRequestStream GetRequestStream(bool chunked, long contentlength) { if (i_stream == null) { byte[] buffer; _memoryStreamFactory.TryGetBuffer(ms, out buffer); int length = (int)ms.Length; ms = null; if (chunked) { this.chunked = true; //context.Response.SendChunked = true; i_stream = new ChunkedInputStream(context, stream, buffer, position, length - position); } else { i_stream = new HttpRequestStream(stream, buffer, position, length - position, contentlength); } } return i_stream; } public Stream GetResponseStream(bool isExpect100Continue = false) { // TODO: can we get this stream before reading the input? if (o_stream == null) { //context.Response.DetermineIfChunked(); var supportsDirectSocketAccess = !context.Response.SendChunked && !isExpect100Continue && !secure; //o_stream = new ResponseStream(stream, context.Response, _memoryStreamFactory, _textEncoding, _fileSystem, sock, supportsDirectSocketAccess, _logger, _environment); o_stream = new HttpResponseStream(stream, context.Response, false, _memoryStreamFactory, sock, supportsDirectSocketAccess, _environment, _fileSystem); } return o_stream; } void OnReadInternal(int nread) { ms.Write(buffer, 0, nread); if (ms.Length > 32768) { SendError("Bad request", 400); Close(true); return; } if (nread == 0) { //if (ms.Length > 0) // SendError (); // Why bother? CloseSocket(); Unbind(); return; } if (ProcessInput(ms)) { if (!context.HaveError) context.Request.FinishInitialization(); if (context.HaveError) { SendError(); Close(true); return; } if (!epl.BindContext(context)) { SendError("Invalid host", 400); Close(true); return; } HttpListener listener = epl.Listener; if (last_listener != listener) { RemoveConnection(); listener.AddConnection(this); last_listener = listener; } context_bound = true; listener.RegisterContext(context); return; } BeginReadRequest(); } private void OnReadInternalException(MemoryStream ms, Exception ex) { //_logger.ErrorException("Error in HttpConnection.OnReadInternal", ex); if (ms != null && ms.Length > 0) SendError(); if (sock != null) { CloseSocket(); Unbind(); } } void RemoveConnection() { if (last_listener == null) epl.RemoveConnection(this); else last_listener.RemoveConnection(this); } enum InputState { RequestLine, Headers } enum LineState { None, CR, LF } InputState input_state = InputState.RequestLine; LineState line_state = LineState.None; int position; // true -> done processing // false -> need more input bool ProcessInput(MemoryStream ms) { byte[] buffer; _memoryStreamFactory.TryGetBuffer(ms, out buffer); 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 (input_state == InputState.RequestLine) continue; current_line = null; ms = null; return true; } if (input_state == InputState.RequestLine) { context.Request.SetRequestLine(line); input_state = 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; } string ReadLine(byte[] buffer, int offset, int len, ref int used) { if (current_line == null) current_line = new StringBuilder(128); int last = offset + len; used = 0; for (int i = offset; i < last && line_state != LineState.LF; i++) { used++; byte b = buffer[i]; if (b == 13) { line_state = LineState.CR; } else if (b == 10) { line_state = LineState.LF; } else { current_line.Append((char)b); } } string result = null; if (line_state == LineState.LF) { line_state = LineState.None; result = current_line.ToString(); current_line.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 = HttpListenerResponse.GetStatusDescription(status); string str; if (msg != null) str = String.Format("

{0} ({1})

", description, msg); else str = String.Format("

{0}

", description); byte[] error = context.Response.ContentEncoding.GetBytes(str); response.ContentLength64 = error.Length; response.OutputStream.Write(error, 0, (int)error.Length); response.Close(); } catch { // response was already closed } } public void SendError() { SendError(context.ErrorMessage, context.ErrorStatus); } void Unbind() { if (context_bound) { epl.UnbindContext(context); context_bound = false; } } public void Close() { Close(false); } private void CloseSocket() { if (sock == null) return; try { sock.Close(); } catch { } finally { sock = null; } RemoveConnection(); } internal void Close(bool force_close) { if (sock != null) { if (!context.Request.IsWebSocketRequest || force_close) { Stream st = GetResponseStream(); if (st != null) { st.Dispose(); } o_stream = null; } } if (sock != null) { force_close |= !context.Request.KeepAlive; if (!force_close) force_close = (string.Equals(context.Response.Headers["connection"], "close", StringComparison.OrdinalIgnoreCase)); /* if (!force_close) { // bool conn_close = (status_code == 400 || status_code == 408 || status_code == 411 || // status_code == 413 || status_code == 414 || status_code == 500 || // status_code == 503); force_close |= (context.Request.ProtocolVersion <= HttpVersion.Version10); } */ if (!force_close && context.Request.FlushInput()) { reuses++; Unbind(); Init(); BeginReadRequest(); return; } IAcceptSocket s = sock; sock = null; try { if (s != null) s.Shutdown(true); } catch { } finally { if (s != null) s.Close(); } Unbind(); RemoveConnection(); return; } } } }