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-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 ;
// 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 ;
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-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 ;
}
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 ;
}
// 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.
// 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-30 22:52:59 +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-30 22:52:59 +06:00
2012-08-07 02:33:08 +06:00
if ( currentPath . StartsWith ( rewritePath , StringComparison . InvariantCultureIgnoreCase ) ) return ;
2012-07-30 22:52:59 +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-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-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...
void LegacyCleanUmbPageFromQueryString ( ref Uri uri , ref string lpath )
{
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 ) ;
lpath = path . ToLower ( ) ;
}
//else
//{
// // strip off question mark
// query = receivedQuery.Substring(1);
//}
}
}
#endregion
#region IHttpModule
// 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
}