diff --git a/src/libraries/System.Web.HttpUtility/src/System.Web.HttpUtility.csproj b/src/libraries/System.Web.HttpUtility/src/System.Web.HttpUtility.csproj
index b7c44506200dd3..5da18b0fae6c82 100644
--- a/src/libraries/System.Web.HttpUtility/src/System.Web.HttpUtility.csproj
+++ b/src/libraries/System.Web.HttpUtility/src/System.Web.HttpUtility.csproj
@@ -15,6 +15,10 @@
+
+
diff --git a/src/libraries/System.Web.HttpUtility/src/System/Web/HttpUtility.cs b/src/libraries/System.Web.HttpUtility/src/System/Web/HttpUtility.cs
index 58dfcd39a9bdf0..270aedb275c946 100644
--- a/src/libraries/System.Web.HttpUtility/src/System/Web/HttpUtility.cs
+++ b/src/libraries/System.Web.HttpUtility/src/System/Web/HttpUtility.cs
@@ -237,12 +237,8 @@ public static NameValueCollection ParseQueryString(string query, Encoding encodi
[return: NotNullIfNotNull(nameof(bytes))]
public static byte[]? UrlDecodeToBytes(byte[]? bytes, int offset, int count) => HttpEncoder.UrlDecode(bytes, offset, count);
- public static string JavaScriptStringEncode(string? value) => HttpEncoder.JavaScriptStringEncode(value);
+ public static string JavaScriptStringEncode(string? value) => HttpEncoder.JavaScriptStringEncode(value, false);
- public static string JavaScriptStringEncode(string? value, bool addDoubleQuotes)
- {
- string encoded = HttpEncoder.JavaScriptStringEncode(value);
- return addDoubleQuotes ? "\"" + encoded + "\"" : encoded;
- }
+ public static string JavaScriptStringEncode(string? value, bool addDoubleQuotes) => HttpEncoder.JavaScriptStringEncode(value, addDoubleQuotes);
}
}
diff --git a/src/libraries/System.Web.HttpUtility/src/System/Web/Util/HttpEncoder.cs b/src/libraries/System.Web.HttpUtility/src/System/Web/Util/HttpEncoder.cs
index 3c96060bd20a92..690645e0759147 100644
--- a/src/libraries/System.Web.HttpUtility/src/System/Web/Util/HttpEncoder.cs
+++ b/src/libraries/System.Web.HttpUtility/src/System/Web/Util/HttpEncoder.cs
@@ -20,22 +20,13 @@ internal static class HttpEncoder
private static readonly SearchValues s_urlSafeBytes = SearchValues.Create(
"!()*-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"u8);
- private static void AppendCharAsUnicodeJavaScript(StringBuilder builder, char c)
- {
- builder.Append($"\\u{(int)c:x4}");
- }
-
- private static bool CharRequiresJavaScriptEncoding(char c) =>
- c < 0x20 // control chars always have to be encoded
- || c == '\"' // chars which must be encoded per JSON spec
- || c == '\\'
- || c == '\'' // HTML-sensitive chars encoded for safety
- || c == '<'
- || c == '>'
- || (c == '&')
- || c == '\u0085' // newline chars (see Unicode 6.2, Table 5-1 [http://www.unicode.org/versions/Unicode6.2.0/ch05.pdf]) have to be encoded
- || c == '\u2028'
- || c == '\u2029';
+ private static readonly SearchValues s_invalidJavaScriptChars = SearchValues.Create(
+ // Any Control, < 32 (' ')
+ "\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u0009\u000A\u000B\u000C\u000D\u000E\u000F\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001A\u001B\u001C\u001D\u001E\u001F" +
+ // Chars which must be encoded per JSON spec / HTML-sensitive chars encoded for safety
+ "\"&'<>\\" +
+ // newline chars (see Unicode 6.2, Table 5-1 [http://www.unicode.org/versions/Unicode6.2.0/ch05.pdf]) have to be encoded
+ "\u0085\u2028\u2029");
[return: NotNullIfNotNull(nameof(value))]
internal static string? HtmlAttributeEncode(string? value)
@@ -137,79 +128,71 @@ private static int IndexOfHtmlAttributeEncodingChars(string s) =>
private static bool IsNonAsciiByte(byte b) => b >= 0x7F || b < 0x20;
- internal static string JavaScriptStringEncode(string? value)
+ internal static string JavaScriptStringEncode(string? value, bool addDoubleQuotes)
{
- if (string.IsNullOrEmpty(value))
+ int i = value.AsSpan().IndexOfAny(s_invalidJavaScriptChars);
+ if (i < 0)
{
- return string.Empty;
+ return addDoubleQuotes ? $"\"{value}\"" : value ?? string.Empty;
}
- StringBuilder? b = null;
- int startIndex = 0;
- int count = 0;
- for (int i = 0; i < value.Length; i++)
- {
- char c = value[i];
+ return EncodeCore(value, i, addDoubleQuotes);
- // Append the unhandled characters (that do not require special treament)
- // to the string builder when special characters are detected.
- if (CharRequiresJavaScriptEncoding(c))
+ static string EncodeCore(ReadOnlySpan value, int i, bool addDoubleQuotes)
+ {
+ var vsb = new ValueStringBuilder(stackalloc char[StackallocThreshold]);
+ if (addDoubleQuotes)
{
- b ??= new StringBuilder(value.Length + 5);
-
- if (count > 0)
- {
- b.Append(value, startIndex, count);
- }
-
- startIndex = i + 1;
- count = 0;
+ vsb.Append('"');
+ }
+ ReadOnlySpan chars = value;
+ do
+ {
+ vsb.Append(chars.Slice(0, i));
+ char c = chars[i];
+ chars = chars.Slice(i + 1);
switch (c)
{
case '\r':
- b.Append("\\r");
+ vsb.Append("\\r");
break;
case '\t':
- b.Append("\\t");
+ vsb.Append("\\t");
break;
case '\"':
- b.Append("\\\"");
+ vsb.Append("\\\"");
break;
case '\\':
- b.Append("\\\\");
+ vsb.Append("\\\\");
break;
case '\n':
- b.Append("\\n");
+ vsb.Append("\\n");
break;
case '\b':
- b.Append("\\b");
+ vsb.Append("\\b");
break;
case '\f':
- b.Append("\\f");
+ vsb.Append("\\f");
break;
default:
- AppendCharAsUnicodeJavaScript(b, c);
+ vsb.Append("\\u");
+ vsb.AppendSpanFormattable((int)c, "x4");
break;
}
- }
- else
+
+ i = chars.IndexOfAny(s_invalidJavaScriptChars);
+ } while (i >= 0);
+
+ vsb.Append(chars);
+
+ if (addDoubleQuotes)
{
- count++;
+ vsb.Append('"');
}
- }
- if (b == null)
- {
- return value;
- }
-
- if (count > 0)
- {
- b.Append(value, startIndex, count);
+ return vsb.ToString();
}
-
- return b.ToString();
}
[return: NotNullIfNotNull(nameof(bytes))]
diff --git a/src/libraries/System.Web.HttpUtility/tests/HttpUtility/HttpUtilityTest.cs b/src/libraries/System.Web.HttpUtility/tests/HttpUtility/HttpUtilityTest.cs
index b95c663e932d4b..27942d50051001 100644
--- a/src/libraries/System.Web.HttpUtility/tests/HttpUtility/HttpUtilityTest.cs
+++ b/src/libraries/System.Web.HttpUtility/tests/HttpUtility/HttpUtilityTest.cs
@@ -310,6 +310,7 @@ public static IEnumerable