@@ -1246,9 +1246,9 @@ if (internalBinding('config').hasIntl) {
1246
1246
}
1247
1247
1248
1248
function btoa ( input ) {
1249
- // The implementation here has not been performance optimized in any way and
1250
- // should not be.
1251
- // Refs: https://github.com/nodejs/node/pull/38433#issuecomment-828426932
1249
+ // The implementation here has been slightly performance optimized,
1250
+ // but still not nearly as much as it should be.
1251
+ // Refs: https://github.com/nodejs/node/pull/51670
1252
1252
if ( arguments . length === 0 ) {
1253
1253
throw new ERR_MISSING_ARGS ( 'input' ) ;
1254
1254
}
@@ -1267,63 +1267,94 @@ function btoa(input) {
1267
1267
// Refs: https://infra.spec.whatwg.org/#forgiving-base64-decode
1268
1268
// https://infra.spec.whatwg.org/#ascii-whitespace
1269
1269
// Valid Characters: [\t\n\f\r +/0-9=A-Za-z]
1270
- // Lookup table (-1 = invalid, 0 = valid)
1270
+ // Lookup table (-1 = invalid, 0 = whitespace, 1 = non-whitespace)
1271
+ // Note that `=` is set to `-1` as it is handled elsewhere.
1271
1272
/* eslint-disable no-multi-spaces, indent */
1272
1273
const kForgivingBase64AllowedChars = [
1273
1274
- 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 ,
1274
1275
- 1 , 0 , 0 , - 1 , 0 , 0 , - 1 , - 1 ,
1275
1276
- 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 ,
1276
1277
- 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 ,
1277
1278
0 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 ,
1278
- - 1 , - 1 , - 1 , 0 , - 1 , - 1 , - 1 , 0 ,
1279
- 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
1280
- 0 , 0 , - 1 , - 1 , - 1 , 0 , - 1 , - 1 ,
1281
- - 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
1282
- 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
1283
- 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
1284
- 0 , 0 , 0 , - 1 , - 1 , - 1 , - 1 , - 1 ,
1285
- - 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
1286
- 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
1287
- 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
1288
- 0 , 0 , 0 , - 1 , - 1 , - 1 , - 1 , - 1 ,
1279
+ - 1 , - 1 , - 1 , 1 , - 1 , - 1 , - 1 , 1 ,
1280
+ 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ,
1281
+ 1 , 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 ,
1282
+ - 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ,
1283
+ 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ,
1284
+ 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ,
1285
+ 1 , 1 , 1 , - 1 , - 1 , - 1 , - 1 , - 1 ,
1286
+ - 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ,
1287
+ 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ,
1288
+ 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ,
1289
+ 1 , 1 , 1 , - 1 , - 1 , - 1 , - 1 , - 1 ,
1289
1290
] ;
1290
1291
/* eslint-enable no-multi-spaces, indent */
1291
1292
1292
1293
function atob ( input ) {
1293
- // The implementation here has not been performance optimized in any way and
1294
- // should not be.
1295
- // Refs: https://github.com/nodejs/node/pull/38433#issuecomment-828426932
1294
+ // The implementation here has been slightly performance optimized,
1295
+ // but still not nearly as much as it should be.
1296
+ // Refs: https://github.com/nodejs/node/pull/51670
1296
1297
if ( arguments . length === 0 ) {
1297
1298
throw new ERR_MISSING_ARGS ( 'input' ) ;
1298
1299
}
1299
1300
1300
1301
input = `${ input } ` ;
1302
+
1301
1303
let nonAsciiWhitespaceCharCount = 0 ;
1302
1304
let equalCharCount = 0 ;
1305
+ let length = input . length ;
1306
+
1307
+ // We use an accumulator to track errors. If, at the end,
1308
+ // any high bits are set in `acc`, an invalid character has
1309
+ // been parsed.
1310
+ //
1311
+ // This works because invalid base64 characters in the lookup
1312
+ // table are `-1` and any non-ascii character will be greater
1313
+ // than 0x7f.
1314
+ let acc = 0 ;
1303
1315
1304
- for ( let n = 0 ; n < input . length ; n ++ ) {
1305
- const ch = StringPrototypeCharCodeAt ( input , n ) ;
1306
- const val = kForgivingBase64AllowedChars [ ch & 0x7f ] ;
1316
+ // Right-trim whitespace and equal signs.
1317
+ while ( length > 0 ) {
1318
+ const ch = StringPrototypeCharCodeAt ( input , length - 1 ) ;
1307
1319
1308
- if ( ( ch | val ) & ~ 0x7f ) {
1309
- throw lazyDOMException ( 'Invalid character' , 'InvalidCharacterError' ) ;
1320
+ // Possibly-valid whitespace.
1321
+ if ( ch <= 0x20 ) {
1322
+ acc |= kForgivingBase64AllowedChars [ ch ] ;
1323
+ length -- ;
1324
+ continue ;
1310
1325
}
1311
1326
1312
- if ( ch > 0x20 ) {
1327
+ // Equals sign.
1328
+ if ( ch === 0x3d ) {
1313
1329
nonAsciiWhitespaceCharCount ++ ;
1330
+ equalCharCount ++ ;
1331
+ length -- ;
1332
+ continue ;
1333
+ }
1314
1334
1315
- if ( ch === 0x3d ) {
1316
- equalCharCount ++ ;
1317
- } else if ( equalCharCount ) {
1318
- // The `=` char is only allowed at the end.
1319
- throw lazyDOMException ( 'Invalid character' , 'InvalidCharacterError' ) ;
1320
- }
1335
+ break ;
1336
+ }
1321
1337
1322
- if ( equalCharCount > 2 ) {
1323
- // Only one more `=` is permitted after the first equal sign.
1324
- throw lazyDOMException ( 'Invalid character' , 'InvalidCharacterError' ) ;
1325
- }
1326
- }
1338
+ // Parse optimistically. Check for errors after.
1339
+ // Equal signs are considered errors at this point (value = -1).
1340
+ for ( let n = 0 ; n < length ; n ++ ) {
1341
+ const ch = StringPrototypeCharCodeAt ( input , n ) ;
1342
+ const value = kForgivingBase64AllowedChars [ ch & 0x7f ] ;
1343
+
1344
+ acc |= ch | value ;
1345
+
1346
+ // Valid non-whitespace has a value of `1`.
1347
+ nonAsciiWhitespaceCharCount += value ;
1348
+ }
1349
+
1350
+ if ( acc & ~ 0x7f ) {
1351
+ // We parsed an invalid character at some point in one of the loops.
1352
+ throw lazyDOMException ( 'Invalid character' , 'InvalidCharacterError' ) ;
1353
+ }
1354
+
1355
+ if ( equalCharCount > 2 ) {
1356
+ // Only two equal signs are permitted.
1357
+ throw lazyDOMException ( 'Invalid character' , 'InvalidCharacterError' ) ;
1327
1358
}
1328
1359
1329
1360
let reminder = nonAsciiWhitespaceCharCount % 4 ;
0 commit comments