| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526 | using System;using System.Collections.Generic;using System.Net;using System.Net.Sockets;using System.Security.Cryptography.X509Certificates;using System.Threading;using MediaBrowser.Model.Cryptography;using MediaBrowser.Model.IO;using MediaBrowser.Model.Net;using MediaBrowser.Model.System;using Microsoft.Extensions.Logging;namespace SocketHttpListener.Net{    internal sealed class HttpEndPointListener    {        private HttpListener _listener;        private IPEndPoint _endpoint;        private Socket _socket;        private Dictionary<ListenerPrefix, HttpListener> _prefixes;        private List<ListenerPrefix> _unhandledPrefixes; // host = '*'        private List<ListenerPrefix> _allPrefixes;       // host = '+'        private X509Certificate _cert;        private bool _secure;        private Dictionary<HttpConnection, HttpConnection> _unregisteredConnections;        private readonly ILogger _logger;        private bool _closed;        private bool _enableDualMode;        private readonly ICryptoProvider _cryptoProvider;        private readonly ISocketFactory _socketFactory;        private readonly IStreamHelper _streamHelper;        private readonly IFileSystem _fileSystem;        private readonly IEnvironmentInfo _environment;        public HttpEndPointListener(HttpListener listener, IPAddress addr, int port, bool secure, X509Certificate cert,            ILogger logger, ICryptoProvider cryptoProvider, ISocketFactory socketFactory, IStreamHelper streamHelper,            IFileSystem fileSystem, IEnvironmentInfo environment)        {            this._listener = listener;            _logger = logger;            _cryptoProvider = cryptoProvider;            _socketFactory = socketFactory;            _streamHelper = streamHelper;            _fileSystem = fileSystem;            _environment = environment;            this._secure = secure;            this._cert = cert;            _enableDualMode = addr.Equals(IPAddress.IPv6Any);            _endpoint = new IPEndPoint(addr, port);            _prefixes = new Dictionary<ListenerPrefix, HttpListener>();            _unregisteredConnections = new Dictionary<HttpConnection, HttpConnection>();            CreateSocket();        }        internal HttpListener Listener => _listener;        private void CreateSocket()        {            try            {                _socket = CreateSocket(_endpoint.Address.AddressFamily, _enableDualMode);            }            catch (SocketCreateException ex)            {                if (_enableDualMode && _endpoint.Address.Equals(IPAddress.IPv6Any) &&                    (string.Equals(ex.ErrorCode, "AddressFamilyNotSupported", StringComparison.OrdinalIgnoreCase) ||                    // mono 4.8.1 and lower on bsd is throwing this                    string.Equals(ex.ErrorCode, "ProtocolNotSupported", StringComparison.OrdinalIgnoreCase) ||                    // mono 5.2 on bsd is throwing this                    string.Equals(ex.ErrorCode, "OperationNotSupported", StringComparison.OrdinalIgnoreCase)))                {                    _endpoint = new IPEndPoint(IPAddress.Any, _endpoint.Port);                    _enableDualMode = false;                    _socket = CreateSocket(_endpoint.Address.AddressFamily, _enableDualMode);                }                else                {                    throw;                }            }            try            {                _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);            }            catch (SocketException)            {                // This is not supported on all operating systems (qnap)            }            _socket.Bind(_endpoint);            // This is the number TcpListener uses.            _socket.Listen(2147483647);            Accept();            _closed = false;        }        private void Accept()        {            var acceptEventArg = new SocketAsyncEventArgs();            acceptEventArg.UserToken = this;            acceptEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(OnAccept);            Accept(acceptEventArg);        }        private static void TryCloseAndDispose(Socket socket)        {            try            {                using (socket)                {                    socket.Close();                }            }            catch            {            }        }        private static void TryClose(Socket socket)        {            try            {                socket.Close();            }            catch            {            }        }        private void Accept(SocketAsyncEventArgs acceptEventArg)        {            // acceptSocket must be cleared since the context object is being reused            acceptEventArg.AcceptSocket = null;            try            {                bool willRaiseEvent = _socket.AcceptAsync(acceptEventArg);                if (!willRaiseEvent)                {                    ProcessAccept(acceptEventArg);                }            }            catch (ObjectDisposedException)            {                // TODO Investigate or properly fix.            }            catch (Exception ex)            {                var epl = (HttpEndPointListener)acceptEventArg.UserToken;                epl._logger.LogError(ex, "Error in socket.AcceptAsync");            }        }        // This method is the callback method associated with Socket.AcceptAsync        // operations and is invoked when an accept operation is complete        //        private static void OnAccept(object sender, SocketAsyncEventArgs e)        {            ProcessAccept(e);        }        private static async void ProcessAccept(SocketAsyncEventArgs args)        {            var epl = (HttpEndPointListener)args.UserToken;            if (epl._closed)            {                return;            }            // http://msdn.microsoft.com/en-us/library/system.net.sockets.acceptSocket.acceptasync%28v=vs.110%29.aspx            // Under certain conditions ConnectionReset can occur            // Need to attept to re-accept            var socketError = args.SocketError;            var accepted = args.AcceptSocket;            epl.Accept(args);            if (socketError == SocketError.ConnectionReset)            {                epl._logger.LogError("SocketError.ConnectionReset reported. Attempting to re-accept.");                return;            }            if (accepted == null)            {                return;            }            if (epl._secure && epl._cert == null)            {                TryClose(accepted);                return;            }            try            {                var remoteEndPointString = accepted.RemoteEndPoint == null ? string.Empty : accepted.RemoteEndPoint.ToString();                var localEndPointString = accepted.LocalEndPoint == null ? string.Empty : accepted.LocalEndPoint.ToString();                //_logger.LogInformation("HttpEndPointListener Accepting connection from {0} to {1} secure connection requested: {2}", remoteEndPointString, localEndPointString, _secure);                var conn = new HttpConnection(epl._logger, accepted, epl, epl._secure, epl._cert, epl._cryptoProvider, epl._streamHelper, epl._fileSystem, epl._environment);                await conn.Init().ConfigureAwait(false);                //_logger.LogDebug("Adding unregistered connection to {0}. Id: {1}", accepted.RemoteEndPoint, connectionId);                lock (epl._unregisteredConnections)                {                    epl._unregisteredConnections[conn] = conn;                }                conn.BeginReadRequest();            }            catch (Exception ex)            {                epl._logger.LogError(ex, "Error in ProcessAccept");                TryClose(accepted);                epl.Accept();                return;            }        }        private Socket CreateSocket(AddressFamily addressFamily, bool dualMode)        {            try            {                var socket = new Socket(addressFamily, System.Net.Sockets.SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp);                if (dualMode)                {                    socket.DualMode = true;                }                return socket;            }            catch (SocketException ex)            {                throw new SocketCreateException(ex.SocketErrorCode.ToString(), ex);            }            catch (ArgumentException ex)            {                if (dualMode)                {                    // Mono for BSD incorrectly throws ArgumentException instead of SocketException                    throw new SocketCreateException("AddressFamilyNotSupported", ex);                }                else                {                    throw;                }            }        }        internal void RemoveConnection(HttpConnection conn)        {            lock (_unregisteredConnections)            {                _unregisteredConnections.Remove(conn);            }        }        public bool BindContext(HttpListenerContext context)        {            var req = context.Request;            HttpListener listener = SearchListener(req.Url, out var prefix);            if (listener == null)                return false;            context.Connection.Prefix = prefix;            return true;        }        public void UnbindContext(HttpListenerContext context)        {            if (context == null || context.Request == null)                return;            _listener.UnregisterContext(context);        }        private HttpListener SearchListener(Uri uri, out ListenerPrefix prefix)        {            prefix = null;            if (uri == null)                return null;            string host = uri.Host;            int port = uri.Port;            string path = WebUtility.UrlDecode(uri.AbsolutePath);            string pathSlash = path[path.Length - 1] == '/' ? path : path + "/";            HttpListener bestMatch = null;            int bestLength = -1;            if (host != null && host != "")            {                Dictionary<ListenerPrefix, HttpListener> localPrefixes = _prefixes;                foreach (var p in localPrefixes.Keys)                {                    string ppath = p.Path;                    if (ppath.Length < bestLength)                        continue;                    if (p.Host != host || p.Port != port)                        continue;                    if (path.StartsWith(ppath) || pathSlash.StartsWith(ppath))                    {                        bestLength = ppath.Length;                        bestMatch = localPrefixes[p];                        prefix = p;                    }                }                if (bestLength != -1)                    return bestMatch;            }            List<ListenerPrefix> list = _unhandledPrefixes;            bestMatch = MatchFromList(host, path, list, out prefix);            if (path != pathSlash && bestMatch == null)                bestMatch = MatchFromList(host, pathSlash, list, out prefix);            if (bestMatch != null)                return bestMatch;            list = _allPrefixes;            bestMatch = MatchFromList(host, path, list, out prefix);            if (path != pathSlash && bestMatch == null)                bestMatch = MatchFromList(host, pathSlash, list, out prefix);            if (bestMatch != null)                return bestMatch;            return null;        }        private HttpListener MatchFromList(string host, string path, List<ListenerPrefix> list, out ListenerPrefix prefix)        {            prefix = null;            if (list == null)                return null;            HttpListener bestMatch = null;            int bestLength = -1;            foreach (ListenerPrefix p in list)            {                string ppath = p.Path;                if (ppath.Length < bestLength)                    continue;                if (path.StartsWith(ppath))                {                    bestLength = ppath.Length;                    bestMatch = p._listener;                    prefix = p;                }            }            return bestMatch;        }        private void AddSpecial(List<ListenerPrefix> list, ListenerPrefix prefix)        {            if (list == null)                return;            foreach (ListenerPrefix p in list)            {                if (p.Path == prefix.Path)                    throw new Exception("net_listener_already");            }            list.Add(prefix);        }        private bool RemoveSpecial(List<ListenerPrefix> list, ListenerPrefix prefix)        {            if (list == null)                return false;            int c = list.Count;            for (int i = 0; i < c; i++)            {                ListenerPrefix p = list[i];                if (p.Path == prefix.Path)                {                    list.RemoveAt(i);                    return true;                }            }            return false;        }        private void CheckIfRemove()        {            if (_prefixes.Count > 0)                return;            List<ListenerPrefix> list = _unhandledPrefixes;            if (list != null && list.Count > 0)                return;            list = _allPrefixes;            if (list != null && list.Count > 0)                return;            HttpEndPointManager.RemoveEndPoint(this, _endpoint);        }        public void Close()        {            _closed = true;            _socket.Close();            lock (_unregisteredConnections)            {                // Clone the list because RemoveConnection can be called from Close                var connections = new List<HttpConnection>(_unregisteredConnections.Keys);                foreach (HttpConnection c in connections)                    c.Close(true);                _unregisteredConnections.Clear();            }        }        public void AddPrefix(ListenerPrefix prefix, HttpListener listener)        {            List<ListenerPrefix> current;            List<ListenerPrefix> future;            if (prefix.Host == "*")            {                do                {                    current = _unhandledPrefixes;                    future = current != null ? new List<ListenerPrefix>(current) : new List<ListenerPrefix>();                    prefix._listener = listener;                    AddSpecial(future, prefix);                } while (Interlocked.CompareExchange(ref _unhandledPrefixes, future, current) != current);                return;            }            if (prefix.Host == "+")            {                do                {                    current = _allPrefixes;                    future = current != null ? new List<ListenerPrefix>(current) : new List<ListenerPrefix>();                    prefix._listener = listener;                    AddSpecial(future, prefix);                } while (Interlocked.CompareExchange(ref _allPrefixes, future, current) != current);                return;            }            Dictionary<ListenerPrefix, HttpListener> prefs, p2;            do            {                prefs = _prefixes;                if (prefs.ContainsKey(prefix))                {                    throw new Exception("net_listener_already");                }                p2 = new Dictionary<ListenerPrefix, HttpListener>(prefs);                p2[prefix] = listener;            } while (Interlocked.CompareExchange(ref _prefixes, p2, prefs) != prefs);        }        public void RemovePrefix(ListenerPrefix prefix, HttpListener listener)        {            List<ListenerPrefix> current;            List<ListenerPrefix> future;            if (prefix.Host == "*")            {                do                {                    current = _unhandledPrefixes;                    future = current != null ? new List<ListenerPrefix>(current) : new List<ListenerPrefix>();                    if (!RemoveSpecial(future, prefix))                        break; // Prefix not found                } while (Interlocked.CompareExchange(ref _unhandledPrefixes, future, current) != current);                CheckIfRemove();                return;            }            if (prefix.Host == "+")            {                do                {                    current = _allPrefixes;                    future = current != null ? new List<ListenerPrefix>(current) : new List<ListenerPrefix>();                    if (!RemoveSpecial(future, prefix))                        break; // Prefix not found                } while (Interlocked.CompareExchange(ref _allPrefixes, future, current) != current);                CheckIfRemove();                return;            }            Dictionary<ListenerPrefix, HttpListener> prefs, p2;            do            {                prefs = _prefixes;                if (!prefs.ContainsKey(prefix))                    break;                p2 = new Dictionary<ListenerPrefix, HttpListener>(prefs);                p2.Remove(prefix);            } while (Interlocked.CompareExchange(ref _prefixes, p2, prefs) != prefs);            CheckIfRemove();        }    }}
 |