2012-07-28 00:13:06 +06:00
using System ;
using System.Collections.Generic ;
using System.Configuration ;
2012-09-13 09:00:21 +07:00
using System.Linq ;
2012-07-28 00:13:06 +06:00
using System.Web ;
using System.Web.Configuration ;
2012-11-14 03:32:50 +05:00
using System.Web.Routing ;
2012-07-28 00:13:06 +06:00
using System.Xml ;
2012-10-06 18:46:12 -02:00
using System.Xml.Linq ;
2012-07-28 00:13:06 +06:00
using Umbraco.Core.IO ;
2012-08-06 22:40:06 +06:00
using Umbraco.Core.Logging ;
2012-07-28 00:13:06 +06:00
namespace Umbraco.Core.Configuration
{
2012-12-31 12:15:46 -01:00
//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!
2012-08-09 07:41:13 +06:00
2012-07-28 00:13:06 +06:00
/// <summary>
/// The GlobalSettings Class contains general settings information for the entire Umbraco instance based on information from web.config appsettings
/// </summary>
internal class GlobalSettings
{
2012-08-06 22:40:06 +06:00
2012-07-28 00:13:06 +06:00
#region Private static fields
2012-11-26 10:07:08 -01:00
private static Version _version ;
2012-12-31 12:15:46 -01:00
private static string _reservedUrlsCache ;
2012-07-28 00:13:06 +06:00
private static string _reservedPathsCache ;
private static StartsWithContainer _reservedList = new StartsWithContainer ( ) ;
#endregion
2012-12-31 12:15:46 -01:00
/// <summary>
2012-07-28 00:13:06 +06:00
/// Gets the reserved urls from web.config.
/// </summary>
/// <value>The reserved urls.</value>
public static string ReservedUrls
{
get
{
2012-12-31 12:15:46 -01:00
return ConfigurationManager . AppSettings . ContainsKey ( "umbracoReservedUrls" )
? ConfigurationManager . AppSettings [ "umbracoReservedUrls" ]
: string . Empty ;
2012-07-28 00:13:06 +06:00
}
}
/// <summary>
/// Gets the reserved paths from web.config
/// </summary>
/// <value>The reserved paths.</value>
public static string ReservedPaths
{
get
{
2012-12-31 12:15:46 -01:00
return ConfigurationManager . AppSettings . ContainsKey ( "umbracoReservedPaths" )
? ConfigurationManager . AppSettings [ "umbracoReservedPaths" ]
: string . Empty ;
2012-07-28 00:13:06 +06:00
}
}
/// <summary>
/// Gets the name of the content XML file.
/// </summary>
/// <value>The content XML.</value>
2012-09-13 09:00:21 +07:00
public static string ContentXmlFile
2012-07-28 00:13:06 +06:00
{
get
2012-12-31 12:15:46 -01:00
{
return ConfigurationManager . AppSettings . ContainsKey ( "umbracoContentXML" )
? ConfigurationManager . AppSettings [ "umbracoContentXML" ]
: string . Empty ;
2012-07-28 00:13:06 +06:00
}
}
/// <summary>
/// Gets the path to the storage directory (/data by default).
/// </summary>
/// <value>The storage directory.</value>
public static string StorageDirectory
{
get
{
2012-12-31 12:15:46 -01:00
return ConfigurationManager . AppSettings . ContainsKey ( "umbracoStorageDirectory" )
? ConfigurationManager . AppSettings [ "umbracoStorageDirectory" ]
: string . Empty ;
2012-07-28 00:13:06 +06:00
}
}
/// <summary>
/// Gets the path to umbraco's root directory (/umbraco by default).
/// </summary>
/// <value>The path.</value>
public static string Path
{
get
{
2012-12-31 12:15:46 -01:00
return ConfigurationManager . AppSettings . ContainsKey ( "umbracoPath" )
? IOHelper . ResolveUrl ( ConfigurationManager . AppSettings [ "umbracoPath" ] )
: string . Empty ;
}
}
/// <summary>
/// This returns the string of the MVC Area route.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
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 ( ) ;
}
}
2012-09-05 09:35:24 +07:00
2012-07-28 00:13:06 +06:00
/// <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>
2012-12-31 12:15:46 -01:00
[Obsolete("Use System.ConfigurationManager.ConnectionStrings to get the connection with the key Umbraco.Core.Configuration.GlobalSettings.UmbracoConnectionName instead")]
2012-07-28 00:13:06 +06:00
public static string DbDsn
{
get
{
2012-12-31 12:15:46 -01:00
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 ;
2012-07-28 00:13:06 +06:00
}
set
{
if ( DbDsn ! = value )
{
2012-12-31 12:28:38 -01:00
if ( value . ToLower ( ) . Contains ( "SQLCE4Umbraco.SqlCEHelper" . ToLower ( ) ) )
{
ApplicationContext . Current . DatabaseContext . ConfigureDatabaseConnection ( ) ;
}
else
{
ApplicationContext . Current . DatabaseContext . ConfigureDatabaseConnection ( value ) ;
}
2012-07-28 00:13:06 +06:00
}
}
}
2012-10-29 09:49:31 -01:00
public const string UmbracoConnectionName = "umbracoDbDSN" ;
2012-12-27 18:52:47 -01:00
public const string UmbracoMigrationName = "Umbraco" ;
2012-10-29 09:49:31 -01:00
2012-07-28 00:13:06 +06:00
/// <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
{
2012-12-31 12:15:46 -01:00
return ConfigurationManager . AppSettings . ContainsKey ( "umbracoConfigurationStatus" )
? ConfigurationManager . AppSettings [ "umbracoConfigurationStatus" ]
: string . Empty ;
2012-07-28 00:13:06 +06:00
}
set
{
SaveSetting ( "umbracoConfigurationStatus" , value ) ;
}
}
2012-12-31 12:15:46 -01:00
2012-07-28 00:13:06 +06:00
/// <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 )
{
2012-12-31 12:15:46 -01:00
var fileName = GetFullWebConfigFileName ( ) ;
2012-12-11 16:23:15 -01:00
var xml = XDocument . Load ( fileName , LoadOptions . PreserveWhitespace ) ;
2012-12-31 12:15:46 -01:00
2012-10-06 18:46:12 -02:00
var appSettings = xml . Root . Descendants ( "appSettings" ) . Single ( ) ;
// Update appSetting if it exists, or else create a new appSetting for the given key and value
2012-12-31 12:15:46 -01:00
var setting = appSettings . Descendants ( "add" ) . FirstOrDefault ( s = > s . Attribute ( "key" ) . Value = = key ) ;
2012-10-06 18:46:12 -02:00
if ( setting = = null )
appSettings . Add ( new XElement ( "add" , new XAttribute ( "key" , key ) , new XAttribute ( "value" , value ) ) ) ;
else
setting . Attribute ( "value" ) . Value = value ;
2012-12-11 16:23:15 -01:00
xml . Save ( fileName , SaveOptions . DisableFormatting ) ;
2012-07-28 00:13:06 +06:00
ConfigurationManager . RefreshSection ( "appSettings" ) ;
}
2012-12-31 12:15:46 -01:00
/// <summary>
/// Removes a setting from the configuration file.
/// </summary>
/// <param name="key">Key of the setting to be removed.</param>
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 ) ;
2012-12-31 12:28:38 -01:00
if ( setting ! = null )
2012-12-31 12:15:46 -01:00
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 ;
}
2012-07-28 00:13:06 +06:00
/// <summary>
/// Gets the full path to root.
/// </summary>
/// <value>The fullpath to root.</value>
public static string FullpathToRoot
{
2012-12-29 18:18:49 -01:00
get { return IOHelper . GetRootDirectorySafe ( ) ; }
2012-07-28 00:13:06 +06:00
}
/// <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 ;
2012-11-26 11:18:06 -01:00
string currentVersion = UmbracoVersion . Current . ToString ( 3 ) ;
2012-07-28 00:13:06 +06:00
if ( currentVersion ! = configStatus )
{
2012-12-31 12:15:46 -01:00
LogHelper . Debug < GlobalSettings > ( "CurrentVersion different from configStatus: '" + currentVersion + "','" + configStatus + "'" ) ;
2012-07-28 00:13:06 +06:00
}
2012-12-31 12:15:46 -01:00
2012-07-28 00:13:06 +06:00
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
{
2012-12-31 12:15:46 -01:00
try
{
return int . Parse ( ConfigurationManager . AppSettings [ "umbracoVersionCheckPeriod" ] ) ;
}
catch
{
return 7 ;
}
2012-07-28 00:13:06 +06:00
}
}
2012-12-31 12:15:46 -01:00
2012-07-28 00:13:06 +06:00
/// <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
{
2012-12-31 12:15:46 -01:00
return ConfigurationManager . AppSettings . ContainsKey ( "umbracoDisableXsltExtensions" )
? ConfigurationManager . AppSettings [ "umbracoDisableXsltExtensions" ]
: string . Empty ;
2012-07-28 00:13:06 +06:00
}
}
/// <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
{
2012-12-31 12:15:46 -01:00
return ConfigurationManager . AppSettings . ContainsKey ( "umbracoEditXhtmlMode" )
? ConfigurationManager . AppSettings [ "umbracoEditXhtmlMode" ]
: string . Empty ;
2012-07-28 00:13:06 +06:00
}
}
/// <summary>
/// Gets the default UI language.
/// </summary>
/// <value>The default UI language.</value>
public static string DefaultUILanguage
{
get
{
2012-12-31 12:15:46 -01:00
return ConfigurationManager . AppSettings . ContainsKey ( "umbracoDefaultUILanguage" )
? ConfigurationManager . AppSettings [ "umbracoDefaultUILanguage" ]
: string . Empty ;
2012-07-28 00:13:06 +06:00
}
}
/// <summary>
/// Gets the profile URL.
/// </summary>
/// <value>The profile URL.</value>
public static string ProfileUrl
{
get
{
2012-12-31 12:15:46 -01:00
return ConfigurationManager . AppSettings . ContainsKey ( "umbracoProfileUrl" )
? ConfigurationManager . AppSettings [ "umbracoProfileUrl" ]
: string . Empty ;
2012-07-28 00:13:06 +06:00
}
}
/// <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
{
2012-12-31 12:15:46 -01:00
try
{
return bool . Parse ( ConfigurationManager . AppSettings [ "umbracoHideTopLevelNodeFromPath" ] ) ;
}
catch
{
return false ;
}
2012-07-28 00:13:06 +06:00
}
}
/// <summary>
/// Gets the current version.
/// </summary>
/// <value>The current version.</value>
2012-11-26 11:18:06 -01:00
[Obsolete("Use Umbraco.Core.Configuration.UmbracoVersion.Current instead", false)]
2012-07-28 00:13:06 +06:00
public static string CurrentVersion
{
get
{
2012-11-26 11:18:06 -01:00
return UmbracoVersion . Current . ToString ( 3 ) ;
2012-07-28 00:13:06 +06:00
}
}
/// <summary>
/// Gets the major version number.
/// </summary>
/// <value>The major version number.</value>
2012-11-26 11:18:06 -01:00
[Obsolete("Use Umbraco.Core.Configuration.UmbracoVersion.Current instead", false)]
2012-07-28 00:13:06 +06:00
public static int VersionMajor
{
get
{
2012-11-26 11:18:06 -01:00
return UmbracoVersion . Current . Major ;
2012-07-28 00:13:06 +06:00
}
}
/// <summary>
/// Gets the minor version number.
/// </summary>
/// <value>The minor version number.</value>
2012-11-26 11:18:06 -01:00
[Obsolete("Use Umbraco.Core.Configuration.UmbracoVersion.Current instead", false)]
2012-07-28 00:13:06 +06:00
public static int VersionMinor
{
get
{
2012-11-26 11:18:06 -01:00
return UmbracoVersion . Current . Minor ;
2012-07-28 00:13:06 +06:00
}
}
/// <summary>
/// Gets the patch version number.
/// </summary>
/// <value>The patch version number.</value>
2012-11-26 11:18:06 -01:00
[Obsolete("Use Umbraco.Core.Configuration.UmbracoVersion.Current instead", false)]
2012-07-28 00:13:06 +06:00
public static int VersionPatch
{
get
{
2012-11-26 11:18:06 -01:00
return UmbracoVersion . Current . Build ;
2012-07-28 00:13:06 +06:00
}
}
/// <summary>
/// Gets the version comment (like beta or RC).
/// </summary>
/// <value>The version comment.</value>
2012-11-26 11:18:06 -01:00
[Obsolete("Use Umbraco.Core.Configuration.UmbracoVersion.Current instead", false)]
2012-07-28 00:13:06 +06:00
public static string VersionComment
{
get
{
2012-11-26 11:18:06 -01:00
return Umbraco . Core . Configuration . UmbracoVersion . CurrentComment ;
2012-07-28 00:13:06 +06:00
}
}
/// <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." ;
2012-12-31 12:15:46 -01:00
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
{
}
2012-09-13 09:00:21 +07:00
return license ;
2012-07-28 00:13:06 +06:00
}
}
2012-12-31 12:15:46 -01:00
/// <summary>
/// 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.
/// </summary>
/// <param name="url"></param>
/// <param name="httpContext"></param>
/// <param name="routes">The route collection to lookup the request in</param>
/// <returns></returns>
public static bool IsReservedPathOrUrl ( string url , HttpContextBase httpContext , RouteCollection routes )
{
if ( httpContext = = null ) throw new ArgumentNullException ( "httpContext" ) ;
if ( routes = = null ) throw new ArgumentNullException ( "routes" ) ;
2012-11-19 19:56:52 +05:00
2012-12-31 12:15:46 -01:00
//check if the current request matches a route, if so then it is reserved.
var route = routes . GetRouteData ( httpContext ) ;
if ( route ! = null )
return true ;
2012-11-19 19:56:52 +05:00
2012-12-31 12:15:46 -01:00
//continue with the standard ignore routine
return IsReservedPathOrUrl ( url ) ;
}
2012-11-19 19:56:52 +05:00
2012-12-31 12:15:46 -01:00
/// <summary>
2012-07-28 00:13:06 +06:00
/// 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 )
2012-12-31 12:15:46 -01:00
{
2012-07-28 00:13:06 +06:00
// 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 ;
}
2012-12-31 12:15:46 -01:00
//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 + = "/" ;
}
2012-07-28 00:13:06 +06:00
// return true if url starts with an element of the reserved list
2012-12-31 12:15:46 -01:00
return _reservedList . StartsWith ( pathPart . ToLowerInvariant ( ) ) ;
}
/// <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 | | 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 ) ;
}
}
}
2012-07-28 00:13:06 +06:00
}
2012-12-31 12:15:46 -01:00
2012-07-28 00:13:06 +06:00
}