123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242 |
- #pragma warning disable CS1591
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Text;
- using System.Threading.Tasks;
- using System.Xml;
- using Emby.Dlna.Didl;
- using Jellyfin.Extensions;
- using MediaBrowser.Controller.Configuration;
- using Microsoft.Extensions.Logging;
- namespace Emby.Dlna.Service
- {
- public abstract class BaseControlHandler
- {
- private const string NsSoapEnv = "http://schemas.xmlsoap.org/soap/envelope/";
- protected BaseControlHandler(IServerConfigurationManager config, ILogger logger)
- {
- Config = config;
- Logger = logger;
- }
- protected IServerConfigurationManager Config { get; }
- protected ILogger Logger { get; }
- public async Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request)
- {
- try
- {
- LogRequest(request);
- var response = await ProcessControlRequestInternalAsync(request).ConfigureAwait(false);
- LogResponse(response);
- return response;
- }
- catch (Exception ex)
- {
- Logger.LogError(ex, "Error processing control request");
- return ControlErrorHandler.GetResponse(ex);
- }
- }
- private async Task<ControlResponse> ProcessControlRequestInternalAsync(ControlRequest request)
- {
- ControlRequestInfo requestInfo;
- using (var streamReader = new StreamReader(request.InputXml, Encoding.UTF8))
- {
- var readerSettings = new XmlReaderSettings()
- {
- ValidationType = ValidationType.None,
- CheckCharacters = false,
- IgnoreProcessingInstructions = true,
- IgnoreComments = true,
- Async = true
- };
- using var reader = XmlReader.Create(streamReader, readerSettings);
- requestInfo = await ParseRequestAsync(reader).ConfigureAwait(false);
- }
- Logger.LogDebug("Received control request {LocalName}, params: {@Headers}", requestInfo.LocalName, requestInfo.Headers);
- return CreateControlResponse(requestInfo);
- }
- private ControlResponse CreateControlResponse(ControlRequestInfo requestInfo)
- {
- var settings = new XmlWriterSettings
- {
- Encoding = Encoding.UTF8,
- CloseOutput = false
- };
- StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8);
- using (var writer = XmlWriter.Create(builder, settings))
- {
- writer.WriteStartDocument(true);
- writer.WriteStartElement("SOAP-ENV", "Envelope", NsSoapEnv);
- writer.WriteAttributeString(string.Empty, "encodingStyle", NsSoapEnv, "http://schemas.xmlsoap.org/soap/encoding/");
- writer.WriteStartElement("SOAP-ENV", "Body", NsSoapEnv);
- writer.WriteStartElement("u", requestInfo.LocalName + "Response", requestInfo.NamespaceURI);
- WriteResult(requestInfo.LocalName, requestInfo.Headers, writer);
- writer.WriteFullEndElement();
- writer.WriteFullEndElement();
- writer.WriteFullEndElement();
- writer.WriteEndDocument();
- }
- var xml = builder.ToString().Replace("xmlns:m=", "xmlns:u=", StringComparison.Ordinal);
- var controlResponse = new ControlResponse(xml, true);
- controlResponse.Headers.Add("EXT", string.Empty);
- return controlResponse;
- }
- private async Task<ControlRequestInfo> ParseRequestAsync(XmlReader reader)
- {
- await reader.MoveToContentAsync().ConfigureAwait(false);
- await reader.ReadAsync().ConfigureAwait(false);
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
- {
- if (reader.NodeType == XmlNodeType.Element)
- {
- if (string.Equals(reader.LocalName, "Body", StringComparison.Ordinal))
- {
- if (reader.IsEmptyElement)
- {
- await reader.ReadAsync().ConfigureAwait(false);
- continue;
- }
- using var subReader = reader.ReadSubtree();
- return await ParseBodyTagAsync(subReader).ConfigureAwait(false);
- }
- await reader.SkipAsync().ConfigureAwait(false);
- }
- else
- {
- await reader.ReadAsync().ConfigureAwait(false);
- }
- }
- throw new EndOfStreamException("Stream ended but no body tag found.");
- }
- private async Task<ControlRequestInfo> ParseBodyTagAsync(XmlReader reader)
- {
- string? namespaceURI = null, localName = null;
- await reader.MoveToContentAsync().ConfigureAwait(false);
- await reader.ReadAsync().ConfigureAwait(false);
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
- {
- if (reader.NodeType == XmlNodeType.Element)
- {
- localName = reader.LocalName;
- namespaceURI = reader.NamespaceURI;
- if (reader.IsEmptyElement)
- {
- await reader.ReadAsync().ConfigureAwait(false);
- }
- else
- {
- var result = new ControlRequestInfo(localName, namespaceURI);
- using var subReader = reader.ReadSubtree();
- await ParseFirstBodyChildAsync(subReader, result.Headers).ConfigureAwait(false);
- return result;
- }
- }
- else
- {
- await reader.ReadAsync().ConfigureAwait(false);
- }
- }
- if (localName is not null && namespaceURI is not null)
- {
- return new ControlRequestInfo(localName, namespaceURI);
- }
- throw new EndOfStreamException("Stream ended but no control found.");
- }
- private async Task ParseFirstBodyChildAsync(XmlReader reader, IDictionary<string, string> headers)
- {
- await reader.MoveToContentAsync().ConfigureAwait(false);
- await reader.ReadAsync().ConfigureAwait(false);
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
- {
- if (reader.NodeType == XmlNodeType.Element)
- {
- // TODO: Should we be doing this here, or should it be handled earlier when decoding the request?
- headers[reader.LocalName.RemoveDiacritics()] = await reader.ReadElementContentAsStringAsync().ConfigureAwait(false);
- }
- else
- {
- await reader.ReadAsync().ConfigureAwait(false);
- }
- }
- }
- protected abstract void WriteResult(string methodName, IReadOnlyDictionary<string, string> methodParams, XmlWriter xmlWriter);
- private void LogRequest(ControlRequest request)
- {
- if (!Config.GetDlnaConfiguration().EnableDebugLog)
- {
- return;
- }
- Logger.LogDebug("Control request. Headers: {@Headers}", request.Headers);
- }
- private void LogResponse(ControlResponse response)
- {
- if (!Config.GetDlnaConfiguration().EnableDebugLog)
- {
- return;
- }
- Logger.LogDebug("Control response. Headers: {@Headers}\n{Xml}", response.Headers, response.Xml);
- }
- private class ControlRequestInfo
- {
- public ControlRequestInfo(string localName, string namespaceUri)
- {
- LocalName = localName;
- NamespaceURI = namespaceUri;
- Headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
- }
- public string LocalName { get; set; }
- public string NamespaceURI { get; set; }
- public Dictionary<string, string> Headers { get; }
- }
- }
- }
|