using System;
using System.IO;
using System.Linq;
using Umbraco.Composing;
using Umbraco.Core.Configuration;
using Umbraco.Core.Hosting;
using Umbraco.Core.IO;
namespace Umbraco.Core
{
///
/// Provides extension methods to .
///
public static class UriExtensions
{
///
/// Checks if the current uri is a back office request
///
///
///
///
///
///
/// There are some special routes we need to check to properly determine this:
///
/// If any route has an extension in the path like .aspx = back office
///
/// These are def back office:
/// /Umbraco/BackOffice = back office
/// /Umbraco/Preview = back office
/// If it's not any of the above, and there's no extension then we cannot determine if it's back office or front-end
/// so we can only assume that it is not back office. This will occur if people use an UmbracoApiController for the backoffice
/// but do not inherit from UmbracoAuthorizedApiController and do not use [IsBackOffice] attribute.
///
/// These are def front-end:
/// /Umbraco/Surface = front-end
/// /Umbraco/Api = front-end
/// But if we've got this far we'll just have to assume it's front-end anyways.
///
///
internal static bool IsBackOfficeRequest(this Uri url, IGlobalSettings globalSettings, IHostingEnvironment hostingEnvironment)
{
var applicationPath = hostingEnvironment.ApplicationVirtualPath;
var fullUrlPath = url.AbsolutePath.TrimStart(new[] {'/'});
var appPath = applicationPath.TrimStart(new[] {'/'});
var urlPath = fullUrlPath.TrimStart(appPath).EnsureStartsWith('/');
//check if this is in the umbraco back office
var backOfficePath = globalSettings.GetBackOfficePath(hostingEnvironment);
var isUmbracoPath = urlPath.InvariantStartsWith(backOfficePath.EnsureStartsWith('/').TrimStart(appPath.EnsureStartsWith('/')).EnsureStartsWith('/'));
//if not, then def not back office
if (isUmbracoPath == false) return false;
var mvcArea = globalSettings.GetUmbracoMvcArea(hostingEnvironment);
//if its the normal /umbraco path
if (urlPath.InvariantEquals("/" + mvcArea)
|| urlPath.InvariantEquals("/" + mvcArea + "/"))
{
return true;
}
//check for a file extension
var extension = Path.GetExtension(url.LocalPath);
//has an extension, def back office
if (extension.IsNullOrWhiteSpace() == false) return true;
//check for special case asp.net calls like:
// /umbraco/webservices/legacyAjaxCalls.asmx/js which will return a null file extension but are still considered requests with an extension
if (urlPath.InvariantContains(".asmx/")
|| urlPath.InvariantContains(".aspx/")
|| urlPath.InvariantContains(".ashx/")
|| urlPath.InvariantContains(".axd/")
|| urlPath.InvariantContains(".svc/"))
{
return true;
}
//check for special back office paths
if (urlPath.InvariantStartsWith("/" + mvcArea + "/BackOffice/")
|| urlPath.InvariantStartsWith("/" + mvcArea + "/Preview/"))
{
return true;
}
//check for special front-end paths
if (urlPath.InvariantStartsWith("/" + mvcArea + "/Surface/")
|| urlPath.InvariantStartsWith("/" + mvcArea + "/Api/"))
{
return false;
}
//if its none of the above, we will have to try to detect if it's a PluginController route, we can detect this by
// checking how many parts the route has, for example, all PluginController routes will be routed like
// Umbraco/MYPLUGINAREA/MYCONTROLLERNAME/{action}/{id}
// so if the path contains at a minimum 3 parts: Umbraco + MYPLUGINAREA + MYCONTROLLERNAME then we will have to assume it is a
// plugin controller for the front-end.
if (urlPath.Split(new[] {'/'}, StringSplitOptions.RemoveEmptyEntries).Length >= 3)
{
return false;
}
//if its anything else we can assume it's back office
return true;
}
///
/// Checks if the current uri is an install request
///
///
///
///
internal static bool IsInstallerRequest(this Uri url, IHostingEnvironment hostingEnvironment)
{
var authority = url.GetLeftPart(UriPartial.Authority);
var afterAuthority = url.GetLeftPart(UriPartial.Query)
.TrimStart(authority)
.TrimStart("/");
//check if this is in the umbraco back office
return afterAuthority.InvariantStartsWith(hostingEnvironment.ToAbsolute("~/install").TrimStart("/"));
}
///
/// Checks if the uri is a request for the default back office page
///
///
///
///
///
internal static bool IsDefaultBackOfficeRequest(this Uri url, IGlobalSettings globalSettings, IHostingEnvironment hostingEnvironment)
{
var backOfficePath = globalSettings.GetBackOfficePath(hostingEnvironment);
if (url.AbsolutePath.InvariantEquals(backOfficePath.TrimEnd("/"))
|| url.AbsolutePath.InvariantEquals(backOfficePath.EnsureEndsWith('/'))
|| url.AbsolutePath.InvariantEquals(backOfficePath.EnsureEndsWith('/') + "Default")
|| url.AbsolutePath.InvariantEquals(backOfficePath.EnsureEndsWith('/') + "Default/"))
{
return true;
}
return false;
}
///
/// This is a performance tweak to check if this not an ASP.Net server file
/// .Net will pass these requests through to the module when in integrated mode.
/// We want to ignore all of these requests immediately.
///
///
///
public static bool IsClientSideRequest(this Uri url)
{
try
{
var ext = Path.GetExtension(url.LocalPath);
if (ext.IsNullOrWhiteSpace()) return false;
var toInclude = new[] {".aspx", ".ashx", ".asmx", ".axd", ".svc"};
return toInclude.Any(ext.InvariantEquals) == false;
}
catch (ArgumentException)
{
Current.Logger.Debug(typeof(UriExtensions), "Failed to determine if request was client side (invalid chars in path \"{Path}\"?)", url.LocalPath);
return false;
}
}
///
/// Rewrites the path of uri.
///
/// The uri.
/// The new path, which must begin with a slash.
/// The rewritten uri.
/// Everything else remains unchanged, except for the fragment which is removed.
public static Uri Rewrite(this Uri uri, string path)
{
if (path.StartsWith("/") == false)
throw new ArgumentException("Path must start with a slash.", "path");
return uri.IsAbsoluteUri
? new Uri(uri.GetLeftPart(UriPartial.Authority) + path + uri.Query)
: new Uri(path + uri.GetSafeQuery(), UriKind.Relative);
}
///
/// Rewrites the path and query of a uri.
///
/// The uri.
/// The new path, which must begin with a slash.
/// The new query, which must be empty or begin with a question mark.
/// The rewritten uri.
/// Everything else remains unchanged, except for the fragment which is removed.
public static Uri Rewrite(this Uri uri, string path, string query)
{
if (path.StartsWith("/") == false)
throw new ArgumentException("Path must start with a slash.", "path");
if (query.Length > 0 && query.StartsWith("?") == false)
throw new ArgumentException("Query must start with a question mark.", "query");
if (query == "?")
query = "";
return uri.IsAbsoluteUri
? new Uri(uri.GetLeftPart(UriPartial.Authority) + path + query)
: new Uri(path + query, UriKind.Relative);
}
///
/// Gets the absolute path of the uri, even if the uri is relative.
///
/// The uri.
/// The absolute path of the uri.
/// Default uri.AbsolutePath does not support relative uris.
public static string GetSafeAbsolutePath(this Uri uri)
{
if (uri.IsAbsoluteUri)
return uri.AbsolutePath;
// cannot get .AbsolutePath on relative uri (InvalidOperation)
var s = uri.OriginalString;
var posq = s.IndexOf("?", StringComparison.Ordinal);
var posf = s.IndexOf("#", StringComparison.Ordinal);
var pos = posq > 0 ? posq : (posf > 0 ? posf : 0);
var path = pos > 0 ? s.Substring(0, pos) : s;
return path;
}
///
/// Gets the decoded, absolute path of the uri.
///
/// The uri.
/// The absolute path of the uri.
/// Only for absolute uris.
public static string GetAbsolutePathDecoded(this Uri uri)
{
return System.Web.HttpUtility.UrlDecode(uri.AbsolutePath);
}
///
/// Gets the decoded, absolute path of the uri, even if the uri is relative.
///
/// The uri.
/// The absolute path of the uri.
/// Default uri.AbsolutePath does not support relative uris.
public static string GetSafeAbsolutePathDecoded(this Uri uri)
{
return System.Net.WebUtility.UrlDecode(uri.GetSafeAbsolutePath());
}
///
/// Rewrites the path of the uri so it ends with a slash.
///
/// The uri.
/// The rewritten uri.
/// Everything else remains unchanged.
public static Uri EndPathWithSlash(this Uri uri)
{
var path = uri.GetSafeAbsolutePath();
if (uri.IsAbsoluteUri)
{
if (path != "/" && path.EndsWith("/") == false)
uri = new Uri(uri.GetLeftPart(UriPartial.Authority) + path + "/" + uri.Query);
return uri;
}
if (path != "/" && path.EndsWith("/") == false)
uri = new Uri(path + "/" + uri.Query, UriKind.Relative);
return uri;
}
///
/// Rewrites the path of the uri so it does not end with a slash.
///
/// The uri.
/// The rewritten uri.
/// Everything else remains unchanged.
public static Uri TrimPathEndSlash(this Uri uri)
{
var path = uri.GetSafeAbsolutePath();
if (uri.IsAbsoluteUri)
{
if (path != "/")
uri = new Uri(uri.GetLeftPart(UriPartial.Authority) + path.TrimEnd('/') + uri.Query);
}
else
{
if (path != "/")
uri = new Uri(path.TrimEnd('/') + uri.Query, UriKind.Relative);
}
return uri;
}
///
/// Transforms a relative uri into an absolute uri.
///
/// The relative uri.
/// The base absolute uri.
/// The absolute uri.
public static Uri MakeAbsolute(this Uri uri, Uri baseUri)
{
if (uri.IsAbsoluteUri)
throw new ArgumentException("Uri is already absolute.", "uri");
return new Uri(baseUri.GetLeftPart(UriPartial.Authority) + uri.GetSafeAbsolutePath() + uri.GetSafeQuery());
}
static string GetSafeQuery(this Uri uri)
{
if (uri.IsAbsoluteUri)
return uri.Query;
// cannot get .Query on relative uri (InvalidOperation)
var s = uri.OriginalString;
var posq = s.IndexOf("?", StringComparison.Ordinal);
var posf = s.IndexOf("#", StringComparison.Ordinal);
var query = posq < 0 ? null : (posf < 0 ? s.Substring(posq) : s.Substring(posq, posf - posq));
return query;
}
///
/// Removes the port from the uri.
///
/// The uri.
/// The same uri, without its port.
public static Uri WithoutPort(this Uri uri)
{
return new Uri(uri.GetComponents(UriComponents.AbsoluteUri & ~UriComponents.Port, UriFormat.UriEscaped));
}
///
/// Replaces the host of a uri.
///
/// The uri.
/// A replacement host.
/// The same uri, with its host replaced.
public static Uri ReplaceHost(this Uri uri, string host)
{
return new UriBuilder(uri) { Host = host }.Uri;
}
}
}