UNPKG

buffer-apg-js

Version:

JavaScript APG, an ABNF Parser Generator

1,255 lines (1,230 loc) 30.8 kB
/* eslint-disable prefer-destructuring */ /* eslint-disable no-plusplus */ /* eslint-disable no-bitwise */ /* ************************************************************************************* * copyright: Copyright (c) 2021 Lowell D. Thomas, all rights reserved * license: BSD-2-Clause (https://opensource.org/licenses/BSD-2-Clause) * ********************************************************************************* */ // This module contains the actual encoding and decoding algorithms. // Throws "RangeError" exceptions on characters or bytes out of range for the given encoding. 'use strict;'; const thisThis = this; const Buffer = require('safer-buffer').Buffer; /* decoding error codes */ const NON_SHORTEST = 0xfffffffc; const TRAILING = 0xfffffffd; const RANGE = 0xfffffffe; const ILL_FORMED = 0xffffffff; /* mask[n] = 2**n - 1, ie. mask[n] = n bits on. e.g. mask[6] = %b111111 */ const mask = [0, 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023]; /* ascii[n] = 'HH', where 0xHH = n, eg. ascii[254] = 'FE' */ const ascii = [ '00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '0A', '0B', '0C', '0D', '0E', '0F', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '1A', '1B', '1C', '1D', '1E', '1F', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '2A', '2B', '2C', '2D', '2E', '2F', '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '3A', '3B', '3C', '3D', '3E', '3F', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '4A', '4B', '4C', '4D', '4E', '4F', '50', '51', '52', '53', '54', '55', '56', '57', '58', '59', '5A', '5B', '5C', '5D', '5E', '5F', '60', '61', '62', '63', '64', '65', '66', '67', '68', '69', '6A', '6B', '6C', '6D', '6E', '6F', '70', '71', '72', '73', '74', '75', '76', '77', '78', '79', '7A', '7B', '7C', '7D', '7E', '7F', '80', '81', '82', '83', '84', '85', '86', '87', '88', '89', '8A', '8B', '8C', '8D', '8E', '8F', '90', '91', '92', '93', '94', '95', '96', '97', '98', '99', '9A', '9B', '9C', '9D', '9E', '9F', 'A0', 'A1', 'A2', 'A3', 'A4', 'A5', 'A6', 'A7', 'A8', 'A9', 'AA', 'AB', 'AC', 'AD', 'AE', 'AF', 'B0', 'B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B9', 'BA', 'BB', 'BC', 'BD', 'BE', 'BF', 'C0', 'C1', 'C2', 'C3', 'C4', 'C5', 'C6', 'C7', 'C8', 'C9', 'CA', 'CB', 'CC', 'CD', 'CE', 'CF', 'D0', 'D1', 'D2', 'D3', 'D4', 'D5', 'D6', 'D7', 'D8', 'D9', 'DA', 'DB', 'DC', 'DD', 'DE', 'DF', 'E0', 'E1', 'E2', 'E3', 'E4', 'E5', 'E6', 'E7', 'E8', 'E9', 'EA', 'EB', 'EC', 'ED', 'EE', 'EF', 'F0', 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'FA', 'FB', 'FC', 'FD', 'FE', 'FF', ]; /* vector of base 64 characters */ const base64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='.split(''); /* vector of base 64 character codes */ const base64codes = []; base64chars.forEach((char) => { base64codes.push(char.charCodeAt(0)); }); // The UTF8 algorithms. exports.utf8 = { encode(chars) { const bytes = []; chars.forEach((char) => { if (char >= 0 && char <= 0x7f) { bytes.push(char); } else if (char <= 0x7ff) { bytes.push(0xc0 + ((char >> 6) & mask[5])); bytes.push(0x80 + (char & mask[6])); } else if (char < 0xd800 || (char > 0xdfff && char <= 0xffff)) { bytes.push(0xe0 + ((char >> 12) & mask[4])); bytes.push(0x80 + ((char >> 6) & mask[6])); bytes.push(0x80 + (char & mask[6])); } else if (char >= 0x10000 && char <= 0x10ffff) { const u = (char >> 16) & mask[5]; bytes.push(0xf0 + (u >> 2)); bytes.push(0x80 + ((u & mask[2]) << 4) + ((char >> 12) & mask[4])); bytes.push(0x80 + ((char >> 6) & mask[6])); bytes.push(0x80 + (char & mask[6])); } else { throw new RangeError(`utf8.encode: character out of range: char: ${char}`); } }); return Buffer.from(bytes); }, decode(buf, bom) { /* bytes functions return error for non-shortest forms & values out of range */ function bytes2(b1, b2) { /* U+0080..U+07FF */ /* 00000000 00000yyy yyxxxxxx | 110yyyyy 10xxxxxx */ if ((b2 & 0xc0) !== 0x80) { return TRAILING; } const x = ((b1 & mask[5]) << 6) + (b2 & mask[6]); if (x < 0x80) { return NON_SHORTEST; } return x; } function bytes3(b1, b2, b3) { /* U+0800..U+FFFF */ /* 00000000 zzzzyyyy yyxxxxxx | 1110zzzz 10yyyyyy 10xxxxxx */ if ((b3 & 0xc0) !== 0x80 || (b2 & 0xc0) !== 0x80) { return TRAILING; } const x = ((b1 & mask[4]) << 12) + ((b2 & mask[6]) << 6) + (b3 & mask[6]); if (x < 0x800) { return NON_SHORTEST; } if (x >= 0xd800 && x <= 0xdfff) { return RANGE; } return x; } function bytes4(b1, b2, b3, b4) { /* U+10000..U+10FFFF */ /* 000uuuuu zzzzyyyy yyxxxxxx | 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx */ if ((b4 & 0xc0) !== 0x80 || (b3 & 0xc0) !== 0x80 || (b2 & 0xc0) !== 0x80) { return TRAILING; } const x = ((((b1 & mask[3]) << 2) + ((b2 >> 4) & mask[2])) << 16) + ((b2 & mask[4]) << 12) + ((b3 & mask[6]) << 6) + (b4 & mask[6]); if (x < 0x10000) { return NON_SHORTEST; } if (x > 0x10ffff) { return RANGE; } return x; } let c; let b1; let i1; let i2; let i3; let inc; const len = buf.length; let i = bom ? 3 : 0; const chars = []; while (i < len) { b1 = buf[i]; c = ILL_FORMED; const TRUE = true; while (TRUE) { if (b1 >= 0 && b1 <= 0x7f) { /* U+0000..U+007F 00..7F */ c = b1; inc = 1; break; } i1 = i + 1; if (i1 < len && b1 >= 0xc2 && b1 <= 0xdf) { /* U+0080..U+07FF C2..DF 80..BF */ c = bytes2(b1, buf[i1]); inc = 2; break; } i2 = i + 2; if (i2 < len && b1 >= 0xe0 && b1 <= 0xef) { /* U+0800..U+FFFF */ c = bytes3(b1, buf[i1], buf[i2]); inc = 3; break; } i3 = i + 3; if (i3 < len && b1 >= 0xf0 && b1 <= 0xf4) { /* U+10000..U+10FFFF */ c = bytes4(b1, buf[i1], buf[i2], buf[i3]); inc = 4; break; } /* if we fall through to here, it is an ill-formed sequence */ break; } if (c > 0x10ffff) { const at = `byte[${i}]`; if (c === ILL_FORMED) { throw new RangeError(`utf8.decode: ill-formed UTF8 byte sequence found at: ${at}`); } if (c === TRAILING) { throw new RangeError(`utf8.decode: illegal trailing byte found at: ${at}`); } if (c === RANGE) { throw new RangeError(`utf8.decode: code point out of range found at: ${at}`); } if (c === NON_SHORTEST) { throw new RangeError(`utf8.decode: non-shortest form found at: ${at}`); } throw new RangeError(`utf8.decode: unrecognized error found at: ${at}`); } chars.push(c); i += inc; } return chars; }, }; // The UTF16BE algorithms. exports.utf16be = { encode(chars) { const bytes = []; let char; let h; let l; for (let i = 0; i < chars.length; i += 1) { char = chars[i]; if ((char >= 0 && char <= 0xd7ff) || (char >= 0xe000 && char <= 0xffff)) { bytes.push((char >> 8) & mask[8]); bytes.push(char & mask[8]); } else if (char >= 0x10000 && char <= 0x10ffff) { l = char - 0x10000; h = 0xd800 + (l >> 10); l = 0xdc00 + (l & mask[10]); bytes.push((h >> 8) & mask[8]); bytes.push(h & mask[8]); bytes.push((l >> 8) & mask[8]); bytes.push(l & mask[8]); } else { throw new RangeError(`utf16be.encode: UTF16BE value out of range: char[${i}]: ${char}`); } } return Buffer.from(bytes); }, decode(buf, bom) { /* assumes caller has insured that buf is a Buffer of bytes */ if (buf.length % 2 > 0) { throw new RangeError(`utf16be.decode: data length must be even multiple of 2: length: ${buf.length}`); } const chars = []; const len = buf.length; let i = bom ? 2 : 0; let j = 0; let c; let inc; let i1; let i3; let high; let low; while (i < len) { const TRUE = true; while (TRUE) { i1 = i + 1; if (i1 < len) { high = (buf[i] << 8) + buf[i1]; if (high < 0xd800 || high > 0xdfff) { c = high; inc = 2; break; } i3 = i + 3; if (i3 < len) { low = (buf[i + 2] << 8) + buf[i3]; if (high <= 0xdbff && low >= 0xdc00 && low <= 0xdfff) { c = 0x10000 + ((high - 0xd800) << 10) + (low - 0xdc00); inc = 4; break; } } } /* if we fall through to here, it is an ill-formed sequence */ throw new RangeError(`utf16be.decode: ill-formed UTF16BE byte sequence found: byte[${i}]`); } chars[j++] = c; i += inc; } return chars; }, }; // The UTF16LE algorithms. exports.utf16le = { encode(chars) { const bytes = []; let char; let h; let l; for (let i = 0; i < chars.length; i += 1) { char = chars[i]; if ((char >= 0 && char <= 0xd7ff) || (char >= 0xe000 && char <= 0xffff)) { bytes.push(char & mask[8]); bytes.push((char >> 8) & mask[8]); } else if (char >= 0x10000 && char <= 0x10ffff) { l = char - 0x10000; h = 0xd800 + (l >> 10); l = 0xdc00 + (l & mask[10]); bytes.push(h & mask[8]); bytes.push((h >> 8) & mask[8]); bytes.push(l & mask[8]); bytes.push((l >> 8) & mask[8]); } else { throw new RangeError(`utf16le.encode: UTF16LE value out of range: char[${i}]: ${char}`); } } return Buffer.from(bytes); }, decode(buf, bom) { /* assumes caller has insured that buf is a Buffer of bytes */ if (buf.length % 2 > 0) { throw new RangeError(`utf16le.decode: data length must be even multiple of 2: length: ${buf.length}`); } const chars = []; const len = buf.length; let i = bom ? 2 : 0; let j = 0; let c; let inc; let i1; let i3; let high; let low; while (i < len) { const TRUE = true; while (TRUE) { i1 = i + 1; if (i1 < len) { high = (buf[i1] << 8) + buf[i]; if (high < 0xd800 || high > 0xdfff) { c = high; inc = 2; break; } i3 = i + 3; if (i3 < len) { low = (buf[i3] << 8) + buf[i + 2]; if (high <= 0xdbff && low >= 0xdc00 && low <= 0xdfff) { c = 0x10000 + ((high - 0xd800) << 10) + (low - 0xdc00); inc = 4; break; } } } /* if we fall through to here, it is an ill-formed sequence */ throw new RangeError(`utf16le.decode: ill-formed UTF16LE byte sequence found: byte[${i}]`); } chars[j++] = c; i += inc; } return chars; }, }; // The UTF32BE algorithms. exports.utf32be = { encode(chars) { const buf = Buffer.alloc(chars.length * 4); let i = 0; chars.forEach((char) => { if ((char >= 0xd800 && char <= 0xdfff) || char > 0x10ffff) { throw new RangeError(`utf32be.encode: UTF32BE character code out of range: char[${i / 4}]: ${char}`); } buf[i++] = (char >> 24) & mask[8]; buf[i++] = (char >> 16) & mask[8]; buf[i++] = (char >> 8) & mask[8]; buf[i++] = char & mask[8]; }); return buf; }, decode(buf, bom) { /* caller to insure buf is a Buffer of bytes */ if (buf.length % 4 > 0) { throw new RangeError(`utf32be.decode: UTF32BE byte length must be even multiple of 4: length: ${buf.length}`); } const chars = []; let i = bom ? 4 : 0; for (; i < buf.length; i += 4) { const char = (buf[i] << 24) + (buf[i + 1] << 16) + (buf[i + 2] << 8) + buf[i + 3]; if ((char >= 0xd800 && char <= 0xdfff) || char > 0x10ffff) { throw new RangeError(`utf32be.decode: UTF32BE character code out of range: char[${i / 4}]: ${char}`); } chars.push(char); } return chars; }, }; // The UTF32LE algorithms. exports.utf32le = { encode(chars) { const buf = Buffer.alloc(chars.length * 4); let i = 0; chars.forEach((char) => { if ((char >= 0xd800 && char <= 0xdfff) || char > 0x10ffff) { throw new RangeError(`utf32le.encode: UTF32LE character code out of range: char[${i / 4}]: ${char}`); } buf[i++] = char & mask[8]; buf[i++] = (char >> 8) & mask[8]; buf[i++] = (char >> 16) & mask[8]; buf[i++] = (char >> 24) & mask[8]; }); return buf; }, decode(buf, bom) { /* caller to insure buf is a Buffer of bytes */ if (buf.length % 4 > 0) { throw new RangeError(`utf32be.decode: UTF32LE byte length must be even multiple of 4: length: ${buf.length}`); } const chars = []; let i = bom ? 4 : 0; for (; i < buf.length; i += 4) { const char = (buf[i + 3] << 24) + (buf[i + 2] << 16) + (buf[i + 1] << 8) + buf[i]; if ((char >= 0xd800 && char <= 0xdfff) || char > 0x10ffff) { throw new RangeError(`utf32le.encode: UTF32LE character code out of range: char[${i / 4}]: ${char}`); } chars.push(char); } return chars; }, }; // The UINT7 algorithms. ASCII or 7-bit unsigned integers. exports.uint7 = { encode(chars) { const buf = Buffer.alloc(chars.length); for (let i = 0; i < chars.length; i += 1) { if (chars[i] > 0x7f) { throw new RangeError(`uint7.encode: UINT7 character code out of range: char[${i}]: ${chars[i]}`); } buf[i] = chars[i]; } return buf; }, decode(buf) { const chars = []; for (let i = 0; i < buf.length; i += 1) { if (buf[i] > 0x7f) { throw new RangeError(`uint7.decode: UINT7 character code out of range: byte[${i}]: ${buf[i]}`); } chars[i] = buf[i]; } return chars; }, }; // The UINT8 algorithms. BINARY, Latin 1 or 8-bit unsigned integers. exports.uint8 = { encode(chars) { const buf = Buffer.alloc(chars.length); for (let i = 0; i < chars.length; i += 1) { if (chars[i] > 0xff) { throw new RangeError(`uint8.encode: UINT8 character code out of range: char[${i}]: ${chars[i]}`); } buf[i] = chars[i]; } return buf; }, decode(buf) { const chars = []; for (let i = 0; i < buf.length; i += 1) { chars[i] = buf[i]; } return chars; }, }; // The UINT16BE algorithms. Big-endian 16-bit unsigned integers. exports.uint16be = { encode(chars) { const buf = Buffer.alloc(chars.length * 2); let i = 0; chars.forEach((char) => { if (char > 0xffff) { throw new RangeError(`uint16be.encode: UINT16BE character code out of range: char[${i / 2}]: ${char}`); } buf[i++] = (char >> 8) & mask[8]; buf[i++] = char & mask[8]; }); return buf; }, decode(buf) { if (buf.length % 2 > 0) { throw new RangeError(`uint16be.decode: UINT16BE byte length must be even multiple of 2: length: ${buf.length}`); } const chars = []; for (let i = 0; i < buf.length; i += 2) { chars.push((buf[i] << 8) + buf[i + 1]); } return chars; }, }; // The UINT16LE algorithms. Little-endian 16-bit unsigned integers. exports.uint16le = { encode(chars) { const buf = Buffer.alloc(chars.length * 2); let i = 0; chars.forEach((char) => { if (char > 0xffff) { throw new RangeError(`uint16le.encode: UINT16LE character code out of range: char[${i / 2}]: ${char}`); } buf[i++] = char & mask[8]; buf[i++] = (char >> 8) & mask[8]; }); return buf; }, decode(buf) { if (buf.length % 2 > 0) { throw new RangeError(`uint16le.decode: UINT16LE byte length must be even multiple of 2: length: ${buf.length}`); } const chars = []; for (let i = 0; i < buf.length; i += 2) { chars.push((buf[i + 1] << 8) + buf[i]); } return chars; }, }; // The UINT32BE algorithms. Big-endian 32-bit unsigned integers. exports.uint32be = { encode(chars) { const buf = Buffer.alloc(chars.length * 4); let i = 0; chars.forEach((char) => { buf[i++] = (char >> 24) & mask[8]; buf[i++] = (char >> 16) & mask[8]; buf[i++] = (char >> 8) & mask[8]; buf[i++] = char & mask[8]; }); return buf; }, decode(buf) { if (buf.length % 4 > 0) { throw new RangeError(`uint32be.decode: UINT32BE byte length must be even multiple of 4: length: ${buf.length}`); } const chars = []; for (let i = 0; i < buf.length; i += 4) { chars.push((buf[i] << 24) + (buf[i + 1] << 16) + (buf[i + 2] << 8) + buf[i + 3]); } return chars; }, }; // The UINT32LE algorithms. Little-endian 32-bit unsigned integers. exports.uint32le = { encode(chars) { const buf = Buffer.alloc(chars.length * 4); let i = 0; chars.forEach((char) => { buf[i++] = char & mask[8]; buf[i++] = (char >> 8) & mask[8]; buf[i++] = (char >> 16) & mask[8]; buf[i++] = (char >> 24) & mask[8]; }); return buf; }, decode(buf) { /* caller to insure buf is a Buffer of bytes */ if (buf.length % 4 > 0) { throw new RangeError(`uint32le.decode: UINT32LE byte length must be even multiple of 4: length: ${buf.length}`); } const chars = []; for (let i = 0; i < buf.length; i += 4) { chars.push((buf[i + 3] << 24) + (buf[i + 2] << 16) + (buf[i + 1] << 8) + buf[i]); } return chars; }, }; // The STRING algorithms. Converts JavaScript strings to Array of 32-bit integers and vice versa. // Uses the node.js Buffer's native "utf16le" capabilites. exports.string = { encode(chars) { return thisThis.utf16le.encode(chars).toString('utf16le'); }, decode(str) { return thisThis.utf16le.decode(Buffer.from(str, 'utf16le'), 0); }, }; // The ESCAPED algorithms. // Note that ESCAPED format contains only ASCII characters. // The characters are always in the form of a Buffer of bytes. exports.escaped = { // Encodes an Array of 32-bit integers into ESCAPED format. encode(chars) { const bytes = []; for (let i = 0; i < chars.length; i += 1) { const char = chars[i]; if (char === 96) { bytes.push(char); bytes.push(char); } else if (char === 10) { bytes.push(char); } else if (char >= 32 && char <= 126) { bytes.push(char); } else { let str = ''; if (char >= 0 && char <= 31) { str += `\`x${ascii[char]}`; } else if (char >= 127 && char <= 255) { str += `\`x${ascii[char]}`; } else if (char >= 0x100 && char <= 0xffff) { str += `\`u${ascii[(char >> 8) & mask[8]]}${ascii[char & mask[8]]}`; } else if (char >= 0x10000 && char <= 0xffffffff) { str += '`u{'; const digit = (char >> 24) & mask[8]; if (digit > 0) { str += ascii[digit]; } str += `${ascii[(char >> 16) & mask[8]] + ascii[(char >> 8) & mask[8]] + ascii[char & mask[8]]}}`; } else { throw new Error('escape.encode(char): char > 0xffffffff not allowed'); } const buf = Buffer.from(str); buf.forEach((b) => { bytes.push(b); }); } } return Buffer.from(bytes); }, // Decodes ESCAPED format from a Buffer of bytes to an Array of 32-bit integers. decode(buf) { function isHex(hex) { if ((hex >= 48 && hex <= 57) || (hex >= 65 && hex <= 70) || (hex >= 97 && hex <= 102)) { return true; } return false; } function getx(i, len, bufArg) { const ret = { char: null, nexti: i + 2, error: true }; if (i + 1 < len) { if (isHex(bufArg[i]) && isHex(bufArg[i + 1])) { const str = String.fromCodePoint(bufArg[i], bufArg[i + 1]); ret.char = parseInt(str, 16); if (!Number.isNaN(ret.char)) { ret.error = false; } } } return ret; } function getu(i, len, bufArg) { const ret = { char: null, nexti: i + 4, error: true }; if (i + 3 < len) { if (isHex(bufArg[i]) && isHex(bufArg[i + 1]) && isHex(bufArg[i + 2]) && isHex(bufArg[i + 3])) { const str = String.fromCodePoint(bufArg[i], bufArg[i + 1], bufArg[i + 2], bufArg[i + 3]); ret.char = parseInt(str, 16); if (!Number.isNaN(ret.char)) { ret.error = false; } } } return ret; } function getU(i, len, bufArg) { const ret = { char: null, nexti: i + 4, error: true }; let str = ''; while (i < len && isHex(bufArg[i])) { str += String.fromCodePoint(bufArg[i]); // eslint-disable-next-line no-param-reassign i += 1; } ret.char = parseInt(str, 16); if (bufArg[i] === 125 && !Number.isNaN(ret.char)) { ret.error = false; } ret.nexti = i + 1; return ret; } const chars = []; const len = buf.length; let i1; let ret; let error; let i = 0; while (i < len) { const TRUE = true; while (TRUE) { error = true; if (buf[i] !== 96) { /* unescaped character */ chars.push(buf[i]); i += 1; error = false; break; } i1 = i + 1; if (i1 >= len) { break; } if (buf[i1] === 96) { /* escaped grave accent */ chars.push(96); i += 2; error = false; break; } if (buf[i1] === 120) { ret = getx(i1 + 1, len, buf); if (ret.error) { break; } /* escaped hex */ chars.push(ret.char); i = ret.nexti; error = false; break; } if (buf[i1] === 117) { if (buf[i1 + 1] === 123) { ret = getU(i1 + 2, len, buf); if (ret.error) { break; } /* escaped utf-32 */ chars.push(ret.char); i = ret.nexti; error = false; break; } ret = getu(i1 + 1, len, buf); if (ret.error) { break; } /* escaped utf-16 */ chars.push(ret.char); i = ret.nexti; error = false; break; } break; } if (error) { throw new Error(`escaped.decode: ill-formed escape sequence at buf[${i}]`); } } return chars; }, }; // The line end conversion algorigthms. const CR = 13; const LF = 10; exports.lineEnds = { crlf(chars) { const lfchars = []; let i = 0; while (i < chars.length) { switch (chars[i]) { case CR: if (i + 1 < chars.length && chars[i + 1] === LF) { i += 2; } else { i += 1; } lfchars.push(CR); lfchars.push(LF); break; case LF: lfchars.push(CR); lfchars.push(LF); i += 1; break; default: lfchars.push(chars[i]); i += 1; break; } } if (lfchars.length > 0 && lfchars[lfchars.length - 1] !== LF) { lfchars.push(CR); lfchars.push(LF); } return lfchars; }, lf(chars) { const lfchars = []; let i = 0; while (i < chars.length) { switch (chars[i]) { case CR: if (i + 1 < chars.length && chars[i + 1] === LF) { i += 2; } else { i += 1; } lfchars.push(LF); break; case LF: lfchars.push(LF); i += 1; break; default: lfchars.push(chars[i]); i += 1; break; } } if (lfchars.length > 0 && lfchars[lfchars.length - 1] !== LF) { lfchars.push(LF); } return lfchars; }, }; // The base 64 algorithms. exports.base64 = { encode(buf) { if (buf.length === 0) { return Buffer.alloc(0); } let i; let j; let n; let tail = buf.length % 3; tail = tail > 0 ? 3 - tail : 0; let units = (buf.length + tail) / 3; const base64 = Buffer.alloc(units * 4); if (tail > 0) { units -= 1; } i = 0; j = 0; for (let u = 0; u < units; u += 1) { n = buf[i++] << 16; n += buf[i++] << 8; n += buf[i++]; base64[j++] = base64codes[(n >> 18) & mask[6]]; base64[j++] = base64codes[(n >> 12) & mask[6]]; base64[j++] = base64codes[(n >> 6) & mask[6]]; base64[j++] = base64codes[n & mask[6]]; } if (tail === 0) { return base64; } if (tail === 1) { n = buf[i++] << 16; n += buf[i] << 8; base64[j++] = base64codes[(n >> 18) & mask[6]]; base64[j++] = base64codes[(n >> 12) & mask[6]]; base64[j++] = base64codes[(n >> 6) & mask[6]]; base64[j] = base64codes[64]; return base64; } if (tail === 2) { n = buf[i] << 16; base64[j++] = base64codes[(n >> 18) & mask[6]]; base64[j++] = base64codes[(n >> 12) & mask[6]]; base64[j++] = base64codes[64]; base64[j] = base64codes[64]; return base64; } return undefined; }, decode(codes) { /* remove white space and ctrl characters, validate & translate characters */ function validate(buf) { const chars = []; let tail = 0; for (let i = 0; i < buf.length; i += 1) { const char = buf[i]; const TRUE = true; while (TRUE) { if (char === 32 || char === 9 || char === 10 || char === 13) { break; } if (char >= 65 && char <= 90) { chars.push(char - 65); break; } if (char >= 97 && char <= 122) { chars.push(char - 71); break; } if (char >= 48 && char <= 57) { chars.push(char + 4); break; } if (char === 43) { chars.push(62); break; } if (char === 47) { chars.push(63); break; } if (char === 61) { chars.push(64); tail += 1; break; } /* invalid character */ throw new RangeError(`base64.decode: invalid character buf[${i}]: ${char}`); } } /* validate length */ if (chars.length % 4 > 0) { throw new RangeError(`base64.decode: string length not integral multiple of 4: ${chars.length}`); } /* validate tail */ switch (tail) { case 0: break; case 1: if (chars[chars.length - 1] !== 64) { throw new RangeError('base64.decode: one tail character found: not last character'); } break; case 2: if (chars[chars.length - 1] !== 64 || chars[chars.length - 2] !== 64) { throw new RangeError('base64.decode: two tail characters found: not last characters'); } break; default: throw new RangeError(`base64.decode: more than two tail characters found: ${tail}`); } return { tail, buf: Buffer.from(chars) }; } if (codes.length === 0) { return Buffer.alloc(0); } const val = validate(codes); const { tail } = val; const base64 = val.buf; let i; let j; let n; let units = base64.length / 4; const buf = Buffer.alloc(units * 3 - tail); if (tail > 0) { units -= 1; } j = 0; i = 0; for (let u = 0; u < units; u += 1) { n = base64[i++] << 18; n += base64[i++] << 12; n += base64[i++] << 6; n += base64[i++]; buf[j++] = (n >> 16) & mask[8]; buf[j++] = (n >> 8) & mask[8]; buf[j++] = n & mask[8]; } if (tail === 1) { n = base64[i++] << 18; n += base64[i++] << 12; n += base64[i] << 6; buf[j++] = (n >> 16) & mask[8]; buf[j] = (n >> 8) & mask[8]; } if (tail === 2) { n = base64[i++] << 18; n += base64[i++] << 12; buf[j] = (n >> 16) & mask[8]; } return buf; }, // Converts a base 64 Buffer of bytes to a JavaScript string with line breaks. toString(buf) { if (buf.length % 4 > 0) { throw new RangeError(`base64.toString: input buffer length not multiple of 4: ${buf.length}`); } let str = ''; let lineLen = 0; function buildLine(c1, c2, c3, c4) { switch (lineLen) { case 76: str += `\r\n${c1}${c2}${c3}${c4}`; lineLen = 4; break; case 75: str += `${c1}\r\n${c2}${c3}${c4}`; lineLen = 3; break; case 74: str += `${c1 + c2}\r\n${c3}${c4}`; lineLen = 2; break; case 73: str += `${c1 + c2 + c3}\r\n${c4}`; lineLen = 1; break; default: str += c1 + c2 + c3 + c4; lineLen += 4; break; } } function validate(c) { if (c >= 65 && c <= 90) { return true; } if (c >= 97 && c <= 122) { return true; } if (c >= 48 && c <= 57) { return true; } if (c === 43) { return true; } if (c === 47) { return true; } if (c === 61) { return true; } return false; } for (let i = 0; i < buf.length; i += 4) { for (let j = i; j < i + 4; j += 1) { if (!validate(buf[j])) { throw new RangeError(`base64.toString: buf[${j}]: ${buf[j]} : not valid base64 character code`); } } buildLine( String.fromCharCode(buf[i]), String.fromCharCode(buf[i + 1]), String.fromCharCode(buf[i + 2]), String.fromCharCode(buf[i + 3]) ); } return str; }, };