diff --git a/src/Microsoft.AspNet.Routing/Internal/PathSegment.cs b/src/Microsoft.AspNet.Routing/Internal/PathSegment.cs new file mode 100644 index 0000000000..44f60819f5 --- /dev/null +++ b/src/Microsoft.AspNet.Routing/Internal/PathSegment.cs @@ -0,0 +1,136 @@ +// 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.AspNet.Routing.Internal +{ + public struct PathSegment : IEquatable, IEquatable + { + private readonly string _path; + private readonly int _start; + private readonly int _length; + + private string _segment; + + public PathSegment(string path, int start, int length) + { + if (path == null) + { + throw new ArgumentNullException(nameof(path)); + } + + if (start < 1 || start >= path.Length) + { + throw new ArgumentOutOfRangeException(nameof(start)); + } + + if (length < 0 || start + length > path.Length) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + _path = path; + _start = start; + _length = length; + + _segment = null; + } + + public int Length => _length; + + public string GetRemainingPath() + { + if (_path == null) + { + return string.Empty; + } + + return _path.Substring(_start); + } + + public override bool Equals(object obj) + { + var other = obj as PathSegment?; + if (other == null) + { + return false; + } + else + { + return Equals(other.Value); + } + } + + public override int GetHashCode() + { + if (_path == null) + { + return 0; + } + + return StringComparer.OrdinalIgnoreCase.GetHashCode(ToString()); + } + + public override string ToString() + { + if (_path == null) + { + return string.Empty; + } + + if (_segment == null) + { + _segment = _path.Substring(_start, _length); + } + + return _segment; + } + + public bool Equals(PathSegment other) + { + if (_path == null) + { + return other._path == null; + } + + if (other._length != _length) + { + return false; + } + + return string.Compare( + _path, + _start, + other._path, + other._start, + _length, + StringComparison.OrdinalIgnoreCase) == 0; + } + + public bool Equals(string other) + { + if (_path == null) + { + return other == null; + } + + if (other.Length != _length) + { + return false; + } + + return string.Compare(_path, _start, other, 0, _length, StringComparison.OrdinalIgnoreCase) == 0; + } + + public static bool operator ==(PathSegment x, PathSegment y) + { + return x.Equals(y); + } + + public static bool operator !=(PathSegment x, PathSegment y) + { + return !x.Equals(y); + } + } +} diff --git a/src/Microsoft.AspNet.Routing/Internal/PathTokenizer.cs b/src/Microsoft.AspNet.Routing/Internal/PathTokenizer.cs new file mode 100644 index 0000000000..b54ee326cc --- /dev/null +++ b/src/Microsoft.AspNet.Routing/Internal/PathTokenizer.cs @@ -0,0 +1,204 @@ +// 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 System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using Microsoft.AspNet.Http; + +namespace Microsoft.AspNet.Routing.Internal +{ + public struct PathTokenizer : IReadOnlyList + { + private readonly string _path; + private int _count; + + public PathTokenizer(PathString path) + { + _path = path.Value; + _count = -1; + } + + public int Count + { + get + { + if (_count == -1) + { + // We haven't computed the real count of segments yet. + if (_path.Length == 0) + { + // The empty string has length of 0. + _count = 0; + return _count; + } + + // A string of length 1 must be "/" - all PathStrings start with '/' + if (_path.Length == 1) + { + // We treat this as empty - there's nothing to parse here for routing, because routing ignores + // a trailing slash. + Debug.Assert(_path[0] == '/'); + _count = 0; + return _count; + } + + // This is a non-trival PathString + _count = 1; + + // Since a non-empty PathString must begin with a `/`, we can just count the number of occurrences + // of `/` to find the number of segments. However, we don't look at the last character, because + // routing ignores a trailing slash. + for (var i = 1; i < _path.Length - 1; i++) + { + if (_path[i] == '/') + { + _count++; + } + } + } + + return _count; + } + } + + public PathSegment this[int index] + { + get + { + if (index >= Count) + { + throw new IndexOutOfRangeException(); + } + + + var currentSegmentIndex = 0; + var currentSegmentStart = 1; + + // Skip the first `/`. + var delimiterIndex = 1; + while ((delimiterIndex = _path.IndexOf('/', delimiterIndex)) != -1) + { + if (currentSegmentIndex++ == index) + { + return new PathSegment(_path, currentSegmentStart, delimiterIndex - currentSegmentStart); + } + else + { + currentSegmentStart = delimiterIndex + 1; + delimiterIndex++; + } + } + + // If we get here we're at the end of the string. The implementation of .Count should protect us + // from these cases. + Debug.Assert(_path[_path.Length - 1] != '/'); + Debug.Assert(currentSegmentIndex == index); + + return new PathSegment(_path, currentSegmentStart, _path.Length - currentSegmentStart); + } + } + + public Enumerator GetEnumerator() + { + return new Enumerator(this); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public struct Enumerator : IEnumerator + { + private readonly string _path; + + private int _index; + private int _length; + + public Enumerator(PathTokenizer tokenizer) + { + _path = tokenizer._path; + + _index = -1; + _length = -1; + } + + public PathSegment Current + { + get + { + return new PathSegment(_path, _index, _length); + } + } + + object IEnumerator.Current + { + get + { + return Current; + } + } + + public void Dispose() + { + } + + public bool MoveNext() + { + if (_path == null || _path.Length <= 1) + { + return false; + } + + if (_index == -1) + { + // Skip the first `/`. + _index = 1; + } + else + { + // Skip to the end of the previous segment + the separator. + _index += _length + 1; + } + + if (_index >= _path.Length) + { + // We're at the end + return false; + } + + var delimiterIndex = _path.IndexOf('/', _index); + if (delimiterIndex != -1) + { + _length = delimiterIndex - _index; + return true; + } + + // We might have some trailing text after the last separator. + if (_path[_path.Length - 1] == '/') + { + // If the last char is a '/' then it's just a trailing slash, we don't have another segment. + return false; + } + else + { + _length = _path.Length - _index; + return true; + } + } + + public void Reset() + { + _index = -1; + _length = -1; + } + } + } +} diff --git a/src/Microsoft.AspNet.Routing/Template/TemplateMatcher.cs b/src/Microsoft.AspNet.Routing/Template/TemplateMatcher.cs index 24e8b3f133..e2de1e5887 100644 --- a/src/Microsoft.AspNet.Routing/Template/TemplateMatcher.cs +++ b/src/Microsoft.AspNet.Routing/Template/TemplateMatcher.cs @@ -4,6 +4,8 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using Microsoft.AspNet.Http; +using Microsoft.AspNet.Routing.Internal; namespace Microsoft.AspNet.Routing.Template { @@ -36,18 +38,12 @@ namespace Microsoft.AspNet.Routing.Template public RouteTemplate Template { get; private set; } - public IDictionary Match(string requestPath) + public IDictionary Match(PathString path) { - if (requestPath == null) - { - throw new ArgumentNullException(nameof(requestPath)); - } - - var requestSegments = requestPath.Split(Delimiters); - var values = new RouteValueDictionary(); - for (var i = 0; i < requestSegments.Length; i++) + var requestSegments = new PathTokenizer(path); + for (var i = 0; i < requestSegments.Count; i++) { var routeSegment = Template.Segments.Count > i ? Template.Segments[i] : null; var requestSegment = requestSegments[i]; @@ -67,7 +63,7 @@ namespace Microsoft.AspNet.Routing.Template var part = routeSegment.Parts[0]; if (part.IsLiteral) { - if (!string.Equals(part.Text, requestSegment, StringComparison.OrdinalIgnoreCase)) + if (!requestSegment.Equals(part.Text)) { return null; } @@ -78,7 +74,7 @@ namespace Microsoft.AspNet.Routing.Template if (part.IsCatchAll) { - var captured = string.Join(SeparatorString, requestSegments, i, requestSegments.Length - i); + var captured = requestSegment.GetRemainingPath(); if (captured.Length > 0) { values.Add(part.Name, captured); @@ -99,7 +95,7 @@ namespace Microsoft.AspNet.Routing.Template { if (requestSegment.Length > 0) { - values.Add(part.Name, requestSegment); + values.Add(part.Name, requestSegment.ToString()); } else { @@ -124,14 +120,14 @@ namespace Microsoft.AspNet.Routing.Template } else { - if (!MatchComplexSegment(routeSegment, requestSegment, Defaults, values)) + if (!MatchComplexSegment(routeSegment, requestSegment.ToString(), Defaults, values)) { return null; } } } - for (var i = requestSegments.Length; i < Template.Segments.Count; i++) + for (var i = requestSegments.Count; i < Template.Segments.Count; i++) { // We've matched the request path so far, but still have remaining route segments. These need // to be all single-part parameter segments with default values or else they won't match. @@ -179,10 +175,11 @@ namespace Microsoft.AspNet.Routing.Template return values; } - private bool MatchComplexSegment(TemplateSegment routeSegment, - string requestSegment, - IReadOnlyDictionary defaults, - RouteValueDictionary values) + private bool MatchComplexSegment( + TemplateSegment routeSegment, + string requestSegment, + IReadOnlyDictionary defaults, + RouteValueDictionary values) { var indexOfLastSegment = routeSegment.Parts.Count - 1; @@ -225,11 +222,12 @@ namespace Microsoft.AspNet.Routing.Template } } - private bool MatchComplexSegmentCore(TemplateSegment routeSegment, - string requestSegment, - IReadOnlyDictionary defaults, - RouteValueDictionary values, - int indexOfLastSegmentUsed) + private bool MatchComplexSegmentCore( + TemplateSegment routeSegment, + string requestSegment, + IReadOnlyDictionary defaults, + RouteValueDictionary values, + int indexOfLastSegmentUsed) { Debug.Assert(routeSegment != null); Debug.Assert(routeSegment.Parts.Count > 1); @@ -269,9 +267,10 @@ namespace Microsoft.AspNet.Routing.Template return false; } - var indexOfLiteral = requestSegment.LastIndexOf(part.Text, - startIndex, - StringComparison.OrdinalIgnoreCase); + var indexOfLiteral = requestSegment.LastIndexOf( + part.Text, + startIndex, + StringComparison.OrdinalIgnoreCase); if (indexOfLiteral == -1) { // If we couldn't find this literal index, this segment cannot match diff --git a/src/Microsoft.AspNet.Routing/Template/TemplateRoute.cs b/src/Microsoft.AspNet.Routing/Template/TemplateRoute.cs index 6347d260cb..48acbe6d7d 100644 --- a/src/Microsoft.AspNet.Routing/Template/TemplateRoute.cs +++ b/src/Microsoft.AspNet.Routing/Template/TemplateRoute.cs @@ -115,13 +115,7 @@ namespace Microsoft.AspNet.Routing.Template EnsureLoggers(context.HttpContext); - var requestPath = context.HttpContext.Request.Path.Value; - - if (!string.IsNullOrEmpty(requestPath) && requestPath[0] == '/') - { - requestPath = requestPath.Substring(1); - } - + var requestPath = context.HttpContext.Request.Path; var values = _matcher.Match(requestPath); if (values == null) diff --git a/test/Microsoft.AspNet.Routing.Tests/Internal/PathTokenizerTest.cs b/test/Microsoft.AspNet.Routing.Tests/Internal/PathTokenizerTest.cs new file mode 100644 index 0000000000..a91775d0d0 --- /dev/null +++ b/test/Microsoft.AspNet.Routing.Tests/Internal/PathTokenizerTest.cs @@ -0,0 +1,116 @@ +// 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.AspNet.Http; +using Xunit; + +namespace Microsoft.AspNet.Routing.Internal +{ + public class PathTokenizerTest + { + public static TheoryData TokenizationData + { + get + { + return new TheoryData + { + { string.Empty, new PathSegment[] { } }, + { "/", new PathSegment[] { } }, + { "//", new PathSegment[] { new PathSegment("//", 1, 0) } }, + { + "///", + new PathSegment[] + { + new PathSegment("///", 1, 0), + new PathSegment("///", 2, 0), + } + }, + { + "////", + new PathSegment[] + { + new PathSegment("////", 1, 0), + new PathSegment("////", 2, 0), + new PathSegment("////", 3, 0), + } + }, + { "/zero", new PathSegment[] { new PathSegment("/zero", 1, 4) } }, + { "/zero/", new PathSegment[] { new PathSegment("/zero/", 1, 4) } }, + { + "/zero/one", + new PathSegment[] + { + new PathSegment("/zero/one", 1, 4), + new PathSegment("/zero/one", 6, 3), + } + }, + { + "/zero/one/", + new PathSegment[] + { + new PathSegment("/zero/one/", 1, 4), + new PathSegment("/zero/one/", 6, 3), + } + }, + { + "/zero/one/two", + new PathSegment[] + { + new PathSegment("/zero/one/two", 1, 4), + new PathSegment("/zero/one/two", 6, 3), + new PathSegment("/zero/one/two", 10, 3), + } + }, + { + "/zero/one/two/", + new PathSegment[] + { + new PathSegment("/zero/one/two/", 1, 4), + new PathSegment("/zero/one/two/", 6, 3), + new PathSegment("/zero/one/two/", 10, 3), + } + }, + }; + } + } + + [Theory] + [MemberData(nameof(TokenizationData))] + public void PathTokenizer_Count(string path, PathSegment[] expectedSegments) + { + // Arrange + var tokenizer = new PathTokenizer(new PathString(path)); + + // Act + var count = tokenizer.Count; + + // Assert + Assert.Equal(expectedSegments.Length, count); + } + + [Theory] + [MemberData(nameof(TokenizationData))] + public void PathTokenizer_Indexer(string path, PathSegment[] expectedSegments) + { + // Arrange + var tokenizer = new PathTokenizer(new PathString(path)); + + // Act & Assert + for (var i = 0; i < expectedSegments.Length; i++) + { + Assert.Equal(expectedSegments[i], tokenizer[i]); + } + } + + [Theory] + [MemberData(nameof(TokenizationData))] + public void PathTokenizer_Enumerator(string path, PathSegment[] expectedSegments) + { + // Arrange + var tokenizer = new PathTokenizer(new PathString(path)); + + // Act & Assert + Assert.Equal(expectedSegments, tokenizer); + } + } +} diff --git a/test/Microsoft.AspNet.Routing.Tests/Template/TemplateMatcherTests.cs b/test/Microsoft.AspNet.Routing.Tests/Template/TemplateMatcherTests.cs index a248f6c853..9463bc21f1 100644 --- a/test/Microsoft.AspNet.Routing.Tests/Template/TemplateMatcherTests.cs +++ b/test/Microsoft.AspNet.Routing.Tests/Template/TemplateMatcherTests.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; +using Microsoft.AspNet.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.OptionsModel; using Xunit; @@ -19,7 +20,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests var matcher = CreateMatcher("{controller}/{action}/{id}"); // Act - var match = matcher.Match("Bank/DoAction/123"); + var match = matcher.Match("/Bank/DoAction/123"); // Assert Assert.NotNull(match); @@ -35,7 +36,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests var matcher = CreateMatcher("{controller}/{action}/{id}"); // Act - var match = matcher.Match("Bank/DoAction"); + var match = matcher.Match("/Bank/DoAction"); // Assert Assert.Null(match); @@ -48,7 +49,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests var matcher = CreateMatcher("{controller}/{action}/{id}", new { id = "default id" }); // Act - var rd = matcher.Match("Bank/DoAction"); + var rd = matcher.Match("/Bank/DoAction"); // Assert Assert.Equal("Bank", rd["controller"]); @@ -63,7 +64,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests var matcher = CreateMatcher("{controller}/{action}/{id}", new { id = "default id" }); // Act - var rd = matcher.Match("Bank"); + var rd = matcher.Match("/Bank"); // Assert Assert.Null(rd); @@ -76,7 +77,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests var matcher = CreateMatcher("moo/{p1}/bar/{p2}", new { p2 = "default p2" }); // Act - var rd = matcher.Match("moo/111/bar/222"); + var rd = matcher.Match("/moo/111/bar/222"); // Assert Assert.Equal("111", rd["p1"]); @@ -90,7 +91,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests var matcher = CreateMatcher("moo/{p1}/bar/{p2}", new { p2 = "default p2" }); // Act - var rd = matcher.Match("moo/111/bar/"); + var rd = matcher.Match("/moo/111/bar/"); // Assert Assert.Equal("111", rd["p1"]); @@ -98,10 +99,10 @@ namespace Microsoft.AspNet.Routing.Template.Tests } [Theory] - [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 { + [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) @@ -117,19 +118,19 @@ namespace Microsoft.AspNet.Routing.Template.Tests } [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)] - [InlineData("moo/{p1}.{p2?}", "moo/foo..bar", "foo.", "bar")] - [InlineData("moo/{p1}.{p2?}", "moo/foo.moo.bar", "foo.moo", "bar")] - [InlineData("moo/{p1}.{p2}", "moo/foo.bar", "foo", "bar")] - [InlineData("moo/foo.{p1}.{p2?}", "moo/foo.moo.bar", "moo", "bar")] - [InlineData("moo/foo.{p1}.{p2?}", "moo/foo.moo", "moo", null)] - [InlineData("moo/.{p2?}", "moo/.foo", null, "foo")] - [InlineData("moo/.{p2?}", "moo", null, null)] - [InlineData("moo/{p1}.{p2?}", "moo/....", "..", ".")] - [InlineData("moo/{p1}.{p2?}", "moo/.bar", ".bar", null)] + [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)] + [InlineData("moo/{p1}.{p2?}", "/moo/foo..bar", "foo.", "bar")] + [InlineData("moo/{p1}.{p2?}", "/moo/foo.moo.bar", "foo.moo", "bar")] + [InlineData("moo/{p1}.{p2}", "/moo/foo.bar", "foo", "bar")] + [InlineData("moo/foo.{p1}.{p2?}", "/moo/foo.moo.bar", "moo", "bar")] + [InlineData("moo/foo.{p1}.{p2?}", "/moo/foo.moo", "moo", null)] + [InlineData("moo/.{p2?}", "/moo/.foo", null, "foo")] + [InlineData("moo/.{p2?}", "/moo", null, null)] + [InlineData("moo/{p1}.{p2?}", "/moo/....", "..", ".")] + [InlineData("moo/{p1}.{p2?}", "/moo/.bar", ".bar", null)] public void MatchRoute_OptionalParameter_FollowedByPeriod_Valid( string template, string path, @@ -154,13 +155,13 @@ namespace Microsoft.AspNet.Routing.Template.Tests } [Theory] - [InlineData("moo/{p1}.{p2}.{p3?}", "moo/foo.moo.bar", "foo", "moo", "bar")] - [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/baz", "foo", "bar", "baz")] + [InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo.moo.bar", "foo", "moo", "bar")] + [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/baz", "foo", "bar", "baz")] public void MatchRoute_OptionalParameter_FollowedByPeriod_3Parameters_Valid( string template, string path, @@ -189,18 +190,18 @@ namespace Microsoft.AspNet.Routing.Template.Tests } [Theory] - [InlineData("moo/{p1}.{p2?}", "moo/foo.")] - [InlineData("moo/{p1}.{p2?}", "moo/.")] - [InlineData("moo/{p1}.{p2}", "foo.")] - [InlineData("moo/{p1}.{p2}", "foo")] - [InlineData("moo/{p1}.{p2}.{p3?}", "moo/foo.moo.")] - [InlineData("moo/foo.{p2}.{p3?}", "moo/bar.foo.moo")] - [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("moo/.{p2?}", "moo/.")] - [InlineData("{p1}.{p2}/{p3}", ".foo/bar")] + [InlineData("moo/{p1}.{p2?}", "/moo/foo.")] + [InlineData("moo/{p1}.{p2?}", "/moo/.")] + [InlineData("moo/{p1}.{p2}", "/foo.")] + [InlineData("moo/{p1}.{p2}", "/foo")] + [InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo.moo.")] + [InlineData("moo/foo.{p2}.{p3?}", "/moo/bar.foo.moo")] + [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("moo/.{p2?}", "/moo/.")] + [InlineData("{p1}.{p2}/{p3}", "/.foo/bar")] public void MatchRoute_OptionalParameter_FollowedByPeriod_Invalid(string template, string path) { // Arrange @@ -220,7 +221,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests var matcher = CreateMatcher("moo/bar"); // Act - var rd = matcher.Match("moo/bar"); + var rd = matcher.Match("/moo/bar"); // Assert Assert.NotNull(rd); @@ -234,7 +235,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests var matcher = CreateMatcher("moo/bars"); // Act - var rd = matcher.Match("moo/bar"); + var rd = matcher.Match("/moo/bar"); // Assert Assert.Null(rd); @@ -247,7 +248,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests var matcher = CreateMatcher("moo/bar"); // Act - var rd = matcher.Match("moo/bar/"); + var rd = matcher.Match("/moo/bar/"); // Assert Assert.NotNull(rd); @@ -261,7 +262,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests var matcher = CreateMatcher("moo/bar/"); // Act - var rd = matcher.Match("moo/bar"); + var rd = matcher.Match("/moo/bar"); // Assert Assert.NotNull(rd); @@ -275,7 +276,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests var matcher = CreateMatcher("{p1}/{p2}/"); // Act - var rd = matcher.Match("moo/bar"); + var rd = matcher.Match("/moo/bar"); // Assert Assert.NotNull(rd); @@ -290,7 +291,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests var matcher = CreateMatcher("{p1}/{p2}/baz"); // Act - var rd = matcher.Match("moo/bar/boo"); + var rd = matcher.Match("/moo/bar/boo"); // Assert Assert.Null(rd); @@ -303,7 +304,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests var matcher = CreateMatcher("{p1}"); // Act - var rd = matcher.Match("moo/bar"); + var rd = matcher.Match("/moo/bar"); // Assert Assert.Null(rd); @@ -316,21 +317,21 @@ namespace Microsoft.AspNet.Routing.Template.Tests var matcher = CreateMatcher("DEFAULT.ASPX"); // Act - var rd = matcher.Match("default.aspx"); + var rd = matcher.Match("/default.aspx"); // Assert Assert.NotNull(rd); } [Theory] - [InlineData("{prefix}x{suffix}", "xxxxxxxxxx")] - [InlineData("{prefix}xyz{suffix}", "xxxxyzxyzxxxxxxyz")] - [InlineData("{prefix}xyz{suffix}", "abcxxxxyzxyzxxxxxxyzxx")] - [InlineData("{prefix}xyz{suffix}", "xyzxyzxyzxyzxyz")] - [InlineData("{prefix}xyz{suffix}", "xyzxyzxyzxyzxyz1")] - [InlineData("{prefix}xyz{suffix}", "xyzxyzxyz")] - [InlineData("{prefix}aa{suffix}", "aaaaa")] - [InlineData("{prefix}aaa{suffix}", "aaaaa")] + [InlineData("{prefix}x{suffix}", "/xxxxxxxxxx")] + [InlineData("{prefix}xyz{suffix}", "/xxxxyzxyzxxxxxxyz")] + [InlineData("{prefix}xyz{suffix}", "/abcxxxxyzxyzxxxxxxyzxx")] + [InlineData("{prefix}xyz{suffix}", "/xyzxyzxyzxyzxyz")] + [InlineData("{prefix}xyz{suffix}", "/xyzxyzxyzxyzxyz1")] + [InlineData("{prefix}xyz{suffix}", "/xyzxyzxyz")] + [InlineData("{prefix}aa{suffix}", "/aaaaa")] + [InlineData("{prefix}aaa{suffix}", "/aaaaa")] public void VerifyRouteMatchesWithContext(string template, string path) { var matcher = CreateMatcher(template); @@ -349,7 +350,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests var matcher = CreateMatcher("{p1}/{p2}", new { p2 = (string)null, foo = "bar" }); // Act - var rd = matcher.Match("v1"); + var rd = matcher.Match("/v1"); // Assert Assert.NotNull(rd); @@ -368,7 +369,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests new { controller = "blog", action = "showpost", m = (string)null, d = (string)null }); // Act - var rd = matcher.Match("date/2007/08"); + var rd = matcher.Match("/date/2007/08"); // Assert Assert.NotNull(rd); @@ -385,7 +386,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests { RunTest( "language/{lang}-{region}", - "language/en-US", + "/language/en-US", null, new RouteValueDictionary(new { lang = "en", region = "US" })); } @@ -395,7 +396,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests { RunTest( "language/{lang}-{region}a", - "language/en-USa", + "/language/en-USa", null, new RouteValueDictionary(new { lang = "en", region = "US" })); } @@ -405,7 +406,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests { RunTest( "language/a{lang}-{region}", - "language/aen-US", + "/language/aen-US", null, new RouteValueDictionary(new { lang = "en", region = "US" })); } @@ -415,7 +416,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests { RunTest( "language/a{lang}-{region}a", - "language/aen-USa", + "/language/aen-USa", null, new RouteValueDictionary(new { lang = "en", region = "US" })); } @@ -425,7 +426,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests { RunTest( "language/a{lang}-{region}a", - "language/a-USa", + "/language/a-USa", null, null); } @@ -435,7 +436,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests { RunTest( "language/a{lang}-{region}a", - "language/aen-a", + "/language/aen-a", null, null); } @@ -445,7 +446,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests { RunTest( "language/{lang}", - "language/en", + "/language/en", null, new RouteValueDictionary(new { lang = "en" })); } @@ -455,7 +456,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests { RunTest( "language/{lang}", - "language/", + "/language/", null, null); } @@ -465,7 +466,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests { RunTest( "language/{lang}", - "language", + "/language", null, null); } @@ -475,7 +476,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests { RunTest( "language/{lang}-", - "language/en-", + "/language/en-", null, new RouteValueDictionary(new { lang = "en" })); } @@ -485,7 +486,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests { RunTest( "language/a{lang}", - "language/aen", + "/language/aen", null, new RouteValueDictionary(new { lang = "en" })); } @@ -495,7 +496,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests { RunTest( "language/a{lang}a", - "language/aena", + "/language/aena", null, new RouteValueDictionary(new { lang = "en" })); } @@ -505,7 +506,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests { RunTest( "{controller}.mvc/{action}/{id}", - "home.mvc/index", + "/home.mvc/index", new RouteValueDictionary(new { action = "Index", id = (string)null }), new RouteValueDictionary(new { controller = "home", action = "index", id = (string)null })); } @@ -515,7 +516,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests { RunTest( "language/{lang}-{region}", - "language/-", + "/language/-", new RouteValueDictionary(new { lang = "xx", region = "yy" }), null); } @@ -525,7 +526,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests { RunTest( "{Controller}..mvc/{id}/{Param1}", - "Home..mvc/123/p1", + "/Home..mvc/123/p1", null, new RouteValueDictionary(new { Controller = "Home", id = "123", Param1 = "p1" })); } @@ -535,7 +536,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests { RunTest( "{Controller}.mvc/../{action}", - "Home.mvc/../index", + "/Home.mvc/../index", null, new RouteValueDictionary(new { Controller = "Home", action = "index" })); } @@ -545,7 +546,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests { RunTest( "{Controller}.mvc/.../{action}", - "Home.mvc/.../index", + "/Home.mvc/.../index", null, new RouteValueDictionary(new { Controller = "Home", action = "index" })); } @@ -555,7 +556,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests { RunTest( "{Controller}.mvc/../../../{action}", - "Home.mvc/../../../index", + "/Home.mvc/../../../index", null, new RouteValueDictionary(new { Controller = "Home", action = "index" })); } @@ -565,7 +566,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests { RunTest( "{Controller}.mvc!/{action}", - "Home.mvc!/index", + "/Home.mvc!/index", null, new RouteValueDictionary(new { Controller = "Home", action = "index" })); } @@ -575,7 +576,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests { RunTest( "../{Controller}.mvc", - "../Home.mvc", + "/../Home.mvc", null, new RouteValueDictionary(new { Controller = "Home" })); } @@ -585,7 +586,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests { RunTest( @"\{Controller}.mvc", - @"\Home.mvc", + @"/\Home.mvc", null, new RouteValueDictionary(new { Controller = "Home" })); } @@ -595,7 +596,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests { RunTest( @"{Controller}.mvc\{id}\{Param1}", - @"Home.mvc\123\p1", + @"/Home.mvc\123\p1", null, new RouteValueDictionary(new { Controller = "Home", id = "123", Param1 = "p1" })); } @@ -605,7 +606,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests { RunTest( @"(Controller).mvc", - @"(Controller).mvc", + @"/(Controller).mvc", null, new RouteValueDictionary()); } @@ -615,7 +616,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests { RunTest( @"Controller.mvc/ ", - @"Controller.mvc/ ", + @"/Controller.mvc/ ", null, new RouteValueDictionary()); } @@ -625,7 +626,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests { RunTest( @"Controller.mvc ", - @"Controller.mvc ", + @"/Controller.mvc ", null, new RouteValueDictionary()); } @@ -636,7 +637,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests // DevDiv Bugs 189892: UrlRouting: Catch all parameter cannot capture url segments that contain the "." RunTest( "Home/ShowPilot/{missionId}/{*name}", - "Home/ShowPilot/777/12345./foobar", + "/Home/ShowPilot/777/12345./foobar", new RouteValueDictionary(new { controller = "Home", @@ -654,7 +655,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests var matcher = CreateMatcher("{p1}/{*p2}"); // Act - var rd = matcher.Match("v1/v2/v3"); + var rd = matcher.Match("/v1/v2/v3"); // Assert Assert.NotNull(rd); @@ -670,7 +671,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests var matcher = CreateMatcher("{p1}/{*p2}"); // Act - var rd = matcher.Match("v1/"); + var rd = matcher.Match("/v1/"); // Assert Assert.NotNull(rd); @@ -686,7 +687,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests var matcher = CreateMatcher("{p1}/{*p2}"); // Act - var rd = matcher.Match("v1"); + var rd = matcher.Match("/v1"); // Assert Assert.NotNull(rd); @@ -702,7 +703,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests var matcher = CreateMatcher("{p1}/{*p2}", new { p2 = "catchall" }); // Act - var rd = matcher.Match("v1"); + var rd = matcher.Match("/v1"); // Assert Assert.NotNull(rd); @@ -718,7 +719,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests var matcher = CreateMatcher("{p1}/{*p2}", new { p2 = "catchall" }); // Act - var rd = matcher.Match("v1/hello/whatever"); + var rd = matcher.Match("/v1/hello/whatever"); // Assert Assert.NotNull(rd); @@ -733,7 +734,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests // DevDiv Bugs 191180: UrlRouting: Wrong template getting matched if a url segment is a substring of the requested url RunTest( "foo", - "fooBAR", + "/fooBAR", null, null); } @@ -744,7 +745,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests // DevDiv Bugs 191180: UrlRouting: Wrong template getting matched if a url segment is a substring of the requested url RunTest( "foo", - "BARfoo", + "/BARfoo", null, null); } @@ -755,7 +756,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests // DevDiv Bugs 191180: UrlRouting: Wrong template getting matched if a url segment is a substring of the requested url RunTest( "foo", - "BARfooBAR", + "/BARfooBAR", null, null); } @@ -766,7 +767,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests // DevDiv Bugs 191180: UrlRouting: Wrong template getting matched if a url segment is a substring of the requested url RunTest( "foo", - "foo", + "/foo", null, new RouteValueDictionary()); } @@ -776,7 +777,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests { RunTest( "foo/{ }/{.!$%}/{dynamic.data}/{op.tional}", - "foo/space/weird/orderid", + "/foo/space/weird/orderid", new RouteValueDictionary() { { " ", "not a space" }, { "op.tional", "default value" }, { "ran!dom", "va@lue" } }, new RouteValueDictionary() { { " ", "space" }, { ".!$%", "weird" }, { "dynamic.data", "orderid" }, { "op.tional", "default value" }, { "ran!dom", "va@lue" } }); } @@ -786,7 +787,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests { RunTest( "{controller}/{language}-{locale}", - "foo", + "/foo", new RouteValueDictionary(new { language = "en", locale = "US" }), null); } @@ -796,7 +797,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests { RunTest( "{controller}/{language}-{locale}", - "foo/xx-", + "/foo/xx-", new RouteValueDictionary(new { language = "en", locale = "US" }), null); } @@ -806,7 +807,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests { RunTest( "{controller}/{language}-{locale}", - "foo/-yy", + "/foo/-yy", new RouteValueDictionary(new { language = "en", locale = "US" }), null); } @@ -816,7 +817,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests { RunTest( "{controller}/{language}-{locale}", - "foo/xx-yy", + "/foo/xx-yy", new RouteValueDictionary(new { language = "en", locale = "US" }), new RouteValueDictionary { { "language", "xx" }, { "locale", "yy" }, { "controller", "foo" } }); } @@ -826,7 +827,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests { // Arrange var route = CreateMatcher("{controller}/{action?}"); - var url = "Home/Index"; + var url = "/Home/Index"; // Act var match = route.Match(url); @@ -843,7 +844,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests { // Arrange var route = CreateMatcher("{controller}/{action?}"); - var url = "Home"; + var url = "/Home"; // Act var match = route.Match(url); @@ -891,7 +892,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests { // Arrange var route = CreateMatcher("{controller}/{action?}/{id?}"); - var url = "Home/Index"; + var url = "/Home/Index"; // Act var match = route.Match(url); @@ -923,7 +924,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests defaults ?? new Dictionary()); // Act - var match = matcher.Match(path); + var match = matcher.Match(new PathString(path)); // Assert if (expected == null)