11// Licensed to the .NET Foundation under one or more agreements.
22// The .NET Foundation licenses this file to you under the MIT license.
33
4+ using System . Buffers ;
5+ using System . Diagnostics ;
46using Microsoft . AspNetCore . DataProtection ;
57using Microsoft . AspNetCore . WebUtilities ;
68using Microsoft . Extensions . ObjectPool ;
@@ -30,26 +32,30 @@ public AntiforgeryToken Deserialize(string serializedToken)
3032 {
3133 var serializationContext = _pool . Get ( ) ;
3234
35+ byte [ ] ? rented = null ;
3336 Exception ? innerException = null ;
3437 try
3538 {
36- var count = serializedToken . Length ;
37- var charsRequired = WebEncoders . GetArraySizeRequiredToDecode ( count ) ;
38- var chars = serializationContext . GetChars ( charsRequired ) ;
39- var tokenBytes = WebEncoders . Base64UrlDecode (
39+ var tokenLength = serializedToken . Length ;
40+ var charsRequired = WebEncoders . GetArraySizeRequiredToDecode ( tokenLength ) ;
41+
42+ var chars = charsRequired < 128 ? stackalloc byte [ 128 ] : ( rented = ArrayPool < byte > . Shared . Rent ( charsRequired ) ) ;
43+ chars = chars [ ..charsRequired ] ;
44+
45+ var decodedResult = WebEncoders . TryBase64UrlDecode (
4046 serializedToken ,
4147 offset : 0 ,
42- buffer : chars ,
43- bufferOffset : 0 ,
44- count : count ) ;
45-
48+ count : tokenLength ,
49+ destination : chars ,
50+ out var bytesWritten ) ;
51+ Debug . Assert ( decodedResult is true ) ;
52+
53+ // when DataProtection obtains Span<>'ish APIs,
54+ // we will just pass in chars directly. For now we need to allocate.
55+ var tokenBytes = chars . Slice ( 0 , bytesWritten ) . ToArray ( ) ;
4656 var unprotectedBytes = _cryptoSystem . Unprotect ( tokenBytes ) ;
47- var stream = serializationContext . Stream ;
48- stream . Write ( unprotectedBytes , offset : 0 , count : unprotectedBytes . Length ) ;
49- stream . Position = 0L ;
5057
51- var reader = serializationContext . Reader ;
52- var token = Deserialize ( reader ) ;
58+ var token = Deserialize ( unprotectedBytes ) ;
5359 if ( token != null )
5460 {
5561 return token ;
@@ -63,6 +69,11 @@ public AntiforgeryToken Deserialize(string serializedToken)
6369 finally
6470 {
6571 _pool . Return ( serializationContext ) ;
72+
73+ if ( rented is not null )
74+ {
75+ ArrayPool < byte > . Shared . Return ( rented ) ;
76+ }
6677 }
6778
6879 // if we reached this point, something went wrong deserializing
@@ -81,39 +92,49 @@ public AntiforgeryToken Deserialize(string serializedToken)
8192 * | `- Username: UTF-8 string with 7-bit integer length prefix
8293 * `- AdditionalData: UTF-8 string with 7-bit integer length prefix
8394 */
84- private static AntiforgeryToken ? Deserialize ( BinaryReader reader )
95+ private static AntiforgeryToken ? Deserialize ( ReadOnlySpan < byte > unprotectedBytes )
8596 {
97+ // pointer to current byte at unprotectedBytes
98+ var pointer = 0 ;
99+
86100 // we can only consume tokens of the same serialized version that we generate
87- var embeddedVersion = reader . ReadByte ( ) ;
101+ var embeddedVersion = unprotectedBytes [ pointer ++ ] ;
88102 if ( embeddedVersion != TokenVersion )
89103 {
90104 return null ;
91105 }
92106
93107 var deserializedToken = new AntiforgeryToken ( ) ;
94- var securityTokenBytes = reader . ReadBytes ( AntiforgeryToken . SecurityTokenBitLength / 8 ) ;
95- deserializedToken . SecurityToken =
96- new BinaryBlob ( AntiforgeryToken . SecurityTokenBitLength , securityTokenBytes ) ;
97- deserializedToken . IsCookieToken = reader . ReadBoolean ( ) ;
98108
109+ var securityTokenBytesLength = AntiforgeryToken . SecurityTokenBitLength / 8 ;
110+ var securityTokenBytes = unprotectedBytes . Slice ( pointer , securityTokenBytesLength ) . ToArray ( ) ;
111+ pointer += securityTokenBytesLength ;
112+ deserializedToken . SecurityToken = new BinaryBlob ( AntiforgeryToken . SecurityTokenBitLength , securityTokenBytes ) ;
113+
114+ deserializedToken . IsCookieToken = unprotectedBytes . BinaryReadBoolean ( ref pointer ) ;
99115 if ( ! deserializedToken . IsCookieToken )
100116 {
101- var isClaimsBased = reader . ReadBoolean ( ) ;
117+ var isClaimsBased = unprotectedBytes . BinaryReadBoolean ( ref pointer ) ;
102118 if ( isClaimsBased )
103119 {
104- var claimUidBytes = reader . ReadBytes ( AntiforgeryToken . ClaimUidBitLength / 8 ) ;
120+ var claimUidBytesLength = AntiforgeryToken . ClaimUidBitLength / 8 ;
121+ var claimUidBytes = unprotectedBytes . Slice ( pointer , claimUidBytesLength ) . ToArray ( ) ;
122+ pointer += claimUidBytesLength ;
105123 deserializedToken . ClaimUid = new BinaryBlob ( AntiforgeryToken . ClaimUidBitLength , claimUidBytes ) ;
106124 }
107125 else
108126 {
109- deserializedToken . Username = reader . ReadString ( ) ;
127+ deserializedToken . Username = unprotectedBytes . Slice ( pointer ) . BinaryReadString ( out var usernameBytesRead ) ;
128+ pointer += usernameBytesRead ;
110129 }
111130
112- deserializedToken . AdditionalData = reader . ReadString ( ) ;
131+ deserializedToken . AdditionalData = unprotectedBytes . Slice ( pointer ) . BinaryReadString ( out var addDataBytesRead ) ;
132+ pointer += addDataBytesRead ;
133+
113134 }
114135
115136 // if there's still unconsumed data in the stream, fail
116- if ( reader . BaseStream . ReadByte ( ) != - 1 )
137+ if ( pointer < unprotectedBytes . Length && unprotectedBytes [ pointer ] != 0x00 )
117138 {
118139 return null ;
119140 }
0 commit comments