SsdpHttpClient.cs 5.3 KB

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