Web.Routing - refactor domains management
This commit is contained in:
309
src/Umbraco.Tests/Routing/SiteDomainHelperTests.cs
Normal file
309
src/Umbraco.Tests/Routing/SiteDomainHelperTests.cs
Normal file
@@ -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<ArgumentOutOfRangeException>(() => 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -285,6 +285,7 @@
|
||||
<Compile Include="Routing\ContentFinderByNiceUrlWithDomainsTests.cs" />
|
||||
<Compile Include="Routing\DomainsAndCulturesTests.cs" />
|
||||
<Compile Include="Routing\NiceUrlsProviderWithDomainsTests.cs" />
|
||||
<Compile Include="Routing\SiteDomainHelperTests.cs" />
|
||||
<Compile Include="Routing\uQueryGetNodeIdByUrlTests.cs" />
|
||||
<Compile Include="Routing\UrlsWithNestedDomains.cs" />
|
||||
<Compile Include="Services\BaseServiceTest.cs" />
|
||||
|
||||
@@ -10,6 +10,12 @@ namespace Umbraco.Web.Routing
|
||||
/// </summary>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Gets the nice url of a published content.
|
||||
/// </summary>
|
||||
@@ -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<ContentFinderByUrlAlias>();
|
||||
var hasHandler = ContentFinderResolver.Current.ContainsType<ContentFinderByNotFoundHandlers>()
|
||||
&& NotFoundHandlerHelper.CustomHandlerTypes.Contains(typeof(global::umbraco.SearchForAlias));
|
||||
return hasFinder || hasHandler;
|
||||
}
|
||||
}
|
||||
#region GetOtherUrls
|
||||
|
||||
/// <summary>
|
||||
/// Gets the other urls of a published content.
|
||||
@@ -66,32 +63,51 @@ namespace Umbraco.Web.Routing
|
||||
if (string.IsNullOrWhiteSpace(umbracoUrlName))
|
||||
return Enumerable.Empty<string>();
|
||||
|
||||
/*
|
||||
|
||||
// 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<ContentFinderByUrlAlias>();
|
||||
var hasHandler = ContentFinderResolver.Current.ContainsType<ContentFinderByNotFoundHandlers>()
|
||||
&& 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +94,8 @@ namespace Umbraco.Web.Routing
|
||||
docreq.PublishedContent = node;
|
||||
LogHelper.Debug<ContentFinderByNiceUrl>("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<ContentFinderByNiceUrl>("Non canonical url");
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace Umbraco.Web.Routing
|
||||
/// </remarks>
|
||||
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 /<path> or <domainRootId>/<path>
|
||||
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<string>();
|
||||
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<string> GetOtherUrls(UmbracoContext umbracoContext, IPublishedContentStore contentCache, int id, Uri current)
|
||||
{
|
||||
string path;
|
||||
IEnumerable<Uri> domainUris;
|
||||
IEnumerable<DomainAndUri> domainUris;
|
||||
|
||||
// will not read cache if previewing!
|
||||
var route = umbracoContext.InPreviewMode
|
||||
@@ -132,7 +132,7 @@ namespace Umbraco.Web.Routing
|
||||
// route is /<path> or <domainRootId>/<path>
|
||||
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<string>();
|
||||
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<Uri> AssembleUrls(IEnumerable<Uri> domainUris, string path)
|
||||
IEnumerable<Uri> AssembleUrls(IEnumerable<DomainAndUri> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters a list of <c>DomainAndUri</c> to pick one that best matches the current request.
|
||||
/// </summary>
|
||||
/// <param name="domainAndUris">The list of <c>DomainAndUri</c> to filter.</param>
|
||||
/// <param name="current">The Uri of the current request.</param>
|
||||
/// <returns>The selected <c>DomainAndUri</c>.</returns>
|
||||
/// <remarks>
|
||||
/// <para>If the filter is invoked then <paramref name="domainAndUris"/> is _not_ empty and
|
||||
/// <paramref name="current"/> is _not_ null, and <paramref name="current"/> could not be
|
||||
/// matched with anything in <paramref name="domainAndUris"/>.</para>
|
||||
/// <para>The filter _must_ return something else an exception will be thrown.</para>
|
||||
/// </remarks>
|
||||
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<DomainAndUri> 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<string> pathParts)
|
||||
{
|
||||
// in theory if hideTopLevelNodeFromPath is true, then there should be only once
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides utilities to handle domains.
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Provides utilities to handle domains.
|
||||
/// </summary>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Finds the domain that best matches the current uri, into an enumeration of domains.
|
||||
/// Gets all domains defined in the system.
|
||||
/// </summary>
|
||||
/// <param name="domains">The enumeration of Umbraco domains.</param>
|
||||
/// <param name="current">The uri of the current request, or null.</param>
|
||||
/// <returns>All domains defined in the system.</returns>
|
||||
/// <remarks>This is to temporarily abstract Umbraco's API.</remarks>
|
||||
internal static Domain[] GetAllDomains()
|
||||
{
|
||||
return Domain.GetDomains().ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all domains defined in the system at a specified node.
|
||||
/// </summary>
|
||||
/// <param name="nodeId">The node identifier.</param>
|
||||
/// <returns>All domains defined in the system at the specified node.</returns>
|
||||
/// <remarks>This is to temporarily abstract Umbraco's API.</remarks>
|
||||
internal static Domain[] GetNodeDomains(int nodeId)
|
||||
{
|
||||
return Domain.GetDomainsById(nodeId);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Domain for Node
|
||||
|
||||
/// <summary>
|
||||
/// Finds the domain for the specified node, if any, that best matches a specified uri.
|
||||
/// </summary>
|
||||
/// <param name="nodeId">The node identifier.</param>
|
||||
/// <param name="current">The uri, or null.</param>
|
||||
/// <returns>The domain and its uri, if any, that best matches the specified uri.</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the domains for the specified node, if any, that match a specified uri.
|
||||
/// </summary>
|
||||
/// <param name="nodeId">The node identifier.</param>
|
||||
/// <param name="current">The uri, or null.</param>
|
||||
/// <returns>The domains and their uris, that match the specified uri.</returns>
|
||||
internal static IEnumerable<DomainAndUri> 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
|
||||
|
||||
/// <summary>
|
||||
/// Finds the domain that best matches a specified uri, into a group of domains.
|
||||
/// </summary>
|
||||
/// <param name="domains">The group of domains.</param>
|
||||
/// <param name="current">The uri, or null.</param>
|
||||
/// <param name="filter">A function to filter the list of domains, if more than one applies, or <c>null</c>.</param>
|
||||
/// <returns>The domain and its normalized uri, that best matches the current uri.</returns>
|
||||
/// <returns>The domain and its normalized uri, that best matches the specified uri.</returns>
|
||||
/// <remarks>
|
||||
/// <para>If more than one domain matches, then the <paramref name="filter"/> function is used to pick
|
||||
/// the right one, unless it is <c>null</c>, in which case the method returns <c>null</c>.</para>
|
||||
/// <para>The filter, if any, will be called only with a non-empty argument, and _must_ return something.</para>
|
||||
/// </remarks>
|
||||
public static DomainAndUri DomainMatch(Domain[] domains, Uri current, Func<DomainAndUri[], DomainAndUri> filter = null)
|
||||
internal static DomainAndUri DomainForUri(Domain[] domains, Uri current, Func<DomainAndUri[], DomainAndUri> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an enumeration of <see cref="DomainAndUri"/> matching an enumeration of Umbraco domains.
|
||||
/// </summary>
|
||||
/// <param name="domains">The enumeration of Umbraco domains.</param>
|
||||
/// <param name="current">The uri of the current request, or null.</param>
|
||||
/// <returns>The enumeration of <see cref="DomainAndUri"/> matching the enumeration of Umbraco domains.</returns>
|
||||
public static IEnumerable<DomainAndUri> DomainMatches(Domain[] domains, Uri current)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the domains that match a specified uri, into a group of domains.
|
||||
/// </summary>
|
||||
/// <param name="domains">The group of domains.</param>
|
||||
/// <param name="current">The uri, or null.</param>
|
||||
/// <returns>The domains and their normalized uris, that match the specified uri.</returns>
|
||||
internal static IEnumerable<DomainAndUri> 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether there is another domain defined down in the path to a node under the current domain's root node.
|
||||
/// </summary>
|
||||
/// <param name="current">The current domain.</param>
|
||||
/// <param name="path">The path to a node under the current domain's root node.</param>
|
||||
/// <returns>A value indicating if there is another domain defined down in the path.</returns>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Gets the deepest wildcard <see cref="Domain"/> in a node path.
|
||||
/// </summary>
|
||||
/// <param name="domains">The Umbraco domains.</param>
|
||||
/// <param name="path">The node path.</param>
|
||||
/// <param name="rootNodeId">The current domain root node identifier, or null.</param>
|
||||
/// <returns>The deepest wildcard <see cref="Domain"/> in the path, or null.</returns>
|
||||
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."
|
||||
/// <summary>
|
||||
/// Sanitize a Domain.
|
||||
/// </summary>
|
||||
/// <param name="domain">The Domain to sanitize.</param>
|
||||
/// <returns>The sanitized domain.</returns>
|
||||
/// <remarks>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.</remarks>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether there is another domain defined down in the path to a node under the current domain's root node.
|
||||
/// </summary>
|
||||
/// <param name="domains">The domains.</param>
|
||||
/// <param name="path">The path to a node under the current domain's root node eg '-1,1234,5678'.</param>
|
||||
/// <param name="rootNodeId">The current domain root node identifier, or null.</param>
|
||||
/// <returns>A value indicating if there is another domain defined down in the path.</returns>
|
||||
/// <remarks>Looks _under_ rootNodeId but not _at_ rootNodeId.</remarks>
|
||||
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);
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the deepest non-wildcard Domain, if any, from a group of Domains, in a node path.
|
||||
/// </summary>
|
||||
/// <param name="domains">The domains.</param>
|
||||
/// <param name="path">The node path eg '-1,1234,5678'.</param>
|
||||
/// <param name="rootNodeId">The current domain root node identifier, or null.</param>
|
||||
/// <returns>The deepest non-wildcard Domain in the path, or null.</returns>
|
||||
/// <remarks>Looks _under_ rootNodeId but not _at_ rootNodeId.</remarks>
|
||||
internal static Domain FindDomainInPath(Domain[] domains, string path, int? rootNodeId)
|
||||
{
|
||||
var stopNodeId = rootNodeId ?? -1;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the part of a path relative to the uri of a domain.
|
||||
/// </summary>
|
||||
/// <param name="domainUri">The normalized uri of the domain.</param>
|
||||
/// <param name="path">The full path of the uri.</param>
|
||||
/// <returns>The path part relative to the uri of the domain.</returns>
|
||||
/// <remarks>Eg the relative part of <c>/foo/bar/nil</c> to domain <c>example.com/foo</c> is <c>/bar/nil</c>.</remarks>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the deepest wildcard Domain, if any, from a group of Domains, in a node path.
|
||||
/// </summary>
|
||||
/// <param name="domains">The domains.</param>
|
||||
/// <param name="path">The node path eg '-1,1234,5678'.</param>
|
||||
/// <param name="rootNodeId">The current domain root node identifier, or null.</param>
|
||||
/// <returns>The deepest wildcard Domain in the path, or null.</returns>
|
||||
/// <remarks>Looks _under_ rootNodeId but not _at_ rootNodeId.</remarks>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the part of a path relative to the uri of a domain.
|
||||
/// </summary>
|
||||
/// <param name="domainUri">The normalized uri of the domain.</param>
|
||||
/// <param name="path">The full path of the uri.</param>
|
||||
/// <returns>The path part relative to the uri of the domain.</returns>
|
||||
/// <remarks>Eg the relative part of <c>/foo/bar/nil</c> to domain <c>example.com/foo</c> is <c>/bar/nil</c>.</remarks>
|
||||
internal static string PathRelativeToDomain(Uri domainUri, string path)
|
||||
{
|
||||
return path.Substring(domainUri.AbsolutePath.Length).EnsureStartsWith('/');
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
36
src/Umbraco.Web/Routing/ISiteDomainHelper.cs
Normal file
36
src/Umbraco.Web/Routing/ISiteDomainHelper.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Umbraco.Web.Routing
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides utilities to handle site domains.
|
||||
/// </summary>
|
||||
internal interface ISiteDomainHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Filters a list of <c>DomainAndUri</c> to pick one that best matches the current request.
|
||||
/// </summary>
|
||||
/// <param name="current">The Uri of the current request.</param>
|
||||
/// <param name="domainAndUris">The list of <c>DomainAndUri</c> to filter.</param>
|
||||
/// <returns>The selected <c>DomainAndUri</c>.</returns>
|
||||
/// <remarks>
|
||||
/// <para>If the filter is invoked then <paramref name="domainAndUris"/> is _not_ empty and
|
||||
/// <paramref name="current"/> is _not_ null, and <paramref name="current"/> could not be
|
||||
/// matched with anything in <paramref name="domainAndUris"/>.</para>
|
||||
/// <para>The filter _must_ return something else an exception will be thrown.</para>
|
||||
/// </remarks>
|
||||
DomainAndUri MapDomain(Uri current, DomainAndUri[] domainAndUris);
|
||||
|
||||
/// <summary>
|
||||
/// Filters a list of <c>DomainAndUri</c> to pick those that best matches the current request.
|
||||
/// </summary>
|
||||
/// <param name="current">The Uri of the current request.</param>
|
||||
/// <param name="domainAndUris">The list of <c>DomainAndUri</c> to filter.</param>
|
||||
/// <returns>The selected <c>DomainAndUri</c> items.</returns>
|
||||
/// <remarks>The filter must return something, even empty, else an exception will be thrown.</remarks>
|
||||
IEnumerable<DomainAndUri> MapDomains(Uri current, DomainAndUri[] domainAndUris);
|
||||
}
|
||||
}
|
||||
@@ -166,7 +166,7 @@ namespace Umbraco.Web.Routing
|
||||
LogHelper.Debug<PublishedContentRequestEngine>("{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<PublishedContentRequestEngine>("{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)
|
||||
{
|
||||
|
||||
314
src/Umbraco.Web/Routing/SiteDomainHelper.cs
Normal file
314
src/Umbraco.Web/Routing/SiteDomainHelper.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides utilities to handle site domains.
|
||||
/// </summary>
|
||||
internal class SiteDomainHelper : ISiteDomainHelper
|
||||
{
|
||||
#region Configure
|
||||
|
||||
private static readonly ReaderWriterLockSlim ConfigLock = new ReaderWriterLockSlim();
|
||||
private static Dictionary<string, string[]> _sites;
|
||||
private static Dictionary<string, List<string>> _bindings;
|
||||
private static Dictionary<string, Dictionary<string, string[]>> _qualifiedSites;
|
||||
|
||||
// these are for unit tests *only*
|
||||
internal static Dictionary<string, string[]> Sites { get { return _sites; } }
|
||||
internal static Dictionary<string, List<string>> 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);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a disposable object that represents safe write access to config.
|
||||
/// </summary>
|
||||
/// <remarks>Should be used in a <c>using(SiteDomainHelper.ConfigWriteLock) { ... }</c> mode.</remarks>
|
||||
protected static IDisposable ConfigWriteLock
|
||||
{
|
||||
get { return new WriteLock(ConfigLock); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a disposable object that represents safe read access to config.
|
||||
/// </summary>
|
||||
/// <remarks>Should be used in a <c>using(SiteDomainHelper.ConfigWriteLock) { ... }</c> mode.</remarks>
|
||||
protected static IDisposable ConfigReadLock
|
||||
{
|
||||
get { return new ReadLock(ConfigLock); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the entire configuration.
|
||||
/// </summary>
|
||||
public static void Clear()
|
||||
{
|
||||
using (ConfigWriteLock)
|
||||
{
|
||||
_sites = null;
|
||||
_bindings = null;
|
||||
_qualifiedSites = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<string> ValidateDomains(IEnumerable<string> 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;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a site.
|
||||
/// </summary>
|
||||
/// <param name="key">A key uniquely identifying the site.</param>
|
||||
/// <param name="domains">The site domains.</param>
|
||||
/// <remarks>At the moment there is no public way to remove a site. Clear and reconfigure.</remarks>
|
||||
public static void AddSite(string key, IEnumerable<string> domains)
|
||||
{
|
||||
using (ConfigWriteLock)
|
||||
{
|
||||
_sites = _sites ?? new Dictionary<string, string[]>();
|
||||
_sites[key] = ValidateDomains(domains).ToArray();
|
||||
_qualifiedSites = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a site.
|
||||
/// </summary>
|
||||
/// <param name="key">A key uniquely identifying the site.</param>
|
||||
/// <param name="domains">The site domains.</param>
|
||||
/// <remarks>At the moment there is no public way to remove a site. Clear and reconfigure.</remarks>
|
||||
public static void AddSite(string key, params string[] domains)
|
||||
{
|
||||
using (ConfigWriteLock)
|
||||
{
|
||||
_sites = _sites ?? new Dictionary<string, string[]>();
|
||||
_sites[key] = ValidateDomains(domains).ToArray();
|
||||
_qualifiedSites = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a site.
|
||||
/// </summary>
|
||||
/// <param name="key">A key uniquely identifying the site.</param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binds some sites.
|
||||
/// </summary>
|
||||
/// <param name="keys">The keys uniquely identifying the sites to bind.</param>
|
||||
/// <remarks>
|
||||
/// <para>At the moment there is no public way to unbind sites. Clear and reconfigure.</para>
|
||||
/// <para>If site1 is bound to site2 and site2 is bound to site3 then site1 is bound to site3.</para>
|
||||
/// </remarks>
|
||||
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<string, List<string>>();
|
||||
|
||||
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<string>();
|
||||
var xkey = key;
|
||||
var addKeys = allkeys.Where(k => k != xkey).Except(_bindings[key]);
|
||||
_bindings[key].AddRange(addKeys);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Map domains
|
||||
|
||||
/// <summary>
|
||||
/// Filters a list of <c>DomainAndUri</c> to pick one that best matches the current request.
|
||||
/// </summary>
|
||||
/// <param name="current">The Uri of the current request.</param>
|
||||
/// <param name="domainAndUris">The list of <c>DomainAndUri</c> to filter.</param>
|
||||
/// <returns>The selected <c>DomainAndUri</c>.</returns>
|
||||
/// <remarks>
|
||||
/// <para>If the filter is invoked then <paramref name="domainAndUris"/> is _not_ empty and
|
||||
/// <paramref name="current"/> is _not_ null, and <paramref name="current"/> could not be
|
||||
/// matched with anything in <paramref name="domainAndUris"/>.</para>
|
||||
/// <para>The filter _must_ return something else an exception will be thrown.</para>
|
||||
/// </remarks>
|
||||
public virtual DomainAndUri MapDomain(Uri current, DomainAndUri[] domainAndUris)
|
||||
{
|
||||
var currentAuthority = current.GetLeftPart(UriPartial.Authority);
|
||||
var qualifiedSites = GetQualifiedSites(current);
|
||||
|
||||
return MapDomain(domainAndUris, qualifiedSites, currentAuthority);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters a list of <c>DomainAndUri</c> to pick those that best matches the current request.
|
||||
/// </summary>
|
||||
/// <param name="current">The Uri of the current request.</param>
|
||||
/// <param name="domainAndUris">The list of <c>DomainAndUri</c> to filter.</param>
|
||||
/// <returns>The selected <c>DomainAndUri</c> items.</returns>
|
||||
/// <remarks>The filter must return something, even empty, else an exception will be thrown.</remarks>
|
||||
public virtual IEnumerable<DomainAndUri> MapDomains(Uri current, DomainAndUri[] domainAndUris)
|
||||
{
|
||||
var currentAuthority = current.GetLeftPart(UriPartial.Authority);
|
||||
KeyValuePair<string, string[]>[] candidateSites;
|
||||
IEnumerable<DomainAndUri> 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<string, string[]> GetQualifiedSites(Uri current)
|
||||
{
|
||||
using (ConfigReadLock)
|
||||
{
|
||||
return GetQualifiedSitesInsideLock(current);
|
||||
}
|
||||
}
|
||||
|
||||
private static Dictionary<string, string[]> 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<string, Dictionary<string, string[]>>();
|
||||
|
||||
// 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<string, string[]> 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<string, string[]>))
|
||||
? 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
|
||||
}
|
||||
}
|
||||
38
src/Umbraco.Web/Routing/SiteDomainHelperResolver.cs
Normal file
38
src/Umbraco.Web/Routing/SiteDomainHelperResolver.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using Umbraco.Core.ObjectResolution;
|
||||
|
||||
namespace Umbraco.Web.Routing
|
||||
{
|
||||
/// <summary>
|
||||
/// Resolves the <see cref="ISiteDomainHelper"/> implementation.
|
||||
/// </summary>
|
||||
internal sealed class SiteDomainHelperResolver : SingleObjectResolverBase<SiteDomainHelperResolver, ISiteDomainHelper>
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SiteDomainHelperResolver"/> class with an <see cref="ISiteDomainHelper"/> implementation.
|
||||
/// </summary>
|
||||
/// <param name="helper">The <see cref="ISiteDomainHelper"/> implementation.</param>
|
||||
internal SiteDomainHelperResolver(ISiteDomainHelper helper)
|
||||
: base(helper)
|
||||
{ }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Can be used by developers at runtime to set their IDomainHelper at app startup
|
||||
/// </summary>
|
||||
/// <param name="helper"></param>
|
||||
public void SetHelper(ISiteDomainHelper helper)
|
||||
{
|
||||
Value = helper;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="ISiteDomainHelper"/> implementation.
|
||||
/// </summary>
|
||||
public ISiteDomainHelper Helper
|
||||
{
|
||||
get { return Value; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,8 @@ namespace Umbraco.Web.Routing
|
||||
/// </summary>
|
||||
internal class UrlProvider
|
||||
{
|
||||
#region Ctor and configuration
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UrlProvider"/> class with an Umbraco context, a content cache, and a list of url providers.
|
||||
/// </summary>
|
||||
@@ -34,6 +36,10 @@ namespace Umbraco.Web.Routing
|
||||
/// </summary>
|
||||
public bool EnforceAbsoluteUrls { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region GetUrl
|
||||
|
||||
/// <summary>
|
||||
/// Gets the url of a published content.
|
||||
/// </summary>
|
||||
@@ -85,6 +91,10 @@ namespace Umbraco.Web.Routing
|
||||
return url ?? "#"; // legacy wants this
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region GetOtherUrls
|
||||
|
||||
/// <summary>
|
||||
/// Gets the other urls of a published content.
|
||||
/// </summary>
|
||||
@@ -117,5 +127,7 @@ namespace Umbraco.Web.Routing
|
||||
|
||||
return urls;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,10 +385,13 @@
|
||||
<Compile Include="Routing\ContentFinderByPageIdQuery.cs" />
|
||||
<Compile Include="Mvc\SurfaceControllerResolver.cs" />
|
||||
<Compile Include="Routing\ContentFinderByNotFoundHandlers.cs" />
|
||||
<Compile Include="Routing\ISiteDomainHelper.cs" />
|
||||
<Compile Include="Routing\RoutableAttemptEventArgs.cs" />
|
||||
<Compile Include="Routing\DefaultUrlProvider.cs" />
|
||||
<Compile Include="Routing\DomainAndUri.cs" />
|
||||
<Compile Include="Routing\IUrlProvider.cs" />
|
||||
<Compile Include="Routing\SiteDomainHelper.cs" />
|
||||
<Compile Include="Routing\SiteDomainHelperResolver.cs" />
|
||||
<Compile Include="Routing\UrlProviderResolver.cs" />
|
||||
<Compile Include="Routing\NotFoundHandlerHelper.cs" />
|
||||
<Compile Include="Routing\EnsureRoutableOutcome.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(
|
||||
|
||||
Reference in New Issue
Block a user