using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; namespace MediaBrowser.WebDashboard.Api { public class PackageCreator { private readonly IFileSystem _fileSystem; private readonly ILogger _logger; private readonly IServerConfigurationManager _config; private readonly IMemoryStreamFactory _memoryStreamFactory; private readonly string _basePath; public PackageCreator(string basePath, IFileSystem fileSystem, ILogger logger, IServerConfigurationManager config, IMemoryStreamFactory memoryStreamFactory) { _fileSystem = fileSystem; _logger = logger; _config = config; _memoryStreamFactory = memoryStreamFactory; _basePath = basePath; } public async Task GetResource(string virtualPath, string mode, string localizationCulture, string appVersion) { var resourceStream = GetRawResourceStream(virtualPath); if (resourceStream != null) { // Don't apply any caching for html pages // jQuery ajax doesn't seem to handle if-modified-since correctly if (IsFormat(virtualPath, "html")) { if (IsCoreHtml(virtualPath)) { resourceStream = await ModifyHtml(virtualPath, resourceStream, mode, appVersion, localizationCulture).ConfigureAwait(false); } } } return resourceStream; } /// /// Determines whether the specified path is HTML. /// /// The path. /// The format. /// true if the specified path is HTML; otherwise, false. private bool IsFormat(string path, string format) { return Path.GetExtension(path).EndsWith(format, StringComparison.OrdinalIgnoreCase); } /// /// Gets the dashboard resource path. /// /// System.String. private string GetDashboardResourcePath(string virtualPath) { var fullPath = Path.Combine(_basePath, virtualPath.Replace('/', _fileSystem.DirectorySeparatorChar)); try { fullPath = _fileSystem.GetFullPath(fullPath); } catch (Exception ex) { _logger.ErrorException("Error in Path.GetFullPath", ex); } // Don't allow file system access outside of the source folder if (!_fileSystem.ContainsSubPath(_basePath, fullPath)) { throw new SecurityException("Access denied"); } return fullPath; } public bool IsCoreHtml(string path) { if (path.IndexOf(".template.html", StringComparison.OrdinalIgnoreCase) != -1) { return false; } path = GetDashboardResourcePath(path); var parent = _fileSystem.GetDirectoryName(path); return string.Equals(_basePath, parent, StringComparison.OrdinalIgnoreCase) || string.Equals(Path.Combine(_basePath, "voice"), parent, StringComparison.OrdinalIgnoreCase); } /// /// Modifies the HTML by adding common meta tags, css and js. /// /// Task{Stream}. public async Task ModifyHtml(string path, Stream sourceStream, string mode, string appVersion, string localizationCulture) { using (sourceStream) { string html; using (var memoryStream = _memoryStreamFactory.CreateNew()) { await sourceStream.CopyToAsync(memoryStream).ConfigureAwait(false); var originalBytes = memoryStream.ToArray(); html = Encoding.UTF8.GetString(originalBytes, 0, originalBytes.Length); if (!string.IsNullOrWhiteSpace(mode)) { } else if (!string.IsNullOrWhiteSpace(path) && !string.Equals(path, "index.html", StringComparison.OrdinalIgnoreCase)) { var index = html.IndexOf("') + 1); index = html.IndexOf("", StringComparison.OrdinalIgnoreCase); if (index != -1) { html = html.Substring(0, index); } } var mainFile = _fileSystem.ReadAllText(GetDashboardResourcePath("index.html")); html = ReplaceFirst(mainFile, "
", "
" + html + "
"); } if (!string.IsNullOrWhiteSpace(localizationCulture)) { var lang = localizationCulture.Split('-').FirstOrDefault(); html = html.Replace("", "" + GetMetaTags(mode) + GetCommonCss(mode, appVersion)); // Disable embedded scripts from plugins. We'll run them later once resources have loaded if (html.IndexOf("", "-->"); } html = html.Replace("", GetCommonJavascript(mode, appVersion) + ""); var bytes = Encoding.UTF8.GetBytes(html); return _memoryStreamFactory.CreateNew(bytes); } } public string ReplaceFirst(string text, string search, string replace) { int pos = text.IndexOf(search, StringComparison.OrdinalIgnoreCase); if (pos < 0) { return text; } return text.Substring(0, pos) + replace + text.Substring(pos + search.Length); } /// /// Gets the meta tags. /// /// System.String. private static string GetMetaTags(string mode) { var sb = new StringBuilder(); if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase) || string.Equals(mode, "android", StringComparison.OrdinalIgnoreCase)) { sb.Append(""); } else { sb.Append(""); } sb.Append(""); sb.Append(""); sb.Append(""); sb.Append(""); sb.Append(""); sb.Append(""); sb.Append(""); //sb.Append(""); sb.Append(""); // Open graph tags sb.Append(""); sb.Append(""); sb.Append(""); sb.Append(""); sb.Append(""); sb.Append(""); // http://developer.apple.com/library/ios/#DOCUMENTATION/AppleApplications/Reference/SafariWebContent/ConfiguringWebApplications/ConfiguringWebApplications.html sb.Append(""); sb.Append(""); sb.Append(""); sb.Append(""); sb.Append(""); sb.Append(""); sb.Append(""); sb.Append(""); return sb.ToString(); } /// /// Gets the common CSS. /// /// The mode. /// The version. /// System.String. private string GetCommonCss(string mode, string version) { var versionString = string.IsNullOrWhiteSpace(mode) ? "?v=" + version : string.Empty; var files = new[] { "css/site.css" + versionString, "css/librarymenu.css" + versionString, "css/librarybrowser.css" + versionString, "thirdparty/paper-button-style.css" + versionString }; var tags = files.Select(s => string.Format("", s)).ToArray(); return string.Join(string.Empty, tags); } /// /// Gets the common javascript. /// /// The mode. /// The version. /// System.String. private string GetCommonJavascript(string mode, string version) { var builder = new StringBuilder(); builder.Append(""); var versionString = string.IsNullOrWhiteSpace(mode) ? "?v=" + version : string.Empty; var files = new List(); files.Add("scripts/apploader.js" + versionString); if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase)) { files.Insert(0, "cordova.js"); } var tags = files.Select(s => string.Format("", s)).ToArray(); builder.Append(string.Join(string.Empty, tags)); return builder.ToString(); } /// /// Gets the raw resource stream. /// private Stream GetRawResourceStream(string virtualPath) { return _fileSystem.GetFileStream(GetDashboardResourcePath(virtualPath), FileOpenMode.Open, FileAccessMode.Read, FileShareMode.ReadWrite, true); } } }