BaseControlHandler.cs 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  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 MediaBrowser.Controller.Configuration;
  10. using MediaBrowser.Controller.Extensions;
  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))
  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 {0}", requestInfo.LocalName);
  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
  77. {
  78. Xml = xml,
  79. IsSuccessful = true
  80. };
  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. switch (reader.LocalName)
  94. {
  95. case "Body":
  96. {
  97. if (!reader.IsEmptyElement)
  98. {
  99. using var subReader = reader.ReadSubtree();
  100. return await ParseBodyTagAsync(subReader).ConfigureAwait(false);
  101. }
  102. else
  103. {
  104. await reader.ReadAsync().ConfigureAwait(false);
  105. }
  106. break;
  107. }
  108. default:
  109. {
  110. await reader.SkipAsync().ConfigureAwait(false);
  111. break;
  112. }
  113. }
  114. }
  115. else
  116. {
  117. await reader.ReadAsync().ConfigureAwait(false);
  118. }
  119. }
  120. throw new EndOfStreamException("Stream ended but no body tag found.");
  121. }
  122. private async Task<ControlRequestInfo> ParseBodyTagAsync(XmlReader reader)
  123. {
  124. string namespaceURI = null, localName = null;
  125. await reader.MoveToContentAsync().ConfigureAwait(false);
  126. await reader.ReadAsync().ConfigureAwait(false);
  127. // Loop through each element
  128. while (!reader.EOF && reader.ReadState == ReadState.Interactive)
  129. {
  130. if (reader.NodeType == XmlNodeType.Element)
  131. {
  132. localName = reader.LocalName;
  133. namespaceURI = reader.NamespaceURI;
  134. if (!reader.IsEmptyElement)
  135. {
  136. var result = new ControlRequestInfo(localName, namespaceURI);
  137. using var subReader = reader.ReadSubtree();
  138. await ParseFirstBodyChildAsync(subReader, result.Headers).ConfigureAwait(false);
  139. }
  140. else
  141. {
  142. await reader.ReadAsync().ConfigureAwait(false);
  143. }
  144. }
  145. else
  146. {
  147. await reader.ReadAsync().ConfigureAwait(false);
  148. }
  149. }
  150. if (localName != null && namespaceURI != null)
  151. {
  152. return new ControlRequestInfo(localName, namespaceURI);
  153. }
  154. throw new EndOfStreamException("Stream ended but no control found.");
  155. }
  156. private async Task ParseFirstBodyChildAsync(XmlReader reader, IDictionary<string, string> headers)
  157. {
  158. await reader.MoveToContentAsync().ConfigureAwait(false);
  159. await reader.ReadAsync().ConfigureAwait(false);
  160. // Loop through each element
  161. while (!reader.EOF && reader.ReadState == ReadState.Interactive)
  162. {
  163. if (reader.NodeType == XmlNodeType.Element)
  164. {
  165. // TODO: Should we be doing this here, or should it be handled earlier when decoding the request?
  166. headers[reader.LocalName.RemoveDiacritics()] = await reader.ReadElementContentAsStringAsync().ConfigureAwait(false);
  167. }
  168. else
  169. {
  170. await reader.ReadAsync().ConfigureAwait(false);
  171. }
  172. }
  173. }
  174. protected abstract void WriteResult(string methodName, IDictionary<string, string> methodParams, XmlWriter xmlWriter);
  175. private void LogRequest(ControlRequest request)
  176. {
  177. if (!Config.GetDlnaConfiguration().EnableDebugLog)
  178. {
  179. return;
  180. }
  181. Logger.LogDebug("Control request. Headers: {@Headers}", request.Headers);
  182. }
  183. private void LogResponse(ControlResponse response)
  184. {
  185. if (!Config.GetDlnaConfiguration().EnableDebugLog)
  186. {
  187. return;
  188. }
  189. Logger.LogDebug("Control response. Headers: {@Headers}\n{Xml}", response.Headers, response.Xml);
  190. }
  191. private class ControlRequestInfo
  192. {
  193. public ControlRequestInfo(string localName, string namespaceUri)
  194. {
  195. LocalName = localName;
  196. NamespaceURI = namespaceUri;
  197. Headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
  198. }
  199. public string LocalName { get; set; }
  200. public string NamespaceURI { get; set; }
  201. public Dictionary<string, string> Headers { get; }
  202. }
  203. }
  204. }