From 8eecad9d830d634701010567523af65e4894f362 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Thu, 24 Apr 2014 16:32:08 -0700 Subject: [PATCH] Bringing back UrlHelper.IsLocalUrl This has been compied verbatim from MVC (intentional). The tests have been modernized a bit as well, but all the cases covered in the original are there. This may be moved to HttpAbstractions at some point in the future. --- src/Microsoft.AspNet.Mvc.Core/IUrlHelper.cs | 2 + src/Microsoft.AspNet.Mvc.Core/UrlHelper.cs | 12 + .../UrlHelperTest.cs | 215 ++++++++++++++++++ 3 files changed, 229 insertions(+) diff --git a/src/Microsoft.AspNet.Mvc.Core/IUrlHelper.cs b/src/Microsoft.AspNet.Mvc.Core/IUrlHelper.cs index d8505dfd5b..010b6f40e5 100644 --- a/src/Microsoft.AspNet.Mvc.Core/IUrlHelper.cs +++ b/src/Microsoft.AspNet.Mvc.Core/IUrlHelper.cs @@ -5,6 +5,8 @@ string Action(string action, string controller, object values, string protocol, string host, string fragment); string Content(string contentPath); + + bool IsLocalUrl(string url); string RouteUrl(object values, string protocol, string host, string fragment); } diff --git a/src/Microsoft.AspNet.Mvc.Core/UrlHelper.cs b/src/Microsoft.AspNet.Mvc.Core/UrlHelper.cs index 0a07aea650..fef108200a 100644 --- a/src/Microsoft.AspNet.Mvc.Core/UrlHelper.cs +++ b/src/Microsoft.AspNet.Mvc.Core/UrlHelper.cs @@ -67,6 +67,18 @@ namespace Microsoft.AspNet.Mvc return GenerateUrl(protocol, host, path, fragment); } + public bool IsLocalUrl(string url) + { + return + !string.IsNullOrEmpty(url) && + + // Allows "/" or "/foo" but not "//" or "/\". + ((url[0] == '/' && (url.Length == 1 || (url[1] != '/' && url[1] != '\\'))) || + + // Allows "~/" or "~/foo". + (url.Length > 1 && url[0] == '~' && url[1] == '/')); + } + public string RouteUrl(object values, string protocol, string host, string fragment) { var valuesDictionary = TypeHelper.ObjectToDictionary(values); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/UrlHelperTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/UrlHelperTest.cs index 66d666ee26..6812d5a640 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/UrlHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/UrlHelperTest.cs @@ -52,6 +52,201 @@ namespace Microsoft.AspNet.Mvc.Core.Test Assert.Equal(expectedPath, path); } + [Theory] + [InlineData(null)] + [InlineData("")] + public void IsLocalUrl_ReturnsFalseOnEmpty(string url) + { + // Arrange + var helper = CreateUrlHelper(); + + // Act + var result = helper.IsLocalUrl(url); + + // Assert + Assert.False(result); + } + + [Theory] + [InlineData("/foo.html")] + [InlineData("/www.example.com")] + [InlineData("/")] + public void IsLocalUrl_AcceptsRootedUrls(string url) + { + // Arrange + var helper = CreateUrlHelper(); + + // Act + var result = helper.IsLocalUrl(url); + + // Assert + Assert.True(result); + } + + [Theory] + [InlineData("~/")] + [InlineData("~/foo.html")] + public void IsLocalUrl_AcceptsApplicationRelativeUrls(string url) + { + // Arrange + var helper = CreateUrlHelper(); + + // Act + var result = helper.IsLocalUrl(url); + + // Assert + Assert.True(result); + } + + [Theory] + [InlineData("foo.html")] + [InlineData("../foo.html")] + [InlineData("fold/foo.html")] + public void IsLocalUrl_RejectsRelativeUrls(string url) + { + // Arrange + var helper = CreateUrlHelper(); + + // Act + var result = helper.IsLocalUrl(url); + + // Assert + Assert.False(result); + } + + [Theory] + [InlineData("http:/foo.html")] + [InlineData("hTtP:foo.html")] + [InlineData("http:/www.example.com")] + [InlineData("HtTpS:/www.example.com")] + public void IsLocalUrl_RejectValidButUnsafeRelativeUrls(string url) + { + // Arrange + var helper = CreateUrlHelper(); + + // Act + var result = helper.IsLocalUrl(url); + + // Assert + Assert.False(result); + } + + [Theory] + [InlineData("http://www.mysite.com/appDir/foo.html")] + [InlineData("http://WWW.MYSITE.COM")] + public void IsLocalUrl_RejectsUrlsOnTheSameHost(string url) + { + // Arrange + var helper = CreateUrlHelper("www.mysite.com"); + + // Act + var result = helper.IsLocalUrl(url); + + // Assert + Assert.False(result); + } + + [Theory] + [InlineData("http://localhost/foobar.html")] + [InlineData("http://127.0.0.1/foobar.html")] + public void IsLocalUrl_RejectsUrlsOnLocalHost(string url) + { + // Arrange + var helper = CreateUrlHelper("www.mysite.com"); + + // Act + var result = helper.IsLocalUrl(url); + + // Assert + Assert.False(result); + } + + [Theory] + [InlineData("https://www.mysite.com/")] + public void IsLocalUrl_RejectsUrlsOnTheSameHostButDifferentScheme(string url) + { + // Arrange + var helper = CreateUrlHelper("www.mysite.com"); + + // Act + var result = helper.IsLocalUrl(url); + + // Assert + Assert.False(result); + } + + [Theory] + [InlineData("http://www.example.com")] + [InlineData("https://www.example.com")] + [InlineData("hTtP://www.example.com")] + [InlineData("HtTpS://www.example.com")] + public void IsLocalUrl_RejectsUrlsOnDifferentHost(string url) + { + // Arrange + var helper = CreateUrlHelper("www.mysite.com"); + + // Act + var result = helper.IsLocalUrl(url); + + // Assert + Assert.False(result); + } + + [Theory] + [InlineData("http://///www.example.com/foo.html")] + [InlineData("https://///www.example.com/foo.html")] + [InlineData("HtTpS://///www.example.com/foo.html")] + [InlineData("http:///www.example.com/foo.html")] + [InlineData("http:////www.example.com/foo.html")] + [InlineData("http://///www.example.com/foo.html")] + public void IsLocalUrl_RejectsUrlsWithTooManySchemeSeparatorCharacters(string url) + { + // Arrange + var helper = CreateUrlHelper("www.mysite.com"); + + // Act + var result = helper.IsLocalUrl(url); + + // Assert + Assert.False(result); + } + + [Theory] + [InlineData("//www.example.com")] + [InlineData("//www.example.com?")] + [InlineData("//www.example.com:80")] + [InlineData("//www.example.com/foobar.html")] + [InlineData("///www.example.com")] + [InlineData("//////www.example.com")] + public void IsLocalUrl_RejectsUrlsWithMissingSchemeName(string url) + { + // Arrange + var helper = CreateUrlHelper("www.mysite.com"); + + // Act + var result = helper.IsLocalUrl(url); + + // Assert + Assert.False(result); + } + + [Theory] + [InlineData("http:\\\\www.example.com")] + [InlineData("http:\\\\www.example.com\\")] + [InlineData("/\\")] + [InlineData("/\\foo")] + public void IsLocalUrl_RejectsInvalidUrls(string url) + { + // Arrange + var helper = CreateUrlHelper("www.mysite.com"); + + // Act + var result = helper.IsLocalUrl(url); + + // Assert + Assert.False(result); + } + private static HttpContext CreateHttpContext(string appRoot) { var appRootPath = new PathString(appRoot); @@ -76,6 +271,26 @@ namespace Microsoft.AspNet.Mvc.Core.Test return contextAccessor.Object; } + private static UrlHelper CreateUrlHelper() + { + var context = CreateHttpContext(string.Empty); + var actionContext = CreateActionContext(context); + + var actionSelector = new Mock(MockBehavior.Strict); + return new UrlHelper(actionContext, actionSelector.Object); + } + + private static UrlHelper CreateUrlHelper(string host) + { + var context = CreateHttpContext(string.Empty); + context.Request.Host = new HostString(host); + + var actionContext = CreateActionContext(context); + + var actionSelector = new Mock(MockBehavior.Strict); + return new UrlHelper(actionContext, actionSelector.Object); + } + private static UrlHelper CreateUrlHelper(IContextAccessor contextAccessor) { var actionSelector = new Mock(MockBehavior.Strict);