UNPKG

@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
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 };