diff --git a/src/libraries/System.Private.Uri/src/System/UriBuilder.cs b/src/libraries/System.Private.Uri/src/System/UriBuilder.cs
index e890e9aa45e998..91c0e59002fcb8 100644
--- a/src/libraries/System.Private.Uri/src/System/UriBuilder.cs
+++ b/src/libraries/System.Private.Uri/src/System/UriBuilder.cs
@@ -158,16 +158,40 @@ 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(@":/\?#@[]");
+ private static readonly SearchValues s_hostReservedCharsExceptColon = 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('[') || !value.EndsWith(']'))
+ {
+ // probable ipv6 address - Note: this is only supported for cases where the authority is inet-based.
+ value = $"[{value}]";
+ }
+
+ if (value.AsSpan(1, value.Length - 2).ContainsAny(s_hostReservedCharsExceptColon))
+ {
+ // 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".
+ // 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 +389,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..13ba7768956f4b 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