SsdpHttpClient.cs 5.3 KB

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