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. // Don't let the MemoryStream grow beyond 1 MB.
private const int MaximumStreamSize = 0x100000; 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 BinaryReader _reader;
private BinaryWriter _writer; private BinaryWriter _writer;
private SHA256 _sha256; private SHA256 _sha256;
@ -25,16 +33,16 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
{ {
get get
{ {
if (_memory == null) if (_stream == null)
{ {
_memory = new MemoryStream(InitialStreamSize); _stream = new MemoryStream(InitialStreamSize);
} }
return _memory; return _stream;
} }
private set 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() public void Reset()
{ {
if (Stream.Capacity > MaximumStreamSize) if (_chars != null && _chars.Length > MaximumCharsLength)
{ {
Stream = null; _chars = null;
Reader = null;
Writer = null;
} }
else
if (_stream != null)
{ {
Stream.Position = 0L; if (Stream.Capacity > MaximumStreamSize)
Stream.SetLength(0L); {
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; Exception innerException = null;
try 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 unprotectedBytes = _cryptoSystem.Unprotect(tokenBytes);
var stream = serializationContext.Stream; var stream = serializationContext.Stream;
stream.Write(unprotectedBytes, 0, unprotectedBytes.Length); stream.Write(unprotectedBytes, offset: 0, count: unprotectedBytes.Length);
stream.Position = 0L; stream.Position = 0L;
var reader = serializationContext.Reader; var reader = serializationContext.Reader;
@ -156,7 +165,19 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
writer.Flush(); writer.Flush();
var stream = serializationContext.Stream; 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 finally
{ {