Introducing new **catchAll parameter to allow generating links with unencoded values
This commit is contained in:
parent
730646c301
commit
731767837a
|
|
@ -81,20 +81,54 @@ namespace RoutingSample.Web
|
|||
EndpointMetadataCollection.Empty,
|
||||
"withoptionalconstraints"),
|
||||
new MatcherEndpoint((next) => (httpContext) =>
|
||||
{
|
||||
using (var writer = new StreamWriter(httpContext.Response.Body, Encoding.UTF8, 1024, leaveOpen: true))
|
||||
{
|
||||
var graphWriter = httpContext.RequestServices.GetRequiredService<DfaGraphWriter>();
|
||||
var dataSource = httpContext.RequestServices.GetRequiredService<CompositeEndpointDataSource>();
|
||||
graphWriter.Write(dataSource, writer);
|
||||
}
|
||||
using (var writer = new StreamWriter(httpContext.Response.Body, Encoding.UTF8, 1024, leaveOpen: true))
|
||||
{
|
||||
var graphWriter = httpContext.RequestServices.GetRequiredService<DfaGraphWriter>();
|
||||
var dataSource = httpContext.RequestServices.GetRequiredService<CompositeEndpointDataSource>();
|
||||
graphWriter.Write(dataSource, writer);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
RoutePatternFactory.Parse("/graph"),
|
||||
0,
|
||||
new EndpointMetadataCollection(new HttpMethodMetadata(new[]{ "GET", })),
|
||||
"DFA Graph"),
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
RoutePatternFactory.Parse("/graph"),
|
||||
0,
|
||||
new EndpointMetadataCollection(new HttpMethodMetadata(new[]{ "GET", })),
|
||||
"DFA Graph"),
|
||||
new MatcherEndpoint((next) => (httpContext) =>
|
||||
{
|
||||
var linkGenerator = httpContext.RequestServices.GetRequiredService<LinkGenerator>();
|
||||
|
||||
var response = httpContext.Response;
|
||||
response.StatusCode = 200;
|
||||
response.ContentType = "text/plain";
|
||||
return response.WriteAsync(
|
||||
"Link: " + linkGenerator.GetLink(httpContext, "WithSingleAsteriskCatchAll", new { }));
|
||||
},
|
||||
RoutePatternFactory.Parse("/WithSingleAsteriskCatchAll/{*path}"),
|
||||
0,
|
||||
new EndpointMetadataCollection(
|
||||
new RouteValuesAddressMetadata(
|
||||
name: "WithSingleAsteriskCatchAll",
|
||||
requiredValues: new RouteValueDictionary())),
|
||||
"WithSingleAsteriskCatchAll"),
|
||||
new MatcherEndpoint((next) => (httpContext) =>
|
||||
{
|
||||
var linkGenerator = httpContext.RequestServices.GetRequiredService<LinkGenerator>();
|
||||
|
||||
var response = httpContext.Response;
|
||||
response.StatusCode = 200;
|
||||
response.ContentType = "text/plain";
|
||||
return response.WriteAsync(
|
||||
"Link: " + linkGenerator.GetLink(httpContext, "WithDoubleAsteriskCatchAll", new { }));
|
||||
},
|
||||
RoutePatternFactory.Parse("/WithDoubleAsteriskCatchAll/{**path}"),
|
||||
0,
|
||||
new EndpointMetadataCollection(
|
||||
new RouteValuesAddressMetadata(
|
||||
name: "WithDoubleAsteriskCatchAll",
|
||||
requiredValues: new RouteValueDictionary())),
|
||||
"WithDoubleAsteriskCatchAll"),
|
||||
});
|
||||
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<EndpointDataSource>(endpointDataSource));
|
||||
|
|
|
|||
|
|
@ -43,6 +43,11 @@ namespace Microsoft.AspNetCore.Routing.Internal
|
|||
public TextWriter Writer { get; }
|
||||
|
||||
public bool Accept(string value)
|
||||
{
|
||||
return Accept(value, encodeSlashes: true);
|
||||
}
|
||||
|
||||
public bool Accept(string value, bool encodeSlashes)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
|
|
@ -67,7 +72,7 @@ namespace Microsoft.AspNetCore.Routing.Internal
|
|||
{
|
||||
if (_buffer[i].RequiresEncoding)
|
||||
{
|
||||
_urlEncoder.Encode(Writer, _buffer[i].Value);
|
||||
EncodeValue(_buffer[i].Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -88,16 +93,17 @@ namespace Microsoft.AspNetCore.Routing.Internal
|
|||
UriState = SegmentState.Inside;
|
||||
|
||||
_lastValueOffset = _uri.Length;
|
||||
|
||||
// Allow the first segment to have a leading slash.
|
||||
// This prevents the leading slash from PathString segments from being encoded.
|
||||
if (_uri.Length == 0 && value.Length > 0 && value[0] == '/')
|
||||
{
|
||||
_uri.Append("/");
|
||||
_urlEncoder.Encode(Writer, value, 1, value.Length - 1);
|
||||
EncodeValue(value, 1, value.Length - 1, encodeSlashes);
|
||||
}
|
||||
else
|
||||
{
|
||||
_urlEncoder.Encode(Writer, value);
|
||||
EncodeValue(value, encodeSlashes);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
@ -197,6 +203,44 @@ namespace Microsoft.AspNetCore.Routing.Internal
|
|||
return _uri.ToString();
|
||||
}
|
||||
|
||||
private void EncodeValue(string value)
|
||||
{
|
||||
EncodeValue(value, encodeSlashes: true);
|
||||
}
|
||||
|
||||
private void EncodeValue(string value, bool encodeSlashes)
|
||||
{
|
||||
EncodeValue(value, start: 0, characterCount: value.Length, encodeSlashes);
|
||||
}
|
||||
|
||||
// For testing
|
||||
internal void EncodeValue(string value, int start, int characterCount, bool encodeSlashes)
|
||||
{
|
||||
// Just encode everything if its ok to encode slashes
|
||||
if (encodeSlashes)
|
||||
{
|
||||
_urlEncoder.Encode(Writer, value, start, characterCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
int end;
|
||||
int length = start + characterCount;
|
||||
while ((end = value.IndexOf('/', start, characterCount)) >= 0)
|
||||
{
|
||||
_urlEncoder.Encode(Writer, value, start, end - start);
|
||||
_uri.Append("/");
|
||||
|
||||
start = end + 1;
|
||||
characterCount = length - start;
|
||||
}
|
||||
|
||||
if (end < 0 && characterCount >= 0)
|
||||
{
|
||||
_urlEncoder.Encode(Writer, value, start, length - start);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string DebuggerToString()
|
||||
{
|
||||
return string.Format("{{Accepted: '{0}' Buffered: '{1}'}}", _uri, string.Join("", _buffer));
|
||||
|
|
|
|||
|
|
@ -27,9 +27,17 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
|||
|
||||
var startIndex = 0;
|
||||
var endIndex = parameter.Length - 1;
|
||||
var encodeSlashes = true;
|
||||
|
||||
var parameterKind = RoutePatternParameterKind.Standard;
|
||||
if (parameter[0] == '*')
|
||||
|
||||
if (parameter.StartsWith("**", StringComparison.Ordinal))
|
||||
{
|
||||
encodeSlashes = false;
|
||||
parameterKind = RoutePatternParameterKind.CatchAll;
|
||||
startIndex += 2;
|
||||
}
|
||||
else if (parameter[0] == '*')
|
||||
{
|
||||
parameterKind = RoutePatternParameterKind.CatchAll;
|
||||
startIndex++;
|
||||
|
|
@ -79,7 +87,12 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
|||
defaultValue = parameter.Substring(currentIndex + 1, endIndex - currentIndex);
|
||||
}
|
||||
|
||||
return new RoutePatternParameterPart(parameterName, defaultValue, parameterKind, parseResults.Constraints.ToArray());
|
||||
return new RoutePatternParameterPart(
|
||||
parameterName,
|
||||
defaultValue,
|
||||
parameterKind,
|
||||
parseResults.Constraints.ToArray(),
|
||||
encodeSlashes);
|
||||
}
|
||||
|
||||
private static ConstraintParseResults ParseConstraints(
|
||||
|
|
@ -237,7 +250,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
|||
public readonly int CurrentIndex;
|
||||
|
||||
public readonly IReadOnlyList<RoutePatternConstraintReference> Constraints;
|
||||
|
||||
|
||||
public ConstraintParseResults(int currentIndex, IReadOnlyList<RoutePatternConstraintReference> constraints)
|
||||
{
|
||||
CurrentIndex = currentIndex;
|
||||
|
|
|
|||
|
|
@ -284,7 +284,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
|||
{
|
||||
var segment = VisitSegment(updatedSegments[i]);
|
||||
updatedSegments[i] = segment;
|
||||
|
||||
|
||||
for (var j = 0; j < segment.Parts.Count; j++)
|
||||
{
|
||||
if (segment.Parts[j] is RoutePatternParameterPart parameter)
|
||||
|
|
@ -339,7 +339,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
|||
|
||||
@default = newDefault;
|
||||
}
|
||||
|
||||
|
||||
if (parameter.Default != null)
|
||||
{
|
||||
updatedDefaults.Add(parameter.Name, parameter.Default);
|
||||
|
|
@ -361,7 +361,8 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
|||
parameter.Name,
|
||||
@default,
|
||||
parameter.ParameterKind,
|
||||
(IEnumerable<RoutePatternConstraintReference>)parameterConstraints ?? Array.Empty<RoutePatternConstraintReference>());
|
||||
(IEnumerable<RoutePatternConstraintReference>)parameterConstraints ?? Array.Empty<RoutePatternConstraintReference>(),
|
||||
parameter.EncodeSlashes);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -624,7 +625,22 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
|||
RoutePatternParameterKind parameterKind,
|
||||
IEnumerable<RoutePatternConstraintReference> constraints)
|
||||
{
|
||||
return new RoutePatternParameterPart(parameterName, @default, parameterKind, constraints.ToArray());
|
||||
return ParameterPartCore(parameterName, @default, parameterKind, constraints, encodeSlashes: true);
|
||||
}
|
||||
|
||||
private static RoutePatternParameterPart ParameterPartCore(
|
||||
string parameterName,
|
||||
object @default,
|
||||
RoutePatternParameterKind parameterKind,
|
||||
IEnumerable<RoutePatternConstraintReference> constraints,
|
||||
bool encodeSlashes)
|
||||
{
|
||||
return new RoutePatternParameterPart(
|
||||
parameterName,
|
||||
@default,
|
||||
parameterKind,
|
||||
constraints.ToArray(),
|
||||
encodeSlashes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -19,6 +19,16 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
|||
object @default,
|
||||
RoutePatternParameterKind parameterKind,
|
||||
RoutePatternConstraintReference[] constraints)
|
||||
: this(parameterName, @default, parameterKind, constraints, encodeSlashes: true)
|
||||
{
|
||||
}
|
||||
|
||||
internal RoutePatternParameterPart(
|
||||
string parameterName,
|
||||
object @default,
|
||||
RoutePatternParameterKind parameterKind,
|
||||
RoutePatternConstraintReference[] constraints,
|
||||
bool encodeSlashes)
|
||||
: base(RoutePatternPartKind.Parameter)
|
||||
{
|
||||
// See #475 - this code should have some asserts, but it can't because of the design of RouteParameterParser.
|
||||
|
|
@ -27,6 +37,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
|||
Default = @default;
|
||||
ParameterKind = parameterKind;
|
||||
Constraints = constraints;
|
||||
EncodeSlashes = encodeSlashes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -34,6 +45,11 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
|||
/// </summary>
|
||||
public IReadOnlyList<RoutePatternConstraintReference> Constraints { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value indicating if slashes in current parameter's value should be encoded.
|
||||
/// </summary>
|
||||
public bool EncodeSlashes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default value of this route parameter. May be null.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using System.Collections.Generic;
|
|||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Text.Encodings.Web;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing.Internal;
|
||||
using Microsoft.AspNetCore.Routing.Patterns;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
|
|
@ -223,12 +224,12 @@ namespace Microsoft.AspNetCore.Routing.Template
|
|||
// If the value is not accepted, it is null or empty value in the
|
||||
// middle of the segment. We accept this if the parameter is an
|
||||
// optional parameter and it is preceded by an optional seperator.
|
||||
// I this case, we need to remove the optional seperator that we
|
||||
// In this case, we need to remove the optional seperator that we
|
||||
// have added to the URI
|
||||
// Example: template = {id}.{format?}. parameters: id=5
|
||||
// In this case after we have generated "5.", we wont find any value
|
||||
// for format, so we remove '.' and generate 5.
|
||||
if (!context.Accept(converted))
|
||||
if (!context.Accept(converted, parameterPart.EncodeSlashes))
|
||||
{
|
||||
if (j != 0 && parameterPart.IsOptional && (separatorPart = segment.Parts[j - 1] as RoutePatternSeparatorPart) != null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -130,6 +130,54 @@ namespace Microsoft.AspNetCore.Routing.FunctionalTests
|
|||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/WithSingleAsteriskCatchAll/a/b/c", "Link: /WithSingleAsteriskCatchAll/a%2Fb%2Fc")]
|
||||
[InlineData("/WithSingleAsteriskCatchAll/a/b b1/c c1", "Link: /WithSingleAsteriskCatchAll/a%2Fb%20b1%2Fc%20c1")]
|
||||
public async Task GeneratesLink_ToEndpointWithSingleAsteriskCatchAllParameter_EncodesValue(
|
||||
string url,
|
||||
string expected)
|
||||
{
|
||||
// Arrange & Act
|
||||
var response = await _client.GetAsync(url);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.NotNull(response.Content);
|
||||
var actualContent = await response.Content.ReadAsStringAsync();
|
||||
Assert.Equal(expected, actualContent);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/WithDoubleAsteriskCatchAll/a/b/c", "Link: /WithDoubleAsteriskCatchAll/a/b/c")]
|
||||
[InlineData("/WithDoubleAsteriskCatchAll/a/b/c/", "Link: /WithDoubleAsteriskCatchAll/a/b/c/")]
|
||||
[InlineData("/WithDoubleAsteriskCatchAll/a//b/c", "Link: /WithDoubleAsteriskCatchAll/a//b/c")]
|
||||
public async Task GeneratesLink_ToEndpointWithDoubleAsteriskCatchAllParameter_DoesNotEncodeSlashes(
|
||||
string url,
|
||||
string expected)
|
||||
{
|
||||
// Arrange & Act
|
||||
var response = await _client.GetAsync(url);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.NotNull(response.Content);
|
||||
var actualContent = await response.Content.ReadAsStringAsync();
|
||||
Assert.Equal(expected, actualContent);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GeneratesLink_ToEndpointWithDoubleAsteriskCatchAllParameter_EncodesContentOtherThanSlashes()
|
||||
{
|
||||
// Arrange & Act
|
||||
var response = await _client.GetAsync("/WithDoubleAsteriskCatchAll/a/b b1/c c1");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.NotNull(response.Content);
|
||||
var actualContent = await response.Content.ReadAsStringAsync();
|
||||
Assert.Equal("Link: /WithDoubleAsteriskCatchAll/a/b%20b1/c%20c1", actualContent);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_testServer.Dispose();
|
||||
|
|
|
|||
|
|
@ -98,6 +98,76 @@ namespace Microsoft.AspNetCore.Routing
|
|||
Assert.Equal("/Home/Index", link);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetLink_EncodesIntermediate_DefaultValues()
|
||||
{
|
||||
// Arrange
|
||||
var endpoint = EndpointFactory.CreateMatcherEndpoint("{p1}/{p2=a b}/{p3=foo}");
|
||||
var linkGenerator = CreateLinkGenerator(endpoint);
|
||||
|
||||
// Act
|
||||
var link = linkGenerator.GetLink(new { p1 = "Home", p3 = "bar" });
|
||||
|
||||
// Assert
|
||||
Assert.Equal("/Home/a%20b/bar", link);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("a/b/c", "/Home/Index/a%2Fb%2Fc")]
|
||||
[InlineData("a/b b1/c c1", "/Home/Index/a%2Fb%20b1%2Fc%20c1")]
|
||||
public void GetLink_EncodesValue_OfSingleAsteriskCatchAllParameter(string routeValue, string expected)
|
||||
{
|
||||
// Arrange
|
||||
var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}/{*path}");
|
||||
var linkGenerator = CreateLinkGenerator(endpoint);
|
||||
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Index" });
|
||||
|
||||
// Act
|
||||
var link = linkGenerator.GetLink(httpContext, new { path = routeValue });
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, link);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/", "/Home/Index//")]
|
||||
[InlineData("a", "/Home/Index/a")]
|
||||
[InlineData("a/", "/Home/Index/a/")]
|
||||
[InlineData("a/b", "/Home/Index/a/b")]
|
||||
[InlineData("a/b/c", "/Home/Index/a/b/c")]
|
||||
[InlineData("a/b/cc", "/Home/Index/a/b/cc")]
|
||||
[InlineData("a/b/c/", "/Home/Index/a/b/c/")]
|
||||
[InlineData("a/b/c//", "/Home/Index/a/b/c//")]
|
||||
[InlineData("a//b//c", "/Home/Index/a//b//c")]
|
||||
public void GetLink_DoesNotEncodeSlashes_OfDoubleAsteriskCatchAllParameter(string routeValue, string expected)
|
||||
{
|
||||
// Arrange
|
||||
var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}/{**path}");
|
||||
var linkGenerator = CreateLinkGenerator(endpoint);
|
||||
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Index" });
|
||||
|
||||
// Act
|
||||
var link = linkGenerator.GetLink(httpContext, new { path = routeValue });
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, link);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetLink_EncodesContentOtherThanSlashes_OfDoubleAsteriskCatchAllParameter()
|
||||
{
|
||||
// Arrange
|
||||
var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}/{**path}");
|
||||
var linkGenerator = CreateLinkGenerator(endpoint);
|
||||
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Index" });
|
||||
|
||||
// Act
|
||||
var link = linkGenerator.GetLink(httpContext, new { path = "a/b b1/c c1" });
|
||||
|
||||
// Assert
|
||||
Assert.Equal("/Home/Index/a/b%20b1/c%20c1", link);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetLink_EncodesValues()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.Extensions.WebEncoders.Testing;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Internal
|
||||
{
|
||||
public class UriBuildingContextTest
|
||||
{
|
||||
[Fact]
|
||||
public void EncodeValue_EncodesEntireValue_WhenEncodeSlashes_IsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var urlTestEncoder = new UrlTestEncoder();
|
||||
var value = "a/b b1/c";
|
||||
var expected = "/UrlEncode[[a/b b1/c]]";
|
||||
var uriBuilldingContext = new UriBuildingContext(urlTestEncoder);
|
||||
|
||||
// Act
|
||||
uriBuilldingContext.EncodeValue(value, 0, value.Length, encodeSlashes: true);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, uriBuilldingContext.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EncodeValue_EncodesOnlySlashes_WhenEncodeSlashes_IsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var urlTestEncoder = new UrlTestEncoder();
|
||||
var value = "a/b b1/c";
|
||||
var expected = "/UrlEncode[[a]]/UrlEncode[[b b1]]/UrlEncode[[c]]";
|
||||
var uriBuilldingContext = new UriBuildingContext(urlTestEncoder);
|
||||
|
||||
// Act
|
||||
uriBuilldingContext.EncodeValue(value, 0, value.Length, encodeSlashes: false);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, uriBuilldingContext.ToString());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("a/b b1/c", 0, 2, "/UrlEncode[[a]]/")]
|
||||
[InlineData("a/b b1/c", 3, 4, "/UrlEncode[[ b1]]/")]
|
||||
[InlineData("a/b b1/c", 3, 5, "/UrlEncode[[ b1]]/UrlEncode[[c]]")]
|
||||
[InlineData("a/b b1/c/", 8, 1, "/")]
|
||||
[InlineData("/", 0, 1, "/")]
|
||||
[InlineData("/a", 0, 2, "/UrlEncode[[a]]")]
|
||||
[InlineData("a", 0, 1, "/UrlEncode[[a]]")]
|
||||
[InlineData("a/", 0, 2, "/UrlEncode[[a]]/")]
|
||||
public void EncodeValue_EncodesOnlySlashes_WithinSubsegment_WhenEncodeSlashes_IsFalse(
|
||||
string value,
|
||||
int startIndex,
|
||||
int characterCount,
|
||||
string expected)
|
||||
{
|
||||
// Arrange
|
||||
var urlTestEncoder = new UrlTestEncoder();
|
||||
var uriBuilldingContext = new UriBuildingContext(urlTestEncoder);
|
||||
|
||||
// Act
|
||||
uriBuilldingContext.EncodeValue(value, startIndex, characterCount, encodeSlashes: false);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, uriBuilldingContext.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Routing.Constraints;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Patterns
|
||||
|
|
@ -919,6 +920,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
|||
[InlineData("", "")]
|
||||
[InlineData("?", "")]
|
||||
[InlineData("*", "")]
|
||||
[InlineData("**", "")]
|
||||
[InlineData(" ", " ")]
|
||||
[InlineData("\t", "\t")]
|
||||
[InlineData("#!@#$%Q@#@%", "#!@#$%Q@#@%")]
|
||||
|
|
@ -936,6 +938,134 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
|||
Assert.Null(templatePart.Default);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_WithSingleAsteriskCatchAll_IsParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var parameterPart = ParseParameter("*path");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("path", parameterPart.Name);
|
||||
Assert.True(parameterPart.IsCatchAll);
|
||||
Assert.Equal(RoutePatternParameterKind.CatchAll, parameterPart.ParameterKind);
|
||||
Assert.True(parameterPart.EncodeSlashes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_WithSingleAsteriskCatchAll_AndDefaultValue_IsParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var parameterPart = ParseParameter("*path=a/b/c");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("path", parameterPart.Name);
|
||||
Assert.True(parameterPart.IsCatchAll);
|
||||
Assert.NotNull(parameterPart.Default);
|
||||
Assert.Equal("a/b/c", parameterPart.Default.ToString());
|
||||
Assert.Equal(RoutePatternParameterKind.CatchAll, parameterPart.ParameterKind);
|
||||
Assert.True(parameterPart.EncodeSlashes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_WithSingleAsteriskCatchAll_AndConstraints_IsParsedCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var constraintContent = "regex(^(/[^/ ]*)+/?$)";
|
||||
|
||||
// Act
|
||||
var parameterPart = ParseParameter($"*path:{constraintContent}");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("path", parameterPart.Name);
|
||||
Assert.True(parameterPart.IsCatchAll);
|
||||
Assert.Equal(RoutePatternParameterKind.CatchAll, parameterPart.ParameterKind);
|
||||
var constraintReference = Assert.Single(parameterPart.Constraints);
|
||||
Assert.Equal(constraintContent, constraintReference.Content);
|
||||
Assert.True(parameterPart.EncodeSlashes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_WithSingleAsteriskCatchAll_AndConstraints_AndDefaultValue_IsParsedCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var constraintContent = "regex(^(/[^/ ]*)+/?$)";
|
||||
|
||||
// Act
|
||||
var parameterPart = ParseParameter($"*path:{constraintContent}=a/b/c");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("path", parameterPart.Name);
|
||||
Assert.True(parameterPart.IsCatchAll);
|
||||
Assert.Equal(RoutePatternParameterKind.CatchAll, parameterPart.ParameterKind);
|
||||
var constraintReference = Assert.Single(parameterPart.Constraints);
|
||||
Assert.Equal(constraintContent, constraintReference.Content);
|
||||
Assert.NotNull(parameterPart.Default);
|
||||
Assert.Equal("a/b/c", parameterPart.Default.ToString());
|
||||
Assert.True(parameterPart.EncodeSlashes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_WithDoubleAsteriskCatchAll_IsParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var parameterPart = ParseParameter("**path");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("path", parameterPart.Name);
|
||||
Assert.True(parameterPart.IsCatchAll);
|
||||
Assert.False(parameterPart.EncodeSlashes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_WithDoubleAsteriskCatchAll_AndDefaultValue_IsParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var parameterPart = ParseParameter("**path=a/b/c");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("path", parameterPart.Name);
|
||||
Assert.True(parameterPart.IsCatchAll);
|
||||
Assert.NotNull(parameterPart.Default);
|
||||
Assert.Equal("a/b/c", parameterPart.Default.ToString());
|
||||
Assert.False(parameterPart.EncodeSlashes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_WithDoubleAsteriskCatchAll_AndConstraints_IsParsedCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var constraintContent = "regex(^(/[^/ ]*)+/?$)";
|
||||
|
||||
// Act
|
||||
var parameterPart = ParseParameter($"**path:{constraintContent}");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("path", parameterPart.Name);
|
||||
Assert.True(parameterPart.IsCatchAll);
|
||||
Assert.False(parameterPart.EncodeSlashes);
|
||||
var constraintReference = Assert.Single(parameterPart.Constraints);
|
||||
Assert.Equal(constraintContent, constraintReference.Content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_WithDoubleAsteriskCatchAll_AndConstraints_AndDefaultValue_IsParsedCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var constraintContent = "regex(^(/[^/ ]*)+/?$)";
|
||||
|
||||
// Act
|
||||
var parameterPart = ParseParameter($"**path:{constraintContent}=a/b/c");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("path", parameterPart.Name);
|
||||
Assert.True(parameterPart.IsCatchAll);
|
||||
Assert.False(parameterPart.EncodeSlashes);
|
||||
var constraintReference = Assert.Single(parameterPart.Constraints);
|
||||
Assert.Equal(constraintContent, constraintReference.Content);
|
||||
Assert.NotNull(parameterPart.Default);
|
||||
Assert.Equal("a/b/c", parameterPart.Default.ToString());
|
||||
}
|
||||
|
||||
private RoutePatternParameterPart ParseParameter(string routeParameter)
|
||||
{
|
||||
// See: #475 - these tests don't pass the 'whole' text.
|
||||
|
|
|
|||
|
|
@ -470,7 +470,6 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("{**}", "*")]
|
||||
[InlineData("{a*}", "a*")]
|
||||
[InlineData("{*a*}", "a*")]
|
||||
[InlineData("{*a*:int}", "a*")]
|
||||
|
|
|
|||
|
|
@ -626,7 +626,6 @@ namespace Microsoft.AspNetCore.Routing.Template.Tests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("{**}", "*")]
|
||||
[InlineData("{a*}", "a*")]
|
||||
[InlineData("{*a*}", "a*")]
|
||||
[InlineData("{*a*:int}", "a*")]
|
||||
|
|
|
|||
Loading…
Reference in New Issue