Skip to content

Commit cb5e05f

Browse files
committed
perf: faster fromBase64 on Hermes, recheck size instead of whitespace
1 parent 12c1cba commit cb5e05f

File tree

1 file changed

+13
-2
lines changed

1 file changed

+13
-2
lines changed

base64.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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
9999
const 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+
101110
let fromBase64impl
102111
if (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

Comments
 (0)