SsdpHttpClient.cs 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  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 CultureInfo _usCulture = new CultureInfo("en-US");
  20. private readonly IHttpClientFactory _httpClientFactory;
  21. public SsdpHttpClient(IHttpClientFactory httpClientFactory)
  22. {
  23. _httpClientFactory = httpClientFactory;
  24. }
  25. public async Task<XDocument> SendCommandAsync(
  26. string baseUrl,
  27. DeviceService service,
  28. string command,
  29. string postData,
  30. string header = null,
  31. CancellationToken cancellationToken = default)
  32. {
  33. var url = NormalizeServiceUrl(baseUrl, service.ControlUrl);
  34. using var response = await PostSoapDataAsync(
  35. url,
  36. $"\"{service.ServiceType}#{command}\"",
  37. postData,
  38. header,
  39. cancellationToken)
  40. .ConfigureAwait(false);
  41. await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
  42. return await XDocument.LoadAsync(
  43. stream,
  44. LoadOptions.PreserveWhitespace,
  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(_usCulture));
  71. options.Headers.TryAddWithoutValidation("CALLBACK", "<" + localIp + ":" + eventport.ToString(_usCulture) + ">");
  72. options.Headers.TryAddWithoutValidation("NT", "upnp:event");
  73. options.Headers.TryAddWithoutValidation("TIMEOUT", "Second-" + timeOut.ToString(_usCulture));
  74. using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
  75. .SendAsync(options, HttpCompletionOption.ResponseHeadersRead)
  76. .ConfigureAwait(false);
  77. }
  78. public async Task<XDocument> GetDataAsync(string url, CancellationToken cancellationToken)
  79. {
  80. using var options = new HttpRequestMessage(HttpMethod.Get, url);
  81. options.Headers.UserAgent.ParseAdd(USERAGENT);
  82. options.Headers.TryAddWithoutValidation("FriendlyName.DLNA.ORG", FriendlyName);
  83. using var response = await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
  84. await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
  85. try
  86. {
  87. return await XDocument.LoadAsync(
  88. stream,
  89. LoadOptions.PreserveWhitespace,
  90. cancellationToken).ConfigureAwait(false);
  91. }
  92. catch
  93. {
  94. return null;
  95. }
  96. }
  97. private async Task<HttpResponseMessage> PostSoapDataAsync(
  98. string url,
  99. string soapAction,
  100. string postData,
  101. string header,
  102. CancellationToken cancellationToken)
  103. {
  104. if (soapAction[0] != '\"')
  105. {
  106. soapAction = $"\"{soapAction}\"";
  107. }
  108. using var options = new HttpRequestMessage(HttpMethod.Post, url);
  109. options.Headers.UserAgent.ParseAdd(USERAGENT);
  110. options.Headers.TryAddWithoutValidation("SOAPACTION", soapAction);
  111. options.Headers.TryAddWithoutValidation("Pragma", "no-cache");
  112. options.Headers.TryAddWithoutValidation("FriendlyName.DLNA.ORG", FriendlyName);
  113. if (!string.IsNullOrEmpty(header))
  114. {
  115. options.Headers.TryAddWithoutValidation("contentFeatures.dlna.org", header);
  116. }
  117. options.Content = new StringContent(postData, Encoding.UTF8, MediaTypeNames.Text.Xml);
  118. return await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
  119. }
  120. }
  121. }