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];
for (var j = 0; j < segment.Parts.Count; j++)
{
var parameter = segment.Parts[j] as RoutePatternParameter;
if (parameter != null)
if (segment.Parts[j] is RoutePatternParameter parameter)
{
parameters.Add(parameter);
}

View File

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

View File

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

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
var parameterName = parameter.Name;
object newParameterValue;
var hasNewParameterValue = values.TryGetValue(parameterName, out newParameterValue);
var hasNewParameterValue = values.TryGetValue(parameterName, out var newParameterValue);
object currentParameterValue = null;
var hasCurrentParameterValue = ambientValues != null &&
@ -179,8 +178,7 @@ namespace Microsoft.AspNetCore.Dispatcher
continue;
}
object value;
if (values.TryGetValue(filter.Key, out value))
if (values.TryGetValue(filter.Key, out var value))
{
if (!RoutePartsEqual(value, filter.Value))
{

View File

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

View File

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

View File

@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Dispatcher.Patterns;
using Microsoft.Extensions.ObjectPool;
using Microsoft.Extensions.WebEncoders.Testing;
using Xunit;
@ -651,6 +650,28 @@ namespace Microsoft.AspNetCore.Dispatcher
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]
public void GetUrlWithCatchAllWithValue()
{
@ -1192,8 +1213,7 @@ namespace Microsoft.AspNetCore.Dispatcher
foreach (var kvp in expectedParts.Parameters)
{
string value;
Assert.True(actualParts.Parameters.TryGetValue(kvp.Key, out value));
Assert.True(actualParts.Parameters.TryGetValue(kvp.Key, out var value));
Assert.Equal(kvp.Value, value);
}
}

View File

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