BaseControlHandler.cs 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  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 Diacritics.Extensions;
  9. using Emby.Dlna.Didl;
  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 = null;
  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. var settings = new XmlWriterSettings
  57. {
  58. Encoding = Encoding.UTF8,
  59. CloseOutput = false
  60. };
  61. StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8);
  62. using (var writer = XmlWriter.Create(builder, settings))
  63. {
  64. writer.WriteStartDocument(true);
  65. writer.WriteStartElement("SOAP-ENV", "Envelope", NsSoapEnv);
  66. writer.WriteAttributeString(string.Empty, "encodingStyle", NsSoapEnv, "http://schemas.xmlsoap.org/soap/encoding/");
  67. writer.WriteStartElement("SOAP-ENV", "Body", NsSoapEnv);
  68. writer.WriteStartElement("u", requestInfo.LocalName + "Response", requestInfo.NamespaceURI);
  69. WriteResult(requestInfo.LocalName, requestInfo.Headers, writer);
  70. writer.WriteFullEndElement();
  71. writer.WriteFullEndElement();
  72. writer.WriteFullEndElement();
  73. writer.WriteEndDocument();
  74. }
  75. var xml = builder.ToString().Replace("xmlns:m=", "xmlns:u=", StringComparison.Ordinal);
  76. var controlResponse = new ControlResponse(xml, true);
  77. controlResponse.Headers.Add("EXT", string.Empty);
  78. return controlResponse;
  79. }
  80. private async Task<ControlRequestInfo> ParseRequestAsync(XmlReader reader)
  81. {
  82. await reader.MoveToContentAsync().ConfigureAwait(false);
  83. await reader.ReadAsync().ConfigureAwait(false);
  84. // Loop through each element
  85. while (!reader.EOF && reader.ReadState == ReadState.Interactive)
  86. {
  87. if (reader.NodeType == XmlNodeType.Element)
  88. {
  89. switch (reader.LocalName)
  90. {
  91. case "Body":
  92. {
  93. if (!reader.IsEmptyElement)
  94. {
  95. using var subReader = reader.ReadSubtree();
  96. return await ParseBodyTagAsync(subReader).ConfigureAwait(false);
  97. }
  98. else
  99. {
  100. await reader.ReadAsync().ConfigureAwait(false);
  101. }
  102. break;
  103. }
  104. default:
  105. {
  106. await reader.SkipAsync().ConfigureAwait(false);
  107. break;
  108. }
  109. }
  110. }
  111. else
  112. {
  113. await reader.ReadAsync().ConfigureAwait(false);
  114. }
  115. }
  116. throw new EndOfStreamException("Stream ended but no body tag found.");
  117. }
  118. private async Task<ControlRequestInfo> ParseBodyTagAsync(XmlReader reader)
  119. {
  120. string? namespaceURI = null, localName = null;
  121. await reader.MoveToContentAsync().ConfigureAwait(false);
  122. await reader.ReadAsync().ConfigureAwait(false);
  123. // Loop through each element
  124. while (!reader.EOF && reader.ReadState == ReadState.Interactive)
  125. {
  126. if (reader.NodeType == XmlNodeType.Element)
  127. {
  128. localName = reader.LocalName;
  129. namespaceURI = reader.NamespaceURI;
  130. if (!reader.IsEmptyElement)
  131. {
  132. var result = new ControlRequestInfo(localName, namespaceURI);
  133. using var subReader = reader.ReadSubtree();
  134. await ParseFirstBodyChildAsync(subReader, result.Headers).ConfigureAwait(false);
  135. return result;
  136. }
  137. else
  138. {
  139. await reader.ReadAsync().ConfigureAwait(false);
  140. }
  141. }
  142. else
  143. {
  144. await reader.ReadAsync().ConfigureAwait(false);
  145. }
  146. }
  147. if (localName != null && namespaceURI != null)
  148. {
  149. return new ControlRequestInfo(localName, namespaceURI);
  150. }
  151. throw new EndOfStreamException("Stream ended but no control found.");
  152. }
  153. private async Task ParseFirstBodyChildAsync(XmlReader reader, IDictionary<string, string> headers)
  154. {
  155. await reader.MoveToContentAsync().ConfigureAwait(false);
  156. await reader.ReadAsync().ConfigureAwait(false);
  157. // Loop through each element
  158. while (!reader.EOF && reader.ReadState == ReadState.Interactive)
  159. {
  160. if (reader.NodeType == XmlNodeType.Element)
  161. {
  162. // TODO: Should we be doing this here, or should it be handled earlier when decoding the request?
  163. headers[reader.LocalName.RemoveDiacritics()] = await reader.ReadElementContentAsStringAsync().ConfigureAwait(false);
  164. }
  165. else
  166. {
  167. await reader.ReadAsync().ConfigureAwait(false);
  168. }
  169. }
  170. }
  171. protected abstract void WriteResult(string methodName, IReadOnlyDictionary<string, string> methodParams, XmlWriter xmlWriter);
  172. private void LogRequest(ControlRequest request)
  173. {
  174. if (!Config.GetDlnaConfiguration().EnableDebugLog)
  175. {
  176. return;
  177. }
  178. Logger.LogDebug("Control request. Headers: {@Headers}", request.Headers);
  179. }
  180. private void LogResponse(ControlResponse response)
  181. {
  182. if (!Config.GetDlnaConfiguration().EnableDebugLog)
  183. {
  184. return;
  185. }
  186. Logger.LogDebug("Control response. Headers: {@Headers}\n{Xml}", response.Headers, response.Xml);
  187. }
  188. private class ControlRequestInfo
  189. {
  190. public ControlRequestInfo(string localName, string namespaceUri)
  191. {
  192. LocalName = localName;
  193. NamespaceURI = namespaceUri;
  194. Headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
  195. }
  196. public string LocalName { get; set; }
  197. public string NamespaceURI { get; set; }
  198. public Dictionary<string, string> Headers { get; }
  199. }
  200. }
  201. }