base64-ct
Version:
Safe, constant-time Base64 encoding and decoding in pure JavaScript, with support for common Base64 variants.
184 lines (183 loc) • 6.27 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Base64UrlSafe = exports.Base64 = exports.Base64Codec = void 0;
function eq(x, y) { return (((0 - (x ^ y)) >> 16) & 0xffff) ^ 0xffff; }
function gt(x, y) { return ((y - x) >> 8) & 0xffff; }
function lt(x, y) { return gt(y, x); }
function ge(x, y) { return gt(y, x) ^ 0xffff; }
function le(x, y) { return ge(y, x); }
function byteToCharOriginal(x) {
const c = (lt(x, 26) & (x + 'A'.charCodeAt(0))) |
(ge(x, 26) & lt(x, 52) & (x + ('a'.charCodeAt(0) - 26))) |
(ge(x, 52) & lt(x, 62) & (x + ('0'.charCodeAt(0) - 52))) | (eq(x, 62) & '+'.charCodeAt(0)) |
(eq(x, 63) & '/'.charCodeAt(0));
return String.fromCharCode(c);
}
function charToByteOriginal(c) {
const x = (ge(c, 'A'.charCodeAt(0)) & le(c, 'Z'.charCodeAt(0)) & (c - 'A'.charCodeAt(0))) |
(ge(c, 'a'.charCodeAt(0)) & le(c, 'z'.charCodeAt(0)) & (c - ('a'.charCodeAt(0) - 26))) |
(ge(c, '0'.charCodeAt(0)) & le(c, '9'.charCodeAt(0)) & (c - ('0'.charCodeAt(0) - 52))) | (eq(c, '+'.charCodeAt(0)) & 62) |
(eq(c, '/'.charCodeAt(0)) & 63);
return x | (eq(x, 0) & (eq(c, 'A'.charCodeAt(0)) ^ 0xffff));
}
function byteToCharUrlSafe(x) {
const c = (lt(x, 26) & (x + 'A'.charCodeAt(0))) |
(ge(x, 26) & lt(x, 52) & (x + ('a'.charCodeAt(0) - 26))) |
(ge(x, 52) & lt(x, 62) & (x + ('0'.charCodeAt(0) - 52))) | (eq(x, 62) & '-'.charCodeAt(0)) |
(eq(x, 63) & '_'.charCodeAt(0));
return String.fromCharCode(c);
}
function charToByteUrlSafe(c) {
const x = (ge(c, 'A'.charCodeAt(0)) & le(c, 'Z'.charCodeAt(0)) & (c - 'A'.charCodeAt(0))) |
(ge(c, 'a'.charCodeAt(0)) & le(c, 'z'.charCodeAt(0)) & (c - ('a'.charCodeAt(0) - 26))) |
(ge(c, '0'.charCodeAt(0)) & le(c, '9'.charCodeAt(0)) & (c - ('0'.charCodeAt(0) - 52))) | (eq(c, '-'.charCodeAt(0)) & 62) |
(eq(c, '_'.charCodeAt(0)) & 63);
return x | (eq(x, 0) & (eq(c, 'A'.charCodeAt(0)) ^ 0xffff));
}
function bin2Base64(bin, padding, byteToChar) {
let bin_len = bin.length;
let nibbles = Math.floor(bin_len / 3);
let remainder = bin_len - 3 * nibbles;
let b64_len = nibbles * 4;
if (remainder) {
if (padding) {
b64_len += 4;
}
else {
b64_len += 2 + (remainder >> 1);
}
}
let b64 = "";
let acc = 0, acc_len = 0, bin_pos = 0;
while (bin_pos < bin_len) {
acc = ((acc << 8) + bin[bin_pos++]) & 0xfff;
acc_len += 8;
while (acc_len >= 6) {
acc_len -= 6;
b64 += byteToChar((acc >> acc_len) & 0x3F);
}
}
if (acc_len > 0) {
b64 += byteToChar((acc << (6 - acc_len)) & 0x3F);
}
while (b64.length < b64_len) {
b64 += '=';
}
return b64;
}
function skipPadding(b64, ignore, padding_len) {
let i = 0;
while (padding_len > 0) {
let c = b64[i++];
if (c == '=') {
padding_len--;
}
else if (!ignore || ignore.indexOf(c) < 0) {
throw new Error("Invalid base64 padding");
}
}
if (i !== b64.length) {
throw new Error("Invalid base64 padding length");
}
}
function base642Bin(b64, padding, ignore, charToByte) {
let b64_len = b64.length;
let bin = new Uint8Array(Math.ceil(b64_len * 3 / 4));
let acc = 0, acc_len = 0, bin_len = 0, b64_pos = 0;
while (b64_pos < b64_len) {
const c = b64[b64_pos];
const d = charToByte(c.charCodeAt(0));
if (d == 0xffff) {
if (ignore && ignore.indexOf(c) >= 0) {
b64_pos++;
continue;
}
break;
}
acc = ((acc << 6) + d) & 0xfff;
acc_len += 6;
if (acc_len >= 8) {
acc_len -= 8;
bin[bin_len++] = (acc >> acc_len) & 0xff;
}
b64_pos++;
}
if (acc_len > 4 || (acc & ((1 << acc_len) - 1)) != 0) {
throw new Error("Non-canonical base64 encoding");
}
if (padding) {
skipPadding(b64.slice(b64_pos), ignore, acc_len / 2);
}
return new Uint8Array(bin.buffer, 0, bin_len);
}
/**
* Custom Base64 encoding/decoding, with support for arbitrary char<->maps.
*/
class Base64Codec {
/**
* Custom Base64 encoding/decoding.
*
* @param padding - whether padding is used.
* @param ignore - string of characters to ignore.
* @param charToByte - function to convert Base64 char to a byte.
* @param byteToChar - function to convert byte to a Base64 char.
*/
constructor(padding = false, ignore = null, charToByte, byteToChar) {
this._ignore = null;
this._padding = false;
this._padding = padding;
this._ignore = ignore;
this._charToByte = charToByte;
this._byteToChar = byteToChar;
}
/**
* Encode a buffer to Base64.
*
* @param data - a buffer to encode.
* @returns a Base64 encoded string.
*/
encode(data) {
return bin2Base64(data, this._padding, this._byteToChar);
}
/**
* Decode a Base64 string to a buffer.
*
* @param data - a Base64 string to decode.
* @returns a buffer.
* @throws an error if the string is not a valid Base64 string.
*/
decode(data) {
return base642Bin(data, this._padding, this._ignore, this._charToByte);
}
}
exports.Base64Codec = Base64Codec;
/**
* Traditional Base64 encoding/decoding.
*/
class Base64 extends Base64Codec {
/**
* Traditional Base64 encoding/decoding.
*
* @param padding - whether padding is used.
* @param ignore - string of characters to ignore.
*/
constructor(padding = true, ignore = null) {
super(padding, ignore, charToByteOriginal, byteToCharOriginal);
}
}
exports.Base64 = Base64;
/**
* URL-safe Base64 encoding/decoding.
*/
class Base64UrlSafe extends Base64Codec {
/**
* URL-safe Base64 encoding/decoding.
*
* @param padding - whether padding is used.
* @param ignore - string of characters to ignore.
*/
constructor(padding = false, ignore = null) {
super(padding, ignore, charToByteUrlSafe, byteToCharUrlSafe);
}
}
exports.Base64UrlSafe = Base64UrlSafe;