Skip to content

Commit 0a0b942

Browse files
committed
Optimize base64Write, remove base64-js dependency
Also adds support for `base64url` encoding.
1 parent 633dc05 commit 0a0b942

File tree

2 files changed

+148
-36
lines changed

2 files changed

+148
-36
lines changed

index.js

Lines changed: 148 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
'use strict'
1010

11-
const base64 = require('base64-js')
1211
const ieee754 = require('ieee754')
1312
const customInspectSymbol =
1413
(typeof Symbol === 'function' && typeof Symbol['for'] === 'function') // eslint-disable-line dot-notation
@@ -393,6 +392,7 @@ Buffer.isEncoding = function isEncoding (encoding) {
393392
case 'latin1':
394393
case 'binary':
395394
case 'base64':
395+
case 'base64url':
396396
case 'ucs2':
397397
case 'ucs-2':
398398
case 'utf16le':
@@ -484,7 +484,8 @@ function byteLength (string, encoding) {
484484
case 'hex':
485485
return len >>> 1
486486
case 'base64':
487-
return base64ToBytes(string).length
487+
case 'base64url':
488+
return base64ByteLength(string, len)
488489
default:
489490
if (loweredCase) {
490491
return mustMatch ? -1 : utf8ByteLength(string) // assume utf8
@@ -550,7 +551,10 @@ function slowToString (encoding, start, end) {
550551
return latin1Slice(this, start, end)
551552

552553
case 'base64':
553-
return base64Slice(this, start, end)
554+
return base64Slice(this, start, end, base64Charset, true)
555+
556+
case 'base64url':
557+
return base64Slice(this, start, end, base64UrlCharset, false)
554558

555559
case 'ucs2':
556560
case 'ucs-2':
@@ -1015,7 +1019,62 @@ function asciiWrite (buf, string, offset, length) {
10151019
}
10161020

10171021
function base64Write (buf, string, offset, length) {
1018-
return blitBuffer(base64ToBytes(string), buf, offset, length)
1022+
const src = string.replace(/[^+/0-9A-Za-z-_=]/g, '')
1023+
const eq = src.indexOf('=')
1024+
const dst = buf
1025+
1026+
let srcLen = eq >= 0 ? eq : src.length
1027+
let srcPos = 0
1028+
let dstLen = length
1029+
let dstPos = offset
1030+
1031+
while (srcLen >= 4 && dstLen >= 3) {
1032+
const t1 = base64Table[src.charCodeAt(srcPos++)]
1033+
const t2 = base64Table[src.charCodeAt(srcPos++)]
1034+
const t3 = base64Table[src.charCodeAt(srcPos++)]
1035+
const t4 = base64Table[src.charCodeAt(srcPos++)]
1036+
1037+
dst[dstPos++] = (t1 << 2) | (t2 >> 4)
1038+
dst[dstPos++] = (t2 << 4) | (t3 >> 2)
1039+
dst[dstPos++] = (t3 << 6) | (t4 >> 0)
1040+
1041+
srcLen -= 4
1042+
dstLen -= 3
1043+
}
1044+
1045+
{
1046+
let w1, w2, w3, w4
1047+
1048+
while (srcLen && dstLen) {
1049+
const w = base64Table[src.charCodeAt(srcPos)]
1050+
1051+
switch (srcPos & 3) {
1052+
case 0:
1053+
w1 = w
1054+
break
1055+
case 1:
1056+
w2 = w
1057+
dst[dstPos++] = (w1 << 2) | (w2 >> 4)
1058+
dstLen--
1059+
break
1060+
case 2:
1061+
w3 = w
1062+
dst[dstPos++] = (w2 << 4) | (w3 >> 2)
1063+
dstLen--
1064+
break
1065+
case 3:
1066+
w4 = w
1067+
dst[dstPos++] = (w3 << 6) | (w4 >> 0)
1068+
dstLen--
1069+
break
1070+
}
1071+
1072+
srcPos++
1073+
srcLen--
1074+
}
1075+
}
1076+
1077+
return dstPos - offset
10191078
}
10201079

10211080
function ucs2Write (buf, string, offset, length) {
@@ -1091,6 +1150,7 @@ Buffer.prototype.write = function write (string, offset, length, encoding) {
10911150
return asciiWrite(this, string, offset, length)
10921151

10931152
case 'base64':
1153+
case 'base64url':
10941154
// Warning: maxLength not taken into account in base64Write
10951155
return base64Write(this, string, offset, length)
10961156

@@ -1115,12 +1175,53 @@ Buffer.prototype.toJSON = function toJSON () {
11151175
}
11161176
}
11171177

1118-
function base64Slice (buf, start, end) {
1119-
if (start === 0 && end === buf.length) {
1120-
return base64.fromByteArray(buf)
1121-
} else {
1122-
return base64.fromByteArray(buf.slice(start, end))
1178+
function base64Slice (buf, start, end, charset, pad) {
1179+
end = Math.min(buf.length, end)
1180+
1181+
let left = end - start
1182+
let i = start
1183+
let str = ''
1184+
1185+
while (left >= 3) {
1186+
const c1 = buf[i++]
1187+
const c2 = buf[i++]
1188+
const c3 = buf[i++]
1189+
1190+
str += charset[c1 >> 2]
1191+
str += charset[((c1 & 3) << 4) | (c2 >> 4)]
1192+
str += charset[((c2 & 15) << 2) | (c3 >> 6)]
1193+
str += charset[c3 & 63]
1194+
1195+
left -= 3
1196+
}
1197+
1198+
switch (left) {
1199+
case 1: {
1200+
const c1 = buf[i++]
1201+
1202+
str += charset[c1 >> 2]
1203+
str += charset[(c1 & 3) << 4]
1204+
1205+
if (pad) str += '=='
1206+
1207+
break
1208+
}
1209+
1210+
case 2: {
1211+
const c1 = buf[i++]
1212+
const c2 = buf[i++]
1213+
1214+
str += charset[c1 >> 2]
1215+
str += charset[((c1 & 3) << 4) | (c2 >> 4)]
1216+
str += charset[(c2 & 15) << 2]
1217+
1218+
if (pad) str += '='
1219+
1220+
break
1221+
}
11231222
}
1223+
1224+
return str
11241225
}
11251226

11261227
function utf8Slice (buf, start, end) {
@@ -2109,26 +2210,6 @@ function boundsError (value, length, type) {
21092210
// HELPER FUNCTIONS
21102211
// ================
21112212

2112-
const INVALID_BASE64_RE = /[^+/0-9A-Za-z-_]/g
2113-
2114-
function base64clean (str) {
2115-
// Node takes equal signs as end of the Base64 encoding
2116-
str = str.split('=')[0]
2117-
// Node strips out invalid characters like \n and \t from the string, base64-js does not
2118-
str = str.trim().replace(INVALID_BASE64_RE, '')
2119-
// Node converts strings with length < 2 to ''
2120-
if (str.length < 2) return ''
2121-
// Node allows for non-padded base64 strings (missing trailing ===), base64-js does not
2122-
while (str.length % 4 !== 0) {
2123-
str = str + '='
2124-
}
2125-
return str
2126-
}
2127-
2128-
function base64ToBytes (str) {
2129-
return base64.toByteArray(base64clean(str))
2130-
}
2131-
21322213
function writeInvalid (buf, pos) {
21332214
// U+FFFD (Replacement Character)
21342215
buf[pos++] = 0xef
@@ -2137,13 +2218,18 @@ function writeInvalid (buf, pos) {
21372218
return pos
21382219
}
21392220

2140-
function blitBuffer (src, dst, offset, length) {
2141-
let i
2142-
for (i = 0; i < length; ++i) {
2143-
if ((i + offset >= dst.length) || (i >= src.length)) break
2144-
dst[i + offset] = src[i]
2221+
function base64ByteLength (str, bytes) {
2222+
// Handle padding
2223+
if (bytes > 0 && str.charCodeAt(bytes - 1) === 0x3d) {
2224+
bytes--
2225+
}
2226+
2227+
if (bytes > 1 && str.charCodeAt(bytes - 1) === 0x3d) {
2228+
bytes--
21452229
}
2146-
return i
2230+
2231+
// Base64 ratio: 3/4
2232+
return (bytes * 3) >>> 2
21472233
}
21482234

21492235
// ArrayBuffer or Uint8Array objects from other contexts (i.e. iframes) do not pass
@@ -2195,6 +2281,33 @@ const hexCharValueTable = [
21952281
]
21962282
/* eslint-enable no-multi-spaces, indent */
21972283

2284+
const base64Charset =
2285+
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
2286+
2287+
const base64UrlCharset =
2288+
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'
2289+
2290+
/* eslint-disable no-multi-spaces, indent */
2291+
const base64Table = [
2292+
-1, -1, -1, -1, -1, -1, -1, -1,
2293+
-1, -1, -1, -1, -1, -1, -1, -1,
2294+
-1, -1, -1, -1, -1, -1, -1, -1,
2295+
-1, -1, -1, -1, -1, -1, -1, -1,
2296+
-1, -1, -1, -1, -1, -1, -1, -1,
2297+
-1, -1, -1, 62, -1, 62, -1, 63,
2298+
52, 53, 54, 55, 56, 57, 58, 59,
2299+
60, 61, -1, -1, -1, -1, -1, -1,
2300+
-1, 0, 1, 2, 3, 4, 5, 6,
2301+
7, 8, 9, 10, 11, 12, 13, 14,
2302+
15, 16, 17, 18, 19, 20, 21, 22,
2303+
23, 24, 25, -1, -1, -1, -1, 63,
2304+
-1, 26, 27, 28, 29, 30, 31, 32,
2305+
33, 34, 35, 36, 37, 38, 39, 40,
2306+
41, 42, 43, 44, 45, 46, 47, 48,
2307+
49, 50, 51, -1, -1, -1, -1, -1
2308+
]
2309+
/* eslint-enable no-multi-spaces, indent */
2310+
21982311
// Return not function with Error if BigInt not supported
21992312
function defineBigIntMethod (fn) {
22002313
return typeof BigInt === 'undefined' ? BufferBigIntNotDefined : fn

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
"James Halliday <mail@substack.net>"
1717
],
1818
"dependencies": {
19-
"base64-js": "^1.3.1",
2019
"ieee754": "^1.2.1"
2120
},
2221
"devDependencies": {

0 commit comments

Comments
 (0)