diff --git a/src/Microsoft.AspNetCore.Http.Abstractions/Internal/PathStringHelper.cs b/src/Microsoft.AspNetCore.Http.Abstractions/Internal/PathStringHelper.cs index 45b44ab1..b6cebb2b 100644 --- a/src/Microsoft.AspNetCore.Http.Abstractions/Internal/PathStringHelper.cs +++ b/src/Microsoft.AspNetCore.Http.Abstractions/Internal/PathStringHelper.cs @@ -28,5 +28,20 @@ public static bool IsValidPathChar(char c) { return c < ValidPathChars.Length && ValidPathChars[c]; } + + public static bool IsPercentEncodedChar(string str, int index) + { + return index < str.Length - 2 + && str[index] == '%' + && IsHexadecimalChar(str[index + 1]) + && IsHexadecimalChar(str[index + 2]); + } + + public static bool IsHexadecimalChar(char c) + { + return ('0' <= c && c <= '9') + || ('A' <= c && c <= 'F') + || ('a' <= c && c <= 'f'); + } } } diff --git a/src/Microsoft.AspNetCore.Http.Abstractions/PathString.cs b/src/Microsoft.AspNetCore.Http.Abstractions/PathString.cs index a211d273..50124e5e 100644 --- a/src/Microsoft.AspNetCore.Http.Abstractions/PathString.cs +++ b/src/Microsoft.AspNetCore.Http.Abstractions/PathString.cs @@ -53,7 +53,7 @@ public bool HasValue } /// - /// Provides the path string escaped in a way which is correct for combining into the URI representation. + /// Provides the path string escaped in a way which is correct for combining into the URI representation. /// /// The escaped path value public override string ToString() @@ -77,9 +77,12 @@ public string ToUriComponent() var start = 0; var count = 0; var requiresEscaping = false; - for (int i = 0; i < _value.Length; ++i) + var i = 0; + + while (i < _value.Length) { - if (PathStringHelper.IsValidPathChar(_value[i])) + var isPercentEncodedChar = PathStringHelper.IsPercentEncodedChar(_value, i); + if (PathStringHelper.IsValidPathChar(_value[i]) || isPercentEncodedChar) { if (requiresEscaping) { @@ -96,7 +99,16 @@ public string ToUriComponent() count = 0; } - count++; + if (isPercentEncodedChar) + { + count += 3; + i += 3; + } + else + { + count++; + i++; + } } else { @@ -116,6 +128,7 @@ public string ToUriComponent() } count++; + i++; } } @@ -146,6 +159,7 @@ public string ToUriComponent() } } + /// /// Returns an PathString given the path as it is escaped in the URI format. The string MUST NOT contain any /// value that is not a path. @@ -279,7 +293,7 @@ public bool StartsWithSegments(PathString other, StringComparison comparisonType } /// - /// Adds two PathString instances into a combined PathString value. + /// Adds two PathString instances into a combined PathString value. /// /// The combined PathString value public PathString Add(PathString other) @@ -297,7 +311,7 @@ public PathString Add(PathString other) } /// - /// Combines a PathString and QueryString into the joined URI formatted string value. + /// Combines a PathString and QueryString into the joined URI formatted string value. /// /// The joined URI formatted string value public string Add(QueryString other) diff --git a/test/Microsoft.AspNetCore.Http.Abstractions.Tests/PathStringTests.cs b/test/Microsoft.AspNetCore.Http.Abstractions.Tests/PathStringTests.cs index b9019445..f5cbe052 100644 --- a/test/Microsoft.AspNetCore.Http.Abstractions.Tests/PathStringTests.cs +++ b/test/Microsoft.AspNetCore.Http.Abstractions.Tests/PathStringTests.cs @@ -195,6 +195,10 @@ public void StartsWithSegmentsWithRemainder_DoesMatchUsingSpecifiedComparison(st [InlineData("pct-encoding", "/单行道", "/%E5%8D%95%E8%A1%8C%E9%81%93")] [InlineData("mixed1", "/index/单行道=(x*y)[abc]", "/index/%E5%8D%95%E8%A1%8C%E9%81%93=(x*y)%5Babc%5D")] [InlineData("mixed2", "/index/单行道=(x*y)[abc]_", "/index/%E5%8D%95%E8%A1%8C%E9%81%93=(x*y)%5Babc%5D_")] + [InlineData("encoded", "/http%3a%2f%2f[foo]%3A5000/", "/http%3a%2f%2f%5Bfoo%5D%3A5000/")] + [InlineData("encoded", "/http%3a%2f%2f[foo]%3A5000/%", "/http%3a%2f%2f%5Bfoo%5D%3A5000/%25")] + [InlineData("encoded", "/http%3a%2f%2f[foo]%3A5000/%2", "/http%3a%2f%2f%5Bfoo%5D%3A5000/%252")] + [InlineData("encoded", "/http%3a%2f%2f[foo]%3A5000/%2F", "/http%3a%2f%2f%5Bfoo%5D%3A5000/%2F")] public void ToUriComponentEscapeCorrectly(string category, string input, string expected) { var path = new PathString(input);