Add TextWriter-based overloads to the encoding routines
These make the core implementations slightly slower but provide the benefit of reducing allocations, which is useful when these methods are called frequently by Razor.
This commit is contained in:
parent
14c872d981
commit
e5c6fd401f
|
|
@ -0,0 +1,55 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Microsoft.AspNet.WebUtilities.Encoders
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpful extension methods for the encoder classes.
|
||||
/// </summary>
|
||||
public static class EncoderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// HTML-encodes a string and writes the result to the supplied output.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The encoded value is also safe for inclusion inside an HTML attribute
|
||||
/// as long as the attribute value is surrounded by single or double quotes.
|
||||
/// </remarks>
|
||||
public static void HtmlEncode([NotNull] this IHtmlEncoder htmlEncoder, string value, [NotNull] TextWriter output)
|
||||
{
|
||||
if (!String.IsNullOrEmpty(value))
|
||||
{
|
||||
htmlEncoder.HtmlEncode(value, 0, value.Length, output);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// JavaScript-escapes a string and writes the result to the supplied output.
|
||||
/// </summary>
|
||||
public static void JavaScriptStringEncode([NotNull] this IJavaScriptStringEncoder javaScriptStringEncoder, string value, [NotNull] TextWriter output)
|
||||
{
|
||||
if (!String.IsNullOrEmpty(value))
|
||||
{
|
||||
javaScriptStringEncoder.JavaScriptStringEncode(value, 0, value.Length, output);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// URL-encodes a string and writes the result to the supplied output.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The encoded value is safe for use in the segment, query, or
|
||||
/// fragment portion of a URI.
|
||||
/// </remarks>
|
||||
public static void UrlEncode([NotNull] this IUrlEncoder urlEncoder, string value, [NotNull] TextWriter output)
|
||||
{
|
||||
if (!String.IsNullOrEmpty(value))
|
||||
{
|
||||
urlEncoder.UrlEncode(value, 0, value.Length, output);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
|
||||
namespace Microsoft.AspNet.WebUtilities.Encoders
|
||||
|
|
@ -63,6 +63,14 @@ namespace Microsoft.AspNet.WebUtilities.Encoders
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Everybody's favorite HtmlEncode routine.
|
||||
/// </summary>
|
||||
public void HtmlEncode(char[] value, int startIndex, int charCount, TextWriter output)
|
||||
{
|
||||
_innerUnicodeEncoder.Encode(value, startIndex, charCount, output);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Everybody's favorite HtmlEncode routine.
|
||||
/// </summary>
|
||||
|
|
@ -71,6 +79,14 @@ namespace Microsoft.AspNet.WebUtilities.Encoders
|
|||
return _innerUnicodeEncoder.Encode(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Everybody's favorite HtmlEncode routine.
|
||||
/// </summary>
|
||||
public void HtmlEncode(string value, int startIndex, int charCount, TextWriter output)
|
||||
{
|
||||
_innerUnicodeEncoder.Encode(value, startIndex, charCount, output);
|
||||
}
|
||||
|
||||
private sealed class HtmlUnicodeEncoder : UnicodeEncoderBase
|
||||
{
|
||||
// A singleton instance of the basic latin encoder.
|
||||
|
|
@ -101,17 +117,17 @@ namespace Microsoft.AspNet.WebUtilities.Encoders
|
|||
}
|
||||
|
||||
// Writes a scalar value as an HTML-encoded entity.
|
||||
protected override void WriteEncodedScalar(StringBuilder builder, uint value)
|
||||
protected override void WriteEncodedScalar<T>(T output, Action<T, string> writeString, Action<T, char> writeChar, uint value)
|
||||
{
|
||||
if (value == (uint)'\"') { builder.Append("""); }
|
||||
else if (value == (uint)'&') { builder.Append("&"); }
|
||||
else if (value == (uint)'<') { builder.Append("<"); }
|
||||
else if (value == (uint)'>') { builder.Append(">"); }
|
||||
else { WriteEncodedScalarAsNumericEntity(builder, value); }
|
||||
if (value == (uint)'\"') { writeString(output, """); }
|
||||
else if (value == (uint)'&') { writeString(output, "&"); }
|
||||
else if (value == (uint)'<') { writeString(output, "<"); }
|
||||
else if (value == (uint)'>') { writeString(output, ">"); }
|
||||
else { WriteEncodedScalarAsNumericEntity(output, writeChar, value); }
|
||||
}
|
||||
|
||||
// Writes a scalar value as an HTML-encoded numeric entity.
|
||||
private static void WriteEncodedScalarAsNumericEntity(StringBuilder builder, uint value)
|
||||
private static void WriteEncodedScalarAsNumericEntity<T>(T output, Action<T, char> writeChar, uint value) where T : class
|
||||
{
|
||||
// We're building the characters up in reverse
|
||||
char* chars = stackalloc char[8 /* "FFFFFFFF" */];
|
||||
|
|
@ -125,15 +141,15 @@ namespace Microsoft.AspNet.WebUtilities.Encoders
|
|||
} while (value != 0);
|
||||
|
||||
// Finally, write out the HTML-encoded scalar value.
|
||||
builder.Append('&');
|
||||
builder.Append('#');
|
||||
builder.Append('x');
|
||||
writeChar(output, '&');
|
||||
writeChar(output, '#');
|
||||
writeChar(output, 'x');
|
||||
Debug.Assert(numCharsWritten > 0, "At least one character should've been written.");
|
||||
do
|
||||
{
|
||||
builder.Append(chars[--numCharsWritten]);
|
||||
writeChar(output, chars[--numCharsWritten]);
|
||||
} while (numCharsWritten != 0);
|
||||
builder.Append(';');
|
||||
writeChar(output, ';');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
using System;
|
||||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Microsoft.AspNet.WebUtilities.Encoders
|
||||
{
|
||||
|
|
@ -10,6 +12,16 @@ namespace Microsoft.AspNet.WebUtilities.Encoders
|
|||
/// </summary>
|
||||
public interface IHtmlEncoder
|
||||
{
|
||||
/// <summary>
|
||||
/// HTML-encodes a character array and writes the result to the supplied
|
||||
/// output.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The encoded value is also safe for inclusion inside an HTML attribute
|
||||
/// as long as the attribute value is surrounded by single or double quotes.
|
||||
/// </remarks>
|
||||
void HtmlEncode([NotNull] char[] value, int startIndex, int charCount, [NotNull] TextWriter output);
|
||||
|
||||
/// <summary>
|
||||
/// HTML-encodes a given input string.
|
||||
/// </summary>
|
||||
|
|
@ -21,5 +33,15 @@ namespace Microsoft.AspNet.WebUtilities.Encoders
|
|||
/// as long as the attribute value is surrounded by single or double quotes.
|
||||
/// </remarks>
|
||||
string HtmlEncode(string value);
|
||||
|
||||
/// <summary>
|
||||
/// HTML-encodes a given input string and writes the result to the
|
||||
/// supplied output.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The encoded value is also safe for inclusion inside an HTML attribute
|
||||
/// as long as the attribute value is surrounded by single or double quotes.
|
||||
/// </remarks>
|
||||
void HtmlEncode([NotNull] string value, int startIndex, int charCount, [NotNull] TextWriter output);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Microsoft.AspNet.WebUtilities.Encoders
|
||||
{
|
||||
|
|
@ -10,6 +11,12 @@ namespace Microsoft.AspNet.WebUtilities.Encoders
|
|||
/// </summary>
|
||||
public interface IJavaScriptStringEncoder
|
||||
{
|
||||
/// <summary>
|
||||
/// JavaScript-escapes a character array and writes the result to the
|
||||
/// supplied output.
|
||||
/// </summary>
|
||||
void JavaScriptStringEncode([NotNull] char[] value, int startIndex, int charCount, [NotNull] TextWriter output);
|
||||
|
||||
/// <summary>
|
||||
/// JavaScript-escapes a given input string.
|
||||
/// </summary>
|
||||
|
|
@ -17,5 +24,11 @@ namespace Microsoft.AspNet.WebUtilities.Encoders
|
|||
/// The JavaScript-escaped value, or null if the input string was null.
|
||||
/// </returns>
|
||||
string JavaScriptStringEncode(string value);
|
||||
|
||||
/// <summary>
|
||||
/// JavaScript-escapes a given input string and writes the
|
||||
/// result to the supplied output.
|
||||
/// </summary>
|
||||
void JavaScriptStringEncode([NotNull] string value, int startIndex, int charCount, [NotNull] TextWriter output);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Microsoft.AspNet.WebUtilities.Encoders
|
||||
{
|
||||
|
|
@ -10,6 +11,16 @@ namespace Microsoft.AspNet.WebUtilities.Encoders
|
|||
/// </summary>
|
||||
public interface IUrlEncoder
|
||||
{
|
||||
/// <summary>
|
||||
/// URL-escapes a character array and writes the result to the supplied
|
||||
/// output.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The encoded value is safe for use in the segment, query, or
|
||||
/// fragment portion of a URI.
|
||||
/// </remarks>
|
||||
void UrlEncode([NotNull] char[] value, int startIndex, int charCount, [NotNull] TextWriter output);
|
||||
|
||||
/// <summary>
|
||||
/// URL-escapes a given input string.
|
||||
/// </summary>
|
||||
|
|
@ -21,5 +32,14 @@ namespace Microsoft.AspNet.WebUtilities.Encoders
|
|||
/// fragment portion of a URI.
|
||||
/// </remarks>
|
||||
string UrlEncode(string value);
|
||||
|
||||
/// <summary>
|
||||
/// URL-escapes a string and writes the result to the supplied output.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The encoded value is safe for use in the segment, query, or
|
||||
/// fragment portion of a URI.
|
||||
/// </remarks>
|
||||
void UrlEncode([NotNull] string value, int startIndex, int charCount, [NotNull] TextWriter output);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
|
||||
namespace Microsoft.AspNet.WebUtilities.Encoders
|
||||
|
|
@ -63,6 +63,14 @@ namespace Microsoft.AspNet.WebUtilities.Encoders
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Everybody's favorite JavaScriptStringEncode routine.
|
||||
/// </summary>
|
||||
public void JavaScriptStringEncode(char[] value, int startIndex, int charCount, TextWriter output)
|
||||
{
|
||||
_innerUnicodeEncoder.Encode(value, startIndex, charCount, output);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Everybody's favorite JavaScriptStringEncode routine.
|
||||
/// </summary>
|
||||
|
|
@ -71,6 +79,14 @@ namespace Microsoft.AspNet.WebUtilities.Encoders
|
|||
return _innerUnicodeEncoder.Encode(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Everybody's favorite JavaScriptStringEncode routine.
|
||||
/// </summary>
|
||||
public void JavaScriptStringEncode(string value, int startIndex, int charCount, TextWriter output)
|
||||
{
|
||||
_innerUnicodeEncoder.Encode(value, startIndex, charCount, output);
|
||||
}
|
||||
|
||||
private sealed class JavaScriptStringUnicodeEncoder : UnicodeEncoderBase
|
||||
{
|
||||
// A singleton instance of the basic latin encoder.
|
||||
|
|
@ -108,7 +124,7 @@ namespace Microsoft.AspNet.WebUtilities.Encoders
|
|||
// See ECMA-262, Sec. 7.8.4, and ECMA-404, Sec. 9
|
||||
// http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.4
|
||||
// http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf
|
||||
protected override void WriteEncodedScalar(StringBuilder builder, uint value)
|
||||
protected override void WriteEncodedScalar<T>(T output, Action<T, string> writeString, Action<T, char> writeChar, uint value)
|
||||
{
|
||||
// ECMA-262 allows encoding U+000B as "\v", but ECMA-404 does not.
|
||||
// Both ECMA-262 and ECMA-404 allow encoding U+002F SOLIDUS as "\/".
|
||||
|
|
@ -117,46 +133,46 @@ namespace Microsoft.AspNet.WebUtilities.Encoders
|
|||
// be written out as numeric entities for defense-in-depth.
|
||||
// See UnicodeEncoderBase ctor comments for more info.
|
||||
|
||||
if (value == (uint)'\b') { builder.Append(@"\b"); }
|
||||
else if (value == (uint)'\t') { builder.Append(@"\t"); }
|
||||
else if (value == (uint)'\n') { builder.Append(@"\n"); }
|
||||
else if (value == (uint)'\f') { builder.Append(@"\f"); }
|
||||
else if (value == (uint)'\r') { builder.Append(@"\r"); }
|
||||
else if (value == (uint)'/') { builder.Append(@"\/"); }
|
||||
else if (value == (uint)'\\') { builder.Append(@"\\"); }
|
||||
else { WriteEncodedScalarAsNumericEntity(builder, value); }
|
||||
if (value == (uint)'\b') { writeString(output, @"\b"); }
|
||||
else if (value == (uint)'\t') { writeString(output, @"\t"); }
|
||||
else if (value == (uint)'\n') { writeString(output, @"\n"); }
|
||||
else if (value == (uint)'\f') { writeString(output, @"\f"); }
|
||||
else if (value == (uint)'\r') { writeString(output, @"\r"); }
|
||||
else if (value == (uint)'/') { writeString(output, @"\/"); }
|
||||
else if (value == (uint)'\\') { writeString(output, @"\\"); }
|
||||
else { WriteEncodedScalarAsNumericEntity(output, writeChar, value); }
|
||||
}
|
||||
|
||||
// Writes a scalar value as an JavaScript-escaped character (or sequence of characters).
|
||||
private static void WriteEncodedScalarAsNumericEntity(StringBuilder builder, uint value)
|
||||
private static void WriteEncodedScalarAsNumericEntity<T>(T output, Action<T, char> writeChar, uint value) where T : class
|
||||
{
|
||||
if (UnicodeHelpers.IsSupplementaryCodePoint((int)value))
|
||||
{
|
||||
// Convert this back to UTF-16 and write out both characters.
|
||||
char leadingSurrogate, trailingSurrogate;
|
||||
UnicodeHelpers.GetUtf16SurrogatePairFromAstralScalarValue((int)value, out leadingSurrogate, out trailingSurrogate);
|
||||
WriteEncodedSingleCharacter(builder, leadingSurrogate);
|
||||
WriteEncodedSingleCharacter(builder, trailingSurrogate);
|
||||
WriteEncodedSingleCharacter(output, writeChar, leadingSurrogate);
|
||||
WriteEncodedSingleCharacter(output, writeChar, trailingSurrogate);
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is only a single character.
|
||||
WriteEncodedSingleCharacter(builder, value);
|
||||
WriteEncodedSingleCharacter(output, writeChar, value);
|
||||
}
|
||||
}
|
||||
|
||||
// Writes an encoded scalar value (in the BMP) as a JavaScript-escaped character.
|
||||
private static void WriteEncodedSingleCharacter(StringBuilder builder, uint value)
|
||||
private static void WriteEncodedSingleCharacter<T>(T output, Action<T, char> writeChar, uint value) where T : class
|
||||
{
|
||||
Debug.Assert(!UnicodeHelpers.IsSupplementaryCodePoint((int)value), "The incoming value should've been in the BMP.");
|
||||
|
||||
// Encode this as 6 chars "\uFFFF".
|
||||
builder.Append('\\');
|
||||
builder.Append('u');
|
||||
builder.Append(HexUtil.IntToChar(value >> 12));
|
||||
builder.Append(HexUtil.IntToChar((value >> 8) & 0xFU));
|
||||
builder.Append(HexUtil.IntToChar((value >> 4) & 0xFU));
|
||||
builder.Append(HexUtil.IntToChar(value & 0xFU));
|
||||
writeChar(output, '\\');
|
||||
writeChar(output, 'u');
|
||||
writeChar(output, HexUtil.IntToChar(value >> 12));
|
||||
writeChar(output, HexUtil.IntToChar((value >> 8) & 0xFU));
|
||||
writeChar(output, HexUtil.IntToChar((value >> 4) & 0xFU));
|
||||
writeChar(output, HexUtil.IntToChar(value & 0xFU));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
|
||||
|
|
@ -10,6 +11,12 @@ namespace Microsoft.AspNet.WebUtilities.Encoders
|
|||
{
|
||||
internal unsafe abstract class UnicodeEncoderBase
|
||||
{
|
||||
// Stubs for appending data to a TextWriter or StringBuilder
|
||||
private static readonly Action<StringBuilder, char> _appendCharToStringBuilderStub = PrepareDelegate((Action<StringBuilder, char>)AppendToStringBuilder);
|
||||
private static readonly Action<TextWriter, char> _appendCharToTextWriterStub = PrepareDelegate((Action<TextWriter, char>)AppendToTextWriter);
|
||||
private static readonly Action<StringBuilder, string> _appendStringToStringBuilderStub = PrepareDelegate((Action<StringBuilder, string>)AppendToStringBuilder);
|
||||
private static readonly Action<TextWriter, string> _appendStringToTextWriterStub = PrepareDelegate((Action<TextWriter, string>)AppendToTextWriter);
|
||||
|
||||
// A bitmap of characters which are allowed to be returned unescaped.
|
||||
private readonly uint[] _allowedCharsBitmap = new uint[0x10000 / 32];
|
||||
|
||||
|
|
@ -70,6 +77,26 @@ namespace Microsoft.AspNet.WebUtilities.Encoders
|
|||
_allowedCharsBitmap[index] |= 0x1U << offset;
|
||||
}
|
||||
|
||||
private static void AppendToStringBuilder(StringBuilder builder, char value)
|
||||
{
|
||||
builder.Append(value);
|
||||
}
|
||||
|
||||
private static void AppendToStringBuilder(StringBuilder builder, string value)
|
||||
{
|
||||
builder.Append(value);
|
||||
}
|
||||
|
||||
private static void AppendToTextWriter(TextWriter writer, char value)
|
||||
{
|
||||
writer.Write(value);
|
||||
}
|
||||
|
||||
private static void AppendToTextWriter(TextWriter writer, string value)
|
||||
{
|
||||
writer.Write(value);
|
||||
}
|
||||
|
||||
// Marks a character as forbidden (must be returned encoded)
|
||||
protected void ForbidCharacter(char c)
|
||||
{
|
||||
|
|
@ -79,6 +106,37 @@ namespace Microsoft.AspNet.WebUtilities.Encoders
|
|||
_allowedCharsBitmap[index] &= ~(0x1U << offset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Entry point to the encoder.
|
||||
/// </summary>
|
||||
public void Encode([NotNull] char[] value, int startIndex, int charCount, [NotNull] TextWriter output)
|
||||
{
|
||||
// Input checking
|
||||
ValidateInputs(startIndex, charCount, actualInputLength: value.Length);
|
||||
|
||||
if (charCount != 0)
|
||||
{
|
||||
fixed (char* pChars = value)
|
||||
{
|
||||
int indexOfFirstCharWhichRequiresEncoding = GetIndexOfFirstCharWhichRequiresEncoding(&pChars[startIndex], charCount);
|
||||
if (indexOfFirstCharWhichRequiresEncoding < 0)
|
||||
{
|
||||
// All chars are valid - just copy the buffer as-is.
|
||||
output.Write(value, startIndex, charCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Flush all chars which are known to be valid, then encode the remainder individually
|
||||
if (indexOfFirstCharWhichRequiresEncoding > 0)
|
||||
{
|
||||
output.Write(value, startIndex, indexOfFirstCharWhichRequiresEncoding);
|
||||
}
|
||||
EncodeCore(&pChars[startIndex + indexOfFirstCharWhichRequiresEncoding], (uint)(charCount - indexOfFirstCharWhichRequiresEncoding), output);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Entry point to the encoder.
|
||||
/// </summary>
|
||||
|
|
@ -95,22 +153,60 @@ namespace Microsoft.AspNet.WebUtilities.Encoders
|
|||
{
|
||||
if (!IsCharacterAllowed(value[i]))
|
||||
{
|
||||
return EncodeCore(value, i);
|
||||
return EncodeCore(value, idxOfFirstCharWhichRequiresEncoding: i);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Entry point to the encoder.
|
||||
/// </summary>
|
||||
public void Encode([NotNull] string value, int startIndex, int charCount, [NotNull] TextWriter output)
|
||||
{
|
||||
// Input checking
|
||||
ValidateInputs(startIndex, charCount, actualInputLength: value.Length);
|
||||
|
||||
if (charCount != 0)
|
||||
{
|
||||
fixed (char* pChars = value)
|
||||
{
|
||||
if (charCount == value.Length)
|
||||
{
|
||||
// Optimize for the common case: we're being asked to encode the entire input string
|
||||
// (not just a subset). If all characters are safe, we can just spit it out as-is.
|
||||
int indexOfFirstCharWhichRequiresEncoding = GetIndexOfFirstCharWhichRequiresEncoding(pChars, charCount);
|
||||
if (indexOfFirstCharWhichRequiresEncoding < 0)
|
||||
{
|
||||
output.Write(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Flush all chars which are known to be valid, then encode the remainder individually
|
||||
for (int i = 0; i < indexOfFirstCharWhichRequiresEncoding; i++)
|
||||
{
|
||||
output.Write(pChars[i]);
|
||||
}
|
||||
EncodeCore(&pChars[indexOfFirstCharWhichRequiresEncoding], (uint)(charCount - indexOfFirstCharWhichRequiresEncoding), output);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// We're being asked to encode a subset, so we need to go through the slow path of appending
|
||||
// each character individually.
|
||||
EncodeCore(&pChars[startIndex], (uint)charCount, output);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string EncodeCore(string input, int idxOfFirstCharWhichRequiresEncoding)
|
||||
{
|
||||
Debug.Assert(idxOfFirstCharWhichRequiresEncoding >= 0);
|
||||
Debug.Assert(idxOfFirstCharWhichRequiresEncoding < input.Length);
|
||||
|
||||
// The worst case encoding is 8 output chars per input char: [input] U+FFFF -> [output] ""
|
||||
// We don't need to worry about astral code points since they consume *two* input chars to
|
||||
// generate at most 10 output chars (""), which equates to 5 output per input.
|
||||
int numCharsWhichMayRequireEncoding = input.Length - idxOfFirstCharWhichRequiresEncoding;
|
||||
int sbCapacity = checked(idxOfFirstCharWhichRequiresEncoding + EncoderCommon.GetCapacityOfOutputStringBuilder(numCharsWhichMayRequireEncoding, worstCaseOutputCharsPerInputChar: 8));
|
||||
int sbCapacity = checked(idxOfFirstCharWhichRequiresEncoding + EncoderCommon.GetCapacityOfOutputStringBuilder(numCharsWhichMayRequireEncoding, _maxOutputCharsPerInputChar));
|
||||
Debug.Assert(sbCapacity >= input.Length);
|
||||
|
||||
// Allocate the StringBuilder with the first (known to not require encoding) part of the input string,
|
||||
|
|
@ -118,11 +214,17 @@ namespace Microsoft.AspNet.WebUtilities.Encoders
|
|||
StringBuilder builder = new StringBuilder(input, 0, idxOfFirstCharWhichRequiresEncoding, sbCapacity);
|
||||
fixed (char* pInput = input)
|
||||
{
|
||||
return EncodeCore2(builder, &pInput[idxOfFirstCharWhichRequiresEncoding], (uint)numCharsWhichMayRequireEncoding);
|
||||
EncodeCore(builder, _appendStringToStringBuilderStub, _appendCharToStringBuilderStub, &pInput[idxOfFirstCharWhichRequiresEncoding], (uint)numCharsWhichMayRequireEncoding);
|
||||
}
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private string EncodeCore2(StringBuilder builder, char* input, uint charsRemaining)
|
||||
private void EncodeCore(char* input, uint charsRemaining, TextWriter output)
|
||||
{
|
||||
EncodeCore(output, _appendStringToTextWriterStub, _appendCharToTextWriterStub, input, charsRemaining);
|
||||
}
|
||||
|
||||
private void EncodeCore<T>(T output, Action<T, string> writeString, Action<T, char> writeChar, char* input, uint charsRemaining) where T : class
|
||||
{
|
||||
while (charsRemaining != 0)
|
||||
{
|
||||
|
|
@ -130,7 +232,7 @@ namespace Microsoft.AspNet.WebUtilities.Encoders
|
|||
if (UnicodeHelpers.IsSupplementaryCodePoint(nextScalar))
|
||||
{
|
||||
// Supplementary characters should always be encoded numerically.
|
||||
WriteEncodedScalar(builder, (uint)nextScalar);
|
||||
WriteEncodedScalar(output, writeString, writeChar, (uint)nextScalar);
|
||||
|
||||
// We consume two UTF-16 characters for a single supplementary character.
|
||||
input += 2;
|
||||
|
|
@ -144,16 +246,26 @@ namespace Microsoft.AspNet.WebUtilities.Encoders
|
|||
char c = (char)nextScalar;
|
||||
if (IsCharacterAllowed(c))
|
||||
{
|
||||
builder.Append(c);
|
||||
writeChar(output, c);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteEncodedScalar(builder, (uint)nextScalar);
|
||||
WriteEncodedScalar(output, writeString, writeChar, (uint)nextScalar);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
private int GetIndexOfFirstCharWhichRequiresEncoding(char* input, int inputLength)
|
||||
{
|
||||
for (int i = 0; i < inputLength; i++)
|
||||
{
|
||||
if (!IsCharacterAllowed(input[i]))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1; // no characters require encoding
|
||||
}
|
||||
|
||||
// Determines whether the given character can be returned unencoded.
|
||||
|
|
@ -166,6 +278,34 @@ namespace Microsoft.AspNet.WebUtilities.Encoders
|
|||
return ((_allowedCharsBitmap[index] >> offset) & 0x1U) != 0;
|
||||
}
|
||||
|
||||
protected abstract void WriteEncodedScalar(StringBuilder builder, uint value);
|
||||
private static T PrepareDelegate<T>(T @del) where T : class
|
||||
{
|
||||
#if ASPNETCORE50
|
||||
// RuntimeHelpers.PrepareMethod doesn't exist on CoreCLR, so we'll depend
|
||||
// on cross-gen for performance optimizations.
|
||||
return del;
|
||||
#else
|
||||
// We prepare the method ahead of time to ensure that it's JITted before
|
||||
// the delegate is constructed; this allows the delegate to point straight
|
||||
// to the processor code rather than to the prestub dispatch code.
|
||||
Delegate castDel = (Delegate)(object)del;
|
||||
RuntimeHelpers.PrepareMethod(castDel.Method.MethodHandle);
|
||||
return (T)(object)castDel.Method.CreateDelegate(typeof(T));
|
||||
#endif
|
||||
}
|
||||
|
||||
private static void ValidateInputs(int startIndex, int charCount, int actualInputLength)
|
||||
{
|
||||
if (startIndex < 0 || startIndex > actualInputLength)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(startIndex));
|
||||
}
|
||||
if (charCount < 0 || charCount > (actualInputLength - startIndex))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(charCount));
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void WriteEncodedScalar<T>(T output, Action<T, string> writeString, Action<T, char> writeChar, uint value) where T : class;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
|
||||
namespace Microsoft.AspNet.WebUtilities.Encoders
|
||||
|
|
@ -63,6 +63,14 @@ namespace Microsoft.AspNet.WebUtilities.Encoders
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Everybody's favorite UrlEncode routine.
|
||||
/// </summary>
|
||||
public void UrlEncode(char[] value, int startIndex, int charCount, TextWriter output)
|
||||
{
|
||||
_innerUnicodeEncoder.Encode(value, startIndex, charCount, output);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Everybody's favorite UrlEncode routine.
|
||||
/// </summary>
|
||||
|
|
@ -71,6 +79,14 @@ namespace Microsoft.AspNet.WebUtilities.Encoders
|
|||
return _innerUnicodeEncoder.Encode(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Everybody's favorite UrlEncode routine.
|
||||
/// </summary>
|
||||
public void UrlEncode(string value, int startIndex, int charCount, TextWriter output)
|
||||
{
|
||||
_innerUnicodeEncoder.Encode(value, startIndex, charCount, output);
|
||||
}
|
||||
|
||||
private sealed class UrlUnicodeEncoder : UnicodeEncoderBase
|
||||
{
|
||||
// A singleton instance of the basic latin encoder.
|
||||
|
|
@ -144,16 +160,16 @@ namespace Microsoft.AspNet.WebUtilities.Encoders
|
|||
}
|
||||
|
||||
// Writes a scalar value as a percent-encoded sequence of UTF8 bytes, per RFC 3987.
|
||||
protected override void WriteEncodedScalar(StringBuilder builder, uint value)
|
||||
protected override void WriteEncodedScalar<T>(T output, Action<T, string> writeString, Action<T, char> writeChar, uint value)
|
||||
{
|
||||
uint asUtf8 = (uint)UnicodeHelpers.GetUtf8RepresentationForScalarValue(value);
|
||||
do
|
||||
{
|
||||
char highNibble, lowNibble;
|
||||
HexUtil.WriteHexEncodedByte((byte)asUtf8, out highNibble, out lowNibble);
|
||||
builder.Append('%');
|
||||
builder.Append(highNibble);
|
||||
builder.Append(lowNibble);
|
||||
writeChar(output, '%');
|
||||
writeChar(output, highNibble);
|
||||
writeChar(output, lowNibble);
|
||||
} while ((asUtf8 >>= 8) != 0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue