diff --git a/src/Microsoft.AspNet.WebUtilities/Encoders/EncoderExtensions.cs b/src/Microsoft.AspNet.WebUtilities/Encoders/EncoderExtensions.cs
new file mode 100644
index 0000000000..57c3b59ba7
--- /dev/null
+++ b/src/Microsoft.AspNet.WebUtilities/Encoders/EncoderExtensions.cs
@@ -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
+{
+ ///
+ /// Helpful extension methods for the encoder classes.
+ ///
+ public static class EncoderExtensions
+ {
+ ///
+ /// HTML-encodes a string and writes the result to the supplied output.
+ ///
+ ///
+ /// 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.
+ ///
+ public static void HtmlEncode([NotNull] this IHtmlEncoder htmlEncoder, string value, [NotNull] TextWriter output)
+ {
+ if (!String.IsNullOrEmpty(value))
+ {
+ htmlEncoder.HtmlEncode(value, 0, value.Length, output);
+ }
+ }
+
+ ///
+ /// JavaScript-escapes a string and writes the result to the supplied output.
+ ///
+ public static void JavaScriptStringEncode([NotNull] this IJavaScriptStringEncoder javaScriptStringEncoder, string value, [NotNull] TextWriter output)
+ {
+ if (!String.IsNullOrEmpty(value))
+ {
+ javaScriptStringEncoder.JavaScriptStringEncode(value, 0, value.Length, output);
+ }
+ }
+
+ ///
+ /// URL-encodes a string and writes the result to the supplied output.
+ ///
+ ///
+ /// The encoded value is safe for use in the segment, query, or
+ /// fragment portion of a URI.
+ ///
+ public static void UrlEncode([NotNull] this IUrlEncoder urlEncoder, string value, [NotNull] TextWriter output)
+ {
+ if (!String.IsNullOrEmpty(value))
+ {
+ urlEncoder.UrlEncode(value, 0, value.Length, output);
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.WebUtilities/Encoders/HtmlEncoder.cs b/src/Microsoft.AspNet.WebUtilities/Encoders/HtmlEncoder.cs
index 0f205b3e43..30a3da320e 100644
--- a/src/Microsoft.AspNet.WebUtilities/Encoders/HtmlEncoder.cs
+++ b/src/Microsoft.AspNet.WebUtilities/Encoders/HtmlEncoder.cs
@@ -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
}
}
+ ///
+ /// Everybody's favorite HtmlEncode routine.
+ ///
+ public void HtmlEncode(char[] value, int startIndex, int charCount, TextWriter output)
+ {
+ _innerUnicodeEncoder.Encode(value, startIndex, charCount, output);
+ }
+
///
/// Everybody's favorite HtmlEncode routine.
///
@@ -71,6 +79,14 @@ namespace Microsoft.AspNet.WebUtilities.Encoders
return _innerUnicodeEncoder.Encode(value);
}
+ ///
+ /// Everybody's favorite HtmlEncode routine.
+ ///
+ 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 output, Action writeString, Action 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 output, Action 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, ';');
}
}
}
diff --git a/src/Microsoft.AspNet.WebUtilities/Encoders/IHtmlEncoder.cs b/src/Microsoft.AspNet.WebUtilities/Encoders/IHtmlEncoder.cs
index e80fb908a7..89947567ce 100644
--- a/src/Microsoft.AspNet.WebUtilities/Encoders/IHtmlEncoder.cs
+++ b/src/Microsoft.AspNet.WebUtilities/Encoders/IHtmlEncoder.cs
@@ -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
///
public interface IHtmlEncoder
{
+ ///
+ /// HTML-encodes a character array and writes the result to the supplied
+ /// output.
+ ///
+ ///
+ /// 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.
+ ///
+ void HtmlEncode([NotNull] char[] value, int startIndex, int charCount, [NotNull] TextWriter output);
+
///
/// HTML-encodes a given input string.
///
@@ -21,5 +33,15 @@ namespace Microsoft.AspNet.WebUtilities.Encoders
/// as long as the attribute value is surrounded by single or double quotes.
///
string HtmlEncode(string value);
+
+ ///
+ /// HTML-encodes a given input string and writes the result to the
+ /// supplied output.
+ ///
+ ///
+ /// 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.
+ ///
+ void HtmlEncode([NotNull] string value, int startIndex, int charCount, [NotNull] TextWriter output);
}
}
diff --git a/src/Microsoft.AspNet.WebUtilities/Encoders/IJavaScriptStringEncoder.cs b/src/Microsoft.AspNet.WebUtilities/Encoders/IJavaScriptStringEncoder.cs
index 8a287548cf..502c699ac4 100644
--- a/src/Microsoft.AspNet.WebUtilities/Encoders/IJavaScriptStringEncoder.cs
+++ b/src/Microsoft.AspNet.WebUtilities/Encoders/IJavaScriptStringEncoder.cs
@@ -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
///
public interface IJavaScriptStringEncoder
{
+ ///
+ /// JavaScript-escapes a character array and writes the result to the
+ /// supplied output.
+ ///
+ void JavaScriptStringEncode([NotNull] char[] value, int startIndex, int charCount, [NotNull] TextWriter output);
+
///
/// JavaScript-escapes a given input string.
///
@@ -17,5 +24,11 @@ namespace Microsoft.AspNet.WebUtilities.Encoders
/// The JavaScript-escaped value, or null if the input string was null.
///
string JavaScriptStringEncode(string value);
+
+ ///
+ /// JavaScript-escapes a given input string and writes the
+ /// result to the supplied output.
+ ///
+ void JavaScriptStringEncode([NotNull] string value, int startIndex, int charCount, [NotNull] TextWriter output);
}
}
diff --git a/src/Microsoft.AspNet.WebUtilities/Encoders/IUrlEncoder.cs b/src/Microsoft.AspNet.WebUtilities/Encoders/IUrlEncoder.cs
index 0806f5f971..c04014570e 100644
--- a/src/Microsoft.AspNet.WebUtilities/Encoders/IUrlEncoder.cs
+++ b/src/Microsoft.AspNet.WebUtilities/Encoders/IUrlEncoder.cs
@@ -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
///
public interface IUrlEncoder
{
+ ///
+ /// URL-escapes a character array and writes the result to the supplied
+ /// output.
+ ///
+ ///
+ /// The encoded value is safe for use in the segment, query, or
+ /// fragment portion of a URI.
+ ///
+ void UrlEncode([NotNull] char[] value, int startIndex, int charCount, [NotNull] TextWriter output);
+
///
/// URL-escapes a given input string.
///
@@ -21,5 +32,14 @@ namespace Microsoft.AspNet.WebUtilities.Encoders
/// fragment portion of a URI.
///
string UrlEncode(string value);
+
+ ///
+ /// URL-escapes a string and writes the result to the supplied output.
+ ///
+ ///
+ /// The encoded value is safe for use in the segment, query, or
+ /// fragment portion of a URI.
+ ///
+ void UrlEncode([NotNull] string value, int startIndex, int charCount, [NotNull] TextWriter output);
}
}
diff --git a/src/Microsoft.AspNet.WebUtilities/Encoders/JavaScriptStringEncoder.cs b/src/Microsoft.AspNet.WebUtilities/Encoders/JavaScriptStringEncoder.cs
index 671d2081b3..460cae57cd 100644
--- a/src/Microsoft.AspNet.WebUtilities/Encoders/JavaScriptStringEncoder.cs
+++ b/src/Microsoft.AspNet.WebUtilities/Encoders/JavaScriptStringEncoder.cs
@@ -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
}
}
+ ///
+ /// Everybody's favorite JavaScriptStringEncode routine.
+ ///
+ public void JavaScriptStringEncode(char[] value, int startIndex, int charCount, TextWriter output)
+ {
+ _innerUnicodeEncoder.Encode(value, startIndex, charCount, output);
+ }
+
///
/// Everybody's favorite JavaScriptStringEncode routine.
///
@@ -71,6 +79,14 @@ namespace Microsoft.AspNet.WebUtilities.Encoders
return _innerUnicodeEncoder.Encode(value);
}
+ ///
+ /// Everybody's favorite JavaScriptStringEncode routine.
+ ///
+ 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 output, Action writeString, Action 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 output, Action 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 output, Action 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));
}
}
}
diff --git a/src/Microsoft.AspNet.WebUtilities/Encoders/UnicodeEncoderBase.cs b/src/Microsoft.AspNet.WebUtilities/Encoders/UnicodeEncoderBase.cs
index 2f04910b30..d370fbe4f6 100644
--- a/src/Microsoft.AspNet.WebUtilities/Encoders/UnicodeEncoderBase.cs
+++ b/src/Microsoft.AspNet.WebUtilities/Encoders/UnicodeEncoderBase.cs
@@ -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 _appendCharToStringBuilderStub = PrepareDelegate((Action)AppendToStringBuilder);
+ private static readonly Action _appendCharToTextWriterStub = PrepareDelegate((Action)AppendToTextWriter);
+ private static readonly Action _appendStringToStringBuilderStub = PrepareDelegate((Action)AppendToStringBuilder);
+ private static readonly Action _appendStringToTextWriterStub = PrepareDelegate((Action)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);
}
+ ///
+ /// Entry point to the encoder.
+ ///
+ 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);
+ }
+ }
+ }
+ }
+
///
/// Entry point to the encoder.
///
@@ -95,22 +153,60 @@ namespace Microsoft.AspNet.WebUtilities.Encoders
{
if (!IsCharacterAllowed(value[i]))
{
- return EncodeCore(value, i);
+ return EncodeCore(value, idxOfFirstCharWhichRequiresEncoding: i);
}
}
return value;
}
+ ///
+ /// Entry point to the encoder.
+ ///
+ 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 output, Action writeString, Action 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 @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 output, Action writeString, Action writeChar, uint value) where T : class;
}
}
diff --git a/src/Microsoft.AspNet.WebUtilities/Encoders/UrlEncoder.cs b/src/Microsoft.AspNet.WebUtilities/Encoders/UrlEncoder.cs
index 81706559ff..2eda7cb52e 100644
--- a/src/Microsoft.AspNet.WebUtilities/Encoders/UrlEncoder.cs
+++ b/src/Microsoft.AspNet.WebUtilities/Encoders/UrlEncoder.cs
@@ -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
}
}
+ ///
+ /// Everybody's favorite UrlEncode routine.
+ ///
+ public void UrlEncode(char[] value, int startIndex, int charCount, TextWriter output)
+ {
+ _innerUnicodeEncoder.Encode(value, startIndex, charCount, output);
+ }
+
///
/// Everybody's favorite UrlEncode routine.
///
@@ -71,6 +79,14 @@ namespace Microsoft.AspNet.WebUtilities.Encoders
return _innerUnicodeEncoder.Encode(value);
}
+ ///
+ /// Everybody's favorite UrlEncode routine.
+ ///
+ 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 output, Action writeString, Action 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);
}
}