using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using Emby.Dlna.Main; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Services; namespace Emby.Dlna.Api { [Route("/Dlna/{UuId}/description.xml", "GET", Summary = "Gets dlna server info")] [Route("/Dlna/{UuId}/description", "GET", Summary = "Gets dlna server info")] public class GetDescriptionXml { [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")] public string UuId { get; set; } } [Route("/Dlna/{UuId}/contentdirectory/contentdirectory.xml", "GET", Summary = "Gets dlna content directory xml")] [Route("/Dlna/{UuId}/contentdirectory/contentdirectory", "GET", Summary = "Gets dlna content directory xml")] public class GetContentDirectory { [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")] public string UuId { get; set; } } [Route("/Dlna/{UuId}/connectionmanager/connectionmanager.xml", "GET", Summary = "Gets dlna connection manager xml")] [Route("/Dlna/{UuId}/connectionmanager/connectionmanager", "GET", Summary = "Gets dlna connection manager xml")] public class GetConnnectionManager { [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")] public string UuId { get; set; } } [Route("/Dlna/{UuId}/mediareceiverregistrar/mediareceiverregistrar.xml", "GET", Summary = "Gets dlna mediareceiverregistrar xml")] [Route("/Dlna/{UuId}/mediareceiverregistrar/mediareceiverregistrar", "GET", Summary = "Gets dlna mediareceiverregistrar xml")] public class GetMediaReceiverRegistrar { [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")] public string UuId { get; set; } } [Route("/Dlna/{UuId}/contentdirectory/control", "POST", Summary = "Processes a control request")] public class ProcessContentDirectoryControlRequest : IRequiresRequestStream { [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")] public string UuId { get; set; } public Stream RequestStream { get; set; } } [Route("/Dlna/{UuId}/connectionmanager/control", "POST", Summary = "Processes a control request")] public class ProcessConnectionManagerControlRequest : IRequiresRequestStream { [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")] public string UuId { get; set; } public Stream RequestStream { get; set; } } [Route("/Dlna/{UuId}/mediareceiverregistrar/control", "POST", Summary = "Processes a control request")] public class ProcessMediaReceiverRegistrarControlRequest : IRequiresRequestStream { [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")] public string UuId { get; set; } public Stream RequestStream { get; set; } } [Route("/Dlna/{UuId}/mediareceiverregistrar/events", "SUBSCRIBE", Summary = "Processes an event subscription request")] [Route("/Dlna/{UuId}/mediareceiverregistrar/events", "UNSUBSCRIBE", Summary = "Processes an event subscription request")] public class ProcessMediaReceiverRegistrarEventRequest { [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "SUBSCRIBE,UNSUBSCRIBE")] public string UuId { get; set; } } [Route("/Dlna/{UuId}/contentdirectory/events", "SUBSCRIBE", Summary = "Processes an event subscription request")] [Route("/Dlna/{UuId}/contentdirectory/events", "UNSUBSCRIBE", Summary = "Processes an event subscription request")] public class ProcessContentDirectoryEventRequest { [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "SUBSCRIBE,UNSUBSCRIBE")] public string UuId { get; set; } } [Route("/Dlna/{UuId}/connectionmanager/events", "SUBSCRIBE", Summary = "Processes an event subscription request")] [Route("/Dlna/{UuId}/connectionmanager/events", "UNSUBSCRIBE", Summary = "Processes an event subscription request")] public class ProcessConnectionManagerEventRequest { [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "SUBSCRIBE,UNSUBSCRIBE")] public string UuId { get; set; } } [Route("/Dlna/{UuId}/icons/{Filename}", "GET", Summary = "Gets a server icon")] [Route("/Dlna/icons/{Filename}", "GET", Summary = "Gets a server icon")] public class GetIcon { [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string UuId { get; set; } [ApiMember(Name = "Filename", Description = "The icon filename", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public string Filename { get; set; } } public class DlnaServerService : IService, IRequiresRequest { private readonly IDlnaManager _dlnaManager; private const string XMLContentType = "text/xml; charset=UTF-8"; public IRequest Request { get; set; } private IHttpResultFactory _resultFactory; private IContentDirectory ContentDirectory => DlnaEntryPoint.Current.ContentDirectory; private IConnectionManager ConnectionManager => DlnaEntryPoint.Current.ConnectionManager; private IMediaReceiverRegistrar MediaReceiverRegistrar => DlnaEntryPoint.Current.MediaReceiverRegistrar; public DlnaServerService(IDlnaManager dlnaManager, IHttpResultFactory httpResultFactory) { _dlnaManager = dlnaManager; _resultFactory = httpResultFactory; } private string GetHeader(string name) { return Request.Headers[name]; } public object Get(GetDescriptionXml request) { var url = Request.AbsoluteUri; var serverAddress = url.Substring(0, url.IndexOf("/dlna/", StringComparison.OrdinalIgnoreCase)); var xml = _dlnaManager.GetServerDescriptionXml(Request.Headers.ToDictionary(), request.UuId, serverAddress); var cacheLength = TimeSpan.FromDays(1); var cacheKey = Request.RawUrl.GetMD5(); var bytes = Encoding.UTF8.GetBytes(xml); return _resultFactory.GetStaticResult(Request, cacheKey, null, cacheLength, XMLContentType, () => Task.FromResult(new MemoryStream(bytes))); } public object Get(GetContentDirectory request) { var xml = ContentDirectory.GetServiceXml(Request.Headers.ToDictionary()); return _resultFactory.GetResult(Request, xml, XMLContentType); } public object Get(GetMediaReceiverRegistrar request) { var xml = MediaReceiverRegistrar.GetServiceXml(Request.Headers.ToDictionary()); return _resultFactory.GetResult(Request, xml, XMLContentType); } public object Get(GetConnnectionManager request) { var xml = ConnectionManager.GetServiceXml(Request.Headers.ToDictionary()); return _resultFactory.GetResult(Request, xml, XMLContentType); } public object Post(ProcessMediaReceiverRegistrarControlRequest request) { var response = PostAsync(request.RequestStream, MediaReceiverRegistrar); return _resultFactory.GetResult(Request, response.Xml, XMLContentType); } public object Post(ProcessContentDirectoryControlRequest request) { var response = PostAsync(request.RequestStream, ContentDirectory); return _resultFactory.GetResult(Request, response.Xml, XMLContentType); } public object Post(ProcessConnectionManagerControlRequest request) { var response = PostAsync(request.RequestStream, ConnectionManager); return _resultFactory.GetResult(Request, response.Xml, XMLContentType); } private ControlResponse PostAsync(Stream requestStream, IUpnpService service) { var id = GetPathValue(2); return service.ProcessControlRequest(new ControlRequest { Headers = Request.Headers.ToDictionary(), InputXml = requestStream, TargetServerUuId = id, RequestedUrl = Request.AbsoluteUri }); } protected string GetPathValue(int index) { var pathInfo = Parse(Request.PathInfo); var first = pathInfo[0]; // backwards compatibility // TODO: Work out what this is doing. if (string.Equals(first, "mediabrowser", StringComparison.OrdinalIgnoreCase) || string.Equals(first, "emby", StringComparison.OrdinalIgnoreCase) || string.Equals(first, "jellyfin", StringComparison.OrdinalIgnoreCase)) { index++; } return pathInfo[index]; } private List Parse(string pathUri) { var actionParts = pathUri.Split(new[] { "://" }, StringSplitOptions.None); var pathInfo = actionParts[actionParts.Length - 1]; var optionsPos = pathInfo.LastIndexOf('?'); if (optionsPos != -1) { pathInfo = pathInfo.Substring(0, optionsPos); } var args = pathInfo.Split('/'); return args.Skip(1).ToList(); } public object Get(GetIcon request) { var contentType = "image/" + Path.GetExtension(request.Filename) .TrimStart('.') .ToLowerInvariant(); var cacheLength = TimeSpan.FromDays(365); var cacheKey = Request.RawUrl.GetMD5(); return _resultFactory.GetStaticResult(Request, cacheKey, null, cacheLength, contentType, () => Task.FromResult(_dlnaManager.GetIcon(request.Filename).Stream)); } public object Subscribe(ProcessContentDirectoryEventRequest request) { return ProcessEventRequest(ContentDirectory); } public object Subscribe(ProcessConnectionManagerEventRequest request) { return ProcessEventRequest(ConnectionManager); } public object Subscribe(ProcessMediaReceiverRegistrarEventRequest request) { return ProcessEventRequest(MediaReceiverRegistrar); } public object Unsubscribe(ProcessContentDirectoryEventRequest request) { return ProcessEventRequest(ContentDirectory); } public object Unsubscribe(ProcessConnectionManagerEventRequest request) { return ProcessEventRequest(ConnectionManager); } public object Unsubscribe(ProcessMediaReceiverRegistrarEventRequest request) { return ProcessEventRequest(MediaReceiverRegistrar); } private object ProcessEventRequest(IEventManager eventManager) { var subscriptionId = GetHeader("SID"); if (string.Equals(Request.Verb, "SUBSCRIBE", StringComparison.OrdinalIgnoreCase)) { var notificationType = GetHeader("NT"); var callback = GetHeader("CALLBACK"); var timeoutString = GetHeader("TIMEOUT"); if (string.IsNullOrEmpty(notificationType)) { return GetSubscriptionResponse(eventManager.RenewEventSubscription(subscriptionId, notificationType, timeoutString, callback)); } return GetSubscriptionResponse(eventManager.CreateEventSubscription(notificationType, timeoutString, callback)); } return GetSubscriptionResponse(eventManager.CancelEventSubscription(subscriptionId)); } private object GetSubscriptionResponse(EventSubscriptionResponse response) { return _resultFactory.GetResult(Request, response.Content, response.ContentType, response.Headers); } } }