BaseControlHandler.cs 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. #pragma warning disable CS1591
  2. using System;
  3. using System.Collections.Generic;
  4. using System.IO;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7. using System.Xml;
  8. using Emby.Dlna.Didl;
  9. using Jellyfin.Extensions;
  10. using MediaBrowser.Controller.Configuration;
  11. using Microsoft.Extensions.Logging;
  12. namespace Emby.Dlna.Service
  13. {
  14. public abstract class BaseControlHandler
  15. {
  16. private const string NsSoapEnv = "http://schemas.xmlsoap.org/soap/envelope/";
  17. protected BaseControlHandler(IServerConfigurationManager config, ILogger logger)
  18. {
  19. Config = config;
  20. Logger = logger;
  21. }
  22. protected IServerConfigurationManager Config { get; }
  23. protected ILogger Logger { get; }
  24. public async Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request)
  25. {
  26. try
  27. {
  28. LogRequest(request);
  29. var response = await ProcessControlRequestInternalAsync(request).ConfigureAwait(false);
  30. LogResponse(response);
  31. return response;
  32. }
  33. catch (Exception ex)
  34. {
  35. Logger.LogError(ex, "Error processing control request");
  36. return ControlErrorHandler.GetResponse(ex);
  37. }
  38. }
  39. private async Task<ControlResponse> ProcessControlRequestInternalAsync(ControlRequest request)
  40. {
  41. ControlRequestInfo requestInfo;
  42. using (var streamReader = new StreamReader(request.InputXml, Encoding.UTF8))
  43. {
  44. var readerSettings = new XmlReaderSettings()
  45. {
  46. ValidationType = ValidationType.None,
  47. CheckCharacters = false,
  48. IgnoreProcessingInstructions = true,
  49. IgnoreComments = true,
  50. Async = true
  51. };
  52. using var reader = XmlReader.Create(streamReader, readerSettings);
  53. requestInfo = await ParseRequestAsync(reader).ConfigureAwait(false);
  54. }
  55. Logger.LogDebug("Received control request {LocalName}, params: {@Headers}", requestInfo.LocalName, requestInfo.Headers);
  56. return CreateControlResponse(requestInfo);
  57. }
  58. private ControlResponse CreateControlResponse(ControlRequestInfo requestInfo)
  59. {
  60. var settings = new XmlWriterSettings
  61. {
  62. Encoding = Encoding.UTF8,
  63. CloseOutput = false
  64. };
  65. StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8);
  66. using (var writer = XmlWriter.Create(builder, settings))
  67. {
  68. writer.WriteStartDocument(true);
  69. writer.WriteStartElement("SOAP-ENV", "Envelope", NsSoapEnv);
  70. writer.WriteAttributeString(string.Empty, "encodingStyle", NsSoapEnv, "http://schemas.xmlsoap.org/soap/encoding/");
  71. writer.WriteStartElement("SOAP-ENV", "Body", NsSoapEnv);
  72. writer.WriteStartElement("u", requestInfo.LocalName + "Response", requestInfo.NamespaceURI);
  73. WriteResult(requestInfo.LocalName, requestInfo.Headers, writer);
  74. writer.WriteFullEndElement();
  75. writer.WriteFullEndElement();
  76. writer.WriteFullEndElement();
  77. writer.WriteEndDocument();
  78. }
  79. var xml = builder.ToString().Replace("xmlns:m=", "xmlns:u=", StringComparison.Ordinal);
  80. var controlResponse = new ControlResponse(xml, true);
  81. controlResponse.Headers.Add("EXT", string.Empty);
  82. return controlResponse;
  83. }
  84. private async Task<ControlRequestInfo> ParseRequestAsync(XmlReader reader)
  85. {
  86. await reader.MoveToContentAsync().ConfigureAwait(false);
  87. await reader.ReadAsync().ConfigureAwait(false);
  88. // Loop through each element
  89. while (!reader.EOF && reader.ReadState == ReadState.Interactive)
  90. {
  91. if (reader.NodeType == XmlNodeType.Element)
  92. {
  93. if (string.Equals(reader.LocalName, "Body", StringComparison.Ordinal))
  94. {
  95. if (reader.IsEmptyElement)
  96. {
  97. await reader.ReadAsync().ConfigureAwait(false);
  98. continue;
  99. }
  100. using var subReader = reader.ReadSubtree();
  101. return await ParseBodyTagAsync(subReader).ConfigureAwait(false);
  102. }
  103. await reader.SkipAsync().ConfigureAwait(false);
  104. }
  105. else
  106. {
  107. await reader.ReadAsync().ConfigureAwait(false);
  108. }
  109. }
  110. throw new EndOfStreamException("Stream ended but no body tag found.");
  111. }
  112. private async Task<ControlRequestInfo> ParseBodyTagAsync(XmlReader reader)
  113. {
  114. string? namespaceURI = null, localName = null;
  115. await reader.MoveToContentAsync().ConfigureAwait(false);
  116. await reader.ReadAsync().ConfigureAwait(false);
  117. // Loop through each element
  118. while (!reader.EOF && reader.ReadState == ReadState.Interactive)
  119. {
  120. if (reader.NodeType == XmlNodeType.Element)
  121. {
  122. localName = reader.LocalName;
  123. namespaceURI = reader.NamespaceURI;
  124. if (reader.IsEmptyElement)
  125. {
  126. await reader.ReadAsync().ConfigureAwait(false);
  127. }
  128. else
  129. {
  130. var result = new ControlRequestInfo(localName, namespaceURI);
  131. using var subReader = reader.ReadSubtree();
  132. await ParseFirstBodyChildAsync(subReader, result.Headers).ConfigureAwait(false);
  133. return result;
  134. }
  135. }
  136. else
  137. {
  138. await reader.ReadAsync().ConfigureAwait(false);
  139. }
  140. }
  141. if (localName is not null && namespaceURI is not null)
  142. {
  143. return new ControlRequestInfo(localName, namespaceURI);
  144. }
  145. throw new EndOfStreamException("Stream ended but no control found.");
  146. }
  147. private async Task ParseFirstBodyChildAsync(XmlReader reader, IDictionary<string, string> headers)
  148. {
  149. await reader.MoveToContentAsync().ConfigureAwait(false);
  150. await reader.ReadAsync().ConfigureAwait(false);
  151. // Loop through each element
  152. while (!reader.EOF && reader.ReadState == ReadState.Interactive)
  153. {
  154. if (reader.NodeType == XmlNodeType.Element)
  155. {
  156. // TODO: Should we be doing this here, or should it be handled earlier when decoding the request?
  157. headers[reader.LocalName.RemoveDiacritics()] = await reader.ReadElementContentAsStringAsync().ConfigureAwait(false);
  158. }
  159. else
  160. {
  161. await reader.ReadAsync().ConfigureAwait(false);
  162. }
  163. }
  164. }
  165. protected abstract void WriteResult(string methodName, IReadOnlyDictionary<string, string> methodParams, XmlWriter xmlWriter);
  166. private void LogRequest(ControlRequest request)
  167. {
  168. if (!Config.GetDlnaConfiguration().EnableDebugLog)
  169. {
  170. return;
  171. }
  172. Logger.LogDebug("Control request. Headers: {@Headers}", request.Headers);
  173. }
  174. private void LogResponse(ControlResponse response)
  175. {
  176. if (!Config.GetDlnaConfiguration().EnableDebugLog)
  177. {
  178. return;
  179. }
  180. Logger.LogDebug("Control response. Headers: {@Headers}\n{Xml}", response.Headers, response.Xml);
  181. }
  182. private class ControlRequestInfo
  183. {
  184. public ControlRequestInfo(string localName, string namespaceUri)
  185. {
  186. LocalName = localName;
  187. NamespaceURI = namespaceUri;
  188. Headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
  189. }
  190. public string LocalName { get; set; }
  191. public string NamespaceURI { get; set; }
  192. public Dictionary<string, string> Headers { get; }
  193. }
  194. }
  195. }