From 478edc1735716993cb9771f5640a46bb418f6e88 Mon Sep 17 00:00:00 2001 From: Doug Bunting Date: Wed, 17 Feb 2016 23:17:15 -0800 Subject: [PATCH] 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 --- .../AntiforgerySerializationContext.cs | 58 ++++++++++++++----- .../DefaultAntiforgeryTokenSerializer.cs | 27 ++++++++- 2 files changed, 69 insertions(+), 16 deletions(-) diff --git a/src/Microsoft.AspNetCore.Antiforgery/Internal/AntiforgerySerializationContext.cs b/src/Microsoft.AspNetCore.Antiforgery/Internal/AntiforgerySerializationContext.cs index 27f7fa3725..5347f6651a 100644 --- a/src/Microsoft.AspNetCore.Antiforgery/Internal/AntiforgerySerializationContext.cs +++ b/src/Microsoft.AspNetCore.Antiforgery/Internal/AntiforgerySerializationContext.cs @@ -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); + } } } } -} +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Antiforgery/Internal/DefaultAntiforgeryTokenSerializer.cs b/src/Microsoft.AspNetCore.Antiforgery/Internal/DefaultAntiforgeryTokenSerializer.cs index 216f79ae09..474b2fd7a9 100644 --- a/src/Microsoft.AspNetCore.Antiforgery/Internal/DefaultAntiforgeryTokenSerializer.cs +++ b/src/Microsoft.AspNetCore.Antiforgery/Internal/DefaultAntiforgeryTokenSerializer.cs @@ -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 {