SsdpHttpClient.cs 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. #nullable disable
  2. #pragma warning disable CS1591
  3. using System;
  4. using System.Globalization;
  5. using System.Net.Http;
  6. using System.Net.Mime;
  7. using System.Text;
  8. using System.Threading;
  9. using System.Threading.Tasks;
  10. using System.Xml.Linq;
  11. using Emby.Dlna.Common;
  12. using MediaBrowser.Common.Net;
  13. namespace Emby.Dlna.PlayTo
  14. {
  15. public class SsdpHttpClient
  16. {
  17. private const string USERAGENT = "Microsoft-Windows/6.2 UPnP/1.0 Microsoft-DLNA DLNADOC/1.50";
  18. private const string FriendlyName = "Jellyfin";
  19. private readonly IHttpClientFactory _httpClientFactory;
  20. public SsdpHttpClient(IHttpClientFactory httpClientFactory)
  21. {
  22. _httpClientFactory = httpClientFactory;
  23. }
  24. public async Task<XDocument> SendCommandAsync(
  25. string baseUrl,
  26. DeviceService service,
  27. string command,
  28. string postData,
  29. string header = null,
  30. CancellationToken cancellationToken = default)
  31. {
  32. var url = NormalizeServiceUrl(baseUrl, service.ControlUrl);
  33. using var response = await PostSoapDataAsync(
  34. url,
  35. $"\"{service.ServiceType}#{command}\"",
  36. postData,
  37. header,
  38. cancellationToken)
  39. .ConfigureAwait(false);
  40. response.EnsureSuccessStatusCode();
  41. await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
  42. return await XDocument.LoadAsync(
  43. stream,
  44. LoadOptions.None,
  45. cancellationToken).ConfigureAwait(false);
  46. }
  47. private static string NormalizeServiceUrl(string baseUrl, string serviceUrl)
  48. {
  49. // If it's already a complete url, don't stick anything onto the front of it
  50. if (serviceUrl.StartsWith("http", StringComparison.OrdinalIgnoreCase))
  51. {
  52. return serviceUrl;
  53. }
  54. if (!serviceUrl.StartsWith('/'))
  55. {
  56. serviceUrl = "/" + serviceUrl;
  57. }
  58. return baseUrl + serviceUrl;
  59. }
  60. public async Task SubscribeAsync(
  61. string url,
  62. string ip,
  63. int port,
  64. string localIp,
  65. int eventport,
  66. int timeOut = 3600)
  67. {
  68. using var options = new HttpRequestMessage(new HttpMethod("SUBSCRIBE"), url);
  69. options.Headers.UserAgent.ParseAdd(USERAGENT);
  70. options.Headers.TryAddWithoutValidation("HOST", ip + ":" + port.ToString(CultureInfo.InvariantCulture));
  71. options.Headers.TryAddWithoutValidation("CALLBACK", "<" + localIp + ":" + eventport.ToString(CultureInfo.InvariantCulture) + ">");
  72. options.Headers.TryAddWithoutValidation("NT", "upnp:event");
  73. options.Headers.TryAddWithoutValidation("TIMEOUT", "Second-" + timeOut.ToString(CultureInfo.InvariantCulture));
  74. using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
  75. .SendAsync(options, HttpCompletionOption.ResponseHeadersRead)
  76. .ConfigureAwait(false);
  77. response.EnsureSuccessStatusCode();
  78. }
  79. public async Task<XDocument> GetDataAsync(string url, CancellationToken cancellationToken)
  80. {
  81. using var options = new HttpRequestMessage(HttpMethod.Get, url);
  82. options.Headers.UserAgent.ParseAdd(USERAGENT);
  83. options.Headers.TryAddWithoutValidation("FriendlyName.DLNA.ORG", FriendlyName);
  84. using var response = await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
  85. response.EnsureSuccessStatusCode();
  86. await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
  87. try
  88. {
  89. return await XDocument.LoadAsync(
  90. stream,
  91. LoadOptions.None,
  92. cancellationToken).ConfigureAwait(false);
  93. }
  94. catch
  95. {
  96. return null;
  97. }
  98. }
  99. private async Task<HttpResponseMessage> PostSoapDataAsync(
  100. string url,
  101. string soapAction,
  102. string postData,
  103. string header,
  104. CancellationToken cancellationToken)
  105. {
  106. if (soapAction[0] != '\"')
  107. {
  108. soapAction = $"\"{soapAction}\"";
  109. }
  110. using var options = new HttpRequestMessage(HttpMethod.Post, url);
  111. options.Headers.UserAgent.ParseAdd(USERAGENT);
  112. options.Headers.TryAddWithoutValidation("SOAPACTION", soapAction);
  113. options.Headers.TryAddWithoutValidation("Pragma", "no-cache");
  114. options.Headers.TryAddWithoutValidation("FriendlyName.DLNA.ORG", FriendlyName);
  115. if (!string.IsNullOrEmpty(header))
  116. {
  117. options.Headers.TryAddWithoutValidation("contentFeatures.dlna.org", header);
  118. }
  119. options.Content = new StringContent(postData, Encoding.UTF8, MediaTypeNames.Text.Xml);
  120. return await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
  121. }
  122. }
  123. }