diff --git a/src/Umbraco.Tests/Routing/SiteDomainHelperTests.cs b/src/Umbraco.Tests/Routing/SiteDomainHelperTests.cs new file mode 100644 index 0000000000..6c5e4cfb3b --- /dev/null +++ b/src/Umbraco.Tests/Routing/SiteDomainHelperTests.cs @@ -0,0 +1,309 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NUnit.Framework; +using Umbraco.Tests.TestHelpers; +using Umbraco.Web.Routing; +using umbraco.cms.businesslogic.web; +using System.Reflection; + +namespace Umbraco.Tests.Routing +{ + [TestFixture] + public class SiteDomainHelperTests + { + [SetUp] + public void SetUp() + { + SiteDomainHelper.Clear(); // assuming this works! + } + + [Test] + public void AddSites() + { + SiteDomainHelper.AddSite("site1", "domain1.com", "domain1.net", "domain1.org"); + SiteDomainHelper.AddSite("site2", "domain2.com", "domain2.net", "domain2.org"); + + var sites = SiteDomainHelper.Sites; + + Assert.AreEqual(2, sites.Count); + + Assert.Contains("site1", sites.Keys); + Assert.Contains("site2", sites.Keys); + + var domains = sites["site1"]; + Assert.AreEqual(3, domains.Count()); + Assert.Contains("domain1.com", domains); + Assert.Contains("domain1.net", domains); + Assert.Contains("domain1.org", domains); + + domains = sites["site2"]; + Assert.AreEqual(3, domains.Count()); + Assert.Contains("domain2.com", domains); + Assert.Contains("domain2.net", domains); + Assert.Contains("domain2.org", domains); + } + + [TestCase("foo")] // that one is suspect + [TestCase("domain.com")] + [TestCase("domain.com/")] + [TestCase("domain.com:12")] + [TestCase("domain.com:12/")] + [TestCase("http://www.domain.com")] + [TestCase("http://www.domain.com:12")] + [TestCase("http://www.domain.com:12/")] + [TestCase("https://foo.www.domain.com")] + [TestCase("https://foo.www.domain.com:5478/")] + public void AddValidSite(string domain) + { + SiteDomainHelper.AddSite("site1", domain); + } + + [TestCase("domain.com/foo")] + [TestCase("http:/domain.com")] + [TestCase("*")] + public void AddInvalidSite(string domain) + { + Assert.Throws(() => SiteDomainHelper.AddSite("site1", domain)); + } + + [Test] + public void AddRemoveSites() + { + SiteDomainHelper.AddSite("site1", "domain1.com", "domain1.net", "domain1.org"); + SiteDomainHelper.AddSite("site2", "domain2.com", "domain2.net", "domain2.org"); + + var sites = SiteDomainHelper.Sites; + + SiteDomainHelper.RemoveSite("site1"); + SiteDomainHelper.RemoveSite("site3"); + + Assert.AreEqual(1, sites.Count); + + Assert.Contains("site2", sites.Keys); + } + + [Test] + public void AddSiteAgain() + { + SiteDomainHelper.AddSite("site1", "domain1.com", "domain1.net", "domain1.org"); + SiteDomainHelper.AddSite("site1", "domain2.com", "domain1.net"); + + var sites = SiteDomainHelper.Sites; + + Assert.AreEqual(1, sites.Count); + + Assert.Contains("site1", sites.Keys); + + var domains = sites["site1"]; + Assert.AreEqual(2, domains.Count()); + Assert.Contains("domain2.com", domains); + Assert.Contains("domain1.net", domains); + } + + [Test] + public void BindSitesOnce() + { + SiteDomainHelper.AddSite("site1", "domain1.com", "domain1.net", "domain1.org"); + SiteDomainHelper.AddSite("site2", "domain2.com", "domain2.net", "domain2.org"); + SiteDomainHelper.AddSite("site3", "domain3.com", "domain3.net", "domain3.org"); + SiteDomainHelper.AddSite("site4", "domain4.com", "domain4.net", "domain4.org"); + + SiteDomainHelper.BindSites("site1", "site2"); + + var bindings = SiteDomainHelper.Bindings; + + Assert.AreEqual(2, bindings.Count); + Assert.Contains("site1", bindings.Keys); + Assert.Contains("site2", bindings.Keys); + + var others = bindings["site1"]; + Assert.AreEqual(1, others.Count); + Assert.Contains("site2", others); + + others = bindings["site2"]; + Assert.AreEqual(1, others.Count); + Assert.Contains("site1", others); + } + + [Test] + public void BindMoreSites() + { + SiteDomainHelper.AddSite("site1", "domain1.com", "domain1.net", "domain1.org"); + SiteDomainHelper.AddSite("site2", "domain2.com", "domain2.net", "domain2.org"); + SiteDomainHelper.AddSite("site3", "domain3.com", "domain3.net", "domain3.org"); + SiteDomainHelper.AddSite("site4", "domain4.com", "domain4.net", "domain4.org"); + + SiteDomainHelper.BindSites("site1", "site2"); + SiteDomainHelper.BindSites("site1", "site3"); + + var bindings = SiteDomainHelper.Bindings; + + Assert.AreEqual(3, bindings.Count); + Assert.Contains("site1", bindings.Keys); + Assert.Contains("site2", bindings.Keys); + Assert.Contains("site3", bindings.Keys); + + var others = bindings["site1"]; + Assert.AreEqual(2, others.Count); + Assert.Contains("site2", others); + Assert.Contains("site3", others); + + others = bindings["site2"]; + Assert.AreEqual(2, others.Count); + Assert.Contains("site1", others); + Assert.Contains("site3", others); + + others = bindings["site3"]; + Assert.AreEqual(2, others.Count); + Assert.Contains("site1", others); + Assert.Contains("site2", others); + } + + [Test] + public void MapDomain() + { + SiteDomainHelper.AddSite("site1", "domain1.com", "domain1.net", "domain1.org"); + SiteDomainHelper.AddSite("site2", "domain2.com", "domain2.net", "domain2.org"); + SiteDomainHelper.AddSite("site3", "domain3.com", "domain3.net", "domain3.org"); + SiteDomainHelper.AddSite("site4", "domain4.com", "domain4.net", "domain4.org"); + + //SiteDomainHelper.BindSites("site1", "site3"); + //SiteDomainHelper.BindSites("site2", "site4"); + + // map methods are not static because we can override them + var helper = new SiteDomainHelper(); + + // current is a site1 uri, domains contain current + // so we'll get current + // + var current = new Uri("http://domain1.com/foo/bar"); + var output = helper.MapDomain(current, new[] + { + new DomainAndUri(new MockDomain("domain1.com"), Uri.UriSchemeHttp), + new DomainAndUri(new MockDomain("domain2.com"), Uri.UriSchemeHttp), + }).Uri.ToString(); + Assert.AreEqual("http://domain1.com/", output); + + // current is a site1 uri, domains do not contain current + // so we'll get the corresponding site1 domain + // + current = new Uri("http://domain1.com/foo/bar"); + output = helper.MapDomain(current, new[] + { + new DomainAndUri(new MockDomain("domain1.net"), Uri.UriSchemeHttp), + new DomainAndUri(new MockDomain("domain2.net"), Uri.UriSchemeHttp) + }).Uri.ToString(); + Assert.AreEqual("http://domain1.net/", output); + + // current is a site1 uri, domains do not contain current + // so we'll get the corresponding site1 domain + // order does not matter + // + current = new Uri("http://domain1.com/foo/bar"); + output = helper.MapDomain(current, new[] + { + new DomainAndUri(new MockDomain("domain2.net"), Uri.UriSchemeHttp), + new DomainAndUri(new MockDomain("domain1.net"), Uri.UriSchemeHttp) + }).Uri.ToString(); + Assert.AreEqual("http://domain1.net/", output); + } + + [Test] + public void MapDomains() + { + SiteDomainHelper.AddSite("site1", "domain1.com", "domain1.net", "domain1.org"); + SiteDomainHelper.AddSite("site2", "domain2.com", "domain2.net", "domain2.org"); + SiteDomainHelper.AddSite("site3", "domain3.com", "domain3.net", "domain3.org"); + SiteDomainHelper.AddSite("site4", "domain4.com", "domain4.net", "domain4.org"); + + // map methods are not static because we can override them + var helper = new SiteDomainHelper(); + + // the rule is: + // - exclude the current domain + // - exclude what MapDomain would return + // - return all domains from same site, or bound sites + + // current is a site1 uri, domains contains current + // + var current = new Uri("http://domain1.com/foo/bar"); + var output = helper.MapDomains(current, new[] + { + new DomainAndUri(new MockDomain("domain1.com"), Uri.UriSchemeHttp), // no: current + what MapDomain would pick + new DomainAndUri(new MockDomain("domain2.com"), Uri.UriSchemeHttp), // no: not same site + new DomainAndUri(new MockDomain("domain3.com"), Uri.UriSchemeHttp), // no: not same site + new DomainAndUri(new MockDomain("domain4.com"), Uri.UriSchemeHttp), // no: not same site + new DomainAndUri(new MockDomain("domain1.org"), Uri.UriSchemeHttp), // yes: same site (though bogus setup) + }).ToArray(); + + Assert.AreEqual(1, output.Count()); + Assert.Contains("http://domain1.org/", output.Select(d => d.Uri.ToString()).ToArray()); + + // current is a site1 uri, domains does not contain current + // + current = new Uri("http://domain1.com/foo/bar"); + output = helper.MapDomains(current, new[] + { + new DomainAndUri(new MockDomain("domain1.net"), Uri.UriSchemeHttp), // no: what MapDomain would pick + new DomainAndUri(new MockDomain("domain2.com"), Uri.UriSchemeHttp), // no: not same site + new DomainAndUri(new MockDomain("domain3.com"), Uri.UriSchemeHttp), // no: not same site + new DomainAndUri(new MockDomain("domain4.com"), Uri.UriSchemeHttp), // no: not same site + new DomainAndUri(new MockDomain("domain1.org"), Uri.UriSchemeHttp), // yes: same site (though bogus setup) + }).ToArray(); + + Assert.AreEqual(1, output.Count()); + Assert.Contains("http://domain1.org/", output.Select(d => d.Uri.ToString()).ToArray()); + + SiteDomainHelper.BindSites("site1", "site3"); + SiteDomainHelper.BindSites("site2", "site4"); + + // current is a site1 uri, domains contains current + // + current = new Uri("http://domain1.com/foo/bar"); + output = helper.MapDomains(current, new[] + { + new DomainAndUri(new MockDomain("domain1.com"), Uri.UriSchemeHttp), // no: current + what MapDomain would pick + new DomainAndUri(new MockDomain("domain2.com"), Uri.UriSchemeHttp), // no: not same site + new DomainAndUri(new MockDomain("domain3.com"), Uri.UriSchemeHttp), // yes: bound site + new DomainAndUri(new MockDomain("domain3.org"), Uri.UriSchemeHttp), // yes: bound site + new DomainAndUri(new MockDomain("domain4.com"), Uri.UriSchemeHttp), // no: not same site + new DomainAndUri(new MockDomain("domain1.org"), Uri.UriSchemeHttp), // yes: same site (though bogus setup) + }).ToArray(); + + Assert.AreEqual(3, output.Count()); + Assert.Contains("http://domain1.org/", output.Select(d => d.Uri.ToString()).ToArray()); + Assert.Contains("http://domain3.com/", output.Select(d => d.Uri.ToString()).ToArray()); + Assert.Contains("http://domain3.org/", output.Select(d => d.Uri.ToString()).ToArray()); + + // current is a site1 uri, domains does not contain current + // + current = new Uri("http://domain1.com/foo/bar"); + output = helper.MapDomains(current, new[] + { + new DomainAndUri(new MockDomain("domain1.net"), Uri.UriSchemeHttp), // no: what MapDomain would pick + new DomainAndUri(new MockDomain("domain2.com"), Uri.UriSchemeHttp), // no: not same site + new DomainAndUri(new MockDomain("domain3.com"), Uri.UriSchemeHttp), // yes: bound site + new DomainAndUri(new MockDomain("domain3.org"), Uri.UriSchemeHttp), // yes: bound site + new DomainAndUri(new MockDomain("domain4.com"), Uri.UriSchemeHttp), // no: not same site + new DomainAndUri(new MockDomain("domain1.org"), Uri.UriSchemeHttp), // yes: same site (though bogus setup) + }).ToArray(); + + Assert.AreEqual(3, output.Count()); + Assert.Contains("http://domain1.org/", output.Select(d => d.Uri.ToString()).ToArray()); + Assert.Contains("http://domain3.com/", output.Select(d => d.Uri.ToString()).ToArray()); + Assert.Contains("http://domain3.org/", output.Select(d => d.Uri.ToString()).ToArray()); + } + + class MockDomain : Domain + { + private static readonly FieldInfo NameField = typeof (Domain).GetField("_name", BindingFlags.Instance | BindingFlags.NonPublic); + + public MockDomain(string name) + { + NameField.SetValue(this, name); + } + } + } +} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 8cc0669248..5d3acd7706 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -285,6 +285,7 @@ + diff --git a/src/Umbraco.Web/Routing/AliasUrlProvider.cs b/src/Umbraco.Web/Routing/AliasUrlProvider.cs index 5d935f3865..7aeb129948 100644 --- a/src/Umbraco.Web/Routing/AliasUrlProvider.cs +++ b/src/Umbraco.Web/Routing/AliasUrlProvider.cs @@ -10,6 +10,12 @@ namespace Umbraco.Web.Routing /// internal class AliasUrlProvider : IUrlProvider { + // note - at the moment we seem to accept pretty much anything as an alias + // without any form of validation ... could even prob. kill the XPath ... + // ok, this is somewhat experimental and is NOT enabled by default + + #region GetUrl + /// /// Gets the nice url of a published content. /// @@ -29,18 +35,9 @@ namespace Umbraco.Web.Routing return null; // we have nothing to say } - const string UmbracoUrlAlias = "umbracoUrlAlias"; + #endregion - private bool FindByUrlAliasEnabled - { - get - { - var hasFinder = ContentFinderResolver.Current.ContainsType(); - var hasHandler = ContentFinderResolver.Current.ContainsType() - && NotFoundHandlerHelper.CustomHandlerTypes.Contains(typeof(global::umbraco.SearchForAlias)); - return hasFinder || hasHandler; - } - } + #region GetOtherUrls /// /// Gets the other urls of a published content. @@ -66,32 +63,51 @@ namespace Umbraco.Web.Routing if (string.IsNullOrWhiteSpace(umbracoUrlName)) return Enumerable.Empty(); - /* - - // walk up from that node until we hit a node with a domain, - // or we reach the content root, collecting urls in the way var n = node; - Uri domainUri = DomainUriAtNode(n.Id, current); - while (domainUri == null && n != null) // n is null at root + var domainUris = DomainHelper.DomainsForNode(n.Id, current); + while (domainUris == null && n != null) // n is null at root { - // get the url - var urlName = n.UrlName; - // move to parent node n = n.Parent; - domainUri = n == null ? null : DomainUriAtNode(n.Id, current); + domainUris = n == null ? null : DomainHelper.DomainsForNode(n.Id, current); } - if (domainUri == null) - return new string[] { "/" + umbracoUrlName }; - else - // fuck - there may be MANY domains actually!! - return null; - * - */ + var path = "/" + umbracoUrlName; - // just for fun - return new[] { "/" + umbracoUrlName }; + if (domainUris == null) + { + var uri = new Uri(path, UriKind.Relative); + return new[] { UriUtility.UriFromUmbraco(uri).ToString() }; + } + + return domainUris + .Select(domainUri => new Uri(CombinePaths(domainUri.Uri.GetLeftPart(UriPartial.Path), path))) + .Select(uri => UriUtility.UriFromUmbraco(uri).ToString()); } + + #endregion + + #region Utilities + + const string UmbracoUrlAlias = "umbracoUrlAlias"; + + private bool FindByUrlAliasEnabled + { + get + { + var hasFinder = ContentFinderResolver.Current.ContainsType(); + var hasHandler = ContentFinderResolver.Current.ContainsType() + && NotFoundHandlerHelper.CustomHandlerTypes.Contains(typeof(global::umbraco.SearchForAlias)); + return hasFinder || hasHandler; + } + } + + string CombinePaths(string path1, string path2) + { + string path = path1.TrimEnd('/') + path2; + return path == "/" ? path : path.TrimEnd('/'); + } + + #endregion } } diff --git a/src/Umbraco.Web/Routing/ContentFinderByNiceUrl.cs b/src/Umbraco.Web/Routing/ContentFinderByNiceUrl.cs index 5689239569..226ec9ffe2 100644 --- a/src/Umbraco.Web/Routing/ContentFinderByNiceUrl.cs +++ b/src/Umbraco.Web/Routing/ContentFinderByNiceUrl.cs @@ -94,7 +94,8 @@ namespace Umbraco.Web.Routing docreq.PublishedContent = node; LogHelper.Debug("Query matches, id={0}", () => docreq.PublishedContent.Id); - var iscanon = _doDomainLookup && !DomainHelper.ExistsDomainInPath(docreq.Domain, node.Path); + var rootNodeId = docreq.Domain == null ? (int?) null : docreq.Domain.RootNodeId; + var iscanon = _doDomainLookup && !DomainHelper.ExistsDomainInPath(DomainHelper.GetAllDomains(), node.Path, rootNodeId); if (!iscanon) LogHelper.Debug("Non canonical url"); diff --git a/src/Umbraco.Web/Routing/DefaultUrlProvider.cs b/src/Umbraco.Web/Routing/DefaultUrlProvider.cs index b2a4ecb882..dd0f8c73e2 100644 --- a/src/Umbraco.Web/Routing/DefaultUrlProvider.cs +++ b/src/Umbraco.Web/Routing/DefaultUrlProvider.cs @@ -32,7 +32,7 @@ namespace Umbraco.Web.Routing /// public virtual string GetUrl(UmbracoContext umbracoContext, IPublishedContentStore contentCache, int id, Uri current, bool absolute) { - Uri domainUri; + DomainAndUri domainUri; string path; if (!current.IsAbsoluteUri) @@ -51,7 +51,7 @@ namespace Umbraco.Web.Routing // route is / or / int pos = route.IndexOf('/'); path = pos == 0 ? route : route.Substring(pos); - domainUri = pos == 0 ? null : DomainUriAtNode(int.Parse(route.Substring(0, pos)), current); + domainUri = pos == 0 ? null : DomainHelper.DomainForNode(int.Parse(route.Substring(0, pos)), current); } else { @@ -70,7 +70,7 @@ namespace Umbraco.Web.Routing // or we reach the content root, collecting urls in the way var pathParts = new List(); var n = node; - domainUri = DomainUriAtNode(n.Id, current); + domainUri = DomainHelper.DomainForNode(n.Id, current); while (domainUri == null && n != null) // n is null at root { // get the url @@ -79,7 +79,7 @@ namespace Umbraco.Web.Routing // move to parent node n = n.Parent; - domainUri = n == null ? null : DomainUriAtNode(n.Id, current); + domainUri = n == null ? null : DomainHelper.DomainForNode(n.Id, current); } // no domain, respect HideTopLevelNodeFromPath for legacy purposes @@ -119,7 +119,7 @@ namespace Umbraco.Web.Routing public virtual IEnumerable GetOtherUrls(UmbracoContext umbracoContext, IPublishedContentStore contentCache, int id, Uri current) { string path; - IEnumerable domainUris; + IEnumerable domainUris; // will not read cache if previewing! var route = umbracoContext.InPreviewMode @@ -132,7 +132,7 @@ namespace Umbraco.Web.Routing // route is / or / int pos = route.IndexOf('/'); path = pos == 0 ? route : route.Substring(pos); - domainUris = pos == 0 ? null : DomainUrisAtNode(int.Parse(route.Substring(0, pos)), current); + domainUris = pos == 0 ? null : DomainHelper.DomainsForNode(int.Parse(route.Substring(0, pos)), current); } else { @@ -151,7 +151,7 @@ namespace Umbraco.Web.Routing // or we reach the content root, collecting urls in the way var pathParts = new List(); var n = node; - domainUris = DomainUrisAtNode(n.Id, current); + domainUris = DomainHelper.DomainsForNode(n.Id, current); while (domainUris == null && n != null) // n is null at root { // get the url @@ -160,7 +160,7 @@ namespace Umbraco.Web.Routing // move to parent node n = n.Parent; - domainUris = n == null ? null : DomainUrisAtNode(n.Id, current); + domainUris = n == null ? null : DomainHelper.DomainsForNode(n.Id, current); } // no domain, respect HideTopLevelNodeFromPath for legacy purposes @@ -185,7 +185,7 @@ namespace Umbraco.Web.Routing #region Utilities - Uri AssembleUrl(Uri domainUri, string path, Uri current, bool absolute) + Uri AssembleUrl(DomainAndUri domainUri, string path, Uri current, bool absolute) { Uri uri; @@ -202,10 +202,10 @@ namespace Umbraco.Web.Routing { // a domain was found : return an absolute or relative url // ignore vdir at that point - if (!absolute && current != null && domainUri.GetLeftPart(UriPartial.Authority) == current.GetLeftPart(UriPartial.Authority)) - uri = new Uri(CombinePaths(domainUri.AbsolutePath, path), UriKind.Relative); // relative + if (!absolute && current != null && domainUri.Uri.GetLeftPart(UriPartial.Authority) == current.GetLeftPart(UriPartial.Authority)) + uri = new Uri(CombinePaths(domainUri.Uri.AbsolutePath, path), UriKind.Relative); // relative else - uri = new Uri(CombinePaths(domainUri.GetLeftPart(UriPartial.Path), path)); // absolute + uri = new Uri(CombinePaths(domainUri.Uri.GetLeftPart(UriPartial.Path), path)); // absolute } // UriFromUmbraco will handle vdir @@ -220,7 +220,7 @@ namespace Umbraco.Web.Routing } // always build absolute urls unless we really cannot - IEnumerable AssembleUrls(IEnumerable domainUris, string path) + IEnumerable AssembleUrls(IEnumerable domainUris, string path) { // no domain == no "other" url if (domainUris == null) @@ -228,64 +228,13 @@ namespace Umbraco.Web.Routing // if no domain was found and then we have no "other" url // else return absolute urls, ignoring vdir at that point - var uris = domainUris.Select(domainUri => new Uri(CombinePaths(domainUri.GetLeftPart(UriPartial.Path), path))); + var uris = domainUris.Select(domainUri => new Uri(CombinePaths(domainUri.Uri.GetLeftPart(UriPartial.Path), path))); // UriFromUmbraco will handle vdir // meaning it will add vdir into domain urls too! return uris.Select(UriUtility.UriFromUmbraco); } - Uri DomainUriAtNode(int nodeId, Uri current) - { - // be safe - if (nodeId <= 0) - return null; - - // get the domains on that node - var domains = Domain.GetDomainsById(nodeId); - - // filter those that match - var domainAndUri = DomainHelper.DomainMatch(domains, current, domainAndUris => MapDomain(current, domainAndUris)); - - return domainAndUri == null ? null : domainAndUri.Uri; - } - - /// - /// Filters a list of DomainAndUri to pick one that best matches the current request. - /// - /// The list of DomainAndUri to filter. - /// The Uri of the current request. - /// The selected DomainAndUri. - /// - /// If the filter is invoked then is _not_ empty and - /// is _not_ null, and could not be - /// matched with anything in . - /// The filter _must_ return something else an exception will be thrown. - /// - protected virtual DomainAndUri MapDomain(Uri current, DomainAndUri[] domainAndUris) - { - // all we can do at the moment - return domainAndUris.First(); - } - - Uri[] DomainUrisAtNode(int nodeId, Uri current) - { - // be safe - if (nodeId <= 0) - return null; - - var domainAndUris = DomainHelper.DomainMatches(Domain.GetDomainsById(nodeId), current).ToArray(); - - // if any, then filter them (and maybe return empty) else return null - return !domainAndUris.Any() ? null : MapDomains(current, domainAndUris).Select(d => d.Uri).ToArray(); - } - - protected virtual IEnumerable MapDomains(Uri current, DomainAndUri[] domainAndUris) - { - // all we can do at the moment - return domainAndUris; - } - static void ApplyHideTopLevelNodeFromPath(UmbracoContext umbracoContext, IPublishedContentStore contentCache, Core.Models.IPublishedContent node, IList pathParts) { // in theory if hideTopLevelNodeFromPath is true, then there should be only once diff --git a/src/Umbraco.Web/Routing/DomainHelper.cs b/src/Umbraco.Web/Routing/DomainHelper.cs index bf59cbe182..5abb59bc56 100644 --- a/src/Umbraco.Web/Routing/DomainHelper.cs +++ b/src/Umbraco.Web/Routing/DomainHelper.cs @@ -1,47 +1,108 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using Umbraco.Core; using umbraco.cms.businesslogic.web; namespace Umbraco.Web.Routing { - /// - /// Provides utilities to handle domains. - /// + /// + /// Provides utilities to handle domains. + /// internal class DomainHelper { - private static Domain SanitizeForBackwardCompatibility(Domain d) - { - // this is a _really_ nasty one that should be removed in 6.x - // some people were using hostnames such as "/en" which happened to work pre-4.10 - // but make _no_ sense at all... and 4.10 throws on them, so here we just try - // to find a way so 4.11 does not throw. - // but, really. - // no. - var context = System.Web.HttpContext.Current; - if (context != null && d.Name.StartsWith("/")) - { - // turn /en into http://whatever.com/en so it becomes a parseable uri - var authority = context.Request.Url.GetLeftPart(UriPartial.Authority); - d.Name = authority + d.Name; - } - return d; - } + #region Temp. abstract Umbraco's API /// - /// Finds the domain that best matches the current uri, into an enumeration of domains. + /// Gets all domains defined in the system. /// - /// The enumeration of Umbraco domains. - /// The uri of the current request, or null. + /// All domains defined in the system. + /// This is to temporarily abstract Umbraco's API. + internal static Domain[] GetAllDomains() + { + return Domain.GetDomains().ToArray(); + } + + /// + /// Gets all domains defined in the system at a specified node. + /// + /// The node identifier. + /// All domains defined in the system at the specified node. + /// This is to temporarily abstract Umbraco's API. + internal static Domain[] GetNodeDomains(int nodeId) + { + return Domain.GetDomainsById(nodeId); + } + + #endregion + + #region Domain for Node + + /// + /// Finds the domain for the specified node, if any, that best matches a specified uri. + /// + /// The node identifier. + /// The uri, or null. + /// The domain and its uri, if any, that best matches the specified uri. + internal static DomainAndUri DomainForNode(int nodeId, Uri current) + { + // be safe + if (nodeId <= 0) + return null; + + // get the domains on that node + var domains = GetNodeDomains(nodeId); + + // filter those that match + var helper = SiteDomainHelperResolver.Current.Helper; + var domainAndUri = DomainForUri(domains, current, domainAndUris => helper.MapDomain(current, domainAndUris)); + + // return null or the uri + return domainAndUri; + } + + /// + /// Find the domains for the specified node, if any, that match a specified uri. + /// + /// The node identifier. + /// The uri, or null. + /// The domains and their uris, that match the specified uri. + internal static IEnumerable DomainsForNode(int nodeId, Uri current) + { + // be safe + if (nodeId <= 0) + return null; + + // get the domains on that node + var domains = GetNodeDomains(nodeId); + + // filter those that match + var domainAndUris = DomainsForUri(domains, current).ToArray(); + + // return null or a (maybe empty) enumerable of uris + var helper = SiteDomainHelperResolver.Current.Helper; + var domainAndUris2 = helper.MapDomains(current, domainAndUris).ToArray(); + return domainAndUris2.Any() ? domainAndUris2 : null; + } + + #endregion + + #region Domain for Uri + + /// + /// Finds the domain that best matches a specified uri, into a group of domains. + /// + /// The group of domains. + /// The uri, or null. /// A function to filter the list of domains, if more than one applies, or null. - /// The domain and its normalized uri, that best matches the current uri. + /// The domain and its normalized uri, that best matches the specified uri. /// /// If more than one domain matches, then the function is used to pick /// the right one, unless it is null, in which case the method returns null. /// The filter, if any, will be called only with a non-empty argument, and _must_ return something. /// - public static DomainAndUri DomainMatch(Domain[] domains, Uri current, Func filter = null) + internal static DomainAndUri DomainForUri(Domain[] domains, Uri current, Func filter = null) { // sanitize the list to have proper uris for comparison (scheme, path end with /) // we need to end with / because example.com/foo cannot match example.com/foobar @@ -84,73 +145,113 @@ namespace Umbraco.Web.Routing return domainAndUri; } - /// - /// Gets an enumeration of matching an enumeration of Umbraco domains. - /// - /// The enumeration of Umbraco domains. - /// The uri of the current request, or null. - /// The enumeration of matching the enumeration of Umbraco domains. - public static IEnumerable DomainMatches(Domain[] domains, Uri current) - { + /// + /// Gets the domains that match a specified uri, into a group of domains. + /// + /// The group of domains. + /// The uri, or null. + /// The domains and their normalized uris, that match the specified uri. + internal static IEnumerable DomainsForUri(Domain[] domains, Uri current) + { var scheme = current == null ? Uri.UriSchemeHttp : current.Scheme; - var domainsAndUris = domains - .Where(d => !d.IsWildcard) - .Select(SanitizeForBackwardCompatibility) + return domains + .Where(d => !d.IsWildcard) + .Select(SanitizeForBackwardCompatibility) .Select(d => new DomainAndUri(d, scheme)) .OrderByDescending(d => d.Uri.ToString()); - return domainsAndUris; - } + } - /// - /// Gets a value indicating whether there is another domain defined down in the path to a node under the current domain's root node. - /// - /// The current domain. - /// The path to a node under the current domain's root node. - /// A value indicating if there is another domain defined down in the path. - public static bool ExistsDomainInPath(Domain current, string path) - { - var domains = Domain.GetDomains(); - var stopNodeId = current == null ? -1 : current.RootNodeId; + #endregion - return path.Split(',') - .Reverse() - .Select(int.Parse) - .TakeWhile(id => id != stopNodeId) - .Any(id => domains.Any(d => d.RootNodeId == id && !d.IsWildcard)); - } + #region Utilities - /// - /// Gets the deepest wildcard in a node path. - /// - /// The Umbraco domains. - /// The node path. - /// The current domain root node identifier, or null. - /// The deepest wildcard in the path, or null. - public static Domain LookForWildcardDomain(Domain[] domains, string path, int? rootNodeId) - { - // "When you perform comparisons with nullable types, if the value of one of the nullable - // types is null and the other is not, all comparisons evaluate to false." + /// + /// Sanitize a Domain. + /// + /// The Domain to sanitize. + /// The sanitized domain. + /// This is a _really_ nasty one that should be removed at some point. Some people were + /// using hostnames such as "/en" which happened to work pre-4.10 but really make no sense at + /// all... and 4.10 throws on them, so here we just try to find a way so 4.11 does not throw. + /// But really... no. + private static Domain SanitizeForBackwardCompatibility(Domain domain) + { + var context = System.Web.HttpContext.Current; + if (context != null && domain.Name.StartsWith("/")) + { + // turn "/en" into "http://whatever.com/en" so it becomes a parseable uri + var authority = context.Request.Url.GetLeftPart(UriPartial.Authority); + domain.Name = authority + domain.Name; + } + return domain; + } + + /// + /// Gets a value indicating whether there is another domain defined down in the path to a node under the current domain's root node. + /// + /// The domains. + /// The path to a node under the current domain's root node eg '-1,1234,5678'. + /// The current domain root node identifier, or null. + /// A value indicating if there is another domain defined down in the path. + /// Looks _under_ rootNodeId but not _at_ rootNodeId. + internal static bool ExistsDomainInPath(Domain[] domains, string path, int? rootNodeId) + { + return FindDomainInPath(domains, path, rootNodeId) != null; + } - return path - .Split(',') - .Select(int.Parse) - .Skip(1) - .Reverse() - .TakeWhile(id => !rootNodeId.HasValue || id != rootNodeId) - .Select(nodeId => domains.FirstOrDefault(d => d.RootNodeId == nodeId && d.IsWildcard)) - .FirstOrDefault(domain => domain != null); - } + /// + /// Gets the deepest non-wildcard Domain, if any, from a group of Domains, in a node path. + /// + /// The domains. + /// The node path eg '-1,1234,5678'. + /// The current domain root node identifier, or null. + /// The deepest non-wildcard Domain in the path, or null. + /// Looks _under_ rootNodeId but not _at_ rootNodeId. + internal static Domain FindDomainInPath(Domain[] domains, string path, int? rootNodeId) + { + var stopNodeId = rootNodeId ?? -1; - /// - /// Returns the part of a path relative to the uri of a domain. - /// - /// The normalized uri of the domain. - /// The full path of the uri. - /// The path part relative to the uri of the domain. - /// Eg the relative part of /foo/bar/nil to domain example.com/foo is /bar/nil. - public static string PathRelativeToDomain(Uri domainUri, string path) - { - return path.Substring(domainUri.AbsolutePath.Length).EnsureStartsWith('/'); - } - } + return path.Split(',') + .Reverse() + .Select(int.Parse) + .TakeWhile(id => id != stopNodeId) + .Select(id => domains.FirstOrDefault(d => d.RootNodeId == id && !d.IsWildcard)) + .SkipWhile(domain => domain == null) + .FirstOrDefault(); + } + + /// + /// Gets the deepest wildcard Domain, if any, from a group of Domains, in a node path. + /// + /// The domains. + /// The node path eg '-1,1234,5678'. + /// The current domain root node identifier, or null. + /// The deepest wildcard Domain in the path, or null. + /// Looks _under_ rootNodeId but not _at_ rootNodeId. + internal static Domain FindWildcardDomainInPath(Domain[] domains, string path, int? rootNodeId) + { + var stopNodeId = rootNodeId ?? -1; + + return path.Split(',') + .Reverse() + .Select(int.Parse) + .TakeWhile(id => id != stopNodeId) + .Select(id => domains.FirstOrDefault(d => d.RootNodeId == id && d.IsWildcard)) + .FirstOrDefault(domain => domain != null); + } + + /// + /// Returns the part of a path relative to the uri of a domain. + /// + /// The normalized uri of the domain. + /// The full path of the uri. + /// The path part relative to the uri of the domain. + /// Eg the relative part of /foo/bar/nil to domain example.com/foo is /bar/nil. + internal static string PathRelativeToDomain(Uri domainUri, string path) + { + return path.Substring(domainUri.AbsolutePath.Length).EnsureStartsWith('/'); + } + + #endregion + } } diff --git a/src/Umbraco.Web/Routing/ISiteDomainHelper.cs b/src/Umbraco.Web/Routing/ISiteDomainHelper.cs new file mode 100644 index 0000000000..3f4cb3589f --- /dev/null +++ b/src/Umbraco.Web/Routing/ISiteDomainHelper.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Umbraco.Web.Routing +{ + /// + /// Provides utilities to handle site domains. + /// + internal interface ISiteDomainHelper + { + /// + /// Filters a list of DomainAndUri to pick one that best matches the current request. + /// + /// The Uri of the current request. + /// The list of DomainAndUri to filter. + /// The selected DomainAndUri. + /// + /// If the filter is invoked then is _not_ empty and + /// is _not_ null, and could not be + /// matched with anything in . + /// The filter _must_ return something else an exception will be thrown. + /// + DomainAndUri MapDomain(Uri current, DomainAndUri[] domainAndUris); + + /// + /// Filters a list of DomainAndUri to pick those that best matches the current request. + /// + /// The Uri of the current request. + /// The list of DomainAndUri to filter. + /// The selected DomainAndUri items. + /// The filter must return something, even empty, else an exception will be thrown. + IEnumerable MapDomains(Uri current, DomainAndUri[] domainAndUris); + } +} diff --git a/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs b/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs index c9e0fb491d..a800bf017e 100644 --- a/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs +++ b/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs @@ -166,7 +166,7 @@ namespace Umbraco.Web.Routing LogHelper.Debug("{0}Uri=\"{1}\"", () => tracePrefix, () => _pcr.Uri); // try to find a domain matching the current request - var domainAndUri = DomainHelper.DomainMatch(Domain.GetDomains().ToArray(), _pcr.Uri); + var domainAndUri = DomainHelper.DomainForUri(Domain.GetDomains().ToArray(), _pcr.Uri); // handle domain if (domainAndUri != null) @@ -216,7 +216,7 @@ namespace Umbraco.Web.Routing var nodePath = _pcr.PublishedContent.Path; LogHelper.Debug("{0}Path=\"{1}\"", () => tracePrefix, () => nodePath); var rootNodeId = _pcr.HasDomain ? _pcr.Domain.RootNodeId : (int?)null; - var domain = DomainHelper.LookForWildcardDomain(Domain.GetDomains().ToArray(), nodePath, rootNodeId); + var domain = DomainHelper.FindWildcardDomainInPath(Domain.GetDomains().ToArray(), nodePath, rootNodeId); if (domain != null) { diff --git a/src/Umbraco.Web/Routing/SiteDomainHelper.cs b/src/Umbraco.Web/Routing/SiteDomainHelper.cs new file mode 100644 index 0000000000..5a4bbdfea3 --- /dev/null +++ b/src/Umbraco.Web/Routing/SiteDomainHelper.cs @@ -0,0 +1,314 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Text.RegularExpressions; +using Umbraco.Core; +using umbraco.cms.businesslogic.web; + +namespace Umbraco.Web.Routing +{ + /// + /// Provides utilities to handle site domains. + /// + internal class SiteDomainHelper : ISiteDomainHelper + { + #region Configure + + private static readonly ReaderWriterLockSlim ConfigLock = new ReaderWriterLockSlim(); + private static Dictionary _sites; + private static Dictionary> _bindings; + private static Dictionary> _qualifiedSites; + + // these are for unit tests *only* + internal static Dictionary Sites { get { return _sites; } } + internal static Dictionary> Bindings { get { return _bindings; } } + + // these are for validation + //private const string DomainValidationSource = @"^(\*|((?i:http[s]?://)?([-\w]+(\.[-\w]+)*)(:\d+)?(/[-\w]*)?))$"; + private const string DomainValidationSource = @"^(((?i:http[s]?://)?([-\w]+(\.[-\w]+)*)(:\d+)?(/)?))$"; + private static readonly Regex DomainValidation = new Regex(DomainValidationSource, RegexOptions.IgnoreCase | RegexOptions.Compiled); + + /// + /// Returns a disposable object that represents safe write access to config. + /// + /// Should be used in a using(SiteDomainHelper.ConfigWriteLock) { ... } mode. + protected static IDisposable ConfigWriteLock + { + get { return new WriteLock(ConfigLock); } + } + + /// + /// Returns a disposable object that represents safe read access to config. + /// + /// Should be used in a using(SiteDomainHelper.ConfigWriteLock) { ... } mode. + protected static IDisposable ConfigReadLock + { + get { return new ReadLock(ConfigLock); } + } + + /// + /// Clears the entire configuration. + /// + public static void Clear() + { + using (ConfigWriteLock) + { + _sites = null; + _bindings = null; + _qualifiedSites = null; + } + } + + private static IEnumerable ValidateDomains(IEnumerable domains) + { + // must use authority format w/optional scheme and port, but no path + // any domain should appear only once + return domains.Select(domain => + { + if (!DomainValidation.IsMatch(domain)) + throw new ArgumentOutOfRangeException("domains", string.Format("Invalid domain: \"{0}\"", domain)); + return domain; + }); + } + + /// + /// Adds a site. + /// + /// A key uniquely identifying the site. + /// The site domains. + /// At the moment there is no public way to remove a site. Clear and reconfigure. + public static void AddSite(string key, IEnumerable domains) + { + using (ConfigWriteLock) + { + _sites = _sites ?? new Dictionary(); + _sites[key] = ValidateDomains(domains).ToArray(); + _qualifiedSites = null; + } + } + + /// + /// Adds a site. + /// + /// A key uniquely identifying the site. + /// The site domains. + /// At the moment there is no public way to remove a site. Clear and reconfigure. + public static void AddSite(string key, params string[] domains) + { + using (ConfigWriteLock) + { + _sites = _sites ?? new Dictionary(); + _sites[key] = ValidateDomains(domains).ToArray(); + _qualifiedSites = null; + } + } + + /// + /// Removes a site. + /// + /// A key uniquely identifying the site. + internal static void RemoveSite(string key) + { + using (ConfigWriteLock) + { + if (_sites != null && _sites.ContainsKey(key)) + { + _sites.Remove(key); + if (_sites.Count == 0) + _sites = null; + + if (_bindings != null && _bindings.ContainsKey(key)) + { + foreach (var b in _bindings[key]) + { + _bindings[b].Remove(key); + if (_bindings[b].Count == 0) + _bindings.Remove(b); + } + _bindings.Remove(key); + if (_bindings.Count > 0) + _bindings = null; + } + + _qualifiedSites = null; + } + } + } + + /// + /// Binds some sites. + /// + /// The keys uniquely identifying the sites to bind. + /// + /// At the moment there is no public way to unbind sites. Clear and reconfigure. + /// If site1 is bound to site2 and site2 is bound to site3 then site1 is bound to site3. + /// + public static void BindSites(params string[] keys) + { + using (ConfigWriteLock) + { + foreach (var key in keys.Where(key => !_sites.ContainsKey(key))) + throw new ArgumentException(string.Format("Not an existing site key: {0}", key), "keys"); + + _bindings = _bindings ?? new Dictionary>(); + + var allkeys = _bindings + .Where(kvp => keys.Contains(kvp.Key)) + .SelectMany(kvp => kvp.Value) + .Union(keys) + .ToArray(); + + foreach (var key in allkeys) + { + if (!_bindings.ContainsKey(key)) + _bindings[key] = new List(); + var xkey = key; + var addKeys = allkeys.Where(k => k != xkey).Except(_bindings[key]); + _bindings[key].AddRange(addKeys); + } + } + } + + #endregion + + #region Map domains + + /// + /// Filters a list of DomainAndUri to pick one that best matches the current request. + /// + /// The Uri of the current request. + /// The list of DomainAndUri to filter. + /// The selected DomainAndUri. + /// + /// If the filter is invoked then is _not_ empty and + /// is _not_ null, and could not be + /// matched with anything in . + /// The filter _must_ return something else an exception will be thrown. + /// + public virtual DomainAndUri MapDomain(Uri current, DomainAndUri[] domainAndUris) + { + var currentAuthority = current.GetLeftPart(UriPartial.Authority); + var qualifiedSites = GetQualifiedSites(current); + + return MapDomain(domainAndUris, qualifiedSites, currentAuthority); + } + + /// + /// Filters a list of DomainAndUri to pick those that best matches the current request. + /// + /// The Uri of the current request. + /// The list of DomainAndUri to filter. + /// The selected DomainAndUri items. + /// The filter must return something, even empty, else an exception will be thrown. + public virtual IEnumerable MapDomains(Uri current, DomainAndUri[] domainAndUris) + { + var currentAuthority = current.GetLeftPart(UriPartial.Authority); + KeyValuePair[] candidateSites; + IEnumerable ret; + + using (ConfigReadLock) // so nothing changes between GetQualifiedSites and access to bindings + { + var qualifiedSites = GetQualifiedSitesInsideLock(current); + + // exclude the current one (avoid producing the absolute equivalent of what GetUrl returns) + ret = domainAndUris.Where(d => d.Uri.GetLeftPart(UriPartial.Authority) != currentAuthority); + + // exclude the default one (avoid producing a possible duplicate of what GetUrl returns) + var mainDomain = MapDomain(domainAndUris, qualifiedSites, currentAuthority); // what GetUrl would get + ret = ret.Where(d => d != mainDomain); + + // we do our best, but can't do the impossible + if (qualifiedSites == null) + return ret; + + // find a site that contains the current authority + var currentSite = qualifiedSites.FirstOrDefault(site => site.Value.Contains(currentAuthority)); + + // if current belongs to a site, pick every element from domainAndUris that also belong + // to that site -- or to any site bound to that site + + candidateSites = new[] { currentSite }; + if (_bindings != null && _bindings.ContainsKey(currentSite.Key)) + { + var boundSites = qualifiedSites.Where(site => _bindings[currentSite.Key].Contains(site.Key)); + candidateSites = candidateSites.Union(boundSites).ToArray(); + + // .ToArray ensures it is evaluated before the configuration lock is exited + } + } + + return ret.Where(d => + { + var authority = d.Uri.GetLeftPart(UriPartial.Authority); + return candidateSites.Any(site => site.Value.Contains(authority)); + }); + } + + private static Dictionary GetQualifiedSites(Uri current) + { + using (ConfigReadLock) + { + return GetQualifiedSitesInsideLock(current); + } + } + + private static Dictionary GetQualifiedSitesInsideLock(Uri current) + { + // we do our best, but can't do the impossible + if (_sites == null) + return null; + + // cached? + if (_qualifiedSites != null && _qualifiedSites.ContainsKey(current.Scheme)) + return _qualifiedSites[current.Scheme]; + + _qualifiedSites = _qualifiedSites ?? new Dictionary>(); + + // convert sites into authority sites based upon current scheme + // because some domains in the sites might not have a scheme -- and cache + return _qualifiedSites[current.Scheme] = _sites + .ToDictionary( + kvp => kvp.Key, + kvp => kvp.Value.Select(d => new Uri(UriUtility.StartWithScheme(d, current.Scheme)).GetLeftPart(UriPartial.Authority)).ToArray() + ); + + // .ToDictionary will evaluate and create the dictionary immediately + // the new value is .ToArray so it will also be evaluated immediately + // therefore it is safe to return and exit the configuration lock + } + + private static DomainAndUri MapDomain(DomainAndUri[] domainAndUris, Dictionary qualifiedSites, string currentAuthority) + { + // we do our best, but can't do the impossible + if (qualifiedSites == null) + return domainAndUris.First(); + + // find a site that contains the current authority + var currentSite = qualifiedSites.FirstOrDefault(site => site.Value.Contains(currentAuthority)); + + // if current belongs to a site - try to pick the first element + // from domainAndUris that also belongs to that site + var ret = currentSite.Equals(default(KeyValuePair)) + ? null + : domainAndUris.FirstOrDefault(d => currentSite.Value.Contains(d.Uri.GetLeftPart(UriPartial.Authority))); + + // no match means that either current does not belong to a site, or the site it belongs to + // does not contain any of domainAndUris. Yet we have to return something. here, it becomes + // a bit arbitrary. + + // look through sites in order and pick the first domainAndUri that belongs to a site + ret = ret ?? qualifiedSites + .Where(site => site.Key != currentSite.Key) + .Select(site => domainAndUris.FirstOrDefault(domainAndUri => site.Value.Contains(domainAndUri.Uri.GetLeftPart(UriPartial.Authority)))) + .FirstOrDefault(domainAndUri => domainAndUri != null); + + // random, really + ret = ret ?? domainAndUris.First(); + + return ret; + } + + #endregion + } +} diff --git a/src/Umbraco.Web/Routing/SiteDomainHelperResolver.cs b/src/Umbraco.Web/Routing/SiteDomainHelperResolver.cs new file mode 100644 index 0000000000..9cb101b6b1 --- /dev/null +++ b/src/Umbraco.Web/Routing/SiteDomainHelperResolver.cs @@ -0,0 +1,38 @@ +using System; +using Umbraco.Core.ObjectResolution; + +namespace Umbraco.Web.Routing +{ + /// + /// Resolves the implementation. + /// + internal sealed class SiteDomainHelperResolver : SingleObjectResolverBase + { + + /// + /// Initializes a new instance of the class with an implementation. + /// + /// The implementation. + internal SiteDomainHelperResolver(ISiteDomainHelper helper) + : base(helper) + { } + + + /// + /// Can be used by developers at runtime to set their IDomainHelper at app startup + /// + /// + public void SetHelper(ISiteDomainHelper helper) + { + Value = helper; + } + + /// + /// Gets or sets the implementation. + /// + public ISiteDomainHelper Helper + { + get { return Value; } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Routing/UrlProvider.cs b/src/Umbraco.Web/Routing/UrlProvider.cs index d393720df2..6b9b5e9778 100644 --- a/src/Umbraco.Web/Routing/UrlProvider.cs +++ b/src/Umbraco.Web/Routing/UrlProvider.cs @@ -10,6 +10,8 @@ namespace Umbraco.Web.Routing /// internal class UrlProvider { + #region Ctor and configuration + /// /// Initializes a new instance of the class with an Umbraco context, a content cache, and a list of url providers. /// @@ -34,6 +36,10 @@ namespace Umbraco.Web.Routing /// public bool EnforceAbsoluteUrls { get; set; } + #endregion + + #region GetUrl + /// /// Gets the url of a published content. /// @@ -85,6 +91,10 @@ namespace Umbraco.Web.Routing return url ?? "#"; // legacy wants this } + #endregion + + #region GetOtherUrls + /// /// Gets the other urls of a published content. /// @@ -117,5 +127,7 @@ namespace Umbraco.Web.Routing return urls; } + + #endregion } } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index ae76a047d5..5bf95f7d22 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -385,10 +385,13 @@ + + + diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index 1e9ecfba36..48a836bc7c 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -283,7 +283,7 @@ namespace Umbraco.Web }); UrlProviderResolver.Current = new UrlProviderResolver( - typeof(AliasUrlProvider), + //typeof(AliasUrlProvider), // not enabled by default typeof(DefaultUrlProvider) ); @@ -305,6 +305,8 @@ namespace Umbraco.Web typeof (ContentFinderByNotFoundHandlers) ); + SiteDomainHelperResolver.Current = new SiteDomainHelperResolver(new SiteDomainHelper()); + RoutesCacheResolver.Current = new RoutesCacheResolver(new DefaultRoutesCache(_isForTesting == false)); ThumbnailProvidersResolver.Current = new ThumbnailProvidersResolver(