using Emby.Dlna.Common; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Extensions; using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Security; using System.Text; namespace Emby.Dlna.Server { public class DescriptionXmlBuilder { private readonly DeviceProfile _profile; private readonly CultureInfo _usCulture = new CultureInfo("en-US"); private readonly string _serverUdn; private readonly string _serverAddress; private readonly string _serverName; private readonly string _serverId; public DescriptionXmlBuilder(DeviceProfile profile, string serverUdn, string serverAddress, string serverName, string serverId) { if (string.IsNullOrWhiteSpace(serverUdn)) { throw new ArgumentNullException("serverUdn"); } if (string.IsNullOrWhiteSpace(serverAddress)) { throw new ArgumentNullException("serverAddress"); } _profile = profile; _serverUdn = serverUdn; _serverAddress = serverAddress; _serverName = serverName; _serverId = serverId; } private bool EnableAbsoluteUrls { get { return false; } } public string GetXml() { var builder = new StringBuilder(); builder.Append("<?xml version=\"1.0\"?>"); builder.Append("<root"); var attributes = _profile.XmlRootAttributes.ToList(); attributes.Insert(0, new XmlAttribute { Name = "xmlns:dlna", Value = "urn:schemas-dlna-org:device-1-0" }); attributes.Insert(0, new XmlAttribute { Name = "xmlns", Value = "urn:schemas-upnp-org:device-1-0" }); foreach (var att in attributes) { builder.AppendFormat(" {0}=\"{1}\"", att.Name, att.Value); } builder.Append(">"); builder.Append("<specVersion>"); builder.Append("<major>1</major>"); builder.Append("<minor>0</minor>"); builder.Append("</specVersion>"); AppendDeviceInfo(builder); builder.Append("</root>"); return builder.ToString(); } private void AppendDeviceInfo(StringBuilder builder) { builder.Append("<device>"); AppendDeviceProperties(builder); AppendIconList(builder); AppendServiceList(builder); builder.Append("</device>"); } private static readonly char[] s_escapeChars = new char[] { '<', '>', '"', '\'', '&' }; private static readonly string[] s_escapeStringPairs = new string[] { "<", "<", ">", ">", "\"", """, "'", "'", "&", "&" }; private static string GetEscapeSequence(char c) { int num = s_escapeStringPairs.Length; for (int i = 0; i < num; i += 2) { string text = s_escapeStringPairs[i]; string result = s_escapeStringPairs[i + 1]; if (text[0] == c) { return result; } } return c.ToString(); } /// <summary>Replaces invalid XML characters in a string with their valid XML equivalent.</summary> /// <returns>The input string with invalid characters replaced.</returns> /// <param name="str">The string within which to escape invalid characters. </param> public static string Escape(string str) { if (str == null) { return null; } StringBuilder stringBuilder = null; int length = str.Length; int num = 0; while (true) { int num2 = str.IndexOfAny(s_escapeChars, num); if (num2 == -1) { break; } if (stringBuilder == null) { stringBuilder = new StringBuilder(); } stringBuilder.Append(str, num, num2 - num); stringBuilder.Append(GetEscapeSequence(str[num2])); num = num2 + 1; } if (stringBuilder == null) { return str; } stringBuilder.Append(str, num, length - num); return stringBuilder.ToString(); } private void AppendDeviceProperties(StringBuilder builder) { builder.Append("<deviceType>urn:schemas-upnp-org:device:MediaServer:1</deviceType>"); builder.Append("<dlna:X_DLNACAP>" + Escape(_profile.XDlnaCap ?? string.Empty) + "</dlna:X_DLNACAP>"); builder.Append("<dlna:X_DLNADOC xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\">M-DMS-1.50</dlna:X_DLNADOC>"); builder.Append("<dlna:X_DLNADOC xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\">" + Escape(_profile.XDlnaDoc ?? string.Empty) + "</dlna:X_DLNADOC>"); builder.Append("<friendlyName>" + Escape(GetFriendlyName()) + "</friendlyName>"); builder.Append("<manufacturer>" + Escape(_profile.Manufacturer ?? string.Empty) + "</manufacturer>"); builder.Append("<manufacturerURL>" + Escape(_profile.ManufacturerUrl ?? string.Empty) + "</manufacturerURL>"); builder.Append("<modelDescription>" + Escape(_profile.ModelDescription ?? string.Empty) + "</modelDescription>"); builder.Append("<modelName>" + Escape(_profile.ModelName ?? string.Empty) + "</modelName>"); builder.Append("<modelNumber>" + Escape(_profile.ModelNumber ?? string.Empty) + "</modelNumber>"); builder.Append("<modelURL>" + Escape(_profile.ModelUrl ?? string.Empty) + "</modelURL>"); if (string.IsNullOrWhiteSpace(_profile.SerialNumber)) { builder.Append("<serialNumber>" + Escape(_serverId) + "</serialNumber>"); } else { builder.Append("<serialNumber>" + Escape(_profile.SerialNumber) + "</serialNumber>"); } builder.Append("<UDN>uuid:" + Escape(_serverUdn) + "</UDN>"); builder.Append("<presentationURL>" + Escape(_serverAddress) + "</presentationURL>"); if (!EnableAbsoluteUrls) { //builder.Append("<URLBase>" + Escape(_serverAddress) + "</URLBase>"); } if (!string.IsNullOrWhiteSpace(_profile.SonyAggregationFlags)) { builder.Append("<av:aggregationFlags xmlns:av=\"urn:schemas-sony-com:av\">" + Escape(_profile.SonyAggregationFlags) + "</av:aggregationFlags>"); } } private string GetFriendlyName() { if (string.IsNullOrWhiteSpace(_profile.FriendlyName)) { return "Emby - " + _serverName; } var characterList = new List<char>(); foreach (var c in _serverName) { if (char.IsLetterOrDigit(c) || c == '-') { characterList.Add(c); } } var characters = characterList.ToArray(); var serverName = new string(characters); var name = (_profile.FriendlyName ?? string.Empty).Replace("${HostName}", serverName, StringComparison.OrdinalIgnoreCase); return name; } private void AppendIconList(StringBuilder builder) { builder.Append("<iconList>"); foreach (var icon in GetIcons()) { builder.Append("<icon>"); builder.Append("<mimetype>" + Escape(icon.MimeType ?? string.Empty) + "</mimetype>"); builder.Append("<width>" + Escape(icon.Width.ToString(_usCulture)) + "</width>"); builder.Append("<height>" + Escape(icon.Height.ToString(_usCulture)) + "</height>"); builder.Append("<depth>" + Escape(icon.Depth ?? string.Empty) + "</depth>"); builder.Append("<url>" + BuildUrl(icon.Url) + "</url>"); builder.Append("</icon>"); } builder.Append("</iconList>"); } private void AppendServiceList(StringBuilder builder) { builder.Append("<serviceList>"); foreach (var service in GetServices()) { builder.Append("<service>"); builder.Append("<serviceType>" + Escape(service.ServiceType ?? string.Empty) + "</serviceType>"); builder.Append("<serviceId>" + Escape(service.ServiceId ?? string.Empty) + "</serviceId>"); builder.Append("<SCPDURL>" + BuildUrl(service.ScpdUrl) + "</SCPDURL>"); builder.Append("<controlURL>" + BuildUrl(service.ControlUrl) + "</controlURL>"); builder.Append("<eventSubURL>" + BuildUrl(service.EventSubUrl) + "</eventSubURL>"); builder.Append("</service>"); } builder.Append("</serviceList>"); } private string BuildUrl(string url) { if (string.IsNullOrWhiteSpace(url)) { return string.Empty; } url = url.TrimStart('/'); url = "/dlna/" + _serverUdn + "/" + url; if (EnableAbsoluteUrls) { url = _serverAddress.TrimEnd('/') + url; } return Escape(url); } private IEnumerable<DeviceIcon> GetIcons() { var list = new List<DeviceIcon>(); list.Add(new DeviceIcon { MimeType = "image/png", Depth = "24", Width = 240, Height = 240, Url = "icons/logo240.png" }); list.Add(new DeviceIcon { MimeType = "image/jpeg", Depth = "24", Width = 240, Height = 240, Url = "icons/logo240.jpg" }); list.Add(new DeviceIcon { MimeType = "image/png", Depth = "24", Width = 120, Height = 120, Url = "icons/logo120.png" }); list.Add(new DeviceIcon { MimeType = "image/jpeg", Depth = "24", Width = 120, Height = 120, Url = "icons/logo120.jpg" }); list.Add(new DeviceIcon { MimeType = "image/png", Depth = "24", Width = 48, Height = 48, Url = "icons/logo48.png" }); list.Add(new DeviceIcon { MimeType = "image/jpeg", Depth = "24", Width = 48, Height = 48, Url = "icons/logo48.jpg" }); return list; } private IEnumerable<DeviceService> GetServices() { var list = new List<DeviceService>(); list.Add(new DeviceService { ServiceType = "urn:schemas-upnp-org:service:ContentDirectory:1", ServiceId = "urn:upnp-org:serviceId:ContentDirectory", ScpdUrl = "contentdirectory/contentdirectory.xml", ControlUrl = "contentdirectory/control", EventSubUrl = "contentdirectory/events" }); list.Add(new DeviceService { ServiceType = "urn:schemas-upnp-org:service:ConnectionManager:1", ServiceId = "urn:upnp-org:serviceId:ConnectionManager", ScpdUrl = "connectionmanager/connectionmanager.xml", ControlUrl = "connectionmanager/control", EventSubUrl = "connectionmanager/events" }); if (_profile.EnableMSMediaReceiverRegistrar) { list.Add(new DeviceService { ServiceType = "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1", ServiceId = "urn:microsoft.com:serviceId:X_MS_MediaReceiverRegistrar", ScpdUrl = "mediareceiverregistrar/mediareceiverregistrar.xml", ControlUrl = "mediareceiverregistrar/control", EventSubUrl = "mediareceiverregistrar/events" }); } return list; } public override string ToString() { return GetXml(); } } }