@exodus/bytes 1.0.0-rc.0 → 1.0.0-rc.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +169 -2
- package/array.js +1 -1
- package/assert.js +10 -6
- package/base32.js +40 -0
- package/base58.js +212 -0
- package/base58check.js +69 -0
- package/base64.js +133 -160
- package/bech32.js +254 -0
- package/encoding-lite.js +7 -0
- package/encoding.js +12 -0
- package/fallback/_utils.js +118 -0
- package/fallback/base32.js +233 -0
- package/fallback/base64.js +192 -0
- package/fallback/encoding.js +279 -0
- package/fallback/encoding.labels.js +46 -0
- package/fallback/encoding.util.js +34 -0
- package/fallback/hex.js +127 -0
- package/fallback/latin1.js +120 -0
- package/fallback/multi-byte.encodings.cjs +1 -0
- package/fallback/multi-byte.encodings.json +545 -0
- package/fallback/multi-byte.js +448 -0
- package/fallback/multi-byte.table.js +114 -0
- package/fallback/single-byte.encodings.js +45 -0
- package/fallback/single-byte.js +83 -0
- package/fallback/utf16.js +180 -0
- package/fallback/utf8.js +245 -0
- package/hex.js +12 -71
- package/hex.node.js +28 -0
- package/multi-byte.js +13 -0
- package/multi-byte.node.js +25 -0
- package/package.json +105 -14
- package/single-byte.js +55 -0
- package/single-byte.node.js +62 -0
- package/utf16.js +73 -0
- package/utf16.node.js +79 -0
- package/utf8.js +80 -0
- package/utf8.node.js +54 -0
- package/wif.js +42 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
const { Buffer, TextEncoder, TextDecoder } = globalThis
|
|
2
|
+
const haveNativeBuffer = Buffer && !Buffer.TYPED_ARRAY_SUPPORT
|
|
3
|
+
let isNative = (x) => x && (haveNativeBuffer || `${x}`.includes('[native code]')) // we consider Node.js TextDecoder/TextEncoder native
|
|
4
|
+
if (!haveNativeBuffer && isNative(() => {})) isNative = () => false // e.g. XS, we don't want false positives
|
|
5
|
+
|
|
6
|
+
export const nativeEncoder = isNative(TextEncoder) ? new TextEncoder() : null
|
|
7
|
+
export const nativeDecoder = isNative(TextDecoder)
|
|
8
|
+
? new TextDecoder('utf-8', { ignoreBOM: true })
|
|
9
|
+
: null
|
|
10
|
+
export const nativeBuffer = haveNativeBuffer ? Buffer : null
|
|
11
|
+
export const isHermes = Boolean(globalThis.HermesInternal)
|
|
12
|
+
export const isDeno = Boolean(globalThis.Deno)
|
|
13
|
+
export const isLE = new Uint8Array(Uint16Array.of(258).buffer)[0] === 2
|
|
14
|
+
|
|
15
|
+
// Actually windows-1252, compatible with ascii and latin1 decoding
|
|
16
|
+
// Beware that on non-latin1, i.e. on windows-1252, this is broken in ~all Node.js versions released
|
|
17
|
+
// in 2025 due to a regression, so we call it Latin1 as it's usable only for that
|
|
18
|
+
let nativeDecoderLatin1impl = null
|
|
19
|
+
if (isNative(TextDecoder)) {
|
|
20
|
+
// Not all barebone engines with TextDecoder support something except utf-8, detect
|
|
21
|
+
try {
|
|
22
|
+
nativeDecoderLatin1impl = new TextDecoder('latin1', { ignoreBOM: true })
|
|
23
|
+
} catch {}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const nativeDecoderLatin1 = nativeDecoderLatin1impl
|
|
27
|
+
export const canDecoders = Boolean(nativeDecoderLatin1impl)
|
|
28
|
+
|
|
29
|
+
// Block Firefox < 146 specifically from using native hex/base64, as it's very slow there
|
|
30
|
+
// Refs: https://bugzilla.mozilla.org/show_bug.cgi?id=1994067 (and linked issues), fixed in 146
|
|
31
|
+
// Before that, all versions of Firefox >= 133 are slow
|
|
32
|
+
// TODO: this could be removed when < 146 usage diminishes (note ESR)
|
|
33
|
+
// We do not worry about false-negatives here but worry about false-positives!
|
|
34
|
+
function shouldSkipBuiltins() {
|
|
35
|
+
const g = globalThis
|
|
36
|
+
// First, attempt to exclude as many things as we can using trivial checks, just in case, and to not hit ua
|
|
37
|
+
if (haveNativeBuffer || isHermes || !g.window || g.chrome || !g.navigator) return false
|
|
38
|
+
try {
|
|
39
|
+
// This was fixed specifically in Firefox 146. Other engines except Hermes (already returned) get this right
|
|
40
|
+
new WeakSet().add(Symbol()) // eslint-disable-line symbol-description
|
|
41
|
+
return false
|
|
42
|
+
} catch {
|
|
43
|
+
// In catch and not after in case if something too smart optimizes out code in try. False-negative is acceptable in that case
|
|
44
|
+
if (!('onmozfullscreenerror' in g)) return false // Firefox has it (might remove in the future, but we don't care)
|
|
45
|
+
return /firefox/i.test(g.navigator.userAgent || '') // as simple as we can
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return false // eslint-disable-line no-unreachable
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const skipWeb = shouldSkipBuiltins()
|
|
52
|
+
|
|
53
|
+
function decodePartAddition(a, start, end, m) {
|
|
54
|
+
let o = ''
|
|
55
|
+
let i = start
|
|
56
|
+
for (const last3 = end - 3; i < last3; i += 4) {
|
|
57
|
+
const x0 = a[i]
|
|
58
|
+
const x1 = a[i + 1]
|
|
59
|
+
const x2 = a[i + 2]
|
|
60
|
+
const x3 = a[i + 3]
|
|
61
|
+
o += m[x0]
|
|
62
|
+
o += m[x1]
|
|
63
|
+
o += m[x2]
|
|
64
|
+
o += m[x3]
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
while (i < end) o += m[a[i++]]
|
|
68
|
+
return o
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Decoding with templates is faster on Hermes
|
|
72
|
+
function decodePartTemplates(a, start, end, m) {
|
|
73
|
+
let o = ''
|
|
74
|
+
let i = start
|
|
75
|
+
for (const last15 = end - 15; i < last15; i += 16) {
|
|
76
|
+
const x0 = a[i]
|
|
77
|
+
const x1 = a[i + 1]
|
|
78
|
+
const x2 = a[i + 2]
|
|
79
|
+
const x3 = a[i + 3]
|
|
80
|
+
const x4 = a[i + 4]
|
|
81
|
+
const x5 = a[i + 5]
|
|
82
|
+
const x6 = a[i + 6]
|
|
83
|
+
const x7 = a[i + 7]
|
|
84
|
+
const x8 = a[i + 8]
|
|
85
|
+
const x9 = a[i + 9]
|
|
86
|
+
const x10 = a[i + 10]
|
|
87
|
+
const x11 = a[i + 11]
|
|
88
|
+
const x12 = a[i + 12]
|
|
89
|
+
const x13 = a[i + 13]
|
|
90
|
+
const x14 = a[i + 14]
|
|
91
|
+
const x15 = a[i + 15]
|
|
92
|
+
o += `${m[x0]}${m[x1]}${m[x2]}${m[x3]}${m[x4]}${m[x5]}${m[x6]}${m[x7]}${m[x8]}${m[x9]}${m[x10]}${m[x11]}${m[x12]}${m[x13]}${m[x14]}${m[x15]}`
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
while (i < end) o += m[a[i++]]
|
|
96
|
+
return o
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const decodePart = isHermes ? decodePartTemplates : decodePartAddition
|
|
100
|
+
export function decode2string(arr, start, end, m) {
|
|
101
|
+
if (start - end > 30_000) {
|
|
102
|
+
// Limit concatenation to avoid excessive GC
|
|
103
|
+
// Thresholds checked on Hermes for toHex
|
|
104
|
+
const concat = []
|
|
105
|
+
for (let i = start; i < end; ) {
|
|
106
|
+
const step = i + 500
|
|
107
|
+
const iNext = step > end ? end : step
|
|
108
|
+
concat.push(decodePart(arr, i, iNext, m))
|
|
109
|
+
i = iNext
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const res = concat.join('')
|
|
113
|
+
concat.length = 0
|
|
114
|
+
return res
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return decodePart(arr, start, end, m)
|
|
118
|
+
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { assertUint8 } from '../assert.js'
|
|
2
|
+
import { nativeEncoder, nativeDecoder, isHermes } from './_utils.js'
|
|
3
|
+
import { encodeAscii, decodeAscii } from './latin1.js'
|
|
4
|
+
|
|
5
|
+
// See https://datatracker.ietf.org/doc/html/rfc4648
|
|
6
|
+
|
|
7
|
+
const BASE32 = [...'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'] // RFC 4648, #6
|
|
8
|
+
const BASE32HEX = [...'0123456789ABCDEFGHIJKLMNOPQRSTUV'] // RFC 4648, #7
|
|
9
|
+
const BASE32_HELPERS = {}
|
|
10
|
+
const BASE32HEX_HELPERS = {}
|
|
11
|
+
|
|
12
|
+
export const E_CHAR = 'Invalid character in base32 input'
|
|
13
|
+
export const E_PADDING = 'Invalid base32 padding'
|
|
14
|
+
export const E_LENGTH = 'Invalid base32 length'
|
|
15
|
+
export const E_LAST = 'Invalid last chunk'
|
|
16
|
+
|
|
17
|
+
const useTemplates = isHermes // Faster on Hermes and JSC, but we use it only on Hermes
|
|
18
|
+
|
|
19
|
+
// We construct output by concatenating chars, this seems to be fine enough on modern JS engines
|
|
20
|
+
export function toBase32(arr, isBase32Hex, padding) {
|
|
21
|
+
assertUint8(arr)
|
|
22
|
+
const fullChunks = Math.floor(arr.length / 5)
|
|
23
|
+
const fullChunksBytes = fullChunks * 5
|
|
24
|
+
let o = ''
|
|
25
|
+
let i = 0
|
|
26
|
+
|
|
27
|
+
const alphabet = isBase32Hex ? BASE32HEX : BASE32
|
|
28
|
+
const helpers = isBase32Hex ? BASE32HEX_HELPERS : BASE32_HELPERS
|
|
29
|
+
if (!helpers.pairs) {
|
|
30
|
+
helpers.pairs = []
|
|
31
|
+
if (nativeDecoder) {
|
|
32
|
+
// Lazy to save memory in case if this is not needed
|
|
33
|
+
helpers.codepairs = new Uint16Array(32 * 32)
|
|
34
|
+
const u16 = helpers.codepairs
|
|
35
|
+
const u8 = new Uint8Array(u16.buffer, u16.byteOffset, u16.byteLength) // write as 1-byte to ignore BE/LE difference
|
|
36
|
+
for (let i = 0; i < 32; i++) {
|
|
37
|
+
const ic = alphabet[i].charCodeAt(0)
|
|
38
|
+
for (let j = 0; j < 32; j++) u8[(i << 6) | (j << 1)] = u8[(j << 6) | ((i << 1) + 1)] = ic
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
const p = helpers.pairs
|
|
42
|
+
for (let i = 0; i < 32; i++) {
|
|
43
|
+
for (let j = 0; j < 32; j++) p.push(`${alphabet[i]}${alphabet[j]}`)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const { pairs, codepairs } = helpers
|
|
49
|
+
|
|
50
|
+
// Fast path for complete blocks
|
|
51
|
+
// This whole loop can be commented out, the algorithm won't change, it's just an optimization of the next loop
|
|
52
|
+
if (nativeDecoder) {
|
|
53
|
+
const oa = new Uint16Array(fullChunks * 4)
|
|
54
|
+
for (let j = 0; i < fullChunksBytes; i += 5) {
|
|
55
|
+
const a = arr[i]
|
|
56
|
+
const b = arr[i + 1]
|
|
57
|
+
const c = arr[i + 2]
|
|
58
|
+
const d = arr[i + 3]
|
|
59
|
+
const e = arr[i + 4]
|
|
60
|
+
const x0 = (a << 2) | (b >> 6) // 8 + 8 - 5 - 5 = 6 left
|
|
61
|
+
const x1 = ((b & 0x3f) << 4) | (c >> 4) // 6 + 8 - 5 - 5 = 4 left
|
|
62
|
+
const x2 = ((c & 0xf) << 6) | (d >> 2) // 4 + 8 - 5 - 5 = 2 left
|
|
63
|
+
const x3 = ((d & 0x3) << 8) | e // 2 + 8 - 5 - 5 = 0 left
|
|
64
|
+
oa[j] = codepairs[x0]
|
|
65
|
+
oa[j + 1] = codepairs[x1]
|
|
66
|
+
oa[j + 2] = codepairs[x2]
|
|
67
|
+
oa[j + 3] = codepairs[x3]
|
|
68
|
+
j += 4
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
o = decodeAscii(oa)
|
|
72
|
+
} else if (useTemplates) {
|
|
73
|
+
// Templates are faster only on Hermes and JSC. Browsers have TextDecoder anyway
|
|
74
|
+
for (; i < fullChunksBytes; i += 5) {
|
|
75
|
+
const a = arr[i]
|
|
76
|
+
const b = arr[i + 1]
|
|
77
|
+
const c = arr[i + 2]
|
|
78
|
+
const d = arr[i + 3]
|
|
79
|
+
const e = arr[i + 4]
|
|
80
|
+
const x0 = (a << 2) | (b >> 6) // 8 + 8 - 5 - 5 = 6 left
|
|
81
|
+
const x1 = ((b & 0x3f) << 4) | (c >> 4) // 6 + 8 - 5 - 5 = 4 left
|
|
82
|
+
const x2 = ((c & 0xf) << 6) | (d >> 2) // 4 + 8 - 5 - 5 = 2 left
|
|
83
|
+
const x3 = ((d & 0x3) << 8) | e // 2 + 8 - 5 - 5 = 0 left
|
|
84
|
+
o += `${pairs[x0]}${pairs[x1]}${pairs[x2]}${pairs[x3]}`
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
for (; i < fullChunksBytes; i += 5) {
|
|
88
|
+
const a = arr[i]
|
|
89
|
+
const b = arr[i + 1]
|
|
90
|
+
const c = arr[i + 2]
|
|
91
|
+
const d = arr[i + 3]
|
|
92
|
+
const e = arr[i + 4]
|
|
93
|
+
const x0 = (a << 2) | (b >> 6) // 8 + 8 - 5 - 5 = 6 left
|
|
94
|
+
const x1 = ((b & 0x3f) << 4) | (c >> 4) // 6 + 8 - 5 - 5 = 4 left
|
|
95
|
+
const x2 = ((c & 0xf) << 6) | (d >> 2) // 4 + 8 - 5 - 5 = 2 left
|
|
96
|
+
const x3 = ((d & 0x3) << 8) | e // 2 + 8 - 5 - 5 = 0 left
|
|
97
|
+
o += pairs[x0]
|
|
98
|
+
o += pairs[x1]
|
|
99
|
+
o += pairs[x2]
|
|
100
|
+
o += pairs[x3]
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// If we have something left, process it with a full algo
|
|
105
|
+
let carry = 0
|
|
106
|
+
let shift = 3 // First byte needs to be shifted by 3 to get 5 bits
|
|
107
|
+
for (; i < arr.length; i++) {
|
|
108
|
+
const x = arr[i]
|
|
109
|
+
o += alphabet[carry | (x >> shift)] // shift >= 3, so this fits
|
|
110
|
+
if (shift >= 5) {
|
|
111
|
+
shift -= 5
|
|
112
|
+
o += alphabet[(x >> shift) & 0x1f]
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
carry = (x << (5 - shift)) & 0x1f
|
|
116
|
+
shift += 3 // Each byte prints 5 bits and leaves 3 bits
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (shift !== 3) o += alphabet[carry] // shift 3 means we have no carry left
|
|
120
|
+
if (padding) o += ['', '======', '====', '===', '='][arr.length - fullChunksBytes]
|
|
121
|
+
|
|
122
|
+
return o
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// TODO: can this be optimized? This only affects non-Hermes barebone engines though
|
|
126
|
+
const mapSize = nativeEncoder ? 128 : 65_536 // we have to store 64 KiB map or recheck everything if we can't decode to byte array
|
|
127
|
+
|
|
128
|
+
export function fromBase32(str, isBase32Hex) {
|
|
129
|
+
let inputLength = str.length
|
|
130
|
+
while (str[inputLength - 1] === '=') inputLength--
|
|
131
|
+
const paddingLength = str.length - inputLength
|
|
132
|
+
const tailLength = inputLength % 8
|
|
133
|
+
const mainLength = inputLength - tailLength // multiples of 8
|
|
134
|
+
if (![0, 2, 4, 5, 7].includes(tailLength)) throw new SyntaxError(E_LENGTH) // fast verification
|
|
135
|
+
if (paddingLength > 7 || (paddingLength !== 0 && str.length % 8 !== 0)) {
|
|
136
|
+
throw new SyntaxError(E_PADDING)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const alphabet = isBase32Hex ? BASE32HEX : BASE32
|
|
140
|
+
const helpers = isBase32Hex ? BASE32HEX_HELPERS : BASE32_HELPERS
|
|
141
|
+
|
|
142
|
+
if (!helpers.fromMap) {
|
|
143
|
+
helpers.fromMap = new Int8Array(mapSize).fill(-1) // no regex input validation here, so we map all other bytes to -1 and recheck sign
|
|
144
|
+
alphabet.forEach((c, i) => {
|
|
145
|
+
helpers.fromMap[c.charCodeAt(0)] = helpers.fromMap[c.toLowerCase().charCodeAt(0)] = i
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const m = helpers.fromMap
|
|
150
|
+
|
|
151
|
+
const arr = new Uint8Array(Math.floor((inputLength * 5) / 8))
|
|
152
|
+
let at = 0
|
|
153
|
+
let i = 0
|
|
154
|
+
|
|
155
|
+
if (nativeEncoder) {
|
|
156
|
+
const codes = encodeAscii(str, E_CHAR)
|
|
157
|
+
for (; i < mainLength; i += 8) {
|
|
158
|
+
// each 5 bits, grouped 5 * 4 = 20
|
|
159
|
+
const x0 = codes[i]
|
|
160
|
+
const x1 = codes[i + 1]
|
|
161
|
+
const x2 = codes[i + 2]
|
|
162
|
+
const x3 = codes[i + 3]
|
|
163
|
+
const x4 = codes[i + 4]
|
|
164
|
+
const x5 = codes[i + 5]
|
|
165
|
+
const x6 = codes[i + 6]
|
|
166
|
+
const x7 = codes[i + 7]
|
|
167
|
+
const a = (m[x0] << 15) | (m[x1] << 10) | (m[x2] << 5) | m[x3]
|
|
168
|
+
const b = (m[x4] << 15) | (m[x5] << 10) | (m[x6] << 5) | m[x7]
|
|
169
|
+
if (a < 0 || b < 0) throw new SyntaxError(E_CHAR)
|
|
170
|
+
arr[at] = a >> 12
|
|
171
|
+
arr[at + 1] = (a >> 4) & 0xff
|
|
172
|
+
arr[at + 2] = ((a << 4) & 0xff) | (b >> 16)
|
|
173
|
+
arr[at + 3] = (b >> 8) & 0xff
|
|
174
|
+
arr[at + 4] = b & 0xff
|
|
175
|
+
at += 5
|
|
176
|
+
}
|
|
177
|
+
} else {
|
|
178
|
+
for (; i < mainLength; i += 8) {
|
|
179
|
+
// each 5 bits, grouped 5 * 4 = 20
|
|
180
|
+
const x0 = str.charCodeAt(i)
|
|
181
|
+
const x1 = str.charCodeAt(i + 1)
|
|
182
|
+
const x2 = str.charCodeAt(i + 2)
|
|
183
|
+
const x3 = str.charCodeAt(i + 3)
|
|
184
|
+
const x4 = str.charCodeAt(i + 4)
|
|
185
|
+
const x5 = str.charCodeAt(i + 5)
|
|
186
|
+
const x6 = str.charCodeAt(i + 6)
|
|
187
|
+
const x7 = str.charCodeAt(i + 7)
|
|
188
|
+
const a = (m[x0] << 15) | (m[x1] << 10) | (m[x2] << 5) | m[x3]
|
|
189
|
+
const b = (m[x4] << 15) | (m[x5] << 10) | (m[x6] << 5) | m[x7]
|
|
190
|
+
if (a < 0 || b < 0) throw new SyntaxError(E_CHAR)
|
|
191
|
+
arr[at] = a >> 12
|
|
192
|
+
arr[at + 1] = (a >> 4) & 0xff
|
|
193
|
+
arr[at + 2] = ((a << 4) & 0xff) | (b >> 16)
|
|
194
|
+
arr[at + 3] = (b >> 8) & 0xff
|
|
195
|
+
arr[at + 4] = b & 0xff
|
|
196
|
+
at += 5
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Last block, valid tailLength: 0 2 4 5 7, checked already
|
|
201
|
+
// We check last chunk to be strict
|
|
202
|
+
if (tailLength < 2) return arr
|
|
203
|
+
const ab = (m[str.charCodeAt(i++)] << 5) | m[str.charCodeAt(i++)]
|
|
204
|
+
if (ab < 0) throw new SyntaxError(E_CHAR)
|
|
205
|
+
arr[at++] = ab >> 2
|
|
206
|
+
if (tailLength < 4) {
|
|
207
|
+
if (ab & 0x3) throw new SyntaxError(E_LAST)
|
|
208
|
+
return arr
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const cd = (m[str.charCodeAt(i++)] << 5) | m[str.charCodeAt(i++)]
|
|
212
|
+
if (cd < 0) throw new SyntaxError(E_CHAR)
|
|
213
|
+
arr[at++] = ((ab << 6) & 0xff) | (cd >> 4)
|
|
214
|
+
if (tailLength < 5) {
|
|
215
|
+
if (cd & 0xf) throw new SyntaxError(E_LAST)
|
|
216
|
+
return arr
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const e = m[str.charCodeAt(i++)]
|
|
220
|
+
if (e < 0) throw new SyntaxError(E_CHAR)
|
|
221
|
+
arr[at++] = ((cd << 4) & 0xff) | (e >> 1) // 4 + 4
|
|
222
|
+
if (tailLength < 7) {
|
|
223
|
+
if (e & 0x1) throw new SyntaxError(E_LAST)
|
|
224
|
+
return arr
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const fg = (m[str.charCodeAt(i++)] << 5) | m[str.charCodeAt(i++)]
|
|
228
|
+
if (fg < 0) throw new SyntaxError(E_CHAR)
|
|
229
|
+
arr[at++] = ((e << 7) & 0xff) | (fg >> 3) // 1 + 5 + 2
|
|
230
|
+
// Can't be 8, so no h
|
|
231
|
+
if (fg & 0x7) throw new SyntaxError(E_LAST)
|
|
232
|
+
return arr
|
|
233
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { assertUint8 } from '../assert.js'
|
|
2
|
+
import { nativeEncoder, nativeDecoder } from './_utils.js'
|
|
3
|
+
import { encodeAscii, decodeAscii } from './latin1.js'
|
|
4
|
+
|
|
5
|
+
// See https://datatracker.ietf.org/doc/html/rfc4648
|
|
6
|
+
|
|
7
|
+
const BASE64 = [...'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/']
|
|
8
|
+
const BASE64URL = [...'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_']
|
|
9
|
+
const BASE64_HELPERS = {}
|
|
10
|
+
const BASE64URL_HELPERS = {}
|
|
11
|
+
|
|
12
|
+
export const E_CHAR = 'Invalid character in base64 input'
|
|
13
|
+
export const E_PADDING = 'Invalid base64 padding'
|
|
14
|
+
export const E_LENGTH = 'Invalid base64 length'
|
|
15
|
+
export const E_LAST = 'Invalid last chunk'
|
|
16
|
+
|
|
17
|
+
// We construct output by concatenating chars, this seems to be fine enough on modern JS engines
|
|
18
|
+
export function toBase64(arr, isURL, padding) {
|
|
19
|
+
assertUint8(arr)
|
|
20
|
+
const fullChunks = (arr.length / 3) | 0
|
|
21
|
+
const fullChunksBytes = fullChunks * 3
|
|
22
|
+
let o = ''
|
|
23
|
+
let i = 0
|
|
24
|
+
|
|
25
|
+
const alphabet = isURL ? BASE64URL : BASE64
|
|
26
|
+
const helpers = isURL ? BASE64URL_HELPERS : BASE64_HELPERS
|
|
27
|
+
if (!helpers.pairs) {
|
|
28
|
+
helpers.pairs = []
|
|
29
|
+
if (nativeDecoder) {
|
|
30
|
+
// Lazy to save memory in case if this is not needed
|
|
31
|
+
helpers.codepairs = new Uint16Array(64 * 64)
|
|
32
|
+
const u16 = helpers.codepairs
|
|
33
|
+
const u8 = new Uint8Array(u16.buffer, u16.byteOffset, u16.byteLength) // write as 1-byte to ignore BE/LE difference
|
|
34
|
+
for (let i = 0; i < 64; i++) {
|
|
35
|
+
const ic = alphabet[i].charCodeAt(0)
|
|
36
|
+
for (let j = 0; j < 64; j++) u8[(i << 7) | (j << 1)] = u8[(j << 7) | ((i << 1) + 1)] = ic
|
|
37
|
+
}
|
|
38
|
+
} else {
|
|
39
|
+
const p = helpers.pairs
|
|
40
|
+
for (let i = 0; i < 64; i++) {
|
|
41
|
+
for (let j = 0; j < 64; j++) p.push(`${alphabet[i]}${alphabet[j]}`)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const { pairs, codepairs } = helpers
|
|
47
|
+
|
|
48
|
+
// Fast path for complete blocks
|
|
49
|
+
// This whole loop can be commented out, the algorithm won't change, it's just an optimization of the next loop
|
|
50
|
+
if (nativeDecoder) {
|
|
51
|
+
const oa = new Uint16Array(fullChunks * 2)
|
|
52
|
+
let j = 0
|
|
53
|
+
for (const last = arr.length - 11; i < last; i += 12, j += 8) {
|
|
54
|
+
const x0 = arr[i]
|
|
55
|
+
const x1 = arr[i + 1]
|
|
56
|
+
const x2 = arr[i + 2]
|
|
57
|
+
const x3 = arr[i + 3]
|
|
58
|
+
const x4 = arr[i + 4]
|
|
59
|
+
const x5 = arr[i + 5]
|
|
60
|
+
const x6 = arr[i + 6]
|
|
61
|
+
const x7 = arr[i + 7]
|
|
62
|
+
const x8 = arr[i + 8]
|
|
63
|
+
const x9 = arr[i + 9]
|
|
64
|
+
const x10 = arr[i + 10]
|
|
65
|
+
const x11 = arr[i + 11]
|
|
66
|
+
oa[j] = codepairs[(x0 << 4) | (x1 >> 4)]
|
|
67
|
+
oa[j + 1] = codepairs[((x1 & 0x0f) << 8) | x2]
|
|
68
|
+
oa[j + 2] = codepairs[(x3 << 4) | (x4 >> 4)]
|
|
69
|
+
oa[j + 3] = codepairs[((x4 & 0x0f) << 8) | x5]
|
|
70
|
+
oa[j + 4] = codepairs[(x6 << 4) | (x7 >> 4)]
|
|
71
|
+
oa[j + 5] = codepairs[((x7 & 0x0f) << 8) | x8]
|
|
72
|
+
oa[j + 6] = codepairs[(x9 << 4) | (x10 >> 4)]
|
|
73
|
+
oa[j + 7] = codepairs[((x10 & 0x0f) << 8) | x11]
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// i < last here is equivalent to i < fullChunksBytes
|
|
77
|
+
for (const last = arr.length - 2; i < last; i += 3, j += 2) {
|
|
78
|
+
const a = arr[i]
|
|
79
|
+
const b = arr[i + 1]
|
|
80
|
+
const c = arr[i + 2]
|
|
81
|
+
oa[j] = codepairs[(a << 4) | (b >> 4)]
|
|
82
|
+
oa[j + 1] = codepairs[((b & 0x0f) << 8) | c]
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
o = decodeAscii(oa)
|
|
86
|
+
} else {
|
|
87
|
+
// This can be optimized by ~25% with templates on Hermes, but this codepath is not called on Hermes, it uses btoa
|
|
88
|
+
// Check git history for templates version
|
|
89
|
+
for (; i < fullChunksBytes; i += 3) {
|
|
90
|
+
const a = arr[i]
|
|
91
|
+
const b = arr[i + 1]
|
|
92
|
+
const c = arr[i + 2]
|
|
93
|
+
o += pairs[(a << 4) | (b >> 4)]
|
|
94
|
+
o += pairs[((b & 0x0f) << 8) | c]
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// If we have something left, process it with a full algo
|
|
99
|
+
let carry = 0
|
|
100
|
+
let shift = 2 // First byte needs to be shifted by 2 to get 6 bits
|
|
101
|
+
const length = arr.length
|
|
102
|
+
for (; i < length; i++) {
|
|
103
|
+
const x = arr[i]
|
|
104
|
+
o += alphabet[carry | (x >> shift)] // shift >= 2, so this fits
|
|
105
|
+
if (shift === 6) {
|
|
106
|
+
shift = 0
|
|
107
|
+
o += alphabet[x & 0x3f]
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
carry = (x << (6 - shift)) & 0x3f
|
|
111
|
+
shift += 2 // Each byte prints 6 bits and leaves 2 bits
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (shift !== 2) o += alphabet[carry] // shift 2 means we have no carry left
|
|
115
|
+
if (padding) o += ['', '==', '='][length - fullChunksBytes]
|
|
116
|
+
|
|
117
|
+
return o
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// TODO: can this be optimized? This only affects non-Hermes barebone engines though
|
|
121
|
+
const mapSize = nativeEncoder ? 128 : 65_536 // we have to store 64 KiB map or recheck everything if we can't decode to byte array
|
|
122
|
+
|
|
123
|
+
export function fromBase64(str, isURL) {
|
|
124
|
+
let inputLength = str.length
|
|
125
|
+
while (str[inputLength - 1] === '=') inputLength--
|
|
126
|
+
const paddingLength = str.length - inputLength
|
|
127
|
+
const tailLength = inputLength % 4
|
|
128
|
+
const mainLength = inputLength - tailLength // multiples of 4
|
|
129
|
+
if (tailLength === 1) throw new SyntaxError(E_LENGTH)
|
|
130
|
+
if (paddingLength > 3 || (paddingLength !== 0 && str.length % 4 !== 0)) {
|
|
131
|
+
throw new SyntaxError(E_PADDING)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const alphabet = isURL ? BASE64URL : BASE64
|
|
135
|
+
const helpers = isURL ? BASE64URL_HELPERS : BASE64_HELPERS
|
|
136
|
+
|
|
137
|
+
if (!helpers.fromMap) {
|
|
138
|
+
helpers.fromMap = new Int8Array(mapSize).fill(-1) // no regex input validation here, so we map all other bytes to -1 and recheck sign
|
|
139
|
+
alphabet.forEach((c, i) => (helpers.fromMap[c.charCodeAt(0)] = i))
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const m = helpers.fromMap
|
|
143
|
+
|
|
144
|
+
const arr = new Uint8Array(Math.floor((inputLength * 3) / 4))
|
|
145
|
+
let at = 0
|
|
146
|
+
let i = 0
|
|
147
|
+
|
|
148
|
+
if (nativeEncoder) {
|
|
149
|
+
const codes = encodeAscii(str, E_CHAR)
|
|
150
|
+
for (; i < mainLength; i += 4) {
|
|
151
|
+
const c0 = codes[i]
|
|
152
|
+
const c1 = codes[i + 1]
|
|
153
|
+
const c2 = codes[i + 2]
|
|
154
|
+
const c3 = codes[i + 3]
|
|
155
|
+
const a = (m[c0] << 18) | (m[c1] << 12) | (m[c2] << 6) | m[c3]
|
|
156
|
+
if (a < 0) throw new SyntaxError(E_CHAR)
|
|
157
|
+
arr[at] = a >> 16
|
|
158
|
+
arr[at + 1] = (a >> 8) & 0xff
|
|
159
|
+
arr[at + 2] = a & 0xff
|
|
160
|
+
at += 3
|
|
161
|
+
}
|
|
162
|
+
} else {
|
|
163
|
+
for (; i < mainLength; i += 4) {
|
|
164
|
+
const c0 = str.charCodeAt(i)
|
|
165
|
+
const c1 = str.charCodeAt(i + 1)
|
|
166
|
+
const c2 = str.charCodeAt(i + 2)
|
|
167
|
+
const c3 = str.charCodeAt(i + 3)
|
|
168
|
+
const a = (m[c0] << 18) | (m[c1] << 12) | (m[c2] << 6) | m[c3]
|
|
169
|
+
if (a < 0) throw new SyntaxError(E_CHAR)
|
|
170
|
+
arr[at] = a >> 16
|
|
171
|
+
arr[at + 1] = (a >> 8) & 0xff
|
|
172
|
+
arr[at + 2] = a & 0xff
|
|
173
|
+
at += 3
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Can be 0, 2 or 3, verified by padding checks already
|
|
178
|
+
if (tailLength < 2) return arr // 0
|
|
179
|
+
const ab = (m[str.charCodeAt(i++)] << 6) | m[str.charCodeAt(i++)]
|
|
180
|
+
if (ab < 0) throw new SyntaxError(E_CHAR)
|
|
181
|
+
arr[at++] = ab >> 4
|
|
182
|
+
if (tailLength < 3) {
|
|
183
|
+
if (ab & 0xf) throw new SyntaxError(E_LAST)
|
|
184
|
+
return arr // 2
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const c = m[str.charCodeAt(i++)]
|
|
188
|
+
if (c < 0) throw new SyntaxError(E_CHAR)
|
|
189
|
+
arr[at++] = ((ab << 4) & 0xff) | (c >> 2)
|
|
190
|
+
if (c & 0x3) throw new SyntaxError(E_LAST)
|
|
191
|
+
return arr // 3
|
|
192
|
+
}
|