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);