From 8ecb14733258afa8a8086ac8e53dc2cd0bfdf545 Mon Sep 17 00:00:00 2001 From: damianedwards Date: Fri, 2 Oct 2015 15:38:22 -0700 Subject: [PATCH] Added overload to PathString.StartsWithSegments to allow specifying StringComparison: - This allows us to have a fast-path (or just be more explicit) for the comparison by doing case-sensitive checks (which are cheaper) --- .../PathString.cs | 56 +++++++++++--- .../PathStringTests.cs | 77 +++++++++++++++++++ 2 files changed, 124 insertions(+), 9 deletions(-) diff --git a/src/Microsoft.AspNet.Http.Abstractions/PathString.cs b/src/Microsoft.AspNet.Http.Abstractions/PathString.cs index 4f1499650d..f9538e7444 100644 --- a/src/Microsoft.AspNet.Http.Abstractions/PathString.cs +++ b/src/Microsoft.AspNet.Http.Abstractions/PathString.cs @@ -26,7 +26,7 @@ namespace Microsoft.AspNet.Http /// The unescaped path to be assigned to the Value property. public PathString(string value) { - if (!String.IsNullOrEmpty(value) && value[0] != '/') + if (!string.IsNullOrEmpty(value) && value[0] != '/') { throw new ArgumentException(""/*Resources.Exception_PathMustStartWithSlash*/, nameof(value)); } @@ -46,7 +46,7 @@ namespace Microsoft.AspNet.Http /// public bool HasValue { - get { return !String.IsNullOrEmpty(_value); } + get { return !string.IsNullOrEmpty(_value); } } /// @@ -98,23 +98,61 @@ namespace Microsoft.AspNet.Http return new PathString("/" + uri.GetComponents(UriComponents.Path, UriFormat.Unescaped)); } + /// + /// Determines whether the beginning of this instance matches the specified . + /// + /// The to compare. + /// true if value matches the beginning of this string; otherwise, false. public bool StartsWithSegments(PathString other) { - string value1 = Value ?? String.Empty; - string value2 = other.Value ?? String.Empty; - if (value1.StartsWith(value2, StringComparison.OrdinalIgnoreCase)) + return StartsWithSegments(other, StringComparison.OrdinalIgnoreCase); + } + + /// + /// Determines whether the beginning of this instance matches the specified when compared + /// using the specified comparison option. + /// + /// The to compare. + /// One of the enumeration values that determines how this and value are compared. + /// true if value matches the beginning of this string; otherwise, false. + public bool StartsWithSegments(PathString other, StringComparison comparisonType) + { + var value1 = Value ?? string.Empty; + var value2 = other.Value ?? string.Empty; + if (value1.StartsWith(value2, comparisonType)) { return value1.Length == value2.Length || value1[value2.Length] == '/'; } return false; } + /// + /// Determines whether the beginning of this PathString instance matches the specified when compared + /// using the specified comparison option and returns the remaining segments. + /// + /// The to compare. + /// The remaining segments after the match. + /// true if value matches the beginning of this string; otherwise, false. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#", Justification = "Secondary information needed after boolean result obtained")] public bool StartsWithSegments(PathString other, out PathString remaining) { - string value1 = Value ?? String.Empty; - string value2 = other.Value ?? String.Empty; - if (value1.StartsWith(value2, StringComparison.OrdinalIgnoreCase)) + return StartsWithSegments(other, StringComparison.OrdinalIgnoreCase, out remaining); + } + + /// + /// Determines whether the beginning of this instance matches the specified and returns + /// the remaining segments. + /// + /// The to compare. + /// One of the enumeration values that determines how this and value are compared. + /// The remaining segments after the match. + /// true if value matches the beginning of this string; otherwise, false. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#", Justification = "Secondary information needed after boolean result obtained")] + public bool StartsWithSegments(PathString other, StringComparison comparisonType, out PathString remaining) + { + var value1 = Value ?? string.Empty; + var value2 = other.Value ?? string.Empty; + if (value1.StartsWith(value2, comparisonType)) { if (value1.Length == value2.Length || value1[value2.Length] == '/') { @@ -278,7 +316,7 @@ namespace Microsoft.AspNet.Http /// Implicitly calls ToString(). /// /// - public static implicit operator string(PathString path) + public static implicit operator string (PathString path) { return path.ToString(); } diff --git a/test/Microsoft.AspNet.Http.Abstractions.Tests/PathStringTests.cs b/test/Microsoft.AspNet.Http.Abstractions.Tests/PathStringTests.cs index 245ce693e2..48d403a9ee 100644 --- a/test/Microsoft.AspNet.Http.Abstractions.Tests/PathStringTests.cs +++ b/test/Microsoft.AspNet.Http.Abstractions.Tests/PathStringTests.cs @@ -1,6 +1,7 @@ // 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.AspNet.Testing; using Xunit; @@ -70,5 +71,81 @@ namespace Microsoft.AspNet.Http result = path + "text"; Assert.Equal("/pathtext", result); } + + [Theory] + [InlineData("/test/path", "/TEST", true)] + [InlineData("/test/path", "/TEST/pa", false)] + [InlineData("/TEST/PATH", "/test", true)] + [InlineData("/TEST/path", "/test/pa", false)] + [InlineData("/test/PATH/path/TEST", "/TEST/path/PATH", true)] + public void StartsWithSegments_DoesACaseInsensitiveMatch(string sourcePath, string testPath, bool expectedResult) + { + var source = new PathString(sourcePath); + var test = new PathString(testPath); + + var result = source.StartsWithSegments(test); + + Assert.Equal(expectedResult, result); + } + + [Theory] + [InlineData("/test/path", "/TEST", true)] + [InlineData("/test/path", "/TEST/pa", false)] + [InlineData("/TEST/PATH", "/test", true)] + [InlineData("/TEST/path", "/test/pa", false)] + [InlineData("/test/PATH/path/TEST", "/TEST/path/PATH", true)] + public void StartsWithSegmentsWithRemainder_DoesACaseInsensitiveMatch(string sourcePath, string testPath, bool expectedResult) + { + var source = new PathString(sourcePath); + var test = new PathString(testPath); + + PathString remaining; + var result = source.StartsWithSegments(test, out remaining); + + Assert.Equal(expectedResult, result); + } + + [Theory] + [InlineData("/test/path", "/TEST", StringComparison.OrdinalIgnoreCase, true)] + [InlineData("/test/path", "/TEST", StringComparison.Ordinal, false)] + [InlineData("/test/path", "/TEST/pa", StringComparison.OrdinalIgnoreCase, false)] + [InlineData("/test/path", "/TEST/pa", StringComparison.Ordinal, false)] + [InlineData("/TEST/PATH", "/test", StringComparison.OrdinalIgnoreCase, true)] + [InlineData("/TEST/PATH", "/test", StringComparison.Ordinal, false)] + [InlineData("/TEST/path", "/test/pa", StringComparison.OrdinalIgnoreCase, false)] + [InlineData("/TEST/path", "/test/pa", StringComparison.Ordinal, false)] + [InlineData("/test/PATH/path/TEST", "/TEST/path/PATH", StringComparison.OrdinalIgnoreCase, true)] + [InlineData("/test/PATH/path/TEST", "/TEST/path/PATH", StringComparison.Ordinal, false)] + public void StartsWithSegments_DoesMatchUsingSpecifiedComparison(string sourcePath, string testPath, StringComparison comparison, bool expectedResult) + { + var source = new PathString(sourcePath); + var test = new PathString(testPath); + + var result = source.StartsWithSegments(test, comparison); + + Assert.Equal(expectedResult, result); + } + + [Theory] + [InlineData("/test/path", "/TEST", StringComparison.OrdinalIgnoreCase, true)] + [InlineData("/test/path", "/TEST", StringComparison.Ordinal, false)] + [InlineData("/test/path", "/TEST/pa", StringComparison.OrdinalIgnoreCase, false)] + [InlineData("/test/path", "/TEST/pa", StringComparison.Ordinal, false)] + [InlineData("/TEST/PATH", "/test", StringComparison.OrdinalIgnoreCase, true)] + [InlineData("/TEST/PATH", "/test", StringComparison.Ordinal, false)] + [InlineData("/TEST/path", "/test/pa", StringComparison.OrdinalIgnoreCase, false)] + [InlineData("/TEST/path", "/test/pa", StringComparison.Ordinal, false)] + [InlineData("/test/PATH/path/TEST", "/TEST/path/PATH", StringComparison.OrdinalIgnoreCase, true)] + [InlineData("/test/PATH/path/TEST", "/TEST/path/PATH", StringComparison.Ordinal, false)] + public void StartsWithSegmentsWithRemainder_DoesMatchUsingSpecifiedComparison(string sourcePath, string testPath, StringComparison comparison, bool expectedResult) + { + var source = new PathString(sourcePath); + var test = new PathString(testPath); + + PathString remaining; + var result = source.StartsWithSegments(test, comparison, out remaining); + + Assert.Equal(expectedResult, result); + } } }