From b68bcba85e50f803e06f5eafcc83050375820eb0 Mon Sep 17 00:00:00 2001 From: "shannon@ShandemVaio" Date: Tue, 7 Aug 2012 02:33:08 +0600 Subject: [PATCH] Updated UmbracoModule to support the TransferRequest just like umbraMVCo does using query strings. Added more unit tests for UmbracoModule, refactored the Umbraco.Web.Routing.Domains to not have static methods and created an interface for it so that we can unit test it. Changed DocumentRequest stuff to internal. Finally got unit test working for the module --- .hgignore | 1 + src/Umbraco.Core/StringExtensions.cs | 5 + src/Umbraco.Tests/BusinessLogic/BaseTest.cs | 4 +- .../TestHelpers/FakeHttpContextFactory.cs | 3 + src/Umbraco.Tests/UmbracoModuleTests.cs | 114 +++++++- .../{Domains.cs => DefaultDomainHelper.cs} | 4 +- .../Routing/DefaultLastChanceLookup.cs | 24 +- src/Umbraco.Web/Routing/DocumentRequest.cs | 6 +- src/Umbraco.Web/Routing/IDocumentLookup.cs | 2 +- src/Umbraco.Web/Routing/LookupByNiceUrl.cs | 2 +- .../Routing/LookupByNiceUrlAndTemplate.cs | 2 +- src/Umbraco.Web/Routing/NiceUrlProvider.cs | 8 +- src/Umbraco.Web/Umbraco.Web.csproj | 2 +- src/Umbraco.Web/UmbracoContext.cs | 8 + src/Umbraco.Web/UmbracoModule.cs | 258 ++++++++++++------ src/Umbraco.Web/UriUtility.cs | 25 ++ src/umbraco.cms/businesslogic/web/Access.cs | 8 +- src/umbraco.cms/businesslogic/web/Domain.cs | 8 + 18 files changed, 371 insertions(+), 113 deletions(-) rename src/Umbraco.Web/Routing/{Domains.cs => DefaultDomainHelper.cs} (96%) diff --git a/.hgignore b/.hgignore index 4df9ef46fd..6f19e4c261 100644 --- a/.hgignore +++ b/.hgignore @@ -44,3 +44,4 @@ build/UmbracoCms.zip src/Umbraco.Tests/config/applications.config src/Umbraco.Tests/config/trees.config src/Umbraco.Web.UI/web.config +src/Umbraco.Tests/config/404handlers.config diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs index d186a29019..6d31e094ac 100644 --- a/src/Umbraco.Core/StringExtensions.cs +++ b/src/Umbraco.Core/StringExtensions.cs @@ -405,6 +405,11 @@ namespace Umbraco.Core return String.Equals(compare, compareTo, StringComparison.InvariantCultureIgnoreCase); } + public static bool InvariantStartsWith(this string compare, string compareTo) + { + return compare.StartsWith(compareTo, StringComparison.InvariantCultureIgnoreCase); + } + public static bool InvariantContains(this string compare, string compareTo) { return compare.IndexOf(compareTo, StringComparison.OrdinalIgnoreCase) >= 0; diff --git a/src/Umbraco.Tests/BusinessLogic/BaseTest.cs b/src/Umbraco.Tests/BusinessLogic/BaseTest.cs index 0fe0cb4059..6fec1c5962 100644 --- a/src/Umbraco.Tests/BusinessLogic/BaseTest.cs +++ b/src/Umbraco.Tests/BusinessLogic/BaseTest.cs @@ -10,7 +10,7 @@ using GlobalSettings = umbraco.GlobalSettings; namespace Umbraco.Tests.BusinessLogic { - [TestFixture] + [TestFixture, RequiresSTA] public abstract class BaseTest { /// @@ -47,6 +47,8 @@ namespace Umbraco.Tests.BusinessLogic { ConfigurationManager.AppSettings.Set("umbracoDbDSN", @"datalayer=SQLCE4Umbraco.SqlCEHelper,SQLCE4Umbraco;data source=|DataDirectory|\Umbraco.sdf"); + ClearDatabase(); + var dataHelper = DataLayerHelper.CreateSqlHelper(GlobalSettings.DbDSN); var installer = dataHelper.Utility.CreateInstaller(); if (installer.CanConnect) diff --git a/src/Umbraco.Tests/TestHelpers/FakeHttpContextFactory.cs b/src/Umbraco.Tests/TestHelpers/FakeHttpContextFactory.cs index 572157ff98..d1cbec30af 100644 --- a/src/Umbraco.Tests/TestHelpers/FakeHttpContextFactory.cs +++ b/src/Umbraco.Tests/TestHelpers/FakeHttpContextFactory.cs @@ -60,6 +60,9 @@ namespace Umbraco.Tests.TestHelpers request.Stub(x => x.ApplicationPath).Return("/"); request.Stub(x => x.Cookies).Return(new HttpCookieCollection()); request.Stub(x => x.ServerVariables).Return(new NameValueCollection()); + var queryStrings = HttpUtility.ParseQueryString(fullUrl.Query); + request.Stub(x => x.QueryString).Return(queryStrings); + request.Stub(x => x.Form).Return(new NameValueCollection()); //Cache var cache = MockRepository.GenerateMock(); diff --git a/src/Umbraco.Tests/UmbracoModuleTests.cs b/src/Umbraco.Tests/UmbracoModuleTests.cs index e4d36eaa34..aab8759929 100644 --- a/src/Umbraco.Tests/UmbracoModuleTests.cs +++ b/src/Umbraco.Tests/UmbracoModuleTests.cs @@ -1,13 +1,28 @@ using System; +using System.Collections.Generic; using System.Configuration; +using System.IO; +using System.Linq; +using System.Xml; using NUnit.Framework; +using SqlCE4Umbraco; using Umbraco.Core; using Umbraco.Tests.TestHelpers; using Umbraco.Web; +using Umbraco.Web.Media.ThumbnailProviders; +using Umbraco.Web.Routing; +using umbraco.BusinessLogic; +using umbraco.DataLayer; +using umbraco.IO; +using umbraco.cms.businesslogic.cache; +using umbraco.cms.businesslogic.language; +using umbraco.cms.businesslogic.template; +using umbraco.cms.businesslogic.web; +using GlobalSettings = umbraco.GlobalSettings; namespace Umbraco.Tests { - [TestFixture] + [TestFixture, RequiresSTA] public class UmbracoModuleTests { private UmbracoModule _module; @@ -24,6 +39,19 @@ namespace Umbraco.Tests ConfigurationManager.AppSettings.Set("umbracoConfigurationStatus", Umbraco.Core.Configuration.GlobalSettings.CurrentVersion); ConfigurationManager.AppSettings.Set("umbracoReservedPaths", "~/umbraco,~/install/"); ConfigurationManager.AppSettings.Set("umbracoReservedUrls", "~/config/splashes/booting.aspx,~/install/default.aspx,~/config/splashes/noNodes.aspx,~/VSEnterpriseHelper.axd"); + Cache.ClearAllCache(); + InitializeDatabase(); + + //create the not found handlers config + using(var sw = File.CreateText(IOHelper.MapPath(SystemFiles.NotFoundhandlersConfig, false))) + { + sw.Write(@" + + + + +"); + } } [TearDown] @@ -38,6 +66,30 @@ namespace Umbraco.Tests ConfigurationManager.AppSettings.Set("umbracoConfigurationStatus", ""); ConfigurationManager.AppSettings.Set("umbracoReservedPaths", ""); ConfigurationManager.AppSettings.Set("umbracoReservedUrls", ""); + ClearDatabase(); + Cache.ClearAllCache(); + } + + private void ClearDatabase() + { + var dataHelper = DataLayerHelper.CreateSqlHelper(GlobalSettings.DbDSN) as SqlCEHelper; + if (dataHelper == null) + throw new InvalidOperationException("The sql helper for unit tests must be of type SqlCEHelper, check the ensure the connection string used for this test is set to use SQLCE"); + dataHelper.ClearDatabase(); + } + + private void InitializeDatabase() + { + ConfigurationManager.AppSettings.Set("umbracoDbDSN", @"datalayer=SQLCE4Umbraco.SqlCEHelper,SQLCE4Umbraco;data source=|DataDirectory|\Umbraco.sdf"); + + ClearDatabase(); + + var dataHelper = DataLayerHelper.CreateSqlHelper(GlobalSettings.DbDSN); + var installer = dataHelper.Utility.CreateInstaller(); + if (installer.CanConnect) + { + installer.Install(); + } } [TestCase("/umbraco_client/Tree/treeIcons.css", false)] @@ -78,5 +130,65 @@ namespace Umbraco.Tests Assert.AreEqual(assert, result); } + [TestCase("/default.aspx?path=/", true)] + [TestCase("/default.aspx?path=/home.aspx", true)] + [TestCase("/default.aspx?path=/home.aspx?altTemplate=blah", true)] + public void Process_Front_End_Document_Request(string url, bool assert) + { + var httpContextFactory = new FakeHttpContextFactory(url); + var httpContext = httpContextFactory.HttpContext; + var umbracoContext = new UmbracoContext(httpContext, ApplicationContext.Current, new DefaultRoutesCache(false)); + + StateHelper.HttpContext = httpContext; + + //because of so much dependency on the db, we need to create som stuff here, i originally abstracted out stuff but + //was turning out to be quite a deep hole because ultimately we'd have to abstract the old 'Domain' and 'Language' classes + Domain.MakeNew("Test.com", 1000, Language.GetByCultureCode("en-US").id); + + //need to create a template with id 1045 + var template = Template.MakeNew("test", new User(0)); + + SetupUmbracoContextForTest(umbracoContext, template); + + var result = _module.ProcessFrontEndDocumentRequest( + httpContext, + umbracoContext, + new IDocumentLookup[] {new LookupByNiceUrl()}, + new DefaultLastChanceLookup()); + + Assert.AreEqual(assert, result); + } + + + private void SetupUmbracoContextForTest(UmbracoContext umbracoContext, Template template) + { + umbracoContext.GetXmlDelegate = () => + { + var xDoc = new XmlDocument(); + + //create a custom xml structure to return + + xDoc.LoadXml(@" + + +]> + + + + + + + + + + + + +"); + //return the custom x doc + return xDoc; + }; + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Routing/Domains.cs b/src/Umbraco.Web/Routing/DefaultDomainHelper.cs similarity index 96% rename from src/Umbraco.Web/Routing/Domains.cs rename to src/Umbraco.Web/Routing/DefaultDomainHelper.cs index 72454c3718..d230962a34 100644 --- a/src/Umbraco.Web/Routing/Domains.cs +++ b/src/Umbraco.Web/Routing/DefaultDomainHelper.cs @@ -11,7 +11,7 @@ namespace Umbraco.Web.Routing /// /// Provides utilities to handle domains. /// - public class Domains + internal class DomainHelper { /// /// Represents an Umbraco domain and its normalized uri. @@ -20,7 +20,7 @@ namespace Umbraco.Web.Routing /// In Umbraco it is valid to create domains with name such as example.com, https://www.example.com, example.com/foo/. /// The normalized uri of a domain begins with a scheme and ends with no slash, eg http://example.com/, https://www.example.com/, http://example.com/foo/. /// - public class DomainAndUri + internal class DomainAndUri { /// /// The Umbraco domain. diff --git a/src/Umbraco.Web/Routing/DefaultLastChanceLookup.cs b/src/Umbraco.Web/Routing/DefaultLastChanceLookup.cs index d17d1c299a..19ea5c7a75 100644 --- a/src/Umbraco.Web/Routing/DefaultLastChanceLookup.cs +++ b/src/Umbraco.Web/Routing/DefaultLastChanceLookup.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Reflection; using System.Web; using System.Xml; +using Umbraco.Core.Logging; using umbraco.IO; using umbraco.interfaces; @@ -14,7 +15,6 @@ namespace Umbraco.Web.Routing /// internal class DefaultLastChanceLookup : IDocumentLastChanceLookup { - static TraceSource _trace = new TraceSource("DefaultLastChanceLookup"); /// /// Tries to find and assign an Umbraco document to a DocumentRequest. @@ -33,7 +33,8 @@ namespace Umbraco.Web.Routing XmlNode HandlePageNotFound(DocumentRequest docRequest) { - HttpContext.Current.Trace.Write("NotFoundHandler", string.Format("Running for url='{0}'.", docRequest.Uri.AbsolutePath)); + LogHelper.Debug("Running for url='{0}'.", () => docRequest.Uri.AbsolutePath); + XmlNode currentPage = null; foreach (var handler in GetNotFoundHandlers()) @@ -45,8 +46,7 @@ namespace Umbraco.Web.Routing // FIXME - could it be null? - HttpContext.Current.Trace.Write("NotFoundHandler", - string.Format("Handler '{0}' found node with id={1}.", handler.GetType().FullName, handler.redirectID)); + LogHelper.Debug("Handler '{0}' found node with id={1}.", () => handler.GetType().FullName, () => handler.redirectID); //// check for caching //if (handler.CacheUrl) @@ -77,7 +77,7 @@ namespace Umbraco.Web.Routing // initialize handlers // create the definition cache - HttpContext.Current.Trace.Write("NotFoundHandler", "Registering custom handlers."); + LogHelper.Debug("Registering custom handlers."); _customHandlerTypes = new List(); @@ -101,17 +101,19 @@ namespace Umbraco.Web.Routing ns = nsAttr.Value; Type type = null; - HttpContext.Current.Trace.Write("NotFoundHandler", - string.Format("Registering '{0}.{1},{2}'.", ns, typeName, assemblyName)); - + LogHelper.Debug("Registering '{0}.{1},{2}'.", () => ns, () => typeName, () => assemblyName); + try { + //TODO: This isn't a good way to load the assembly, its already in the Domain so we should be getting the type + // this loads the assembly into the wrong assembly load context!! + var assembly = Assembly.LoadFrom(IOHelper.MapPath(SystemDirectories.Bin + "/" + assemblyName + ".dll")); type = assembly.GetType(ns + "." + typeName); } catch (Exception e) { - HttpContext.Current.Trace.Warn("NotFoundHandler", "Error registering handler, ignoring.", e); + LogHelper.Error("Error registering handler, ignoring.", e); } if (type != null) @@ -142,9 +144,7 @@ namespace Umbraco.Web.Routing } catch (Exception e) { - HttpContext.Current.Trace.Warn("NotFoundHandler", - string.Format("Error instanciating handler {0}, ignoring.", type.FullName), - e); + LogHelper.Error(string.Format("Error instanciating handler {0}, ignoring.", type.FullName), e); } } diff --git a/src/Umbraco.Web/Routing/DocumentRequest.cs b/src/Umbraco.Web/Routing/DocumentRequest.cs index 6806861d96..9b36dd0eec 100644 --- a/src/Umbraco.Web/Routing/DocumentRequest.cs +++ b/src/Umbraco.Web/Routing/DocumentRequest.cs @@ -22,9 +22,9 @@ namespace Umbraco.Web.Routing /// represents a request for one specified Umbraco document to be rendered /// by one specified template, using one particular culture. /// - public class DocumentRequest + internal class DocumentRequest { - public DocumentRequest(Uri uri, RoutingContext routingContext) + public DocumentRequest(Uri uri, RoutingContext routingContext) { this.Uri = uri; RoutingContext = routingContext; @@ -159,7 +159,7 @@ namespace Umbraco.Web.Routing LogHelper.Debug("{0}Uri=\"{1}\"", () => tracePrefix, () => this.Uri); // try to find a domain matching the current request - var domainAndUri = Domains.DomainMatch(Domain.GetDomains(), RoutingContext.UmbracoContext.UmbracoUrl, false); + var domainAndUri = DomainHelper.DomainMatch(Domain.GetDomains(), RoutingContext.UmbracoContext.UmbracoUrl, false); // handle domain if (domainAndUri != null) diff --git a/src/Umbraco.Web/Routing/IDocumentLookup.cs b/src/Umbraco.Web/Routing/IDocumentLookup.cs index b84d9b37e2..17d74098ee 100644 --- a/src/Umbraco.Web/Routing/IDocumentLookup.cs +++ b/src/Umbraco.Web/Routing/IDocumentLookup.cs @@ -3,7 +3,7 @@ namespace Umbraco.Web.Routing /// /// Provides a method to try to find an assign an Umbraco document to a DocumentRequest. /// - public interface IDocumentLookup + internal interface IDocumentLookup { /// /// Tries to find and assign an Umbraco document to a DocumentRequest. diff --git a/src/Umbraco.Web/Routing/LookupByNiceUrl.cs b/src/Umbraco.Web/Routing/LookupByNiceUrl.cs index 4bea29e415..2b7c2d2d14 100644 --- a/src/Umbraco.Web/Routing/LookupByNiceUrl.cs +++ b/src/Umbraco.Web/Routing/LookupByNiceUrl.cs @@ -24,7 +24,7 @@ namespace Umbraco.Web.Routing { string route; if (docreq.HasDomain) - route = docreq.Domain.RootNodeId.ToString() + Domains.PathRelativeToDomain(docreq.DomainUri, docreq.Uri.AbsolutePath); + route = docreq.Domain.RootNodeId.ToString() + DomainHelper.PathRelativeToDomain(docreq.DomainUri, docreq.Uri.AbsolutePath); else route = docreq.Uri.AbsolutePath; diff --git a/src/Umbraco.Web/Routing/LookupByNiceUrlAndTemplate.cs b/src/Umbraco.Web/Routing/LookupByNiceUrlAndTemplate.cs index 58ba1a904b..2e83212b71 100644 --- a/src/Umbraco.Web/Routing/LookupByNiceUrlAndTemplate.cs +++ b/src/Umbraco.Web/Routing/LookupByNiceUrlAndTemplate.cs @@ -28,7 +28,7 @@ namespace Umbraco.Web.Routing string path = docreq.Uri.AbsolutePath; if (docreq.HasDomain) - path = Domains.PathRelativeToDomain(docreq.DomainUri, path); + path = DomainHelper.PathRelativeToDomain(docreq.DomainUri, path); if (path != "/") // no template if "/" { var pos = docreq.Uri.AbsolutePath.LastIndexOf('/'); diff --git a/src/Umbraco.Web/Routing/NiceUrlProvider.cs b/src/Umbraco.Web/Routing/NiceUrlProvider.cs index a124ba1d13..a48f38b719 100644 --- a/src/Umbraco.Web/Routing/NiceUrlProvider.cs +++ b/src/Umbraco.Web/Routing/NiceUrlProvider.cs @@ -25,11 +25,11 @@ namespace Umbraco.Web.Routing public NiceUrlProvider(ContentStore contentStore, UmbracoContext umbracoContext) { _umbracoContext = umbracoContext; - _contentStore = contentStore; + _contentStore = contentStore; } private readonly UmbracoContext _umbracoContext; - private readonly ContentStore _contentStore; + private readonly ContentStore _contentStore; // note: this could be a parameter... const string UrlNameProperty = "@urlName"; @@ -207,7 +207,7 @@ namespace Umbraco.Web.Routing return null; // apply filter on domains defined on that node - var domainAndUri = Domains.DomainMatch(Domain.GetDomainsById(nodeId), current, true); + var domainAndUri = DomainHelper.DomainMatch(Domain.GetDomainsById(nodeId), current, true); return domainAndUri == null ? null : domainAndUri.Uri; } @@ -217,7 +217,7 @@ namespace Umbraco.Web.Routing if (nodeId <= 0) return new Uri[] { }; - var domainAndUris = Domains.DomainMatches(Domain.GetDomainsById(nodeId), current); + var domainAndUris = DomainHelper.DomainMatches(Domain.GetDomainsById(nodeId), current); return domainAndUris.Select(d => d.Uri); } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index de9c357565..0e62ba8839 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -258,7 +258,7 @@ - + diff --git a/src/Umbraco.Web/UmbracoContext.cs b/src/Umbraco.Web/UmbracoContext.cs index f96497f45a..6d50265d4a 100644 --- a/src/Umbraco.Web/UmbracoContext.cs +++ b/src/Umbraco.Web/UmbracoContext.cs @@ -189,6 +189,14 @@ namespace Umbraco.Web return GetXmlDelegate(); } + /// + /// Boolean value indicating whether the current request is a front-end umbraco request + /// + public bool IsFrontEndUmbracoRequest + { + get { return DocumentRequest != null; } + } + /// /// Gets/sets the DocumentRequest object /// diff --git a/src/Umbraco.Web/UmbracoModule.cs b/src/Umbraco.Web/UmbracoModule.cs index a2589d5bba..88b5eed2e2 100644 --- a/src/Umbraco.Web/UmbracoModule.cs +++ b/src/Umbraco.Web/UmbracoModule.cs @@ -1,9 +1,12 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; using System.Web; +using System.Web.Mvc; +using System.Web.UI; using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Web.Routing; @@ -22,6 +25,87 @@ namespace Umbraco.Web public class UmbracoModule : IHttpModule { + /// + /// 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 + /// + /// + /// + /// + /// + internal bool ProcessFrontEndDocumentRequest( + HttpContextBase httpContext, + UmbracoContext umbracoContext, + IEnumerable 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(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; + } /// /// This is a performance tweak to check if this is a .css, .js or .ico file request since @@ -32,7 +116,7 @@ namespace Umbraco.Web /// internal bool IsClientSideRequest(Uri url) { - var toIgnore = new[] {".js", ".css", ".ico"}; + var toIgnore = new[] { ".js", ".css", ".ico" }; return toIgnore.Any(x => Path.GetExtension(url.LocalPath).InvariantEquals(x)); } @@ -69,12 +153,9 @@ namespace Umbraco.Web { LogHelper.Debug("Start processing request"); - var uri = httpContext.Request.Url; - var lpath = uri.AbsolutePath.ToLower(); - - if (IsClientSideRequest(uri)) + if (IsClientSideRequest(httpContext.Request.Url)) { - LogHelper.Debug("End processing request, not transfering to handler, this is a client side file request {0}", () => uri); + LogHelper.Debug("End processing request, not transfering to handler, this is a client side file request {0}", () => httpContext.Request.Url); return; } @@ -83,8 +164,12 @@ namespace Umbraco.Web httpContext.Response.AddHeader("X-Umbraco-Version", string.Format("{0}.{1}", GlobalSettings.VersionMajor, GlobalSettings.VersionMinor)); //create the legacy UmbracoContext - global::umbraco.presentation.UmbracoContext.Current - = new global::umbraco.presentation.UmbracoContext(httpContext); + 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(); //create the UmbracoContext singleton, one per request!! var umbracoContext = new UmbracoContext( @@ -93,66 +178,32 @@ namespace Umbraco.Web RoutesCacheResolver.Current.RoutesCache); UmbracoContext.Current = umbracoContext; - //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, + //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, DocumentLookupsResolver.Current.DocumentLookups, - LastChanceLookupResolver.Current.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; - - //create the LegacyRequestInitializer - var legacyRequestInitializer = new LegacyRequestInitializer(httpContext.Request.Url, httpContext); - // legacy - initialize legacy stuff - legacyRequestInitializer.InitializeRequest(); - - // 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 - - //do not continue if this request is not a front-end routable page - if (!EnsureUmbracoRoutablePage(uri, lpath, httpContext)) + LastChanceLookupResolver.Current.LastChanceLookup)) { - LogHelper.Debug("End processing request, not transfering to handler {0}", () => uri); - return; - } + var uri = httpContext.Request.Url; + var lpath = uri.AbsolutePath.ToLower(); - // legacy - no idea what this is - LegacyCleanUmbPageFromQueryString(ref uri, ref lpath); + // legacy - no idea what this is + LegacyCleanUmbPageFromQueryString(ref uri, ref lpath); - //**THERE** we should create the doc request - // before, we're not sure we handling a doc request - 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); + //do not continue if this request is not a front-end routable page + if (EnsureUmbracoRoutablePage(uri, lpath, httpContext)) + { + RewriteToPath(HttpContext.Current, lpath, uri.Query); + //RewriteToPath(HttpContext.Current, "", docreq.Uri.Query); + } + else + { + LogHelper.Debug("End processing request, not transfering to handler {0}", () => uri); + } + } - if (docreq.Is404) - httpContext.Response.StatusCode = 404; - TransferRequest("~/default.aspx" + docreq.Uri.Query, httpContext); - - // 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?) } /// @@ -245,7 +296,11 @@ namespace Umbraco.Web // fixme ?orgurl=... ?retry=... } - TransferRequest(bootUrl, httpContext); + //RewriteToPath(HttpContext.Current, bootUrl, "", ""); + //TransferRequest(bootUrl, httpContext); + + httpContext.RewritePath(bootUrl); + return false; } @@ -291,37 +346,70 @@ namespace Umbraco.Web return true; } - // transfers the request using the fastest method available on the server - void TransferRequest(string path, HttpContextBase httpContext) + private static void RewriteToPath(HttpContext context, string currentPath, string currentQuery) + where THandler : IHttpHandler { - LogHelper.Debug("Transfering to " + path); - var integrated = HttpRuntime.UsingIntegratedPipeline; + if ((context.CurrentHandler is THandler)) return; - // 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 + var rewritePath = "~/default.aspx?path=" + + context.Server.UrlEncode(currentPath) + + "&qry=" + + context.Server.UrlEncode(currentQuery); - // 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 :) + if (currentPath.StartsWith(rewritePath, StringComparison.InvariantCultureIgnoreCase)) return; - integrated = false; + var isMvc = TypeHelper.IsTypeAssignableFrom(typeof(MvcHandler)); - // 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 + LogHelper.Debug("Transfering to " + rewritePath); - if (integrated) - httpContext.Server.TransferRequest(path); + if (HttpRuntime.UsingIntegratedPipeline) + { + context.Server.TransferRequest(rewritePath, true); + } else - httpContext.RewritePath(path); + { + // Pre MVC 3 + context.RewritePath(rewritePath, false); + if (isMvc) + { + IHttpHandler httpHandler = new MvcHttpHandler(); + httpHandler.ProcessRequest(context); + } + } } + //// transfers the request using the fastest method available on the server + //void TransferRequest(string path, HttpContextBase httpContext) + //{ + // LogHelper.Debug("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); + //} + #region Legacy @@ -380,7 +468,7 @@ namespace Umbraco.Web // used to be done in PostAuthorizeRequest but then it disabled OutputCaching due // to rewriting happening too early in the chain (Alex Norcliffe 2010-02). //app.PostResolveRequestCache += (sender, e) => - + //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) => diff --git a/src/Umbraco.Web/UriUtility.cs b/src/Umbraco.Web/UriUtility.cs index ee21225554..b88948666a 100644 --- a/src/Umbraco.Web/UriUtility.cs +++ b/src/Umbraco.Web/UriUtility.cs @@ -157,5 +157,30 @@ namespace Umbraco.Web } #endregion + + /// + /// Returns an faull url with the host, port, etc... + /// + /// An absolute path (i.e. starts with a '/' ) + /// + /// + /// + /// Based on http://stackoverflow.com/questions/3681052/get-absolute-url-from-relative-path-refactored-method + /// + internal static Uri ToFullUrl(string absolutePath, HttpContextBase httpContext) + { + if (httpContext == null) throw new ArgumentNullException("httpContext"); + if (string.IsNullOrEmpty(absolutePath)) + throw new ArgumentNullException("absolutePath"); + + if (!absolutePath.StartsWith("/")) + throw new FormatException("The absolutePath specified does not start with a '/'"); + + + var url = httpContext.Request.Url; + var port = url.Port != 80 ? (":" + url.Port) : String.Empty; + + return new Uri(string.Format("{0}://{1}{2}{3}", url.Scheme, url.Host, port, absolutePath)); + } } } \ No newline at end of file diff --git a/src/umbraco.cms/businesslogic/web/Access.cs b/src/umbraco.cms/businesslogic/web/Access.cs index 3a3d7c09ca..bc66364a0d 100644 --- a/src/umbraco.cms/businesslogic/web/Access.cs +++ b/src/umbraco.cms/businesslogic/web/Access.cs @@ -24,7 +24,8 @@ namespace umbraco.cms.businesslogic.web static private Hashtable _checkedPages = new Hashtable(); - static private XmlDocument _accessXmlContent; + //must be volatile for double check lock to work + static private volatile XmlDocument _accessXmlContent; static private string _accessXmlSource; private static void clearCheckPages() @@ -54,6 +55,11 @@ namespace umbraco.cms.businesslogic.web if (!System.IO.File.Exists(_accessXmlSource)) { + var file = new FileInfo(_accessXmlSource); + if (!Directory.Exists(file.DirectoryName)) + { + Directory.CreateDirectory(file.Directory.FullName); //ensure the folder exists! + } System.IO.FileStream f = System.IO.File.Open(_accessXmlSource, FileMode.Create); System.IO.StreamWriter sw = new StreamWriter(f); sw.WriteLine(""); diff --git a/src/umbraco.cms/businesslogic/web/Domain.cs b/src/umbraco.cms/businesslogic/web/Domain.cs index 0ba9361175..c65be24612 100644 --- a/src/umbraco.cms/businesslogic/web/Domain.cs +++ b/src/umbraco.cms/businesslogic/web/Domain.cs @@ -29,6 +29,14 @@ namespace umbraco.cms.businesslogic.web get { return Application.SqlHelper; } } + /// + /// Empty ctor used for unit tests to create a custom domain + /// + internal Domain() + { + + } + public Domain(int Id) { initDomain(Id);