diff --git a/BDInfo/BDROM.cs b/BDInfo/BDROM.cs index 2a23645b16..97dbfbf3bd 100644 --- a/BDInfo/BDROM.cs +++ b/BDInfo/BDROM.cs @@ -22,7 +22,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using MediaBrowser.Model.IO; -using MediaBrowser.Model.TextEncoding; +using MediaBrowser.Model.Text; namespace BDInfo { @@ -75,7 +75,7 @@ namespace BDInfo public event OnPlaylistFileScanError PlaylistFileScanError; public BDROM( - string path, IFileSystem fileSystem, IEncoding textEncoding) + string path, IFileSystem fileSystem, ITextEncoding textEncoding) { _fileSystem = fileSystem; // diff --git a/BDInfo/TSPlaylistFile.cs b/BDInfo/TSPlaylistFile.cs index 2826b3c803..46d66f513e 100644 --- a/BDInfo/TSPlaylistFile.cs +++ b/BDInfo/TSPlaylistFile.cs @@ -23,14 +23,14 @@ using System.Collections.Generic; using System.IO; using System.Text; using MediaBrowser.Model.IO; -using MediaBrowser.Model.TextEncoding; +using MediaBrowser.Model.Text; namespace BDInfo { public class TSPlaylistFile { private readonly IFileSystem _fileSystem; - private readonly IEncoding _textEncoding; + private readonly ITextEncoding _textEncoding; private FileSystemMetadata FileInfo = null; public string FileType = null; public bool IsInitialized = false; @@ -67,7 +67,7 @@ namespace BDInfo public TSPlaylistFile( BDROM bdrom, - FileSystemMetadata fileInfo, IFileSystem fileSystem, IEncoding textEncoding) + FileSystemMetadata fileInfo, IFileSystem fileSystem, ITextEncoding textEncoding) { BDROM = bdrom; FileInfo = fileInfo; @@ -79,7 +79,7 @@ namespace BDInfo public TSPlaylistFile( BDROM bdrom, string name, - List clips, IFileSystem fileSystem, IEncoding textEncoding) + List clips, IFileSystem fileSystem, ITextEncoding textEncoding) { BDROM = bdrom; Name = name; @@ -1247,7 +1247,7 @@ namespace BDInfo ref int pos) { string val = - _textEncoding.GetASCIIString(data, pos, count); + _textEncoding.GetASCIIEncoding().GetString(data, pos, count); pos += count; diff --git a/BDInfo/TSStreamClipFile.cs b/BDInfo/TSStreamClipFile.cs index 118e3b5fce..f2accb88d8 100644 --- a/BDInfo/TSStreamClipFile.cs +++ b/BDInfo/TSStreamClipFile.cs @@ -23,14 +23,14 @@ using System.Collections.Generic; using System.IO; using System.Text; using MediaBrowser.Model.IO; -using MediaBrowser.Model.TextEncoding; +using MediaBrowser.Model.Text; namespace BDInfo { public class TSStreamClipFile { private readonly IFileSystem _fileSystem; - private readonly IEncoding _textEncoding; + private readonly ITextEncoding _textEncoding; public FileSystemMetadata FileInfo = null; public string FileType = null; public bool IsValid = false; @@ -40,7 +40,7 @@ namespace BDInfo new Dictionary(); public TSStreamClipFile( - FileSystemMetadata fileInfo, IFileSystem fileSystem, IEncoding textEncoding) + FileSystemMetadata fileInfo, IFileSystem fileSystem, ITextEncoding textEncoding) { FileInfo = fileInfo; _fileSystem = fileSystem; @@ -70,7 +70,7 @@ namespace BDInfo byte[] fileType = new byte[8]; Array.Copy(data, 0, fileType, 0, fileType.Length); - FileType = _textEncoding.GetASCIIString(fileType, 0, fileType.Length); + FileType = _textEncoding.GetASCIIEncoding().GetString(fileType, 0, fileType.Length); if (FileType != "HDMV0100" && FileType != "HDMV0200") { @@ -167,7 +167,7 @@ namespace BDInfo Array.Copy(clipData, streamOffset + 3, languageBytes, 0, languageBytes.Length); string languageCode = - _textEncoding.GetASCIIString(languageBytes, 0, languageBytes.Length); + _textEncoding.GetASCIIEncoding().GetString(languageBytes, 0, languageBytes.Length); TSChannelLayout channelLayout = (TSChannelLayout) (clipData[streamOffset + 2] >> 4); @@ -198,7 +198,7 @@ namespace BDInfo Array.Copy(clipData, streamOffset + 2, languageBytes, 0, languageBytes.Length); string languageCode = - _textEncoding.GetASCIIString(languageBytes, 0, languageBytes.Length); + _textEncoding.GetASCIIEncoding().GetString(languageBytes, 0, languageBytes.Length); stream = new TSGraphicsStream(); stream.LanguageCode = languageCode; @@ -218,7 +218,7 @@ namespace BDInfo Array.Copy(clipData, streamOffset + 3, languageBytes, 0, languageBytes.Length); string languageCode = - _textEncoding.GetASCIIString(languageBytes, 0, languageBytes.Length); + _textEncoding.GetASCIIEncoding().GetString(languageBytes, 0, languageBytes.Length); #if DEBUG Debug.WriteLine(string.Format( "\t{0} {1} {2}", diff --git a/Emby.Common.Implementations/BaseApplicationHost.cs b/Emby.Common.Implementations/BaseApplicationHost.cs index 9585abb2a2..0cf11e8255 100644 --- a/Emby.Common.Implementations/BaseApplicationHost.cs +++ b/Emby.Common.Implementations/BaseApplicationHost.cs @@ -170,7 +170,7 @@ namespace Emby.Common.Implementations /// true if this instance is running as service; otherwise, false. public abstract bool IsRunningAsService { get; } - protected ICryptographyProvider CryptographyProvider = new CryptographyProvider(); + protected ICryptoProvider CryptographyProvider = new CryptographyProvider(); protected IEnvironmentInfo EnvironmentInfo = new Emby.Common.Implementations.EnvironmentInfo.EnvironmentInfo(); @@ -183,7 +183,7 @@ namespace Emby.Common.Implementations { _deviceId = new DeviceId(ApplicationPaths, LogManager.GetLogger("SystemId"), FileSystemManager); } - + return _deviceId.Value; } } @@ -193,7 +193,7 @@ namespace Emby.Common.Implementations get { return EnvironmentInfo.OperatingSystemName; } } - public IMemoryStreamProvider MemoryStreamProvider { get; set; } + public IMemoryStreamFactory MemoryStreamProvider { get; set; } /// /// The container @@ -209,7 +209,7 @@ namespace Emby.Common.Implementations { // hack alert, until common can target .net core BaseExtensions.CryptographyProvider = CryptographyProvider; - + XmlSerializer = new MyXmlSerializer(fileSystem, logManager.GetLogger("XmlSerializer")); FailedAssemblies = new List(); @@ -267,7 +267,7 @@ namespace Emby.Common.Implementations progress.Report(100); } - protected abstract IMemoryStreamProvider CreateMemoryStreamProvider(); + protected abstract IMemoryStreamFactory CreateMemoryStreamProvider(); protected abstract ISystemEvents CreateSystemEvents(); protected virtual void OnLoggerLoaded(bool isFirstLoad) diff --git a/Emby.Common.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Common.Implementations/Cryptography/CryptographyProvider.cs index 7b8d95b96c..01a31bcc03 100644 --- a/Emby.Common.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Common.Implementations/Cryptography/CryptographyProvider.cs @@ -6,21 +6,14 @@ using MediaBrowser.Model.Cryptography; namespace Emby.Common.Implementations.Cryptography { - public class CryptographyProvider : ICryptographyProvider + public class CryptographyProvider : ICryptoProvider { public Guid GetMD5(string str) { - return new Guid(GetMD5Bytes(str)); - } - public byte[] GetMD5Bytes(string str) - { - using (var provider = MD5.Create()) - { - return provider.ComputeHash(Encoding.Unicode.GetBytes(str)); - } + return new Guid(ComputeMD5(Encoding.Unicode.GetBytes(str))); } - public byte[] GetSHA1Bytes(byte[] bytes) + public byte[] ComputeSHA1(byte[] bytes) { using (var provider = SHA1.Create()) { @@ -28,7 +21,7 @@ namespace Emby.Common.Implementations.Cryptography } } - public byte[] GetMD5Bytes(Stream str) + public byte[] ComputeMD5(Stream str) { using (var provider = MD5.Create()) { @@ -36,7 +29,7 @@ namespace Emby.Common.Implementations.Cryptography } } - public byte[] GetMD5Bytes(byte[] bytes) + public byte[] ComputeMD5(byte[] bytes) { using (var provider = MD5.Create()) { diff --git a/Emby.Common.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Common.Implementations/HttpClientManager/HttpClientManager.cs index 85fcb556f0..06af5af536 100644 --- a/Emby.Common.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Common.Implementations/HttpClientManager/HttpClientManager.cs @@ -42,7 +42,7 @@ namespace Emby.Common.Implementations.HttpClientManager private readonly IApplicationPaths _appPaths; private readonly IFileSystem _fileSystem; - private readonly IMemoryStreamProvider _memoryStreamProvider; + private readonly IMemoryStreamFactory _memoryStreamProvider; /// /// Initializes a new instance of the class. @@ -53,7 +53,7 @@ namespace Emby.Common.Implementations.HttpClientManager /// appPaths /// or /// logger - public HttpClientManager(IApplicationPaths appPaths, ILogger logger, IFileSystem fileSystem, IMemoryStreamProvider memoryStreamProvider) + public HttpClientManager(IApplicationPaths appPaths, ILogger logger, IFileSystem fileSystem, IMemoryStreamFactory memoryStreamProvider) { if (appPaths == null) { diff --git a/Emby.Common.Implementations/Net/NetSocket.cs b/Emby.Common.Implementations/Net/NetSocket.cs new file mode 100644 index 0000000000..72faa41a9a --- /dev/null +++ b/Emby.Common.Implementations/Net/NetSocket.cs @@ -0,0 +1,85 @@ +using System; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using Emby.Common.Implementations.Networking; +using MediaBrowser.Model.Net; +using MediaBrowser.Model.Logging; + +namespace Emby.Common.Implementations.Net +{ + public class NetSocket : ISocket + { + public Socket Socket { get; private set; } + private readonly ILogger _logger; + + public NetSocket(Socket socket, ILogger logger) + { + Socket = socket; + _logger = logger; + } + + public IpEndPointInfo LocalEndPoint + { + get + { + return BaseNetworkManager.ToIpEndPointInfo((IPEndPoint)Socket.LocalEndPoint); + } + } + + public IpEndPointInfo RemoteEndPoint + { + get + { + return BaseNetworkManager.ToIpEndPointInfo((IPEndPoint)Socket.RemoteEndPoint); + } + } + + public void Close() + { +#if NET46 + Socket.Close(); +#else + Socket.Dispose(); +#endif + } + + public void Shutdown(bool both) + { + if (both) + { + Socket.Shutdown(SocketShutdown.Both); + } + else + { + // Change interface if ever needed + throw new NotImplementedException(); + } + } + + public void Listen(int backlog) + { + Socket.Listen(backlog); + } + + public void Bind(IpEndPointInfo endpoint) + { + var nativeEndpoint = BaseNetworkManager.ToIPEndPoint(endpoint); + + Socket.Bind(nativeEndpoint); + } + + private SocketAcceptor _acceptor; + public void StartAccept(Action onAccept, Func isClosed) + { + _acceptor = new SocketAcceptor(_logger, Socket, onAccept, isClosed); + + _acceptor.StartAccept(); + } + + public void Dispose() + { + Socket.Dispose(); + } + } +} diff --git a/Emby.Common.Implementations/Net/SocketAcceptor.cs b/Emby.Common.Implementations/Net/SocketAcceptor.cs new file mode 100644 index 0000000000..fd65e9fbc8 --- /dev/null +++ b/Emby.Common.Implementations/Net/SocketAcceptor.cs @@ -0,0 +1,111 @@ +using System; +using System.Net.Sockets; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Net; + +namespace Emby.Common.Implementations.Net +{ + public class SocketAcceptor + { + private readonly ILogger _logger; + private readonly Socket _originalSocket; + private readonly Func _isClosed; + private readonly Action _onAccept; + + public SocketAcceptor(ILogger logger, Socket originalSocket, Action onAccept, Func isClosed) + { + _logger = logger; + _originalSocket = originalSocket; + _isClosed = isClosed; + _onAccept = onAccept; + } + + public void StartAccept() + { + Socket dummy = null; + StartAccept(null, ref dummy); + } + + public void StartAccept(SocketAsyncEventArgs acceptEventArg, ref Socket accepted) + { + if (acceptEventArg == null) + { + acceptEventArg = new SocketAsyncEventArgs(); + acceptEventArg.Completed += new EventHandler(AcceptEventArg_Completed); + } + else + { + // socket must be cleared since the context object is being reused + acceptEventArg.AcceptSocket = null; + } + + try + { + bool willRaiseEvent = _originalSocket.AcceptAsync(acceptEventArg); + + if (!willRaiseEvent) + { + ProcessAccept(acceptEventArg); + } + } + catch (Exception ex) + { + 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 + // + void AcceptEventArg_Completed(object sender, SocketAsyncEventArgs e) + { + ProcessAccept(e); + } + + private void ProcessAccept(SocketAsyncEventArgs e) + { + if (_isClosed()) + { + return; + } + + // http://msdn.microsoft.com/en-us/library/system.net.sockets.socket.acceptasync%28v=vs.110%29.aspx + // Under certain conditions ConnectionReset can occur + // Need to attept to re-accept + if (e.SocketError == SocketError.ConnectionReset) + { + _logger.Error("SocketError.ConnectionReset reported. Attempting to re-accept."); + Socket dummy = null; + StartAccept(e, ref dummy); + return; + } + + var acceptSocket = e.AcceptSocket; + if (acceptSocket != null) + { + //ProcessAccept(acceptSocket); + _onAccept(new NetSocket(acceptSocket, _logger)); + } + + if (_originalSocket != null) + { + // Accept the next connection request + StartAccept(e, ref acceptSocket); + } + } + } +} diff --git a/Emby.Common.Implementations/Net/SocketFactory.cs b/Emby.Common.Implementations/Net/SocketFactory.cs index bb38c72da5..922b0f3cc5 100644 --- a/Emby.Common.Implementations/Net/SocketFactory.cs +++ b/Emby.Common.Implementations/Net/SocketFactory.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Net; using System.Net.Sockets; using System.Threading.Tasks; +using MediaBrowser.Model.Logging; using MediaBrowser.Model.Net; namespace Emby.Common.Implementations.Net @@ -22,16 +23,28 @@ namespace Emby.Common.Implementations.Net /// private IPAddress _LocalIP; - /// - /// Default constructor. - /// - /// A string containing the IP address of the local network adapter to bind sockets to. Null or empty string will use . - public SocketFactory(string localIP) + private ILogger _logger; + + public SocketFactory(ILogger logger) { - if (String.IsNullOrEmpty(localIP)) - _LocalIP = IPAddress.Any; - else - _LocalIP = IPAddress.Parse(localIP); + _logger = logger; + _LocalIP = IPAddress.Any; + } + + public ISocket CreateSocket(IpAddressFamily family, MediaBrowser.Model.Net.SocketType socketType, MediaBrowser.Model.Net.ProtocolType protocolType, bool dualMode) + { + var addressFamily = family == IpAddressFamily.InterNetwork + ? AddressFamily.InterNetwork + : AddressFamily.InterNetworkV6; + + var socket = new Socket(addressFamily, System.Net.Sockets.SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp); + + if (dualMode) + { + socket.DualMode = true; + } + + return new NetSocket(socket, _logger); } #region ISocketFactory Members @@ -44,7 +57,7 @@ namespace Emby.Common.Implementations.Net { if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort"); - var retVal = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + var retVal = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp); try { retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); @@ -68,7 +81,7 @@ namespace Emby.Common.Implementations.Net { if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort"); - var retVal = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + var retVal = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp); try { retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); @@ -99,7 +112,7 @@ namespace Emby.Common.Implementations.Net if (multicastTimeToLive <= 0) throw new ArgumentException("multicastTimeToLive cannot be zero or less.", "multicastTimeToLive"); if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort"); - var retVal = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + var retVal = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp); try { diff --git a/Emby.Common.Implementations/Net/UdpSocket.cs b/Emby.Common.Implementations/Net/UdpSocket.cs index d999d3fe87..244b37bb41 100644 --- a/Emby.Common.Implementations/Net/UdpSocket.cs +++ b/Emby.Common.Implementations/Net/UdpSocket.cs @@ -5,6 +5,7 @@ using System.Net; using System.Net.Sockets; using System.Security; using System.Threading.Tasks; +using Emby.Common.Implementations.Networking; using MediaBrowser.Model.Net; namespace Emby.Common.Implementations.Net @@ -174,16 +175,7 @@ namespace Emby.Common.Implementations.Net return null; } - return new IpEndPointInfo - { - IpAddress = new IpAddressInfo - { - Address = endpoint.Address.ToString(), - IsIpv6 = endpoint.AddressFamily == AddressFamily.InterNetworkV6 - }, - - Port = endpoint.Port - }; + return BaseNetworkManager.ToIpEndPointInfo(endpoint); } private void ProcessResponse(IAsyncResult asyncResult) diff --git a/Emby.Common.Implementations/Networking/BaseNetworkManager.cs b/Emby.Common.Implementations/Networking/BaseNetworkManager.cs index 10d0db9684..f1ac8413be 100644 --- a/Emby.Common.Implementations/Networking/BaseNetworkManager.cs +++ b/Emby.Common.Implementations/Networking/BaseNetworkManager.cs @@ -22,14 +22,10 @@ namespace Emby.Common.Implementations.Networking Logger = logger; } - private List _localIpAddresses; + private List _localIpAddresses; private readonly object _localIpAddressSyncLock = new object(); - /// - /// Gets the machine's local ip address - /// - /// IPAddress. - public IEnumerable GetLocalIpAddresses() + public IEnumerable GetLocalIpAddresses() { const int cacheMinutes = 5; @@ -39,7 +35,7 @@ namespace Emby.Common.Implementations.Networking if (_localIpAddresses == null || forceRefresh) { - var addresses = GetLocalIpAddressesInternal().ToList(); + var addresses = GetLocalIpAddressesInternal().Select(ToIpAddressInfo).ToList(); _localIpAddresses = addresses; _lastRefresh = DateTime.UtcNow; @@ -405,18 +401,85 @@ namespace Emby.Common.Implementations.Networking IPAddress address; if (IPAddress.TryParse(ipAddress, out address)) { - - ipAddressInfo = new IpAddressInfo - { - Address = address.ToString(), - IsIpv6 = address.AddressFamily == AddressFamily.InterNetworkV6 - }; - + ipAddressInfo = ToIpAddressInfo(address); return true; } ipAddressInfo = null; return false; } + + public static IpEndPointInfo ToIpEndPointInfo(IPEndPoint endpoint) + { + if (endpoint == null) + { + return null; + } + + return new IpEndPointInfo(ToIpAddressInfo(endpoint.Address), endpoint.Port); + } + + public static IPEndPoint ToIPEndPoint(IpEndPointInfo endpoint) + { + if (endpoint == null) + { + return null; + } + + return new IPEndPoint(ToIPAddress(endpoint.IpAddress), endpoint.Port); + } + + public static IPAddress ToIPAddress(IpAddressInfo address) + { + if (address.Equals(IpAddressInfo.Any)) + { + return IPAddress.Any; + } + if (address.Equals(IpAddressInfo.IPv6Any)) + { + return IPAddress.IPv6Any; + } + if (address.Equals(IpAddressInfo.Loopback)) + { + return IPAddress.Loopback; + } + if (address.Equals(IpAddressInfo.IPv6Loopback)) + { + return IPAddress.IPv6Loopback; + } + + return IPAddress.Parse(address.Address); + } + + public static IpAddressInfo ToIpAddressInfo(IPAddress address) + { + if (address.Equals(IPAddress.Any)) + { + return IpAddressInfo.Any; + } + if (address.Equals(IPAddress.IPv6Any)) + { + return IpAddressInfo.IPv6Any; + } + if (address.Equals(IPAddress.Loopback)) + { + return IpAddressInfo.Loopback; + } + if (address.Equals(IPAddress.IPv6Loopback)) + { + return IpAddressInfo.IPv6Loopback; + } + return new IpAddressInfo + { + Address = address.ToString(), + AddressFamily = address.AddressFamily == AddressFamily.InterNetworkV6 ? IpAddressFamily.InterNetworkV6 : IpAddressFamily.InterNetwork + }; + } + + public async Task GetHostAddressesAsync(string host) + { + var addresses = await Dns.GetHostAddressesAsync(host).ConfigureAwait(false); + return addresses.Select(ToIpAddressInfo).ToArray(); + } } } diff --git a/Emby.Common.Implementations/TextEncoding/TextEncoding.cs b/Emby.Common.Implementations/TextEncoding/TextEncoding.cs index 35b869e43b..254d352224 100644 --- a/Emby.Common.Implementations/TextEncoding/TextEncoding.cs +++ b/Emby.Common.Implementations/TextEncoding/TextEncoding.cs @@ -1,10 +1,10 @@ using System.Text; using MediaBrowser.Model.IO; -using MediaBrowser.Model.TextEncoding; +using MediaBrowser.Model.Text; namespace Emby.Common.Implementations.TextEncoding { - public class TextEncoding : IEncoding + public class TextEncoding : ITextEncoding { private readonly IFileSystem _fileSystem; @@ -13,14 +13,9 @@ namespace Emby.Common.Implementations.TextEncoding _fileSystem = fileSystem; } - public byte[] GetASCIIBytes(string text) + public Encoding GetASCIIEncoding() { - return Encoding.ASCII.GetBytes(text); - } - - public string GetASCIIString(byte[] bytes, int startIndex, int length) - { - return Encoding.ASCII.GetString(bytes, 0, bytes.Length); + return Encoding.ASCII; } public Encoding GetFileEncoding(string srcFile) diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index 142b9f96e7..ef27c029dc 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -250,14 +250,12 @@ namespace Emby.Dlna.Main // continue; //} - var addressString = address.ToString(); - var fullService = "urn:schemas-upnp-org:device:MediaServer:1"; - _logger.Info("Registering publisher for {0} on {1}", fullService, addressString); + _logger.Info("Registering publisher for {0} on {1}", fullService, address.ToString()); var descriptorUri = "/dlna/" + udn + "/description.xml"; - var uri = new Uri(_appHost.GetLocalApiUrl(addressString, address.IsIpv6) + descriptorUri); + var uri = new Uri(_appHost.GetLocalApiUrl(address) + descriptorUri); var device = new SsdpRootDevice { diff --git a/Emby.Dlna/PlayTo/PlayToManager.cs b/Emby.Dlna/PlayTo/PlayToManager.cs index b714f4ac01..a93f7da14e 100644 --- a/Emby.Dlna/PlayTo/PlayToManager.cs +++ b/Emby.Dlna/PlayTo/PlayToManager.cs @@ -16,6 +16,7 @@ using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Events; using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Net; using MediaBrowser.Model.Threading; namespace Emby.Dlna.PlayTo @@ -131,11 +132,11 @@ namespace Emby.Dlna.PlayTo string serverAddress; if (info.LocalIpAddress == null) { - serverAddress = await GetServerAddress(null, false).ConfigureAwait(false); + serverAddress = await GetServerAddress(null).ConfigureAwait(false); } else { - serverAddress = await GetServerAddress(info.LocalIpAddress.Address, info.LocalIpAddress.IsIpv6).ConfigureAwait(false); + serverAddress = await GetServerAddress(info.LocalIpAddress).ConfigureAwait(false); } string accessToken = null; @@ -189,14 +190,14 @@ namespace Emby.Dlna.PlayTo } } - private Task GetServerAddress(string ipAddress, bool isIpv6) + private Task GetServerAddress(IpAddressInfo address) { - if (string.IsNullOrWhiteSpace(ipAddress)) + if (address == null) { return _appHost.GetLocalApiUrl(); } - return Task.FromResult(_appHost.GetLocalApiUrl(ipAddress, isIpv6)); + return Task.FromResult(_appHost.GetLocalApiUrl(address)); } public void Dispose() diff --git a/Emby.Server.Implementations/Connect/ConnectEntryPoint.cs b/Emby.Server.Implementations/Connect/ConnectEntryPoint.cs index d7574d466c..170ef07f31 100644 --- a/Emby.Server.Implementations/Connect/ConnectEntryPoint.cs +++ b/Emby.Server.Implementations/Connect/ConnectEntryPoint.cs @@ -64,7 +64,7 @@ namespace Emby.Server.Implementations.Connect validIpAddress = await GetIpAddress(ipLookupUrl).ConfigureAwait(false); // Try to find the ipv4 address, if present - if (!validIpAddress.IsIpv6) + if (validIpAddress.AddressFamily != IpAddressFamily.InterNetworkV6) { break; } @@ -77,9 +77,9 @@ namespace Emby.Server.Implementations.Connect _logger.ErrorException("Error getting connection info", ex); } } - + // If this produced an ipv6 address, try again - if (validIpAddress != null && validIpAddress.IsIpv6) + if (validIpAddress != null && validIpAddress.AddressFamily == IpAddressFamily.InterNetworkV6) { foreach (var ipLookupUrl in _ipLookups) { @@ -88,7 +88,7 @@ namespace Emby.Server.Implementations.Connect var newAddress = await GetIpAddress(ipLookupUrl, true).ConfigureAwait(false); // Try to find the ipv4 address, if present - if (!newAddress.IsIpv6) + if (newAddress.AddressFamily != IpAddressFamily.InterNetworkV6) { validIpAddress = newAddress; break; diff --git a/Emby.Server.Implementations/Connect/ConnectManager.cs b/Emby.Server.Implementations/Connect/ConnectManager.cs index 6c2ac40c32..079bfe8685 100644 --- a/Emby.Server.Implementations/Connect/ConnectManager.cs +++ b/Emby.Server.Implementations/Connect/ConnectManager.cs @@ -74,7 +74,7 @@ namespace Emby.Server.Implementations.Connect if (string.IsNullOrWhiteSpace(address) && DiscoveredWanIpAddress != null) { - if (DiscoveredWanIpAddress.IsIpv6) + if (DiscoveredWanIpAddress.AddressFamily == IpAddressFamily.InterNetworkV6) { address = "[" + DiscoveredWanIpAddress + "]"; } diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 894ba334d9..806702dfd3 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -68,11 +68,18 @@ + + + + + + + @@ -254,6 +261,9 @@ {442b5058-dcaf-4263-bb6a-f21e31120a1b} MediaBrowser.Providers + + ..\ThirdParty\emby\SocketHttpListener.Portable.dll + ..\packages\UniversalDetector.1.0.1\lib\portable-net45+sl4+wp71+win8+wpa81\UniversalDetector.dll True diff --git a/MediaBrowser.Server.Implementations/HttpServer/LoggerUtils.cs b/Emby.Server.Implementations/HttpServer/LoggerUtils.cs similarity index 96% rename from MediaBrowser.Server.Implementations/HttpServer/LoggerUtils.cs rename to Emby.Server.Implementations/HttpServer/LoggerUtils.cs index bfbb228edf..8fc92a09a7 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/LoggerUtils.cs +++ b/Emby.Server.Implementations/HttpServer/LoggerUtils.cs @@ -3,7 +3,7 @@ using System; using System.Globalization; using SocketHttpListener.Net; -namespace MediaBrowser.Server.Implementations.HttpServer +namespace Emby.Server.Implementations.HttpServer { public static class LoggerUtils { diff --git a/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs similarity index 96% rename from MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs rename to Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs index 7d4cd3b4dc..e88994bec0 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs +++ b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs @@ -1,5 +1,4 @@ using MediaBrowser.Model.Logging; -using ServiceStack.Web; using System; using System.Collections.Generic; using System.Globalization; @@ -9,7 +8,7 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Services; -namespace MediaBrowser.Server.Implementations.HttpServer +namespace Emby.Server.Implementations.HttpServer { public class RangeRequestWriter : IAsyncStreamWriter, IHttpResult { @@ -41,7 +40,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer /// private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - public Func ResultScope { get; set; } public List Cookies { get; private set; } /// @@ -213,8 +211,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer public object Response { get; set; } - public IContentTypeWriter ResponseFilter { get; set; } - public int Status { get; set; } public HttpStatusCode StatusCode @@ -224,7 +220,5 @@ namespace MediaBrowser.Server.Implementations.HttpServer } public string StatusDescription { get; set; } - - public int PaddingLength { get; set; } } } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/HttpServer/ResponseFilter.cs b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs similarity index 80% rename from MediaBrowser.Server.Implementations/HttpServer/ResponseFilter.cs rename to Emby.Server.Implementations/HttpServer/ResponseFilter.cs index 6247e4c170..6d9d7d921d 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/ResponseFilter.cs +++ b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs @@ -1,12 +1,11 @@ using MediaBrowser.Model.Logging; -using MediaBrowser.Server.Implementations.HttpServer.SocketSharp; using System; using System.Globalization; -using System.Net; using System.Text; +using Emby.Server.Implementations.HttpServer.SocketSharp; using MediaBrowser.Model.Services; -namespace MediaBrowser.Server.Implementations.HttpServer +namespace Emby.Server.Implementations.HttpServer { public class ResponseFilter { @@ -28,6 +27,9 @@ namespace MediaBrowser.Server.Implementations.HttpServer { // Try to prevent compatibility view res.AddHeader("X-UA-Compatible", "IE=Edge"); + res.AddHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, Range, X-MediaBrowser-Token, X-Emby-Authorization"); + res.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS"); + res.AddHeader("Access-Control-Allow-Origin", "*"); var exception = dto as Exception; @@ -68,15 +70,15 @@ namespace MediaBrowser.Server.Implementations.HttpServer { res.SetContentLength(length); - var listenerResponse = res.OriginalResponse as HttpListenerResponse; + //var listenerResponse = res.OriginalResponse as HttpListenerResponse; - if (listenerResponse != null) - { - // Disable chunked encoding. Technically this is only needed when using Content-Range, but - // anytime we know the content length there's no need for it - listenerResponse.SendChunked = false; - return; - } + //if (listenerResponse != null) + //{ + // // Disable chunked encoding. Technically this is only needed when using Content-Range, but + // // anytime we know the content length there's no need for it + // listenerResponse.SendChunked = false; + // return; + //} if (sharpResponse != null) { diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/Extensions.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/Extensions.cs new file mode 100644 index 0000000000..07a338f198 --- /dev/null +++ b/Emby.Server.Implementations/HttpServer/SocketSharp/Extensions.cs @@ -0,0 +1,12 @@ +using SocketHttpListener.Net; + +namespace Emby.Server.Implementations.HttpServer.SocketSharp +{ + public static class Extensions + { + public static string GetOperationName(this HttpListenerRequest request) + { + return request.Url.Segments[request.Url.Segments.Length - 1]; + } + } +} diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/SharpWebSocket.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/SharpWebSocket.cs similarity index 93% rename from MediaBrowser.Server.Implementations/HttpServer/SocketSharp/SharpWebSocket.cs rename to Emby.Server.Implementations/HttpServer/SocketSharp/SharpWebSocket.cs index d363c4de6c..0a312f7b96 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/SharpWebSocket.cs +++ b/Emby.Server.Implementations/HttpServer/SocketSharp/SharpWebSocket.cs @@ -6,7 +6,7 @@ using System.Threading; using System.Threading.Tasks; using WebSocketState = MediaBrowser.Model.Net.WebSocketState; -namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp +namespace Emby.Server.Implementations.HttpServer.SocketSharp { public class SharpWebSocket : IWebSocket { @@ -25,12 +25,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); - /// - /// Initializes a new instance of the class. - /// - /// The socket. - /// The logger. - /// socket public SharpWebSocket(SocketHttpListener.WebSocket socket, ILogger logger) { if (socket == null) diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs similarity index 64% rename from MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs rename to Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs index 5619348543..db7b9bc4b4 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs @@ -1,37 +1,51 @@ -using System.Collections.Specialized; -using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Net; using MediaBrowser.Model.Logging; using SocketHttpListener.Net; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using Emby.Server.Implementations.HttpServer; using Emby.Server.Implementations.Logging; -using MediaBrowser.Common.IO; +using MediaBrowser.Common.Net; +using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.IO; +using MediaBrowser.Model.Net; using MediaBrowser.Model.Services; -using ServiceStack; +using MediaBrowser.Model.Text; +using SocketHttpListener.Primitives; -namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp +namespace Emby.Server.Implementations.HttpServer.SocketSharp { public class WebSocketSharpListener : IHttpListener { private HttpListener _listener; private readonly ILogger _logger; - private readonly string _certificatePath; - private readonly IMemoryStreamProvider _memoryStreamProvider; + private readonly ICertificate _certificate; + private readonly IMemoryStreamFactory _memoryStreamProvider; + private readonly ITextEncoding _textEncoding; + private readonly INetworkManager _networkManager; + private readonly ISocketFactory _socketFactory; + private readonly ICryptoProvider _cryptoProvider; + private readonly IStreamFactory _streamFactory; + private readonly Func _httpRequestFactory; + private readonly bool _enableDualMode; - public WebSocketSharpListener(ILogger logger, string certificatePath, IMemoryStreamProvider memoryStreamProvider) + public WebSocketSharpListener(ILogger logger, ICertificate certificate, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, INetworkManager networkManager, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, bool enableDualMode, Func httpRequestFactory) { _logger = logger; - _certificatePath = certificatePath; + _certificate = certificate; _memoryStreamProvider = memoryStreamProvider; + _textEncoding = textEncoding; + _networkManager = networkManager; + _socketFactory = socketFactory; + _cryptoProvider = cryptoProvider; + _streamFactory = streamFactory; + _enableDualMode = enableDualMode; + _httpRequestFactory = httpRequestFactory; } public Action ErrorHandler { get; set; } - public Func RequestHandler { get; set; } public Action WebSocketConnecting { get; set; } @@ -41,7 +55,14 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp public void Start(IEnumerable urlPrefixes) { if (_listener == null) - _listener = new HttpListener(new PatternsLogger(_logger), _certificatePath); + _listener = new HttpListener(new PatternsLogger(_logger), _cryptoProvider, _streamFactory, _socketFactory, _networkManager, _textEncoding, _memoryStreamProvider); + + _listener.EnableDualMode = _enableDualMode; + + if (_certificate != null) + { + _listener.LoadCert(_certificate); + } foreach (var prefix in urlPrefixes) { @@ -59,41 +80,32 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp Task.Factory.StartNew(() => InitTask(context)); } - private void InitTask(HttpListenerContext context) + private Task InitTask(HttpListenerContext context) { + IHttpRequest httpReq = null; + var request = context.Request; + try { - var task = this.ProcessRequestAsync(context); - task.ContinueWith(x => HandleError(x.Exception, context), TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.AttachedToParent); + if (request.IsWebSocketRequest) + { + LoggerUtils.LogRequest(_logger, request); - //if (task.Status == TaskStatus.Created) - //{ - // task.RunSynchronously(); - //} + ProcessWebSocketRequest(context); + return Task.FromResult(true); + } + + httpReq = GetRequest(context); } catch (Exception ex) { - HandleError(ex, context); - } - } + _logger.ErrorException("Error processing request", ex); - private Task ProcessRequestAsync(HttpListenerContext context) - { - var request = context.Request; - - if (request.IsWebSocketRequest) - { - LoggerUtils.LogRequest(_logger, request); - - ProcessWebSocketRequest(context); + httpReq = httpReq ?? GetRequest(context); + ErrorHandler(ex, httpReq); return Task.FromResult(true); } - if (string.IsNullOrEmpty(context.Request.RawUrl)) - return ((object)null).AsTaskResult(); - - var httpReq = GetRequest(context); - return RequestHandler(httpReq, request.Url); } @@ -103,19 +115,11 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp { var endpoint = ctx.Request.RemoteEndPoint.ToString(); var url = ctx.Request.RawUrl; - var queryString = ctx.Request.QueryString ?? new NameValueCollection(); - - var queryParamCollection = new QueryParamCollection(); - - foreach (var key in queryString.AllKeys) - { - queryParamCollection[key] = queryString[key]; - } var connectingArgs = new WebSocketConnectingEventArgs { Url = url, - QueryString = queryParamCollection, + QueryString = ctx.Request.QueryString, Endpoint = endpoint }; @@ -135,7 +139,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp WebSocketConnected(new WebSocketConnectEventArgs { Url = url, - QueryString = queryParamCollection, + QueryString = ctx.Request.QueryString, WebSocket = new SharpWebSocket(webSocketContext.WebSocket, _logger), Endpoint = endpoint }); @@ -158,22 +162,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp private IHttpRequest GetRequest(HttpListenerContext httpContext) { - var operationName = httpContext.Request.GetOperationName(); - - var req = new WebSocketSharpRequest(httpContext, operationName, RequestAttributes.None, _logger, _memoryStreamProvider); - req.RequestAttributes = req.GetAttributes(); - - return req; - } - - private void HandleError(Exception ex, HttpListenerContext context) - { - var httpReq = GetRequest(context); - - if (ErrorHandler != null) - { - ErrorHandler(ex, httpReq); - } + return _httpRequestFactory(httpContext); } public void Stop() @@ -214,4 +203,5 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp } } } + } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs similarity index 69% rename from MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs rename to Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs index 3aae6c9ca6..de0b33fe30 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs +++ b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs @@ -2,14 +2,13 @@ using System.Collections.Generic; using System.IO; using System.Net; +using System.Text; using MediaBrowser.Model.Logging; -using ServiceStack; -using ServiceStack.Host; using HttpListenerResponse = SocketHttpListener.Net.HttpListenerResponse; using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse; using IRequest = MediaBrowser.Model.Services.IRequest; -namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp +namespace Emby.Server.Implementations.HttpServer.SocketSharp { public class WebSocketSharpResponse : IHttpResponse { @@ -98,7 +97,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp try { - this._response.CloseOutputStream(_logger); + CloseOutputStream(this._response); } catch (Exception ex) { @@ -107,6 +106,20 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp } } + public void CloseOutputStream(HttpListenerResponse response) + { + try + { + response.OutputStream.Flush(); + response.OutputStream.Dispose(); + response.Close(); + } + catch (Exception ex) + { + _logger.ErrorException("Error in HttpListenerResponseWrapper: " + ex.Message, ex); + } + } + public void End() { Close(); @@ -133,10 +146,49 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp public void SetCookie(Cookie cookie) { - var cookieStr = cookie.AsHeaderValue(); - _response.Headers.Add(HttpHeaders.SetCookie, cookieStr); + var cookieStr = AsHeaderValue(cookie); + _response.Headers.Add("Set-Cookie", cookieStr); } + public static string AsHeaderValue(Cookie cookie) + { + var defaultExpires = DateTime.MinValue; + + var path = cookie.Expires == defaultExpires + ? "/" + : cookie.Path ?? "/"; + + var sb = new StringBuilder(); + + sb.Append($"{cookie.Name}={cookie.Value};path={path}"); + + if (cookie.Expires != defaultExpires) + { + sb.Append($";expires={cookie.Expires:R}"); + } + + if (!string.IsNullOrEmpty(cookie.Domain)) + { + sb.Append($";domain={cookie.Domain}"); + } + //else if (restrictAllCookiesToDomain != null) + //{ + // sb.Append($";domain={restrictAllCookiesToDomain}"); + //} + + if (cookie.Secure) + { + sb.Append(";Secure"); + } + if (cookie.HttpOnly) + { + sb.Append(";HttpOnly"); + } + + return sb.ToString(); + } + + public bool SendChunked { get { return _response.SendChunked; } diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 7ae00d94c2..8800777ca9 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -2496,10 +2496,12 @@ namespace Emby.Server.Implementations.Library }).OrderBy(i => i.Path).ToList(); } + private static readonly string[] ExtrasSubfolderNames = new[] { "extras", "specials", "shorts", "scenes", "featurettes", "behind the scenes", "deleted scenes" }; + public IEnumerable