@@ -98,11 +98,22 @@ function fromBase64common(str, isBase64url, padding, format, rest) {
9898// ASCII whitespace is U+0009 TAB, U+000A LF, U+000C FF, U+000D CR, or U+0020 SPACE
9999const ASCII_WHITESPACE = / [ \t \n \f \r ] / // non-u for JSC perf
100100
101+ function noWhitespaceSeen ( str , arr ) {
102+ const at = str . indexOf ( '=' , str . length - 3 )
103+ const paddingLength = at >= 0 ? str . length - at : 0
104+ const chars = str . length - paddingLength
105+ const e = chars % 4 // extra chars past blocks of 4
106+ const b = arr . length - ( ( chars - e ) / 4 ) * 3 // remaining bytes not covered by full blocks of chars
107+ return ( e === 0 && b === 0 ) || ( e === 2 && b === 1 ) || ( e === 3 && b === 2 )
108+ }
109+
101110let fromBase64impl
102111if ( Uint8Array . fromBase64 ) {
103112 // NOTICE: this is actually slower than our JS impl in older JavaScriptCore and (slightly) in SpiderMonkey, but faster on V8 and new JavaScriptCore
104113 fromBase64impl = ( str , isBase64url ) => {
105114 const alphabet = isBase64url ? 'base64url' : 'base64'
115+ // We have to check for whitespace _before_ constructing padding (unlike in noWhitespaceSeen logic),
116+ // otherwise we won't get correct error reporting
106117 if ( ASCII_WHITESPACE . test ( str ) ) throw new SyntaxError ( E_CHAR ) // all other chars are checked natively
107118 const padded = str . length % 4 > 0 ? `${ str } ${ '=' . repeat ( 4 - ( str . length % 4 ) ) } ` : str
108119 return Uint8Array . fromBase64 ( padded , { alphabet, lastChunkHandling : 'strict' } )
@@ -121,15 +132,15 @@ if (Uint8Array.fromBase64) {
121132 if ( isBase64url ) {
122133 if ( / [ \t \n \f \r + / ] / . test ( str ) ) throw new SyntaxError ( E_CHAR ) // atob verifies other invalid input
123134 str = fromUrl ( str )
124- } else {
125- if ( ASCII_WHITESPACE . test ( str ) ) throw new SyntaxError ( E_CHAR ) // all other chars are checked natively
126135 }
127136
128137 try {
129138 arr = ascii . encodeLatin1 ( atob ( str ) )
130139 } catch {
131140 throw new SyntaxError ( E_CHAR ) // convert atob errors
132141 }
142+
143+ if ( ! isBase64url && ! noWhitespaceSeen ( str , arr ) ) throw new SyntaxError ( E_CHAR ) // base64url checks input above
133144 } else {
134145 return js . fromBase64 ( str , isBase64url ) // early return to skip last chunk verification, it's already validated in js
135146 }
0 commit comments