Skip to content

Commit 90ab2cb

Browse files
gfoidlBrennanConroy
authored andcommitted
Spanified Webencoders.Base64UrlEncode (#11047)
1 parent 4d1262c commit 90ab2cb

File tree

3 files changed

+80
-4
lines changed

3 files changed

+80
-4
lines changed

src/Http/WebUtilities/ref/Microsoft.AspNetCore.WebUtilities.netcoreapp3.0.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ public static partial class WebEncoders
233233
public static string Base64UrlEncode(byte[] input) { throw null; }
234234
public static int Base64UrlEncode(byte[] input, int offset, char[] output, int outputOffset, int count) { throw null; }
235235
public static string Base64UrlEncode(byte[] input, int offset, int count) { throw null; }
236+
public static string Base64UrlEncode(System.ReadOnlySpan<byte> input) { throw null; }
236237
public static int GetArraySizeRequiredToDecode(int count) { throw null; }
237238
public static int GetArraySizeRequiredToEncode(int count) { throw null; }
238239
}

src/Shared/WebEncoders/WebEncoders.cs

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Buffers;
56
using System.Diagnostics;
67
using System.Globalization;
78
using Microsoft.Extensions.WebEncoders.Sources;
@@ -220,6 +221,9 @@ public static string Base64UrlEncode(byte[] input, int offset, int count)
220221

221222
ValidateParameters(input.Length, nameof(input), offset, count);
222223

224+
#if NETCOREAPP3_0
225+
return Base64UrlEncode(input.AsSpan(offset, count));
226+
#else
223227
// Special-case empty input
224228
if (count == 0)
225229
{
@@ -229,7 +233,8 @@ public static string Base64UrlEncode(byte[] input, int offset, int count)
229233
var buffer = new char[GetArraySizeRequiredToEncode(count)];
230234
var numBase64Chars = Base64UrlEncode(input, offset, buffer, outputOffset: 0, count: count);
231235

232-
return new String(buffer, startIndex: 0, length: numBase64Chars);
236+
return new string(buffer, startIndex: 0, length: numBase64Chars);
237+
#endif
233238
}
234239

235240
/// <summary>
@@ -280,6 +285,9 @@ public static int Base64UrlEncode(byte[] input, int offset, char[] output, int o
280285
nameof(count));
281286
}
282287

288+
#if NETCOREAPP3_0
289+
return Base64UrlEncode(input.AsSpan(offset, count), output.AsSpan(outputOffset));
290+
#else
283291
// Special-case empty input.
284292
if (count == 0)
285293
{
@@ -311,6 +319,7 @@ public static int Base64UrlEncode(byte[] input, int offset, char[] output, int o
311319
}
312320

313321
return numBase64Chars;
322+
#endif
314323
}
315324

316325
/// <summary>
@@ -327,6 +336,73 @@ public static int GetArraySizeRequiredToEncode(int count)
327336
return checked(numWholeOrPartialInputBlocks * 4);
328337
}
329338

339+
#if NETCOREAPP3_0
340+
/// <summary>
341+
/// Encodes <paramref name="input"/> using base64url encoding.
342+
/// </summary>
343+
/// <param name="input">The binary input to encode.</param>
344+
/// <returns>The base64url-encoded form of <paramref name="input"/>.</returns>
345+
public static string Base64UrlEncode(ReadOnlySpan<byte> input)
346+
{
347+
if (input.IsEmpty)
348+
{
349+
return string.Empty;
350+
}
351+
352+
int bufferSize = GetArraySizeRequiredToEncode(input.Length);
353+
354+
char[] bufferToReturnToPool = null;
355+
Span<char> buffer = bufferSize <= 128
356+
? stackalloc char[bufferSize]
357+
: bufferToReturnToPool = ArrayPool<char>.Shared.Rent(bufferSize);
358+
359+
var numBase64Chars = Base64UrlEncode(input, buffer);
360+
var base64Url = new string(buffer.Slice(0, numBase64Chars));
361+
362+
if (bufferToReturnToPool != null)
363+
{
364+
ArrayPool<char>.Shared.Return(bufferToReturnToPool);
365+
}
366+
367+
return base64Url;
368+
}
369+
370+
private static int Base64UrlEncode(ReadOnlySpan<byte> input, Span<char> output)
371+
{
372+
Debug.Assert(output.Length >= GetArraySizeRequiredToEncode(input.Length));
373+
374+
if (input.IsEmpty)
375+
{
376+
return 0;
377+
}
378+
379+
// Use base64url encoding with no padding characters. See RFC 4648, Sec. 5.
380+
381+
Convert.TryToBase64Chars(input, output, out int charsWritten);
382+
383+
// Fix up '+' -> '-' and '/' -> '_'. Drop padding characters.
384+
for (var i = 0; i < charsWritten; i++)
385+
{
386+
var ch = output[i];
387+
if (ch == '+')
388+
{
389+
output[i] = '-';
390+
}
391+
else if (ch == '/')
392+
{
393+
output[i] = '_';
394+
}
395+
else if (ch == '=')
396+
{
397+
// We've reached a padding character; truncate the remainder.
398+
return i;
399+
}
400+
}
401+
402+
return charsWritten;
403+
}
404+
#endif
405+
330406
private static int GetNumBase64PaddingCharsInString(string str)
331407
{
332408
// Assumption: input contains a well-formed base64 string with no whitespace.

src/SignalR/common/Http.Connections/src/Internal/HttpConnectionManager.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,10 @@ public void RemoveConnection(string id)
105105

106106
private static string MakeNewConnectionId()
107107
{
108-
// TODO: Use Span when WebEncoders implements Span methods https://github.com/aspnet/Home/issues/2966
109108
// 128 bit buffer / 8 bits per byte = 16 bytes
110-
var buffer = new byte[16];
111-
_keyGenerator.GetBytes(buffer);
109+
Span<byte> buffer = stackalloc byte[16];
112110
// Generate the id with RNGCrypto because we want a cryptographically random id, which GUID is not
111+
_keyGenerator.GetBytes(buffer);
113112
return WebEncoders.Base64UrlEncode(buffer);
114113
}
115114

0 commit comments

Comments
 (0)