diff --git a/Emby.Common.Implementations/Net/SocketFactory.cs b/Emby.Common.Implementations/Net/SocketFactory.cs index c65593242e..1242520971 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 Emby.Common.Implementations.Networking; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Net; @@ -18,11 +19,6 @@ namespace Emby.Common.Implementations.Net // but that wasn't really the point so kept to YAGNI principal for now, even if the // interfaces are a bit ugly, specific and make assumptions. - /// - /// Used by RSSDP components to create implementations of the interface, to perform platform agnostic socket communications. - /// - private IPAddress _LocalIP; - private readonly ILogger _logger; public SocketFactory(ILogger logger) @@ -33,7 +29,6 @@ namespace Emby.Common.Implementations.Net } _logger = logger; - _LocalIP = IPAddress.Any; } public ISocket CreateSocket(IpAddressFamily family, MediaBrowser.Model.Net.SocketType socketType, MediaBrowser.Model.Net.ProtocolType protocolType, bool dualMode) @@ -66,7 +61,7 @@ namespace Emby.Common.Implementations.Net try { retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); - return new UdpSocket(retVal, localPort, _LocalIP); + return new UdpSocket(retVal, localPort, IPAddress.Any); } catch { @@ -80,9 +75,8 @@ namespace Emby.Common.Implementations.Net /// /// Creates a new UDP socket that is a member of the SSDP multicast local admin group and binds it to the specified local port. /// - /// An integer specifying the local port to bind the socket to. /// An implementation of the interface used by RSSDP components to perform socket operations. - public IUdpSocket CreateSsdpUdpSocket(int localPort) + public IUdpSocket CreateSsdpUdpSocket(IpAddressInfo localIpAddress, int localPort) { if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort"); @@ -91,8 +85,11 @@ namespace Emby.Common.Implementations.Net { retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 4); - retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse("239.255.255.250"), _LocalIP)); - return new UdpSocket(retVal, localPort, _LocalIP); + + var localIp = NetworkManager.ToIPAddress(localIpAddress); + + retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse("239.255.255.250"), localIp)); + return new UdpSocket(retVal, localPort, localIp); } catch { @@ -134,10 +131,13 @@ namespace Emby.Common.Implementations.Net //retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true); retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, multicastTimeToLive); - retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse(ipAddress), _LocalIP)); + + var localIp = IPAddress.Any; + + retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse(ipAddress), localIp)); retVal.MulticastLoopback = true; - return new UdpSocket(retVal, localPort, _LocalIP); + return new UdpSocket(retVal, localPort, localIp); } catch { diff --git a/Emby.Common.Implementations/Net/UdpSocket.cs b/Emby.Common.Implementations/Net/UdpSocket.cs index 367d2242c6..b2af9d162e 100644 --- a/Emby.Common.Implementations/Net/UdpSocket.cs +++ b/Emby.Common.Implementations/Net/UdpSocket.cs @@ -20,7 +20,6 @@ namespace Emby.Common.Implementations.Net private Socket _Socket; private int _LocalPort; - #endregion #region Constructors @@ -31,12 +30,19 @@ namespace Emby.Common.Implementations.Net _Socket = socket; _LocalPort = localPort; + LocalIPAddress = NetworkManager.ToIpAddressInfo(ip); _Socket.Bind(new IPEndPoint(ip, _LocalPort)); } #endregion + public IpAddressInfo LocalIPAddress + { + get; + private set; + } + #region IUdpSocket Members public Task ReceiveAsync() @@ -50,18 +56,18 @@ namespace Emby.Common.Implementations.Net state.TaskCompletionSource = tcs; #if NETSTANDARD1_6 - _Socket.ReceiveFromAsync(new ArraySegment(state.Buffer),SocketFlags.None, state.EndPoint) + _Socket.ReceiveFromAsync(new ArraySegment(state.Buffer),SocketFlags.None, state.RemoteEndPoint) .ContinueWith((task, asyncState) => { if (task.Status != TaskStatus.Faulted) { var receiveState = asyncState as AsyncReceiveState; - receiveState.EndPoint = task.Result.RemoteEndPoint; - ProcessResponse(receiveState, () => task.Result.ReceivedBytes); + receiveState.RemoteEndPoint = task.Result.RemoteEndPoint; + ProcessResponse(receiveState, () => task.Result.ReceivedBytes, LocalIPAddress); } }, state); #else - _Socket.BeginReceiveFrom(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, ref state.EndPoint, ProcessResponse, state); + _Socket.BeginReceiveFrom(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, ref state.RemoteEndPoint, ProcessResponse, state); #endif return tcs.Task; @@ -74,6 +80,8 @@ namespace Emby.Common.Implementations.Net if (buffer == null) throw new ArgumentNullException("messageData"); if (endPoint == null) throw new ArgumentNullException("endPoint"); + var ipEndPoint = NetworkManager.ToIPEndPoint(endPoint); + #if NETSTANDARD1_6 if (size != buffer.Length) @@ -83,14 +91,14 @@ namespace Emby.Common.Implementations.Net buffer = copy; } - _Socket.SendTo(buffer, new IPEndPoint(IPAddress.Parse(endPoint.IpAddress.ToString()), endPoint.Port)); + _Socket.SendTo(buffer, ipEndPoint); return Task.FromResult(true); #else var taskSource = new TaskCompletionSource(); try { - _Socket.BeginSendTo(buffer, 0, size, SocketFlags.None, new System.Net.IPEndPoint(IPAddress.Parse(endPoint.IpAddress.ToString()), endPoint.Port), result => + _Socket.BeginSendTo(buffer, 0, size, SocketFlags.None, ipEndPoint, result => { try { @@ -109,7 +117,7 @@ namespace Emby.Common.Implementations.Net taskSource.TrySetException(ex); } - //_Socket.SendTo(messageData, new System.Net.IPEndPoint(IPAddress.Parse(endPoint.IPAddress), endPoint.Port)); + //_Socket.SendTo(messageData, new System.Net.IPEndPoint(IPAddress.Parse(RemoteEndPoint.IPAddress), RemoteEndPoint.Port)); return taskSource.Task; #endif @@ -133,19 +141,20 @@ namespace Emby.Common.Implementations.Net #region Private Methods - private static void ProcessResponse(AsyncReceiveState state, Func receiveData) + private static void ProcessResponse(AsyncReceiveState state, Func receiveData, IpAddressInfo localIpAddress) { try { var bytesRead = receiveData(); - var ipEndPoint = state.EndPoint as IPEndPoint; + var ipEndPoint = state.RemoteEndPoint as IPEndPoint; state.TaskCompletionSource.SetResult( - new SocketReceiveResult() + new SocketReceiveResult { Buffer = state.Buffer, ReceivedBytes = bytesRead, - RemoteEndPoint = ToIpEndPointInfo(ipEndPoint) + RemoteEndPoint = ToIpEndPointInfo(ipEndPoint), + LocalIPAddress = localIpAddress } ); } @@ -182,15 +191,16 @@ namespace Emby.Common.Implementations.Net var state = asyncResult.AsyncState as AsyncReceiveState; try { - var bytesRead = state.Socket.EndReceiveFrom(asyncResult, ref state.EndPoint); + var bytesRead = state.Socket.EndReceiveFrom(asyncResult, ref state.RemoteEndPoint); - var ipEndPoint = state.EndPoint as IPEndPoint; + var ipEndPoint = state.RemoteEndPoint as IPEndPoint; state.TaskCompletionSource.SetResult( new SocketReceiveResult { Buffer = state.Buffer, ReceivedBytes = bytesRead, - RemoteEndPoint = ToIpEndPointInfo(ipEndPoint) + RemoteEndPoint = ToIpEndPointInfo(ipEndPoint), + LocalIPAddress = LocalIPAddress } ); } @@ -211,13 +221,13 @@ namespace Emby.Common.Implementations.Net private class AsyncReceiveState { - public AsyncReceiveState(Socket socket, EndPoint endPoint) + public AsyncReceiveState(Socket socket, EndPoint remoteEndPoint) { this.Socket = socket; - this.EndPoint = endPoint; + this.RemoteEndPoint = remoteEndPoint; } - public EndPoint EndPoint; + public EndPoint RemoteEndPoint; public byte[] Buffer = new byte[8192]; public Socket Socket { get; private set; } diff --git a/Emby.Common.Implementations/Networking/NetworkManager.cs b/Emby.Common.Implementations/Networking/NetworkManager.cs index a4f8f7ced0..b9100f9db3 100644 --- a/Emby.Common.Implementations/Networking/NetworkManager.cs +++ b/Emby.Common.Implementations/Networking/NetworkManager.cs @@ -27,7 +27,7 @@ namespace Emby.Common.Implementations.Networking private List _localIpAddresses; private readonly object _localIpAddressSyncLock = new object(); - public IEnumerable GetLocalIpAddresses() + public List GetLocalIpAddresses() { const int cacheMinutes = 5; diff --git a/Emby.Dlna/ConnectionManager/ControlHandler.cs b/Emby.Dlna/ConnectionManager/ControlHandler.cs index 0bc44db170..ae983c5e75 100644 --- a/Emby.Dlna/ConnectionManager/ControlHandler.cs +++ b/Emby.Dlna/ConnectionManager/ControlHandler.cs @@ -14,7 +14,7 @@ namespace Emby.Dlna.ConnectionManager { private readonly DeviceProfile _profile; - protected override IEnumerable> GetResult(string methodName, Headers methodParams) + protected override IEnumerable> GetResult(string methodName, IDictionary methodParams) { if (string.Equals(methodName, "GetProtocolInfo", StringComparison.OrdinalIgnoreCase)) { @@ -26,7 +26,7 @@ namespace Emby.Dlna.ConnectionManager private IEnumerable> HandleGetProtocolInfo() { - return new Headers(true) + return new Dictionary(StringComparer.OrdinalIgnoreCase) { { "Source", _profile.ProtocolInfo }, { "Sink", "" } diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs index d77919e47a..98a151f295 100644 --- a/Emby.Dlna/ContentDirectory/ControlHandler.cs +++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs @@ -65,7 +65,7 @@ namespace Emby.Dlna.ContentDirectory _didlBuilder = new DidlBuilder(profile, user, imageProcessor, serverAddress, accessToken, userDataManager, localization, mediaSourceManager, Logger, libraryManager, mediaEncoder); } - protected override IEnumerable> GetResult(string methodName, Headers methodParams) + protected override IEnumerable> GetResult(string methodName, IDictionary methodParams) { var deviceId = "test"; @@ -118,17 +118,20 @@ namespace Emby.Dlna.ContentDirectory _userDataManager.SaveUserData(user.Id, item, userdata, UserDataSaveReason.TogglePlayed, CancellationToken.None); - return new Headers(); + return new Dictionary(StringComparer.OrdinalIgnoreCase); } private IEnumerable> HandleGetSearchCapabilities() { - return new Headers(true) { { "SearchCaps", "res@resolution,res@size,res@duration,dc:title,dc:creator,upnp:actor,upnp:artist,upnp:genre,upnp:album,dc:date,upnp:class,@id,@refID,@protocolInfo,upnp:author,dc:description,pv:avKeywords" } }; + return new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "SearchCaps", "res@resolution,res@size,res@duration,dc:title,dc:creator,upnp:actor,upnp:artist,upnp:genre,upnp:album,dc:date,upnp:class,@id,@refID,@protocolInfo,upnp:author,dc:description,pv:avKeywords" } + }; } private IEnumerable> HandleGetSortCapabilities() { - return new Headers(true) + return new Dictionary(StringComparer.OrdinalIgnoreCase) { { "SortCaps", "res@duration,res@size,res@bitrate,dc:date,dc:title,dc:size,upnp:album,upnp:artist,upnp:albumArtist,upnp:episodeNumber,upnp:genre,upnp:originalTrackNumber,upnp:rating" } }; @@ -136,7 +139,7 @@ namespace Emby.Dlna.ContentDirectory private IEnumerable> HandleGetSortExtensionCapabilities() { - return new Headers(true) + return new Dictionary(StringComparer.OrdinalIgnoreCase) { { "SortExtensionCaps", "res@duration,res@size,res@bitrate,dc:date,dc:title,dc:size,upnp:album,upnp:artist,upnp:albumArtist,upnp:episodeNumber,upnp:genre,upnp:originalTrackNumber,upnp:rating" } }; @@ -144,14 +147,14 @@ namespace Emby.Dlna.ContentDirectory private IEnumerable> HandleGetSystemUpdateID() { - var headers = new Headers(true); + var headers = new Dictionary(StringComparer.OrdinalIgnoreCase); headers.Add("Id", _systemUpdateId.ToString(_usCulture)); return headers; } private IEnumerable> HandleGetFeatureList() { - return new Headers(true) + return new Dictionary(StringComparer.OrdinalIgnoreCase) { { "FeatureList", GetFeatureListXml() } }; @@ -159,7 +162,7 @@ namespace Emby.Dlna.ContentDirectory private IEnumerable> HandleXGetFeatureList() { - return new Headers(true) + return new Dictionary(StringComparer.OrdinalIgnoreCase) { { "FeatureList", GetFeatureListXml() } }; @@ -183,12 +186,24 @@ namespace Emby.Dlna.ContentDirectory return builder.ToString(); } - private async Task>> HandleBrowse(Headers sparams, User user, string deviceId) + public string GetValueOrDefault(IDictionary sparams, string key, string defaultValue) + { + string val; + + if (sparams.TryGetValue(key, out val)) + { + return val; + } + + return defaultValue; + } + + private async Task>> HandleBrowse(IDictionary sparams, User user, string deviceId) { var id = sparams["ObjectID"]; var flag = sparams["BrowseFlag"]; - var filter = new Filter(sparams.GetValueOrDefault("Filter", "*")); - var sortCriteria = new SortCriteria(sparams.GetValueOrDefault("SortCriteria", "")); + var filter = new Filter(GetValueOrDefault(sparams, "Filter", "*")); + var sortCriteria = new SortCriteria(GetValueOrDefault(sparams, "SortCriteria", "")); var provided = 0; @@ -294,11 +309,11 @@ namespace Emby.Dlna.ContentDirectory }; } - private async Task>> HandleSearch(Headers sparams, User user, string deviceId) + private async Task>> HandleSearch(IDictionary sparams, User user, string deviceId) { - var searchCriteria = new SearchCriteria(sparams.GetValueOrDefault("SearchCriteria", "")); - var sortCriteria = new SortCriteria(sparams.GetValueOrDefault("SortCriteria", "")); - var filter = new Filter(sparams.GetValueOrDefault("Filter", "*")); + var searchCriteria = new SearchCriteria(GetValueOrDefault(sparams, "SearchCriteria", "")); + var sortCriteria = new SortCriteria(GetValueOrDefault(sparams, "SortCriteria", "")); + var filter = new Filter(GetValueOrDefault(sparams, "Filter", "*")); // sort example: dc:title, dc:date diff --git a/Emby.Dlna/Emby.Dlna.csproj b/Emby.Dlna/Emby.Dlna.csproj index 0441cb3be0..4d1aacfec5 100644 --- a/Emby.Dlna/Emby.Dlna.csproj +++ b/Emby.Dlna/Emby.Dlna.csproj @@ -152,7 +152,6 @@ - diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index 170b4cee0e..858b1ae9ec 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -54,6 +54,7 @@ namespace Emby.Dlna.Main private readonly ITimerFactory _timerFactory; private readonly ISocketFactory _socketFactory; private readonly IEnvironmentInfo _environmentInfo; + private readonly INetworkManager _networkManager; private ISsdpCommunicationsServer _communicationsServer; @@ -69,7 +70,7 @@ namespace Emby.Dlna.Main IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, - IDeviceDiscovery deviceDiscovery, IMediaEncoder mediaEncoder, ISocketFactory socketFactory, ITimerFactory timerFactory, IEnvironmentInfo environmentInfo) + IDeviceDiscovery deviceDiscovery, IMediaEncoder mediaEncoder, ISocketFactory socketFactory, ITimerFactory timerFactory, IEnvironmentInfo environmentInfo, INetworkManager networkManager) { _config = config; _appHost = appHost; @@ -87,6 +88,7 @@ namespace Emby.Dlna.Main _socketFactory = socketFactory; _timerFactory = timerFactory; _environmentInfo = environmentInfo; + _networkManager = networkManager; _logger = logManager.GetLogger("Dlna"); } @@ -156,7 +158,7 @@ namespace Emby.Dlna.Main { if (_communicationsServer == null) { - _communicationsServer = new SsdpCommunicationsServer(_socketFactory) + _communicationsServer = new SsdpCommunicationsServer(_socketFactory, _networkManager, _logger) { IsShared = true }; diff --git a/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs b/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs index 5e232aeaca..daf46b1061 100644 --- a/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs +++ b/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs @@ -11,7 +11,7 @@ namespace Emby.Dlna.MediaReceiverRegistrar { public class ControlHandler : BaseControlHandler { - protected override IEnumerable> GetResult(string methodName, Headers methodParams) + protected override IEnumerable> GetResult(string methodName, IDictionary methodParams) { if (string.Equals(methodName, "IsAuthorized", StringComparison.OrdinalIgnoreCase)) return HandleIsAuthorized(); @@ -23,7 +23,7 @@ namespace Emby.Dlna.MediaReceiverRegistrar private IEnumerable> HandleIsAuthorized() { - return new Headers(true) + return new Dictionary(StringComparer.OrdinalIgnoreCase) { { "Result", "1" } }; @@ -31,7 +31,7 @@ namespace Emby.Dlna.MediaReceiverRegistrar private IEnumerable> HandleIsValidated() { - return new Headers(true) + return new Dictionary(StringComparer.OrdinalIgnoreCase) { { "Result", "1" } }; diff --git a/Emby.Dlna/Server/Headers.cs b/Emby.Dlna/Server/Headers.cs deleted file mode 100644 index 47dd8e3215..0000000000 --- a/Emby.Dlna/Server/Headers.cs +++ /dev/null @@ -1,169 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; - -namespace Emby.Dlna.Server -{ - public class Headers : IDictionary - { - private readonly bool _asIs = false; - private readonly Dictionary _dict = new Dictionary(); - private readonly static Regex Validator = new Regex(@"^[a-z\d][a-z\d_.-]+$", RegexOptions.IgnoreCase); - - public Headers(bool asIs) - { - _asIs = asIs; - } - - public Headers() - : this(asIs: false) - { - } - - public int Count - { - get - { - return _dict.Count; - } - } - public string HeaderBlock - { - get - { - var hb = new StringBuilder(); - foreach (var h in this) - { - hb.AppendFormat("{0}: {1}\r\n", h.Key, h.Value); - } - return hb.ToString(); - } - } - public bool IsReadOnly - { - get - { - return false; - } - } - public ICollection Keys - { - get - { - return _dict.Keys; - } - } - public ICollection Values - { - get - { - return _dict.Values; - } - } - - - public string this[string key] - { - get - { - return _dict[Normalize(key)]; - } - set - { - _dict[Normalize(key)] = value; - } - } - - - private string Normalize(string header) - { - if (!_asIs) - { - header = header.ToLower(); - } - header = header.Trim(); - if (!Validator.IsMatch(header)) - { - throw new ArgumentException("Invalid header: " + header); - } - return header; - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return _dict.GetEnumerator(); - } - - public void Add(KeyValuePair item) - { - Add(item.Key, item.Value); - } - - public void Add(string key, string value) - { - _dict.Add(Normalize(key), value); - } - - public void Clear() - { - _dict.Clear(); - } - - public bool Contains(KeyValuePair item) - { - var p = new KeyValuePair(Normalize(item.Key), item.Value); - return _dict.Contains(p); - } - - public bool ContainsKey(string key) - { - return _dict.ContainsKey(Normalize(key)); - } - - public void CopyTo(KeyValuePair[] array, int arrayIndex) - { - throw new NotImplementedException(); - } - - public IEnumerator> GetEnumerator() - { - return _dict.GetEnumerator(); - } - - public bool Remove(string key) - { - return _dict.Remove(Normalize(key)); - } - - public bool Remove(KeyValuePair item) - { - return Remove(item.Key); - } - - public override string ToString() - { - return string.Format("({0})", string.Join(", ", (from x in _dict - select string.Format("{0}={1}", x.Key, x.Value)))); - } - - public bool TryGetValue(string key, out string value) - { - return _dict.TryGetValue(Normalize(key), out value); - } - - public string GetValueOrDefault(string key, string defaultValue) - { - string val; - - if (TryGetValue(key, out val)) - { - return val; - } - - return defaultValue; - } - } -} diff --git a/Emby.Dlna/Service/BaseControlHandler.cs b/Emby.Dlna/Service/BaseControlHandler.cs index 4ce047172e..3092589c12 100644 --- a/Emby.Dlna/Service/BaseControlHandler.cs +++ b/Emby.Dlna/Service/BaseControlHandler.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Text; using System.Xml; using Emby.Dlna.Didl; +using MediaBrowser.Controller.Extensions; using MediaBrowser.Model.Xml; namespace Emby.Dlna.Service @@ -185,8 +186,7 @@ namespace Emby.Dlna.Service { using (var subReader = reader.ReadSubtree()) { - result.Headers = ParseFirstBodyChild(subReader); - + ParseFirstBodyChild(subReader, result.Headers); return result; } } @@ -204,10 +204,8 @@ namespace Emby.Dlna.Service return result; } - private Headers ParseFirstBodyChild(XmlReader reader) + private void ParseFirstBodyChild(XmlReader reader, IDictionary headers) { - var result = new Headers(); - reader.MoveToContent(); reader.Read(); @@ -216,25 +214,24 @@ namespace Emby.Dlna.Service { if (reader.NodeType == XmlNodeType.Element) { - result.Add(reader.LocalName, reader.ReadElementContentAsString()); + // TODO: Should we be doing this here, or should it be handled earlier when decoding the request? + headers[reader.LocalName.RemoveDiacritics()] = reader.ReadElementContentAsString(); } else { reader.Read(); } } - - return result; } private class ControlRequestInfo { public string LocalName; public string NamespaceURI; - public Headers Headers = new Headers(); + public IDictionary Headers = new Dictionary(StringComparer.OrdinalIgnoreCase); } - protected abstract IEnumerable> GetResult(string methodName, Headers methodParams); + protected abstract IEnumerable> GetResult(string methodName, IDictionary methodParams); private void LogRequest(ControlRequest request) { diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 8a5ae2c3a5..0e1f5a5517 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -518,7 +518,7 @@ namespace Emby.Server.Implementations.HttpServer return; } - var handler = HttpHandlerFactory.GetHandler(httpReq); + var handler = HttpHandlerFactory.GetHandler(httpReq, _logger); if (handler != null) { diff --git a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs index b337e1b9e4..f140f799fd 100644 --- a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs @@ -299,7 +299,8 @@ namespace Emby.Server.Implementations.Library.Resolvers return false; } - return directoryService.GetFiles(fullPath).Any(i => string.Equals(i.Extension, ".m2ts", StringComparison.OrdinalIgnoreCase)); + return directoryService.GetFiles(fullPath).Any(i => string.Equals(i.Extension, ".m2ts", StringComparison.OrdinalIgnoreCase) || + string.Equals(i.Extension, ".mts", StringComparison.OrdinalIgnoreCase)); } } } diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs index 557fa1f5ff..57307aa73a 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs @@ -77,7 +77,9 @@ namespace Emby.Server.Implementations.LiveTv.Listings // It's going to come back gzipped regardless of this value // So we need to make sure the decompression method is set to gzip - EnableHttpCompression = true + EnableHttpCompression = true, + + UserAgent = "Emby/3.0" }).ConfigureAwait(false); diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs index 5ac701f820..3bc22c07f5 100644 --- a/MediaBrowser.Common/Net/INetworkManager.cs +++ b/MediaBrowser.Common/Net/INetworkManager.cs @@ -47,7 +47,7 @@ namespace MediaBrowser.Common.Net /// true if [is in local network] [the specified endpoint]; otherwise, false. bool IsInLocalNetwork(string endpoint); - IEnumerable GetLocalIpAddresses(); + List GetLocalIpAddresses(); IpAddressInfo ParseIpAddress(string ipAddress); diff --git a/MediaBrowser.Model/Net/ISocketFactory.cs b/MediaBrowser.Model/Net/ISocketFactory.cs index 599292ddf8..ac406e7f12 100644 --- a/MediaBrowser.Model/Net/ISocketFactory.cs +++ b/MediaBrowser.Model/Net/ISocketFactory.cs @@ -17,7 +17,7 @@ namespace MediaBrowser.Model.Net /// /// Createa a new unicast socket using the specified local port number. /// - IUdpSocket CreateSsdpUdpSocket(int localPort); + IUdpSocket CreateSsdpUdpSocket(IpAddressInfo localIp, int localPort); /// /// Createa a new multicast socket using the specified multicast IP address, multicast time to live and local port. diff --git a/MediaBrowser.Model/Net/IUdpSocket.cs b/MediaBrowser.Model/Net/IUdpSocket.cs index ef090e010c..c705107266 100644 --- a/MediaBrowser.Model/Net/IUdpSocket.cs +++ b/MediaBrowser.Model/Net/IUdpSocket.cs @@ -10,16 +10,18 @@ namespace MediaBrowser.Model.Net /// Provides a common interface across platforms for UDP sockets used by this SSDP implementation. /// public interface IUdpSocket : IDisposable - { - /// - /// Waits for and returns the next UDP message sent to this socket (uni or multicast). - /// - /// - Task ReceiveAsync(); + { + IpAddressInfo LocalIPAddress { get; } + + /// + /// Waits for and returns the next UDP message sent to this socket (uni or multicast). + /// + /// + Task ReceiveAsync(); /// /// Sends a UDP message to a particular end point (uni or multicast). /// Task SendAsync(byte[] buffer, int bytes, IpEndPointInfo endPoint); - } + } } \ No newline at end of file diff --git a/MediaBrowser.Model/Net/SocketReceiveResult.cs b/MediaBrowser.Model/Net/SocketReceiveResult.cs index 0a2d04ad39..483e2297b9 100644 --- a/MediaBrowser.Model/Net/SocketReceiveResult.cs +++ b/MediaBrowser.Model/Net/SocketReceiveResult.cs @@ -20,5 +20,6 @@ namespace MediaBrowser.Model.Net /// The the data was received from. /// public IpEndPointInfo RemoteEndPoint { get; set; } - } + public IpAddressInfo LocalIPAddress { get; set; } + } } diff --git a/RSSDP/ISsdpCommunicationsServer.cs b/RSSDP/ISsdpCommunicationsServer.cs index 462f33fe67..eea5e0ed67 100644 --- a/RSSDP/ISsdpCommunicationsServer.cs +++ b/RSSDP/ISsdpCommunicationsServer.cs @@ -46,7 +46,8 @@ namespace Rssdp.Infrastructure /// /// A byte array containing the data to send. /// A representing the destination address for the data. Can be either a multicast or unicast destination. - Task SendMessage(byte[] messageData, IpEndPointInfo destination); + /// A The local ip address to send from, or .Any if sending from all available + Task SendMessage(byte[] messageData, IpEndPointInfo destination, IpAddressInfo fromLocalIpAddress); /// /// Sends a message to the SSDP multicast address and port. diff --git a/RSSDP/RSSDP.csproj b/RSSDP/RSSDP.csproj index d60f6ea441..ef1f322078 100644 --- a/RSSDP/RSSDP.csproj +++ b/RSSDP/RSSDP.csproj @@ -50,7 +50,6 @@ - @@ -70,6 +69,10 @@ + + {9142eefa-7570-41e1-bfcc-468bb571af2f} + MediaBrowser.Common + {7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b} MediaBrowser.Model diff --git a/RSSDP/ReadOnlyEnumerable.cs b/RSSDP/ReadOnlyEnumerable.cs deleted file mode 100644 index 1a69f88379..0000000000 --- a/RSSDP/ReadOnlyEnumerable.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Rssdp -{ - internal sealed class ReadOnlyEnumerable : System.Collections.Generic.IEnumerable - { - - #region Fields - - private IEnumerable _Items; - - #endregion - - #region Constructors - - public ReadOnlyEnumerable(IEnumerable items) - { - if (items == null) throw new ArgumentNullException("items"); - - _Items = items; - } - - #endregion - - #region IEnumerable Members - - public IEnumerator GetEnumerator() - { - return _Items.GetEnumerator(); - } - - #endregion - - #region IEnumerable Members - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return _Items.GetEnumerator(); - } - - #endregion - } -} diff --git a/RSSDP/RequestReceivedEventArgs.cs b/RSSDP/RequestReceivedEventArgs.cs index fb4fac8714..03c059634f 100644 --- a/RSSDP/RequestReceivedEventArgs.cs +++ b/RSSDP/RequestReceivedEventArgs.cs @@ -22,17 +22,18 @@ namespace Rssdp.Infrastructure #endregion - #region Constructors + public IpAddressInfo LocalIpAddress { get; private set; } + + #region Constructors /// /// Full constructor. /// - /// The that was received. - /// A representing the sender's address (sometimes used for replies). - public RequestReceivedEventArgs(HttpRequestMessage message, IpEndPointInfo receivedFrom) + public RequestReceivedEventArgs(HttpRequestMessage message, IpEndPointInfo receivedFrom, IpAddressInfo localIpAddress) { _Message = message; _ReceivedFrom = receivedFrom; + LocalIpAddress = localIpAddress; } #endregion diff --git a/RSSDP/SsdpCommunicationsServer.cs b/RSSDP/SsdpCommunicationsServer.cs index 4de47f3d33..5d8ca15bba 100644 --- a/RSSDP/SsdpCommunicationsServer.cs +++ b/RSSDP/SsdpCommunicationsServer.cs @@ -5,6 +5,8 @@ using System.Net; using System.Net.Http; using System.Text; using System.Threading.Tasks; +using MediaBrowser.Common.Net; +using MediaBrowser.Model.Logging; using MediaBrowser.Model.Net; namespace Rssdp.Infrastructure @@ -38,12 +40,13 @@ namespace Rssdp.Infrastructure private IUdpSocket _BroadcastListenSocket; private object _SendSocketSynchroniser = new object(); - private IUdpSocket _SendSocket; + private List _sendSockets; private HttpRequestParser _RequestParser; private HttpResponseParser _ResponseParser; - + private readonly ILogger _logger; private ISocketFactory _SocketFactory; + private readonly INetworkManager _networkManager; private int _LocalPort; private int _MulticastTtl; @@ -71,22 +74,18 @@ namespace Rssdp.Infrastructure /// /// Minimum constructor. /// - /// An implementation of the interface that can be used to make new unicast and multicast sockets. Cannot be null. /// The argument is null. - public SsdpCommunicationsServer(ISocketFactory socketFactory) - : this(socketFactory, 0, SsdpConstants.SsdpDefaultMulticastTimeToLive) + public SsdpCommunicationsServer(ISocketFactory socketFactory, INetworkManager networkManager, ILogger logger) + : this(socketFactory, 0, SsdpConstants.SsdpDefaultMulticastTimeToLive, networkManager, logger) { } /// /// Full constructor. /// - /// An implementation of the interface that can be used to make new unicast and multicast sockets. Cannot be null. - /// The specific local port to use for all sockets created by this instance. Specify zero to indicate the system should choose a free port itself. - /// The multicast time to live value for multicast sockets. Technically this is a number of router hops, not a 'Time'. Must be greater than zero. /// The argument is null. /// The argument is less than or equal to zero. - public SsdpCommunicationsServer(ISocketFactory socketFactory, int localPort, int multicastTimeToLive) + public SsdpCommunicationsServer(ISocketFactory socketFactory, int localPort, int multicastTimeToLive, INetworkManager networkManager, ILogger logger) { if (socketFactory == null) throw new ArgumentNullException("socketFactory"); if (multicastTimeToLive <= 0) throw new ArgumentOutOfRangeException("multicastTimeToLive", "multicastTimeToLive must be greater than zero."); @@ -101,6 +100,8 @@ namespace Rssdp.Infrastructure _ResponseParser = new HttpResponseParser(); _MulticastTtl = multicastTimeToLive; + _networkManager = networkManager; + _logger = logger; } #endregion @@ -148,25 +149,72 @@ namespace Rssdp.Infrastructure /// /// A byte array containing the data to send. /// A representing the destination address for the data. Can be either a multicast or unicast destination. + /// A The local ip address to send from, or .Any if sending from all available /// Thrown if the argument is null. /// Thrown if the property is true (because has been called previously). - public async Task SendMessage(byte[] messageData, IpEndPointInfo destination) + public async Task SendMessage(byte[] messageData, IpEndPointInfo destination, IpAddressInfo fromLocalIpAddress) { if (messageData == null) throw new ArgumentNullException("messageData"); ThrowIfDisposed(); - EnsureSendSocketCreated(); + var sockets = GetSendSockets(fromLocalIpAddress, destination); + + if (sockets.Count == 0) + { + return; + } // SSDP spec recommends sending messages multiple times (not more than 3) to account for possible packet loss over UDP. for (var i = 0; i < SsdpConstants.UdpResendCount; i++) { - await SendMessageIfSocketNotDisposed(messageData, destination).ConfigureAwait(false); + var tasks = sockets.Select(s => SendFromSocket(s, messageData, destination)).ToArray(); + await Task.WhenAll(tasks).ConfigureAwait(false); await Task.Delay(100).ConfigureAwait(false); } } + private async Task SendFromSocket(IUdpSocket socket, byte[] messageData, IpEndPointInfo destination) + { + try + { + await socket.SendAsync(messageData, messageData.Length, destination).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("Error sending socket message from {0} to {1}", ex, socket.LocalIPAddress.ToString(), destination.ToString()); + } + } + + private List GetSendSockets(IpAddressInfo fromLocalIpAddress, IpEndPointInfo destination) + { + EnsureSendSocketCreated(); + + lock (_SendSocketSynchroniser) + { + var sockets = _sendSockets.Where(i => i.LocalIPAddress.AddressFamily == fromLocalIpAddress.AddressFamily); + + // Send from the Any socket and the socket with the matching address + if (fromLocalIpAddress.AddressFamily == IpAddressFamily.InterNetwork) + { + sockets = sockets.Where(i => i.LocalIPAddress.Equals(IpAddressInfo.Any) || fromLocalIpAddress.Equals(i.LocalIPAddress)); + } + else if (fromLocalIpAddress.AddressFamily == IpAddressFamily.InterNetworkV6) + { + sockets = sockets.Where(i => i.LocalIPAddress.Equals(IpAddressInfo.IPv6Any) || fromLocalIpAddress.Equals(i.LocalIPAddress)); + } + + // If sending to the loopback address, filter the socket list as well + if (destination.IpAddress.Equals(IpAddressInfo.Loopback)) + { + sockets = sockets.Where(i => i.LocalIPAddress.Equals(IpAddressInfo.Any) || i.LocalIPAddress.Equals(IpAddressInfo.Loopback)); + } + + return sockets.ToList(); + } + } + /// /// Sends a message to the SSDP multicast address and port. /// @@ -185,7 +233,10 @@ namespace Rssdp.Infrastructure { await SendMessageIfSocketNotDisposed(messageData, new IpEndPointInfo { - IpAddress = new IpAddressInfo { Address = SsdpConstants.MulticastLocalAdminAddress }, + IpAddress = new IpAddressInfo + { + Address = SsdpConstants.MulticastLocalAdminAddress + }, Port = SsdpConstants.MulticastPort }).ConfigureAwait(false); @@ -204,10 +255,16 @@ namespace Rssdp.Infrastructure lock (_SendSocketSynchroniser) { - var socket = _SendSocket; - _SendSocket = null; - if (socket != null) - socket.Dispose(); + if (_sendSockets != null) + { + var sockets = _sendSockets.ToList(); + _sendSockets = null; + + foreach (var socket in sockets) + { + socket.Dispose(); + } + } } } @@ -247,8 +304,16 @@ namespace Rssdp.Infrastructure lock (_SendSocketSynchroniser) { - if (_SendSocket != null) - _SendSocket.Dispose(); + if (_sendSockets != null) + { + var sockets = _sendSockets.ToList(); + _sendSockets = null; + + foreach (var socket in sockets) + { + socket.Dispose(); + } + } } } } @@ -257,16 +322,20 @@ namespace Rssdp.Infrastructure #region Private Methods - private Task SendMessageIfSocketNotDisposed(byte[] messageData, IpEndPointInfo destination) + private async Task SendMessageIfSocketNotDisposed(byte[] messageData, IpEndPointInfo destination) { - var socket = _SendSocket; - if (socket != null) + var sockets = _sendSockets; + if (sockets != null) { - return _SendSocket.SendAsync(messageData, messageData.Length, destination); + sockets = sockets.ToList(); + + foreach (var socket in sockets) + { + await socket.SendAsync(messageData, messageData.Length, destination).ConfigureAwait(false); + } } ThrowIfDisposed(); - return Task.FromResult(true); } private IUdpSocket ListenForBroadcastsAsync() @@ -278,13 +347,30 @@ namespace Rssdp.Infrastructure return socket; } - private IUdpSocket CreateSocketAndListenForResponsesAsync() + private List CreateSocketAndListenForResponsesAsync() { - _SendSocket = _SocketFactory.CreateSsdpUdpSocket(_LocalPort); + var sockets = new List(); - ListenToSocket(_SendSocket); + sockets.Add(_SocketFactory.CreateSsdpUdpSocket(IpAddressInfo.Any, _LocalPort)); - return _SendSocket; + foreach (var address in _networkManager.GetLocalIpAddresses().ToList()) + { + try + { + sockets.Add(_SocketFactory.CreateSsdpUdpSocket(address, _LocalPort)); + } + catch (Exception ex) + { + _logger.ErrorException("Error in CreateSsdpUdpSocket. IPAddress: {0}", ex, address); + } + } + + foreach (var socket in sockets) + { + ListenToSocket(socket); + } + + return sockets; } [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "t", Justification = "Capturing task to local variable removes compiler warning, task is not otherwise required.")] @@ -305,7 +391,7 @@ namespace Rssdp.Infrastructure // Strange cannot convert compiler error here if I don't explicitly // assign or cast to Action first. Assignment is easier to read, // so went with that. - ProcessMessage(System.Text.UTF8Encoding.UTF8.GetString(result.Buffer, 0, result.ReceivedBytes), result.RemoteEndPoint); + ProcessMessage(System.Text.UTF8Encoding.UTF8.GetString(result.Buffer, 0, result.ReceivedBytes), result.RemoteEndPoint, result.LocalIPAddress); } } catch (ObjectDisposedException) @@ -322,19 +408,19 @@ namespace Rssdp.Infrastructure private void EnsureSendSocketCreated() { - if (_SendSocket == null) + if (_sendSockets == null) { lock (_SendSocketSynchroniser) { - if (_SendSocket == null) + if (_sendSockets == null) { - _SendSocket = CreateSocketAndListenForResponsesAsync(); + _sendSockets = CreateSocketAndListenForResponsesAsync(); } } } } - private void ProcessMessage(string data, IpEndPointInfo endPoint) + private void ProcessMessage(string data, IpEndPointInfo endPoint, IpAddressInfo receivedOnLocalIpAddress) { //Responses start with the HTTP version, prefixed with HTTP/ while //requests start with a method which can vary and might be one we haven't @@ -347,7 +433,10 @@ namespace Rssdp.Infrastructure { responseMessage = _ResponseParser.Parse(data); } - catch (ArgumentException) { } // Ignore invalid packets. + catch (ArgumentException ex) + { + // Ignore invalid packets. + } if (responseMessage != null) OnResponseReceived(responseMessage, endPoint); @@ -359,23 +448,31 @@ namespace Rssdp.Infrastructure { requestMessage = _RequestParser.Parse(data); } - catch (ArgumentException) { } // Ignore invalid packets. + catch (ArgumentException ex) + { + // Ignore invalid packets. + } if (requestMessage != null) - OnRequestReceived(requestMessage, endPoint); + { + OnRequestReceived(requestMessage, endPoint, receivedOnLocalIpAddress); + } } } - private void OnRequestReceived(HttpRequestMessage data, IpEndPointInfo endPoint) + private void OnRequestReceived(HttpRequestMessage data, IpEndPointInfo remoteEndPoint, IpAddressInfo receivedOnLocalIpAddress) { //SSDP specification says only * is currently used but other uri's might //be implemented in the future and should be ignored unless understood. //Section 4.2 - http://tools.ietf.org/html/draft-cai-ssdp-v1-03#page-11 - if (data.RequestUri.ToString() != "*") return; + if (data.RequestUri.ToString() != "*") + { + return; + } var handlers = this.RequestReceived; if (handlers != null) - handlers(this, new RequestReceivedEventArgs(data, endPoint)); + handlers(this, new RequestReceivedEventArgs(data, remoteEndPoint, receivedOnLocalIpAddress)); } private void OnResponseReceived(HttpResponseMessage data, IpEndPointInfo endPoint) diff --git a/RSSDP/SsdpDevice.cs b/RSSDP/SsdpDevice.cs index 8a4992239b..a595742d00 100644 --- a/RSSDP/SsdpDevice.cs +++ b/RSSDP/SsdpDevice.cs @@ -64,7 +64,7 @@ namespace Rssdp this.Icons = new List(); _Devices = new List(); - this.Devices = new ReadOnlyEnumerable(_Devices); + this.Devices = new ReadOnlyCollection(_Devices); _CustomResponseHeaders = new CustomHttpHeadersCollection(); _CustomProperties = new SsdpDevicePropertiesCollection(); } diff --git a/RSSDP/SsdpDevicePublisherBase.cs b/RSSDP/SsdpDevicePublisherBase.cs index 7737733f7b..ee33359574 100644 --- a/RSSDP/SsdpDevicePublisherBase.cs +++ b/RSSDP/SsdpDevicePublisherBase.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using System.Net.Http; using System.Text; @@ -25,7 +26,7 @@ namespace Rssdp.Infrastructure private bool _SupportPnpRootDevice; private IList _Devices; - private ReadOnlyEnumerable _ReadOnlyDevices; + private IReadOnlyList _ReadOnlyDevices; private ITimer _RebroadcastAliveNotificationsTimer; private ITimerFactory _timerFactory; @@ -62,7 +63,7 @@ namespace Rssdp.Infrastructure _SupportPnpRootDevice = true; _timerFactory = timerFactory; _Devices = new List(); - _ReadOnlyDevices = new ReadOnlyEnumerable(_Devices); + _ReadOnlyDevices = new ReadOnlyCollection(_Devices); _RecentSearchRequests = new Dictionary(StringComparer.OrdinalIgnoreCase); _Random = new Random(); _DeviceValidator = new Upnp10DeviceValidator(); //Should probably inject this later, but for now we only support 1.0. @@ -236,17 +237,17 @@ namespace Rssdp.Infrastructure #region Search Related Methods - private void ProcessSearchRequest(string mx, string searchTarget, IpEndPointInfo endPoint) + private void ProcessSearchRequest(string mx, string searchTarget, IpEndPointInfo remoteEndPoint, IpAddressInfo receivedOnlocalIpAddress) { if (String.IsNullOrEmpty(searchTarget)) { - WriteTrace(String.Format("Invalid search request received From {0}, Target is null/empty.", endPoint.ToString())); + WriteTrace(String.Format("Invalid search request received From {0}, Target is null/empty.", remoteEndPoint.ToString())); return; } - WriteTrace(String.Format("Search Request Received From {0}, Target = {1}", endPoint.ToString(), searchTarget)); + WriteTrace(String.Format("Search Request Received From {0}, Target = {1}", remoteEndPoint.ToString(), searchTarget)); - if (IsDuplicateSearchRequest(searchTarget, endPoint)) + if (IsDuplicateSearchRequest(searchTarget, remoteEndPoint)) { WriteTrace("Search Request is Duplicate, ignoring."); return; @@ -294,7 +295,7 @@ namespace Rssdp.Infrastructure foreach (var device in deviceList) { - SendDeviceSearchResponses(device, endPoint); + SendDeviceSearchResponses(device, remoteEndPoint, receivedOnlocalIpAddress); } } else @@ -307,19 +308,19 @@ namespace Rssdp.Infrastructure return _Devices.Union(_Devices.SelectManyRecursive((d) => d.Devices)); } - private void SendDeviceSearchResponses(SsdpDevice device, IpEndPointInfo endPoint) + private void SendDeviceSearchResponses(SsdpDevice device, IpEndPointInfo endPoint, IpAddressInfo receivedOnlocalIpAddress) { bool isRootDevice = (device as SsdpRootDevice) != null; if (isRootDevice) { - SendSearchResponse(SsdpConstants.UpnpDeviceTypeRootDevice, device, GetUsn(device.Udn, SsdpConstants.UpnpDeviceTypeRootDevice), endPoint); + SendSearchResponse(SsdpConstants.UpnpDeviceTypeRootDevice, device, GetUsn(device.Udn, SsdpConstants.UpnpDeviceTypeRootDevice), endPoint, receivedOnlocalIpAddress); if (this.SupportPnpRootDevice) - SendSearchResponse(SsdpConstants.PnpDeviceTypeRootDevice, device, GetUsn(device.Udn, SsdpConstants.PnpDeviceTypeRootDevice), endPoint); + SendSearchResponse(SsdpConstants.PnpDeviceTypeRootDevice, device, GetUsn(device.Udn, SsdpConstants.PnpDeviceTypeRootDevice), endPoint, receivedOnlocalIpAddress); } - SendSearchResponse(device.Udn, device, device.Udn, endPoint); + SendSearchResponse(device.Udn, device, device.Udn, endPoint, receivedOnlocalIpAddress); - SendSearchResponse(device.FullDeviceType, device, GetUsn(device.Udn, device.FullDeviceType), endPoint); + SendSearchResponse(device.FullDeviceType, device, GetUsn(device.Udn, device.FullDeviceType), endPoint, receivedOnlocalIpAddress); } private static string GetUsn(string udn, string fullDeviceType) @@ -327,7 +328,7 @@ namespace Rssdp.Infrastructure return String.Format("{0}::{1}", udn, fullDeviceType); } - private async void SendSearchResponse(string searchTarget, SsdpDevice device, string uniqueServiceName, IpEndPointInfo endPoint) + private async void SendSearchResponse(string searchTarget, SsdpDevice device, string uniqueServiceName, IpEndPointInfo endPoint, IpAddressInfo receivedOnlocalIpAddress) { var rootDevice = device.ToRootDevice(); @@ -349,7 +350,7 @@ namespace Rssdp.Infrastructure try { - await _CommsServer.SendMessage(System.Text.Encoding.UTF8.GetBytes(message), endPoint).ConfigureAwait(false); + await _CommsServer.SendMessage(System.Text.Encoding.UTF8.GetBytes(message), endPoint, receivedOnlocalIpAddress).ConfigureAwait(false); } catch (Exception ex) { @@ -674,7 +675,7 @@ namespace Rssdp.Infrastructure //else if (!e.Message.Headers.Contains("MAN")) // WriteTrace("Ignoring search request - missing MAN header."); //else - ProcessSearchRequest(GetFirstHeaderValue(e.Message.Headers, "MX"), GetFirstHeaderValue(e.Message.Headers, "ST"), e.ReceivedFrom); + ProcessSearchRequest(GetFirstHeaderValue(e.Message.Headers, "MX"), GetFirstHeaderValue(e.Message.Headers, "ST"), e.ReceivedFrom, e.LocalIpAddress); } } diff --git a/ServiceStack/Host/RestPath.cs b/ServiceStack/Host/RestPath.cs index 7222578a98..5bbd03a21e 100644 --- a/ServiceStack/Host/RestPath.cs +++ b/ServiceStack/Host/RestPath.cs @@ -109,7 +109,7 @@ namespace ServiceStack.Host this.Notes = notes; this.restPath = path; - this.allowsAllVerbs = verbs == null || verbs == WildCard; + this.allowsAllVerbs = verbs == null || string.Equals(verbs, WildCard, StringComparison.OrdinalIgnoreCase); if (!this.allowsAllVerbs) { this.allowedVerbs = verbs.ToUpper(); @@ -123,7 +123,7 @@ namespace ServiceStack.Host { if (string.IsNullOrEmpty(component)) continue; - if (component.Contains(VariablePrefix) + if (StringContains(component, VariablePrefix) && component.IndexOf(ComponentSeperator) != -1) { hasSeparators.Add(true); @@ -240,12 +240,17 @@ namespace ServiceStack.Host score += Math.Max((10 - VariableArgsCount), 1) * 100; //Exact verb match is better than ANY - var exactVerb = httpMethod == AllowedVerbs; + var exactVerb = string.Equals(httpMethod, AllowedVerbs, StringComparison.OrdinalIgnoreCase); score += exactVerb ? 10 : 1; return score; } + private bool StringContains(string str1, string str2) + { + return str1.IndexOf(str2, StringComparison.OrdinalIgnoreCase) != -1; + } + /// /// For performance withPathInfoParts should already be a lower case string /// to minimize redundant matching operations. @@ -259,7 +264,7 @@ namespace ServiceStack.Host wildcardMatchCount = 0; if (withPathInfoParts.Length != this.PathComponentsCount && !this.IsWildCardPath) return false; - if (!this.allowsAllVerbs && !this.allowedVerbs.Contains(httpMethod.ToUpper())) return false; + if (!this.allowsAllVerbs && !StringContains(this.allowedVerbs, httpMethod)) return false; if (!ExplodeComponents(ref withPathInfoParts)) return false; if (this.TotalComponentsCount != withPathInfoParts.Length && !this.IsWildCardPath) return false; diff --git a/ServiceStack/Host/ServiceController.cs b/ServiceStack/Host/ServiceController.cs index 7eb1253b32..378c21d5d9 100644 --- a/ServiceStack/Host/ServiceController.cs +++ b/ServiceStack/Host/ServiceController.cs @@ -83,7 +83,7 @@ namespace ServiceStack.Host } } - public readonly Dictionary> RestPathMap = new Dictionary>(); + public readonly Dictionary> RestPathMap = new Dictionary>(StringComparer.OrdinalIgnoreCase); public void RegisterRestPaths(Type requestType) { diff --git a/ServiceStack/HttpHandlerFactory.cs b/ServiceStack/HttpHandlerFactory.cs index d48bfeb5f8..5f4892d51c 100644 --- a/ServiceStack/HttpHandlerFactory.cs +++ b/ServiceStack/HttpHandlerFactory.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using MediaBrowser.Model.Logging; using MediaBrowser.Model.Services; using ServiceStack.Host; @@ -9,12 +10,16 @@ namespace ServiceStack public class HttpHandlerFactory { // Entry point for HttpListener - public static RestHandler GetHandler(IHttpRequest httpReq) + public static RestHandler GetHandler(IHttpRequest httpReq, ILogger logger) { var pathInfo = httpReq.PathInfo; var pathParts = pathInfo.TrimStart('/').Split('/'); - if (pathParts.Length == 0) return null; + if (pathParts.Length == 0) + { + logger.Error("Path parts empty for PathInfo: {0}, Url: {1}", pathInfo, httpReq.RawUrl); + return null; + } string contentType; var restPath = RestHandler.FindMatchingRestPath(httpReq.HttpMethod, pathInfo, out contentType);