@i-xi-dev/base64
Version:
A JavaScript Base64 encoder and decoder, implements Forgiving base64 defined in WHATWG Infra Standard.
662 lines (661 loc) • 22.4 kB
JavaScript
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var __DecoderStreamRegulator_pending, __EncoderStreamRegulator_pending;
import { BytesEncoding } from "../deps.js";
const _forgiving = true;
const _BASE64_CHARS = [
"!",
"+",
"-",
".",
"/",
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
":",
"=",
"A",
"B",
"C",
"D",
"E",
"F",
"G",
"H",
"I",
"J",
"K",
"L",
"M",
"N",
"O",
"P",
"Q",
"R",
"S",
"T",
"U",
"V",
"W",
"X",
"Y",
"Z",
"_",
"a",
"b",
"c",
"d",
"e",
"f",
"g",
"h",
"i",
"j",
"k",
"l",
"m",
"n",
"o",
"p",
"q",
"r",
"s",
"t",
"u",
"v",
"w",
"x",
"y",
"z",
];
function _isBase64Char(value) {
if ((typeof value === "string") && (value.length === 1)) {
return _BASE64_CHARS.includes(value);
}
return false;
}
/**
* 62文字目(インデックス0~61)までの変換テーブル
*/
const _TABLE_62 = Object.freeze([
"A",
"B",
"C",
"D",
"E",
"F",
"G",
"H",
"I",
"J",
"K",
"L",
"M",
"N",
"O",
"P",
"Q",
"R",
"S",
"T",
"U",
"V",
"W",
"X",
"Y",
"Z",
"a",
"b",
"c",
"d",
"e",
"f",
"g",
"h",
"i",
"j",
"k",
"l",
"m",
"n",
"o",
"p",
"q",
"r",
"s",
"t",
"u",
"v",
"w",
"x",
"y",
"z",
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9", // 61
]);
function _isBase64Table(value) {
return (Array.isArray(value) && (value.length === 64) &&
value.every((i) => _isBase64Char(i)));
}
const _RFC4648_OPTIONS = Object.freeze({
rawTable: Object.freeze([..._TABLE_62, "+", "/"]),
noPadding: false,
paddingChar: "=",
});
const _RFC4648URL_OPTIONS = Object.freeze({
rawTable: Object.freeze([..._TABLE_62, "-", "_"]),
noPadding: true,
paddingChar: "=",
});
/**
* 文字列をバイト列にBase64復号し、結果のバイト列を返却
*
* {@link [Infra Standard](https://infra.spec.whatwg.org/#forgiving-base64-decode)}および、
* {@link [RFC 4648](https://datatracker.ietf.org/doc/html/rfc4648)}の仕様に従った。
* 改行には非対応(必要であれば改行を除去してからdecodeすべし)。
*
* @param encoded Base64符号化された文字列
* @param options オプション
* @returns バイト列
* @throws {TypeError} The `encoded` is not Base64-encoded string.
*/
function _decode(encoded, options) {
let work = encoded;
if (_forgiving === true) {
// deno-lint-ignore no-control-regex
work = work.replaceAll(/[\u{9}\u{A}\u{C}\u{D}\u{20}]/gu, "");
}
if (_forgiving === true) {
// work.lengthの箇所は、仕様では符号位置数だがlengthを使用する
// length !== 符号位置数の場合の処理結果が正しくなくなるが、そうなったとしてもisEncodedでエラーとなる為問題は無いはず
if ((work.length % 4) === 0) {
for (let i = 0; i < 2; i++) {
if (work.endsWith(options.paddingChar)) {
work = work.substring(0, work.length - 1);
}
else {
break;
}
}
}
if ((work.length % 4) === 1) {
throw new TypeError("forgiving decode error");
}
}
if (_isEncoded(work, options) !== true) {
throw new TypeError("decode error (1)");
}
const paddingStart = work.indexOf(options.paddingChar);
let paddingCount;
let encodedBody;
if ((options.noPadding !== true) && (_forgiving !== true)) {
if ((work.length % 4) !== 0) {
throw new TypeError("decode error (2)");
}
if (paddingStart >= 0) {
paddingCount = work.length - paddingStart;
encodedBody = work.substring(0, paddingStart);
}
else {
paddingCount = 0;
encodedBody = work;
}
}
else {
// if (paddingStart >= 0) {
// throw new TypeError("decode error (3)"); (1)で例外になる
// }
paddingCount = (work.length % 4 === 0) ? 0 : 4 - (work.length % 4);
encodedBody = work;
}
let _6bit1;
let _6bit2;
let _6bit3;
let _6bit4;
let _8bitI = 0;
const encodedByteCount = ((encodedBody.length + paddingCount) * 3 / 4) -
paddingCount;
const decodedBytes = new Uint8Array(encodedByteCount);
let i = 0;
if (encodedBody.length >= 4) {
for (i = 0; i < encodedBody.length; i = i + 4) {
// 8-bit (1)
_6bit1 = options.rawTable.indexOf(encodedBody[i]);
_6bit2 = options.rawTable.indexOf(encodedBody[i + 1]);
decodedBytes[_8bitI++] = (_6bit1 << 2) | (_6bit2 >> 4);
// 8-bit (2)
if (typeof encodedBody[i + 2] !== "string") {
decodedBytes[_8bitI++] = (_6bit2 & 0b001111) << 4;
break;
}
_6bit3 = options.rawTable.indexOf(encodedBody[i + 2]);
decodedBytes[_8bitI++] = ((_6bit2 & 0b001111) << 4) | (_6bit3 >> 2);
// 8-bit (3)
if (typeof encodedBody[i + 3] !== "string") {
decodedBytes[_8bitI++] = (_6bit3 & 0b000011) << 6;
break;
}
_6bit4 = options.rawTable.indexOf(encodedBody[i + 3]);
decodedBytes[_8bitI++] = ((_6bit3 & 0b000011) << 6) | _6bit4;
}
}
return decodedBytes;
}
function _isEncoded(work, options) {
const tablePattern = "[" +
options.rawTable.map((chr) => `\\u{${chr.charCodeAt(0).toString(16)}}`)
.join("") +
"]";
let regex;
if ((options.noPadding !== true) && (_forgiving !== true)) {
const paddingPattern = `\\u{${options.paddingChar.charCodeAt(0).toString(16)}}`;
regex = new RegExp(`^(${tablePattern}+${paddingPattern}*|${tablePattern}*)$`, "u");
}
else {
regex = new RegExp(`^${tablePattern}*$`, "u");
}
return regex.test(work);
}
/**
* バイト列を文字列にBase64符号化し、結果の文字列を返却
*
* {@link [Infra Standard](https://infra.spec.whatwg.org/#forgiving-base64-encode)}および、
* {@link [RFC 4648](https://datatracker.ietf.org/doc/html/rfc4648)}の仕様に従った。
* 改行には非対応(必要であればencode結果を改行すべし)。
*
* @param toEncode バイト列
* @param options Base64符号化方式オプション
* @returns Base64符号化された文字列
*/
function _encode(toEncode, options) {
let _6bit1e;
let _6bit2e;
let _6bit3e;
let _6bit4e;
let encodedChars = "";
for (let i = 0; i < toEncode.byteLength; i = i + 3) {
const [_n8bit1, _n8bit2, _n8bit3] = toEncode.subarray(i, i + 3);
const _8bit1 = _n8bit1;
const _8bit2 = (_n8bit2 !== undefined) ? _n8bit2 : 0;
// 6-bit (1)
_6bit1e = options.rawTable[_8bit1 >> 2];
// 6-bit (2)
_6bit2e = options
.rawTable[((_8bit1 & 0b00000011) << 4) | (_8bit2 >> 4)];
if (_n8bit2 !== undefined) {
const _8bit3 = (_n8bit3 !== undefined) ? _n8bit3 : 0;
// 6-bit (3)
_6bit3e = options
.rawTable[((_8bit2 & 0b00001111) << 2) | (_8bit3 >> 6)];
if (_n8bit3 !== undefined) {
// 6-bit (4)
_6bit4e = options.rawTable[_8bit3 & 0b00111111];
}
else {
_6bit4e = (options.noPadding !== true) ? options.paddingChar : "";
}
}
else {
_6bit3e = (options.noPadding !== true) ? options.paddingChar : "";
_6bit4e = (options.noPadding !== true) ? options.paddingChar : "";
}
encodedChars = encodedChars.concat(_6bit1e + _6bit2e + _6bit3e + _6bit4e);
}
return encodedChars;
}
/**
* オプションをResolvedOptions型に変換する
* 未設定項目はデフォルト値で埋める
*
* @param options オプション
* @returns 未設定項目を埋めたオプションの複製
* @throws {RangeError} The `options.rawTable` contains duplicate characters, or the `options.paddingChar` character is contained in the `options.rawTable`.
*/
function _resolveOptions(options = {}) {
const defaultOptions = _RFC4648_OPTIONS;
let rawTable;
if (("tableLastChars" in options) && Array.isArray(options.tableLastChars) &&
(options.tableLastChars.length === 2) &&
options.tableLastChars.every((c) => _isBase64Char(c))) {
rawTable = Object.freeze([
...defaultOptions.rawTable.slice(0, 62),
options.tableLastChars[0],
options.tableLastChars[1],
]);
}
else if (("rawTable" in options) && _isBase64Table(options.rawTable)) {
rawTable = Object.freeze([...options.rawTable]);
}
else if (("table" in options) && _isBase64Table(options.table)) {
rawTable = Object.freeze([...options.table]);
}
else {
rawTable = defaultOptions.rawTable;
}
let noPadding = defaultOptions.noPadding;
if (typeof options.noPadding === "boolean") {
noPadding = options.noPadding;
}
let paddingChar = defaultOptions.paddingChar;
if (_isBase64Char(options.paddingChar)) {
paddingChar = options.paddingChar;
}
// tableとpaddingの重複チェック
if ((new Set([...rawTable, paddingChar])).size !== 65) {
throw new RangeError("options error: character duplicated");
}
return Object.freeze({
rawTable,
noPadding,
paddingChar,
});
}
class _DecoderStreamRegulator {
constructor() {
__DecoderStreamRegulator_pending.set(this, void 0);
__classPrivateFieldSet(this, __DecoderStreamRegulator_pending, "", "f");
}
regulate(chunk) {
const temp = __classPrivateFieldGet(this, __DecoderStreamRegulator_pending, "f") + chunk;
const surplus = temp.length % 24;
if (temp.length < 24) {
__classPrivateFieldSet(this, __DecoderStreamRegulator_pending, temp, "f");
return "";
}
else if (surplus === 0) {
__classPrivateFieldSet(this, __DecoderStreamRegulator_pending, "", "f");
return temp;
}
else {
const pendingLength = temp.length - surplus;
__classPrivateFieldSet(this, __DecoderStreamRegulator_pending, temp.substring(pendingLength), "f");
return temp.substring(0, pendingLength);
}
}
flush() {
const remains = __classPrivateFieldGet(this, __DecoderStreamRegulator_pending, "f");
__classPrivateFieldSet(this, __DecoderStreamRegulator_pending, "", "f");
return remains;
}
}
__DecoderStreamRegulator_pending = new WeakMap();
Object.freeze(_DecoderStreamRegulator);
class _EncoderStreamRegulator {
constructor() {
__EncoderStreamRegulator_pending.set(this, void 0);
__classPrivateFieldSet(this, __EncoderStreamRegulator_pending, new Uint8Array(0), "f");
}
regulate(chunk) {
const temp = new Uint8Array(__classPrivateFieldGet(this, __EncoderStreamRegulator_pending, "f").length + chunk.length);
temp.set(__classPrivateFieldGet(this, __EncoderStreamRegulator_pending, "f"));
temp.set(chunk, __classPrivateFieldGet(this, __EncoderStreamRegulator_pending, "f").length);
const surplus = temp.length % 24;
if (temp.length < 24) {
__classPrivateFieldSet(this, __EncoderStreamRegulator_pending, temp, "f");
return new Uint8Array(0);
}
else if (surplus === 0) {
__classPrivateFieldSet(this, __EncoderStreamRegulator_pending, new Uint8Array(0), "f");
return temp;
}
else {
const pendingLength = temp.length - surplus;
__classPrivateFieldSet(this, __EncoderStreamRegulator_pending, temp.subarray(pendingLength), "f");
return temp.subarray(0, pendingLength);
}
}
flush() {
const remains = __classPrivateFieldGet(this, __EncoderStreamRegulator_pending, "f");
__classPrivateFieldSet(this, __EncoderStreamRegulator_pending, new Uint8Array(0), "f");
return remains;
}
}
__EncoderStreamRegulator_pending = new WeakMap();
Object.freeze(_EncoderStreamRegulator);
/**
* Provides Base64 decoding and Base64 encoding methods.
*/
var Base64;
(function (Base64) {
var _Decoder_options, _Encoder_options;
/**
* Decodes a Base64-encoded string into an `Uint8Array`.
*
* @param encoded The string to decode.
* @param options The `Base64.Options` dictionary.
* @returns An `Uint8Array` containing the decoded byte sequence.
* @throws {RangeError} The `options.rawTable` contains duplicate characters, or the `options.paddingChar` character is contained in the `options.rawTable`.
* @throws {TypeError} The `encoded` is not Base64-encoded string.
* @example
* ```javascript
* Base64.decode("AwIBAP/+/fw=");
* // → Uint8Array[ 0x03, 0x02, 0x01, 0x00, 0xFF, 0xFE, 0xFD, 0xFC ]
* ```
* @example
* ```javascript
* const rfc4648urlOptions = Base64.Options.RFC4648URL;
* Base64.decode("AwIBAP_-_fw", rfc4648urlOptions);
* // → Uint8Array[ 0x03, 0x02, 0x01, 0x00, 0xFF, 0xFE, 0xFD, 0xFC ]
* ```
*/
function decode(encoded, options) {
const resolvedOptions = _resolveOptions(options);
return _decode(encoded, resolvedOptions);
}
Base64.decode = decode;
/**
* Encodes the specified byte sequence into a string.
*
* @param toEncode The byte sequence to encode.
* @param options The `Base64.Options` dictionary.
* @returns A string containing the Base64-encoded characters.
* @throws {RangeError} The `options.rawTable` contains duplicate characters, or the `options.paddingChar` character is contained in the `options.rawTable`.
* @example
* ```javascript
* Base64.encode(Uint8Array.of(0x03, 0x02, 0x01, 0x00, 0xFF, 0xFE, 0xFD, 0xFC));
* // → "AwIBAP/+/fw="
* ```
* @example
* ```javascript
* const rfc4648urlOptions = Base64.Options.RFC4648URL;
* Base64.encode(Uint8Array.of(0x03, 0x02, 0x01, 0x00, 0xFF, 0xFE, 0xFD, 0xFC), rfc4648urlOptions);
* // → "AwIBAP_-_fw"
* ```
*/
function encode(toEncode, options) {
const resolvedOptions = _resolveOptions(options);
return _encode(toEncode, resolvedOptions);
}
Base64.encode = encode;
let Options;
(function (Options) {
/**
* The options for [RFC 4648 Base64](https://datatracker.ietf.org/doc/html/rfc4648#section-4).
*
* | field | value |
* | :--- | :--- |
* | `rawTable` | `[ "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "+", "/" ]` |
* | `noPadding` | `false` |
* | `paddingChar` | `"="` |
*/
Options.RFC4648 = _RFC4648_OPTIONS;
/**
* The options for [RFC 4648 Base64url](https://datatracker.ietf.org/doc/html/rfc4648#section-5).
*
* | field | value |
* | :--- | :--- |
* | `rawTable` | `[ "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "-", "_" ]` |
* | `noPadding` | `true` |
* | `paddingChar` | `"="` |
*/
Options.RFC4648URL = _RFC4648URL_OPTIONS;
})(Options = Base64.Options || (Base64.Options = {}));
Object.freeze(Options);
/**
* Base64 decoder
*
* @example
* ```javascript
* const decoder = new Base64.Decoder();
*
* decoder.decode("AwIBAP/+/fw=");
* // → Uint8Array[ 0x03, 0x02, 0x01, 0x00, 0xFF, 0xFE, 0xFD, 0xFC ]
* ```
* @example
* ```javascript
* const rfc4648urlOptions = Base64.Options.RFC4648URL;
* const decoder = new Base64.Decoder(rfc4648urlOptions);
*
* decoder.decode("AwIBAP_-_fw");
* // → Uint8Array[ 0x03, 0x02, 0x01, 0x00, 0xFF, 0xFE, 0xFD, 0xFC ]
* ```
*/
class Decoder {
/**
* @param options The `Base64.Options` dictionary.
* @throws {RangeError} The `options.rawTable` contains duplicate characters, or the `options.padding` character is contained in the `options.rawTable`.
*/
constructor(options) {
/**
* 未設定項目を埋めたオプション
*/
_Decoder_options.set(this, void 0);
__classPrivateFieldSet(this, _Decoder_options, _resolveOptions(options), "f");
Object.freeze(this);
}
/**
* Decodes a Base64-encoded string into an `Uint8Array`.
*
* @param encoded The string to decode.
* @returns An `Uint8Array` containing the decoded byte sequence.
* @throws {TypeError} The `encoded` is not Base64-encoded string.
*/
decode(encoded) {
return _decode(encoded, __classPrivateFieldGet(this, _Decoder_options, "f"));
}
}
_Decoder_options = new WeakMap();
Base64.Decoder = Decoder;
Object.freeze(Decoder);
/**
* Base64 encoder
*
* @example
* ```javascript
* const encoder = new Base64.Encoder();
*
* encoder.encode(Uint8Array.of(0x03, 0x02, 0x01, 0x00, 0xFF, 0xFE, 0xFD, 0xFC));
* // → "AwIBAP/+/fw="
* ```
* @example
* ```javascript
* const rfc4648urlOptions = Base64.Options.RFC4648URL;
* const encoder = new Base64.Encoder(rfc4648urlOptions);
*
* encoder.encode(Uint8Array.of(0x03, 0x02, 0x01, 0x00, 0xFF, 0xFE, 0xFD, 0xFC));
* // → "AwIBAP_-_fw"
* ```
*/
class Encoder {
/**
* @param options The `Base64.Options` dictionary.
* @throws {RangeError} The `options.rawTable` contains duplicate characters, or the `options.padding` character is contained in the `options.rawTable`.
*/
constructor(options) {
/**
* 未設定項目を埋めたオプション
*/
_Encoder_options.set(this, void 0);
__classPrivateFieldSet(this, _Encoder_options, _resolveOptions(options), "f");
Object.freeze(this);
}
/**
* Encodes the specified byte sequence into a string.
*
* @param toEncode The byte sequence to encode.
* @returns A string containing the Base64-encoded characters.
*/
encode(toEncode) {
return _encode(toEncode, __classPrivateFieldGet(this, _Encoder_options, "f"));
}
}
_Encoder_options = new WeakMap();
Base64.Encoder = Encoder;
Object.freeze(Encoder);
/**
* The `TransformStream` that decodes a stream of Base64-encoded string into `Uint8Array` stream.
*
* @example
* ```javascript
* const decoderStream = new Base64.DecoderStream();
* // readableStream: ReadableStream<string>
* // writableStream: WritableStream<Uint8Array>
*
* readableStream.pipeThrough(decoderStream).pipeTo(writableStream);
* ```
*/
class DecoderStream extends BytesEncoding.DecoderStream {
/**
* @param options The `Base64.Options` dictionary.
*/
constructor(options) {
const decoder = new Base64.Decoder(options);
const regulator = new _DecoderStreamRegulator();
super(decoder, regulator);
Object.freeze(this);
}
}
Base64.DecoderStream = DecoderStream;
Object.freeze(DecoderStream);
/**
* The `TransformStream` that encodes a stream of `Uint8Array` into Base64-encoded string stream.
*
* @example
* ```javascript
* const encoderStream = new Base64.EncoderStream();
* // readableStream: ReadableStream<Uint8Array>
* // writableStream: WritableStream<string>
*
* readableStream.pipeThrough(encoderStream).pipeTo(writableStream);
* ```
*/
class EncoderStream extends BytesEncoding.EncoderStream {
/**
* @param options The `Base64.Options` dictionary.
*/
constructor(options) {
const encoder = new Base64.Encoder(options);
const regulator = new _EncoderStreamRegulator();
super(encoder, regulator);
Object.freeze(this);
}
}
Base64.EncoderStream = EncoderStream;
Object.freeze(EncoderStream);
})(Base64 || (Base64 = {}));
Object.freeze(Base64);
export { Base64 };