diff --git a/src/libraries/System.Private.Uri/src/System/UriBuilder.cs b/src/libraries/System.Private.Uri/src/System/UriBuilder.cs index 29611f4f5f9db1..74cbfcc0847c1e 100644 --- a/src/libraries/System.Private.Uri/src/System/UriBuilder.cs +++ b/src/libraries/System.Private.Uri/src/System/UriBuilder.cs @@ -255,6 +255,24 @@ public Uri Uri public override int GetHashCode() => Uri.GetHashCode(); + private static string EncodeUserInfo(string input) + { + // The following characters ("/" / "\" / "?" / "#" / "@") are from the gen-delims group. + // We have to escape them to avoid corrupting the rest of the Uri string. + // Other characters like spaces or non-ASCII will be escaped by Uri, we can ignore them here. + if (input.AsSpan().IndexOfAny(@"/\?#@") < 0) + { + return input; + } + + return input + .Replace("/", "%2F", StringComparison.Ordinal) + .Replace(@"\", "%5C", StringComparison.Ordinal) + .Replace("?", "%3F", StringComparison.Ordinal) + .Replace("#", "%23", StringComparison.Ordinal) + .Replace("@", "%40", StringComparison.Ordinal); + } + private void SetFieldsFromUri() { Debug.Assert(_uri is not null); @@ -315,12 +333,12 @@ public override string ToString() vsb.Append(schemeDelimiter); } - string username = UserName; + string username = EncodeUserInfo(UserName); if (username.Length != 0) { vsb.Append(username); - string password = Password; + string password = EncodeUserInfo(Password); if (password.Length != 0) { vsb.Append(':'); diff --git a/src/libraries/System.Private.Uri/tests/FunctionalTests/UriBuilderTests.cs b/src/libraries/System.Private.Uri/tests/FunctionalTests/UriBuilderTests.cs index 570894a66a1ed5..1bf1f0a9abb93e 100644 --- a/src/libraries/System.Private.Uri/tests/FunctionalTests/UriBuilderTests.cs +++ b/src/libraries/System.Private.Uri/tests/FunctionalTests/UriBuilderTests.cs @@ -385,6 +385,22 @@ public void ToString_Invalid() Assert.Throws(() => uriBuilder.ToString()); // Uri has a password but no username } + [Theory] + [InlineData(@"user/\?#@name", "", "http://user%2F%5C%3F%23%40name@localhost/")] + [InlineData(@"user/\?#@name", @"pass/\?#@word", "http://user%2F%5C%3F%23%40name:pass%2F%5C%3F%23%40word@localhost/")] + public void ToString_EncodingUserInfo(string username, string password, string expectedToString) + { + var uriBuilder = new UriBuilder + { + UserName = username, + Password = password + }; + + Assert.Equal(expectedToString, uriBuilder.ToString()); + Assert.Equal(username, uriBuilder.UserName); + Assert.Equal(password, uriBuilder.Password); + } + private static void VerifyUriBuilder(UriBuilder uriBuilder, string scheme, string userName, string password, string host, int port, string path, string query, string fragment) { Assert.Equal(scheme, uriBuilder.Scheme);