using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Routing; using Umbraco.Core.IO; namespace Umbraco.Core.Configuration { public static class GlobalSettingsExtensions { /// /// Used in unit testing to reset all config items, this is automatically called by GlobalSettings.Reset() /// internal static void Reset() { _reservedUrlsCache = null; _mvcArea = null; } private static readonly object Locker = new object(); //make this volatile so that we can ensure thread safety with a double check lock private static volatile string _reservedUrlsCache; private static string _reservedPathsCache; private static HashSet _reservedList = new HashSet(); private static string _mvcArea; /// /// This returns the string of the MVC Area route. /// /// /// 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. /// /// We also make sure that the virtual directory (SystemDirectories.Root) is stripped off first, otherwise we'd end up with something /// like "MyVirtualDirectory-Umbraco" instead of just "Umbraco". /// public static string GetUmbracoMvcArea(this IGlobalSettings globalSettings) { if (_mvcArea != null) return _mvcArea; if (globalSettings.Path.IsNullOrWhiteSpace()) { throw new InvalidOperationException("Cannot create an MVC Area path without the umbracoPath specified"); } var path = globalSettings.Path; if (path.StartsWith(SystemDirectories.Root)) // beware of TrimStart, see U4-2518 path = path.Substring(SystemDirectories.Root.Length); _mvcArea = path.TrimStart('~').TrimStart('/').Replace('/', '-').Trim().ToLower(); return _mvcArea; } /// /// 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. /// internal static bool IsReservedPathOrUrl(this IGlobalSettings globalSettings, string url) { if (_reservedUrlsCache == null) { lock (Locker) { if (_reservedUrlsCache == null) { // store references to strings to determine changes _reservedPathsCache = globalSettings.ReservedPaths; _reservedUrlsCache = globalSettings.ReservedUrls; // add URLs and paths to a new list var newReservedList = new HashSet(); foreach (var reservedUrlTrimmed in _reservedUrlsCache .Split(new[] {","}, StringSplitOptions.RemoveEmptyEntries) .Select(x => x.Trim().ToLowerInvariant()) .Where(x => x.IsNullOrWhiteSpace() == false) .Select(reservedUrl => IOHelper.ResolveUrl(reservedUrl).Trim().EnsureStartsWith("/")) .Where(reservedUrlTrimmed => reservedUrlTrimmed.IsNullOrWhiteSpace() == false)) { newReservedList.Add(reservedUrlTrimmed); } foreach (var reservedPathTrimmed in _reservedPathsCache .Split(new[] {","}, StringSplitOptions.RemoveEmptyEntries) .Select(x => x.Trim().ToLowerInvariant()) .Where(x => x.IsNullOrWhiteSpace() == false) .Select(reservedPath => IOHelper.ResolveUrl(reservedPath).Trim().EnsureStartsWith("/").EnsureEndsWith("/")) .Where(reservedPathTrimmed => reservedPathTrimmed.IsNullOrWhiteSpace() == false)) { newReservedList.Add(reservedPathTrimmed); } // 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(new[] {'?'}, StringSplitOptions.RemoveEmptyEntries)[0].ToLowerInvariant(); if (pathPart.Contains(".") == false) { pathPart = pathPart.EnsureEndsWith('/'); } // return true if url starts with an element of the reserved list return _reservedList.Any(x => pathPart.InvariantStartsWith(x)); } /// /// 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 /// internal static bool IsReservedPathOrUrl(this IGlobalSettings globalSettings, string url, HttpContextBase httpContext, RouteCollection routes) { if (httpContext == null) throw new ArgumentNullException(nameof(httpContext)); if (routes == null) throw new ArgumentNullException(nameof(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 globalSettings.IsReservedPathOrUrl(url); } } }