2020-01-22 21:00:07 +01:00
|
|
|
#pragma warning disable CS1591
|
|
|
|
|
2019-01-13 20:54:44 +01:00
|
|
|
using System;
|
2016-10-30 00:22:20 +02:00
|
|
|
using System.Collections.Generic;
|
2016-11-04 09:31:05 +01:00
|
|
|
using System.IO;
|
2016-10-30 00:22:20 +02:00
|
|
|
using System.Text;
|
2020-01-21 17:59:41 +01:00
|
|
|
using System.Threading.Tasks;
|
2016-10-30 00:22:20 +02:00
|
|
|
using System.Xml;
|
2021-06-12 11:22:26 +02:00
|
|
|
using Diacritics.Extensions;
|
2016-11-04 09:31:05 +01:00
|
|
|
using Emby.Dlna.Didl;
|
2019-01-13 20:16:19 +01:00
|
|
|
using MediaBrowser.Controller.Configuration;
|
|
|
|
using Microsoft.Extensions.Logging;
|
2016-10-30 00:22:20 +02:00
|
|
|
|
2016-10-30 00:34:54 +02:00
|
|
|
namespace Emby.Dlna.Service
|
2016-10-30 00:22:20 +02:00
|
|
|
{
|
|
|
|
public abstract class BaseControlHandler
|
|
|
|
{
|
2020-08-20 17:59:27 +02:00
|
|
|
private const string NsSoapEnv = "http://schemas.xmlsoap.org/soap/envelope/";
|
2016-10-30 00:22:20 +02:00
|
|
|
|
2019-02-01 17:43:31 +01:00
|
|
|
protected BaseControlHandler(IServerConfigurationManager config, ILogger logger)
|
2016-10-30 00:22:20 +02:00
|
|
|
{
|
|
|
|
Config = config;
|
2020-01-21 17:59:41 +01:00
|
|
|
Logger = logger;
|
2016-10-30 00:22:20 +02:00
|
|
|
}
|
|
|
|
|
2020-08-20 17:01:04 +02:00
|
|
|
protected IServerConfigurationManager Config { get; }
|
|
|
|
|
|
|
|
protected ILogger Logger { get; }
|
|
|
|
|
2020-01-21 17:59:41 +01:00
|
|
|
public async Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request)
|
2016-10-30 00:22:20 +02:00
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
2020-01-21 17:59:41 +01:00
|
|
|
LogRequest(request);
|
2016-10-30 00:22:20 +02:00
|
|
|
|
2020-01-21 17:59:41 +01:00
|
|
|
var response = await ProcessControlRequestInternalAsync(request).ConfigureAwait(false);
|
|
|
|
LogResponse(response);
|
2016-10-30 00:22:20 +02:00
|
|
|
return response;
|
|
|
|
}
|
|
|
|
catch (Exception ex)
|
|
|
|
{
|
2020-01-21 17:59:41 +01:00
|
|
|
Logger.LogError(ex, "Error processing control request");
|
2016-10-30 00:22:20 +02:00
|
|
|
|
2020-01-21 17:59:41 +01:00
|
|
|
return ControlErrorHandler.GetResponse(ex);
|
2016-10-30 00:22:20 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-21 17:59:41 +01:00
|
|
|
private async Task<ControlResponse> ProcessControlRequestInternalAsync(ControlRequest request)
|
2016-10-30 00:22:20 +02:00
|
|
|
{
|
2022-01-07 11:48:59 +01:00
|
|
|
ControlRequestInfo requestInfo;
|
2016-10-30 00:22:20 +02:00
|
|
|
|
2020-12-30 18:31:26 +01:00
|
|
|
using (var streamReader = new StreamReader(request.InputXml, Encoding.UTF8))
|
2016-10-30 00:22:20 +02:00
|
|
|
{
|
2019-02-01 17:43:31 +01:00
|
|
|
var readerSettings = new XmlReaderSettings()
|
|
|
|
{
|
|
|
|
ValidationType = ValidationType.None,
|
|
|
|
CheckCharacters = false,
|
|
|
|
IgnoreProcessingInstructions = true,
|
2020-01-18 00:09:25 +01:00
|
|
|
IgnoreComments = true,
|
|
|
|
Async = true
|
2019-02-01 17:43:31 +01:00
|
|
|
};
|
2016-11-04 09:31:05 +01:00
|
|
|
|
2020-09-13 15:36:10 +02:00
|
|
|
using var reader = XmlReader.Create(streamReader, readerSettings);
|
|
|
|
requestInfo = await ParseRequestAsync(reader).ConfigureAwait(false);
|
2016-10-30 00:22:20 +02:00
|
|
|
}
|
|
|
|
|
2021-11-08 09:24:33 +01:00
|
|
|
Logger.LogDebug("Received control request {LocalName}, params: {@Headers}", requestInfo.LocalName, requestInfo.Headers);
|
2016-10-30 00:22:20 +02:00
|
|
|
|
2022-01-07 11:48:59 +01:00
|
|
|
return CreateControlResponse(requestInfo);
|
|
|
|
}
|
|
|
|
|
|
|
|
private ControlResponse CreateControlResponse(ControlRequestInfo requestInfo)
|
|
|
|
{
|
2016-11-04 09:31:05 +01:00
|
|
|
var settings = new XmlWriterSettings
|
|
|
|
{
|
|
|
|
Encoding = Encoding.UTF8,
|
2020-01-28 17:50:25 +01:00
|
|
|
CloseOutput = false
|
2016-11-04 09:31:05 +01:00
|
|
|
};
|
2016-10-30 00:22:20 +02:00
|
|
|
|
2016-11-04 09:31:05 +01:00
|
|
|
StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8);
|
2016-10-30 00:22:20 +02:00
|
|
|
|
2019-01-13 21:37:13 +01:00
|
|
|
using (var writer = XmlWriter.Create(builder, settings))
|
2016-10-30 00:22:20 +02:00
|
|
|
{
|
2016-11-04 09:31:05 +01:00
|
|
|
writer.WriteStartDocument(true);
|
|
|
|
|
2020-08-20 17:59:27 +02:00
|
|
|
writer.WriteStartElement("SOAP-ENV", "Envelope", NsSoapEnv);
|
|
|
|
writer.WriteAttributeString(string.Empty, "encodingStyle", NsSoapEnv, "http://schemas.xmlsoap.org/soap/encoding/");
|
2016-11-04 09:31:05 +01:00
|
|
|
|
2020-08-20 17:59:27 +02:00
|
|
|
writer.WriteStartElement("SOAP-ENV", "Body", NsSoapEnv);
|
2016-11-04 09:31:05 +01:00
|
|
|
writer.WriteStartElement("u", requestInfo.LocalName + "Response", requestInfo.NamespaceURI);
|
2020-01-22 21:00:07 +01:00
|
|
|
|
|
|
|
WriteResult(requestInfo.LocalName, requestInfo.Headers, writer);
|
|
|
|
|
2016-11-06 18:30:44 +01:00
|
|
|
writer.WriteFullEndElement();
|
|
|
|
writer.WriteFullEndElement();
|
2016-11-04 09:31:05 +01:00
|
|
|
|
2016-11-06 18:30:44 +01:00
|
|
|
writer.WriteFullEndElement();
|
2016-11-04 09:31:05 +01:00
|
|
|
writer.WriteEndDocument();
|
2016-10-30 00:22:20 +02:00
|
|
|
}
|
|
|
|
|
2020-01-27 23:34:40 +01:00
|
|
|
var xml = builder.ToString().Replace("xmlns:m=", "xmlns:u=", StringComparison.Ordinal);
|
2016-12-04 00:57:34 +01:00
|
|
|
|
2021-07-26 23:02:32 +02:00
|
|
|
var controlResponse = new ControlResponse(xml, true);
|
2016-10-30 00:22:20 +02:00
|
|
|
|
|
|
|
controlResponse.Headers.Add("EXT", string.Empty);
|
|
|
|
|
|
|
|
return controlResponse;
|
|
|
|
}
|
|
|
|
|
2020-01-21 17:59:41 +01:00
|
|
|
private async Task<ControlRequestInfo> ParseRequestAsync(XmlReader reader)
|
2016-11-04 09:31:05 +01:00
|
|
|
{
|
2020-01-21 17:59:41 +01:00
|
|
|
await reader.MoveToContentAsync().ConfigureAwait(false);
|
|
|
|
await reader.ReadAsync().ConfigureAwait(false);
|
2016-11-04 09:31:05 +01:00
|
|
|
|
|
|
|
// Loop through each element
|
2016-12-03 22:46:06 +01:00
|
|
|
while (!reader.EOF && reader.ReadState == ReadState.Interactive)
|
2016-11-04 09:31:05 +01:00
|
|
|
{
|
|
|
|
if (reader.NodeType == XmlNodeType.Element)
|
|
|
|
{
|
2022-01-07 11:48:59 +01:00
|
|
|
if (string.Equals(reader.LocalName, "Body", StringComparison.Ordinal))
|
2016-11-04 09:31:05 +01:00
|
|
|
{
|
2022-01-07 11:48:59 +01:00
|
|
|
if (reader.IsEmptyElement)
|
|
|
|
{
|
|
|
|
await reader.ReadAsync().ConfigureAwait(false);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
using var subReader = reader.ReadSubtree();
|
|
|
|
return await ParseBodyTagAsync(subReader).ConfigureAwait(false);
|
2016-11-04 09:31:05 +01:00
|
|
|
}
|
2022-01-07 11:48:59 +01:00
|
|
|
|
|
|
|
await reader.SkipAsync().ConfigureAwait(false);
|
2016-11-04 09:31:05 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-01-21 17:59:41 +01:00
|
|
|
await reader.ReadAsync().ConfigureAwait(false);
|
2016-11-04 09:31:05 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-25 19:40:10 +02:00
|
|
|
throw new EndOfStreamException("Stream ended but no body tag found.");
|
2016-11-04 09:31:05 +01:00
|
|
|
}
|
|
|
|
|
2020-01-21 17:59:41 +01:00
|
|
|
private async Task<ControlRequestInfo> ParseBodyTagAsync(XmlReader reader)
|
2016-11-04 09:31:05 +01:00
|
|
|
{
|
2021-05-07 14:43:50 +02:00
|
|
|
string? namespaceURI = null, localName = null;
|
2016-11-04 09:31:05 +01:00
|
|
|
|
2020-01-21 17:59:41 +01:00
|
|
|
await reader.MoveToContentAsync().ConfigureAwait(false);
|
|
|
|
await reader.ReadAsync().ConfigureAwait(false);
|
2016-11-04 09:31:05 +01:00
|
|
|
|
|
|
|
// Loop through each element
|
2016-12-03 22:46:06 +01:00
|
|
|
while (!reader.EOF && reader.ReadState == ReadState.Interactive)
|
2016-11-04 09:31:05 +01:00
|
|
|
{
|
|
|
|
if (reader.NodeType == XmlNodeType.Element)
|
|
|
|
{
|
2020-09-25 20:44:16 +02:00
|
|
|
localName = reader.LocalName;
|
|
|
|
namespaceURI = reader.NamespaceURI;
|
2016-11-04 09:31:05 +01:00
|
|
|
|
2022-01-07 11:48:59 +01:00
|
|
|
if (reader.IsEmptyElement)
|
|
|
|
{
|
|
|
|
await reader.ReadAsync().ConfigureAwait(false);
|
|
|
|
}
|
|
|
|
else
|
2016-11-04 09:31:05 +01:00
|
|
|
{
|
2020-09-25 20:44:16 +02:00
|
|
|
var result = new ControlRequestInfo(localName, namespaceURI);
|
2020-09-13 15:36:10 +02:00
|
|
|
using var subReader = reader.ReadSubtree();
|
|
|
|
await ParseFirstBodyChildAsync(subReader, result.Headers).ConfigureAwait(false);
|
2020-11-07 19:59:32 +01:00
|
|
|
return result;
|
2016-12-04 00:57:34 +01:00
|
|
|
}
|
2016-11-04 09:31:05 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-01-21 17:59:41 +01:00
|
|
|
await reader.ReadAsync().ConfigureAwait(false);
|
2016-11-04 09:31:05 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-25 20:51:37 +02:00
|
|
|
if (localName != null && namespaceURI != null)
|
2020-09-25 20:44:16 +02:00
|
|
|
{
|
|
|
|
return new ControlRequestInfo(localName, namespaceURI);
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new EndOfStreamException("Stream ended but no control found.");
|
2016-11-04 09:31:05 +01:00
|
|
|
}
|
|
|
|
|
2020-01-21 17:59:41 +01:00
|
|
|
private async Task ParseFirstBodyChildAsync(XmlReader reader, IDictionary<string, string> headers)
|
2016-11-04 09:31:05 +01:00
|
|
|
{
|
2020-01-21 17:59:41 +01:00
|
|
|
await reader.MoveToContentAsync().ConfigureAwait(false);
|
|
|
|
await reader.ReadAsync().ConfigureAwait(false);
|
2016-11-04 09:31:05 +01:00
|
|
|
|
|
|
|
// Loop through each element
|
2016-12-03 22:46:06 +01:00
|
|
|
while (!reader.EOF && reader.ReadState == ReadState.Interactive)
|
2016-11-04 09:31:05 +01:00
|
|
|
{
|
|
|
|
if (reader.NodeType == XmlNodeType.Element)
|
|
|
|
{
|
2016-12-04 22:30:38 +01:00
|
|
|
// TODO: Should we be doing this here, or should it be handled earlier when decoding the request?
|
2020-01-21 17:59:41 +01:00
|
|
|
headers[reader.LocalName.RemoveDiacritics()] = await reader.ReadElementContentAsStringAsync().ConfigureAwait(false);
|
2016-11-04 09:31:05 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-01-21 17:59:41 +01:00
|
|
|
await reader.ReadAsync().ConfigureAwait(false);
|
2016-11-04 09:31:05 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-01 19:16:00 +02:00
|
|
|
protected abstract void WriteResult(string methodName, IReadOnlyDictionary<string, string> methodParams, XmlWriter xmlWriter);
|
2016-10-30 00:22:20 +02:00
|
|
|
|
|
|
|
private void LogRequest(ControlRequest request)
|
|
|
|
{
|
2017-08-09 21:56:38 +02:00
|
|
|
if (!Config.GetDlnaConfiguration().EnableDebugLog)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
2016-10-30 00:22:20 +02:00
|
|
|
|
2020-01-22 17:46:26 +01:00
|
|
|
Logger.LogDebug("Control request. Headers: {@Headers}", request.Headers);
|
2016-10-30 00:22:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private void LogResponse(ControlResponse response)
|
|
|
|
{
|
2017-08-09 21:56:38 +02:00
|
|
|
if (!Config.GetDlnaConfiguration().EnableDebugLog)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
2016-10-30 00:22:20 +02:00
|
|
|
|
2020-01-21 17:59:41 +01:00
|
|
|
Logger.LogDebug("Control response. Headers: {@Headers}\n{Xml}", response.Headers, response.Xml);
|
2016-10-30 00:22:20 +02:00
|
|
|
}
|
2020-08-20 21:04:57 +02:00
|
|
|
|
|
|
|
private class ControlRequestInfo
|
|
|
|
{
|
2020-09-25 20:44:16 +02:00
|
|
|
public ControlRequestInfo(string localName, string namespaceUri)
|
|
|
|
{
|
|
|
|
LocalName = localName;
|
|
|
|
NamespaceURI = namespaceUri;
|
|
|
|
Headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
|
|
|
}
|
|
|
|
|
2020-08-20 21:04:57 +02:00
|
|
|
public string LocalName { get; set; }
|
|
|
|
|
|
|
|
public string NamespaceURI { get; set; }
|
|
|
|
|
2020-09-25 20:44:16 +02:00
|
|
|
public Dictionary<string, string> Headers { get; }
|
2020-08-20 21:04:57 +02:00
|
|
|
}
|
2016-10-30 00:22:20 +02:00
|
|
|
}
|
|
|
|
}
|