2016-11-28 11:42:58 +01:00
|
|
|
/**
|
2018-04-02 18:10:51 +02:00
|
|
|
* Base64 functions.
|
2016-11-28 11:42:58 +01:00
|
|
|
*
|
|
|
|
* @author n1474335 [n1474335@gmail.com]
|
|
|
|
* @copyright Crown Copyright 2016
|
|
|
|
* @license Apache-2.0
|
|
|
|
*/
|
2016-12-14 17:39:17 +01:00
|
|
|
|
2019-07-09 13:23:59 +02:00
|
|
|
import Utils from "../Utils.mjs";
|
2019-10-15 17:25:52 +02:00
|
|
|
import OperationError from "../errors/OperationError.mjs";
|
2016-12-14 17:39:17 +01:00
|
|
|
|
2018-04-02 18:10:51 +02:00
|
|
|
/**
|
|
|
|
* Base64's the input byte array using the given alphabet, returning a string.
|
|
|
|
*
|
2019-09-04 14:54:59 +02:00
|
|
|
* @param {byteArray|Uint8Array|ArrayBuffer|string} data
|
2018-04-02 18:10:51 +02:00
|
|
|
* @param {string} [alphabet="A-Za-z0-9+/="]
|
|
|
|
* @returns {string}
|
|
|
|
*
|
|
|
|
* @example
|
|
|
|
* // returns "SGVsbG8="
|
|
|
|
* toBase64([72, 101, 108, 108, 111]);
|
|
|
|
*
|
|
|
|
* // returns "SGVsbG8="
|
|
|
|
* toBase64("Hello");
|
|
|
|
*/
|
|
|
|
export function toBase64(data, alphabet="A-Za-z0-9+/=") {
|
|
|
|
if (!data) return "";
|
2023-02-03 18:10:33 +01:00
|
|
|
if (typeof data == "string") {
|
|
|
|
data = Utils.strToArrayBuffer(data);
|
|
|
|
}
|
2019-09-04 14:54:59 +02:00
|
|
|
if (data instanceof ArrayBuffer) {
|
|
|
|
data = new Uint8Array(data);
|
|
|
|
}
|
2018-04-02 18:10:51 +02:00
|
|
|
|
|
|
|
alphabet = Utils.expandAlphRange(alphabet).join("");
|
2019-10-15 17:25:52 +02:00
|
|
|
if (alphabet.length !== 64 && alphabet.length !== 65) { // Allow for padding
|
|
|
|
throw new OperationError(`Invalid Base64 alphabet length (${alphabet.length}): ${alphabet}`);
|
|
|
|
}
|
2018-04-02 18:10:51 +02:00
|
|
|
|
|
|
|
let output = "",
|
|
|
|
chr1, chr2, chr3,
|
|
|
|
enc1, enc2, enc3, enc4,
|
|
|
|
i = 0;
|
|
|
|
|
|
|
|
while (i < data.length) {
|
|
|
|
chr1 = data[i++];
|
|
|
|
chr2 = data[i++];
|
|
|
|
chr3 = data[i++];
|
|
|
|
|
|
|
|
enc1 = chr1 >> 2;
|
|
|
|
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
|
|
|
|
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
|
|
|
|
enc4 = chr3 & 63;
|
|
|
|
|
|
|
|
if (isNaN(chr2)) {
|
|
|
|
enc3 = enc4 = 64;
|
|
|
|
} else if (isNaN(chr3)) {
|
|
|
|
enc4 = 64;
|
2016-11-28 11:42:58 +01:00
|
|
|
}
|
|
|
|
|
2018-04-02 18:10:51 +02:00
|
|
|
output += alphabet.charAt(enc1) + alphabet.charAt(enc2) +
|
|
|
|
alphabet.charAt(enc3) + alphabet.charAt(enc4);
|
|
|
|
}
|
2016-11-28 11:42:58 +01:00
|
|
|
|
2018-04-02 18:10:51 +02:00
|
|
|
return output;
|
|
|
|
}
|
2016-11-28 11:42:58 +01:00
|
|
|
|
2016-12-14 17:39:17 +01:00
|
|
|
|
2018-04-02 18:10:51 +02:00
|
|
|
/**
|
|
|
|
* UnBase64's the input string using the given alphabet, returning a byte array.
|
|
|
|
*
|
2019-07-29 18:09:46 +02:00
|
|
|
* @param {string} data
|
2018-04-02 18:10:51 +02:00
|
|
|
* @param {string} [alphabet="A-Za-z0-9+/="]
|
|
|
|
* @param {string} [returnType="string"] - Either "string" or "byteArray"
|
|
|
|
* @param {boolean} [removeNonAlphChars=true]
|
|
|
|
* @returns {byteArray}
|
|
|
|
*
|
|
|
|
* @example
|
|
|
|
* // returns "Hello"
|
|
|
|
* fromBase64("SGVsbG8=");
|
|
|
|
*
|
|
|
|
* // returns [72, 101, 108, 108, 111]
|
|
|
|
* fromBase64("SGVsbG8=", null, "byteArray");
|
|
|
|
*/
|
2022-06-03 22:41:37 +02:00
|
|
|
export function fromBase64(data, alphabet="A-Za-z0-9+/=", returnType="string", removeNonAlphChars=true, strictMode=false) {
|
2018-04-02 18:10:51 +02:00
|
|
|
if (!data) {
|
|
|
|
return returnType === "string" ? "" : [];
|
|
|
|
}
|
2016-12-14 17:39:17 +01:00
|
|
|
|
2018-08-24 01:32:52 +02:00
|
|
|
alphabet = alphabet || "A-Za-z0-9+/=";
|
2018-04-02 18:10:51 +02:00
|
|
|
alphabet = Utils.expandAlphRange(alphabet).join("");
|
2022-06-03 22:41:37 +02:00
|
|
|
|
|
|
|
// Confirm alphabet is a valid length
|
2019-10-15 17:25:52 +02:00
|
|
|
if (alphabet.length !== 64 && alphabet.length !== 65) { // Allow for padding
|
2022-06-03 22:41:37 +02:00
|
|
|
throw new OperationError(`Error: Base64 alphabet should be 64 characters long, or 65 with a padding character. Found ${alphabet.length}: ${alphabet}`);
|
2019-10-15 17:25:52 +02:00
|
|
|
}
|
2016-12-14 17:39:17 +01:00
|
|
|
|
2022-06-03 22:41:37 +02:00
|
|
|
// Remove non-alphabet characters
|
2018-04-02 18:10:51 +02:00
|
|
|
if (removeNonAlphChars) {
|
|
|
|
const re = new RegExp("[^" + alphabet.replace(/[[\]\\\-^$]/g, "\\$&") + "]", "g");
|
|
|
|
data = data.replace(re, "");
|
|
|
|
}
|
2016-12-14 17:39:17 +01:00
|
|
|
|
2022-06-03 22:41:37 +02:00
|
|
|
if (strictMode) {
|
|
|
|
// Check for incorrect lengths (even without padding)
|
|
|
|
if (data.length % 4 === 1) {
|
|
|
|
throw new OperationError(`Error: Invalid Base64 input length (${data.length}). Cannot be 4n+1, even without padding chars.`);
|
|
|
|
}
|
2016-12-14 17:39:17 +01:00
|
|
|
|
2022-06-03 22:41:37 +02:00
|
|
|
if (alphabet.length === 65) { // Padding character included
|
|
|
|
const pad = alphabet.charAt(64);
|
|
|
|
const padPos = data.indexOf(pad);
|
|
|
|
if (padPos >= 0) {
|
|
|
|
// Check that the padding character is only used at the end and maximum of twice
|
|
|
|
if (padPos < data.length - 2 || data.charAt(data.length - 1) !== pad) {
|
|
|
|
throw new OperationError(`Error: Base64 padding character (${pad}) not used in the correct place.`);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check that input is padded to the correct length
|
|
|
|
if (data.length % 4 !== 0) {
|
|
|
|
throw new OperationError("Error: Base64 not padded to a multiple of 4.");
|
|
|
|
}
|
2021-10-04 15:39:16 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-03 22:41:37 +02:00
|
|
|
const output = [];
|
|
|
|
let chr1, chr2, chr3,
|
|
|
|
enc1, enc2, enc3, enc4,
|
|
|
|
i = 0;
|
|
|
|
|
2021-10-04 15:39:16 +02:00
|
|
|
while (i < data.length) {
|
2022-06-06 15:54:06 +02:00
|
|
|
// Including `|| null` forces empty strings to null so that indexOf returns -1 instead of 0
|
|
|
|
enc1 = alphabet.indexOf(data.charAt(i++) || null);
|
|
|
|
enc2 = alphabet.indexOf(data.charAt(i++) || null);
|
|
|
|
enc3 = alphabet.indexOf(data.charAt(i++) || null);
|
|
|
|
enc4 = alphabet.indexOf(data.charAt(i++) || null);
|
2022-06-03 22:41:37 +02:00
|
|
|
|
|
|
|
if (strictMode && (enc1 < 0 || enc2 < 0 || enc3 < 0 || enc4 < 0)) {
|
|
|
|
throw new OperationError("Error: Base64 input contains non-alphabet char(s)");
|
2021-10-04 15:39:16 +02:00
|
|
|
}
|
2016-12-14 17:39:17 +01:00
|
|
|
|
2018-04-02 18:10:51 +02:00
|
|
|
chr1 = (enc1 << 2) | (enc2 >> 4);
|
|
|
|
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
|
|
|
|
chr3 = ((enc3 & 3) << 6) | enc4;
|
2016-12-14 17:39:17 +01:00
|
|
|
|
2022-06-06 15:54:06 +02:00
|
|
|
if (chr1 >= 0 && chr1 < 256) {
|
2022-06-03 22:41:37 +02:00
|
|
|
output.push(chr1);
|
|
|
|
}
|
2022-06-06 15:54:06 +02:00
|
|
|
if (chr2 >= 0 && chr2 < 256 && enc3 !== 64) {
|
2018-04-02 18:10:51 +02:00
|
|
|
output.push(chr2);
|
2016-11-28 11:42:58 +01:00
|
|
|
}
|
2022-06-06 15:54:06 +02:00
|
|
|
if (chr3 >= 0 && chr3 < 256 && enc4 !== 64) {
|
2018-04-02 18:10:51 +02:00
|
|
|
output.push(chr3);
|
2016-11-28 11:42:58 +01:00
|
|
|
}
|
2018-04-02 18:10:51 +02:00
|
|
|
}
|
2016-12-14 17:39:17 +01:00
|
|
|
|
2018-04-02 18:10:51 +02:00
|
|
|
return returnType === "string" ? Utils.byteArrayToUtf8(output) : output;
|
|
|
|
}
|
2016-12-14 17:39:17 +01:00
|
|
|
|
2018-03-27 00:14:23 +02:00
|
|
|
|
2018-04-02 18:10:51 +02:00
|
|
|
/**
|
|
|
|
* Base64 alphabets.
|
|
|
|
*/
|
2018-03-27 00:14:23 +02:00
|
|
|
export const ALPHABET_OPTIONS = [
|
2018-11-19 15:34:52 +01:00
|
|
|
{name: "Standard (RFC 4648): A-Za-z0-9+/=", value: "A-Za-z0-9+/="},
|
|
|
|
{name: "URL safe (RFC 4648 \u00A75): A-Za-z0-9-_", value: "A-Za-z0-9-_"},
|
2018-03-27 00:14:23 +02:00
|
|
|
{name: "Filename safe: A-Za-z0-9+-=", value: "A-Za-z0-9+\\-="},
|
|
|
|
{name: "itoa64: ./0-9A-Za-z=", value: "./0-9A-Za-z="},
|
|
|
|
{name: "XML: A-Za-z0-9_.", value: "A-Za-z0-9_."},
|
|
|
|
{name: "y64: A-Za-z0-9._-", value: "A-Za-z0-9._-"},
|
|
|
|
{name: "z64: 0-9a-zA-Z+/=", value: "0-9a-zA-Z+/="},
|
2018-11-19 15:34:52 +01:00
|
|
|
{name: "Radix-64 (RFC 4880): 0-9A-Za-z+/=", value: "0-9A-Za-z+/="},
|
2018-03-27 00:14:23 +02:00
|
|
|
{name: "Uuencoding: [space]-_", value: " -_"},
|
|
|
|
{name: "Xxencoding: +-0-9A-Za-z", value: "+\\-0-9A-Za-z"},
|
|
|
|
{name: "BinHex: !-,-0-689@A-NP-VX-Z[`a-fh-mp-r", value: "!-,-0-689@A-NP-VX-Z[`a-fh-mp-r"},
|
|
|
|
{name: "ROT13: N-ZA-Mn-za-m0-9+/=", value: "N-ZA-Mn-za-m0-9+/="},
|
|
|
|
{name: "UNIX crypt: ./0-9A-Za-z", value: "./0-9A-Za-z"},
|
2020-01-22 11:35:11 +01:00
|
|
|
{name: "Atom128: /128GhIoPQROSTeUbADfgHijKLM+n0pFWXY456xyzB7=39VaqrstJklmNuZvwcdEC", value: "/128GhIoPQROSTeUbADfgHijKLM+n0pFWXY456xyzB7=39VaqrstJklmNuZvwcdEC"},
|
|
|
|
{name: "Megan35: 3GHIJKLMNOPQRSTUb=cdefghijklmnopWXYZ/12+406789VaqrstuvwxyzABCDEF5", value: "3GHIJKLMNOPQRSTUb=cdefghijklmnopWXYZ/12+406789VaqrstuvwxyzABCDEF5"},
|
|
|
|
{name: "Zong22: ZKj9n+yf0wDVX1s/5YbdxSo=ILaUpPBCHg8uvNO4klm6iJGhQ7eFrWczAMEq3RTt2", value: "ZKj9n+yf0wDVX1s/5YbdxSo=ILaUpPBCHg8uvNO4klm6iJGhQ7eFrWczAMEq3RTt2"},
|
|
|
|
{name: "Hazz15: HNO4klm6ij9n+J2hyf0gzA8uvwDEq3X1Q7ZKeFrWcVTts/MRGYbdxSo=ILaUpPBC5", value: "HNO4klm6ij9n+J2hyf0gzA8uvwDEq3X1Q7ZKeFrWcVTts/MRGYbdxSo=ILaUpPBC5"}
|
2018-03-27 00:14:23 +02:00
|
|
|
];
|