diff --git a/src/Orchard.Tests/Environment/RunningShellTableTests.cs b/src/Orchard.Tests/Environment/RunningShellTableTests.cs new file mode 100644 index 000000000..6dac36484 --- /dev/null +++ b/src/Orchard.Tests/Environment/RunningShellTableTests.cs @@ -0,0 +1,166 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NUnit.Framework; +using Orchard.Environment; +using Orchard.Environment.Configuration; +using Orchard.Tests.Stubs; + +namespace Orchard.Tests.Environment { + [TestFixture] + public class RunningShellTableTests { + [Test] + public void NoShellsGiveNoMatch() { + var table = new RunningShellTable(); + var match = table.Match(new StubHttpContext()); + Assert.That(match, Is.Null); + } + + [Test] + public void DefaultShellMatchesByDefault() { + var table = (IRunningShellTable)new RunningShellTable(); + var settings = new ShellSettings { Name = "Default" }; + table.Add(settings); + var match = table.Match(new StubHttpContext()); + Assert.That(match, Is.SameAs(settings)); + } + + [Test] + public void AnotherShellMatchesByHostHeader() { + var table = (IRunningShellTable)new RunningShellTable(); + var settings = new ShellSettings { Name = "Default" }; + var settingsA = new ShellSettings { Name = "Alpha", RequestUrlHost = "a.example.com" }; + table.Add(settings); + table.Add(settingsA); + var match = table.Match(new StubHttpContext("~/foo/bar", "a.example.com")); + Assert.That(match, Is.SameAs(settingsA)); + } + + [Test] + public void DefaultStillCatchesWhenOtherShellsMiss() { + var table = (IRunningShellTable)new RunningShellTable(); + var settings = new ShellSettings { Name = "Default" }; + var settingsA = new ShellSettings { Name = "Alpha", RequestUrlHost = "a.example.com" }; + table.Add(settings); + table.Add(settingsA); + var match = table.Match(new StubHttpContext("~/foo/bar", "b.example.com")); + Assert.That(match, Is.SameAs(settings)); + } + + [Test] + public void DefaultWontFallbackIfItHasCriteria() { + var table = (IRunningShellTable)new RunningShellTable(); + var settings = new ShellSettings { Name = "Default", RequestUrlHost = "www.example.com" }; + var settingsA = new ShellSettings { Name = "Alpha", RequestUrlHost = "a.example.com" }; + table.Add(settings); + table.Add(settingsA); + var match = table.Match(new StubHttpContext("~/foo/bar", "b.example.com")); + Assert.That(match, Is.Null); + } + + [Test] + public void DefaultWillCatchRequestsIfItMatchesCriteria() { + var table = (IRunningShellTable)new RunningShellTable(); + var settings = new ShellSettings { Name = "Default", RequestUrlHost = "www.example.com" }; + var settingsA = new ShellSettings { Name = "Alpha", RequestUrlHost = "a.example.com" }; + table.Add(settings); + table.Add(settingsA); + var match = table.Match(new StubHttpContext("~/foo/bar", "www.example.com")); + Assert.That(match, Is.EqualTo(settings)); + } + + [Test] + public void NonDefaultCatchallWillFallbackIfNothingElseMatches() { + var table = (IRunningShellTable)new RunningShellTable(); + var settings = new ShellSettings { Name = "Default", RequestUrlHost = "www.example.com" }; + var settingsA = new ShellSettings { Name = "Alpha" }; + table.Add(settings); + table.Add(settingsA); + var match = table.Match(new StubHttpContext("~/foo/bar", "b.example.com")); + Assert.That(match, Is.EqualTo(settingsA)); + } + + [Test] + public void DefaultCatchallIsFallbackEvenWhenOthersAreUnqualified() { + var table = (IRunningShellTable)new RunningShellTable(); + var settings = new ShellSettings { Name = "Default" }; + var settingsA = new ShellSettings { Name = "Alpha" }; + var settingsB = new ShellSettings { Name = "Beta", RequestUrlHost = "b.example.com" }; + var settingsG = new ShellSettings { Name = "Gamma" }; + table.Add(settings); + table.Add(settingsA); + table.Add(settingsB); + table.Add(settingsG); + var match = table.Match(new StubHttpContext("~/foo/bar", "a.example.com")); + Assert.That(match, Is.EqualTo(settings)); + } + + [Test] + public void ThereIsNoFallbackIfMultipleSitesAreUnqualifiedButDefaultIsNotOneOfThem() { + var table = (IRunningShellTable)new RunningShellTable(); + var settings = new ShellSettings { Name = "Default", RequestUrlHost = "www.example.com" }; + var settingsA = new ShellSettings { Name = "Alpha" }; + var settingsB = new ShellSettings { Name = "Beta", RequestUrlHost = "b.example.com" }; + var settingsG = new ShellSettings { Name = "Gamma" }; + table.Add(settings); + table.Add(settingsA); + table.Add(settingsB); + table.Add(settingsG); + var match = table.Match(new StubHttpContext("~/foo/bar", "a.example.com")); + Assert.That(match, Is.Null); + } + + [Test] + public void PathAlsoCausesMatch() { + var table = (IRunningShellTable)new RunningShellTable(); + var settings = new ShellSettings { Name = "Default" }; + var settingsA = new ShellSettings { Name = "Alpha", RequestUrlPrefix = "~/foo" }; + table.Add(settings); + table.Add(settingsA); + var match = table.Match(new StubHttpContext("~/foo/bar", "a.example.com")); + Assert.That(match, Is.SameAs(settingsA)); + } + + [Test] + public void PathAndHostMustBothMatch() { + var table = (IRunningShellTable)new RunningShellTable(); + var settings = new ShellSettings { Name = "Default", RequestUrlHost = "www.example.com", }; + var settingsA = new ShellSettings { Name = "Alpha", RequestUrlHost = "wiki.example.com", RequestUrlPrefix = "~/foo" }; + var settingsB = new ShellSettings { Name = "Beta", RequestUrlHost = "wiki.example.com", RequestUrlPrefix = "~/bar" }; + var settingsG = new ShellSettings { Name = "Gamma", RequestUrlHost = "wiki.example.com" }; + var settingsD = new ShellSettings { Name = "Delta", RequestUrlPrefix = "~/Quux" }; + table.Add(settings); + table.Add(settingsA); + table.Add(settingsB); + table.Add(settingsG); + table.Add(settingsD); + + Assert.That(table.Match(new StubHttpContext("~/foo/bar", "wiki.example.com")), Is.SameAs(settingsA)); + Assert.That(table.Match(new StubHttpContext("~/bar/foo", "wiki.example.com")), Is.SameAs(settingsB)); + Assert.That(table.Match(new StubHttpContext("~/baaz", "wiki.example.com")), Is.SameAs(settingsG)); + Assert.That(table.Match(new StubHttpContext("~/foo/bar", "www.example.com")), Is.SameAs(settings)); + Assert.That(table.Match(new StubHttpContext("~/bar/foo", "www.example.com")), Is.SameAs(settings)); + Assert.That(table.Match(new StubHttpContext("~/baaz", "www.example.com")), Is.SameAs(settings)); + Assert.That(table.Match(new StubHttpContext("~/foo/bar", "a.example.com")), Is.Null); + + Assert.That(table.Match(new StubHttpContext("~/quux/quad", "wiki.example.com")), Is.SameAs(settingsG)); + Assert.That(table.Match(new StubHttpContext("~/quux/quad", "www.example.com")), Is.SameAs(settings)); + Assert.That(table.Match(new StubHttpContext("~/quux/quad", "a.example.com")), Is.SameAs(settingsD)); + Assert.That(table.Match(new StubHttpContext("~/yarg", "wiki.example.com")), Is.SameAs(settingsG)); + Assert.That(table.Match(new StubHttpContext("~/yarg", "www.example.com")), Is.SameAs(settings)); + Assert.That(table.Match(new StubHttpContext("~/yarg", "a.example.com")), Is.Null); + } + + [Test] + public void PathAloneWillMatch() { + var table = (IRunningShellTable)new RunningShellTable(); + var settingsA = new ShellSettings { Name = "Alpha", RequestUrlPrefix = "~/foo" }; + table.Add(settingsA); + + Assert.That(table.Match(new StubHttpContext("~/foo/bar", "wiki.example.com")), Is.SameAs(settingsA)); + Assert.That(table.Match(new StubHttpContext("~/bar/foo", "wiki.example.com")), Is.Null); + } + + } +} diff --git a/src/Orchard.Tests/Mvc/Routes/ShellRouteTests.cs b/src/Orchard.Tests/Mvc/Routes/ShellRouteTests.cs index 0eb826af8..40c0d5087 100644 --- a/src/Orchard.Tests/Mvc/Routes/ShellRouteTests.cs +++ b/src/Orchard.Tests/Mvc/Routes/ShellRouteTests.cs @@ -6,6 +6,7 @@ using System.Web; using System.Web.Mvc; using System.Web.Routing; using Autofac; +using Autofac.Integration.Web; using NUnit.Framework; using Orchard.Environment; using Orchard.Environment.Configuration; @@ -109,19 +110,35 @@ namespace Orchard.Tests.Mvc.Routes { public void MatchingRouteToActiveShellTableWillLimitTheAbilityToMatchRoutes() { Init(); - var routeA = new Route("foo", new MvcRouteHandler()); - var routeB = new Route("bar", new MvcRouteHandler()); - var routeC = new Route("quux", new MvcRouteHandler()); + var routeFoo = new Route("foo", new MvcRouteHandler()); + _settingsA.RequestUrlHost = "a.example.com"; _containerA.Resolve().Publish( - new[] {new RouteDescriptor {Priority = 0, Route = routeA}}); + new[] {new RouteDescriptor {Priority = 0, Route = routeFoo}}); + _settingsB.RequestUrlHost = "b.example.com"; _containerB.Resolve().Publish( - new[] {new RouteDescriptor {Priority = 0, Route = routeB}}); + new[] {new RouteDescriptor {Priority = 0, Route = routeFoo}}); var httpContext = new StubHttpContext("~/foo"); var routeData = _routes.GetRouteData(httpContext); Assert.That(routeData, Is.Null); + + var httpContextA = new StubHttpContext("~/foo", "a.example.com"); + var routeDataA = _routes.GetRouteData(httpContextA); + Assert.That(routeDataA, Is.Not.Null); + Assert.That(routeDataA.DataTokens.ContainsKey("IContainerProvider"), Is.True); + var routeContainerProviderA = (IContainerProvider)routeDataA.DataTokens["IContainerProvider"]; + Assert.That(routeContainerProviderA.ApplicationContainer.Resolve(), Is.SameAs(_containerA.Resolve())); + Assert.That(routeContainerProviderA.ApplicationContainer.Resolve(), Is.Not.SameAs(_containerB.Resolve())); + + var httpContextB = new StubHttpContext("~/foo", "b.example.com"); + var routeDataB = _routes.GetRouteData(httpContextB); + Assert.That(routeDataB, Is.Not.Null); + Assert.That(routeDataB.DataTokens.ContainsKey("IContainerProvider"), Is.True); + var routeContainerProviderB = (IContainerProvider)routeDataA.DataTokens["IContainerProvider"]; + Assert.That(routeContainerProviderB.ApplicationContainer.Resolve(), Is.SameAs(_containerB.Resolve())); + Assert.That(routeContainerProviderB.ApplicationContainer.Resolve(), Is.Not.SameAs(_containerA.Resolve())); } } diff --git a/src/Orchard.Tests/Orchard.Framework.Tests.csproj b/src/Orchard.Tests/Orchard.Framework.Tests.csproj index 09146453f..43e1a0240 100644 --- a/src/Orchard.Tests/Orchard.Framework.Tests.csproj +++ b/src/Orchard.Tests/Orchard.Framework.Tests.csproj @@ -158,6 +158,7 @@ + diff --git a/src/Orchard.Tests/Stubs/StubHttpContext.cs b/src/Orchard.Tests/Stubs/StubHttpContext.cs index 5439a5c79..d8495e5a1 100644 --- a/src/Orchard.Tests/Stubs/StubHttpContext.cs +++ b/src/Orchard.Tests/Stubs/StubHttpContext.cs @@ -1,10 +1,12 @@ using System.Collections; using System.Collections.Generic; +using System.Collections.Specialized; using System.Web; namespace Orchard.Tests.Stubs { public class StubHttpContext : HttpContextBase { private readonly string _appRelativeCurrentExecutionFilePath; + private readonly string _hostHeader; private readonly IDictionary _items = new Dictionary(); public StubHttpContext() { @@ -15,32 +17,46 @@ namespace Orchard.Tests.Stubs { _appRelativeCurrentExecutionFilePath = appRelativeCurrentExecutionFilePath; } + public StubHttpContext(string appRelativeCurrentExecutionFilePath, string hostHeader) { + _appRelativeCurrentExecutionFilePath = appRelativeCurrentExecutionFilePath; + _hostHeader = hostHeader; + } + public override HttpRequestBase Request { - get { return new StupHttpRequest(_appRelativeCurrentExecutionFilePath); } + get { return new StubHttpRequest(this); } } public override IDictionary Items { get { return _items; } } - public class StupHttpRequest : HttpRequestBase { - private readonly string _appRelativeCurrentExecutionFilePath; + class StubHttpRequest : HttpRequestBase { + private readonly StubHttpContext _httpContext; + private NameValueCollection _serverVariables; - public StupHttpRequest(string appRelativeCurrentExecutionFilePath) { - _appRelativeCurrentExecutionFilePath = appRelativeCurrentExecutionFilePath; + public StubHttpRequest(StubHttpContext httpContext) { + _httpContext = httpContext; } public override string AppRelativeCurrentExecutionFilePath { - get { return _appRelativeCurrentExecutionFilePath; } + get { return _httpContext._appRelativeCurrentExecutionFilePath; } } public override string ApplicationPath { - get { return "/"; } + get { return "/"; } } public override string PathInfo { get { return ""; } } + + public override NameValueCollection ServerVariables { + get { + return _serverVariables = _serverVariables + ?? new NameValueCollection { { "HTTP_HOST", _httpContext._hostHeader } }; + } + } + } } } \ No newline at end of file diff --git a/src/Orchard/Environment/RunningShellTable.cs b/src/Orchard/Environment/RunningShellTable.cs index d73531bc2..6efaa53d5 100644 --- a/src/Orchard/Environment/RunningShellTable.cs +++ b/src/Orchard/Environment/RunningShellTable.cs @@ -14,12 +14,40 @@ namespace Orchard.Environment { public class RunningShellTable : IRunningShellTable { private IEnumerable _shells = Enumerable.Empty(); + private IEnumerable> _shellsByHost = Enumerable.Empty().GroupBy(x => default(string)); + private ShellSettings _fallback; public void Add(ShellSettings settings) { _shells = _shells - .Where(s=>s.Name != settings.Name) + .Where(s => s.Name != settings.Name) .Concat(new[] { settings }) .ToArray(); + + var qualified = + _shells.Where(x => !string.IsNullOrEmpty(x.RequestUrlHost) || !string.IsNullOrEmpty(x.RequestUrlPrefix)); + + var unqualified = + _shells.Where(x => string.IsNullOrEmpty(x.RequestUrlHost) && string.IsNullOrEmpty(x.RequestUrlPrefix)); + + _shellsByHost = qualified + .GroupBy(s => s.RequestUrlHost ?? "") + .OrderByDescending(g => g.Key.Length); + + if (unqualified.Count() == 1) { + // only one shell had no request url criteria + _fallback = unqualified.Single(); + } + else if (unqualified.Any()) { + // two or more shells had no request criteria. + // this is technically a misconfiguration - so fallback to the default shell + // if it's one which will catch all requests + _fallback = unqualified.SingleOrDefault(x => x.Name == "Default"); + } + else { + // no shells are unqualified - a request that does not match a shell's spec + // will not be mapped to routes coming from orchard + _fallback = null; + } } public IEnumerable List() { @@ -27,7 +55,23 @@ namespace Orchard.Environment { } public ShellSettings Match(HttpContextBase httpContext) { - return _shells.SingleOrDefault(settings => settings.Name == "Default"); + var host = httpContext.Request.ServerVariables.Get("HTTP_HOST") ?? ""; + + var hostLength = host.IndexOf(':'); + if (hostLength != -1) + host = host.Substring(0, hostLength); + + var appRelativePath = httpContext.Request.AppRelativeCurrentExecutionFilePath; + + var mostQualifiedMatch = _shellsByHost + .Where(group => host.EndsWith(group.Key, StringComparison.OrdinalIgnoreCase)) + .SelectMany(group => group + .OrderByDescending(settings => (settings.RequestUrlPrefix ?? "").Length)) + .Where(settings => appRelativePath.StartsWith(settings.RequestUrlPrefix ?? "", StringComparison.OrdinalIgnoreCase)) + .FirstOrDefault(); + + return mostQualifiedMatch ?? _fallback; } + } }