2012-07-20 01:04:35 +06:00
using System ;
2012-08-07 02:33:08 +06:00
using System.Collections.Generic ;
2012-07-20 01:04:35 +06:00
using System.Diagnostics ;
2012-08-06 23:04:08 +06:00
using System.IO ;
2012-07-20 01:04:35 +06:00
using System.Linq ;
using System.Threading ;
using System.Web ;
2012-09-29 07:36:19 +07:00
using System.Web.Compilation ;
2012-08-07 02:33:08 +06:00
using System.Web.Mvc ;
2012-08-08 23:10:34 +06:00
using System.Web.Routing ;
2012-08-07 02:33:08 +06:00
using System.Web.UI ;
2012-07-20 01:04:35 +06:00
using Umbraco.Core ;
2012-07-28 23:40:48 +06:00
using Umbraco.Core.Logging ;
2012-07-20 01:04:35 +06:00
using Umbraco.Web.Routing ;
2012-07-29 00:04:11 +06:00
using umbraco ;
2012-07-20 01:04:35 +06:00
using umbraco.IO ;
2012-07-29 00:04:11 +06:00
using GlobalSettings = Umbraco . Core . Configuration . GlobalSettings ;
using UmbracoSettings = Umbraco . Core . Configuration . UmbracoSettings ;
2012-07-20 01:04:35 +06:00
namespace Umbraco.Web
{
2012-07-30 22:52:59 +06:00
// also look at IOHelper.ResolveUrlsFromTextString - nightmarish?!
2012-07-20 01:04:35 +06:00
2012-07-30 22:52:59 +06:00
// context.RewritePath supports ~/ or else must begin with /vdir
// Request.RawUrl is still there
// response.Redirect does?! always remap to /vdir?!
2012-07-20 01:04:35 +06:00
2012-07-30 22:52:59 +06:00
public class UmbracoModule : IHttpModule
{
2012-08-08 23:10:34 +06:00
#region HttpModule event handlers
2012-08-06 22:40:06 +06:00
2012-07-30 22:52:59 +06:00
/// <summary>
2012-09-24 12:49:36 -02:00
/// Begins to process a request.
2012-07-30 22:52:59 +06:00
/// </summary>
/// <param name="httpContext"></param>
2012-09-24 12:49:36 -02:00
void BeginRequest ( HttpContextBase httpContext )
2012-07-30 22:52:59 +06:00
{
2012-09-28 07:04:33 -02:00
// do not process if client-side request
2012-08-07 02:33:08 +06:00
if ( IsClientSideRequest ( httpContext . Request . Url ) )
2012-11-11 09:09:06 +05:00
return ;
//write the trace output for diagnostics at the end of the request
httpContext . Trace . Write ( "UmbracoModule" , "Umbraco request begins" ) ;
2012-08-07 02:33:08 +06:00
2012-09-28 07:04:33 -02:00
// ok, process
2012-09-24 12:49:36 -02:00
// create the LegacyRequestInitializer
// and initialize legacy stuff
2012-08-07 02:33:08 +06:00
var legacyRequestInitializer = new LegacyRequestInitializer ( httpContext . Request . Url , httpContext ) ;
legacyRequestInitializer . InitializeRequest ( ) ;
2012-07-21 00:47:17 +06:00
2012-09-24 12:49:36 -02:00
// create the UmbracoContext singleton, one per request, and assign
2012-07-30 22:52:59 +06:00
var umbracoContext = new UmbracoContext (
httpContext ,
2012-07-21 00:20:50 +06:00
ApplicationContext . Current ,
2012-07-30 22:52:59 +06:00
RoutesCacheResolver . Current . RoutesCache ) ;
UmbracoContext . Current = umbracoContext ;
2012-09-24 12:49:36 -02:00
// create the nice urls provider
2012-09-08 11:59:01 +07:00
var niceUrls = new NiceUrlProvider ( PublishedContentStoreResolver . Current . PublishedContentStore , umbracoContext ) ;
2012-09-24 12:49:36 -02:00
// create the RoutingContext, and assign
2012-08-07 03:54:47 +06:00
var routingContext = new RoutingContext (
2012-08-09 04:15:35 +06:00
umbracoContext ,
2012-08-07 03:54:47 +06:00
DocumentLookupsResolver . Current . DocumentLookups ,
LastChanceLookupResolver . Current . LastChanceLookup ,
2012-09-08 11:59:01 +07:00
PublishedContentStoreResolver . Current . PublishedContentStore ,
2012-08-07 03:54:47 +06:00
niceUrls ) ;
umbracoContext . RoutingContext = routingContext ;
2012-09-24 12:49:36 -02:00
}
/// <summary>
/// Processses the Umbraco Request
/// </summary>
/// <param name="httpContext"></param>
void ProcessRequest ( HttpContextBase httpContext )
{
2012-09-28 07:04:33 -02:00
// do not process if client-side request
2012-09-24 12:49:36 -02:00
if ( IsClientSideRequest ( httpContext . Request . Url ) )
return ;
2012-10-26 02:55:57 +05:00
if ( UmbracoContext . Current = = null )
throw new NullReferenceException ( "The UmbracoContext.Current is null, ProcessRequest cannot proceed unless there is a current UmbracoContext" ) ;
if ( UmbracoContext . Current . RoutingContext = = null )
throw new NullReferenceException ( "The UmbracoContext.RoutingContext has not been assigned, ProcessRequest cannot proceed unless there is a RoutingContext assigned to the UmbracoContext" ) ;
var umbracoContext = UmbracoContext . Current ;
2012-09-13 12:19:56 +07:00
2012-09-28 07:04:33 -02:00
// do not process but remap to handler if it is a base rest request
2012-10-26 02:55:57 +05:00
if ( BaseRest . BaseRestHandler . IsBaseRestRequest ( umbracoContext . OriginalRequestUrl ) )
2012-09-18 08:54:54 -02:00
{
2012-09-20 14:37:23 -02:00
httpContext . RemapHandler ( new BaseRest . BaseRestHandler ( ) ) ;
2012-09-28 07:04:33 -02:00
return ;
2012-09-18 08:54:54 -02:00
}
2012-09-28 07:04:33 -02:00
// do not process if this request is not a front-end routable page
if ( ! EnsureUmbracoRoutablePage ( umbracoContext , httpContext ) )
return ;
2012-11-11 09:09:06 +05:00
httpContext . Trace . Write ( "UmbracoModule" , "Umbraco request confirmed" ) ;
2012-09-28 07:04:33 -02:00
// ok, process
2012-10-26 02:55:57 +05:00
var uri = umbracoContext . OriginalRequestUrl ;
2012-09-28 07:04:33 -02:00
2012-10-26 02:55:57 +05:00
// legacy - no idea what this is but does something with the query strings
2012-09-28 07:04:33 -02:00
LegacyCleanUmbPageFromQueryString ( ref uri ) ;
2012-10-29 12:35:39 -01:00
// instanciate a request a process
// important to use CleanedUmbracoUrl - lowercase path-only version of the current url
var docreq = new PublishedContentRequest ( umbracoContext . CleanedUmbracoUrl , umbracoContext . RoutingContext ) ;
2012-10-30 06:49:36 +06:00
docreq . ProcessRequest ( httpContext , umbracoContext ,
docreq2 = > RewriteToUmbracoHandler ( HttpContext . Current , uri . Query , docreq2 . RenderingEngine ) ) ;
2012-07-30 22:52:59 +06:00
}
2012-07-20 01:04:35 +06:00
2012-07-29 00:04:11 +06:00
/// <summary>
/// Checks if the xml cache file needs to be updated/persisted
/// </summary>
/// <param name="httpContext"></param>
/// <remarks>
/// TODO: This needs an overhaul, see the error report created here:
/// https://docs.google.com/document/d/1neGE3q3grB4lVJfgID1keWY2v9JYqf-pw75sxUUJiyo/edit
/// </remarks>
void PersistXmlCache ( HttpContextBase httpContext )
{
if ( content . Instance . IsXmlQueuedForPersistenceToFile )
{
2012-09-02 07:35:57 +07:00
content . Instance . RemoveXmlFilePersistenceQueue ( ) ;
2012-07-29 00:04:11 +06:00
content . Instance . PersistXmlToFile ( ) ;
}
2012-09-13 12:19:56 +07:00
}
2012-08-08 23:10:34 +06:00
#endregion
#region Route helper methods
/// <summary>
/// This is a performance tweak to check if this is a .css, .js or .ico file request since
/// .Net will pass these requests through to the module when in integrated mode.
/// We want to ignore all of these requests immediately.
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
internal bool IsClientSideRequest ( Uri url )
{
var toIgnore = new [ ] { ".js" , ".css" , ".ico" } ;
return toIgnore . Any ( x = > Path . GetExtension ( url . LocalPath ) . InvariantEquals ( x ) ) ;
}
/// <summary>
/// Checks the current request and ensures that it is routable based on the structure of the request and URI
2012-09-13 09:00:21 +07:00
/// </summary>
/// <param name="context"></param>
2012-08-08 23:10:34 +06:00
/// <param name="httpContext"></param>
/// <returns></returns>
2012-09-13 09:00:21 +07:00
internal bool EnsureUmbracoRoutablePage ( UmbracoContext context , HttpContextBase httpContext )
2012-08-08 23:10:34 +06:00
{
2012-10-26 02:55:57 +05:00
var uri = context . OriginalRequestUrl ;
2012-09-13 09:00:21 +07:00
2012-08-08 23:10:34 +06:00
// ensure this is a document request
2012-09-13 09:00:21 +07:00
if ( ! EnsureDocumentRequest ( httpContext , uri ) )
2012-08-08 23:10:34 +06:00
return false ;
// ensure Umbraco is ready to serve documents
if ( ! EnsureIsReady ( httpContext , uri ) )
return false ;
// ensure Umbraco is properly configured to serve documents
if ( ! EnsureIsConfigured ( httpContext , uri ) )
return false ;
2012-09-28 07:04:33 -02:00
// ensure Umbraco has documents to serve
2012-09-30 14:02:11 -02:00
if ( ! EnsureHasContent ( context , httpContext ) )
2012-09-28 07:04:33 -02:00
return false ;
2012-08-08 23:10:34 +06:00
return true ;
2012-07-29 00:04:11 +06:00
}
2012-07-30 22:52:59 +06:00
2012-07-28 23:40:48 +06:00
/// <summary>
/// Ensures that the request is a document request (i.e. one that the module should handle)
/// </summary>
/// <param name="httpContext"></param>
/// <param name="uri"></param>
/// <returns></returns>
2012-09-13 09:00:21 +07:00
bool EnsureDocumentRequest ( HttpContextBase httpContext , Uri uri )
2012-07-30 22:52:59 +06:00
{
var maybeDoc = true ;
2012-09-13 09:00:21 +07:00
var lpath = uri . AbsolutePath . ToLowerInvariant ( ) ;
2012-07-30 22:52:59 +06:00
// handle directory-urls used for asmx
// legacy - what's the point really?
if ( maybeDoc & & GlobalSettings . UseDirectoryUrls )
{
int asmxPos = lpath . IndexOf ( ".asmx/" ) ;
if ( asmxPos > = 0 )
{
// use uri.AbsolutePath, not path, 'cos path has been lowercased
httpContext . RewritePath ( uri . AbsolutePath . Substring ( 0 , asmxPos + 5 ) , // filePath
uri . AbsolutePath . Substring ( asmxPos + 5 ) , // pathInfo
uri . Query . TrimStart ( '?' ) ) ;
maybeDoc = false ;
}
}
// a document request should be
// /foo/bar/nil
// /foo/bar/nil/
// /foo/bar/nil.aspx
// where /foo is not a reserved path
// if the path contains an extension that is not .aspx
// then it cannot be a document request
if ( maybeDoc & & lpath . Contains ( '.' ) & & ! lpath . EndsWith ( ".aspx" ) )
maybeDoc = false ;
// at that point, either we have no extension, or it is .aspx
// if the path is reserved then it cannot be a document request
if ( maybeDoc & & GlobalSettings . IsReservedPathOrUrl ( lpath ) )
maybeDoc = false ;
2012-10-11 02:30:48 +05:00
//NOTE: No need to warn, plus if we do we should log the document, as this message doesn't really tell us anything :)
//if (!maybeDoc)
//{
// LogHelper.Warn<UmbracoModule>("Not a document");
//}
2012-07-30 22:52:59 +06:00
return maybeDoc ;
}
// ensures Umbraco is ready to handle requests
// if not, set status to 503 and transfer request, and return false
// if yes, return true
bool EnsureIsReady ( HttpContextBase httpContext , Uri uri )
{
2012-09-28 11:11:47 -02:00
var ready = ApplicationContext . Current . IsReady ;
2012-07-30 22:52:59 +06:00
// ensure we are ready
2012-09-28 11:11:47 -02:00
if ( ! ready )
2012-07-28 23:40:48 +06:00
{
LogHelper . Warn < UmbracoModule > ( "Umbraco is not ready" ) ;
2012-07-30 22:52:59 +06:00
2012-09-28 11:11:47 -02:00
if ( ! UmbracoSettings . EnableSplashWhileLoading )
2012-07-30 22:52:59 +06:00
{
2012-09-28 11:11:47 -02:00
// let requests pile up and wait for 10s then show the splash anyway
ready = ApplicationContext . Current . WaitForReady ( 10 * 1000 ) ;
2012-07-30 22:52:59 +06:00
}
2012-09-28 11:11:47 -02:00
if ( ! ready )
{
httpContext . Response . StatusCode = 503 ;
2012-08-07 02:33:08 +06:00
2012-09-28 11:11:47 -02:00
var bootUrl = UmbracoSettings . BootSplashPage ;
if ( string . IsNullOrWhiteSpace ( bootUrl ) )
bootUrl = "~/config/splashes/booting.aspx" ;
httpContext . RewritePath ( UriUtility . ToAbsolute ( bootUrl ) + "?url=" + HttpUtility . UrlEncode ( uri . ToString ( ) ) ) ;
return false ;
}
2012-07-30 22:52:59 +06:00
}
return true ;
}
2012-09-28 11:11:47 -02:00
// ensures Umbraco has at least one published node
// if not, rewrites to splash and return false
// if yes, return true
2012-09-30 14:02:11 -02:00
bool EnsureHasContent ( UmbracoContext context , HttpContextBase httpContext )
2012-09-28 11:11:47 -02:00
{
var store = context . RoutingContext . PublishedContentStore ;
if ( ! store . HasContent ( context ) )
{
LogHelper . Warn < UmbracoModule > ( "Umbraco has no content" ) ;
httpContext . Response . StatusCode = 503 ;
var noContentUrl = "~/config/splashes/noNodes.aspx" ;
httpContext . RewritePath ( UriUtility . ToAbsolute ( noContentUrl ) ) ;
return false ;
}
else
{
return true ;
}
}
2012-09-28 07:04:33 -02:00
2012-07-30 22:52:59 +06:00
// ensures Umbraco is configured
// if not, redirect to install and return false
// if yes, return true
bool EnsureIsConfigured ( HttpContextBase httpContext , Uri uri )
{
if ( ! ApplicationContext . Current . IsConfigured )
{
2012-07-28 23:40:48 +06:00
LogHelper . Warn < UmbracoModule > ( "Umbraco is not configured" ) ;
2012-07-20 18:54:59 -02:00
string installPath = UriUtility . ToAbsolute ( SystemDirectories . Install ) ;
2012-07-30 22:52:59 +06:00
string installUrl = string . Format ( "{0}/default.aspx?redir=true&url={1}" , installPath , HttpUtility . UrlEncode ( uri . ToString ( ) ) ) ;
httpContext . Response . Redirect ( installUrl , true ) ;
return false ;
}
return true ;
}
2012-08-08 23:10:34 +06:00
#endregion
2012-07-20 01:04:35 +06:00
2012-08-07 04:55:27 +06:00
/// <summary>
2012-08-08 23:10:34 +06:00
/// Rewrites to the correct Umbraco handler, either WebForms or Mvc
2012-08-07 04:55:27 +06:00
/// </summary>
/// <param name="context"></param>
/// <param name="currentQuery"></param>
2012-09-29 07:20:23 +07:00
/// <param name="engine"> </param>
2012-09-07 07:57:25 +07:00
private void RewriteToUmbracoHandler ( HttpContext context , string currentQuery , RenderingEngine engine )
2012-07-30 22:52:59 +06:00
{
2012-08-07 04:55:27 +06:00
//NOTE: We do not want to use TransferRequest even though many docs say it is better with IIS7, turns out this is
//not what we need. The purpose of TransferRequest is to ensure that .net processes all of the rules for the newly
//rewritten url, but this is not what we want!
// http://forums.iis.net/t/1146511.aspx
2012-09-07 07:57:25 +07:00
string rewritePath ;
switch ( engine )
{
case RenderingEngine . Mvc :
//the Path is normally ~/umbraco but we need to remove the start ~/ of it and if someone modifies this
//then we should be rendering the MVC stuff in that location.
rewritePath = "~/"
+ GlobalSettings . Path . TrimStart ( new [ ] { '~' , '/' } ) . TrimEnd ( new [ ] { '/' } )
2012-10-26 02:55:57 +05:00
+ "/RenderMvc" ;
2012-10-29 12:35:39 -01:00
// we rewrite the path to the path of the handler (i.e. default.aspx or /umbraco/RenderMvc )
2012-09-07 07:57:25 +07:00
context . RewritePath ( rewritePath , "" , currentQuery . TrimStart ( new [ ] { '?' } ) , false ) ;
//if it is MVC we need to do something special, we are not using TransferRequest as this will
//require us to rewrite the path with query strings and then reparse the query strings, this would
//also mean that we need to handle IIS 7 vs pre-IIS 7 differently. Instead we are just going to create
//an instance of the UrlRoutingModule and call it's PostResolveRequestCache method. This does:
// * Looks up the route based on the new rewritten URL
// * Creates the RequestContext with all route parameters and then executes the correct handler that matches the route
2012-10-26 02:55:57 +05:00
//we also cannot re-create this functionality because the setter for the HttpContext.Request.RequestContext is internal
//so really, this is pretty much the only way without using Server.TransferRequest and if we did that, we'd have to rethink
//a bunch of things!
2012-09-07 07:57:25 +07:00
var urlRouting = new UrlRoutingModule ( ) ;
urlRouting . PostResolveRequestCache ( new HttpContextWrapper ( context ) ) ;
2012-09-13 12:19:56 +07:00
2012-09-07 07:57:25 +07:00
break ;
case RenderingEngine . WebForms :
default :
2012-10-26 02:55:57 +05:00
rewritePath = "~/default.aspx" ;
2012-10-29 12:35:39 -01:00
// rewrite the path to the path of the handler (i.e. default.aspx or /umbraco/RenderMvc )
2012-09-13 12:19:56 +07:00
context . RewritePath ( rewritePath , "" , currentQuery . TrimStart ( new [ ] { '?' } ) , false ) ;
2012-09-29 07:36:19 +07:00
2012-09-07 07:57:25 +07:00
break ;
}
2012-08-08 23:10:34 +06:00
2012-07-30 22:52:59 +06:00
}
#region Legacy
// "Clean umbPage from querystring, caused by .NET 2.0 default Auth Controls"
// but really, at the moment I have no idea what this does, and why...
2012-08-09 11:08:24 +06:00
// SD: I also have no idea what this does, I've googled umbPage and really nothing shows up
2012-09-13 09:00:21 +07:00
internal static void LegacyCleanUmbPageFromQueryString ( ref Uri uri )
2012-07-30 22:52:59 +06:00
{
string receivedQuery = uri . Query ;
string path = uri . AbsolutePath ;
string query = null ;
if ( receivedQuery . Length > 0 )
{
// Clean umbPage from querystring, caused by .NET 2.0 default Auth Controls
if ( receivedQuery . IndexOf ( "umbPage" ) > 0 )
{
int ampPos = receivedQuery . IndexOf ( '&' ) ;
// query contains no ampersand?
if ( ampPos < 0 )
{
// no ampersand means no original query string
query = String . Empty ;
// ampersand would occur past then end the of received query
ampPos = receivedQuery . Length ;
}
else
{
// original query string past ampersand
query = receivedQuery . Substring ( ampPos + 1 ,
receivedQuery . Length - ampPos - 1 ) ;
}
// get umbPage out of query string (9 = "&umbPage".Length() + 1)
path = receivedQuery . Substring ( 9 , ampPos - 9 ) ; //this will fail if there are < 9 characters before the &umbPage query string
// --added when refactoring--
uri = uri . Rewrite ( path , query ) ;
2012-09-13 12:19:56 +07:00
}
2012-07-30 22:52:59 +06:00
}
}
#endregion
#region IHttpModule
2012-08-08 23:10:34 +06:00
/// <summary>
/// Initialize the module, this will trigger for each new application
/// and there may be more than 1 application per application domain
/// </summary>
/// <param name="app"></param>
2012-07-30 22:52:59 +06:00
public void Init ( HttpApplication app )
{
2012-09-24 12:49:36 -02:00
app . BeginRequest + = ( sender , e ) = >
{
2012-11-11 09:09:06 +05:00
var httpContext = ( ( HttpApplication ) sender ) . Context ;
2012-09-24 12:49:36 -02:00
BeginRequest ( new HttpContextWrapper ( httpContext ) ) ;
} ;
2012-08-07 02:33:08 +06:00
2012-08-07 03:54:47 +06:00
app . PostResolveRequestCache + = ( sender , e ) = >
2012-08-08 23:10:34 +06:00
{
2012-09-13 12:19:56 +07:00
var httpContext = ( ( HttpApplication ) sender ) . Context ;
2012-08-08 23:10:34 +06:00
ProcessRequest ( new HttpContextWrapper ( httpContext ) ) ;
} ;
2012-09-13 12:19:56 +07:00
2012-07-29 00:04:11 +06:00
// used to check if the xml cache file needs to be updated/persisted
2012-07-30 22:52:59 +06:00
app . PostRequestHandlerExecute + = ( sender , e ) = >
2012-08-08 23:10:34 +06:00
{
2012-09-13 12:19:56 +07:00
var httpContext = ( ( HttpApplication ) sender ) . Context ;
2012-08-08 23:10:34 +06:00
PersistXmlCache ( new HttpContextWrapper ( httpContext ) ) ;
} ;
2012-07-29 00:04:11 +06:00
2012-11-11 09:09:06 +05:00
app . EndRequest + = ( sender , args ) = >
{
var httpContext = ( ( HttpApplication ) sender ) . Context ;
2012-11-12 08:10:12 +05:00
if ( UmbracoContext . Current ! = null & & UmbracoContext . Current . IsFrontEndUmbracoRequest )
{
//write the trace output for diagnostics at the end of the request
httpContext . Trace . Write ( "UmbracoModule" , "Umbraco request completed" ) ;
LogHelper . Debug < UmbracoModule > ( "Total milliseconds for umbraco request to process: " + DateTime . Now . Subtract ( UmbracoContext . Current . ObjectCreated ) . TotalMilliseconds ) ;
}
2012-11-11 09:09:06 +05:00
} ;
2012-07-30 22:52:59 +06:00
}
2012-07-20 01:04:35 +06:00
2012-07-30 22:52:59 +06:00
public void Dispose ( )
2012-08-08 23:10:34 +06:00
{
2012-09-13 12:19:56 +07:00
2012-08-08 23:10:34 +06:00
}
2012-07-20 01:04:35 +06:00
2012-07-30 22:52:59 +06:00
#endregion
2012-07-20 01:04:35 +06:00
2012-07-30 22:52:59 +06:00
}
2012-07-20 01:04:35 +06:00
}