Files
Umbraco-CMS/src/Umbraco.Core/Configuration/GlobalSettings.cs
Shannon Deminick 529c52844d Started writing NiceUrlProvider unit tests as there are a bunch of issues with it. Have fixed one of them.
Updated unit test hierarchy of classes so that if one doesn't require a db it can opt out and thus it runs much much faster.
2012-09-01 08:11:40 +07:00

843 lines
29 KiB
C#

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Web;
using System.Web.Configuration;
using System.Xml;
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: Re-enable Logging!!!!
//TODO: Remove checks for if HttpContext is null, why are we doing this? we should be checking if the config setting
// can be read and if not then return the default.
/// <summary>
/// The GlobalSettings Class contains general settings information for the entire Umbraco instance based on information from web.config appsettings
/// </summary>
internal class GlobalSettings
{
private static HttpContextBase _customHttpContext;
/// <summary>
/// Gets/sets the HttpContext object, this is generally used for unit testing. By default this will
/// use the HttpContext.Current
/// </summary>
internal static HttpContextBase HttpContext
{
get
{
if (_customHttpContext == null && System.Web.HttpContext.Current != null)
{
//return the current HttpContxt, do NOT store this in the _customHttpContext field
//as it will persist across reqeusts!
return new HttpContextWrapper(System.Web.HttpContext.Current);
}
if (_customHttpContext == null && System.Web.HttpContext.Current == null)
{
//throw new NullReferenceException("The HttpContext property has not been set or the object execution is not running inside of an HttpContext");
//NOTE: We should throw an exception here but the legacy code checks for null so we need to stick witht he legacy code for now.
return null;
}
return _customHttpContext;
}
set { _customHttpContext = value; }
}
#region Private static fields
// CURRENT UMBRACO VERSION ID
private const string CurrentUmbracoVersion = "4.9.0";
private static string _reservedUrlsCache;
private static string _reservedPathsCache;
private static StartsWithContainer _reservedList = new StartsWithContainer();
#endregion
/// <summary>
/// Gets the reserved urls from web.config.
/// </summary>
/// <value>The reserved urls.</value>
public static string ReservedUrls
{
get
{
if (HttpContext != null)
return ConfigurationManager.AppSettings["umbracoReservedUrls"];
return String.Empty;
}
}
/// <summary>
/// Gets the reserved paths from web.config
/// </summary>
/// <value>The reserved paths.</value>
public static string ReservedPaths
{
get
{
if (HttpContext != null)
return ConfigurationManager.AppSettings["umbracoReservedPaths"];
return String.Empty;
}
}
/// <summary>
/// Gets the name of the content XML file.
/// </summary>
/// <value>The content XML.</value>
public static string ContentXml
{
get
{
try
{
return ConfigurationManager.AppSettings["umbracoContentXML"];
}
catch
{
return String.Empty;
}
}
}
/// <summary>
/// Gets the path to the storage directory (/data by default).
/// </summary>
/// <value>The storage directory.</value>
public static string StorageDirectory
{
get
{
try
{
return ConfigurationManager.AppSettings["umbracoStorageDirectory"];
}
catch
{
return String.Empty;
}
}
}
/// <summary>
/// Gets the path to umbraco's root directory (/umbraco by default).
/// </summary>
/// <value>The path.</value>
public static string Path
{
get
{
try
{
return IOHelper.ResolveUrl(ConfigurationManager.AppSettings["umbracoPath"]);
}
catch
{
return String.Empty;
}
}
}
/// <summary>
/// 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.
/// </summary>
/// <value>The path.</value>
public static string ClientPath
{
get
{
return Path + "/../umbraco_client";
}
}
/// <summary>
/// Gets the database connection string
/// </summary>
/// <value>The database connection string.</value>
public static string DbDsn
{
get
{
try
{
return ConfigurationManager.AppSettings["umbracoDbDSN"];
}
catch
{
return String.Empty;
}
}
set
{
if (DbDsn != value)
{
SaveSetting("umbracoDbDSN", value);
}
}
}
/// <summary>
/// Gets or sets the configuration status. This will return the version number of the currently installed umbraco instance.
/// </summary>
/// <value>The configuration status.</value>
public static string ConfigurationStatus
{
get
{
try
{
return ConfigurationManager.AppSettings["umbracoConfigurationStatus"];
}
catch
{
return String.Empty;
}
}
set
{
SaveSetting("umbracoConfigurationStatus", value);
}
}
/// <summary>
/// Forces umbraco to be medium trust compatible
/// </summary>
/// <value>If true, umbraco will be medium-trust compatible, no matter what Permission level the server is on.</value>
public static bool UseMediumTrust
{
get
{
try
{
var trustLevel = SystemUtilities.GetCurrentTrustLevel();
if (trustLevel == AspNetHostingPermissionLevel.High || trustLevel == AspNetHostingPermissionLevel.Unrestricted)
return false;
else
return bool.Parse(ConfigurationManager.AppSettings["umbracoUseMediumTrust"]);
}
catch
{
return false;
}
}
}
/// <summary>
/// Saves a setting into the configuration file.
/// </summary>
/// <param name="key">Key of the setting to be saved.</param>
/// <param name="value">Value of the setting to be saved.</param>
internal static void SaveSetting(string key, string value)
{
var webConfig = new WebConfigurationFileMap();
var vDirs = webConfig.VirtualDirectories;
var vDir = FullpathToRoot;
foreach (VirtualDirectoryMapping v in webConfig.VirtualDirectories)
{
if (v.IsAppRoot)
{
vDir = v.PhysicalDirectory;
}
}
var doc = new XmlDocument();
doc.Load(String.Concat(vDir, "web.config"));
var root = doc.DocumentElement;
var setting = doc.SelectSingleNode(String.Concat("//appSettings/add[@key='", key, "']"));
setting.Attributes["value"].InnerText = value;
doc.Save(String.Concat(vDir, "web.config"));
ConfigurationManager.RefreshSection("appSettings");
}
/// <summary>
/// Gets the full path to root.
/// </summary>
/// <value>The fullpath to root.</value>
public static string FullpathToRoot
{
get { return HttpRuntime.AppDomainAppPath; }
}
/// <summary>
/// Gets a value indicating whether umbraco is running in [debug mode].
/// </summary>
/// <value><c>true</c> if [debug mode]; otherwise, <c>false</c>.</value>
public static bool DebugMode
{
get
{
try
{
return bool.Parse(ConfigurationManager.AppSettings["umbracoDebugMode"]);
}
catch
{
return false;
}
}
}
/// <summary>
/// Gets a value indicating whether the current version of umbraco is configured.
/// </summary>
/// <value><c>true</c> if configured; otherwise, <c>false</c>.</value>
public static bool Configured
{
get
{
try
{
string configStatus = ConfigurationStatus;
string currentVersion = CurrentVersion;
if (currentVersion != configStatus)
{
//Log.Add(LogTypes.Debug, User.GetUser(0), -1,
// "CurrentVersion different from configStatus: '" + currentVersion + "','" + configStatus +
// "'");
}
return (configStatus == currentVersion);
}
catch
{
return false;
}
}
}
/// <summary>
/// Gets the time out in minutes.
/// </summary>
/// <value>The time out in minutes.</value>
public static int TimeOutInMinutes
{
get
{
try
{
return int.Parse(ConfigurationManager.AppSettings["umbracoTimeOutInMinutes"]);
}
catch
{
return 20;
}
}
}
/// <summary>
/// Gets a value indicating whether umbraco uses directory urls.
/// </summary>
/// <value><c>true</c> if umbraco uses directory urls; otherwise, <c>false</c>.</value>
public static bool UseDirectoryUrls
{
get
{
try
{
return bool.Parse(ConfigurationManager.AppSettings["umbracoUseDirectoryUrls"]);
}
catch
{
return false;
}
}
}
/// <summary>
/// Returns a string value to determine if umbraco should skip version-checking.
/// </summary>
/// <value>The version check period in days (0 = never).</value>
public static int VersionCheckPeriod
{
get
{
int versionCheckPeriod = 7;
if (HttpContext != null)
{
if (int.TryParse(ConfigurationManager.AppSettings["umbracoVersionCheckPeriod"], out versionCheckPeriod))
return versionCheckPeriod;
}
return versionCheckPeriod;
}
}
/// <summary>
/// Gets the URL forbitten characters.
/// </summary>
/// <value>The URL forbitten characters.</value>
public static string UrlForbittenCharacters
{
get
{
if (HttpContext != null)
return ConfigurationManager.AppSettings["umbracoUrlForbittenCharacters"];
return "";
}
}
/// <summary>
/// Gets the URL space character.
/// </summary>
/// <value>The URL space character.</value>
public static string UrlSpaceCharacter
{
get
{
if (HttpContext != null)
return ConfigurationManager.AppSettings["umbracoUrlSpaceCharacter"];
return "";
}
}
/// <summary>
/// Gets the SMTP server IP-address or hostname.
/// </summary>
/// <value>The SMTP server.</value>
public static string SmtpServer
{
get
{
try
{
var mailSettings = ConfigurationManager.GetSection("system.net/mailSettings") as System.Net.Configuration.MailSettingsSectionGroup;
if (mailSettings != null)
return mailSettings.Smtp.Network.Host;
else
return ConfigurationManager.AppSettings["umbracoSmtpServer"];
}
catch
{
return "";
}
}
}
/// <summary>
/// Returns a string value to determine if umbraco should disbable xslt extensions
/// </summary>
/// <value><c>"true"</c> if version xslt extensions are disabled, otherwise, <c>"false"</c></value>
public static string DisableXsltExtensions
{
get
{
if (HttpContext != null)
return ConfigurationManager.AppSettings["umbracoDisableXsltExtensions"];
return "";
}
}
/// <summary>
/// Returns a string value to determine if umbraco should use Xhtml editing mode in the wysiwyg editor
/// </summary>
/// <value><c>"true"</c> if Xhtml mode is enable, otherwise, <c>"false"</c></value>
public static string EditXhtmlMode
{
get
{
if (HttpContext != null)
return ConfigurationManager.AppSettings["umbracoEditXhtmlMode"];
return "";
}
}
/// <summary>
/// Gets the default UI language.
/// </summary>
/// <value>The default UI language.</value>
public static string DefaultUILanguage
{
get
{
if (HttpContext != null)
return ConfigurationManager.AppSettings["umbracoDefaultUILanguage"];
return "";
}
}
/// <summary>
/// Gets the profile URL.
/// </summary>
/// <value>The profile URL.</value>
public static string ProfileUrl
{
get
{
if (HttpContext != null)
return ConfigurationManager.AppSettings["umbracoProfileUrl"];
return "";
}
}
/// <summary>
/// Gets a value indicating whether umbraco should hide top level nodes from generated urls.
/// </summary>
/// <value>
/// <c>true</c> if umbraco hides top level nodes from urls; otherwise, <c>false</c>.
/// </value>
public static bool HideTopLevelNodeFromPath
{
get
{
try
{
return bool.Parse(ConfigurationManager.AppSettings["umbracoHideTopLevelNodeFromPath"]);
}
catch
{
return false;
}
}
}
/// <summary>
/// Gets the current version.
/// </summary>
/// <value>The current version.</value>
public static string CurrentVersion
{
get
{
// change this to be hardcoded in the binary
return CurrentUmbracoVersion;
}
}
/// <summary>
/// Gets the major version number.
/// </summary>
/// <value>The major version number.</value>
public static int VersionMajor
{
get
{
string[] version = CurrentVersion.Split(".".ToCharArray());
return int.Parse(version[0]);
}
}
/// <summary>
/// Gets the minor version number.
/// </summary>
/// <value>The minor version number.</value>
public static int VersionMinor
{
get
{
string[] version = CurrentVersion.Split(".".ToCharArray());
return int.Parse(version[1]);
}
}
/// <summary>
/// Gets the patch version number.
/// </summary>
/// <value>The patch version number.</value>
public static int VersionPatch
{
get
{
string[] version = CurrentVersion.Split(".".ToCharArray());
return int.Parse(version[2]);
}
}
/// <summary>
/// Gets the version comment (like beta or RC).
/// </summary>
/// <value>The version comment.</value>
public static string VersionComment
{
get
{
string[] version = CurrentVersion.Split(".".ToCharArray());
if (version.Length > 3)
return version[3];
else
return "";
}
}
/// <summary>
/// Requests the is in umbraco application directory structure.
/// </summary>
/// <param name="context">The context.</param>
/// <returns></returns>
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;
}
/// <summary>
/// Gets a value indicating whether umbraco should force a secure (https) connection to the backoffice.
/// </summary>
/// <value><c>true</c> if [use SSL]; otherwise, <c>false</c>.</value>
public static bool UseSSL
{
get
{
try
{
return bool.Parse(ConfigurationManager.AppSettings["umbracoUseSSL"]);
}
catch
{
return false;
}
}
}
/// <summary>
/// Gets the umbraco license.
/// </summary>
/// <value>The license.</value>
public static string License
{
get
{
string license =
"<A href=\"http://umbraco.org/redir/license\" target=\"_blank\">the open source license MIT</A>. The umbraco UI is freeware licensed under the umbraco license.";
if (HttpContext != null)
{
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<br/><b>Registered to:</b><br/>" +
licensedTo.Replace("\n", "<br/>") + "<br/><b>For use with domain:</b><br/>" +
licenseUrl;
}
}
catch
{
}
}
return license;
}
}
/// <summary>
/// Developer method to test if configuration settings are loaded properly.
/// </summary>
/// <value><c>true</c> if succesfull; otherwise, <c>false</c>.</value>
internal static bool Test
{
get
{
if (HttpContext != null)
{
HttpContext.Response.Write("ContentXML :" + ContentXml + "\n");
HttpContext.Response.Write("DbDSN :" + DbDsn + "\n");
HttpContext.Response.Write("DebugMode :" + DebugMode + "\n");
HttpContext.Response.Write("DefaultUILanguage :" + DefaultUILanguage + "\n");
HttpContext.Response.Write("VersionCheckPeriod :" + VersionCheckPeriod + "\n");
HttpContext.Response.Write("DisableXsltExtensions :" + DisableXsltExtensions + "\n");
HttpContext.Response.Write("EditXhtmlMode :" + EditXhtmlMode + "\n");
HttpContext.Response.Write("HideTopLevelNodeFromPath :" + HideTopLevelNodeFromPath + "\n");
HttpContext.Response.Write("Path :" + Path + "\n");
HttpContext.Response.Write("ProfileUrl :" + ProfileUrl + "\n");
HttpContext.Response.Write("ReservedPaths :" + ReservedPaths + "\n");
HttpContext.Response.Write("ReservedUrls :" + ReservedUrls + "\n");
HttpContext.Response.Write("StorageDirectory :" + StorageDirectory + "\n");
HttpContext.Response.Write("TimeOutInMinutes :" + TimeOutInMinutes + "\n");
HttpContext.Response.Write("UrlForbittenCharacters :" + UrlForbittenCharacters + "\n");
HttpContext.Response.Write("UrlSpaceCharacter :" + UrlSpaceCharacter + "\n");
HttpContext.Response.Write("UseDirectoryUrls :" + UseDirectoryUrls + "\n");
return true;
}
return false;
}
}
/// <summary>
/// Determines whether the specified URL is reserved or is inside a reserved path.
/// </summary>
/// <param name="url">The URL to check.</param>
/// <returns>
/// <c>true</c> if the specified URL is reserved; otherwise, <c>false</c>.
/// </returns>
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;
}
string res = "";
foreach (string st in _reservedList._list.Keys)
res += st + ",";
LogHelper.Debug<GlobalSettings>("reserverd urls: '" + res + "'");
// return true if url starts with an element of the reserved list
return _reservedList.StartsWith(url.ToLower());
}
/// <summary>
/// Structure that checks in logarithmic time
/// if a given string starts with one of the added keys.
/// </summary>
private class StartsWithContainer
{
/// <summary>Internal sorted list of keys.</summary>
public SortedList<string, string> _list
= new SortedList<string, string>(StartsWithComparator.Instance);
/// <summary>
/// Adds the specified new key.
/// </summary>
/// <param name="newKey">The new key.</param>
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<string, string> newList
= new SortedList<string, string>(_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;
}
/// <summary>
/// Checks if the given string starts with any of the added keys.
/// </summary>
/// <param name="target">The target.</param>
/// <returns>true if a key is found that matches the start of target</returns>
/// <remarks>
/// Runs in O(s*log(n)), with n the number of keys and s the length of target.
/// </remarks>
public bool StartsWith(string target)
{
return _list.ContainsKey(target);
}
/// <summary>Comparator that tests if a string starts with another.</summary>
/// <remarks>Not a real comparator, since it is not reflexive. (x==y does not imply y==x)</remarks>
private sealed class StartsWithComparator : IComparer<string>
{
/// <summary>Default string comparer.</summary>
private readonly static Comparer<string> _stringComparer = Comparer<string>.Default;
/// <summary>Gets an instance of the StartsWithComparator.</summary>
public static readonly StartsWithComparator Instance = new StartsWithComparator();
/// <summary>
/// Tests if whole begins with all characters of part.
/// </summary>
/// <param name="part">The part.</param>
/// <param name="whole">The whole.</param>
/// <returns>
/// Returns 0 if whole starts with part, otherwise performs standard string comparison.
/// </returns>
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)
return _stringComparer.Compare(part, whole);
//trim the end '/' of each
part = part.TrimEnd('/');
whole = whole.TrimEnd('/');
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);
}
}
}
}
}