aboutsummaryrefslogtreecommitdiff
path: root/Emby.Dlna/Service
diff options
context:
space:
mode:
Diffstat (limited to 'Emby.Dlna/Service')
-rw-r--r--Emby.Dlna/Service/BaseControlHandler.cs260
-rw-r--r--Emby.Dlna/Service/BaseService.cs37
-rw-r--r--Emby.Dlna/Service/ControlErrorHandler.cs55
-rw-r--r--Emby.Dlna/Service/ServiceXmlBuilder.cs91
4 files changed, 443 insertions, 0 deletions
diff --git a/Emby.Dlna/Service/BaseControlHandler.cs b/Emby.Dlna/Service/BaseControlHandler.cs
new file mode 100644
index 0000000000..3092589c12
--- /dev/null
+++ b/Emby.Dlna/Service/BaseControlHandler.cs
@@ -0,0 +1,260 @@
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Dlna;
+using Emby.Dlna.Server;
+using MediaBrowser.Model.Logging;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Xml;
+using Emby.Dlna.Didl;
+using MediaBrowser.Controller.Extensions;
+using MediaBrowser.Model.Xml;
+
+namespace Emby.Dlna.Service
+{
+ public abstract class BaseControlHandler
+ {
+ private const string NS_SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/";
+
+ protected readonly IServerConfigurationManager Config;
+ protected readonly ILogger Logger;
+ protected readonly IXmlReaderSettingsFactory XmlReaderSettingsFactory;
+
+ protected BaseControlHandler(IServerConfigurationManager config, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory)
+ {
+ Config = config;
+ Logger = logger;
+ XmlReaderSettingsFactory = xmlReaderSettingsFactory;
+ }
+
+ public ControlResponse ProcessControlRequest(ControlRequest request)
+ {
+ try
+ {
+ var enableDebugLogging = Config.GetDlnaConfiguration().EnableDebugLog;
+
+ if (enableDebugLogging)
+ {
+ LogRequest(request);
+ }
+
+ var response = ProcessControlRequestInternal(request);
+
+ if (enableDebugLogging)
+ {
+ LogResponse(response);
+ }
+
+ return response;
+ }
+ catch (Exception ex)
+ {
+ Logger.ErrorException("Error processing control request", ex);
+
+ return new ControlErrorHandler().GetResponse(ex);
+ }
+ }
+
+ private ControlResponse ProcessControlRequestInternal(ControlRequest request)
+ {
+ ControlRequestInfo requestInfo = null;
+
+ using (var streamReader = new StreamReader(request.InputXml))
+ {
+ var readerSettings = XmlReaderSettingsFactory.Create(false);
+
+ readerSettings.CheckCharacters = false;
+ readerSettings.IgnoreProcessingInstructions = true;
+ readerSettings.IgnoreComments = true;
+
+ using (var reader = XmlReader.Create(streamReader, readerSettings))
+ {
+ requestInfo = ParseRequest(reader);
+ }
+ }
+
+ Logger.Debug("Received control request {0}", requestInfo.LocalName);
+
+ var result = GetResult(requestInfo.LocalName, requestInfo.Headers);
+
+ var settings = new XmlWriterSettings
+ {
+ Encoding = Encoding.UTF8,
+ CloseOutput = false
+ };
+
+ StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8);
+
+ using (XmlWriter writer = XmlWriter.Create(builder, settings))
+ {
+ writer.WriteStartDocument(true);
+
+ writer.WriteStartElement("SOAP-ENV", "Envelope", NS_SOAPENV);
+ writer.WriteAttributeString(string.Empty, "encodingStyle", NS_SOAPENV, "http://schemas.xmlsoap.org/soap/encoding/");
+
+ writer.WriteStartElement("SOAP-ENV", "Body", NS_SOAPENV);
+ writer.WriteStartElement("u", requestInfo.LocalName + "Response", requestInfo.NamespaceURI);
+ foreach (var i in result)
+ {
+ writer.WriteStartElement(i.Key);
+ writer.WriteString(i.Value);
+ writer.WriteFullEndElement();
+ }
+ writer.WriteFullEndElement();
+ writer.WriteFullEndElement();
+
+ writer.WriteFullEndElement();
+ writer.WriteEndDocument();
+ }
+
+ var xml = builder.ToString().Replace("xmlns:m=", "xmlns:u=");
+
+ var controlResponse = new ControlResponse
+ {
+ Xml = xml,
+ IsSuccessful = true
+ };
+
+ //Logger.Debug(xml);
+
+ controlResponse.Headers.Add("EXT", string.Empty);
+
+ return controlResponse;
+ }
+
+ private ControlRequestInfo ParseRequest(XmlReader reader)
+ {
+ reader.MoveToContent();
+ reader.Read();
+
+ // Loop through each element
+ while (!reader.EOF && reader.ReadState == ReadState.Interactive)
+ {
+ if (reader.NodeType == XmlNodeType.Element)
+ {
+ switch (reader.LocalName)
+ {
+ case "Body":
+ {
+ if (!reader.IsEmptyElement)
+ {
+ using (var subReader = reader.ReadSubtree())
+ {
+ return ParseBodyTag(subReader);
+ }
+ }
+ else
+ {
+ reader.Read();
+ }
+ break;
+ }
+ default:
+ {
+ reader.Skip();
+ break;
+ }
+ }
+ }
+ else
+ {
+ reader.Read();
+ }
+ }
+
+ return new ControlRequestInfo();
+ }
+
+ private ControlRequestInfo ParseBodyTag(XmlReader reader)
+ {
+ var result = new ControlRequestInfo();
+
+ reader.MoveToContent();
+ reader.Read();
+
+ // Loop through each element
+ while (!reader.EOF && reader.ReadState == ReadState.Interactive)
+ {
+ if (reader.NodeType == XmlNodeType.Element)
+ {
+ result.LocalName = reader.LocalName;
+ result.NamespaceURI = reader.NamespaceURI;
+
+ if (!reader.IsEmptyElement)
+ {
+ using (var subReader = reader.ReadSubtree())
+ {
+ ParseFirstBodyChild(subReader, result.Headers);
+ return result;
+ }
+ }
+ else
+ {
+ reader.Read();
+ }
+ }
+ else
+ {
+ reader.Read();
+ }
+ }
+
+ return result;
+ }
+
+ private void ParseFirstBodyChild(XmlReader reader, IDictionary<string,string> headers)
+ {
+ reader.MoveToContent();
+ reader.Read();
+
+ // 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()] = reader.ReadElementContentAsString();
+ }
+ else
+ {
+ reader.Read();
+ }
+ }
+ }
+
+ private class ControlRequestInfo
+ {
+ public string LocalName;
+ public string NamespaceURI;
+ public IDictionary<string, string> Headers = new Dictionary<string,string>(StringComparer.OrdinalIgnoreCase);
+ }
+
+ protected abstract IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, IDictionary<string, string> methodParams);
+
+ private void LogRequest(ControlRequest request)
+ {
+ var builder = new StringBuilder();
+
+ var headers = string.Join(", ", request.Headers.Select(i => string.Format("{0}={1}", i.Key, i.Value)).ToArray());
+ builder.AppendFormat("Headers: {0}", headers);
+ builder.AppendLine();
+ //builder.Append(request.InputXml);
+
+ Logger.LogMultiline("Control request", LogSeverity.Debug, builder);
+ }
+
+ private void LogResponse(ControlResponse response)
+ {
+ var builder = new StringBuilder();
+
+ var headers = string.Join(", ", response.Headers.Select(i => string.Format("{0}={1}", i.Key, i.Value)).ToArray());
+ builder.AppendFormat("Headers: {0}", headers);
+ builder.AppendLine();
+ builder.Append(response.Xml);
+
+ Logger.LogMultiline("Control response", LogSeverity.Debug, builder);
+ }
+ }
+}
diff --git a/Emby.Dlna/Service/BaseService.cs b/Emby.Dlna/Service/BaseService.cs
new file mode 100644
index 0000000000..574d749588
--- /dev/null
+++ b/Emby.Dlna/Service/BaseService.cs
@@ -0,0 +1,37 @@
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Dlna;
+using Emby.Dlna.Eventing;
+using MediaBrowser.Model.Logging;
+
+namespace Emby.Dlna.Service
+{
+ public class BaseService : IEventManager
+ {
+ protected IEventManager EventManager;
+ protected IHttpClient HttpClient;
+ protected ILogger Logger;
+
+ protected BaseService(ILogger logger, IHttpClient httpClient)
+ {
+ Logger = logger;
+ HttpClient = httpClient;
+
+ EventManager = new EventManager(Logger, HttpClient);
+ }
+
+ public EventSubscriptionResponse CancelEventSubscription(string subscriptionId)
+ {
+ return EventManager.CancelEventSubscription(subscriptionId);
+ }
+
+ public EventSubscriptionResponse RenewEventSubscription(string subscriptionId, int? timeoutSeconds)
+ {
+ return EventManager.RenewEventSubscription(subscriptionId, timeoutSeconds);
+ }
+
+ public EventSubscriptionResponse CreateEventSubscription(string notificationType, int? timeoutSeconds, string callbackUrl)
+ {
+ return EventManager.CreateEventSubscription(notificationType, timeoutSeconds, callbackUrl);
+ }
+ }
+}
diff --git a/Emby.Dlna/Service/ControlErrorHandler.cs b/Emby.Dlna/Service/ControlErrorHandler.cs
new file mode 100644
index 0000000000..a3cd77f0fb
--- /dev/null
+++ b/Emby.Dlna/Service/ControlErrorHandler.cs
@@ -0,0 +1,55 @@
+using MediaBrowser.Controller.Dlna;
+using System;
+using System.IO;
+using System.Text;
+using System.Xml;
+using Emby.Dlna.Didl;
+
+namespace Emby.Dlna.Service
+{
+ public class ControlErrorHandler
+ {
+ private const string NS_SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/";
+
+ public ControlResponse GetResponse(Exception ex)
+ {
+ var settings = new XmlWriterSettings
+ {
+ Encoding = Encoding.UTF8,
+ CloseOutput = false
+ };
+
+ StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8);
+
+ using (XmlWriter writer = XmlWriter.Create(builder, settings))
+ {
+ writer.WriteStartDocument(true);
+
+ writer.WriteStartElement("SOAP-ENV", "Envelope", NS_SOAPENV);
+ writer.WriteAttributeString(string.Empty, "encodingStyle", NS_SOAPENV, "http://schemas.xmlsoap.org/soap/encoding/");
+
+ writer.WriteStartElement("SOAP-ENV", "Body", NS_SOAPENV);
+ writer.WriteStartElement("SOAP-ENV", "Fault", NS_SOAPENV);
+
+ writer.WriteElementString("faultcode", "500");
+ writer.WriteElementString("faultstring", ex.Message);
+
+ writer.WriteStartElement("detail");
+ writer.WriteRaw("<UPnPError xmlns=\"urn:schemas-upnp-org:control-1-0\"><errorCode>401</errorCode><errorDescription>Invalid Action</errorDescription></UPnPError>");
+ writer.WriteFullEndElement();
+
+ writer.WriteFullEndElement();
+ writer.WriteFullEndElement();
+
+ writer.WriteFullEndElement();
+ writer.WriteEndDocument();
+ }
+
+ return new ControlResponse
+ {
+ Xml = builder.ToString(),
+ IsSuccessful = false
+ };
+ }
+ }
+}
diff --git a/Emby.Dlna/Service/ServiceXmlBuilder.cs b/Emby.Dlna/Service/ServiceXmlBuilder.cs
new file mode 100644
index 0000000000..08eb804033
--- /dev/null
+++ b/Emby.Dlna/Service/ServiceXmlBuilder.cs
@@ -0,0 +1,91 @@
+using Emby.Dlna.Common;
+using System.Collections.Generic;
+using System.Security;
+using System.Text;
+using Emby.Dlna.Server;
+
+namespace Emby.Dlna.Service
+{
+ public class ServiceXmlBuilder
+ {
+ public string GetXml(IEnumerable<ServiceAction> actions, IEnumerable<StateVariable> stateVariables)
+ {
+ var builder = new StringBuilder();
+
+ builder.Append("<?xml version=\"1.0\"?>");
+ builder.Append("<scpd xmlns=\"urn:schemas-upnp-org:service-1-0\">");
+
+ builder.Append("<specVersion>");
+ builder.Append("<major>1</major>");
+ builder.Append("<minor>0</minor>");
+ builder.Append("</specVersion>");
+
+ AppendActionList(builder, actions);
+ AppendServiceStateTable(builder, stateVariables);
+
+ builder.Append("</scpd>");
+
+ return builder.ToString();
+ }
+
+ private void AppendActionList(StringBuilder builder, IEnumerable<ServiceAction> actions)
+ {
+ builder.Append("<actionList>");
+
+ foreach (var item in actions)
+ {
+ builder.Append("<action>");
+
+ builder.Append("<name>" + DescriptionXmlBuilder.Escape(item.Name ?? string.Empty) + "</name>");
+
+ builder.Append("<argumentList>");
+
+ foreach (var argument in item.ArgumentList)
+ {
+ builder.Append("<argument>");
+
+ builder.Append("<name>" + DescriptionXmlBuilder.Escape(argument.Name ?? string.Empty) + "</name>");
+ builder.Append("<direction>" + DescriptionXmlBuilder.Escape(argument.Direction ?? string.Empty) + "</direction>");
+ builder.Append("<relatedStateVariable>" + DescriptionXmlBuilder.Escape(argument.RelatedStateVariable ?? string.Empty) + "</relatedStateVariable>");
+
+ builder.Append("</argument>");
+ }
+
+ builder.Append("</argumentList>");
+
+ builder.Append("</action>");
+ }
+
+ builder.Append("</actionList>");
+ }
+
+ private void AppendServiceStateTable(StringBuilder builder, IEnumerable<StateVariable> stateVariables)
+ {
+ builder.Append("<serviceStateTable>");
+
+ foreach (var item in stateVariables)
+ {
+ var sendEvents = item.SendsEvents ? "yes" : "no";
+
+ builder.Append("<stateVariable sendEvents=\"" + sendEvents + "\">");
+
+ builder.Append("<name>" + DescriptionXmlBuilder.Escape(item.Name ?? string.Empty) + "</name>");
+ builder.Append("<dataType>" + DescriptionXmlBuilder.Escape(item.DataType ?? string.Empty) + "</dataType>");
+
+ if (item.AllowedValues.Count > 0)
+ {
+ builder.Append("<allowedValueList>");
+ foreach (var allowedValue in item.AllowedValues)
+ {
+ builder.Append("<allowedValue>" + DescriptionXmlBuilder.Escape(allowedValue) + "</allowedValue>");
+ }
+ builder.Append("</allowedValueList>");
+ }
+
+ builder.Append("</stateVariable>");
+ }
+
+ builder.Append("</serviceStateTable>");
+ }
+ }
+}