using System; using System.Globalization; using System.IO; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Xml.Linq; using Emby.Dlna.Common; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; namespace Emby.Dlna.PlayTo { public class SsdpHttpClient { private const string USERAGENT = "Microsoft-Windows/6.2 UPnP/1.0 Microsoft-DLNA DLNADOC/1.50"; private const string FriendlyName = "Jellyfin"; private readonly CultureInfo _usCulture = new CultureInfo("en-US"); private readonly IHttpClient _httpClient; private readonly IServerConfigurationManager _config; public SsdpHttpClient(IHttpClient httpClient, IServerConfigurationManager config) { _httpClient = httpClient; _config = config; } public async Task SendCommandAsync( string baseUrl, DeviceService service, string command, string postData, bool logRequest = true, string header = null) { var cancellationToken = CancellationToken.None; var url = NormalizeServiceUrl(baseUrl, service.ControlUrl); using (var response = await PostSoapDataAsync( url, $"\"{service.ServiceType}#{command}\"", postData, header, logRequest, cancellationToken) .ConfigureAwait(false)) using (var stream = response.Content) using (var reader = new StreamReader(stream, Encoding.UTF8)) { return XDocument.Parse( await reader.ReadToEndAsync().ConfigureAwait(false), LoadOptions.PreserveWhitespace); } } private static string NormalizeServiceUrl(string baseUrl, string serviceUrl) { // If it's already a complete url, don't stick anything onto the front of it if (serviceUrl.StartsWith("http", StringComparison.OrdinalIgnoreCase)) { return serviceUrl; } if (!serviceUrl.StartsWith("/")) serviceUrl = "/" + serviceUrl; return baseUrl + serviceUrl; } public async Task SubscribeAsync( string url, string ip, int port, string localIp, int eventport, int timeOut = 3600) { var options = new HttpRequestOptions { Url = url, UserAgent = USERAGENT, LogErrorResponseBody = true, BufferContent = false, }; options.RequestHeaders["HOST"] = ip + ":" + port.ToString(_usCulture); options.RequestHeaders["CALLBACK"] = "<" + localIp + ":" + eventport.ToString(_usCulture) + ">"; options.RequestHeaders["NT"] = "upnp:event"; options.RequestHeaders["TIMEOUT"] = "Second-" + timeOut.ToString(_usCulture); using (await _httpClient.SendAsync(options, "SUBSCRIBE").ConfigureAwait(false)) { } } public async Task GetDataAsync(string url, CancellationToken cancellationToken) { var options = new HttpRequestOptions { Url = url, UserAgent = USERAGENT, LogErrorResponseBody = true, BufferContent = false, CancellationToken = cancellationToken }; options.RequestHeaders["FriendlyName.DLNA.ORG"] = FriendlyName; using (var response = await _httpClient.SendAsync(options, "GET").ConfigureAwait(false)) using (var stream = response.Content) using (var reader = new StreamReader(stream, Encoding.UTF8)) { return XDocument.Parse( await reader.ReadToEndAsync().ConfigureAwait(false), LoadOptions.PreserveWhitespace); } } private Task PostSoapDataAsync( string url, string soapAction, string postData, string header, bool logRequest, CancellationToken cancellationToken) { if (soapAction[0] != '\"') { soapAction = $"\"{soapAction}\""; } var options = new HttpRequestOptions { Url = url, UserAgent = USERAGENT, LogErrorResponseBody = true, BufferContent = false, CancellationToken = cancellationToken }; options.RequestHeaders["SOAPAction"] = soapAction; options.RequestHeaders["Pragma"] = "no-cache"; options.RequestHeaders["FriendlyName.DLNA.ORG"] = FriendlyName; if (!string.IsNullOrEmpty(header)) { options.RequestHeaders["contentFeatures.dlna.org"] = header; } options.RequestContentType = "text/xml"; options.RequestContent = postData; return _httpClient.Post(options); } } }