aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Emby.Dlna/PlayTo/Device.cs33
-rw-r--r--Emby.Dlna/PlayTo/DlnaHttpClient.cs105
-rw-r--r--Emby.Dlna/PlayTo/SsdpHttpClient.cs141
-rw-r--r--Jellyfin.Server/Startup.cs17
-rw-r--r--MediaBrowser.Common/Net/NamedClient.cs5
5 files changed, 144 insertions, 157 deletions
diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs
index 8eb90f445..0b480f5ab 100644
--- a/Emby.Dlna/PlayTo/Device.cs
+++ b/Emby.Dlna/PlayTo/Device.cs
@@ -235,7 +235,7 @@ namespace Emby.Dlna.PlayTo
_logger.LogDebug("Setting mute");
var value = mute ? 1 : 0;
- await new SsdpHttpClient(_httpClientFactory)
+ await new DlnaHttpClient(_logger, _httpClientFactory)
.SendCommandAsync(
Properties.BaseUrl,
service,
@@ -276,7 +276,7 @@ namespace Emby.Dlna.PlayTo
// Remote control will perform better
Volume = value;
- await new SsdpHttpClient(_httpClientFactory)
+ await new DlnaHttpClient(_logger, _httpClientFactory)
.SendCommandAsync(
Properties.BaseUrl,
service,
@@ -303,7 +303,7 @@ namespace Emby.Dlna.PlayTo
throw new InvalidOperationException("Unable to find service");
}
- await new SsdpHttpClient(_httpClientFactory)
+ await new DlnaHttpClient(_logger, _httpClientFactory)
.SendCommandAsync(
Properties.BaseUrl,
service,
@@ -343,7 +343,7 @@ namespace Emby.Dlna.PlayTo
}
var post = avCommands.BuildPost(command, service.ServiceType, url, dictionary);
- await new SsdpHttpClient(_httpClientFactory)
+ await new DlnaHttpClient(_logger, _httpClientFactory)
.SendCommandAsync(
Properties.BaseUrl,
service,
@@ -400,7 +400,8 @@ namespace Emby.Dlna.PlayTo
}
var post = avCommands.BuildPost(command, service.ServiceType, url, dictionary);
- await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, post, header: header, cancellationToken)
+ await new DlnaHttpClient(_logger, _httpClientFactory)
+ .SendCommandAsync(Properties.BaseUrl, service, command.Name, post, header, cancellationToken)
.ConfigureAwait(false);
}
@@ -428,7 +429,7 @@ namespace Emby.Dlna.PlayTo
throw new InvalidOperationException("Unable to find service");
}
- return new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
+ return new DlnaHttpClient(_logger, _httpClientFactory).SendCommandAsync(
Properties.BaseUrl,
service,
command.Name,
@@ -461,7 +462,7 @@ namespace Emby.Dlna.PlayTo
var service = GetAvTransportService();
- await new SsdpHttpClient(_httpClientFactory)
+ await new DlnaHttpClient(_logger, _httpClientFactory)
.SendCommandAsync(
Properties.BaseUrl,
service,
@@ -485,7 +486,7 @@ namespace Emby.Dlna.PlayTo
var service = GetAvTransportService();
- await new SsdpHttpClient(_httpClientFactory)
+ await new DlnaHttpClient(_logger, _httpClientFactory)
.SendCommandAsync(
Properties.BaseUrl,
service,
@@ -618,7 +619,7 @@ namespace Emby.Dlna.PlayTo
return;
}
- var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
+ var result = await new DlnaHttpClient(_logger, _httpClientFactory).SendCommandAsync(
Properties.BaseUrl,
service,
command.Name,
@@ -668,7 +669,7 @@ namespace Emby.Dlna.PlayTo
return;
}
- var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
+ var result = await new DlnaHttpClient(_logger, _httpClientFactory).SendCommandAsync(
Properties.BaseUrl,
service,
command.Name,
@@ -701,7 +702,7 @@ namespace Emby.Dlna.PlayTo
return null;
}
- var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
+ var result = await new DlnaHttpClient(_logger, _httpClientFactory).SendCommandAsync(
Properties.BaseUrl,
service,
command.Name,
@@ -747,7 +748,7 @@ namespace Emby.Dlna.PlayTo
return null;
}
- var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
+ var result = await new DlnaHttpClient(_logger, _httpClientFactory).SendCommandAsync(
Properties.BaseUrl,
service,
command.Name,
@@ -819,7 +820,7 @@ namespace Emby.Dlna.PlayTo
return (false, null);
}
- var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
+ var result = await new DlnaHttpClient(_logger, _httpClientFactory).SendCommandAsync(
Properties.BaseUrl,
service,
command.Name,
@@ -997,7 +998,7 @@ namespace Emby.Dlna.PlayTo
string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl);
- var httpClient = new SsdpHttpClient(_httpClientFactory);
+ var httpClient = new DlnaHttpClient(_logger, _httpClientFactory);
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
if (document == null)
@@ -1029,7 +1030,7 @@ namespace Emby.Dlna.PlayTo
string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl);
- var httpClient = new SsdpHttpClient(_httpClientFactory);
+ var httpClient = new DlnaHttpClient(_logger, _httpClientFactory);
_logger.LogDebug("Dlna Device.GetRenderingProtocolAsync");
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
if (document == null)
@@ -1064,7 +1065,7 @@ namespace Emby.Dlna.PlayTo
public static async Task<Device> CreateuPnpDeviceAsync(Uri url, IHttpClientFactory httpClientFactory, ILogger logger, CancellationToken cancellationToken)
{
- var ssdpHttpClient = new SsdpHttpClient(httpClientFactory);
+ var ssdpHttpClient = new DlnaHttpClient(logger, httpClientFactory);
var document = await ssdpHttpClient.GetDataAsync(url.ToString(), cancellationToken).ConfigureAwait(false);
if (document == null)
diff --git a/Emby.Dlna/PlayTo/DlnaHttpClient.cs b/Emby.Dlna/PlayTo/DlnaHttpClient.cs
new file mode 100644
index 000000000..623f4526c
--- /dev/null
+++ b/Emby.Dlna/PlayTo/DlnaHttpClient.cs
@@ -0,0 +1,105 @@
+#pragma warning disable CS1591
+
+using System;
+using System.Globalization;
+using System.Net.Http;
+using System.Net.Mime;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Xml;
+using System.Xml.Linq;
+using Emby.Dlna.Common;
+using MediaBrowser.Common.Net;
+using Microsoft.Extensions.Logging;
+
+namespace Emby.Dlna.PlayTo
+{
+ public class DlnaHttpClient
+ {
+ private readonly ILogger _logger;
+ private readonly IHttpClientFactory _httpClientFactory;
+
+ public DlnaHttpClient(ILogger logger, IHttpClientFactory httpClientFactory)
+ {
+ _logger = logger;
+ _httpClientFactory = httpClientFactory;
+ }
+
+ 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;
+ }
+
+ private async Task<XDocument?> SendRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ using var response = await _httpClientFactory.CreateClient(NamedClient.Dlna).SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
+ response.EnsureSuccessStatusCode();
+ await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
+ try
+ {
+ return await XDocument.LoadAsync(
+ stream,
+ LoadOptions.None,
+ cancellationToken).ConfigureAwait(false);
+ }
+ catch (XmlException ex)
+ {
+ _logger.LogError(ex, "Failed to parse response");
+ if (_logger.IsEnabled(LogLevel.Debug))
+ {
+ _logger.LogDebug("Malformed response:\n", await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false));
+ }
+
+ return null;
+ }
+ }
+
+ public Task<XDocument?> GetDataAsync(string url, CancellationToken cancellationToken)
+ {
+ using var request = new HttpRequestMessage(HttpMethod.Get, url);
+ return SendRequestAsync(request, cancellationToken);
+ }
+
+ public Task<XDocument?> SendCommandAsync(
+ string baseUrl,
+ DeviceService service,
+ string command,
+ string postData,
+ string? header = null,
+ CancellationToken cancellationToken = default)
+ {
+ using var request = new HttpRequestMessage(HttpMethod.Post, NormalizeServiceUrl(baseUrl, service.ControlUrl))
+ {
+ Content = new StringContent(postData, Encoding.UTF8, MediaTypeNames.Text.Xml)
+ };
+
+ request.Headers.TryAddWithoutValidation(
+ "SOAPACTION",
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "\"{0}#{1}\"",
+ service.ServiceType,
+ command));
+ request.Headers.Pragma.ParseAdd("no-cache");
+
+ if (!string.IsNullOrEmpty(header))
+ {
+ request.Headers.TryAddWithoutValidation("contentFeatures.dlna.org", header);
+ }
+
+ return SendRequestAsync(request, cancellationToken);
+ }
+ }
+}
diff --git a/Emby.Dlna/PlayTo/SsdpHttpClient.cs b/Emby.Dlna/PlayTo/SsdpHttpClient.cs
deleted file mode 100644
index cade7b4c2..000000000
--- a/Emby.Dlna/PlayTo/SsdpHttpClient.cs
+++ /dev/null
@@ -1,141 +0,0 @@
-#nullable disable
-
-#pragma warning disable CS1591
-
-using System;
-using System.Globalization;
-using System.Net.Http;
-using System.Net.Mime;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Xml.Linq;
-using Emby.Dlna.Common;
-using MediaBrowser.Common.Net;
-
-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 IHttpClientFactory _httpClientFactory;
-
- public SsdpHttpClient(IHttpClientFactory httpClientFactory)
- {
- _httpClientFactory = httpClientFactory;
- }
-
- public async Task<XDocument> SendCommandAsync(
- string baseUrl,
- DeviceService service,
- string command,
- string postData,
- string header = null,
- CancellationToken cancellationToken = default)
- {
- var url = NormalizeServiceUrl(baseUrl, service.ControlUrl);
- using var response = await PostSoapDataAsync(
- url,
- $"\"{service.ServiceType}#{command}\"",
- postData,
- header,
- cancellationToken)
- .ConfigureAwait(false);
- response.EnsureSuccessStatusCode();
-
- await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- return await XDocument.LoadAsync(
- stream,
- LoadOptions.None,
- cancellationToken).ConfigureAwait(false);
- }
-
- 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)
- {
- using var options = new HttpRequestMessage(new HttpMethod("SUBSCRIBE"), url);
- options.Headers.UserAgent.ParseAdd(USERAGENT);
- options.Headers.TryAddWithoutValidation("HOST", ip + ":" + port.ToString(CultureInfo.InvariantCulture));
- options.Headers.TryAddWithoutValidation("CALLBACK", "<" + localIp + ":" + eventport.ToString(CultureInfo.InvariantCulture) + ">");
- options.Headers.TryAddWithoutValidation("NT", "upnp:event");
- options.Headers.TryAddWithoutValidation("TIMEOUT", "Second-" + timeOut.ToString(CultureInfo.InvariantCulture));
-
- using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
- .SendAsync(options, HttpCompletionOption.ResponseHeadersRead)
- .ConfigureAwait(false);
- response.EnsureSuccessStatusCode();
- }
-
- public async Task<XDocument> GetDataAsync(string url, CancellationToken cancellationToken)
- {
- using var options = new HttpRequestMessage(HttpMethod.Get, url);
- options.Headers.UserAgent.ParseAdd(USERAGENT);
- options.Headers.TryAddWithoutValidation("FriendlyName.DLNA.ORG", FriendlyName);
- using var response = await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
- response.EnsureSuccessStatusCode();
- await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- try
- {
- return await XDocument.LoadAsync(
- stream,
- LoadOptions.None,
- cancellationToken).ConfigureAwait(false);
- }
- catch
- {
- return null;
- }
- }
-
- private async Task<HttpResponseMessage> PostSoapDataAsync(
- string url,
- string soapAction,
- string postData,
- string header,
- CancellationToken cancellationToken)
- {
- if (soapAction[0] != '\"')
- {
- soapAction = $"\"{soapAction}\"";
- }
-
- using var options = new HttpRequestMessage(HttpMethod.Post, url);
- options.Headers.UserAgent.ParseAdd(USERAGENT);
- options.Headers.TryAddWithoutValidation("SOAPACTION", soapAction);
- options.Headers.TryAddWithoutValidation("Pragma", "no-cache");
- options.Headers.TryAddWithoutValidation("FriendlyName.DLNA.ORG", FriendlyName);
-
- if (!string.IsNullOrEmpty(header))
- {
- options.Headers.TryAddWithoutValidation("contentFeatures.dlna.org", header);
- }
-
- options.Content = new StringContent(postData, Encoding.UTF8, MediaTypeNames.Text.Xml);
-
- return await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
- }
- }
-}
diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs
index 8eb5f2196..2cb6d937a 100644
--- a/Jellyfin.Server/Startup.cs
+++ b/Jellyfin.Server/Startup.cs
@@ -1,4 +1,5 @@
using System;
+using System.Globalization;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
@@ -103,6 +104,22 @@ namespace Jellyfin.Server
})
.ConfigurePrimaryHttpMessageHandler(defaultHttpClientHandlerDelegate);
+ services.AddHttpClient(NamedClient.Dlna, c =>
+ {
+ c.DefaultRequestHeaders.UserAgent.ParseAdd(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "{0}/{1} UPnP/1.1 {2}/{3}",
+ MediaBrowser.Common.System.OperatingSystem.Name,
+ Environment.OSVersion,
+ _serverApplicationHost.Name,
+ _serverApplicationHost.ApplicationVersionString));
+
+ c.DefaultRequestHeaders.Add("CPFN.UPNP.ORG", _serverApplicationHost.FriendlyName); // Required for UPnP DeviceArchitecture v2.0
+ c.DefaultRequestHeaders.Add("FriendlyName.DLNA.ORG", _serverApplicationHost.FriendlyName); // REVIEW: where does this come from?
+ })
+ .ConfigurePrimaryHttpMessageHandler(defaultHttpClientHandlerDelegate);
+
services.AddHealthChecks()
.AddDbContextCheck<JellyfinDb>();
diff --git a/MediaBrowser.Common/Net/NamedClient.cs b/MediaBrowser.Common/Net/NamedClient.cs
index 0f6161c32..a6cacd4f1 100644
--- a/MediaBrowser.Common/Net/NamedClient.cs
+++ b/MediaBrowser.Common/Net/NamedClient.cs
@@ -14,5 +14,10 @@
/// Gets the value for the MusicBrainz named http client.
/// </summary>
public const string MusicBrainz = nameof(MusicBrainz);
+
+ /// <summary>
+ /// Gets the value for the DLNA named http client.
+ /// </summary>
+ public const string Dlna = nameof(Dlna);
}
}