diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 8244490fc5..1668278fae 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -2217,7 +2217,7 @@ public async Task CheckForApplicationUpdate(CancellationTo try { var result = await new GithubUpdater(HttpClient, JsonSerializer).CheckForUpdateResult("MediaBrowser", - "Emby", + "Emby.Releases", ApplicationVersion, updateLevel, ReleaseAssetFilename, diff --git a/RSSDP/SsdpDevicePublisher.cs b/RSSDP/SsdpDevicePublisher.cs index d914179948..5d4e223c14 100644 --- a/RSSDP/SsdpDevicePublisher.cs +++ b/RSSDP/SsdpDevicePublisher.cs @@ -113,8 +113,6 @@ public void AddDevice(SsdpRootDevice device) { //_MinCacheTime = minCacheTime; - ConnectToDeviceEvents(device); - WriteTrace("Device Added", device); SetRebroadcastAliveNotificationsTimer(minCacheTime); @@ -152,8 +150,6 @@ public async Task RemoveDevice(SsdpRootDevice device) { //_MinCacheTime = minCacheTime; - DisconnectFromDeviceEvents(device); - WriteTrace("Device Removed", device); await SendByeByeNotifications(device, true, CancellationToken.None).ConfigureAwait(false); @@ -527,9 +523,8 @@ private Task SendByeByeNotification(SsdpDevice device, string notificationType, var message = BuildMessage(header, values); var sendCount = IsDisposed ? 1 : 3; + WriteTrace(String.Format("Sent byebye notification"), device); return _CommsServer.SendMulticastMessage(message, sendCount, cancellationToken); - - //WriteTrace(String.Format("Sent byebye notification"), device); } #endregion @@ -630,50 +625,10 @@ private void WriteTrace(string text, SsdpDevice device) WriteTrace(text + " " + device.DeviceType + " - " + device.Uuid); } - private void ConnectToDeviceEvents(SsdpDevice device) - { - device.DeviceAdded += device_DeviceAdded; - device.DeviceRemoved += device_DeviceRemoved; - - foreach (var childDevice in device.Devices) - { - ConnectToDeviceEvents(childDevice); - } - } - - private void DisconnectFromDeviceEvents(SsdpDevice device) - { - device.DeviceAdded -= device_DeviceAdded; - device.DeviceRemoved -= device_DeviceRemoved; - - foreach (var childDevice in device.Devices) - { - DisconnectFromDeviceEvents(childDevice); - } - } - #endregion #region Event Handlers - private void device_DeviceAdded(object sender, DeviceEventArgs e) - { - if (IsDisposed) - { - return; - } - - SendAliveNotifications(e.Device, false, CancellationToken.None); - ConnectToDeviceEvents(e.Device); - } - - private void device_DeviceRemoved(object sender, DeviceEventArgs e) - { - var task = SendByeByeNotifications(e.Device, false, CancellationToken.None); - Task.WaitAll(task); - DisconnectFromDeviceEvents(e.Device); - } - private void CommsServer_RequestReceived(object sender, RequestReceivedEventArgs e) { if (this.IsDisposed) return; diff --git a/SharedVersion.cs b/SharedVersion.cs index f684adaaf2..4c99c8bd94 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,3 +1,3 @@ using System.Reflection; -[assembly: AssemblyVersion("3.3.1.7")] +[assembly: AssemblyVersion("3.3.1.8")] diff --git a/SocketHttpListener/Net/EndPointListener.cs b/SocketHttpListener/Net/EndPointListener.cs deleted file mode 100644 index 14461865ef..0000000000 --- a/SocketHttpListener/Net/EndPointListener.cs +++ /dev/null @@ -1,433 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -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.Logging; -using MediaBrowser.Model.Net; -using MediaBrowser.Model.System; -using MediaBrowser.Model.Text; -using SocketHttpListener.Primitives; -using ProtocolType = MediaBrowser.Model.Net.ProtocolType; -using SocketType = MediaBrowser.Model.Net.SocketType; - -namespace SocketHttpListener.Net -{ - sealed class EndPointListener - { - HttpListener listener; - IPEndPoint endpoint; - Socket sock; - Dictionary prefixes; // Dictionary - List unhandled; // List unhandled; host = '*' - List all; // List all; host = '+' - X509Certificate cert; - bool secure; - Dictionary unregistered; - private readonly ILogger _logger; - private bool _closed; - private bool _enableDualMode; - private readonly ICryptoProvider _cryptoProvider; - private readonly ISocketFactory _socketFactory; - private readonly ITextEncoding _textEncoding; - private readonly IMemoryStreamFactory _memoryStreamFactory; - private readonly IFileSystem _fileSystem; - private readonly IEnvironmentInfo _environment; - - public EndPointListener(HttpListener listener, IPAddress addr, int port, bool secure, X509Certificate cert, ILogger logger, ICryptoProvider cryptoProvider, ISocketFactory socketFactory, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem, IEnvironmentInfo environment) - { - this.listener = listener; - _logger = logger; - _cryptoProvider = cryptoProvider; - _socketFactory = socketFactory; - _memoryStreamFactory = memoryStreamFactory; - _textEncoding = textEncoding; - _fileSystem = fileSystem; - _environment = environment; - - this.secure = secure; - this.cert = cert; - - _enableDualMode = addr.Equals(IPAddress.IPv6Any); - endpoint = new IPEndPoint(addr, port); - - prefixes = new Dictionary(); - unregistered = new Dictionary(); - - CreateSocket(); - } - - internal HttpListener Listener - { - get - { - return listener; - } - } - - private void CreateSocket() - { - try - { - sock = CreateSocket(endpoint.Address.AddressFamily, _enableDualMode); - } - catch (SocketCreateException ex) - { - if (_enableDualMode && endpoint.Address.Equals(IPAddress.IPv6Any) && - (string.Equals(ex.ErrorCode, "AddressFamilyNotSupported", StringComparison.OrdinalIgnoreCase) || - // mono on bsd is throwing this - string.Equals(ex.ErrorCode, "ProtocolNotSupported", StringComparison.OrdinalIgnoreCase))) - { - endpoint = new IPEndPoint(IPAddress.Any, endpoint.Port); - _enableDualMode = false; - sock = CreateSocket(endpoint.Address.AddressFamily, _enableDualMode); - } - else - { - throw; - } - } - - try - { - sock.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); - } - catch (SocketException) - { - // This is not supported on all operating systems (qnap) - } - - sock.Bind(endpoint); - - // This is the number TcpListener uses. - sock.Listen(2147483647); - - new SocketAcceptor(_logger, sock, ProcessAccept, () => _closed).StartAccept(); - _closed = false; - } - - 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; - } - } - } - - private void ProcessAccept(Socket accepted) - { - try - { - var listener = this; - - if (listener.secure && listener.cert == null) - { - accepted.Close(); - return; - } - - HttpConnection conn = new HttpConnection(_logger, accepted, listener, secure, cert, _cryptoProvider, _memoryStreamFactory, _textEncoding, _fileSystem, _environment); - - //_logger.Debug("Adding unregistered connection to {0}. Id: {1}", accepted.RemoteEndPoint, connectionId); - lock (listener.unregistered) - { - listener.unregistered[conn] = conn; - } - conn.BeginReadRequest(); - } - catch (Exception ex) - { - _logger.ErrorException("Error in ProcessAccept", ex); - } - } - - internal void RemoveConnection(HttpConnection conn) - { - lock (unregistered) - { - unregistered.Remove(conn); - } - } - - public bool BindContext(HttpListenerContext context) - { - HttpListenerRequest req = context.Request; - ListenerPrefix prefix; - HttpListener listener = SearchListener(req.Url, out 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); - } - - 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 path_slash = path[path.Length - 1] == '/' ? path : path + "/"; - - HttpListener best_match = null; - int best_length = -1; - - if (host != null && host != "") - { - var p_ro = prefixes; - foreach (ListenerPrefix p in p_ro.Keys) - { - string ppath = p.Path; - if (ppath.Length < best_length) - continue; - - if (p.Host != host || p.Port != port) - continue; - - if (path.StartsWith(ppath) || path_slash.StartsWith(ppath)) - { - best_length = ppath.Length; - best_match = (HttpListener)p_ro[p]; - prefix = p; - } - } - if (best_length != -1) - return best_match; - } - - List list = unhandled; - best_match = MatchFromList(host, path, list, out prefix); - if (path != path_slash && best_match == null) - best_match = MatchFromList(host, path_slash, list, out prefix); - if (best_match != null) - return best_match; - - list = all; - best_match = MatchFromList(host, path, list, out prefix); - if (path != path_slash && best_match == null) - best_match = MatchFromList(host, path_slash, list, out prefix); - if (best_match != null) - return best_match; - - return null; - } - - HttpListener MatchFromList(string host, string path, List list, out ListenerPrefix prefix) - { - prefix = null; - if (list == null) - return null; - - HttpListener best_match = null; - int best_length = -1; - - foreach (ListenerPrefix p in list) - { - string ppath = p.Path; - if (ppath.Length < best_length) - continue; - - if (path.StartsWith(ppath)) - { - best_length = ppath.Length; - best_match = p.Listener; - prefix = p; - } - } - - return best_match; - } - - void AddSpecial(List coll, ListenerPrefix prefix) - { - if (coll == null) - return; - - foreach (ListenerPrefix p in coll) - { - if (p.Path == prefix.Path) //TODO: code - throw new HttpListenerException(400, "Prefix already in use."); - } - coll.Add(prefix); - } - - bool RemoveSpecial(List coll, ListenerPrefix prefix) - { - if (coll == null) - return false; - - int c = coll.Count; - for (int i = 0; i < c; i++) - { - ListenerPrefix p = (ListenerPrefix)coll[i]; - if (p.Path == prefix.Path) - { - coll.RemoveAt(i); - return true; - } - } - return false; - } - - void CheckIfRemove() - { - if (prefixes.Count > 0) - return; - - List list = unhandled; - if (list != null && list.Count > 0) - return; - - list = all; - if (list != null && list.Count > 0) - return; - - EndPointManager.RemoveEndPoint(this, endpoint); - } - - public void Close() - { - _closed = true; - sock.Close(); - lock (unregistered) - { - // - // Clone the list because RemoveConnection can be called from Close - // - var connections = new List(unregistered.Keys); - - foreach (HttpConnection c in connections) - c.Close(true); - unregistered.Clear(); - } - } - - public void AddPrefix(ListenerPrefix prefix, HttpListener listener) - { - List current; - List future; - if (prefix.Host == "*") - { - do - { - current = unhandled; - future = (current != null) ? current.ToList() : new List(); - prefix.Listener = listener; - AddSpecial(future, prefix); - } while (Interlocked.CompareExchange(ref unhandled, future, current) != current); - return; - } - - if (prefix.Host == "+") - { - do - { - current = all; - future = (current != null) ? current.ToList() : new List(); - prefix.Listener = listener; - AddSpecial(future, prefix); - } while (Interlocked.CompareExchange(ref all, future, current) != current); - return; - } - - Dictionary prefs; - Dictionary p2; - do - { - prefs = prefixes; - if (prefs.ContainsKey(prefix)) - { - HttpListener other = (HttpListener)prefs[prefix]; - if (other != listener) // TODO: code. - throw new HttpListenerException(400, "There's another listener for " + prefix); - return; - } - p2 = new Dictionary(prefs); - p2[prefix] = listener; - } while (Interlocked.CompareExchange(ref prefixes, p2, prefs) != prefs); - } - - public void RemovePrefix(ListenerPrefix prefix, HttpListener listener) - { - List current; - List future; - if (prefix.Host == "*") - { - do - { - current = unhandled; - future = (current != null) ? current.ToList() : new List(); - if (!RemoveSpecial(future, prefix)) - break; // Prefix not found - } while (Interlocked.CompareExchange(ref unhandled, future, current) != current); - CheckIfRemove(); - return; - } - - if (prefix.Host == "+") - { - do - { - current = all; - future = (current != null) ? current.ToList() : new List(); - if (!RemoveSpecial(future, prefix)) - break; // Prefix not found - } while (Interlocked.CompareExchange(ref all, future, current) != current); - CheckIfRemove(); - return; - } - - Dictionary prefs; - Dictionary p2; - do - { - prefs = prefixes; - if (!prefs.ContainsKey(prefix)) - break; - - p2 = new Dictionary(prefs); - p2.Remove(prefix); - } while (Interlocked.CompareExchange(ref prefixes, p2, prefs) != prefs); - CheckIfRemove(); - } - } -} diff --git a/SocketHttpListener/Net/EndPointManager.cs b/SocketHttpListener/Net/EndPointManager.cs deleted file mode 100644 index 557caa59a3..0000000000 --- a/SocketHttpListener/Net/EndPointManager.cs +++ /dev/null @@ -1,167 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Net; -using System.Reflection; -using System.Threading.Tasks; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Net; -using SocketHttpListener.Primitives; - -namespace SocketHttpListener.Net -{ - sealed class EndPointManager - { - // Dictionary> - static Dictionary> ip_to_endpoints = new Dictionary>(); - - private EndPointManager() - { - } - - public static void AddListener(ILogger logger, HttpListener listener) - { - List added = new List(); - try - { - lock (ip_to_endpoints) - { - foreach (string prefix in listener.Prefixes) - { - AddPrefixInternal(logger, prefix, listener); - added.Add(prefix); - } - } - } - catch - { - foreach (string prefix in added) - { - RemovePrefix(logger, prefix, listener); - } - throw; - } - } - - public static void AddPrefix(ILogger logger, string prefix, HttpListener listener) - { - lock (ip_to_endpoints) - { - AddPrefixInternal(logger, prefix, listener); - } - } - - static void AddPrefixInternal(ILogger logger, string p, HttpListener listener) - { - ListenerPrefix lp = new ListenerPrefix(p); - if (lp.Path.IndexOf('%') != -1) - throw new HttpListenerException(400, "Invalid path."); - - if (lp.Path.IndexOf("//", StringComparison.Ordinal) != -1) // TODO: Code? - throw new HttpListenerException(400, "Invalid path."); - - // listens on all the interfaces if host name cannot be parsed by IPAddress. - EndPointListener epl = GetEPListener(logger, lp.Host, lp.Port, listener, lp.Secure).Result; - epl.AddPrefix(lp, listener); - } - - private static IPAddress GetIpAnyAddress(HttpListener listener) - { - return listener.EnableDualMode ? IPAddress.IPv6Any : IPAddress.Any; - } - - static async Task GetEPListener(ILogger logger, string host, int port, HttpListener listener, bool secure) - { - var networkManager = listener.NetworkManager; - - IPAddress addr; - if (host == "*" || host == "+") - addr = GetIpAnyAddress(listener); - else if (IPAddress.TryParse(host, out addr) == false) - { - try - { - var all = (await networkManager.GetHostAddressesAsync(host).ConfigureAwait(false)); - - addr = (all.Length == 0 ? null : IPAddress.Parse(all[0].Address)) ?? - GetIpAnyAddress(listener); - } - catch - { - addr = GetIpAnyAddress(listener); - } - } - - Dictionary p = null; // Dictionary - if (!ip_to_endpoints.TryGetValue(addr.ToString(), out p)) - { - p = new Dictionary(); - ip_to_endpoints[addr.ToString()] = p; - } - - EndPointListener epl = null; - if (p.ContainsKey(port)) - { - epl = (EndPointListener)p[port]; - } - else - { - epl = new EndPointListener(listener, addr, port, secure, listener.Certificate, logger, listener.CryptoProvider, listener.SocketFactory, listener.MemoryStreamFactory, listener.TextEncoding, listener.FileSystem, listener.EnvironmentInfo); - p[port] = epl; - } - - return epl; - } - - public static void RemoveEndPoint(EndPointListener epl, IPEndPoint ep) - { - lock (ip_to_endpoints) - { - // Dictionary p - Dictionary p; - if (ip_to_endpoints.TryGetValue(ep.Address.ToString(), out p)) - { - p.Remove(ep.Port); - if (p.Count == 0) - { - ip_to_endpoints.Remove(ep.Address.ToString()); - } - } - epl.Close(); - } - } - - public static void RemoveListener(ILogger logger, HttpListener listener) - { - lock (ip_to_endpoints) - { - foreach (string prefix in listener.Prefixes) - { - RemovePrefixInternal(logger, prefix, listener); - } - } - } - - public static void RemovePrefix(ILogger logger, string prefix, HttpListener listener) - { - lock (ip_to_endpoints) - { - RemovePrefixInternal(logger, prefix, listener); - } - } - - static void RemovePrefixInternal(ILogger logger, string prefix, HttpListener listener) - { - ListenerPrefix lp = new ListenerPrefix(prefix); - if (lp.Path.IndexOf('%') != -1) - return; - - if (lp.Path.IndexOf("//", StringComparison.Ordinal) != -1) - return; - - EndPointListener epl = GetEPListener(logger, lp.Host, lp.Port, listener, lp.Secure).Result; - epl.RemovePrefix(lp, listener); - } - } -} diff --git a/SocketHttpListener/Net/HttpConnection.cs b/SocketHttpListener/Net/HttpConnection.cs index 494094bbfa..4b4c9bad4f 100644 --- a/SocketHttpListener/Net/HttpConnection.cs +++ b/SocketHttpListener/Net/HttpConnection.cs @@ -23,7 +23,7 @@ sealed class HttpConnection const int BufferSize = 8192; Socket _socket; Stream _stream; - EndPointListener _epl; + HttpEndPointListener _epl; MemoryStream _memoryStream; byte[] _buffer; HttpListenerContext _context; @@ -48,7 +48,7 @@ sealed class HttpConnection private readonly IFileSystem _fileSystem; private readonly IEnvironmentInfo _environment; - public HttpConnection(ILogger logger, Socket socket, EndPointListener epl, bool secure, X509Certificate cert, ICryptoProvider cryptoProvider, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem, IEnvironmentInfo environment) + public HttpConnection(ILogger logger, Socket socket, HttpEndPointListener epl, bool secure, X509Certificate cert, ICryptoProvider cryptoProvider, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem, IEnvironmentInfo environment) { _logger = logger; this._socket = socket; @@ -86,7 +86,10 @@ public HttpConnection(ILogger logger, Socket socket, EndPointListener epl, bool }); _stream = ssl_stream; + } + if (ssl_stream != null) + { ssl_stream.AuthenticateAsServer(cert, false, (SslProtocols)ServicePointManager.SecurityProtocol, false); } Init(); diff --git a/SocketHttpListener/Net/HttpEndPointListener.cs b/SocketHttpListener/Net/HttpEndPointListener.cs new file mode 100644 index 0000000000..f40cafab82 --- /dev/null +++ b/SocketHttpListener/Net/HttpEndPointListener.cs @@ -0,0 +1,527 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +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.Logging; +using MediaBrowser.Model.Net; +using MediaBrowser.Model.System; +using MediaBrowser.Model.Text; +using SocketHttpListener.Primitives; +using ProtocolType = MediaBrowser.Model.Net.ProtocolType; +using SocketType = MediaBrowser.Model.Net.SocketType; + +namespace SocketHttpListener.Net +{ + internal sealed class HttpEndPointListener + { + private HttpListener _listener; + private IPEndPoint _endpoint; + private Socket _socket; + private Dictionary _prefixes; + private List _unhandledPrefixes; // host = '*' + private List _allPrefixes; // host = '+' + private X509Certificate _cert; + private bool _secure; + private Dictionary _unregisteredConnections; + + private readonly ILogger _logger; + private bool _closed; + private bool _enableDualMode; + private readonly ICryptoProvider _cryptoProvider; + private readonly ISocketFactory _socketFactory; + private readonly ITextEncoding _textEncoding; + private readonly IMemoryStreamFactory _memoryStreamFactory; + 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, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem, IEnvironmentInfo environment) + { + this._listener = listener; + _logger = logger; + _cryptoProvider = cryptoProvider; + _socketFactory = socketFactory; + _memoryStreamFactory = memoryStreamFactory; + _textEncoding = textEncoding; + _fileSystem = fileSystem; + _environment = environment; + + this._secure = secure; + this._cert = cert; + + _enableDualMode = addr.Equals(IPAddress.IPv6Any); + _endpoint = new IPEndPoint(addr, port); + + _prefixes = new Dictionary(); + _unregisteredConnections = new Dictionary(); + + CreateSocket(); + } + + internal HttpListener Listener + { + get + { + return _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 on bsd is throwing this + string.Equals(ex.ErrorCode, "ProtocolNotSupported", 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(_socket, this); + + _closed = false; + } + + private static void Accept(Socket socket, HttpEndPointListener epl) + { + var acceptEventArg = new SocketAsyncEventArgs(); + acceptEventArg.UserToken = epl; + acceptEventArg.Completed += new EventHandler(OnAccept); + + Socket dummy = null; + Accept(socket, acceptEventArg, ref dummy); + } + + private static void Accept(Socket socket, SocketAsyncEventArgs acceptEventArg, ref Socket accepted) + { + // 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 + { + if (accepted != null) + { + try + { +#if NET46 + accepted.Close(); +#else + accepted.Dispose(); +#endif + } + catch + { + } + accepted = null; + } + } + } + + // 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 void ProcessAccept(SocketAsyncEventArgs args) + { + HttpEndPointListener 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 + if (args.SocketError == SocketError.ConnectionReset) + { + epl._logger.Error("SocketError.ConnectionReset reported. Attempting to re-accept."); + Accept(epl._socket, epl); + return; + } + + var acceptSocket = args.AcceptSocket; + if (acceptSocket != null) + { + epl.ProcessAccept(acceptSocket); + } + + // Accept the next connection request + Accept(epl._socket, args, ref acceptSocket); + } + + 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; + } + } + } + + private void TryClose(Socket accepted) + { + try + { + accepted.Close(); + } + catch + { + + } + } + + private void ProcessAccept(Socket accepted) + { + try + { + var listener = this; + + if (listener._secure && listener._cert == null) + { + accepted.Close(); + return; + } + + HttpConnection conn = new HttpConnection(_logger, accepted, listener, _secure, _cert, _cryptoProvider, _memoryStreamFactory, _textEncoding, _fileSystem, _environment); + + //_logger.Debug("Adding unregistered connection to {0}. Id: {1}", accepted.RemoteEndPoint, connectionId); + lock (listener._unregisteredConnections) + { + listener._unregisteredConnections[conn] = conn; + } + conn.BeginReadRequest(); + } + catch (Exception ex) + { + TryClose(accepted); + _logger.ErrorException("Error in ProcessAccept", ex); + } + } + + internal void RemoveConnection(HttpConnection conn) + { + lock (_unregisteredConnections) + { + _unregisteredConnections.Remove(conn); + } + } + + public bool BindContext(HttpListenerContext context) + { + HttpListenerRequest req = context.Request; + ListenerPrefix prefix; + HttpListener listener = SearchListener(req.Url, out 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 localPrefixes = _prefixes; + foreach (ListenerPrefix 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 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 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 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 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 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(_unregisteredConnections.Keys); + + foreach (HttpConnection c in connections) + c.Close(true); + _unregisteredConnections.Clear(); + } + } + + public void AddPrefix(ListenerPrefix prefix, HttpListener listener) + { + List current; + List future; + if (prefix.Host == "*") + { + do + { + current = _unhandledPrefixes; + future = current != null ? new List(current) : new List(); + 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(current) : new List(); + prefix._listener = listener; + AddSpecial(future, prefix); + } while (Interlocked.CompareExchange(ref _allPrefixes, future, current) != current); + return; + } + + Dictionary prefs, p2; + do + { + prefs = _prefixes; + if (prefs.ContainsKey(prefix)) + { + throw new Exception("net_listener_already"); + } + p2 = new Dictionary(prefs); + p2[prefix] = listener; + } while (Interlocked.CompareExchange(ref _prefixes, p2, prefs) != prefs); + } + + public void RemovePrefix(ListenerPrefix prefix, HttpListener listener) + { + List current; + List future; + if (prefix.Host == "*") + { + do + { + current = _unhandledPrefixes; + future = current != null ? new List(current) : new List(); + 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(current) : new List(); + if (!RemoveSpecial(future, prefix)) + break; // Prefix not found + } while (Interlocked.CompareExchange(ref _allPrefixes, future, current) != current); + CheckIfRemove(); + return; + } + + Dictionary prefs, p2; + do + { + prefs = _prefixes; + if (!prefs.ContainsKey(prefix)) + break; + + p2 = new Dictionary(prefs); + p2.Remove(prefix); + } while (Interlocked.CompareExchange(ref _prefixes, p2, prefs) != prefs); + CheckIfRemove(); + } + } +} diff --git a/SocketHttpListener/Net/HttpEndPointManager.cs b/SocketHttpListener/Net/HttpEndPointManager.cs new file mode 100644 index 0000000000..67910a8834 --- /dev/null +++ b/SocketHttpListener/Net/HttpEndPointManager.cs @@ -0,0 +1,193 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Reflection; +using System.Threading.Tasks; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Net; +using SocketHttpListener.Primitives; + +namespace SocketHttpListener.Net +{ + internal sealed class HttpEndPointManager + { + private static Dictionary> s_ipEndPoints = new Dictionary>(); + + private HttpEndPointManager() + { + } + + public static void AddListener(ILogger logger, HttpListener listener) + { + List added = new List(); + try + { + lock ((s_ipEndPoints as ICollection).SyncRoot) + { + foreach (string prefix in listener.Prefixes) + { + AddPrefixInternal(logger, prefix, listener); + added.Add(prefix); + } + } + } + catch + { + foreach (string prefix in added) + { + RemovePrefix(logger, prefix, listener); + } + throw; + } + } + + public static void AddPrefix(ILogger logger, string prefix, HttpListener listener) + { + lock ((s_ipEndPoints as ICollection).SyncRoot) + { + AddPrefixInternal(logger, prefix, listener); + } + } + + private static void AddPrefixInternal(ILogger logger, string p, HttpListener listener) + { + int start = p.IndexOf(':') + 3; + int colon = p.IndexOf(':', start); + if (colon != -1) + { + // root can't be -1 here, since we've already checked for ending '/' in ListenerPrefix. + int root = p.IndexOf('/', colon, p.Length - colon); + string portString = p.Substring(colon + 1, root - colon - 1); + + int port; + if (!int.TryParse(portString, out port) || port <= 0 || port >= 65536) + { + throw new HttpListenerException((int)HttpStatusCode.BadRequest, "net_invalid_port"); + } + } + + ListenerPrefix lp = new ListenerPrefix(p); + if (lp.Host != "*" && lp.Host != "+" && Uri.CheckHostName(lp.Host) == UriHostNameType.Unknown) + throw new HttpListenerException((int)HttpStatusCode.BadRequest, "net_listener_host"); + + if (lp.Path.IndexOf('%') != -1) + throw new HttpListenerException((int)HttpStatusCode.BadRequest, "net_invalid_path"); + + if (lp.Path.IndexOf("//", StringComparison.Ordinal) != -1) + throw new HttpListenerException((int)HttpStatusCode.BadRequest, "net_invalid_path"); + + // listens on all the interfaces if host name cannot be parsed by IPAddress. + HttpEndPointListener epl = GetEPListener(logger, lp.Host, lp.Port, listener, lp.Secure); + epl.AddPrefix(lp, listener); + } + + private static HttpEndPointListener GetEPListener(ILogger logger, string host, int port, HttpListener listener, bool secure) + { + IPAddress addr; + if (host == "*" || host == "+") + { + addr = IPAddress.Any; + } + else + { + const int NotSupportedErrorCode = 50; + try + { + addr = Dns.GetHostAddresses(host)[0]; + } + catch + { + // Throw same error code as windows, request is not supported. + throw new HttpListenerException(NotSupportedErrorCode, "net_listener_not_supported"); + } + + if (IPAddress.Any.Equals(addr)) + { + // Don't support listening to 0.0.0.0, match windows behavior. + throw new HttpListenerException(NotSupportedErrorCode, "net_listener_not_supported"); + } + } + + Dictionary p = null; + if (s_ipEndPoints.ContainsKey(addr)) + { + p = s_ipEndPoints[addr]; + } + else + { + p = new Dictionary(); + s_ipEndPoints[addr] = p; + } + + HttpEndPointListener epl = null; + if (p.ContainsKey(port)) + { + epl = p[port]; + } + else + { + try + { + epl = new HttpEndPointListener(listener, addr, port, secure, listener.Certificate, logger, listener.CryptoProvider, listener.SocketFactory, listener.MemoryStreamFactory, listener.TextEncoding, listener.FileSystem, listener.EnvironmentInfo); + } + catch (SocketException ex) + { + throw new HttpListenerException(ex.ErrorCode, ex.Message); + } + p[port] = epl; + } + + return epl; + } + + public static void RemoveEndPoint(HttpEndPointListener epl, IPEndPoint ep) + { + lock ((s_ipEndPoints as ICollection).SyncRoot) + { + Dictionary p = null; + p = s_ipEndPoints[ep.Address]; + p.Remove(ep.Port); + if (p.Count == 0) + { + s_ipEndPoints.Remove(ep.Address); + } + epl.Close(); + } + } + + public static void RemoveListener(ILogger logger, HttpListener listener) + { + lock ((s_ipEndPoints as ICollection).SyncRoot) + { + foreach (string prefix in listener.Prefixes) + { + RemovePrefixInternal(logger, prefix, listener); + } + } + } + + public static void RemovePrefix(ILogger logger, string prefix, HttpListener listener) + { + lock ((s_ipEndPoints as ICollection).SyncRoot) + { + RemovePrefixInternal(logger, prefix, listener); + } + } + + private static void RemovePrefixInternal(ILogger logger, string prefix, HttpListener listener) + { + ListenerPrefix lp = new ListenerPrefix(prefix); + if (lp.Path.IndexOf('%') != -1) + return; + + if (lp.Path.IndexOf("//", StringComparison.Ordinal) != -1) + return; + + HttpEndPointListener epl = GetEPListener(logger, lp.Host, lp.Port, listener, lp.Secure); + epl.RemovePrefix(lp, listener); + } + } +} diff --git a/SocketHttpListener/Net/HttpListener.cs b/SocketHttpListener/Net/HttpListener.cs index 32c5e90e09..fafcd1bef8 100644 --- a/SocketHttpListener/Net/HttpListener.cs +++ b/SocketHttpListener/Net/HttpListener.cs @@ -185,7 +185,7 @@ public void Close() void Close(bool force) { CheckDisposed(); - EndPointManager.RemoveListener(_logger, this); + HttpEndPointManager.RemoveListener(_logger, this); Cleanup(force); } @@ -230,7 +230,7 @@ public void Start() if (listening) return; - EndPointManager.AddListener(_logger, this); + HttpEndPointManager.AddListener(_logger, this); listening = true; } diff --git a/SocketHttpListener/Net/HttpListenerPrefixCollection.cs b/SocketHttpListener/Net/HttpListenerPrefixCollection.cs index 0b05539eea..53efcb0fad 100644 --- a/SocketHttpListener/Net/HttpListenerPrefixCollection.cs +++ b/SocketHttpListener/Net/HttpListenerPrefixCollection.cs @@ -36,13 +36,13 @@ public bool IsSynchronized public void Add(string uriPrefix) { listener.CheckDisposed(); - ListenerPrefix.CheckUri(uriPrefix); + //ListenerPrefix.CheckUri(uriPrefix); if (prefixes.Contains(uriPrefix)) return; prefixes.Add(uriPrefix); if (listener.IsListening) - EndPointManager.AddPrefix(_logger, uriPrefix, listener); + HttpEndPointManager.AddPrefix(_logger, uriPrefix, listener); } public void Clear() @@ -50,7 +50,7 @@ public void Clear() listener.CheckDisposed(); prefixes.Clear(); if (listener.IsListening) - EndPointManager.RemoveListener(_logger, listener); + HttpEndPointManager.RemoveListener(_logger, listener); } public bool Contains(string uriPrefix) @@ -89,7 +89,7 @@ public bool Remove(string uriPrefix) bool result = prefixes.Remove(uriPrefix); if (result && listener.IsListening) - EndPointManager.RemovePrefix(_logger, uriPrefix, listener); + HttpEndPointManager.RemovePrefix(_logger, uriPrefix, listener); return result; } diff --git a/SocketHttpListener/Net/HttpListenerRequest.cs b/SocketHttpListener/Net/HttpListenerRequest.cs index c0a94e289f..dbaece22fd 100644 --- a/SocketHttpListener/Net/HttpListenerRequest.cs +++ b/SocketHttpListener/Net/HttpListenerRequest.cs @@ -407,19 +407,13 @@ internal bool FlushInput() byte[] bytes = new byte[length]; while (true) { - // TODO: test if MS has a timeout when doing this try { - var task = InputStream.ReadAsync(bytes, 0, length); - var result = Task.WaitAll(new[] { task }, 1000); - if (!result) - { + IAsyncResult ares = InputStream.BeginRead(bytes, 0, length, null, null); + if (!ares.IsCompleted && !ares.AsyncWaitHandle.WaitOne(1000)) return false; - } - if (task.Result <= 0) - { + if (InputStream.EndRead(ares) <= 0) return true; - } } catch (ObjectDisposedException) { diff --git a/SocketHttpListener/Net/ListenerPrefix.cs b/SocketHttpListener/Net/ListenerPrefix.cs index 605b7b88cf..99bb118e52 100644 --- a/SocketHttpListener/Net/ListenerPrefix.cs +++ b/SocketHttpListener/Net/ListenerPrefix.cs @@ -4,50 +4,50 @@ namespace SocketHttpListener.Net { - sealed class ListenerPrefix + internal sealed class ListenerPrefix { - string original; - string host; - ushort port; - string path; - bool secure; - IPAddress[] addresses; - public HttpListener Listener; + private string _original; + private string _host; + private ushort _port; + private string _path; + private bool _secure; + private IPAddress[] _addresses; + internal HttpListener _listener; public ListenerPrefix(string prefix) { - this.original = prefix; + _original = prefix; Parse(prefix); } public override string ToString() { - return original; + return _original; } public IPAddress[] Addresses { - get { return addresses; } - set { addresses = value; } + get { return _addresses; } + set { _addresses = value; } } public bool Secure { - get { return secure; } + get { return _secure; } } public string Host { - get { return host; } + get { return _host; } } public int Port { - get { return (int)port; } + get { return _port; } } public string Path { - get { return path; } + get { return _path; } } // Equals and GetHashCode are required to detect duplicates in HttpListenerPrefixCollection. @@ -57,92 +57,46 @@ public override bool Equals(object o) if (other == null) return false; - return (original == other.original); + return (_original == other._original); } public override int GetHashCode() { - return original.GetHashCode(); + return _original.GetHashCode(); } - void Parse(string uri) + private void Parse(string uri) { ushort default_port = 80; if (uri.StartsWith("https://")) { default_port = 443; - secure = true; + _secure = true; } int length = uri.Length; int start_host = uri.IndexOf(':') + 3; if (start_host >= length) - throw new ArgumentException("No host specified."); + throw new ArgumentException("net_listener_host"); int colon = uri.IndexOf(':', start_host, length - start_host); int root; if (colon > 0) { - host = uri.Substring(start_host, colon - start_host); + _host = uri.Substring(start_host, colon - start_host); root = uri.IndexOf('/', colon, length - colon); - port = (ushort)Int32.Parse(uri.Substring(colon + 1, root - colon - 1)); - path = uri.Substring(root); + _port = (ushort)int.Parse(uri.Substring(colon + 1, root - colon - 1)); + _path = uri.Substring(root); } else { root = uri.IndexOf('/', start_host, length - start_host); - host = uri.Substring(start_host, root - start_host); - port = default_port; - path = uri.Substring(root); + _host = uri.Substring(start_host, root - start_host); + _port = default_port; + _path = uri.Substring(root); } - if (path.Length != 1) - path = path.Substring(0, path.Length - 1); - } - - public static void CheckUri(string uri) - { - if (uri == null) - throw new ArgumentNullException("uriPrefix"); - - if (!uri.StartsWith("http://") && !uri.StartsWith("https://")) - throw new ArgumentException("Only 'http' and 'https' schemes are supported."); - - int length = uri.Length; - int start_host = uri.IndexOf(':') + 3; - if (start_host >= length) - throw new ArgumentException("No host specified."); - - int colon = uri.IndexOf(':', start_host, length - start_host); - if (start_host == colon) - throw new ArgumentException("No host specified."); - - int root; - if (colon > 0) - { - root = uri.IndexOf('/', colon, length - colon); - if (root == -1) - throw new ArgumentException("No path specified."); - - try - { - int p = Int32.Parse(uri.Substring(colon + 1, root - colon - 1)); - if (p <= 0 || p >= 65536) - throw new Exception(); - } - catch - { - throw new ArgumentException("Invalid port."); - } - } - else - { - root = uri.IndexOf('/', start_host, length - start_host); - if (root == -1) - throw new ArgumentException("No path specified."); - } - - if (uri[uri.Length - 1] != '/') - throw new ArgumentException("The prefix must end with '/'"); + if (_path.Length != 1) + _path = _path.Substring(0, _path.Length - 1); } } } diff --git a/SocketHttpListener/SocketHttpListener.csproj b/SocketHttpListener/SocketHttpListener.csproj index 8e9e01dc89..224e5a9a34 100644 --- a/SocketHttpListener/SocketHttpListener.csproj +++ b/SocketHttpListener/SocketHttpListener.csproj @@ -62,10 +62,10 @@ - - + + diff --git a/ThirdParty/emby/Emby.Server.CinemaMode.dll b/ThirdParty/emby/Emby.Server.CinemaMode.dll index 6d69b34bd1..cf3a0f1402 100644 Binary files a/ThirdParty/emby/Emby.Server.CinemaMode.dll and b/ThirdParty/emby/Emby.Server.CinemaMode.dll differ diff --git a/ThirdParty/emby/Emby.Server.Connect.dll b/ThirdParty/emby/Emby.Server.Connect.dll index d8992201ab..ec30855af0 100644 Binary files a/ThirdParty/emby/Emby.Server.Connect.dll and b/ThirdParty/emby/Emby.Server.Connect.dll differ diff --git a/ThirdParty/emby/Emby.Server.MediaEncoding.dll b/ThirdParty/emby/Emby.Server.MediaEncoding.dll index ff930f64df..361f8f679b 100644 Binary files a/ThirdParty/emby/Emby.Server.MediaEncoding.dll and b/ThirdParty/emby/Emby.Server.MediaEncoding.dll differ diff --git a/ThirdParty/emby/Emby.Server.Sync.dll b/ThirdParty/emby/Emby.Server.Sync.dll index 93a2d74d5b..a9b845424c 100644 Binary files a/ThirdParty/emby/Emby.Server.Sync.dll and b/ThirdParty/emby/Emby.Server.Sync.dll differ