diff --git a/src/Microsoft.AspNetCore.Routing/Template/RoutePrecedence.cs b/src/Microsoft.AspNetCore.Routing/Template/RoutePrecedence.cs index 4d7ba66b35..d529c511d6 100644 --- a/src/Microsoft.AspNetCore.Routing/Template/RoutePrecedence.cs +++ b/src/Microsoft.AspNetCore.Routing/Template/RoutePrecedence.cs @@ -20,6 +20,8 @@ namespace Microsoft.AspNetCore.Routing.Template // /api/template/{id:int} == 1.12 public static decimal ComputeInbound(RouteTemplate template) { + ValidateSegementLength(template.Segments.Count); + // Each precedence digit corresponds to one decimal place. For example, 3 segments with precedences 2, 1, // and 4 results in a combined precedence of 2.14 (decimal). var precedence = 0m; @@ -40,6 +42,8 @@ namespace Microsoft.AspNetCore.Routing.Template // See description on ComputeInbound(RouteTemplate) internal static decimal ComputeInbound(RoutePattern routePattern) { + ValidateSegementLength(routePattern.PathSegments.Count); + var precedence = 0m; for (var i = 0; i < routePattern.PathSegments.Count; i++) @@ -62,6 +66,8 @@ namespace Microsoft.AspNetCore.Routing.Template // /api/template/{id:int} == 5.54 public static decimal ComputeOutbound(RouteTemplate template) { + ValidateSegementLength(template.Segments.Count); + // Each precedence digit corresponds to one decimal place. For example, 3 segments with precedences 2, 1, // and 4 results in a combined precedence of 2.14 (decimal). var precedence = 0m; @@ -82,6 +88,8 @@ namespace Microsoft.AspNetCore.Routing.Template // see description on ComputeOutbound(RouteTemplate) internal static decimal ComputeOutbound(RoutePattern routePattern) { + ValidateSegementLength(routePattern.PathSegments.Count); + // Each precedence digit corresponds to one decimal place. For example, 3 segments with precedences 2, 1, // and 4 results in a combined precedence of 2.14 (decimal). var precedence = 0m; @@ -99,6 +107,15 @@ namespace Microsoft.AspNetCore.Routing.Template return precedence; } + private static void ValidateSegementLength(int length) + { + if (length > 28) + { + // An OverflowException will be thrown by Math.Pow when greater than 28 + throw new InvalidOperationException("Route exceeds the maximum number of allowed segments of 28 and is unable to be processed."); + } + } + // Segments have the following order: // 5 - Literal segments // 4 - Multi-part segments && Constrained parameter segments diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Template/RoutePatternPrecedenceTests.cs b/test/Microsoft.AspNetCore.Routing.Tests/Template/RoutePatternPrecedenceTests.cs new file mode 100644 index 0000000000..049263444e --- /dev/null +++ b/test/Microsoft.AspNetCore.Routing.Tests/Template/RoutePatternPrecedenceTests.cs @@ -0,0 +1,27 @@ +// 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 System; +using Microsoft.AspNetCore.Routing.Patterns; + +namespace Microsoft.AspNetCore.Routing.Template +{ + public class RoutePatternPrecedenceTests : RoutePrecedenceTestsBase + { + protected override decimal ComputeMatched(string template) + { + return ComputeRoutePattern(template, RoutePrecedence.ComputeInbound); + } + + protected override decimal ComputeGenerated(string template) + { + return ComputeRoutePattern(template, RoutePrecedence.ComputeOutbound); + } + + private static decimal ComputeRoutePattern(string template, Func func) + { + var parsed = RoutePatternFactory.Parse(template); + return func(parsed); + } + } +} diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Template/RoutePrecedenceTests.cs b/test/Microsoft.AspNetCore.Routing.Tests/Template/RoutePrecedenceTestsBase.cs similarity index 74% rename from test/Microsoft.AspNetCore.Routing.Tests/Template/RoutePrecedenceTests.cs rename to test/Microsoft.AspNetCore.Routing.Tests/Template/RoutePrecedenceTestsBase.cs index 7dd04454e2..b5c2f87db1 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/Template/RoutePrecedenceTests.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/Template/RoutePrecedenceTestsBase.cs @@ -2,13 +2,11 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using Microsoft.Extensions.Options; -using Moq; using Xunit; namespace Microsoft.AspNetCore.Routing.Template { - public class RoutePrecedenceTests + public abstract class RoutePrecedenceTestsBase { [Theory] [InlineData("Employees/{id}", "Employees/{employeeId}")] @@ -100,22 +98,34 @@ namespace Microsoft.AspNetCore.Routing.Template Assert.True(xPrecedence > yPrecedence); } - private static decimal ComputeMatched(string template) + [Fact] + public void ComputeGenerated_TooManySegments_ThrowHumaneError() { - return Compute(template, RoutePrecedence.ComputeInbound); - } - private static decimal ComputeGenerated(string template) - { - return Compute(template, RoutePrecedence.ComputeOutbound); + var ex = Assert.Throws(() => + { + // Arrange & Act + ComputeGenerated("{a}/{b}/{c}/{d}/{e}/{f}/{g}/{h}/{i}/{j}/{k}/{l}/{m}/{n}/{o}/{p}/{q}/{r}/{s}/{t}/{u}/{v}/{w}/{x}/{y}/{z}/{a2}/{b2}/{b3}"); + }); + + // Assert + Assert.Equal("Route exceeds the maximum number of allowed segments of 28 and is unable to be processed.", ex.Message); } - private static decimal Compute(string template, Func func) + [Fact] + public void ComputeMatched_TooManySegments_ThrowHumaneError() { - var options = new Mock>(); - options.SetupGet(o => o.Value).Returns(new RouteOptions()); + var ex = Assert.Throws(() => + { + // Arrange & Act + ComputeMatched("{a}/{b}/{c}/{d}/{e}/{f}/{g}/{h}/{i}/{j}/{k}/{l}/{m}/{n}/{o}/{p}/{q}/{r}/{s}/{t}/{u}/{v}/{w}/{x}/{y}/{z}/{a2}/{b2}/{b3}"); + }); - var parsed = TemplateParser.Parse(template); - return func(parsed); + // Assert + Assert.Equal("Route exceeds the maximum number of allowed segments of 28 and is unable to be processed.", ex.Message); } + + protected abstract decimal ComputeMatched(string template); + + protected abstract decimal ComputeGenerated(string template); } } diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Template/RouteTemplatePrecedenceTests.cs b/test/Microsoft.AspNetCore.Routing.Tests/Template/RouteTemplatePrecedenceTests.cs new file mode 100644 index 0000000000..98157fa669 --- /dev/null +++ b/test/Microsoft.AspNetCore.Routing.Tests/Template/RouteTemplatePrecedenceTests.cs @@ -0,0 +1,26 @@ +// 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 System; + +namespace Microsoft.AspNetCore.Routing.Template +{ + public class RouteTemplatePrecedenceTests : RoutePrecedenceTestsBase + { + protected override decimal ComputeMatched(string template) + { + return ComputeRouteTemplate(template, RoutePrecedence.ComputeInbound); + } + + protected override decimal ComputeGenerated(string template) + { + return ComputeRouteTemplate(template, RoutePrecedence.ComputeOutbound); + } + + private static decimal ComputeRouteTemplate(string template, Func func) + { + var parsed = TemplateParser.Parse(template); + return func(parsed); + } + } +}