-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathencoding.js
160 lines (141 loc) · 3.63 KB
/
encoding.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
import baseX from 'base-x'
/**
* RFC4648 decode
*
* @param {string} string
* @param {string} alphabet
* @param {number} bitsPerChar
* @param {string} name
* @returns {Uint8Array}
*/
const decode = (string, alphabet, bitsPerChar, name) => {
// Build the character lookup table:
/** @type {Record<string, number>} */
const codes = {}
// eslint-disable-next-line unicorn/no-for-loop
for (let i = 0; i < alphabet.length; ++i) {
codes[alphabet[i]] = i
}
// Count the padding bytes:
let end = string.length
while (string[end - 1] === '=') {
--end
}
// Allocate the output:
const out = new Uint8Array(Math.trunc((end * bitsPerChar) / 8))
// Parse the data:
let bits = 0 // Number of bits currently in the buffer
let buffer = 0 // Bits waiting to be written out, MSB first
let written = 0 // Next byte to write
for (let i = 0; i < end; ++i) {
// Read one character from the string:
const value = codes[string[i]]
if (value === undefined) {
throw new SyntaxError(`Non-${name} character`)
}
// Append the bits to the buffer:
buffer = (buffer << bitsPerChar) | value
bits += bitsPerChar
// Write out some bits if the buffer has a byte's worth:
if (bits >= 8) {
bits -= 8
out[written++] = 0xff & (buffer >> bits)
}
}
// Verify that we have received just enough bits:
if (bits >= bitsPerChar || 0xff & (buffer << (8 - bits))) {
throw new SyntaxError('Unexpected end of data')
}
return out
}
/**
* RFC4648 encode
*
* @param {Uint8Array} data
* @param {string} alphabet
* @param {number} bitsPerChar
* @returns {string}
*/
const encode = (data, alphabet, bitsPerChar) => {
const pad = alphabet[alphabet.length - 1] === '='
const mask = (1 << bitsPerChar) - 1
let out = ''
let bits = 0 // Number of bits currently in the buffer
let buffer = 0 // Bits waiting to be written out, MSB first
for (const datum of data) {
// Slurp data into the buffer:
buffer = (buffer << 8) | datum
bits += 8
// Write out as much as we can:
while (bits > bitsPerChar) {
bits -= bitsPerChar
out += alphabet[mask & (buffer >> bits)]
}
}
// Partial character:
if (bits) {
out += alphabet[mask & (buffer << (bitsPerChar - bits))]
}
// Add padding characters until we hit a byte boundary:
if (pad) {
while ((out.length * bitsPerChar) & 7) {
out += '='
}
}
return out
}
/**
* RFC4648 Factory
*
* @param {Object} options
* @param {string} options.name
* @param {string} options.alphabet
* @param {number} options.bitsPerChar
*/
export const rfc4648 = ({ name, bitsPerChar, alphabet }) => {
return {
/**
* @param {Uint8Array} input
*/
encode(input) {
return encode(input, alphabet, bitsPerChar)
},
/**
* @param {string} input
* @returns {Uint8Array}
*/
decode(input) {
return decode(input, alphabet, bitsPerChar, name)
},
}
}
export const base64Pad = rfc4648({
name: 'base64pad',
bitsPerChar: 6,
alphabet: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=',
})
export const base64url = rfc4648({
name: 'base64url',
bitsPerChar: 6,
alphabet: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_',
})
export const base58btc = baseX(
'123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
)
export const utf8 = {
/**
* @param {Uint8Array} input
*/
encode(input) {
const decoder = new TextDecoder('utf8')
return decoder.decode(input)
},
/**
* @param {string} input
* @returns {Uint8Array}
*/
decode(input) {
const encoder = new TextEncoder()
return encoder.encode(input)
},
}