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-08-07 02:33:08 +06:00
using System.Web.Mvc ;
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-07 02:33:08 +06:00
/// <summary>
/// Checks if the current request should process the request as a front-end umbraco request, if this is tru
/// it then creates the DocumentRequest object, finds the document, domain and culture and stores this back
/// to the UmbracoContext
/// </summary>
/// <param name="httpContext"></param>
/// <param name="umbracoContext"> </param>
/// <param name="docLookups"> </param>
/// <param name="lastChanceLookup"> </param>
internal bool ProcessFrontEndDocumentRequest (
HttpContextBase httpContext ,
UmbracoContext umbracoContext ,
IEnumerable < IDocumentLookup > docLookups ,
IDocumentLastChanceLookup lastChanceLookup )
{
if ( httpContext == null ) throw new ArgumentNullException ( "httpContext" );
if ( umbracoContext == null ) throw new ArgumentNullException ( "umbracoContext" );
if ( httpContext . Request . Url . LocalPath . InvariantStartsWith ( "/default.aspx" )
&& ! string . IsNullOrWhiteSpace ( httpContext . Request . QueryString [ "path" ]))
{
//the path is the original path that the request came in on before we've rewritten it,
//this is required because TransferRequest does not maintain the httpcontext
var path = httpContext . Request . QueryString [ "path" ];
var qry = httpContext . Request . QueryString [ "qry" ];
Uri uri ;
try
{
uri = UriUtility . ToFullUrl ( path + qry , httpContext );
}
catch ( Exception ex )
{
//if this fails then the path could not be parsed to a Uri which could be something malicious
LogHelper . Error < UmbracoModule >( string . Format ( "Could not parse the path {0} into a full Uri" , path ), ex );
return false ;
}
//create request based objects (one per http request)...
//create a content store
var contentStore = new ContentStore ( umbracoContext );
//create the nice urls
var niceUrls = new NiceUrlProvider ( contentStore , umbracoContext );
//create the RoutingContext
var routingContext = new RoutingContext (
umbracoContext ,
docLookups ,
lastChanceLookup ,
contentStore ,
niceUrls );
// create the new document request which will cleanup the uri once and for all
var docreq = new DocumentRequest ( uri , routingContext );
// initialize the DocumentRequest on the UmbracoContext (this is circular dependency but i think in this case is ok)
umbracoContext . DocumentRequest = docreq ;
// note - at that point the original legacy module did something do handle IIS custom 404 errors
// ie pages looking like /anything.aspx?404;/path/to/document - I guess the reason was to support
// "directory urls" without having to do wildcard mapping to ASP.NET on old IIS. This is a pain
// to maintain and probably not used anymore - removed as of 06/2012. @zpqrtbnk.
//
// to trigger Umbraco's not-found, one should configure IIS and/or ASP.NET custom 404 errors
// so that they point to a non-existing page eg /redirect-404.aspx
docreq . LookupDomain ();
if ( docreq . IsRedirect )
httpContext . Response . Redirect ( docreq . RedirectUrl , true );
Thread . CurrentThread . CurrentUICulture = Thread . CurrentThread . CurrentCulture = docreq . Culture ;
docreq . LookupDocument ();
if ( docreq . IsRedirect )
httpContext . Response . Redirect ( docreq . RedirectUrl , true );
if ( docreq . Is404 )
httpContext . Response . StatusCode = 404 ;
// it is up to default.aspx to figure out what to display in case
// there is no document (ugly 404 page?) or no template (blank page?)
return true ;
}
return false ;
}
2012-07-20 01:04:35 +06:00
2012-08-06 23:04:08 +06:00
/// <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 )
{
2012-08-07 02:33:08 +06:00
var toIgnore = new [] { ".js" , ".css" , ".ico" };
2012-08-06 23:04:08 +06:00
return toIgnore . Any ( x => Path . GetExtension ( url . LocalPath ). InvariantEquals ( x ));
}
2012-08-06 22:40:06 +06:00
/// <summary>
/// Checks the current request and ensures that it is routable based on the structure of the request and URI
/// </summary>
/// <param name="uri"></param>
/// <param name="lpath"></param>
/// <param name="httpContext"></param>
/// <returns></returns>
2012-08-06 23:04:08 +06:00
internal bool EnsureUmbracoRoutablePage ( Uri uri , string lpath , HttpContextBase httpContext )
2012-08-06 22:40:06 +06:00
{
// ensure this is a document request
if (! EnsureDocumentRequest ( httpContext , uri , lpath ))
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 ;
// ensure that its not a base rest handler
if (( UmbracoSettings . EnableBaseRestHandler ) && ! EnsureNotBaseRestHandler ( lpath ))
return false ;
return true ;
}
2012-07-30 22:52:59 +06:00
/// <summary>
2012-07-29 00:04:11 +06:00
/// Entry point for a request
2012-07-30 22:52:59 +06:00
/// </summary>
/// <param name="httpContext"></param>
void ProcessRequest ( HttpContextBase httpContext )
{
LogHelper . Debug < UmbracoModule >( "Start processing request" );
2012-08-07 02:33:08 +06:00
if ( IsClientSideRequest ( httpContext . Request . Url ))
2012-08-06 22:40:06 +06:00
{
2012-08-07 02:33:08 +06:00
LogHelper . Debug < UmbracoModule >( "End processing request, not transfering to handler, this is a client side file request {0}" , () => httpContext . Request . Url );
2012-08-06 22:40:06 +06:00
return ;
2012-08-06 23:04:08 +06:00
}
2012-08-06 22:40:06 +06:00
2012-07-30 22:52:59 +06:00
// add Umbraco's signature header
if (! UmbracoSettings . RemoveUmbracoVersionHeader )
httpContext . Response . AddHeader ( "X-Umbraco-Version" , string . Format ( "{0}.{1}" , GlobalSettings . VersionMajor , GlobalSettings . VersionMinor ));
2012-07-20 01:04:35 +06:00
2012-07-21 00:47:17 +06:00
//create the legacy UmbracoContext
2012-08-07 02:33:08 +06:00
global :: umbraco . presentation . UmbracoContext . Current = new global :: umbraco . presentation . UmbracoContext ( httpContext );
//create the LegacyRequestInitializer
var legacyRequestInitializer = new LegacyRequestInitializer ( httpContext . Request . Url , httpContext );
// legacy - initialize legacy stuff
legacyRequestInitializer . InitializeRequest ();
2012-07-21 00:47:17 +06:00
2012-07-30 22:52:59 +06:00
//create the UmbracoContext singleton, one per request!!
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-07-20 01:04:35 +06:00
2012-08-07 02:33:08 +06:00
//Does a check to see if this current request contains the information in order to process the
//request as a front-end request. If not, then its because the rewrite hasn't taken place yet.
//if we need to rewrite, then we'll cleanup the query strings and rewrite to the front end handler.
if (! ProcessFrontEndDocumentRequest ( httpContext , umbracoContext ,
2012-08-01 09:49:10 +06:00
DocumentLookupsResolver . Current . DocumentLookups ,
2012-08-07 02:33:08 +06:00
LastChanceLookupResolver . Current . LastChanceLookup ))
2012-08-06 23:04:08 +06:00
{
2012-08-07 02:33:08 +06:00
var uri = httpContext . Request . Url ;
var lpath = uri . AbsolutePath . ToLower ();
2012-07-20 01:04:35 +06:00
2012-08-07 02:33:08 +06:00
// legacy - no idea what this is
LegacyCleanUmbPageFromQueryString ( ref uri , ref lpath );
2012-07-20 01:04:35 +06:00
2012-08-07 02:33:08 +06:00
//do not continue if this request is not a front-end routable page
if ( EnsureUmbracoRoutablePage ( uri , lpath , httpContext ))
{
RewriteToPath < Page >( HttpContext . Current , lpath , uri . Query );
//RewriteToPath<Page>(HttpContext.Current, "", docreq.Uri.Query);
}
else
{
LogHelper . Debug < UmbracoModule >( "End processing request, not transfering to handler {0}" , () => uri );
}
}
2012-07-20 01:04:35 +06:00
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 )
{
content . Instance . PersistXmlToFile ();
}
}
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>
/// <param name="lpath"></param>
/// <returns></returns>
2012-07-30 22:52:59 +06:00
bool EnsureDocumentRequest ( HttpContextBase httpContext , Uri uri , string lpath )
{
var maybeDoc = true ;
2012-07-20 01:04:35 +06:00
2012-07-30 22:52:59 +06:00
// handle directory-urls used for asmx
// legacy - what's the point really?
if ( maybeDoc && GlobalSettings . UseDirectoryUrls )
2012-07-28 23:40:48 +06:00
{
2012-07-30 22:52:59 +06:00
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 ;
}
}
2012-07-28 23:40:48 +06:00
2012-07-30 22:52:59 +06:00
// a document request should be
// /foo/bar/nil
// /foo/bar/nil/
// /foo/bar/nil.aspx
// where /foo is not a reserved path
2012-07-20 01:04:35 +06:00
2012-07-30 22:52:59 +06:00
// 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 ;
if (! maybeDoc )
{
LogHelper . Warn < UmbracoModule >( "Not a document" );
}
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 )
{
// ensure we are ready
if (! ApplicationContext . Current . IsReady )
{
2012-07-28 23:40:48 +06:00
LogHelper . Warn < UmbracoModule >( "Umbraco is not ready" );
2012-07-30 22:52:59 +06:00
httpContext . Response . StatusCode = 503 ;
2012-07-28 23:53:07 +06:00
// fixme - default.aspx has to be ready for RequestContext.DocumentRequest==null
// fixme - in fact we should transfer to an empty html page...
2012-07-30 22:52:59 +06:00
var bootUrl = UriUtility . ToAbsolute ( UmbracoSettings . BootSplashPage );
2012-07-28 23:53:07 +06:00
if ( UmbracoSettings . EnableSplashWhileLoading ) // legacy - should go
2012-07-30 22:52:59 +06:00
{
2012-07-28 23:40:48 +06:00
var configPath = UriUtility . ToAbsolute ( SystemDirectories . Config );
2012-07-30 22:52:59 +06:00
bootUrl = string . Format ( "{0}/splashes/booting.aspx?url={1}" , configPath , HttpUtility . UrlEncode ( uri . ToString ()));
// fixme ?orgurl=... ?retry=...
}
2012-07-20 01:04:35 +06:00
2012-08-07 02:33:08 +06:00
//RewriteToPath<Page>(HttpContext.Current, bootUrl, "", "");
//TransferRequest(bootUrl, httpContext);
httpContext . RewritePath ( bootUrl );
2012-07-30 22:52:59 +06:00
return false ;
}
2012-07-20 01:04:35 +06:00
2012-07-30 22:52:59 +06:00
return true ;
}
// 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-07-20 01:04:35 +06:00
2012-07-30 22:52:59 +06:00
// checks if the current request is a /base REST handler request
// returns false if it is, otherwise true
2012-08-06 22:40:06 +06:00
bool EnsureNotBaseRestHandler ( string lpath )
2012-07-30 22:52:59 +06:00
{
// the /base REST handler still lives in umbraco.dll and has
// not been refactored at the moment. it still is a module,
// although it should be a handler, or it should be replaced
// by clean WebAPI.
2012-07-20 01:04:35 +06:00
2012-07-30 22:52:59 +06:00
// fixme - do it once when initializing the module
string baseUrl = UriUtility . ToAbsolute ( SystemDirectories . Base ). ToLower ();
if (! baseUrl . EndsWith ( "/" ))
baseUrl += "/" ;
if ( lpath . StartsWith ( baseUrl ))
{
2012-07-28 23:40:48 +06:00
LogHelper . Debug < UmbracoModule >( "Detected /base REST handler" );
2012-07-30 22:52:59 +06:00
return false ;
}
return true ;
}
2012-07-20 01:04:35 +06:00
2012-08-07 02:33:08 +06:00
private static void RewriteToPath < THandler >( HttpContext context , string currentPath , string currentQuery )
where THandler : IHttpHandler
2012-07-30 22:52:59 +06:00
{
2012-07-28 23:40:48 +06:00
2012-08-07 02:33:08 +06:00
if (( context . CurrentHandler is THandler )) return ;
2012-07-20 01:04:35 +06:00
2012-08-07 02:33:08 +06:00
var rewritePath = "~/default.aspx?path="
+ context . Server . UrlEncode ( currentPath )
+ "&qry="
+ context . Server . UrlEncode ( currentQuery );
2012-07-20 01:04:35 +06:00
2012-08-07 02:33:08 +06:00
if ( currentPath . StartsWith ( rewritePath , StringComparison . InvariantCultureIgnoreCase )) return ;
2012-07-20 01:04:35 +06:00
2012-08-07 02:33:08 +06:00
var isMvc = TypeHelper . IsTypeAssignableFrom < THandler >( typeof ( MvcHandler ));
2012-07-30 22:52:59 +06:00
2012-08-07 02:33:08 +06:00
LogHelper . Debug < UmbracoModule >( "Transfering to " + rewritePath );
2012-07-30 22:52:59 +06:00
2012-08-07 02:33:08 +06:00
if ( HttpRuntime . UsingIntegratedPipeline )
{
context . Server . TransferRequest ( rewritePath , true );
}
2012-07-30 22:52:59 +06:00
else
2012-08-07 02:33:08 +06:00
{
// Pre MVC 3
context . RewritePath ( rewritePath , false );
if ( isMvc )
{
IHttpHandler httpHandler = new MvcHttpHandler ();
httpHandler . ProcessRequest ( context );
}
}
2012-07-30 22:52:59 +06:00
}
2012-07-20 01:04:35 +06:00
2012-08-07 02:33:08 +06:00
//// transfers the request using the fastest method available on the server
//void TransferRequest(string path, HttpContextBase httpContext)
//{
// LogHelper.Debug<UmbracoModule>("Transfering to " + path);
// var integrated = HttpRuntime.UsingIntegratedPipeline;
// // fixme - are we doing this properly?
// // fixme - handle virtual directory?
// // fixme - this does not work 'cos it resets the HttpContext
// // so we should move the DocumentRequest stuff etc back to default.aspx?
// // but, also, with TransferRequest, auth & co will run on the new (default.aspx) url,
// // is that really what we want? I need to talk about it with others. @zpqrtbnk
// // NOTE: SD: Need to look at how umbraMVCo does this. It is true that the TransferRequest initializes a new HttpContext,
// // what we need to do is when we transfer to the handler we send a query string with the found page Id to be looked up
// // after we have done our routing check. This however needs some though as we don't want to have to query for this
// // page twice. Again, I'll check how umbraMVCo is doing it as I had though about that when i created it :)
// integrated = false;
// // http://msmvps.com/blogs/luisabreu/archive/2007/10/09/are-you-using-the-new-transferrequest.aspx
// // http://msdn.microsoft.com/en-us/library/aa344903.aspx
// // http://forums.iis.net/t/1146511.aspx
// if (integrated)
// httpContext.Server.TransferRequest(path);
// else
// httpContext.RewritePath(path);
//}
2012-07-29 00:04:11 +06:00
2012-07-30 22:52:59 +06:00
#region Legacy
2012-07-20 01:04:35 +06:00
2012-07-30 22:52:59 +06:00
// "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...
void LegacyCleanUmbPageFromQueryString ( ref Uri uri , ref string lpath )
{
string receivedQuery = uri . Query ;
string path = uri . AbsolutePath ;
string query = null ;
2012-07-20 01:04:35 +06:00
2012-07-30 22:52:59 +06:00
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
2012-07-20 01:04:35 +06:00
2012-07-30 22:52:59 +06:00
// --added when refactoring--
uri = uri . Rewrite ( path , query );
lpath = path . ToLower ();
}
//else
//{
// // strip off question mark
// query = receivedQuery.Substring(1);
//}
}
}
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
#region IHttpModule
2012-07-20 01:04:35 +06:00
2012-07-30 22:52:59 +06:00
// initialize the module, this will trigger for each new application
// and there may be more than 1 application per application domain
public void Init ( HttpApplication app )
{
2012-07-20 22:02:28 +06:00
// used to be done in PostAuthorizeRequest but then it disabled OutputCaching due
// to rewriting happening too early in the chain (Alex Norcliffe 2010-02).
2012-08-06 22:40:06 +06:00
//app.PostResolveRequestCache += (sender, e) =>
2012-08-07 02:33:08 +06:00
2012-08-06 22:40:06 +06:00
//SD: changed to post map request handler so we can know what the handler actually is, this is a better fit for
//when we handle the routing
app . PostMapRequestHandler += ( sender , e ) =>
2012-07-20 22:02:28 +06:00
{
2012-07-29 00:04:11 +06:00
var httpContext = (( HttpApplication ) sender ). Context ;
ProcessRequest ( new HttpContextWrapper ( httpContext ));
2012-07-20 22:02:28 +06: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-07-29 00:04:11 +06:00
var httpContext = (( HttpApplication ) sender ). Context ;
PersistXmlCache ( new HttpContextWrapper ( httpContext ));
2012-07-30 22:52:59 +06:00
};
2012-07-29 00:04:11 +06:00
2012-07-30 22:52:59 +06:00
// todo: initialize request errors handler
}
2012-07-20 01:04:35 +06:00
2012-07-30 22:52:59 +06:00
public void Dispose ()
{ }
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
}