Modified Base64UrlTextEncoder to reduce string allocations.
This commit is contained in:
parent
4fbb0b01fc
commit
aa158f5d25
|
|
@ -2,6 +2,8 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.Extensions.Primitives;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.WebUtilities
|
namespace Microsoft.AspNetCore.WebUtilities
|
||||||
{
|
{
|
||||||
|
|
@ -15,7 +17,8 @@ namespace Microsoft.AspNetCore.WebUtilities
|
||||||
/// <returns>Base64 encoded string modified with non-URL encodable characters</returns>
|
/// <returns>Base64 encoded string modified with non-URL encodable characters</returns>
|
||||||
public static string Encode(byte[] data)
|
public static string Encode(byte[] data)
|
||||||
{
|
{
|
||||||
return Convert.ToBase64String(data).TrimEnd('=').Replace('+', '-').Replace('/', '_');
|
var encodedValue = Convert.ToBase64String(data);
|
||||||
|
return EncodeInternal(encodedValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -26,17 +29,76 @@ namespace Microsoft.AspNetCore.WebUtilities
|
||||||
/// <returns>The decoded data.</returns>
|
/// <returns>The decoded data.</returns>
|
||||||
public static byte[] Decode(string text)
|
public static byte[] Decode(string text)
|
||||||
{
|
{
|
||||||
return Convert.FromBase64String(Pad(text.Replace('-', '+').Replace('_', '/')));
|
return Convert.FromBase64String(DecodeToBase64String(text));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string Pad(string text)
|
// To enable unit testing
|
||||||
|
internal static string EncodeInternal(string base64EncodedString)
|
||||||
{
|
{
|
||||||
var padding = 3 - ((text.Length + 3) % 4);
|
var length = base64EncodedString.Length;
|
||||||
if (padding == 0)
|
while (length > 0 && base64EncodedString[length - 1] == '=')
|
||||||
|
{
|
||||||
|
length--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (length == 0)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
var inplaceStringBuilder = new InplaceStringBuilder(length);
|
||||||
|
for (var i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
if (base64EncodedString[i] == '+')
|
||||||
|
{
|
||||||
|
inplaceStringBuilder.Append('-');
|
||||||
|
}
|
||||||
|
else if (base64EncodedString[i] == '/')
|
||||||
|
{
|
||||||
|
inplaceStringBuilder.Append('_');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
inplaceStringBuilder.Append(base64EncodedString[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return inplaceStringBuilder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// To enable unit testing
|
||||||
|
internal static string DecodeToBase64String(string text)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(text))
|
||||||
{
|
{
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
return text + new string('=', padding);
|
|
||||||
|
var padLength = 3 - ((text.Length + 3) % 4);
|
||||||
|
var inplaceStringBuilder = new InplaceStringBuilder(capacity: text.Length + padLength);
|
||||||
|
|
||||||
|
for (var i = 0; i < text.Length; i++)
|
||||||
|
{
|
||||||
|
if (text[i] == '-')
|
||||||
|
{
|
||||||
|
inplaceStringBuilder.Append('+');
|
||||||
|
}
|
||||||
|
else if (text[i] == '_')
|
||||||
|
{
|
||||||
|
inplaceStringBuilder.Append('/');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
inplaceStringBuilder.Append(text[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < padLength; i++)
|
||||||
|
{
|
||||||
|
inplaceStringBuilder.Append('=');
|
||||||
|
}
|
||||||
|
|
||||||
|
return inplaceStringBuilder.ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,11 @@
|
||||||
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Resources;
|
using System.Resources;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
[assembly: AssemblyMetadata("Serviceable", "True")]
|
[assembly: AssemblyMetadata("Serviceable", "True")]
|
||||||
[assembly: NeutralResourcesLanguage("en-us")]
|
|
||||||
[assembly: AssemblyCompany("Microsoft Corporation.")]
|
[assembly: AssemblyCompany("Microsoft Corporation.")]
|
||||||
[assembly: AssemblyCopyright("© Microsoft Corporation. All rights reserved.")]
|
[assembly: AssemblyCopyright("© Microsoft Corporation. All rights reserved.")]
|
||||||
[assembly: AssemblyProduct("Microsoft ASP.NET Core")]
|
[assembly: AssemblyProduct("Microsoft ASP.NET Core")]
|
||||||
|
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.WebUtilities.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||||
|
[assembly: NeutralResourcesLanguage("en-us")]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.WebUtilities
|
namespace Microsoft.AspNetCore.WebUtilities
|
||||||
|
|
@ -26,5 +27,43 @@ namespace Microsoft.AspNetCore.WebUtilities
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("", "")]
|
||||||
|
[InlineData("+", "-")]
|
||||||
|
[InlineData("/", "_")]
|
||||||
|
[InlineData("=", "")]
|
||||||
|
[InlineData("==", "")]
|
||||||
|
[InlineData("a+b+c+==", "a-b-c-")]
|
||||||
|
[InlineData("a/b/c==", "a_b_c")]
|
||||||
|
[InlineData("a+b/c==", "a-b_c")]
|
||||||
|
[InlineData("a+b/c", "a-b_c")]
|
||||||
|
[InlineData("abcd", "abcd")]
|
||||||
|
public void EncodeInternal_Replaces_UrlEncodableCharacters(string base64EncodedValue, string expectedValue)
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var result = Base64UrlTextEncoder.EncodeInternal(base64EncodedValue);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expectedValue, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("_", "/===")]
|
||||||
|
[InlineData("-", "+===")]
|
||||||
|
[InlineData("a-b-c", "a+b+c===")]
|
||||||
|
[InlineData("a_b_c_d", "a/b/c/d=")]
|
||||||
|
[InlineData("a-b_c", "a+b/c===")]
|
||||||
|
[InlineData("a-b_c-d", "a+b/c+d=")]
|
||||||
|
[InlineData("a-b_c", "a+b/c===")]
|
||||||
|
[InlineData("abcd", "abcd")]
|
||||||
|
public void DecodeToBase64String_ReturnsValid_Base64String(string text, string expectedValue)
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var actual = Base64UrlTextEncoder.DecodeToBase64String(text);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expectedValue, actual);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5,7 +5,8 @@
|
||||||
"xunit": "2.2.0-*"
|
"xunit": "2.2.0-*"
|
||||||
},
|
},
|
||||||
"buildOptions": {
|
"buildOptions": {
|
||||||
"warningsAsErrors": true
|
"warningsAsErrors": true,
|
||||||
|
"keyFile": "../../tools/Key.snk"
|
||||||
},
|
},
|
||||||
"testRunner": "xunit",
|
"testRunner": "xunit",
|
||||||
"frameworks": {
|
"frameworks": {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue