Pool `char`s used for base64url-encoding and -decoding

- #23 part 4
- depends on aspnet/HttpAbstractions@8c120a0

nits:
- correct name of a field in `AntiforgerySerializationContext`
- avoid allocations when returning an `AntiforgerySerializationContext` in (unlikely) case `Stream` is unused
- name literal `int` parameters
This commit is contained in:
Doug Bunting 2016-02-17 23:17:15 -08:00
parent c2f4bd0be5
commit 478edc1735
2 changed files with 69 additions and 16 deletions

View File

@ -16,7 +16,15 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
// Don't let the MemoryStream grow beyond 1 MB.
private const int MaximumStreamSize = 0x100000;
private MemoryStream _memory;
// Start _chars off with length 256 (18 bytes is protected into 116 bytes then encoded into 156 characters).
// Double length from there if necessary.
private const int InitialCharsLength = 256;
// Don't let _chars grow beyond 512k characters.
private const int MaximumCharsLength = 0x80000;
private char[] _chars;
private MemoryStream _stream;
private BinaryReader _reader;
private BinaryWriter _writer;
private SHA256 _sha256;
@ -25,16 +33,16 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
{
get
{
if (_memory == null)
if (_stream == null)
{
_memory = new MemoryStream(InitialStreamSize);
_stream = new MemoryStream(InitialStreamSize);
}
return _memory;
return _stream;
}
private set
{
_memory = value;
_stream = value;
}
}
@ -91,19 +99,43 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
}
}
public char[] GetChars(int count)
{
if (_chars == null || _chars.Length < count)
{
var newLength = _chars == null ? InitialCharsLength : checked(_chars.Length * 2);
while (newLength < count)
{
newLength = checked(newLength * 2);
}
_chars = new char[newLength];
}
return _chars;
}
public void Reset()
{
if (Stream.Capacity > MaximumStreamSize)
if (_chars != null && _chars.Length > MaximumCharsLength)
{
Stream = null;
Reader = null;
Writer = null;
_chars = null;
}
else
if (_stream != null)
{
Stream.Position = 0L;
Stream.SetLength(0L);
if (Stream.Capacity > MaximumStreamSize)
{
Stream = null;
Reader = null;
Writer = null;
}
else
{
Stream.Position = 0L;
Stream.SetLength(0L);
}
}
}
}
}
}

View File

@ -42,10 +42,19 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
Exception innerException = null;
try
{
var tokenBytes = WebEncoders.Base64UrlDecode(serializedToken);
var count = serializedToken.Length;
var charsRequired = WebEncoders.GetArraySizeRequiredToDecode(count);
var chars = serializationContext.GetChars(charsRequired);
var tokenBytes = WebEncoders.Base64UrlDecode(
serializedToken,
offset: 0,
buffer: chars,
bufferOffset: 0,
count: count);
var unprotectedBytes = _cryptoSystem.Unprotect(tokenBytes);
var stream = serializationContext.Stream;
stream.Write(unprotectedBytes, 0, unprotectedBytes.Length);
stream.Write(unprotectedBytes, offset: 0, count: unprotectedBytes.Length);
stream.Position = 0L;
var reader = serializationContext.Reader;
@ -156,7 +165,19 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
writer.Flush();
var stream = serializationContext.Stream;
return WebEncoders.Base64UrlEncode(_cryptoSystem.Protect(stream.ToArray()));
var bytes = _cryptoSystem.Protect(stream.ToArray());
var count = bytes.Length;
var charsRequired = WebEncoders.GetArraySizeRequiredToEncode(count);
var chars = serializationContext.GetChars(charsRequired);
var outputLength = WebEncoders.Base64UrlEncode(
bytes,
offset: 0,
output: chars,
outputOffset: 0,
count: count);
return new string(chars, startIndex: 0, length: outputLength);
}
finally
{