using System; using System.Collections.Generic; using System.Configuration; using System.Linq; using System.Web; using System.Web.Configuration; using System.Web.Routing; using System.Xml; using System.Xml.Linq; using Umbraco.Core.IO; using Umbraco.Core.Logging; namespace Umbraco.Core.Configuration { //NOTE: Do not expose this class ever until we cleanup all configuration including removal of static classes, etc... // we have this two tasks logged: // http://issues.umbraco.org/issue/U4-58 // http://issues.umbraco.org/issue/U4-115 //TODO: Replace checking for if the app settings exist and returning an empty string, instead return the defaults! /// /// The GlobalSettings Class contains general settings information for the entire Umbraco instance based on information from web.config appsettings /// internal class GlobalSettings { #region Private static fields private static Version _version; private static string _reservedUrlsCache; private static string _reservedPathsCache; private static StartsWithContainer _reservedList = new StartsWithContainer(); #endregion /// /// Gets the reserved urls from web.config. /// /// The reserved urls. public static string ReservedUrls { get { return ConfigurationManager.AppSettings.ContainsKey("umbracoReservedUrls") ? ConfigurationManager.AppSettings["umbracoReservedUrls"] : string.Empty; } } /// /// Gets the reserved paths from web.config /// /// The reserved paths. public static string ReservedPaths { get { return ConfigurationManager.AppSettings.ContainsKey("umbracoReservedPaths") ? ConfigurationManager.AppSettings["umbracoReservedPaths"] : string.Empty; } } /// /// Gets the name of the content XML file. /// /// The content XML. public static string ContentXmlFile { get { return ConfigurationManager.AppSettings.ContainsKey("umbracoContentXML") ? ConfigurationManager.AppSettings["umbracoContentXML"] : string.Empty; } } /// /// Gets the path to the storage directory (/data by default). /// /// The storage directory. public static string StorageDirectory { get { return ConfigurationManager.AppSettings.ContainsKey("umbracoStorageDirectory") ? ConfigurationManager.AppSettings["umbracoStorageDirectory"] : string.Empty; } } /// /// Gets the path to umbraco's root directory (/umbraco by default). /// /// The path. public static string Path { get { return ConfigurationManager.AppSettings.ContainsKey("umbracoPath") ? IOHelper.ResolveUrl(ConfigurationManager.AppSettings["umbracoPath"]) : string.Empty; } } /// /// This returns the string of the MVC Area route. /// /// /// THIS IS TEMPORARY AND SHOULD BE REMOVED WHEN WE MIGRATE/UPDATE THE CONFIG SETTINGS TO BE A REAL CONFIG SECTION /// AND SHOULD PROBABLY BE HANDLED IN A MORE ROBUST WAY. /// /// This will return the MVC area that we will route all custom routes through like surface controllers, etc... /// We will use the 'Path' (default ~/umbraco) to create it but since it cannot contain '/' and people may specify a path of ~/asdf/asdf/admin /// we will convert the '/' to '-' and use that as the path. its a bit lame but will work. /// internal static string UmbracoMvcArea { get { if (Path.IsNullOrWhiteSpace()) { throw new InvalidOperationException("Cannot create an MVC Area path without the umbracoPath specified"); } return Path.TrimStart('~').TrimStart('/').Replace('/', '-').Trim(); } } /// /// Gets the path to umbraco's client directory (/umbraco_client by default). /// This is a relative path to the Umbraco Path as it always must exist beside the 'umbraco' /// folder since the CSS paths to images depend on it. /// /// The path. public static string ClientPath { get { return Path + "/../umbraco_client"; } } /// /// Gets the database connection string /// /// The database connection string. [Obsolete("Use System.ConfigurationManager.ConnectionStrings to get the connection with the key Umbraco.Core.Configuration.GlobalSettings.UmbracoConnectionName instead")] public static string DbDsn { get { var settings = ConfigurationManager.ConnectionStrings[UmbracoConnectionName]; var connectionString = string.Empty; if (settings != null) { connectionString = settings.ConnectionString; // The SqlCe connectionString is formatted slightly differently, so we need to updat it if (settings.ProviderName.Contains("SqlServerCe")) connectionString = string.Format("datalayer=SQLCE4Umbraco.SqlCEHelper,SQLCE4Umbraco;{0}", connectionString); } return connectionString; } set { if (DbDsn != value) { if (value.ToLower().Contains("SQLCE4Umbraco.SqlCEHelper".ToLower())) { ApplicationContext.Current.DatabaseContext.ConfigureEmbeddedDatabaseConnection(); } else { ApplicationContext.Current.DatabaseContext.ConfigureDatabaseConnection(value); } } } } public const string UmbracoConnectionName = "umbracoDbDSN"; public const string UmbracoMigrationName = "Umbraco"; /// /// Gets or sets the configuration status. This will return the version number of the currently installed umbraco instance. /// /// The configuration status. public static string ConfigurationStatus { get { return ConfigurationManager.AppSettings.ContainsKey("umbracoConfigurationStatus") ? ConfigurationManager.AppSettings["umbracoConfigurationStatus"] : string.Empty; } set { SaveSetting("umbracoConfigurationStatus", value); } } /// /// Saves a setting into the configuration file. /// /// Key of the setting to be saved. /// Value of the setting to be saved. internal static void SaveSetting(string key, string value) { var fileName = GetFullWebConfigFileName(); var xml = XDocument.Load(fileName, LoadOptions.PreserveWhitespace); var appSettings = xml.Root.Descendants("appSettings").Single(); // Update appSetting if it exists, or else create a new appSetting for the given key and value var setting = appSettings.Descendants("add").FirstOrDefault(s => s.Attribute("key").Value == key); if (setting == null) appSettings.Add(new XElement("add", new XAttribute("key", key), new XAttribute("value", value))); else setting.Attribute("value").Value = value; xml.Save(fileName, SaveOptions.DisableFormatting); ConfigurationManager.RefreshSection("appSettings"); } /// /// Removes a setting from the configuration file. /// /// Key of the setting to be removed. internal static void RemoveSetting(string key) { var fileName = GetFullWebConfigFileName(); var xml = XDocument.Load(fileName, LoadOptions.PreserveWhitespace); var appSettings = xml.Root.Descendants("appSettings").Single(); var setting = appSettings.Descendants("add").FirstOrDefault(s => s.Attribute("key").Value == key); if (setting != null) { setting.Remove(); xml.Save(fileName, SaveOptions.DisableFormatting); ConfigurationManager.RefreshSection("appSettings"); } } private static string GetFullWebConfigFileName() { var webConfig = new WebConfigurationFileMap(); var vDir = FullpathToRoot; foreach (VirtualDirectoryMapping v in webConfig.VirtualDirectories) { if (v.IsAppRoot) vDir = v.PhysicalDirectory; } var fileName = System.IO.Path.Combine(vDir, "web.config"); return fileName; } /// /// Gets the full path to root. /// /// The fullpath to root. public static string FullpathToRoot { get { return IOHelper.GetRootDirectorySafe(); } } /// /// Gets a value indicating whether umbraco is running in [debug mode]. /// /// true if [debug mode]; otherwise, false. public static bool DebugMode { get { try { return bool.Parse(ConfigurationManager.AppSettings["umbracoDebugMode"]); } catch { return false; } } } /// /// Gets a value indicating whether the current version of umbraco is configured. /// /// true if configured; otherwise, false. public static bool Configured { get { try { string configStatus = ConfigurationStatus; string currentVersion = UmbracoVersion.Current.ToString(3); if (currentVersion != configStatus) { LogHelper.Debug("CurrentVersion different from configStatus: '" + currentVersion + "','" + configStatus + "'"); } return (configStatus == currentVersion); } catch { return false; } } } /// /// Gets the time out in minutes. /// /// The time out in minutes. public static int TimeOutInMinutes { get { try { return int.Parse(ConfigurationManager.AppSettings["umbracoTimeOutInMinutes"]); } catch { return 20; } } } /// /// Gets a value indicating whether umbraco uses directory urls. /// /// true if umbraco uses directory urls; otherwise, false. public static bool UseDirectoryUrls { get { try { return bool.Parse(ConfigurationManager.AppSettings["umbracoUseDirectoryUrls"]); } catch { return false; } } } /// /// Returns a string value to determine if umbraco should skip version-checking. /// /// The version check period in days (0 = never). public static int VersionCheckPeriod { get { try { return int.Parse(ConfigurationManager.AppSettings["umbracoVersionCheckPeriod"]); } catch { return 7; } } } /// /// Returns a string value to determine if umbraco should disbable xslt extensions /// /// "true" if version xslt extensions are disabled, otherwise, "false" public static string DisableXsltExtensions { get { return ConfigurationManager.AppSettings.ContainsKey("umbracoDisableXsltExtensions") ? ConfigurationManager.AppSettings["umbracoDisableXsltExtensions"] : string.Empty; } } /// /// Returns a string value to determine if umbraco should use Xhtml editing mode in the wysiwyg editor /// /// "true" if Xhtml mode is enable, otherwise, "false" public static string EditXhtmlMode { get { return ConfigurationManager.AppSettings.ContainsKey("umbracoEditXhtmlMode") ? ConfigurationManager.AppSettings["umbracoEditXhtmlMode"] : string.Empty; } } /// /// Gets the default UI language. /// /// The default UI language. public static string DefaultUILanguage { get { return ConfigurationManager.AppSettings.ContainsKey("umbracoDefaultUILanguage") ? ConfigurationManager.AppSettings["umbracoDefaultUILanguage"] : string.Empty; } } /// /// Gets the profile URL. /// /// The profile URL. public static string ProfileUrl { get { return ConfigurationManager.AppSettings.ContainsKey("umbracoProfileUrl") ? ConfigurationManager.AppSettings["umbracoProfileUrl"] : string.Empty; } } /// /// Gets a value indicating whether umbraco should hide top level nodes from generated urls. /// /// /// true if umbraco hides top level nodes from urls; otherwise, false. /// public static bool HideTopLevelNodeFromPath { get { try { return bool.Parse(ConfigurationManager.AppSettings["umbracoHideTopLevelNodeFromPath"]); } catch { return false; } } } /// /// Gets the current version. /// /// The current version. [Obsolete("Use Umbraco.Core.Configuration.UmbracoVersion.Current instead", false)] public static string CurrentVersion { get { return UmbracoVersion.Current.ToString(3); } } /// /// Gets the major version number. /// /// The major version number. [Obsolete("Use Umbraco.Core.Configuration.UmbracoVersion.Current instead", false)] public static int VersionMajor { get { return UmbracoVersion.Current.Major; } } /// /// Gets the minor version number. /// /// The minor version number. [Obsolete("Use Umbraco.Core.Configuration.UmbracoVersion.Current instead", false)] public static int VersionMinor { get { return UmbracoVersion.Current.Minor; } } /// /// Gets the patch version number. /// /// The patch version number. [Obsolete("Use Umbraco.Core.Configuration.UmbracoVersion.Current instead", false)] public static int VersionPatch { get { return UmbracoVersion.Current.Build; } } /// /// Gets the version comment (like beta or RC). /// /// The version comment. [Obsolete("Use Umbraco.Core.Configuration.UmbracoVersion.Current instead", false)] public static string VersionComment { get { return Umbraco.Core.Configuration.UmbracoVersion.CurrentComment; } } /// /// Requests the is in umbraco application directory structure. /// /// The context. /// public static bool RequestIsInUmbracoApplication(HttpContext context) { return context.Request.Path.ToLower().IndexOf(IOHelper.ResolveUrl(SystemDirectories.Umbraco).ToLower()) > -1; } public static bool RequestIsLiveEditRedirector(HttpContext context) { return context.Request.Path.ToLower().IndexOf(SystemDirectories.Umbraco.ToLower() + "/liveediting.aspx") > -1; } public static bool RequestIsInUmbracoApplication(HttpContextBase context) { return context.Request.Path.ToLower().IndexOf(IOHelper.ResolveUrl(SystemDirectories.Umbraco).ToLower()) > -1; } public static bool RequestIsLiveEditRedirector(HttpContextBase context) { return context.Request.Path.ToLower().IndexOf(SystemDirectories.Umbraco.ToLower() + "/liveediting.aspx") > -1; } /// /// Gets a value indicating whether umbraco should force a secure (https) connection to the backoffice. /// /// true if [use SSL]; otherwise, false. public static bool UseSSL { get { try { return bool.Parse(ConfigurationManager.AppSettings["umbracoUseSSL"]); } catch { return false; } } } /// /// Gets the umbraco license. /// /// The license. public static string License { get { string license = "the open source license MIT. The umbraco UI is freeware licensed under the umbraco license."; var versionDoc = new XmlDocument(); var versionReader = new XmlTextReader(IOHelper.MapPath(SystemDirectories.Umbraco + "/version.xml")); versionDoc.Load(versionReader); versionReader.Close(); // check for license try { string licenseUrl = versionDoc.SelectSingleNode("/version/licensing/licenseUrl").FirstChild.Value; string licenseValidation = versionDoc.SelectSingleNode("/version/licensing/licenseValidation").FirstChild.Value; string licensedTo = versionDoc.SelectSingleNode("/version/licensing/licensedTo").FirstChild.Value; if (licensedTo != "" && licenseUrl != "") { license = "umbraco Commercial License
Registered to:
" + licensedTo.Replace("\n", "
") + "
For use with domain:
" + licenseUrl; } } catch { } return license; } } /// /// Determines whether the current request is reserved based on the route table and /// whether the specified URL is reserved or is inside a reserved path. /// /// /// /// The route collection to lookup the request in /// public static bool IsReservedPathOrUrl(string url, HttpContextBase httpContext, RouteCollection routes) { if (httpContext == null) throw new ArgumentNullException("httpContext"); if (routes == null) throw new ArgumentNullException("routes"); //check if the current request matches a route, if so then it is reserved. var route = routes.GetRouteData(httpContext); if (route != null) return true; //continue with the standard ignore routine return IsReservedPathOrUrl(url); } /// /// Determines whether the specified URL is reserved or is inside a reserved path. /// /// The URL to check. /// /// true if the specified URL is reserved; otherwise, false. /// public static bool IsReservedPathOrUrl(string url) { // check if GlobalSettings.ReservedPaths and GlobalSettings.ReservedUrls are unchanged if (!object.ReferenceEquals(_reservedPathsCache, GlobalSettings.ReservedPaths) || !object.ReferenceEquals(_reservedUrlsCache, GlobalSettings.ReservedUrls)) { // store references to strings to determine changes _reservedPathsCache = GlobalSettings.ReservedPaths; _reservedUrlsCache = GlobalSettings.ReservedUrls; string _root = SystemDirectories.Root.Trim().ToLower(); // add URLs and paths to a new list StartsWithContainer _newReservedList = new StartsWithContainer(); foreach (string reservedUrl in _reservedUrlsCache.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries)) { //resolves the url to support tilde chars string reservedUrlTrimmed = IOHelper.ResolveUrl(reservedUrl).Trim().ToLower(); if (reservedUrlTrimmed.Length > 0) _newReservedList.Add(reservedUrlTrimmed); } foreach (string reservedPath in _reservedPathsCache.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries)) { bool trimEnd = !reservedPath.EndsWith("/"); //resolves the url to support tilde chars string reservedPathTrimmed = IOHelper.ResolveUrl(reservedPath).Trim().ToLower(); if (reservedPathTrimmed.Length > 0) _newReservedList.Add(reservedPathTrimmed + (reservedPathTrimmed.EndsWith("/") ? "" : "/")); } // use the new list from now on _reservedList = _newReservedList; } //The url should be cleaned up before checking: // * If it doesn't contain an '.' in the path then we assume it is a path based URL, if that is the case we should add an trailing '/' because all of our reservedPaths use a trailing '/' // * We shouldn't be comparing the query at all var pathPart = url.Split('?')[0]; if (!pathPart.Contains(".") && !pathPart.EndsWith("/")) { pathPart += "/"; } // return true if url starts with an element of the reserved list return _reservedList.StartsWith(pathPart.ToLowerInvariant()); } /// /// Structure that checks in logarithmic time /// if a given string starts with one of the added keys. /// private class StartsWithContainer { /// Internal sorted list of keys. public SortedList _list = new SortedList(StartsWithComparator.Instance); /// /// Adds the specified new key. /// /// The new key. public void Add(string newKey) { // if the list already contains an element that begins with newKey, return if (String.IsNullOrEmpty(newKey) || StartsWith(newKey)) return; // create a new collection, so the old one can still be accessed SortedList newList = new SortedList(_list.Count + 1, StartsWithComparator.Instance); // add only keys that don't already start with newKey, others are unnecessary foreach (string key in _list.Keys) if (!key.StartsWith(newKey)) newList.Add(key, null); // add the new key newList.Add(newKey, null); // update the list (thread safe, _list was never in incomplete state) _list = newList; } /// /// Checks if the given string starts with any of the added keys. /// /// The target. /// true if a key is found that matches the start of target /// /// Runs in O(s*log(n)), with n the number of keys and s the length of target. /// public bool StartsWith(string target) { return _list.ContainsKey(target); } /// Comparator that tests if a string starts with another. /// Not a real comparator, since it is not reflexive. (x==y does not imply y==x) private sealed class StartsWithComparator : IComparer { /// Default string comparer. private readonly static Comparer _stringComparer = Comparer.Default; /// Gets an instance of the StartsWithComparator. public static readonly StartsWithComparator Instance = new StartsWithComparator(); /// /// Tests if whole begins with all characters of part. /// /// The part. /// The whole. /// /// Returns 0 if whole starts with part, otherwise performs standard string comparison. /// public int Compare(string part, string whole) { // let the default string comparer deal with null or when part is not smaller then whole if (part == null || whole == null || part.Length >= whole.Length) return _stringComparer.Compare(part, whole); ////ensure both have a / on the end //part = part.EndsWith("/") ? part : part + "/"; //whole = whole.EndsWith("/") ? whole : whole + "/"; //if (part.Length >= whole.Length) // return _stringComparer.Compare(part, whole); // loop through all characters that part and whole have in common int pos = 0; bool match; do { match = (part[pos] == whole[pos]); } while (match && ++pos < part.Length); // return result of last comparison return match ? 0 : (part[pos] < whole[pos] ? -1 : 1); } } } } }