From 90ab2cb965aeb8ada13bc4b936b3735ca8dd28df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnther=20Foidl?= Date: Tue, 11 Jun 2019 03:18:32 +0200 Subject: [PATCH] Spanified Webencoders.Base64UrlEncode (#11047) --- ...t.AspNetCore.WebUtilities.netcoreapp3.0.cs | 1 + src/Shared/WebEncoders/WebEncoders.cs | 78 ++++++++++++++++++- .../src/Internal/HttpConnectionManager.cs | 5 +- 3 files changed, 80 insertions(+), 4 deletions(-) diff --git a/src/Http/WebUtilities/ref/Microsoft.AspNetCore.WebUtilities.netcoreapp3.0.cs b/src/Http/WebUtilities/ref/Microsoft.AspNetCore.WebUtilities.netcoreapp3.0.cs index afdad71011..3c502d7d4b 100644 --- a/src/Http/WebUtilities/ref/Microsoft.AspNetCore.WebUtilities.netcoreapp3.0.cs +++ b/src/Http/WebUtilities/ref/Microsoft.AspNetCore.WebUtilities.netcoreapp3.0.cs @@ -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 input) { throw null; } public static int GetArraySizeRequiredToDecode(int count) { throw null; } public static int GetArraySizeRequiredToEncode(int count) { throw null; } } diff --git a/src/Shared/WebEncoders/WebEncoders.cs b/src/Shared/WebEncoders/WebEncoders.cs index 17068ae67a..979ab5d2d7 100644 --- a/src/Shared/WebEncoders/WebEncoders.cs +++ b/src/Shared/WebEncoders/WebEncoders.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.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 } /// @@ -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 } /// @@ -327,6 +336,73 @@ namespace Microsoft.Extensions.Internal return checked(numWholeOrPartialInputBlocks * 4); } +#if NETCOREAPP3_0 + /// + /// Encodes using base64url encoding. + /// + /// The binary input to encode. + /// The base64url-encoded form of . + public static string Base64UrlEncode(ReadOnlySpan input) + { + if (input.IsEmpty) + { + return string.Empty; + } + + int bufferSize = GetArraySizeRequiredToEncode(input.Length); + + char[] bufferToReturnToPool = null; + Span buffer = bufferSize <= 128 + ? stackalloc char[bufferSize] + : bufferToReturnToPool = ArrayPool.Shared.Rent(bufferSize); + + var numBase64Chars = Base64UrlEncode(input, buffer); + var base64Url = new string(buffer.Slice(0, numBase64Chars)); + + if (bufferToReturnToPool != null) + { + ArrayPool.Shared.Return(bufferToReturnToPool); + } + + return base64Url; + } + + private static int Base64UrlEncode(ReadOnlySpan input, Span 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. diff --git a/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionManager.cs b/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionManager.cs index 283bee8d98..206f352acc 100644 --- a/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionManager.cs +++ b/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionManager.cs @@ -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 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); }