Spanified Webencoders.Base64UrlEncode (#11047)

This commit is contained in:
Günther Foidl 2019-06-11 03:18:32 +02:00 committed by Brennan
parent 4d1262c39b
commit 90ab2cb965
3 changed files with 80 additions and 4 deletions

View File

@ -233,6 +233,7 @@ namespace Microsoft.AspNetCore.WebUtilities
public static string Base64UrlEncode(byte[] input) { throw null; }
public static int Base64UrlEncode(byte[] input, int offset, char[] output, int outputOffset, int count) { throw null; }
public static string Base64UrlEncode(byte[] input, int offset, int count) { throw null; }
public static string Base64UrlEncode(System.ReadOnlySpan<byte> input) { throw null; }
public static int GetArraySizeRequiredToDecode(int count) { throw null; }
public static int GetArraySizeRequiredToEncode(int count) { throw null; }
}

View File

@ -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.Buffers;
using System.Diagnostics;
using System.Globalization;
using Microsoft.Extensions.WebEncoders.Sources;
@ -220,6 +221,9 @@ namespace Microsoft.Extensions.Internal
ValidateParameters(input.Length, nameof(input), offset, count);
#if NETCOREAPP3_0
return Base64UrlEncode(input.AsSpan(offset, count));
#else
// Special-case empty input
if (count == 0)
{
@ -229,7 +233,8 @@ namespace Microsoft.Extensions.Internal
var buffer = new char[GetArraySizeRequiredToEncode(count)];
var numBase64Chars = Base64UrlEncode(input, offset, buffer, outputOffset: 0, count: count);
return new String(buffer, startIndex: 0, length: numBase64Chars);
return new string(buffer, startIndex: 0, length: numBase64Chars);
#endif
}
/// <summary>
@ -280,6 +285,9 @@ namespace Microsoft.Extensions.Internal
nameof(count));
}
#if NETCOREAPP3_0
return Base64UrlEncode(input.AsSpan(offset, count), output.AsSpan(outputOffset));
#else
// Special-case empty input.
if (count == 0)
{
@ -311,6 +319,7 @@ namespace Microsoft.Extensions.Internal
}
return numBase64Chars;
#endif
}
/// <summary>
@ -327,6 +336,73 @@ namespace Microsoft.Extensions.Internal
return checked(numWholeOrPartialInputBlocks * 4);
}
#if NETCOREAPP3_0
/// <summary>
/// Encodes <paramref name="input"/> using base64url encoding.
/// </summary>
/// <param name="input">The binary input to encode.</param>
/// <returns>The base64url-encoded form of <paramref name="input"/>.</returns>
public static string Base64UrlEncode(ReadOnlySpan<byte> input)
{
if (input.IsEmpty)
{
return string.Empty;
}
int bufferSize = GetArraySizeRequiredToEncode(input.Length);
char[] bufferToReturnToPool = null;
Span<char> buffer = bufferSize <= 128
? stackalloc char[bufferSize]
: bufferToReturnToPool = ArrayPool<char>.Shared.Rent(bufferSize);
var numBase64Chars = Base64UrlEncode(input, buffer);
var base64Url = new string(buffer.Slice(0, numBase64Chars));
if (bufferToReturnToPool != null)
{
ArrayPool<char>.Shared.Return(bufferToReturnToPool);
}
return base64Url;
}
private static int Base64UrlEncode(ReadOnlySpan<byte> input, Span<char> output)
{
Debug.Assert(output.Length >= GetArraySizeRequiredToEncode(input.Length));
if (input.IsEmpty)
{
return 0;
}
// Use base64url encoding with no padding characters. See RFC 4648, Sec. 5.
Convert.TryToBase64Chars(input, output, out int charsWritten);
// Fix up '+' -> '-' and '/' -> '_'. Drop padding characters.
for (var i = 0; i < charsWritten; i++)
{
var ch = output[i];
if (ch == '+')
{
output[i] = '-';
}
else if (ch == '/')
{
output[i] = '_';
}
else if (ch == '=')
{
// We've reached a padding character; truncate the remainder.
return i;
}
}
return charsWritten;
}
#endif
private static int GetNumBase64PaddingCharsInString(string str)
{
// Assumption: input contains a well-formed base64 string with no whitespace.

View File

@ -105,11 +105,10 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal
private static string MakeNewConnectionId()
{
// TODO: Use Span when WebEncoders implements Span methods https://github.com/aspnet/Home/issues/2966
// 128 bit buffer / 8 bits per byte = 16 bytes
var buffer = new byte[16];
_keyGenerator.GetBytes(buffer);
Span<byte> buffer = stackalloc byte[16];
// Generate the id with RNGCrypto because we want a cryptographically random id, which GUID is not
_keyGenerator.GetBytes(buffer);
return WebEncoders.Base64UrlEncode(buffer);
}