Tolerate leading "~/" or "/" (#499)

Addresses #441
This commit is contained in:
Jass Bagga 2017-11-21 14:20:17 -08:00 committed by GitHub
parent e450e1fa32
commit 54e96bd404
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 70 additions and 38 deletions

View File

@ -58,8 +58,7 @@ namespace Microsoft.AspNetCore.Dispatcher.Patterns
var segment = PathSegments[i]; var segment = PathSegments[i];
for (var j = 0; j < segment.Parts.Count; j++) for (var j = 0; j < segment.Parts.Count; j++)
{ {
var parameter = segment.Parts[j] as RoutePatternParameter; if (segment.Parts[j] is RoutePatternParameter parameter)
if (parameter != null)
{ {
parameters.Add(parameter); parameters.Add(parameter);
} }

View File

@ -33,13 +33,9 @@ namespace Microsoft.AspNetCore.Dispatcher.Patterns
throw new ArgumentNullException(nameof(pattern)); throw new ArgumentNullException(nameof(pattern));
} }
if (IsInvalidPattern(pattern)) var trimmedPattern = TrimPrefix(pattern);
{
throw new RoutePatternException(pattern, Resources.TemplateRoute_InvalidRouteTemplate);
}
var context = new TemplateParserContext(pattern); var context = new TemplateParserContext(trimmedPattern);
var builder = RoutePatternBuilder.Create(pattern);
var segments = new List<RoutePatternPathSegment>(); var segments = new List<RoutePatternPathSegment>();
while (context.MoveNext()) while (context.MoveNext())
@ -69,6 +65,7 @@ namespace Microsoft.AspNetCore.Dispatcher.Patterns
if (IsAllValid(context, segments)) if (IsAllValid(context, segments))
{ {
var builder = RoutePatternBuilder.Create(pattern);
for (var i = 0; i < segments.Count; i++) for (var i = 0; i < segments.Count; i++)
{ {
builder.PathSegments.Add(segments[i]); builder.PathSegments.Add(segments[i]);
@ -257,7 +254,7 @@ namespace Microsoft.AspNetCore.Dispatcher.Patterns
private static bool ParseLiteral(TemplateParserContext context, List<RoutePatternPart> parts) private static bool ParseLiteral(TemplateParserContext context, List<RoutePatternPart> parts)
{ {
context.Mark(); context.Mark();
while (true) while (true)
{ {
if (context.Current == Separator) if (context.Current == Separator)
@ -469,10 +466,21 @@ namespace Microsoft.AspNetCore.Dispatcher.Patterns
return true; return true;
} }
private static bool IsInvalidPattern(string routeTemplate) private static string TrimPrefix(string routePattern)
{ {
return routeTemplate.StartsWith("~", StringComparison.Ordinal) || if (routePattern.StartsWith("~/", StringComparison.Ordinal))
routeTemplate.StartsWith("/", StringComparison.Ordinal); {
return routePattern.Substring(2);
}
else if (routePattern.StartsWith("/", StringComparison.Ordinal))
{
return routePattern.Substring(1);
}
else if (routePattern.StartsWith("~", StringComparison.Ordinal))
{
throw new RoutePatternException(routePattern, Resources.TemplateRoute_InvalidRouteTemplate);
}
return routePattern;
} }
[DebuggerDisplay("{DebuggerToString()}")] [DebuggerDisplay("{DebuggerToString()}")]
@ -555,8 +563,8 @@ namespace Microsoft.AspNetCore.Dispatcher.Patterns
} }
else if (_mark.HasValue) else if (_mark.HasValue)
{ {
return _template.Substring(0, _mark.Value) + return _template.Substring(0, _mark.Value) +
"|" + "|" +
_template.Substring(_mark.Value, _index - _mark.Value) + _template.Substring(_mark.Value, _index - _mark.Value) +
"|" + "|" +
_template.Substring(_index); _template.Substring(_index);

View File

@ -221,7 +221,7 @@ namespace Microsoft.AspNetCore.Dispatcher
=> string.Format(CultureInfo.CurrentCulture, GetString("TemplateRoute_InvalidParameterName"), p0); => string.Format(CultureInfo.CurrentCulture, GetString("TemplateRoute_InvalidParameterName"), p0);
/// <summary> /// <summary>
/// The route template cannot start with a '/' or '~' character. /// The route template cannot start with a '~' character.
/// </summary> /// </summary>
internal static string TemplateRoute_InvalidRouteTemplate internal static string TemplateRoute_InvalidRouteTemplate
{ {
@ -229,7 +229,7 @@ namespace Microsoft.AspNetCore.Dispatcher
} }
/// <summary> /// <summary>
/// The route template cannot start with a '/' or '~' character. /// The route template cannot start with a '~' character.
/// </summary> /// </summary>
internal static string FormatTemplateRoute_InvalidRouteTemplate() internal static string FormatTemplateRoute_InvalidRouteTemplate()
=> GetString("TemplateRoute_InvalidRouteTemplate"); => GetString("TemplateRoute_InvalidRouteTemplate");

View File

@ -164,7 +164,7 @@
<value>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.</value> <value>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.</value>
</data> </data>
<data name="TemplateRoute_InvalidRouteTemplate" xml:space="preserve"> <data name="TemplateRoute_InvalidRouteTemplate" xml:space="preserve">
<value>The route template cannot start with a '/' or '~' character.</value> <value>The route template cannot start with a '~' character unless followed by a '/'.</value>
</data> </data>
<data name="TemplateRoute_MismatchedParameter" xml:space="preserve"> <data name="TemplateRoute_MismatchedParameter" xml:space="preserve">
<value>There is an incomplete parameter in the route template. Check that each '{' character has a matching '}' character.</value> <value>There is an incomplete parameter in the route template. Check that each '{' character has a matching '}' character.</value>

View File

@ -74,8 +74,7 @@ namespace Microsoft.AspNetCore.Dispatcher
// If it's a parameter subsegment, examine the current value to see if it matches the new value // If it's a parameter subsegment, examine the current value to see if it matches the new value
var parameterName = parameter.Name; var parameterName = parameter.Name;
object newParameterValue; var hasNewParameterValue = values.TryGetValue(parameterName, out var newParameterValue);
var hasNewParameterValue = values.TryGetValue(parameterName, out newParameterValue);
object currentParameterValue = null; object currentParameterValue = null;
var hasCurrentParameterValue = ambientValues != null && var hasCurrentParameterValue = ambientValues != null &&
@ -179,8 +178,7 @@ namespace Microsoft.AspNetCore.Dispatcher
continue; continue;
} }
object value; if (values.TryGetValue(filter.Key, out var value))
if (values.TryGetValue(filter.Key, out value))
{ {
if (!RoutePartsEqual(value, filter.Value)) if (!RoutePartsEqual(value, filter.Value))
{ {

View File

@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Routing.Template
try try
{ {
var inner = Microsoft.AspNetCore.Dispatcher.Patterns.RoutePattern.Parse(routeTemplate); var inner = RoutePattern.Parse(routeTemplate);
return new RouteTemplate(inner); return new RouteTemplate(inner);
} }
catch (ArgumentException ex) when (ex.InnerException is RoutePatternException) catch (ArgumentException ex) when (ex.InnerException is RoutePatternException)

View File

@ -622,12 +622,16 @@ namespace Microsoft.AspNetCore.Dispatcher.Patterns
"a literal string."); "a literal string.");
} }
[Fact] [Theory]
public void InvalidTemplate_CannotStartWithSlash() [InlineData("/foo")]
[InlineData("~/foo")]
public void ValidTemplate_CanStartWithSlashOrTildeSlash(string routePattern)
{ {
ExceptionAssert.Throws<RoutePatternException>( // Arrange & Act
() => RoutePatternParser.Parse("/foo"), var pattern = RoutePatternParser.Parse(routePattern);
"The route template cannot start with a '/' or '~' character.");
// Assert
Assert.Equal(routePattern, pattern.RawText);
} }
[Fact] [Fact]
@ -635,7 +639,7 @@ namespace Microsoft.AspNetCore.Dispatcher.Patterns
{ {
ExceptionAssert.Throws<RoutePatternException>( ExceptionAssert.Throws<RoutePatternException>(
() => RoutePatternParser.Parse("~foo"), () => RoutePatternParser.Parse("~foo"),
"The route template cannot start with a '/' or '~' character."); "The route template cannot start with a '~' character unless followed by a '/'.");
} }
[Fact] [Fact]

View File

@ -5,7 +5,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text.Encodings.Web; using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Dispatcher.Patterns;
using Microsoft.Extensions.ObjectPool; using Microsoft.Extensions.ObjectPool;
using Microsoft.Extensions.WebEncoders.Testing; using Microsoft.Extensions.WebEncoders.Testing;
using Xunit; using Xunit;
@ -651,6 +650,28 @@ namespace Microsoft.AspNetCore.Dispatcher
UrlEncoder.Default); UrlEncoder.Default);
} }
[Fact]
public void GetUrlWithLeadingTildeSlash()
{
RunTest(
"~/foo",
null,
null,
new DispatcherValueCollection(new { }),
"/UrlEncode[[foo]]");
}
[Fact]
public void GetUrlWithLeadingSlash()
{
RunTest(
"/foo",
null,
null,
new DispatcherValueCollection(new { }),
"/UrlEncode[[foo]]");
}
[Fact] [Fact]
public void GetUrlWithCatchAllWithValue() public void GetUrlWithCatchAllWithValue()
{ {
@ -1192,8 +1213,7 @@ namespace Microsoft.AspNetCore.Dispatcher
foreach (var kvp in expectedParts.Parameters) foreach (var kvp in expectedParts.Parameters)
{ {
string value; Assert.True(actualParts.Parameters.TryGetValue(kvp.Key, out var value));
Assert.True(actualParts.Parameters.TryGetValue(kvp.Key, out value));
Assert.Equal(kvp.Value, value); Assert.Equal(kvp.Value, value);
} }
} }

View File

@ -765,13 +765,16 @@ namespace Microsoft.AspNetCore.Routing.Template.Tests
"Parameter name: routeTemplate"); "Parameter name: routeTemplate");
} }
[Fact] [Theory]
public void InvalidTemplate_CannotStartWithSlash() [InlineData("/foo")]
[InlineData("~/foo")]
public void ValidTemplate_CanStartWithSlashOrTildeSlash(string routeTemplate)
{ {
ExceptionAssert.Throws<ArgumentException>( // Arrange & Act
() => TemplateParser.Parse("/foo"), var pattern = TemplateParser.Parse(routeTemplate);
"The route template cannot start with a '/' or '~' character." + Environment.NewLine +
"Parameter name: routeTemplate"); // Assert
Assert.Equal(routeTemplate, pattern.TemplateText);
} }
[Fact] [Fact]
@ -779,7 +782,7 @@ namespace Microsoft.AspNetCore.Routing.Template.Tests
{ {
ExceptionAssert.Throws<ArgumentException>( ExceptionAssert.Throws<ArgumentException>(
() => TemplateParser.Parse("~foo"), () => 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"); "Parameter name: routeTemplate");
} }