[Fixes #359] Routing is matching empty segments to parameters and defaults are wrong
This commit is contained in:
parent
032bcf43b2
commit
438ec83227
|
|
@ -82,12 +82,17 @@ namespace Microsoft.AspNetCore.Routing.Template
|
||||||
//
|
//
|
||||||
// On the second pass, we're almost certainly going to match the URL, so go ahead and allocate the 'values'
|
// On the second pass, we're almost certainly going to match the URL, so go ahead and allocate the 'values'
|
||||||
// and start capturing strings.
|
// and start capturing strings.
|
||||||
foreach (var requestSegment in pathTokenizer)
|
foreach (var pathSegment in pathTokenizer)
|
||||||
{
|
{
|
||||||
var routeSegment = Template.GetSegment(i++);
|
if (pathSegment.Length == 0)
|
||||||
if (routeSegment == null && requestSegment.Length > 0)
|
|
||||||
{
|
{
|
||||||
// If pathSegment is null, then we're out of route segments. All we can match is the empty
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var routeSegment = Template.GetSegment(i++);
|
||||||
|
if (routeSegment == null && pathSegment.Length > 0)
|
||||||
|
{
|
||||||
|
// If routeSegment is null, then we're out of route segments. All we can match is the empty
|
||||||
// string.
|
// string.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -95,7 +100,7 @@ namespace Microsoft.AspNetCore.Routing.Template
|
||||||
{
|
{
|
||||||
// This is a literal segment, so we need to match the text, or the route isn't a match.
|
// This is a literal segment, so we need to match the text, or the route isn't a match.
|
||||||
var part = routeSegment.Parts[0];
|
var part = routeSegment.Parts[0];
|
||||||
if (!requestSegment.Equals(part.Text, StringComparison.OrdinalIgnoreCase))
|
if (!pathSegment.Equals(part.Text, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -111,7 +116,7 @@ namespace Microsoft.AspNetCore.Routing.Template
|
||||||
{
|
{
|
||||||
// For a parameter, validate that it's a has some length, or we have a default, or it's optional.
|
// For a parameter, validate that it's a has some length, or we have a default, or it's optional.
|
||||||
var part = routeSegment.Parts[0];
|
var part = routeSegment.Parts[0];
|
||||||
if (requestSegment.Length == 0 &&
|
if (pathSegment.Length == 0 &&
|
||||||
!_hasDefaultValue[i] &&
|
!_hasDefaultValue[i] &&
|
||||||
!part.IsOptional)
|
!part.IsOptional)
|
||||||
{
|
{
|
||||||
|
|
@ -186,7 +191,7 @@ namespace Microsoft.AspNetCore.Routing.Template
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// It's ok for a catch-all to produce a null value, so we don't check _hasDefaultValue.
|
// It's ok for a catch-all to produce a null value, so we don't check _hasDefaultValue.
|
||||||
values[part.Name] =_defaultValues[i];
|
values[part.Name] = _defaultValues[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
// A catch-all has to be the last part, so we're done.
|
// A catch-all has to be the last part, so we're done.
|
||||||
|
|
|
||||||
|
|
@ -1575,6 +1575,34 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
Assert.Equal("RouteName", name);
|
Assert.Equal("RouteName", name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("///")]
|
||||||
|
[InlineData("/a//")]
|
||||||
|
[InlineData("/a/b//")]
|
||||||
|
[InlineData("//b//")]
|
||||||
|
[InlineData("///c")]
|
||||||
|
[InlineData("///c/")]
|
||||||
|
public async Task RouteAsync_MultipleOptionalParameters_WithEmptyIntermediateSegmentsDoesNotMatch(string url)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var builder = CreateRouteBuilder();
|
||||||
|
|
||||||
|
builder.MapRoute(name: null,
|
||||||
|
template: "{controller?}/{action?}/{id?}",
|
||||||
|
defaults: null,
|
||||||
|
constraints: null);
|
||||||
|
|
||||||
|
var route = builder.Build();
|
||||||
|
|
||||||
|
var context = CreateRouteContext(url);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await route.RouteAsync(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Null(context.Handler);
|
||||||
|
}
|
||||||
|
|
||||||
// DataTokens test data for TemplateRoute.GetVirtualPath
|
// DataTokens test data for TemplateRoute.GetVirtualPath
|
||||||
public static IEnumerable<object[]> DataTokensTestData
|
public static IEnumerable<object[]> DataTokensTestData
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -991,6 +991,107 @@ namespace Microsoft.AspNetCore.Routing.Template.Tests
|
||||||
Assert.False(values.ContainsKey("id"));
|
Assert.False(values.ContainsKey("id"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("///")]
|
||||||
|
[InlineData("/a//")]
|
||||||
|
[InlineData("/a/b//")]
|
||||||
|
[InlineData("//b//")]
|
||||||
|
[InlineData("///c")]
|
||||||
|
[InlineData("///c/")]
|
||||||
|
public void TryMatch_MultipleOptionalParameters_WithEmptyIntermediateSegmentsDoesNotMatch(string url)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var route = CreateMatcher("{controller?}/{action?}/{id?}");
|
||||||
|
|
||||||
|
var values = new RouteValueDictionary();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var match = route.TryMatch(url, values);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(match);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("")]
|
||||||
|
[InlineData("/")]
|
||||||
|
[InlineData("/a")]
|
||||||
|
[InlineData("/a/")]
|
||||||
|
[InlineData("/a/b")]
|
||||||
|
[InlineData("/a/b/")]
|
||||||
|
[InlineData("/a/b/c")]
|
||||||
|
[InlineData("/a/b/c/")]
|
||||||
|
public void TryMatch_MultipleOptionalParameters_WithIncrementalOptionalValues(string url)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var route = CreateMatcher("{controller?}/{action?}/{id?}");
|
||||||
|
|
||||||
|
var values = new RouteValueDictionary();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var match = route.TryMatch(url, values);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(match);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("///")]
|
||||||
|
[InlineData("////")]
|
||||||
|
[InlineData("/a//")]
|
||||||
|
[InlineData("/a///")]
|
||||||
|
[InlineData("//b/")]
|
||||||
|
[InlineData("//b//")]
|
||||||
|
[InlineData("///c")]
|
||||||
|
[InlineData("///c/")]
|
||||||
|
public void TryMatch_MultipleParameters_WithEmptyValues(string url)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var route = CreateMatcher("{controller}/{action}/{id}");
|
||||||
|
|
||||||
|
var values = new RouteValueDictionary();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var match = route.TryMatch(url, values);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(match);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("/a/b/c//")]
|
||||||
|
[InlineData("/a/b/c/////")]
|
||||||
|
public void TryMatch_CatchAllParameters_WithEmptyValuesAtTheEnd(string url)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var route = CreateMatcher("{controller}/{action}/{*id}");
|
||||||
|
|
||||||
|
var values = new RouteValueDictionary();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var match = route.TryMatch(url, values);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(match);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("/a/b//")]
|
||||||
|
[InlineData("/a/b///c")]
|
||||||
|
public void TryMatch_CatchAllParameters_WithEmptyValues(string url)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var route = CreateMatcher("{controller}/{action}/{*id}");
|
||||||
|
|
||||||
|
var values = new RouteValueDictionary();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var match = route.TryMatch(url, values);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(match);
|
||||||
|
}
|
||||||
|
|
||||||
private TemplateMatcher CreateMatcher(string template, object defaults = null)
|
private TemplateMatcher CreateMatcher(string template, object defaults = null)
|
||||||
{
|
{
|
||||||
return new TemplateMatcher(
|
return new TemplateMatcher(
|
||||||
|
|
|
||||||
|
|
@ -339,6 +339,127 @@ namespace Microsoft.AspNetCore.Routing.Tree
|
||||||
Assert.Equal(expectedRouteGroup, context.RouteData.Values["test_route_group"]);
|
Assert.Equal(expectedRouteGroup, context.RouteData.Values["test_route_group"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("///")]
|
||||||
|
[InlineData("/a//")]
|
||||||
|
[InlineData("/a/b//")]
|
||||||
|
[InlineData("//b//")]
|
||||||
|
[InlineData("///c")]
|
||||||
|
[InlineData("///c/")]
|
||||||
|
public async Task TryMatch_MultipleOptionalParameters_WithEmptyIntermediateSegmentsDoesNotMatch(string url)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var builder = CreateBuilder();
|
||||||
|
|
||||||
|
MapInboundEntry(builder, "{controller?}/{action?}/{id?}");
|
||||||
|
|
||||||
|
var route = builder.Build();
|
||||||
|
|
||||||
|
var context = CreateRouteContext(url);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await route.RouteAsync(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Null(context.Handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("")]
|
||||||
|
[InlineData("/")]
|
||||||
|
[InlineData("/a")]
|
||||||
|
[InlineData("/a/")]
|
||||||
|
[InlineData("/a/b")]
|
||||||
|
[InlineData("/a/b/")]
|
||||||
|
[InlineData("/a/b/c")]
|
||||||
|
[InlineData("/a/b/c/")]
|
||||||
|
public async Task TryMatch_MultipleOptionalParameters_WithIncrementalOptionalValues(string url)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var builder = CreateBuilder();
|
||||||
|
|
||||||
|
MapInboundEntry(builder, "{controller?}/{action?}/{id?}");
|
||||||
|
|
||||||
|
var route = builder.Build();
|
||||||
|
|
||||||
|
var context = CreateRouteContext(url);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await route.RouteAsync(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(context.Handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("///")]
|
||||||
|
[InlineData("////")]
|
||||||
|
[InlineData("/a//")]
|
||||||
|
[InlineData("/a///")]
|
||||||
|
[InlineData("//b/")]
|
||||||
|
[InlineData("//b//")]
|
||||||
|
[InlineData("///c")]
|
||||||
|
[InlineData("///c/")]
|
||||||
|
public async Task TryMatch_MultipleParameters_WithEmptyValues(string url)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var builder = CreateBuilder();
|
||||||
|
|
||||||
|
MapInboundEntry(builder, "{controller}/{action}/{id}");
|
||||||
|
|
||||||
|
var route = builder.Build();
|
||||||
|
|
||||||
|
var context = CreateRouteContext(url);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await route.RouteAsync(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Null(context.Handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("/a/b/c//")]
|
||||||
|
[InlineData("/a/b/c/////")]
|
||||||
|
public async Task TryMatch_CatchAllParameters_WithEmptyValuesAtTheEnd(string url)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var builder = CreateBuilder();
|
||||||
|
|
||||||
|
MapInboundEntry(builder, "{controller}/{action}/{*id}");
|
||||||
|
|
||||||
|
var route = builder.Build();
|
||||||
|
|
||||||
|
var context = CreateRouteContext(url);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await route.RouteAsync(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(context.Handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("/a/b//")]
|
||||||
|
[InlineData("/a/b///c")]
|
||||||
|
public async Task TryMatch_CatchAllParameters_WithEmptyValues(string url)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var builder = CreateBuilder();
|
||||||
|
|
||||||
|
MapInboundEntry(builder, "{controller}/{action}/{*id}");
|
||||||
|
|
||||||
|
var route = builder.Build();
|
||||||
|
|
||||||
|
var context = CreateRouteContext(url);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await route.RouteAsync(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Null(context.Handler);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("{*path}", "/a", "a")]
|
[InlineData("{*path}", "/a", "a")]
|
||||||
[InlineData("{*path}", "/a/b/c", "a/b/c")]
|
[InlineData("{*path}", "/a/b/c", "a/b/c")]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue