Extend `WebEncoders` API to avoid allocations within the methods
- rewrite existing methods in terms of the new ones - don't allocate multiple 0-length arrays nits: - clarify a couple of doc comments e.g. using `<paramref/>` - move an error message into a resource - pass parameter names into new resource - rename parameters for consistency e.g. `inputLength` -> `count` - name literal `int` parameters - more `var`
This commit is contained in:
parent
5d8231ee0d
commit
8c120a0792
|
|
@ -1,4 +1,4 @@
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// <auto-generated>
|
// <auto-generated>
|
||||||
// This code was generated by a tool.
|
// This code was generated by a tool.
|
||||||
// Runtime Version:4.0.30319.42000
|
// Runtime Version:4.0.30319.42000
|
||||||
|
|
@ -76,5 +76,14 @@ namespace Microsoft.AspNetCore.WebUtilities {
|
||||||
return ResourceManager.GetString("HttpResponseStreamWriter_StreamNotWritable", resourceCulture);
|
return ResourceManager.GetString("HttpResponseStreamWriter_StreamNotWritable", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Invalid {0}, {1} or {2} length..
|
||||||
|
/// </summary>
|
||||||
|
internal static string WebEncoders_InvalidCountOffsetOrLength {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("WebEncoders_InvalidCountOffsetOrLength", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<root>
|
<root>
|
||||||
<!--
|
<!--
|
||||||
Microsoft ResX Schema
|
Microsoft ResX Schema
|
||||||
|
|
||||||
Version 2.0
|
Version 2.0
|
||||||
|
|
||||||
The primary goals of this format is to allow a simple XML format
|
The primary goals of this format is to allow a simple XML format
|
||||||
that is mostly human readable. The generation and parsing of the
|
that is mostly human readable. The generation and parsing of the
|
||||||
various data types are done through the TypeConverter classes
|
various data types are done through the TypeConverter classes
|
||||||
associated with the data types.
|
associated with the data types.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
... ado.net/XML headers & schema ...
|
... ado.net/XML headers & schema ...
|
||||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
<resheader name="version">2.0</resheader>
|
<resheader name="version">2.0</resheader>
|
||||||
|
|
@ -26,36 +26,36 @@
|
||||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
<comment>This is a comment</comment>
|
<comment>This is a comment</comment>
|
||||||
</data>
|
</data>
|
||||||
|
|
||||||
There are any number of "resheader" rows that contain simple
|
There are any number of "resheader" rows that contain simple
|
||||||
name/value pairs.
|
name/value pairs.
|
||||||
|
|
||||||
Each data row contains a name, and value. The row also contains a
|
Each data row contains a name, and value. The row also contains a
|
||||||
type or mimetype. Type corresponds to a .NET class that support
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
text/value conversion through the TypeConverter architecture.
|
text/value conversion through the TypeConverter architecture.
|
||||||
Classes that don't support this are serialized and stored with the
|
Classes that don't support this are serialized and stored with the
|
||||||
mimetype set.
|
mimetype set.
|
||||||
|
|
||||||
The mimetype is used for serialized objects, and tells the
|
The mimetype is used for serialized objects, and tells the
|
||||||
ResXResourceReader how to depersist the object. This is currently not
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
extensible. For a given mimetype the value must be set accordingly:
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
that the ResXResourceWriter will generate, however the reader can
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
read any of the formats listed below.
|
read any of the formats listed below.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.binary.base64
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
value : The object must be serialized with
|
value : The object must be serialized with
|
||||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.soap.base64
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
value : The object must be serialized with
|
value : The object must be serialized with
|
||||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
value : The object must be serialized into a byte array
|
value : The object must be serialized into a byte array
|
||||||
: using a System.ComponentModel.TypeConverter
|
: using a System.ComponentModel.TypeConverter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
-->
|
-->
|
||||||
|
|
@ -123,4 +123,7 @@
|
||||||
<data name="HttpResponseStreamWriter_StreamNotWritable" xml:space="preserve">
|
<data name="HttpResponseStreamWriter_StreamNotWritable" xml:space="preserve">
|
||||||
<value>The stream must support writing.</value>
|
<value>The stream must support writing.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="WebEncoders_InvalidCountOffsetOrLength" xml:space="preserve">
|
||||||
|
<value>Invalid {0}, {1} or {2} length.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.WebUtilities
|
namespace Microsoft.AspNetCore.WebUtilities
|
||||||
{
|
{
|
||||||
|
|
@ -11,6 +12,8 @@ namespace Microsoft.AspNetCore.WebUtilities
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class WebEncoders
|
public static class WebEncoders
|
||||||
{
|
{
|
||||||
|
private static readonly byte[] EmptyBytes = new byte[0];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Decodes a base64url-encoded string.
|
/// Decodes a base64url-encoded string.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -18,7 +21,7 @@ namespace Microsoft.AspNetCore.WebUtilities
|
||||||
/// <returns>The base64url-decoded form of the input.</returns>
|
/// <returns>The base64url-decoded form of the input.</returns>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// The input must not contain any whitespace or padding characters.
|
/// The input must not contain any whitespace or padding characters.
|
||||||
/// Throws FormatException if the input is malformed.
|
/// Throws <see cref="FormatException"/> if the input is malformed.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public static byte[] Base64UrlDecode(string input)
|
public static byte[] Base64UrlDecode(string input)
|
||||||
{
|
{
|
||||||
|
|
@ -27,7 +30,7 @@ namespace Microsoft.AspNetCore.WebUtilities
|
||||||
throw new ArgumentNullException(nameof(input));
|
throw new ArgumentNullException(nameof(input));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Base64UrlDecode(input, 0, input.Length);
|
return Base64UrlDecode(input, offset: 0, count: input.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -39,7 +42,7 @@ namespace Microsoft.AspNetCore.WebUtilities
|
||||||
/// <returns>The base64url-decoded form of the input.</returns>
|
/// <returns>The base64url-decoded form of the input.</returns>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// The input must not contain any whitespace or padding characters.
|
/// The input must not contain any whitespace or padding characters.
|
||||||
/// Throws FormatException if the input is malformed.
|
/// Throws <see cref="FormatException"/> if the input is malformed.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public static byte[] Base64UrlDecode(string input, int offset, int count)
|
public static byte[] Base64UrlDecode(string input, int offset, int count)
|
||||||
{
|
{
|
||||||
|
|
@ -48,50 +51,139 @@ namespace Microsoft.AspNetCore.WebUtilities
|
||||||
throw new ArgumentNullException(nameof(input));
|
throw new ArgumentNullException(nameof(input));
|
||||||
}
|
}
|
||||||
|
|
||||||
ValidateParameters(input.Length, offset, count);
|
ValidateParameters(input.Length, nameof(input), offset, count);
|
||||||
|
|
||||||
// Special-case empty input
|
// Special-case empty input
|
||||||
if (count == 0)
|
if (count == 0)
|
||||||
{
|
{
|
||||||
return new byte[0];
|
return EmptyBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create array large enough for the Base64 characters, not just shorter Base64-URL-encoded form.
|
||||||
|
var buffer = new char[GetArraySizeRequiredToDecode(count)];
|
||||||
|
|
||||||
|
return Base64UrlDecode(input, offset, buffer, bufferOffset: 0, count: count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decodes a base64url-encoded <paramref name="input"/> into a <c>byte[]</c>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">A string containing the base64url-encoded input to decode.</param>
|
||||||
|
/// <param name="offset">The position in <paramref name="input"/> at which decoding should begin.</param>
|
||||||
|
/// <param name="buffer">
|
||||||
|
/// Scratch buffer to hold the <see cref="char"/>s to decode. Array must be large enough to hold
|
||||||
|
/// <paramref name="bufferOffset"/> and <paramref name="count"/> characters as well as Base64 padding
|
||||||
|
/// characters. Content is not preserved.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="bufferOffset">
|
||||||
|
/// The offset into <paramref name="buffer"/> at which to begin writing the <see cref="char"/>s to decode.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="count">The number of characters in <paramref name="input"/> to decode.</param>
|
||||||
|
/// <returns>The base64url-decoded form of the <paramref name="input"/>.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// The input must not contain any whitespace or padding characters.
|
||||||
|
/// Throws <see cref="FormatException"/> if the input is malformed.
|
||||||
|
/// </remarks>
|
||||||
|
public static byte[] Base64UrlDecode(string input, int offset, char[] buffer, int bufferOffset, int count)
|
||||||
|
{
|
||||||
|
if (input == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(input));
|
||||||
|
}
|
||||||
|
if (buffer == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
ValidateParameters(input.Length, nameof(input), offset, count);
|
||||||
|
if (bufferOffset < 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(bufferOffset));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count == 0)
|
||||||
|
{
|
||||||
|
return EmptyBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assumption: input is base64url encoded without padding and contains no whitespace.
|
// Assumption: input is base64url encoded without padding and contains no whitespace.
|
||||||
|
|
||||||
// First, we need to add the padding characters back.
|
var paddingCharsToAdd = GetNumBase64PaddingCharsToAddForDecode(count);
|
||||||
int numPaddingCharsToAdd = GetNumBase64PaddingCharsToAddForDecode(count);
|
var arraySizeRequired = checked(count + paddingCharsToAdd);
|
||||||
char[] completeBase64Array = new char[checked(count + numPaddingCharsToAdd)];
|
Debug.Assert(arraySizeRequired % 4 == 0, "Invariant: Array length must be a multiple of 4.");
|
||||||
Debug.Assert(completeBase64Array.Length % 4 == 0, "Invariant: Array length must be a multiple of 4.");
|
|
||||||
input.CopyTo(offset, completeBase64Array, 0, count);
|
if (buffer.Length - bufferOffset < arraySizeRequired)
|
||||||
for (int i = 1; i <= numPaddingCharsToAdd; i++)
|
|
||||||
{
|
{
|
||||||
completeBase64Array[completeBase64Array.Length - i] = '=';
|
throw new ArgumentException(
|
||||||
|
string.Format(
|
||||||
|
CultureInfo.CurrentCulture,
|
||||||
|
Resources.WebEncoders_InvalidCountOffsetOrLength,
|
||||||
|
nameof(count),
|
||||||
|
nameof(bufferOffset),
|
||||||
|
nameof(input)),
|
||||||
|
nameof(count));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next, fix up '-' -> '+' and '_' -> '/'
|
// Copy input into buffer, fixing up '-' -> '+' and '_' -> '/'.
|
||||||
for (int i = 0; i < completeBase64Array.Length; i++)
|
var i = bufferOffset;
|
||||||
|
for (var j = offset; i - bufferOffset < count; i++, j++)
|
||||||
{
|
{
|
||||||
char c = completeBase64Array[i];
|
var ch = input[j];
|
||||||
if (c == '-')
|
if (ch == '-')
|
||||||
{
|
{
|
||||||
completeBase64Array[i] = '+';
|
buffer[i] = '+';
|
||||||
}
|
}
|
||||||
else if (c == '_')
|
else if (ch == '_')
|
||||||
{
|
{
|
||||||
completeBase64Array[i] = '/';
|
buffer[i] = '/';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
buffer[i] = ch;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally, decode.
|
// Add the padding characters back.
|
||||||
|
for (; paddingCharsToAdd > 0; i++, paddingCharsToAdd--)
|
||||||
|
{
|
||||||
|
buffer[i] = '=';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode.
|
||||||
// If the caller provided invalid base64 chars, they'll be caught here.
|
// If the caller provided invalid base64 chars, they'll be caught here.
|
||||||
return Convert.FromBase64CharArray(completeBase64Array, 0, completeBase64Array.Length);
|
return Convert.FromBase64CharArray(buffer, bufferOffset, arraySizeRequired);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Encodes an input using base64url encoding.
|
/// Gets the minimum <c>char[]</c> size required for decoding of <paramref name="count"/> characters
|
||||||
|
/// with the <see cref="Base64UrlDecode(char[], int, int)"/> method.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="count">The number of characters to decode.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// The minimum <c>char[]</c> size required for decoding of <paramref name="count"/> characters.
|
||||||
|
/// </returns>
|
||||||
|
public static int GetArraySizeRequiredToDecode(int count)
|
||||||
|
{
|
||||||
|
if (count < 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(count));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var numPaddingCharsToAdd = GetNumBase64PaddingCharsToAddForDecode(count);
|
||||||
|
|
||||||
|
return checked(count + numPaddingCharsToAdd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Encodes <paramref name="input"/> using base64url encoding.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="input">The binary input to encode.</param>
|
/// <param name="input">The binary input to encode.</param>
|
||||||
/// <returns>The base64url-encoded form of the input.</returns>
|
/// <returns>The base64url-encoded form of <paramref name="input"/>.</returns>
|
||||||
public static string Base64UrlEncode(byte[] input)
|
public static string Base64UrlEncode(byte[] input)
|
||||||
{
|
{
|
||||||
if (input == null)
|
if (input == null)
|
||||||
|
|
@ -99,16 +191,16 @@ namespace Microsoft.AspNetCore.WebUtilities
|
||||||
throw new ArgumentNullException(nameof(input));
|
throw new ArgumentNullException(nameof(input));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Base64UrlEncode(input, 0, input.Length);
|
return Base64UrlEncode(input, offset: 0, count: input.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Encodes an input using base64url encoding.
|
/// Encodes <paramref name="input"/> using base64url encoding.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="input">The binary input to encode.</param>
|
/// <param name="input">The binary input to encode.</param>
|
||||||
/// <param name="offset">The offset into <paramref name="input"/> at which to begin encoding.</param>
|
/// <param name="offset">The offset into <paramref name="input"/> at which to begin encoding.</param>
|
||||||
/// <param name="count">The number of bytes of <paramref name="input"/> to encode.</param>
|
/// <param name="count">The number of bytes from <paramref name="input"/> to encode.</param>
|
||||||
/// <returns>The base64url-encoded form of the input.</returns>
|
/// <returns>The base64url-encoded form of <paramref name="input"/>.</returns>
|
||||||
public static string Base64UrlEncode(byte[] input, int offset, int count)
|
public static string Base64UrlEncode(byte[] input, int offset, int count)
|
||||||
{
|
{
|
||||||
if (input == null)
|
if (input == null)
|
||||||
|
|
@ -116,7 +208,7 @@ namespace Microsoft.AspNetCore.WebUtilities
|
||||||
throw new ArgumentNullException(nameof(input));
|
throw new ArgumentNullException(nameof(input));
|
||||||
}
|
}
|
||||||
|
|
||||||
ValidateParameters(input.Length, offset, count);
|
ValidateParameters(input.Length, nameof(input), offset, count);
|
||||||
|
|
||||||
// Special-case empty input
|
// Special-case empty input
|
||||||
if (count == 0)
|
if (count == 0)
|
||||||
|
|
@ -124,38 +216,104 @@ namespace Microsoft.AspNetCore.WebUtilities
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We're going to use base64url encoding with no padding characters.
|
var buffer = new char[GetArraySizeRequiredToEncode(count)];
|
||||||
// See RFC 4648, Sec. 5.
|
var numBase64Chars = Base64UrlEncode(input, offset, buffer, outputOffset: 0, count: count);
|
||||||
char[] buffer = new char[GetNumBase64CharsRequiredForInput(count)];
|
|
||||||
int numBase64Chars = Convert.ToBase64CharArray(input, offset, count, buffer, 0);
|
|
||||||
|
|
||||||
// Fix up '+' -> '-' and '/' -> '_'
|
return new String(buffer, startIndex: 0, length: numBase64Chars);
|
||||||
for (int i = 0; i < numBase64Chars; i++)
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Encodes <paramref name="input"/> using base64url encoding.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">The binary input to encode.</param>
|
||||||
|
/// <param name="offset">The offset into <paramref name="input"/> at which to begin encoding.</param>
|
||||||
|
/// <param name="output">
|
||||||
|
/// Buffer to receive the base64url-encoded form of <paramref name="input"/>. Array must be large enough to
|
||||||
|
/// hold <paramref name="outputOffset"/> characters and the full base64-encoded form of
|
||||||
|
/// <paramref name="input"/>, including padding characters.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="outputOffset">
|
||||||
|
/// The offset into <paramref name="output"/> at which to begin writing the base64url-encoded form of
|
||||||
|
/// <paramref name="input"/>.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="count">The number of <c>byte</c>s from <paramref name="input"/> to encode.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// The number of characters written to <paramref name="output"/>, less any padding characters.
|
||||||
|
/// </returns>
|
||||||
|
public static int Base64UrlEncode(byte[] input, int offset, char[] output, int outputOffset, int count)
|
||||||
|
{
|
||||||
|
if (input == null)
|
||||||
{
|
{
|
||||||
char ch = buffer[i];
|
throw new ArgumentNullException(nameof(input));
|
||||||
|
}
|
||||||
|
if (output == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(output));
|
||||||
|
}
|
||||||
|
|
||||||
|
ValidateParameters(input.Length, nameof(input), offset, count);
|
||||||
|
if (outputOffset < 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(outputOffset));
|
||||||
|
}
|
||||||
|
|
||||||
|
var arraySizeRequired = GetArraySizeRequiredToEncode(count);
|
||||||
|
if (output.Length - outputOffset < arraySizeRequired)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(
|
||||||
|
string.Format(
|
||||||
|
CultureInfo.CurrentCulture,
|
||||||
|
Resources.WebEncoders_InvalidCountOffsetOrLength,
|
||||||
|
nameof(count),
|
||||||
|
nameof(outputOffset),
|
||||||
|
nameof(output)),
|
||||||
|
nameof(count));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special-case empty input.
|
||||||
|
if (count == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use base64url encoding with no padding characters. See RFC 4648, Sec. 5.
|
||||||
|
|
||||||
|
// Start with default Base64 encoding.
|
||||||
|
var numBase64Chars = Convert.ToBase64CharArray(input, offset, count, output, outputOffset);
|
||||||
|
|
||||||
|
// Fix up '+' -> '-' and '/' -> '_'. Drop padding characters.
|
||||||
|
for (var i = outputOffset; i - outputOffset < numBase64Chars; i++)
|
||||||
|
{
|
||||||
|
var ch = output[i];
|
||||||
if (ch == '+')
|
if (ch == '+')
|
||||||
{
|
{
|
||||||
buffer[i] = '-';
|
output[i] = '-';
|
||||||
}
|
}
|
||||||
else if (ch == '/')
|
else if (ch == '/')
|
||||||
{
|
{
|
||||||
buffer[i] = '_';
|
output[i] = '_';
|
||||||
}
|
}
|
||||||
else if (ch == '=')
|
else if (ch == '=')
|
||||||
{
|
{
|
||||||
// We've reached a padding character: truncate the string from this point
|
// We've reached a padding character; truncate the remainder.
|
||||||
return new String(buffer, 0, i);
|
return i - outputOffset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we got this far, the buffer didn't contain any padding chars, so turn
|
return numBase64Chars;
|
||||||
// it directly into a string.
|
|
||||||
return new String(buffer, 0, numBase64Chars);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int GetNumBase64CharsRequiredForInput(int inputLength)
|
/// <summary>
|
||||||
|
/// Get the minimum output <c>char[]</c> size required for encoding <paramref name="count"/>
|
||||||
|
/// <see cref="byte"/>s with the <see cref="Base64UrlEncode(byte[], int, int, char[], int)"/> method.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="count">The number of characters to encode.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// The minimum output <c>char[]</c> size required for encoding <paramref name="count"/> <see cref="byte"/>s.
|
||||||
|
/// </returns>
|
||||||
|
public static int GetArraySizeRequiredToEncode(int count)
|
||||||
{
|
{
|
||||||
int numWholeOrPartialInputBlocks = checked(inputLength + 2) / 3;
|
var numWholeOrPartialInputBlocks = checked(count + 2) / 3;
|
||||||
return checked(numWholeOrPartialInputBlocks * 4);
|
return checked(numWholeOrPartialInputBlocks * 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -190,7 +348,7 @@ namespace Microsoft.AspNetCore.WebUtilities
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ValidateParameters(int bufferLength, int offset, int count)
|
private static void ValidateParameters(int bufferLength, string inputName, int offset, int count)
|
||||||
{
|
{
|
||||||
if (offset < 0)
|
if (offset < 0)
|
||||||
{
|
{
|
||||||
|
|
@ -202,7 +360,14 @@ namespace Microsoft.AspNetCore.WebUtilities
|
||||||
}
|
}
|
||||||
if (bufferLength - offset < count)
|
if (bufferLength - offset < count)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Invalid offset / length.");
|
throw new ArgumentException(
|
||||||
|
string.Format(
|
||||||
|
CultureInfo.CurrentCulture,
|
||||||
|
Resources.WebEncoders_InvalidCountOffsetOrLength,
|
||||||
|
nameof(count),
|
||||||
|
nameof(offset),
|
||||||
|
inputName),
|
||||||
|
nameof(count));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,41 @@ namespace Microsoft.AspNetCore.WebUtilities
|
||||||
Assert.Equal(roundTrippedAsBase64, base64Input);
|
Assert.Equal(roundTrippedAsBase64, base64Input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("", "")]
|
||||||
|
[InlineData("123456qwerty++//X+/x", "123456qwerty--__X-_x")]
|
||||||
|
[InlineData("123456qwerty++//X+/xxw==", "123456qwerty--__X-_xxw")]
|
||||||
|
[InlineData("123456qwerty++//X+/xxw0=", "123456qwerty--__X-_xxw0")]
|
||||||
|
public void Base64UrlEncode_And_Decode_WithBufferOffsets(string base64Input, string expectedBase64Url)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var input = new byte[3].Concat(Convert.FromBase64String(base64Input)).Concat(new byte[2]).ToArray();
|
||||||
|
var buffer = new char[30];
|
||||||
|
var output = new char[30];
|
||||||
|
for (var i = 0; i < buffer.Length; i++)
|
||||||
|
{
|
||||||
|
buffer[i] = '^';
|
||||||
|
output[i] = '^';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Act 1
|
||||||
|
var numEncodedChars =
|
||||||
|
WebEncoders.Base64UrlEncode(input, offset: 3, output: output, outputOffset: 4, count: input.Length - 5);
|
||||||
|
|
||||||
|
// Assert 1
|
||||||
|
var encodedString = new string(output, startIndex: 4, length: numEncodedChars);
|
||||||
|
Assert.Equal(expectedBase64Url, encodedString);
|
||||||
|
|
||||||
|
// Act 2
|
||||||
|
var roundTripInput = new string(output);
|
||||||
|
var roundTripped =
|
||||||
|
WebEncoders.Base64UrlDecode(roundTripInput, offset: 4, buffer: buffer, bufferOffset: 5, count: numEncodedChars);
|
||||||
|
|
||||||
|
// Assert 2, verify that values round-trip
|
||||||
|
var roundTrippedAsBase64 = Convert.ToBase64String(roundTripped);
|
||||||
|
Assert.Equal(roundTrippedAsBase64, base64Input);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(0, 1, 0)]
|
[InlineData(0, 1, 0)]
|
||||||
[InlineData(0, 0, 1)]
|
[InlineData(0, 0, 1)]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue