From 57697baedbe9d08536aa9f8f3bd48654d58ac84e Mon Sep 17 00:00:00 2001 From: Jass Bagga Date: Thu, 11 Jan 2018 13:00:01 -0800 Subject: [PATCH] Tolerate leading "~/" or "/" (#509) --- .../Properties/Resources.Designer.cs | 4 +- .../Resources.resx | 56 +++++++++---------- .../Template/TemplateParser.cs | 24 ++++++-- .../Template/TemplateBinderTests.cs | 26 ++++++++- .../Template/TemplateParserTests.cs | 23 ++++---- 5 files changed, 86 insertions(+), 47 deletions(-) diff --git a/src/Microsoft.AspNetCore.Routing/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Routing/Properties/Resources.Designer.cs index 5cb82620c1..6d2bb0a7c9 100644 --- a/src/Microsoft.AspNetCore.Routing/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.Routing/Properties/Resources.Designer.cs @@ -249,7 +249,7 @@ namespace Microsoft.AspNetCore.Routing => string.Format(CultureInfo.CurrentCulture, GetString("TemplateRoute_InvalidParameterName"), p0); /// - /// The route template cannot start with a '/' or '~' character. + /// The route template cannot start with a '~' character unless followed by a '/'. /// internal static string TemplateRoute_InvalidRouteTemplate { @@ -257,7 +257,7 @@ namespace Microsoft.AspNetCore.Routing } /// - /// The route template cannot start with a '/' or '~' character. + /// The route template cannot start with a '~' character unless followed by a '/'. /// internal static string FormatTemplateRoute_InvalidRouteTemplate() => GetString("TemplateRoute_InvalidRouteTemplate"); diff --git a/src/Microsoft.AspNetCore.Routing/Resources.resx b/src/Microsoft.AspNetCore.Routing/Resources.resx index 96ecf1f445..d883906f7c 100644 --- a/src/Microsoft.AspNetCore.Routing/Resources.resx +++ b/src/Microsoft.AspNetCore.Routing/Resources.resx @@ -1,17 +1,17 @@  - @@ -169,7 +169,7 @@ The route parameter name '{0}' is invalid. Route parameter names must be non-empty and cannot contain these characters: '{{', '}}', '/'. The '?' character marks a parameter as optional, and can occur only at the end of the parameter. The '*' character marks a parameter as catch-all, and can occur only at the start of the parameter. - The route template cannot start with a '/' or '~' character. + The route template cannot start with a '~' character unless followed by a '/'. There is an incomplete parameter in the route template. Check that each '{' character has a matching '}' character. diff --git a/src/Microsoft.AspNetCore.Routing/Template/TemplateParser.cs b/src/Microsoft.AspNetCore.Routing/Template/TemplateParser.cs index 51f598a486..0168b22a4b 100644 --- a/src/Microsoft.AspNetCore.Routing/Template/TemplateParser.cs +++ b/src/Microsoft.AspNetCore.Routing/Template/TemplateParser.cs @@ -25,12 +25,9 @@ namespace Microsoft.AspNetCore.Routing.Template routeTemplate = String.Empty; } - if (IsInvalidRouteTemplate(routeTemplate)) - { - throw new ArgumentException(Resources.TemplateRoute_InvalidRouteTemplate, nameof(routeTemplate)); - } + var trimmedRouteTemplate = TrimPrefix(routeTemplate); - var context = new TemplateParserContext(routeTemplate); + var context = new TemplateParserContext(trimmedRouteTemplate); var segments = new List(); while (context.Next()) @@ -61,6 +58,23 @@ namespace Microsoft.AspNetCore.Routing.Template } } + private static string TrimPrefix(string routeTemplate) + { + if (routeTemplate.StartsWith("~/", StringComparison.Ordinal)) + { + return routeTemplate.Substring(2); + } + else if (routeTemplate.StartsWith("/", StringComparison.Ordinal)) + { + return routeTemplate.Substring(1); + } + else if (routeTemplate.StartsWith("~", StringComparison.Ordinal)) + { + throw new ArgumentException(Resources.TemplateRoute_InvalidRouteTemplate, nameof(routeTemplate)); + } + return routeTemplate; + } + private static bool ParseSegment(TemplateParserContext context, List segments) { Debug.Assert(context != null); diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Template/TemplateBinderTests.cs b/test/Microsoft.AspNetCore.Routing.Tests/Template/TemplateBinderTests.cs index 4d71164ce6..ab06c3f2cc 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/Template/TemplateBinderTests.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/Template/TemplateBinderTests.cs @@ -691,6 +691,28 @@ namespace Microsoft.AspNetCore.Routing.Template.Tests "/UrlEncode[[v1]]"); } + [Fact] + public void GetUrlWithLeadingTildeSlash() + { + RunTest( + "~/foo", + null, + null, + new RouteValueDictionary(new { }), + "/UrlEncode[[foo]]"); + } + + [Fact] + public void GetUrlWithLeadingSlash() + { + RunTest( + "/foo", + null, + null, + new RouteValueDictionary(new { }), + "/UrlEncode[[foo]]"); + } + [Fact] public void TemplateBinder_KeepsExplicitlySuppliedRouteValues_OnFailedRouetMatch() { @@ -721,7 +743,7 @@ namespace Microsoft.AspNetCore.Routing.Template.Tests #if ROUTE_COLLECTION - [Fact] + [Fact] public void GetUrlShouldValidateOnlyAcceptedParametersAndUserDefaultValuesForInvalidatedParameters() { // Arrange @@ -755,7 +777,7 @@ namespace Microsoft.AspNetCore.Routing.Template.Tests Assert.Equal("/app1/UrlConstraints/Validation.mvc/Input5/MissmatchedValidateParameters2/valid1", vpd.VirtualPath); } - [Fact] + [Fact] public void GetUrlWithRouteThatHasExtensionWithSubsequentDefaultValueIncludesExtensionButNotDefaultValue() { // Arrange diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Template/TemplateParserTests.cs b/test/Microsoft.AspNetCore.Routing.Tests/Template/TemplateParserTests.cs index 7ffd8f1026..7f9f9b8b40 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/Template/TemplateParserTests.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/Template/TemplateParserTests.cs @@ -655,6 +655,18 @@ namespace Microsoft.AspNetCore.Routing.Template.Tests "Parameter name: routeTemplate"); } + [Theory] + [InlineData("/foo")] + [InlineData("~/foo")] + public void ValidTemplate_CanStartWithSlashOrTildeSlash(string routeTemplate) + { + // Arrange & Act + var template = TemplateParser.Parse(routeTemplate); + + // Assert + Assert.Equal(routeTemplate, template.TemplateText); + } + [Fact] public void InvalidTemplate_CannotHaveConsecutiveOpenBrace() { @@ -768,21 +780,12 @@ namespace Microsoft.AspNetCore.Routing.Template.Tests "Parameter name: routeTemplate"); } - [Fact] - public void InvalidTemplate_CannotStartWithSlash() - { - ExceptionAssert.Throws( - () => TemplateParser.Parse("/foo"), - "The route template cannot start with a '/' or '~' character." + Environment.NewLine + - "Parameter name: routeTemplate"); - } - [Fact] public void InvalidTemplate_CannotStartWithTilde() { ExceptionAssert.Throws( () => TemplateParser.Parse("~foo"), - "The route template cannot start with a '/' or '~' character." + Environment.NewLine + + "The route template cannot start with a '~' character unless followed by a '/'." + Environment.NewLine + "Parameter name: routeTemplate"); }