diff --git a/UpnpSharp/HttpPacketParser.cs b/UpnpSharp/HttpPacketParser.cs index 9664c22..4b1a9ce 100644 --- a/UpnpSharp/HttpPacketParser.cs +++ b/UpnpSharp/HttpPacketParser.cs @@ -1,11 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection.PortableExecutable; -using System.Text; -using System.Threading.Tasks; - -namespace UpnpSharp +namespace UpnpSharp { public class HttpPacketParser { diff --git a/UpnpSharp/Ssdp/Ssdp.cs b/UpnpSharp/Ssdp/Ssdp.cs deleted file mode 100644 index 512de31..0000000 --- a/UpnpSharp/Ssdp/Ssdp.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace UpnpSharp.Ssdp -{ - public class Ssdp - { - - } -} diff --git a/UpnpSharp/Ssdp/SsdpDevice.cs b/UpnpSharp/Ssdp/SsdpDevice.cs index 9cf2adf..a17503f 100644 --- a/UpnpSharp/Ssdp/SsdpDevice.cs +++ b/UpnpSharp/Ssdp/SsdpDevice.cs @@ -1,9 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; +using System.Net; using System.Text; -using System.Threading.Tasks; using System.Xml; using System.Xml.Serialization; @@ -24,7 +20,7 @@ public class SsdpDevice return this.SsdpDeviceInfo?.Device.GetServices(); } - public string? BaseUrl => this.SsdpDeviceInfo?.Device.UrlBase != null ? this.SsdpDeviceInfo.Device.UrlBase : this.parser["Location"]; + public string? BaseUrl => string.IsNullOrWhiteSpace(this.SsdpDeviceInfo?.Device.UrlBase) ? this.parser["Location"] : this.SsdpDeviceInfo?.Device.UrlBase; string response; protected HttpPacketParser parser; @@ -39,6 +35,7 @@ public SsdpDevice(IPEndPoint address, string message) this.GetDescription(this.parser["Location"]).Wait(); this.ParseXml(); + this.SsdpDeviceInfo.Device.GetServices().Select(service => new SsdpService(BaseUrl, service)).ToList(); } public async Task GetDescription(string? url) diff --git a/UpnpSharp/Ssdp/SsdpRequest.cs b/UpnpSharp/Ssdp/SsdpRequest.cs index e209896..485111a 100644 --- a/UpnpSharp/Ssdp/SsdpRequest.cs +++ b/UpnpSharp/Ssdp/SsdpRequest.cs @@ -1,11 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Net; +using System.Net; using System.Net.Sockets; using System.Text; -using System.Threading.Tasks; namespace UpnpSharp.Ssdp { diff --git a/UpnpSharp/Ssdp/SsdpService.cs b/UpnpSharp/Ssdp/SsdpService.cs new file mode 100644 index 0000000..74f2884 --- /dev/null +++ b/UpnpSharp/Ssdp/SsdpService.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UpnpSharp.Utils; + +namespace UpnpSharp.Ssdp +{ + public class SsdpService + { + string baseUrl, scpdUrl; + protected SsdpDeviceService serviceXml; + + public string Type => this.serviceXml.ServiceType; + public string Id => this.serviceXml.ServiceId; + public string ScpdUrl => scpdUrl; + public string ControlUrl => this.serviceXml.ControlUrl; + public string EventSubUrl => this.serviceXml.EventSubUrl; + public string BaseUrl => baseUrl; + public string? Description { get; protected set; } + public Dictionary Actions { get; protected set; } = new(); + + public SsdpService(string baseUrl, SsdpDeviceService service) + { + this.serviceXml = service; + + this.baseUrl = baseUrl; + this.scpdUrl = service.ScpdUrl; + + UrlParser baseUri = new UrlParser(baseUrl); + UrlParser scpdUri = new UrlParser(scpdUrl); + if (baseUri.Scheme != Uri.UriSchemeHttp) + { + throw new Exception($"Unsupported url scheme: {baseUri.Scheme}"); + } + + if (scpdUri.Scheme == Uri.UriSchemeHttp) + { + if (scpdUri.Host != baseUri.Host) throw new Exception($"Host doesn't match: \"{scpdUri.Host}\" and \"{baseUri.Host}\""); + } + else if (string.IsNullOrWhiteSpace(scpdUri.Scheme)) + { + if (scpdUrl.StartsWith('/')) this.scpdUrl = $"{baseUri.Scheme}://{baseUri.Host}:{baseUri.Port}{scpdUrl}"; + else this.scpdUrl = $"{baseUri.Scheme}://{baseUri.Host}:{baseUri.Port}/{scpdUri.ToString()}"; + } + else throw new Exception($"Unsupported url scheme: {scpdUri.Scheme}"); + + RequestDescription().Wait(); + RequestStateVariables(); + RequestActions(); + } + + private void RequestActions() + { + Console.WriteLine(this.Description); + } + + private void RequestStateVariables() + { + return; + } + + public async Task RequestDescription() + { + HttpClient client = new HttpClient(); + var response = await client.GetAsync(this.ScpdUrl); + if (!response.IsSuccessStatusCode) + { + this.Description = null; + throw new Exception($"Unsuccessful request: 2xx or 3xx status code expected, got {response.StatusCode}."); + } + this.Description = await response.Content.ReadAsStringAsync(); + } + } +} diff --git a/UpnpSharp/Ssdp/SsdpXml.cs b/UpnpSharp/Ssdp/SsdpXml.cs index 50f923d..7137618 100644 --- a/UpnpSharp/Ssdp/SsdpXml.cs +++ b/UpnpSharp/Ssdp/SsdpXml.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Xml.Serialization; +using System.Xml.Serialization; namespace UpnpSharp.Ssdp { @@ -44,7 +39,7 @@ public partial class SsdpDeviceXml public string ModelName { get; set; } = string.Empty; [XmlElement(ElementName = "modelNumber")] - public byte ModelNumber { get; set; } + public string ModelNumber { get; set; } = string.Empty; [XmlElement(ElementName = "modelURL")] public string ModelUrl { get; set; } = string.Empty; @@ -59,10 +54,10 @@ public partial class SsdpDeviceXml public string UrlBase { get; set; } = string.Empty; [XmlElement(ElementName = "UPC")] - public byte Upc { get; set; } + public string Upc { get; set; } [XmlElement(ElementName = "serviceList")] - public SsdpDeviceServiceXml ServiceList { get; set; } = new(); + public SsdpDeviceServiceList ServiceList { get; set; } = new(); /// [XmlElement(ElementName = "deviceList")] @@ -79,7 +74,7 @@ public IEnumerable GetServices() public IEnumerable GetServices(SsdpDeviceXml? xml) { var thisService = xml?.ServiceList.Service; - if (!string.IsNullOrWhiteSpace(thisService?.ServiceType)) yield return thisService; + if (!string.IsNullOrWhiteSpace(thisService?.ServiceType)) yield return thisService; SsdpDeviceXml[]? subDevices = xml?.DeviceList.Devices; if (subDevices != null) { @@ -109,7 +104,7 @@ public class SsdpDevices [Serializable] [System.ComponentModel.DesignerCategory("code")] [XmlType(AnonymousType = true, Namespace = "urn:schemas-upnp-org:device-1-0")] - public partial class SsdpDeviceServiceXml + public partial class SsdpDeviceServiceList { [XmlElement(ElementName = "service")] public SsdpDeviceService Service { get; set; } = new SsdpDeviceService(); diff --git a/UpnpSharp/Upnp/Upnp.cs b/UpnpSharp/Upnp/Upnp.cs index fa808c9..53fcb83 100644 --- a/UpnpSharp/Upnp/Upnp.cs +++ b/UpnpSharp/Upnp/Upnp.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection.Metadata; -using System.Text; -using System.Threading.Tasks; -using UpnpSharp.Ssdp; +using UpnpSharp.Ssdp; namespace UpnpSharp.Upnp { @@ -32,7 +26,7 @@ public IEnumerable Discover(int delay = 2000) yield return device; } } - + public SsdpDevice? GetIgd() { List igds = new List(); diff --git a/UpnpSharp/Utils.cs b/UpnpSharp/Utils.cs deleted file mode 100644 index e1ed4fd..0000000 --- a/UpnpSharp/Utils.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace UpnpSharp -{ - internal class Utils - { - - } -} diff --git a/UpnpSharp/Utils/UrlParser.cs b/UpnpSharp/Utils/UrlParser.cs new file mode 100644 index 0000000..1378b88 --- /dev/null +++ b/UpnpSharp/Utils/UrlParser.cs @@ -0,0 +1,62 @@ +using System; +using System.Text.RegularExpressions; + +namespace UpnpSharp.Utils +{ + public class UrlParser + { + public string Scheme { get; protected set; } = string.Empty; + public string Host { get; protected set; } = string.Empty; + public ushort Port { get; protected set; } = 0; + public string Path { get; protected set; } = string.Empty; + + public UrlParser(string uri) + { + if (string.IsNullOrEmpty(uri)) + throw new ArgumentNullException(nameof(uri), "The URI cannot be null or empty."); + + ParseUri(uri); + } + + private void ParseUri(string uri) + { + var schemeMatch = Regex.Match(uri, @"^(?[a-zA-Z][a-zA-Z0-9+.-]*):"); + if (schemeMatch.Success) + { + Scheme = schemeMatch.Groups["scheme"].Value; + uri = uri.Substring(Scheme.Length + 1); // Remove scheme from uri + } + + var authorityMatch = Regex.Match(uri, @"^//(?[^/]+)"); + if (authorityMatch.Success) + { + var authority = authorityMatch.Groups["authority"].Value; + uri = uri.Substring(authority.Length + 2); // Remove authority from uri + + var hostPortMatch = Regex.Match(authority, @"^(?[^:]+)(:(?\d+))?$"); + if (hostPortMatch.Success) + { + Host = hostPortMatch.Groups["host"].Value; + Port = hostPortMatch.Groups["port"].Success ? ushort.Parse(hostPortMatch.Groups["port"].Value) : GetDefaultPort(Scheme); + } + } + + Path = uri; + } + + private ushort GetDefaultPort(string scheme) + { + switch (scheme?.ToLower()) + { + case "http": + return 80; + case "https": + return 443; + case "ftp": + return 21; + default: + return 0; + } + } + } +}