SsdpHttpClient.cs 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. #pragma warning disable CS1591
  2. using System;
  3. using System.Globalization;
  4. using System.IO;
  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. return await XDocument.LoadAsync(
  86. stream,
  87. LoadOptions.PreserveWhitespace,
  88. cancellationToken).ConfigureAwait(false);
  89. }
  90. private async Task<HttpResponseMessage> PostSoapDataAsync(
  91. string url,
  92. string soapAction,
  93. string postData,
  94. string header,
  95. CancellationToken cancellationToken)
  96. {
  97. if (soapAction[0] != '\"')
  98. {
  99. soapAction = $"\"{soapAction}\"";
  100. }
  101. using var options = new HttpRequestMessage(HttpMethod.Post, url);
  102. options.Headers.UserAgent.ParseAdd(USERAGENT);
  103. options.Headers.TryAddWithoutValidation("SOAPACTION", soapAction);
  104. options.Headers.TryAddWithoutValidation("Pragma", "no-cache");
  105. options.Headers.TryAddWithoutValidation("FriendlyName.DLNA.ORG", FriendlyName);
  106. if (!string.IsNullOrEmpty(header))
  107. {
  108. options.Headers.TryAddWithoutValidation("contentFeatures.dlna.org", header);
  109. }
  110. options.Content = new StringContent(postData, Encoding.UTF8, MediaTypeNames.Text.Xml);
  111. return await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
  112. }
  113. }
  114. }