diff options
Diffstat (limited to 'Emby.Dlna/Service')
| -rw-r--r-- | Emby.Dlna/Service/BaseControlHandler.cs | 260 | ||||
| -rw-r--r-- | Emby.Dlna/Service/BaseService.cs | 37 | ||||
| -rw-r--r-- | Emby.Dlna/Service/ControlErrorHandler.cs | 55 | ||||
| -rw-r--r-- | Emby.Dlna/Service/ServiceXmlBuilder.cs | 91 |
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>"); + } + } +} |
