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);