diff --git a/src/libraries/System.Private.Uri/src/System/UriBuilder.cs b/src/libraries/System.Private.Uri/src/System/UriBuilder.cs
index e890e9aa45e998..35575e022e1d8c 100644
--- a/src/libraries/System.Private.Uri/src/System/UriBuilder.cs
+++ b/src/libraries/System.Private.Uri/src/System/UriBuilder.cs
@@ -158,16 +158,41 @@ public string Password
}
}
+ ///
+ /// Problematic characters that could result in the Host component escaping into other components like the Path.
+ private static readonly SearchValues s_hostReservedChars =
+ SearchValues.Create(@":/\?#@");
+
[AllowNull]
public string Host
{
get => _host;
set
{
- if (!string.IsNullOrEmpty(value) && value.Contains(':') && value[0] != '[')
+ if (!string.IsNullOrEmpty(value) && value.AsSpan().ContainsAny(s_hostReservedChars))
{
- //probable ipv6 address - Note: this is only supported for cases where the authority is inet-based.
- value = "[" + value + "]";
+ if (value.Contains(':'))
+ {
+ if (!value.StartsWith('['))
+ {
+ // probable ipv6 address - Note: this is only supported for cases where the authority is inet-based.
+ value = "[" + value + "]";
+ }
+
+ if (value.AsSpan(0, value.Length - 1).Contains(']'))
+ {
+ // Reject inputs like "[::]/path" or "::]/path".
+ throw new ArgumentException(SR.net_uri_BadHostName, nameof(value));
+ }
+ }
+ else
+ {
+ // Reject inputs like "contoso.com/path" or "user@contoso.com".
+ // We don't take this branch if the input is an IPv6 address because those can contain '/' characters.
+ // If the input is an IPv6 address with invalid characters, Uri parsing will catch it later.
+ // Nonsensical inputs will only be allowed by a custom parser with GenericUriParserOptions.GenericAuthority set.
+ throw new ArgumentException(SR.net_uri_BadHostName, nameof(value));
+ }
}
_host = value ?? string.Empty;
@@ -365,7 +390,7 @@ public override string ToString()
}
}
- var path = Path;
+ string path = Path;
if (path.Length != 0)
{
if (!path.StartsWith('/') && host.Length != 0)
diff --git a/src/libraries/System.Private.Uri/tests/FunctionalTests/UriBuilderTests.cs b/src/libraries/System.Private.Uri/tests/FunctionalTests/UriBuilderTests.cs
index 5e35c4c1e146c7..4ff6d51f2bf896 100644
--- a/src/libraries/System.Private.Uri/tests/FunctionalTests/UriBuilderTests.cs
+++ b/src/libraries/System.Private.Uri/tests/FunctionalTests/UriBuilderTests.cs
@@ -88,7 +88,6 @@ public void Ctor_Uri_Null()
[InlineData("http", "host", "http", "host")]
[InlineData("HTTP", "host", "http", "host")]
[InlineData("http", "[::1]", "http", "[::1]")]
- [InlineData("https", "::1]", "https", "[::1]]")]
[InlineData("http", "::1", "http", "[::1]")]
[InlineData("http1:http2", "host", "http1", "host")]
[InlineData("http", "", "http", "")]
@@ -107,7 +106,6 @@ public void Ctor_String_String(string? schemeName, string? hostName, string expe
[InlineData("http", "host", 0, "http", "host")]
[InlineData("HTTP", "host", 20, "http", "host")]
[InlineData("http", "[::1]", 40, "http", "[::1]")]
- [InlineData("https", "::1]", 60, "https", "[::1]]")]
[InlineData("http", "::1", 80, "http", "[::1]")]
[InlineData("http1:http2", "host", 100, "http1", "host")]
[InlineData("http", "", 120, "http", "")]
@@ -126,7 +124,6 @@ public void Ctor_String_String_Int(string? scheme, string? host, int port, strin
[InlineData("http", "host", 0, "/path", "http", "host", "/path")]
[InlineData("HTTP", "host", 20, "/path1/path2", "http", "host", "/path1/path2")]
[InlineData("http", "[::1]", 40, "/", "http", "[::1]", "/")]
- [InlineData("https", "::1]", 60, "/path1/", "https", "[::1]]", "/path1/")]
[InlineData("http", "::1", 80, null, "http", "[::1]", "/")]
[InlineData("http1:http2", "host", 100, "path1", "http1", "host", "path1")]
[InlineData("http", "", 120, "path1/path2", "http", "", "path1/path2")]
@@ -145,7 +142,6 @@ public void Ctor_String_String_Int_String(string? schemeName, string? hostName,
[InlineData("http", "host", 0, "/path", "?query#fragment", "http", "host", "/path", "?query", "#fragment")]
[InlineData("HTTP", "host", 20, "/path1/path2", "?query&query2=value#fragment", "http", "host", "/path1/path2", "?query&query2=value", "#fragment")]
[InlineData("http", "[::1]", 40, "/", "#fragment?query", "http", "[::1]", "/", "", "#fragment?query")]
- [InlineData("https", "::1]", 60, "/path1/", "?query", "https", "[::1]]", "/path1/", "?query", "")]
[InlineData("http", "::1", 80, null, "#fragment", "http", "[::1]", "/", "", "#fragment")]
[InlineData("http", "", 120, "path1/path2", "?#", "http", "", "path1/path2", "", "")]
[InlineData("", "host", 140, "path1/path2/path3/", "?", "", "host", "path1/path2/path3/", "", "")]
@@ -368,6 +364,7 @@ public static IEnumerable