You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
157 lines
4.8 KiB
157 lines
4.8 KiB
'use strict';
|
|
var globalThis = require('../internals/global-this');
|
|
var uncurryThis = require('../internals/function-uncurry-this');
|
|
var anObjectOrUndefined = require('../internals/an-object-or-undefined');
|
|
var aString = require('../internals/a-string');
|
|
var hasOwn = require('../internals/has-own-property');
|
|
var base64Map = require('../internals/base64-map');
|
|
var getAlphabetOption = require('../internals/get-alphabet-option');
|
|
var notDetached = require('../internals/array-buffer-not-detached');
|
|
|
|
var base64Alphabet = base64Map.c2i;
|
|
var base64UrlAlphabet = base64Map.c2iUrl;
|
|
|
|
var SyntaxError = globalThis.SyntaxError;
|
|
var TypeError = globalThis.TypeError;
|
|
var at = uncurryThis(''.charAt);
|
|
|
|
var skipAsciiWhitespace = function (string, index) {
|
|
var length = string.length;
|
|
for (;index < length; index++) {
|
|
var chr = at(string, index);
|
|
if (chr !== ' ' && chr !== '\t' && chr !== '\n' && chr !== '\f' && chr !== '\r') break;
|
|
} return index;
|
|
};
|
|
|
|
var decodeBase64Chunk = function (chunk, alphabet, throwOnExtraBits) {
|
|
var chunkLength = chunk.length;
|
|
|
|
if (chunkLength < 4) {
|
|
chunk += chunkLength === 2 ? 'AA' : 'A';
|
|
}
|
|
|
|
var triplet = (alphabet[at(chunk, 0)] << 18)
|
|
+ (alphabet[at(chunk, 1)] << 12)
|
|
+ (alphabet[at(chunk, 2)] << 6)
|
|
+ alphabet[at(chunk, 3)];
|
|
|
|
var chunkBytes = [
|
|
(triplet >> 16) & 255,
|
|
(triplet >> 8) & 255,
|
|
triplet & 255
|
|
];
|
|
|
|
if (chunkLength === 2) {
|
|
if (throwOnExtraBits && chunkBytes[1] !== 0) {
|
|
throw new SyntaxError('Extra bits');
|
|
}
|
|
return [chunkBytes[0]];
|
|
}
|
|
|
|
if (chunkLength === 3) {
|
|
if (throwOnExtraBits && chunkBytes[2] !== 0) {
|
|
throw new SyntaxError('Extra bits');
|
|
}
|
|
return [chunkBytes[0], chunkBytes[1]];
|
|
}
|
|
|
|
return chunkBytes;
|
|
};
|
|
|
|
var writeBytes = function (bytes, elements, written) {
|
|
var elementsLength = elements.length;
|
|
for (var index = 0; index < elementsLength; index++) {
|
|
bytes[written + index] = elements[index];
|
|
}
|
|
return written + elementsLength;
|
|
};
|
|
|
|
/* eslint-disable max-statements, max-depth -- TODO */
|
|
module.exports = function (string, options, into, maxLength) {
|
|
aString(string);
|
|
anObjectOrUndefined(options);
|
|
var alphabet = getAlphabetOption(options) === 'base64' ? base64Alphabet : base64UrlAlphabet;
|
|
var lastChunkHandling = options ? options.lastChunkHandling : undefined;
|
|
|
|
if (lastChunkHandling === undefined) lastChunkHandling = 'loose';
|
|
|
|
if (lastChunkHandling !== 'loose' && lastChunkHandling !== 'strict' && lastChunkHandling !== 'stop-before-partial') {
|
|
throw new TypeError('Incorrect `lastChunkHandling` option');
|
|
}
|
|
|
|
if (into) notDetached(into.buffer);
|
|
|
|
var bytes = into || [];
|
|
var written = 0;
|
|
var read = 0;
|
|
var chunk = '';
|
|
var index = 0;
|
|
|
|
if (maxLength) while (true) {
|
|
index = skipAsciiWhitespace(string, index);
|
|
if (index === string.length) {
|
|
if (chunk.length > 0) {
|
|
if (lastChunkHandling === 'stop-before-partial') {
|
|
break;
|
|
}
|
|
if (lastChunkHandling === 'loose') {
|
|
if (chunk.length === 1) {
|
|
throw new SyntaxError('Malformed padding: exactly one additional character');
|
|
}
|
|
written = writeBytes(bytes, decodeBase64Chunk(chunk, alphabet, false), written);
|
|
} else {
|
|
throw new SyntaxError('Missing padding');
|
|
}
|
|
}
|
|
read = string.length;
|
|
break;
|
|
}
|
|
var chr = at(string, index);
|
|
++index;
|
|
if (chr === '=') {
|
|
if (chunk.length < 2) {
|
|
throw new SyntaxError('Padding is too early');
|
|
}
|
|
index = skipAsciiWhitespace(string, index);
|
|
if (chunk.length === 2) {
|
|
if (index === string.length) {
|
|
if (lastChunkHandling === 'stop-before-partial') {
|
|
break;
|
|
}
|
|
throw new SyntaxError('Malformed padding: only one =');
|
|
}
|
|
if (at(string, index) === '=') {
|
|
++index;
|
|
index = skipAsciiWhitespace(string, index);
|
|
}
|
|
}
|
|
if (index < string.length) {
|
|
throw new SyntaxError('Unexpected character after padding');
|
|
}
|
|
written = writeBytes(bytes, decodeBase64Chunk(chunk, alphabet, lastChunkHandling === 'strict'), written);
|
|
read = string.length;
|
|
break;
|
|
}
|
|
if (!hasOwn(alphabet, chr)) {
|
|
throw new SyntaxError('Unexpected character');
|
|
}
|
|
var remainingBytes = maxLength - written;
|
|
if (remainingBytes === 1 && chunk.length === 2 || remainingBytes === 2 && chunk.length === 3) {
|
|
// special case: we can fit exactly the number of bytes currently represented by chunk, so we were just checking for `=`
|
|
break;
|
|
}
|
|
|
|
chunk += chr;
|
|
if (chunk.length === 4) {
|
|
written = writeBytes(bytes, decodeBase64Chunk(chunk, alphabet, false), written);
|
|
chunk = '';
|
|
read = index;
|
|
if (written === maxLength) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return { bytes: bytes, read: read, written: written };
|
|
};
|