Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
<Compile Include="System\Web\Util\Utf16StringValidator.cs" />
<Compile Include="$(CommonPath)System\HexConverter.cs"
Link="Common\System\HexConverter.cs" />
<Compile Include="$(CommonPath)System\Text\ValueStringBuilder.cs"
Link="Common\System\Text\ValueStringBuilder.cs" />
<Compile Include="$(CommonPath)System\Text\ValueStringBuilder.AppendSpanFormattable.cs"
Link="Common\System\Text\ValueStringBuilder.AppendSpanFormattable.cs" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,13 @@ internal static class HttpEncoder
private static readonly SearchValues<byte> 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<char> 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)
Expand Down Expand Up @@ -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<char> 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<char> 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))]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@ public static IEnumerable<object[]> JavaScriptStringEncodeData
yield return new object[] { "", "" };
yield return new object[] {"No escaping needed.", "No escaping needed."};
yield return new object[] {"The \t and \n will need to be escaped.", "The \\t and \\n will need to be escaped."};
yield return new object[] {"The \t and \n will need to be escaped.>", "The \\t and \\n will need to be escaped.\\u003e" };
for (char c = char.MinValue; c < TestMaxChar; c++)
{
if (c >= 0 && c <= 7 || c == 11 || c >= 14 && c <= 31 || c == 38 || c == 39 || c == 60 || c == 62 || c == 133 || c == 8232 || c == 8233)
Expand Down