Line data Source code
1 1 : export const BASE64_TABLE_DEFAULT = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
2 1 : export const BASE64_TABLE_URL = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
3 :
4 1 : export class Base64Codec {
5 1 : lookupTable: Uint8Array;
6 :
7 1 : constructor() {
8 : // Build a lookup query_result
9 1 : this.lookupTable = new Uint8Array(256);
10 1 : this.lookupTable.fill(0xFF);
11 1 : for (let i = 0; i < BASE64_TABLE_DEFAULT.length; i++) {
12 1 : this.lookupTable[BASE64_TABLE_DEFAULT.charCodeAt(i)] = i;
13 : }
14 1 : }
15 :
16 : /// Encode the ArrayBuffer
17 1 : public encode(arraybuffer: ArrayBuffer) {
18 1 : const bytes = new Uint8Array(arraybuffer);
19 1 : let base64 = "";
20 :
21 1 : let reader = 0;
22 1 : for (; (reader + 3) <= bytes.length; reader += 3) {
23 : // Upper 6 bits of first bytes
24 1 : base64 += BASE64_TABLE_DEFAULT[bytes[reader] >> 2];
25 : // Lower 2 bits of first byte and upper 4 bits of second byte
26 1 : base64 += BASE64_TABLE_DEFAULT[((bytes[reader] & 3) << 4) | (bytes[reader + 1] >> 4)];
27 : // Lower 4 bits of second byte and upper 2 bits of third byte
28 1 : base64 += BASE64_TABLE_DEFAULT[((bytes[reader + 1] & 15) << 2) | (bytes[reader + 2] >> 6)];
29 : // Lower 4 bits of third byte
30 1 : base64 += BASE64_TABLE_DEFAULT[bytes[reader + 2] & 63];
31 1 : }
32 : // Now map the remainder
33 1 : const leftOver = bytes.length - reader;
34 1 : switch (leftOver) {
35 : // 2 bytes left, pack into 3 bytes with trailing zero
36 1 : case 2:
37 1 : base64 += BASE64_TABLE_DEFAULT[bytes[reader] >> 2];
38 1 : base64 += BASE64_TABLE_DEFAULT[((bytes[reader] & 3) << 4) | (bytes[reader + 1] >> 4)];
39 1 : base64 += BASE64_TABLE_DEFAULT[(bytes[reader + 1] & 15) << 2];
40 1 : base64 += "=";
41 1 : break;
42 : // 1 bytes left, pack into 2 bytes with trailing zero
43 1 : case 1:
44 1 : base64 += BASE64_TABLE_DEFAULT[bytes[reader] >> 2];
45 1 : base64 += BASE64_TABLE_DEFAULT[(bytes[reader] & 3) << 4];
46 1 : base64 += "==";
47 : break;
48 : case 0:
49 : break;
50 :
51 1 : }
52 1 : return base64;
53 1 : };
54 :
55 : /// Check if the string is a valid base64 sequence
56 1 : public isValidBase64(base64: string) {
57 1 : let valid = true;
58 1 : let dataLength = base64.length;
59 1 : if (base64[base64.length - 1] === "=") {
60 1 : dataLength--;
61 1 : if (base64[base64.length - 2] === "=") {
62 1 : dataLength--;
63 : }
64 1 : }
65 1 : for (let i = 0; i < dataLength; ++i) {
66 1 : const c = base64.charCodeAt(i);
67 1 : valid &&= this.lookupTable[c] != 0xFF;
68 1 : }
69 1 : return valid;
70 1 : }
71 :
72 : /// Decode a Base64 string
73 1 : public decode(base64: string) {
74 : // Cut the trailing equal signs
75 1 : let bufferLength = Math.floor((base64.length / 4) * 3);
76 1 : if (base64.length >= 1 && base64[base64.length - 1] === "=") {
77 1 : bufferLength--;
78 1 : if (base64.length >= 2 && base64[base64.length - 2] === "=") {
79 1 : bufferLength--;
80 : }
81 1 : }
82 :
83 : /// Allocate the ArrayBuffer
84 1 : const arraybuffer = new ArrayBuffer(bufferLength),
85 1 : bytes = new Uint8Array(arraybuffer);
86 :
87 1 : let writer = 0;
88 1 : let reader = 0;
89 1 : for (; (reader + 4) <= base64.length; reader += 4) {
90 : // Read all bytes, storing 6 bits of data each
91 1 : const encoded1 = this.lookupTable[base64.charCodeAt(reader)];
92 1 : const encoded2 = this.lookupTable[base64.charCodeAt(reader + 1)];
93 1 : const encoded3 = this.lookupTable[base64.charCodeAt(reader + 2)];
94 1 : const encoded4 = this.lookupTable[base64.charCodeAt(reader + 3)];
95 :
96 : // Reconstruct original bytes from 6 bits
97 1 : bytes[writer++] = (encoded1 << 2) | (encoded2 >> 4);
98 1 : bytes[writer++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);
99 1 : bytes[writer++] = ((encoded3 & 3) << 6) | (encoded4 & 63);
100 1 : }
101 :
102 : // Read the remainder
103 1 : const leftOver = base64.length - reader;
104 1 : switch (leftOver) {
105 0 : case 3: {
106 0 : const encoded1 = this.lookupTable[base64.charCodeAt(reader)];
107 0 : const encoded2 = this.lookupTable[base64.charCodeAt(reader + 1)];
108 0 : const encoded3 = this.lookupTable[base64.charCodeAt(reader + 2)];
109 0 : bytes[writer++] = (encoded1 << 2) | (encoded2 >> 4);
110 0 : bytes[writer++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);
111 0 : break;
112 0 : }
113 0 : case 2: {
114 0 : const encoded1 = this.lookupTable[base64.charCodeAt(reader)];
115 0 : const encoded2 = this.lookupTable[base64.charCodeAt(reader + 1)];
116 0 : bytes[writer++] = (encoded1 << 2) | (encoded2 >> 4);
117 0 : break;
118 0 : }
119 : case 1: {
120 : // We only ever consume "full" 8 bits from the input.
121 : // 1 base64 character only stores 6 data bits, so we just skip
122 : break;
123 : }
124 1 : }
125 :
126 1 : return arraybuffer;
127 1 : };
128 1 : };
129 :
130 1 : export class Base64UrlCodec {
131 1 : lookupTable: Uint8Array;
132 :
133 1 : constructor() {
134 : // Build a lookup query_result
135 1 : this.lookupTable = new Uint8Array(256);
136 1 : this.lookupTable.fill(0xFF);
137 1 : for (let i = 0; i < BASE64_TABLE_URL.length; i++) {
138 1 : this.lookupTable[BASE64_TABLE_URL.charCodeAt(i)] = i;
139 : }
140 1 : }
141 :
142 : /// Encode the ArrayBuffer as Base64URL
143 1 : public encode(arraybuffer: ArrayBuffer) {
144 1 : const bytes = new Uint8Array(arraybuffer);
145 1 : let base64 = "";
146 :
147 : // Base64 maps 6 input bits to 1 output byte (8 bits).
148 : // First map 3 bytes at once (24 bits), to 32 output bits (4 bytes).
149 1 : let reader = 0;
150 1 : for (; (reader + 3) <= bytes.length; reader += 3) {
151 : // Upper 6 bits of first byte
152 1 : base64 += BASE64_TABLE_URL[bytes[reader] >> 2];
153 : // Lower 2 bits of first byte and upper 4 bits of second byte
154 1 : base64 += BASE64_TABLE_URL[((bytes[reader] & 3) << 4) | (bytes[reader + 1] >> 4)];
155 : // Lower 4 bits of second byte and upper 2 bits of third byte
156 1 : base64 += BASE64_TABLE_URL[((bytes[reader + 1] & 15) << 2) | (bytes[reader + 2] >> 6)];
157 : // Lower 4 bits of third byte
158 1 : base64 += BASE64_TABLE_URL[bytes[reader + 2] & 63];
159 1 : }
160 : // Now map the remainder
161 1 : const leftOver = bytes.length - reader;
162 1 : switch (leftOver) {
163 : // 2 bytes left, pack into 3 bytes with trailing zero
164 1 : case 2:
165 1 : base64 += BASE64_TABLE_URL[bytes[reader] >> 2];
166 1 : base64 += BASE64_TABLE_URL[((bytes[reader] & 3) << 4) | (bytes[reader + 1] >> 4)];
167 1 : base64 += BASE64_TABLE_URL[(bytes[reader + 1] & 15) << 2];
168 1 : break;
169 : // 1 bytes left, pack into 2 bytes with trailing zero
170 1 : case 1:
171 1 : base64 += BASE64_TABLE_URL[bytes[reader] >> 2];
172 1 : base64 += BASE64_TABLE_URL[(bytes[reader] & 3) << 4];
173 : break;
174 : case 0:
175 : break;
176 :
177 1 : }
178 1 : return base64;
179 1 : };
180 :
181 : /// Check if the string is a valid base64 sequence
182 1 : public isValidBase64(base64: string) {
183 1 : let valid = true;
184 1 : for (let i = 0; i < base64.length; ++i) {
185 1 : const c = base64.charCodeAt(i);
186 1 : valid &&= this.lookupTable[c] != 0xFF;
187 1 : }
188 1 : return valid;
189 1 : }
190 :
191 : /// Get the decoded size
192 1 : public getDecodedSize(base64: string) {
193 1 : const rem = base64.length % 4;
194 1 : return Math.floor(base64.length / 4) * 3 + (rem >= 3 ? 2 : rem >= 2 ? 1 : 0);
195 1 : }
196 :
197 : /// Decode a Base64URL string
198 1 : public decode(base64: string) {
199 : /// Allocate the ArrayBuffer
200 1 : const bufferSize = this.getDecodedSize(base64);
201 1 : const buffer = new ArrayBuffer(bufferSize);
202 1 : const bytes = new Uint8Array(buffer);
203 :
204 1 : let writer = 0;
205 1 : let reader = 0;
206 1 : for (; (reader + 4) <= base64.length; reader += 4) {
207 : // Read all bytes, storing 6 bits of data each
208 1 : const encoded1 = this.lookupTable[base64.charCodeAt(reader)];
209 1 : const encoded2 = this.lookupTable[base64.charCodeAt(reader + 1)];
210 1 : const encoded3 = this.lookupTable[base64.charCodeAt(reader + 2)];
211 1 : const encoded4 = this.lookupTable[base64.charCodeAt(reader + 3)];
212 :
213 : // Reconstruct original bytes from 6 bits
214 1 : bytes[writer++] = (encoded1 << 2) | (encoded2 >> 4);
215 1 : bytes[writer++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);
216 1 : bytes[writer++] = ((encoded3 & 3) << 6) | (encoded4 & 63);
217 1 : }
218 :
219 : // Read the remainder
220 1 : const leftOver = base64.length - reader;
221 1 : switch (leftOver) {
222 1 : case 3: {
223 1 : const encoded1 = this.lookupTable[base64.charCodeAt(reader)];
224 1 : const encoded2 = this.lookupTable[base64.charCodeAt(reader + 1)];
225 1 : const encoded3 = this.lookupTable[base64.charCodeAt(reader + 2)];
226 1 : bytes[writer++] = (encoded1 << 2) | (encoded2 >> 4);
227 1 : bytes[writer++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);
228 1 : break;
229 1 : }
230 1 : case 2: {
231 1 : const encoded1 = this.lookupTable[base64.charCodeAt(reader)];
232 1 : const encoded2 = this.lookupTable[base64.charCodeAt(reader + 1)];
233 1 : bytes[writer++] = (encoded1 << 2) | (encoded2 >> 4);
234 1 : break;
235 1 : }
236 : case 1: {
237 : // We only ever consume "full" 8 bits from the input.
238 : // 1 base64 character only stores 6 data bits, so we just skip
239 : break;
240 : }
241 1 : }
242 :
243 1 : return buffer;
244 1 : };
245 1 : };
246 :
247 1 : export const BASE64_CODEC = new Base64Codec();
248 1 : export const BASE64URL_CODEC = new Base64UrlCodec();
|