BaseControlHandler.cs 8.5 KB

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