1 | 'use strict';
|
---|
2 | var globalThis = require('../internals/global-this');
|
---|
3 | var uncurryThis = require('../internals/function-uncurry-this');
|
---|
4 | var anObjectOrUndefined = require('../internals/an-object-or-undefined');
|
---|
5 | var aString = require('../internals/a-string');
|
---|
6 | var hasOwn = require('../internals/has-own-property');
|
---|
7 | var base64Map = require('../internals/base64-map');
|
---|
8 | var getAlphabetOption = require('../internals/get-alphabet-option');
|
---|
9 | var notDetached = require('../internals/array-buffer-not-detached');
|
---|
10 |
|
---|
11 | var base64Alphabet = base64Map.c2i;
|
---|
12 | var base64UrlAlphabet = base64Map.c2iUrl;
|
---|
13 |
|
---|
14 | var SyntaxError = globalThis.SyntaxError;
|
---|
15 | var TypeError = globalThis.TypeError;
|
---|
16 | var at = uncurryThis(''.charAt);
|
---|
17 |
|
---|
18 | var skipAsciiWhitespace = function (string, index) {
|
---|
19 | var length = string.length;
|
---|
20 | for (;index < length; index++) {
|
---|
21 | var chr = at(string, index);
|
---|
22 | if (chr !== ' ' && chr !== '\t' && chr !== '\n' && chr !== '\f' && chr !== '\r') break;
|
---|
23 | } return index;
|
---|
24 | };
|
---|
25 |
|
---|
26 | var decodeBase64Chunk = function (chunk, alphabet, throwOnExtraBits) {
|
---|
27 | var chunkLength = chunk.length;
|
---|
28 |
|
---|
29 | if (chunkLength < 4) {
|
---|
30 | chunk += chunkLength === 2 ? 'AA' : 'A';
|
---|
31 | }
|
---|
32 |
|
---|
33 | var triplet = (alphabet[at(chunk, 0)] << 18)
|
---|
34 | + (alphabet[at(chunk, 1)] << 12)
|
---|
35 | + (alphabet[at(chunk, 2)] << 6)
|
---|
36 | + alphabet[at(chunk, 3)];
|
---|
37 |
|
---|
38 | var chunkBytes = [
|
---|
39 | (triplet >> 16) & 255,
|
---|
40 | (triplet >> 8) & 255,
|
---|
41 | triplet & 255
|
---|
42 | ];
|
---|
43 |
|
---|
44 | if (chunkLength === 2) {
|
---|
45 | if (throwOnExtraBits && chunkBytes[1] !== 0) {
|
---|
46 | throw new SyntaxError('Extra bits');
|
---|
47 | }
|
---|
48 | return [chunkBytes[0]];
|
---|
49 | }
|
---|
50 |
|
---|
51 | if (chunkLength === 3) {
|
---|
52 | if (throwOnExtraBits && chunkBytes[2] !== 0) {
|
---|
53 | throw new SyntaxError('Extra bits');
|
---|
54 | }
|
---|
55 | return [chunkBytes[0], chunkBytes[1]];
|
---|
56 | }
|
---|
57 |
|
---|
58 | return chunkBytes;
|
---|
59 | };
|
---|
60 |
|
---|
61 | var writeBytes = function (bytes, elements, written) {
|
---|
62 | var elementsLength = elements.length;
|
---|
63 | for (var index = 0; index < elementsLength; index++) {
|
---|
64 | bytes[written + index] = elements[index];
|
---|
65 | }
|
---|
66 | return written + elementsLength;
|
---|
67 | };
|
---|
68 |
|
---|
69 | /* eslint-disable max-statements, max-depth -- TODO */
|
---|
70 | module.exports = function (string, options, into, maxLength) {
|
---|
71 | aString(string);
|
---|
72 | anObjectOrUndefined(options);
|
---|
73 | var alphabet = getAlphabetOption(options) === 'base64' ? base64Alphabet : base64UrlAlphabet;
|
---|
74 | var lastChunkHandling = options ? options.lastChunkHandling : undefined;
|
---|
75 |
|
---|
76 | if (lastChunkHandling === undefined) lastChunkHandling = 'loose';
|
---|
77 |
|
---|
78 | if (lastChunkHandling !== 'loose' && lastChunkHandling !== 'strict' && lastChunkHandling !== 'stop-before-partial') {
|
---|
79 | throw new TypeError('Incorrect `lastChunkHandling` option');
|
---|
80 | }
|
---|
81 |
|
---|
82 | if (into) notDetached(into.buffer);
|
---|
83 |
|
---|
84 | var bytes = into || [];
|
---|
85 | var written = 0;
|
---|
86 | var read = 0;
|
---|
87 | var chunk = '';
|
---|
88 | var index = 0;
|
---|
89 |
|
---|
90 | if (maxLength) while (true) {
|
---|
91 | index = skipAsciiWhitespace(string, index);
|
---|
92 | if (index === string.length) {
|
---|
93 | if (chunk.length > 0) {
|
---|
94 | if (lastChunkHandling === 'stop-before-partial') {
|
---|
95 | break;
|
---|
96 | }
|
---|
97 | if (lastChunkHandling === 'loose') {
|
---|
98 | if (chunk.length === 1) {
|
---|
99 | throw new SyntaxError('Malformed padding: exactly one additional character');
|
---|
100 | }
|
---|
101 | written = writeBytes(bytes, decodeBase64Chunk(chunk, alphabet, false), written);
|
---|
102 | } else {
|
---|
103 | throw new SyntaxError('Missing padding');
|
---|
104 | }
|
---|
105 | }
|
---|
106 | read = string.length;
|
---|
107 | break;
|
---|
108 | }
|
---|
109 | var chr = at(string, index);
|
---|
110 | ++index;
|
---|
111 | if (chr === '=') {
|
---|
112 | if (chunk.length < 2) {
|
---|
113 | throw new SyntaxError('Padding is too early');
|
---|
114 | }
|
---|
115 | index = skipAsciiWhitespace(string, index);
|
---|
116 | if (chunk.length === 2) {
|
---|
117 | if (index === string.length) {
|
---|
118 | if (lastChunkHandling === 'stop-before-partial') {
|
---|
119 | break;
|
---|
120 | }
|
---|
121 | throw new SyntaxError('Malformed padding: only one =');
|
---|
122 | }
|
---|
123 | if (at(string, index) === '=') {
|
---|
124 | ++index;
|
---|
125 | index = skipAsciiWhitespace(string, index);
|
---|
126 | }
|
---|
127 | }
|
---|
128 | if (index < string.length) {
|
---|
129 | throw new SyntaxError('Unexpected character after padding');
|
---|
130 | }
|
---|
131 | written = writeBytes(bytes, decodeBase64Chunk(chunk, alphabet, lastChunkHandling === 'strict'), written);
|
---|
132 | read = string.length;
|
---|
133 | break;
|
---|
134 | }
|
---|
135 | if (!hasOwn(alphabet, chr)) {
|
---|
136 | throw new SyntaxError('Unexpected character');
|
---|
137 | }
|
---|
138 | var remainingBytes = maxLength - written;
|
---|
139 | if (remainingBytes === 1 && chunk.length === 2 || remainingBytes === 2 && chunk.length === 3) {
|
---|
140 | // special case: we can fit exactly the number of bytes currently represented by chunk, so we were just checking for `=`
|
---|
141 | break;
|
---|
142 | }
|
---|
143 |
|
---|
144 | chunk += chr;
|
---|
145 | if (chunk.length === 4) {
|
---|
146 | written = writeBytes(bytes, decodeBase64Chunk(chunk, alphabet, false), written);
|
---|
147 | chunk = '';
|
---|
148 | read = index;
|
---|
149 | if (written === maxLength) {
|
---|
150 | break;
|
---|
151 | }
|
---|
152 | }
|
---|
153 | }
|
---|
154 |
|
---|
155 | return { bytes: bytes, read: read, written: written };
|
---|
156 | };
|
---|