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); } }