Fixing Routing Issue: 136
The RegEx inline constraint doesn't take care of Escape characters.
This commit is contained in:
parent
67dcdbf8a1
commit
4e5fc2e2dd
|
|
@ -203,7 +203,7 @@ namespace Microsoft.AspNet.Routing
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// A path segment that contains more than one section, such as a literal section or a parameter, cannot contain an optional parameter.
|
||||
/// In a path segment that contains more than one section, such as a literal section or a parameter, there can only be one optional parameter. The optional parameter must be the last parameter in the segment and must be preceded by one single period (.).
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_CanHaveOnlyLastParameterOptional_IfFollowingOptionalSeperator
|
||||
{
|
||||
|
|
@ -211,7 +211,7 @@ namespace Microsoft.AspNet.Routing
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// A path segment that contains more than one section, such as a literal section or a parameter, cannot contain an optional parameter.
|
||||
/// In a path segment that contains more than one section, such as a literal section or a parameter, there can only be one optional parameter. The optional parameter must be the last parameter in the segment and must be preceded by one single period (.).
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_CanHaveOnlyLastParameterOptional_IfFollowingOptionalSeperator()
|
||||
{
|
||||
|
|
@ -347,7 +347,7 @@ namespace Microsoft.AspNet.Routing
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// The constraint entry '{0}' - '{1}' on route '{2}' must have a string value or be of a type which implements '{3}'.
|
||||
/// The constraint entry '{0}' - '{1}' on the route '{2}' must have a string value or be of a type which implements '{3}'.
|
||||
/// </summary>
|
||||
internal static string RouteConstraintBuilder_ValidationMustBeStringOrCustomConstraint
|
||||
{
|
||||
|
|
@ -355,7 +355,7 @@ namespace Microsoft.AspNet.Routing
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// The constraint entry '{0}' - '{1}' on route '{2}' must have a string value or be of a type which implements '{3}'.
|
||||
/// The constraint entry '{0}' - '{1}' on the route '{2}' must have a string value or be of a type which implements '{3}'.
|
||||
/// </summary>
|
||||
internal static string FormatRouteConstraintBuilder_ValidationMustBeStringOrCustomConstraint(object p0, object p1, object p2, object p3)
|
||||
{
|
||||
|
|
@ -363,7 +363,7 @@ namespace Microsoft.AspNet.Routing
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// The constraint entry '{0}' - '{1}' on route '{2}' could not be resolved by the constraint resolver of type '{3}'.
|
||||
/// The constraint entry '{0}' - '{1}' on the route '{2}' could not be resolved by the constraint resolver of type '{3}'.
|
||||
/// </summary>
|
||||
internal static string RouteConstraintBuilder_CouldNotResolveConstraint
|
||||
{
|
||||
|
|
@ -371,13 +371,29 @@ namespace Microsoft.AspNet.Routing
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// The constraint entry '{0}' - '{1}' on route '{2}' could not be resolved by the constraint resolver of type '{3}'.
|
||||
/// The constraint entry '{0}' - '{1}' on the route '{2}' could not be resolved by the constraint resolver of type '{3}'.
|
||||
/// </summary>
|
||||
internal static string FormatRouteConstraintBuilder_CouldNotResolveConstraint(object p0, object p1, object p2, object p3)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("RouteConstraintBuilder_CouldNotResolveConstraint"), p0, p1, p2, p3);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// In a route parameter, '{' and '}' must be escaped with '{{' and '}}'
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_UnescapedBrace
|
||||
{
|
||||
get { return GetString("TemplateRoute_UnescapedBrace"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// In a route parameter, '{' and '}' must be escaped with '{{' and '}}'
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_UnescapedBrace()
|
||||
{
|
||||
return GetString("TemplateRoute_UnescapedBrace");
|
||||
}
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -186,4 +186,7 @@
|
|||
<data name="RouteConstraintBuilder_CouldNotResolveConstraint" xml:space="preserve">
|
||||
<value>The constraint entry '{0}' - '{1}' on the route '{2}' could not be resolved by the constraint resolver of type '{3}'.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_UnescapedBrace" xml:space="preserve">
|
||||
<value>In a route parameter, '{' and '}' must be escaped with '{{' and '}}'</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Microsoft.AspNet.Routing.Template
|
||||
{
|
||||
|
|
@ -134,30 +135,41 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
|
||||
while (true)
|
||||
{
|
||||
if (context.Current == Separator)
|
||||
if (context.Current == OpenBrace)
|
||||
{
|
||||
// This is a dangling open-brace, which is not allowed
|
||||
context.Error = Resources.TemplateRoute_MismatchedParameter;
|
||||
return false;
|
||||
}
|
||||
else if (context.Current == OpenBrace)
|
||||
{
|
||||
// If we see a '{' while parsing a parameter name it's invalid. We'll just accept it for now
|
||||
// and let the validation code for the name find it.
|
||||
// This is an open brace inside of a parameter, it has to be escaped
|
||||
if (context.Next())
|
||||
{
|
||||
if (context.Current != OpenBrace)
|
||||
{
|
||||
// If we see something like "{p1:regex(^\d{3", we will come here.
|
||||
context.Error = Resources.TemplateRoute_UnescapedBrace;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is a dangling open-brace, which is not allowed
|
||||
// Example: "{p1:regex(^\d{"
|
||||
context.Error = Resources.TemplateRoute_MismatchedParameter;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (context.Current == CloseBrace)
|
||||
{
|
||||
// When we encounter Closed brace here, it either means end of the parameter or it is a closed
|
||||
// brace in the parameter, in that case it needs to be escaped.
|
||||
// Example: {p1:regex(([}}])\w+}. First pair is escaped one and last marks end of the parameter
|
||||
if (!context.Next())
|
||||
{
|
||||
// This is the end of the string - and we have a valid parameter
|
||||
// This is the end of the string -and we have a valid parameter
|
||||
context.Back();
|
||||
break;
|
||||
}
|
||||
|
||||
if (context.Current == CloseBrace)
|
||||
{
|
||||
// This is an 'escaped' brace in a parameter name, which is not allowed.
|
||||
// We'll just accept it for now and let the validation code for the name find it.
|
||||
// This is an 'escaped' brace in a parameter name
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -176,10 +188,11 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
}
|
||||
|
||||
var rawParameter = context.Capture();
|
||||
var decoded = rawParameter.Replace("}}", "}").Replace("{{", "{");
|
||||
|
||||
// At this point, we need to parse the raw name for inline constraint,
|
||||
// default values and optional parameters.
|
||||
var templatePart = InlineRouteParameterParser.ParseRouteParameter(rawParameter);
|
||||
var templatePart = InlineRouteParameterParser.ParseRouteParameter(decoded);
|
||||
|
||||
if (templatePart.IsCatchAll && templatePart.IsOptional)
|
||||
{
|
||||
|
|
@ -272,7 +285,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
}
|
||||
}
|
||||
|
||||
var decoded = encoded.Replace("}}", "}").Replace("{{", "}");
|
||||
var decoded = encoded.Replace("}}", "}").Replace("{{", "{");
|
||||
if (IsValidLiteral(context, decoded))
|
||||
{
|
||||
segment.Parts.Add(TemplatePart.CreateLiteral(decoded));
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@ namespace Microsoft.AspNet.Routing.Tests
|
|||
[InlineData("Abcd", "abc", true)] // Extra char
|
||||
[InlineData("^Abcd", "abc", true)] // Extra special char
|
||||
[InlineData("Abc", " abc", false)] // Missing char
|
||||
[InlineData("123-456-2334", @"^\d{3}-\d{3}-\d{4}$", true)] // ssn
|
||||
[InlineData(@"12/4/2013", @"^\d{1,2}\/\d{1,2}\/\d{4}$", true)] // date
|
||||
[InlineData(@"abc@def.com", @"^\w+[\w\.]*\@\w+((-\w+)|(\w*))\.[a-z]{2,3}$", true)] // email
|
||||
public void RegexConstraintBuildRegexVerbatimFromInput(string routeValue,
|
||||
string constraintValue,
|
||||
bool shouldMatch)
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ namespace Microsoft.AspNet.Routing.Tests
|
|||
// Arrange, Act & Assert
|
||||
var ex = Assert.Throws<InvalidOperationException>(
|
||||
() => _constraintResolver.ResolveConstraint("int(5)"));
|
||||
Assert.Equal("Could not find a constructor for constraint type 'IntRouteConstraint'"+
|
||||
Assert.Equal("Could not find a constructor for constraint type 'IntRouteConstraint'" +
|
||||
" with the following number of parameters: 1.",
|
||||
ex.Message);
|
||||
}
|
||||
|
|
@ -74,6 +74,17 @@ namespace Microsoft.AspNet.Routing.Tests
|
|||
Assert.IsType<RegexInlineRouteConstraint>(constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolveConstraint_RegexInlineConstraint_WithCurlyBraces_Balanced()
|
||||
{
|
||||
// Arrange & Act
|
||||
var constraint = _constraintResolver.ResolveConstraint(
|
||||
@"regex(\\b(?<month>\\d{1,2})/(?<day>\\d{1,2})/(?<year>\\d{2,4})\\b)");
|
||||
|
||||
// Assert
|
||||
Assert.IsType<RegexInlineRouteConstraint>(constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolveConstraint_BoolConstraint()
|
||||
{
|
||||
|
|
@ -267,7 +278,7 @@ namespace Microsoft.AspNet.Routing.Tests
|
|||
|
||||
// Act & Assert
|
||||
var ex = Assert.Throws<InvalidOperationException>(() => resolver.ResolveConstraint("custom"));
|
||||
Assert.Equal("The constraint type 'System.String' which is mapped to constraint key 'custom'"+
|
||||
Assert.Equal("The constraint type 'System.String' which is mapped to constraint key 'custom'" +
|
||||
" must implement the 'IRouteConstraint' interface.",
|
||||
ex.Message);
|
||||
}
|
||||
|
|
@ -287,6 +298,21 @@ namespace Microsoft.AspNet.Routing.Tests
|
|||
ex.Message);
|
||||
}
|
||||
|
||||
// These are cases which parsing does not catch and we'll end up here
|
||||
[Theory]
|
||||
[InlineData("regex(abc")]
|
||||
[InlineData("int/")]
|
||||
[InlineData("in{t")]
|
||||
public void ResolveConstraint_Invalid_Throws(string constraint)
|
||||
{
|
||||
// Arrange
|
||||
var routeOptions = new RouteOptions();
|
||||
var resolver = GetInlineConstraintResolver(routeOptions);
|
||||
|
||||
// Act & Assert
|
||||
Assert.Null(resolver.ResolveConstraint(constraint));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolveConstraint_NoMatchingConstructor_Throws()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -256,6 +256,36 @@ namespace Microsoft.AspNet.Routing.Tests
|
|||
Assert.Single(templatePart.InlineConstraints, c => c.Constraint == @"test(\?)");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintWithBraces_PatternIsParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"p1:regex(^\d{{3}}-\d{{3}}-\d{{4}}$)"); // ssn
|
||||
|
||||
// Assert
|
||||
Assert.Equal("p1", templatePart.Name);
|
||||
Assert.Null(templatePart.DefaultValue);
|
||||
Assert.False(templatePart.IsOptional);
|
||||
|
||||
Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Single(templatePart.InlineConstraints, c => c.Constraint == @"regex(^\d{{3}}-\d{{3}}-\d{{4}}$)");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintWithBraces_WithDefaultValue()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"p1:regex(^\d{{3}}-\d{{3}}-\d{{4}}$)=123-456-7890"); // ssn
|
||||
|
||||
// Assert
|
||||
Assert.Equal("p1", templatePart.Name);
|
||||
Assert.Equal(templatePart.DefaultValue, "123-456-7890");
|
||||
Assert.False(templatePart.IsOptional);
|
||||
|
||||
Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Single(templatePart.InlineConstraints, c => c.Constraint == @"regex(^\d{{3}}-\d{{3}}-\d{{4}}$)");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("", "")]
|
||||
[InlineData("?", "")]
|
||||
|
|
|
|||
|
|
@ -102,7 +102,26 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("moo/{p1}.{p2?}", "moo/foo.bar", "foo", "bar")]
|
||||
[InlineData(@"{p1:regex(^\d{{3}}-\d{{3}}-\d{{4}}$)}", "123-456-7890")] // ssn
|
||||
[InlineData(@"{p1:regex(^\w+\@\w+\.\w+)}", "asd@assds.com")] // email
|
||||
[InlineData(@"{p1:regex(([}}])\w+)}", "}sda")] // Not balanced }
|
||||
[InlineData(@"{p1:regex(([{{)])\w+)}", "})sda")] // Not balanced {
|
||||
public void MatchRoute_RegularExpression_Valid(
|
||||
string template,
|
||||
string path)
|
||||
{
|
||||
// Arrange
|
||||
var matcher = CreateMatcher(template);
|
||||
|
||||
// Act
|
||||
var rd = matcher.Match(path);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(rd);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("moo/{p1}.{p2?}", "moo/foo.bar", "foo", "bar")]
|
||||
[InlineData("moo/{p1?}", "moo/foo", "foo", null)]
|
||||
[InlineData("moo/{p1?}", "moo", null, null)]
|
||||
[InlineData("moo/{p1}.{p2?}", "moo/foo", "foo", null)]
|
||||
|
|
@ -116,9 +135,9 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
[InlineData("moo/{p1}.{p2?}", "moo/....", "..", ".")]
|
||||
[InlineData("moo/{p1}.{p2?}", "moo/.bar", ".bar", null)]
|
||||
public void MatchRoute_OptionalParameter_FollowedByPeriod_Valid(
|
||||
string template,
|
||||
string path,
|
||||
string p1,
|
||||
string template,
|
||||
string path,
|
||||
string p1,
|
||||
string p2)
|
||||
{
|
||||
// Arrange
|
||||
|
|
@ -143,13 +162,14 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
[InlineData("moo/{p1}.{p2}.{p3?}", "moo/foo.moo", "foo", "moo", null)]
|
||||
[InlineData("moo/{p1}.{p2}.{p3}.{p4?}", "moo/foo.moo.bar", "foo", "moo", "bar")]
|
||||
[InlineData("{p1}.{p2?}/{p3}", "foo.moo/bar", "foo", "moo", "bar")]
|
||||
[InlineData("{p1}.{p2?}/{p3}", "foo/bar", "foo", null, "bar")]
|
||||
[InlineData("{p1}.{p2?}/{p3}", "foo/bar", "foo", null, "bar")]
|
||||
[InlineData("{p1}.{p2?}/{p3}", ".foo/bar", ".foo", null, "bar")]
|
||||
[InlineData("{p1}/{p2}/{p3?}", "foo/bar/baz", "foo", "bar", "baz")]
|
||||
public void MatchRoute_OptionalParameter_FollowedByPeriod_3Parameters_Valid(
|
||||
string template,
|
||||
string path,
|
||||
string p1,
|
||||
string p2,
|
||||
string template,
|
||||
string path,
|
||||
string p1,
|
||||
string p2,
|
||||
string p3)
|
||||
{
|
||||
// Arrange
|
||||
|
|
@ -159,9 +179,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
var rd = matcher.Match(path);
|
||||
|
||||
// Assert
|
||||
|
||||
Assert.Equal(p1, rd["p1"]);
|
||||
|
||||
|
||||
if (p2 != null)
|
||||
{
|
||||
Assert.Equal(p2, rd["p2"]);
|
||||
|
|
@ -173,9 +192,9 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Theory]
|
||||
[InlineData("moo/{p1}.{p2?}", "moo/foo.")]
|
||||
[InlineData("moo/{p1}.{p2?}", "moo/.")]
|
||||
[InlineData("moo/{p1}.{p2?}", "moo/.")]
|
||||
[InlineData("moo/{p1}.{p2}", "foo.")]
|
||||
[InlineData("moo/{p1}.{p2}", "foo")]
|
||||
[InlineData("moo/{p1}.{p2}.{p3?}", "moo/foo.moo.")]
|
||||
|
|
@ -183,7 +202,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
[InlineData("moo/foo.{p2}.{p3?}", "moo/kungfoo.moo.bar")]
|
||||
[InlineData("moo/foo.{p2}.{p3?}", "moo/kungfoo.moo")]
|
||||
[InlineData("moo/{p1}.{p2}.{p3?}", "moo/foo")]
|
||||
[InlineData("{p1}.{p2?}/{p3}", "foo./bar")]
|
||||
[InlineData("{p1}.{p2?}/{p3}", "foo./bar")]
|
||||
[InlineData("moo/.{p2?}", "moo/.")]
|
||||
[InlineData("{p1}.{p2}/{p3}", ".foo/bar")]
|
||||
public void MatchRoute_OptionalParameter_FollowedByPeriod_Invalid(string template, string path)
|
||||
|
|
@ -280,7 +299,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
// Assert
|
||||
Assert.Null(rd);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void NoMatchLongerUrl()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#if ASPNET50
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.Testing;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using Microsoft.Framework.DependencyInjection.Fallback;
|
||||
|
|
@ -417,7 +418,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
defaultValue: null,
|
||||
inlineConstraints: null));
|
||||
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[1].Parts.Add(TemplatePart.CreateParameter("p2",
|
||||
false,
|
||||
true,
|
||||
|
|
@ -432,7 +433,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
expected.Parameters.Add(expected.Segments[0].Parts[0]);
|
||||
expected.Parameters.Add(expected.Segments[1].Parts[0]);
|
||||
expected.Parameters.Add(expected.Segments[1].Parts[2]);
|
||||
|
||||
|
||||
// Act
|
||||
var actual = TemplateParser.Parse(template);
|
||||
|
||||
|
|
@ -462,7 +463,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
null,
|
||||
null));
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[0]);
|
||||
expected.Parameters.Add(expected.Segments[1].Parts[1]);
|
||||
expected.Parameters.Add(expected.Segments[1].Parts[1]);
|
||||
|
||||
// Act
|
||||
var actual = TemplateParser.Parse(template);
|
||||
|
|
@ -471,7 +472,62 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Theory]
|
||||
[InlineData(@"{p1:regex(^\d{{3}}-\d{{3}}-\d{{4}}$)}", @"regex(^\d{3}-\d{3}-\d{4}$)")] // ssn
|
||||
[InlineData(@"{p1:regex(^\d{{1,2}}\/\d{{1,2}}\/\d{{4}}$)}", @"regex(^\d{1,2}\/\d{1,2}\/\d{4}$)")] // date
|
||||
[InlineData(@"{p1:regex(^\w+\@\w+\.\w+)}", @"regex(^\w+\@\w+\.\w+)")] // email
|
||||
[InlineData(@"{p1:regex(([}}])\w+)}", @"regex(([}])\w+)")] // Not balanced }
|
||||
[InlineData(@"{p1:regex(([{{(])\w+)}", @"regex(([{(])\w+)")] // Not balanced {
|
||||
public void Parse_RegularExpressions(string template, string constraint)
|
||||
{
|
||||
// Arrange
|
||||
var expected = new RouteTemplate(new List<TemplateSegment>());
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
var c = new InlineConstraint(constraint);
|
||||
expected.Segments[0].Parts.Add(
|
||||
TemplatePart.CreateParameter("p1",
|
||||
false,
|
||||
false,
|
||||
defaultValue: null,
|
||||
inlineConstraints: new List<InlineConstraint> { c }));
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[0]);
|
||||
|
||||
// Act
|
||||
var actual = TemplateParser.Parse(template);
|
||||
|
||||
// Assert
|
||||
Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"{p1:regex(^\d{{3}}-\d{{3}}-\d{{4}}}$)}")] // extra }
|
||||
[InlineData(@"{p1:regex(^\d{{3}}-\d{{3}}-\d{{4}}$)}}")] // extra } at the end
|
||||
[InlineData(@"{{p1:regex(^\d{{3}}-\d{{3}}-\d{{4}}$)}")] // extra { at the begining
|
||||
[InlineData(@"{p1:regex(([}])\w+}")] // Not escaped }
|
||||
[InlineData(@"{p1:regex(^\d{{3}}-\d{{3}}-\d{{4}$)}")] // Not escaped }
|
||||
[InlineData(@"{p1:regex(abc)")]
|
||||
public void Parse_RegularExpressions_Invalid(string template)
|
||||
{
|
||||
// Act and Assert
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse(template),
|
||||
"There is an incomplete parameter in the route template. Check that each '{' character has a matching " +
|
||||
"'}' character." + Environment.NewLine + "Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"{p1:regex(^\d{{3}}-\d{{3}}-\d{{{4}}$)}")] // extra {
|
||||
[InlineData(@"{p1:regex(^\d{{3}}-\d{{3}}-\d{4}}$)}")] // Not escaped {
|
||||
public void Parse_RegularExpressions_Unescaped(string template)
|
||||
{
|
||||
// Act and Assert
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse(template),
|
||||
"In a route parameter, '{' and '}' must be escaped with '{{' and '}}'" + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("{p1?}.{p2?}")]
|
||||
[InlineData("{p1}.{p2?}.{p3}")]
|
||||
[InlineData("{p1?}.{p2}/{p3}")]
|
||||
|
|
@ -485,18 +541,18 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
// Act and Assert
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse(template),
|
||||
"In a path segment that contains more than one section, such as a literal section or a parameter, " +
|
||||
"In a path segment that contains more than one section, such as a literal section or a parameter, " +
|
||||
"there can only be one optional parameter. The optional parameter must be the last parameter in the " +
|
||||
"segment and must be preceded by one single period (.)." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void InvalidTemplate_WithRepeatedParameter()
|
||||
{
|
||||
var ex = ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("{Controller}.mvc/{id}/{controller}"),
|
||||
"The route parameter name 'controller' appears more than one time in the route template." +
|
||||
"The route parameter name 'controller' appears more than one time in the route template." +
|
||||
Environment.NewLine + "Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
|
|
@ -521,7 +577,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("123{a}abc{*moo}"),
|
||||
"A path segment that contains more than one section, such as a literal section or a parameter, " +
|
||||
"A path segment that contains more than one section, such as a literal section or a parameter, " +
|
||||
"cannot contain a catch-all parameter." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
|
@ -531,7 +587,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("{*p1}/{*p2}"),
|
||||
"A catch-all parameter can only appear as the last segment of the route template." +
|
||||
"A catch-all parameter can only appear as the last segment of the route template." +
|
||||
Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
|
@ -565,7 +621,13 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
[InlineData("{*a*:int}", "a*")]
|
||||
[InlineData("{*a*=5}", "a*")]
|
||||
[InlineData("{*a*b=5}", "a*b")]
|
||||
public void ParseRouteParameter_ThrowsIf_ParameterContainsAsterisk(string template, string parameterName)
|
||||
[InlineData("{p1?}.{p2/}/{p3}", "p2/")]
|
||||
[InlineData("{p{{}", "p{")]
|
||||
[InlineData("{p}}}", "p}")]
|
||||
[InlineData("{p/}", "p/")]
|
||||
public void ParseRouteParameter_ThrowsIf_ParameterContainsSpecialCharacters(
|
||||
string template,
|
||||
string parameterName)
|
||||
{
|
||||
// Arrange
|
||||
var expectedMessage = "The route parameter name '" + parameterName + "' is invalid. Route parameter " +
|
||||
|
|
@ -604,7 +666,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("{aaa}/{AAA}"),
|
||||
"The route parameter name 'AAA' appears more than one time in the route template." +
|
||||
"The route parameter name 'AAA' appears more than one time in the route template." +
|
||||
Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
|
@ -614,7 +676,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("{aaa}/{*AAA}"),
|
||||
"The route parameter name 'AAA' appears more than one time in the route template." +
|
||||
"The route parameter name 'AAA' appears more than one time in the route template." +
|
||||
Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
|
@ -624,7 +686,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("{a}/{aa}a}/{z}"),
|
||||
"There is an incomplete parameter in the route template. Check that each '{' character has a " +
|
||||
"There is an incomplete parameter in the route template. Check that each '{' character has a " +
|
||||
"matching '}' character." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
|
@ -634,10 +696,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("{a}/{a{aa}/{z}"),
|
||||
"The route parameter name 'a{aa' 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." + Environment.NewLine +
|
||||
"In a route parameter, '{' and '}' must be escaped with '{{' and '}}'" + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
|
|
@ -680,7 +739,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("foo/{p1}/{*p2}/{p3}"),
|
||||
"A catch-all parameter can only appear as the last segment of the route template." +
|
||||
"A catch-all parameter can only appear as the last segment of the route template." +
|
||||
Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
|
@ -718,7 +777,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("foor?bar"),
|
||||
"The literal section 'foor?bar' is invalid. Literal sections cannot contain the '?' character." +
|
||||
"The literal section 'foor?bar' is invalid. Literal sections cannot contain the '?' character." +
|
||||
Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
|
@ -741,7 +800,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("{foorb?}-bar-{z}"),
|
||||
"In a path segment that contains more than one section, such as a literal section or a parameter, " +
|
||||
"there can only be one optional parameter. The optional parameter must be the last parameter in " +
|
||||
"there can only be one optional parameter. The optional parameter must be the last parameter in " +
|
||||
"the segment and must be preceded by one single period (.)." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
|
@ -814,11 +873,31 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
x.IsCatchAll != y.IsCatchAll ||
|
||||
x.IsOptional != y.IsOptional ||
|
||||
!String.Equals(x.Name, y.Name, StringComparison.Ordinal) ||
|
||||
!String.Equals(x.Name, y.Name, StringComparison.Ordinal))
|
||||
!String.Equals(x.Name, y.Name, StringComparison.Ordinal) ||
|
||||
(x.InlineConstraints == null && y.InlineConstraints != null) ||
|
||||
(x.InlineConstraints != null && y.InlineConstraints == null))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (x.InlineConstraints == null && y.InlineConstraints == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (x.InlineConstraints.Count() != y.InlineConstraints.Count())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var xconstraint in x.InlineConstraints)
|
||||
{
|
||||
if (!y.InlineConstraints.Any<InlineConstraint>(
|
||||
c => string.Equals(c.Constraint, xconstraint.Constraint)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ using Microsoft.AspNet.Routing.Constraints;
|
|||
using Microsoft.AspNet.Routing.Logging;
|
||||
using Microsoft.AspNet.Testing;
|
||||
using Microsoft.Framework.Logging;
|
||||
using Microsoft.AspNet.WebUtilities;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -37,6 +38,28 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
return Tuple.Create(sink, context);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateTemplate_InlineConstraint_Regex_Malformed()
|
||||
{
|
||||
// Arrange
|
||||
var template = @"{controller}/{action}/ {p1:regex(abc} ";
|
||||
var mockTarget = new Mock<IRouter>(MockBehavior.Strict);
|
||||
var expected = "The constraint entry 'p1' - 'regex(abc' on the route " +
|
||||
"'{controller}/{action}/ {p1:regex(abc} ' could not be resolved by the constraint resolver of type " +
|
||||
"'IInlineConstraintResolverProxy'.";
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(
|
||||
() => new TemplateRoute(
|
||||
mockTarget.Object,
|
||||
template,
|
||||
defaults: null,
|
||||
constraints: null,
|
||||
dataTokens: null,
|
||||
inlineConstraintResolver: _inlineConstraintResolver));
|
||||
|
||||
Assert.Equal(expected, exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RouteAsync_MatchSuccess_LogsCorrectValues()
|
||||
{
|
||||
|
|
@ -497,7 +520,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
// Act
|
||||
await route.RouteAsync(context);
|
||||
|
||||
// Assert
|
||||
// Assert
|
||||
Assert.True(context.IsHandled);
|
||||
Assert.True(routeValues.ContainsKey("id"));
|
||||
Assert.Equal("5", routeValues["id"]);
|
||||
|
|
@ -506,6 +529,48 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
Assert.Equal("5", context.RouteData.Values["id"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RouteAsync_InlineConstraint_Regex()
|
||||
{
|
||||
// Arrange
|
||||
var template = @"{controller}/{action}/{ssn:regex(^\d{{3}}-\d{{3}}-\d{{4}}$)}";
|
||||
|
||||
var context = CreateRouteContext("/Home/Index/123-456-7890");
|
||||
|
||||
IDictionary<string, object> routeValues = null;
|
||||
var mockTarget = new Mock<IRouter>(MockBehavior.Strict);
|
||||
mockTarget
|
||||
.Setup(s => s.RouteAsync(It.IsAny<RouteContext>()))
|
||||
.Callback<RouteContext>(ctx =>
|
||||
{
|
||||
routeValues = ctx.RouteData.Values;
|
||||
ctx.IsHandled = true;
|
||||
})
|
||||
.Returns(Task.FromResult(true));
|
||||
|
||||
var route = new TemplateRoute(
|
||||
mockTarget.Object,
|
||||
template,
|
||||
defaults: null,
|
||||
constraints: null,
|
||||
dataTokens: null,
|
||||
inlineConstraintResolver: _inlineConstraintResolver);
|
||||
|
||||
Assert.NotEmpty(route.Constraints);
|
||||
Assert.IsType<RegexInlineRouteConstraint>(route.Constraints["ssn"]);
|
||||
|
||||
// Act
|
||||
await route.RouteAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.True(context.IsHandled);
|
||||
Assert.True(routeValues.ContainsKey("ssn"));
|
||||
Assert.Equal("123-456-7890", routeValues["ssn"]);
|
||||
|
||||
Assert.True(context.RouteData.Values.ContainsKey("ssn"));
|
||||
Assert.Equal("123-456-7890", context.RouteData.Values["ssn"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RouteAsync_InlineConstraint_OptionalParameter_NotPresent()
|
||||
{
|
||||
|
|
@ -1782,7 +1847,10 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
resolverMock.Setup(o => o.ResolveConstraint("int")).Returns(new IntRouteConstraint());
|
||||
resolverMock.Setup(o => o.ResolveConstraint("range(1,20)")).Returns(new RangeRouteConstraint(1, 20));
|
||||
resolverMock.Setup(o => o.ResolveConstraint("alpha")).Returns(new AlphaRouteConstraint());
|
||||
|
||||
resolverMock.Setup(o => o.ResolveConstraint(@"regex(^\d{3}-\d{3}-\d{4}$)")).Returns(
|
||||
new RegexInlineRouteConstraint(@"^\d{3}-\d{3}-\d{4}$"));
|
||||
resolverMock.Setup(o => o.ResolveConstraint(@"regex(^\d{1,2}\/\d{1,2}\/\d{4}$)")).Returns(
|
||||
new RegexInlineRouteConstraint(@"^\d{1,2}\/\d{1,2}\/\d{4}$"));
|
||||
return resolverMock.Object;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,25 @@ namespace Microsoft.AspNet.Routing.Tests
|
|||
Assert.Equal("12", defaults["id"]);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"{controller}/{action}/{p1:regex(([}}])\w+)=}}asd}", "}asd")]
|
||||
[InlineData(@"{p1:regex(^\d{{1,2}}\/\d{{1,2}}\/\d{{4}}$)=12/12/1234}", @"12/12/1234")]
|
||||
public void InlineDefaultValueSpecified_WithSpecialCharacters(string template, string value)
|
||||
{
|
||||
// Arrange & Act
|
||||
var routeBuilder = CreateRouteBuilder();
|
||||
|
||||
// Act
|
||||
routeBuilder.MapRoute("mockName",
|
||||
template,
|
||||
defaults: null,
|
||||
constraints: null);
|
||||
|
||||
// Assert
|
||||
var defaults = ((Template.TemplateRoute)routeBuilder.Routes[0]).Defaults;
|
||||
Assert.Equal(value, defaults["p1"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExplicitDefaultValueSpecified_WithInlineDefaultValue_Throws()
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue