@akala/core
Version:
276 lines (242 loc) • 8.44 kB
text/typescript
///
/// Native/portable equivalents with the same public surface
///
import { IsomorphicBuffer } from "./helpers.js";
// --- Base64 utils ---
/**
* Calculates the decoded byte length of a Base64 string.
* @param sBase64 The Base64 encoded string.
* @param nBlocksSize Optional size to round up to (for buffer alignment).
* @returns The decoded byte length.
*/
export function base64ByteLength(sBase64: string, nBlocksSize: number = 0): number
{
// Keep original semantics: strip everything except Base64 alphabet (no '=')
const sB64Enc = sBase64.replace(/[^A-Za-z0-9+/]/g, "");
const nInLen = sB64Enc.length;
// Original MDN math: floor((nInLen * 3 + 1) / 4)
const bytes = (nInLen * 3 + 1) >> 2;
return nBlocksSize
? Math.ceil(bytes / nBlocksSize) * nBlocksSize
: bytes;
}
const BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
/**
* Decodes a Base64 string into a Uint8Array.
* @param sBase64 The Base64 encoded string.
* @returns The decoded byte array.
*/
export function base64DecToArr(sBase64: string)
{
const clean = sBase64.replace(/[^A-Za-z0-9+/]/g, "");
if (typeof Buffer !== "undefined")
return Buffer.from(clean, "base64");
else
{
const length = Math.floor(clean.length * 3 / 4);
const out = new Uint8Array(length);
let j = 0;
for (let i = 0; i < clean.length; i += 4)
{
const n0 = BASE64_CHARS.indexOf(clean[i]);
const n1 = BASE64_CHARS.indexOf(clean[i + 1]);
const n2 = BASE64_CHARS.indexOf(clean[i + 2]);
const n3 = BASE64_CHARS.indexOf(clean[i + 3]);
const triplet = (n0 << 18) | (n1 << 12) | ((n2 & 63) << 6) | (n3 & 63);
if (j < out.length) out[j++] = (triplet >> 16) & 0xFF;
if (j < out.length) out[j++] = (triplet >> 8) & 0xFF;
if (j < out.length) out[j++] = triplet & 0xFF;
}
return out;
}
}
/**
* Encodes an Uint8Array to Base64URL format.
* @param aBytes The byte array to encode.
* @returns The Base64URL encoded string.
*/
export function base64UrlEncArr(aBytes: Uint8Array): string
{
const s = base64EncArr(aBytes).replace(/\+/g, "-").replace(/\//g, "_");
return s.replace(/=+$/g, "");
}
/**
* Encodes an ArrayBuffer to Base64URL format.
* @param aBytes The ArrayBuffer to encode.
* @returns The Base64URL encoded string.
*/
export function base64UrlEncArrBuff(aBytes: ArrayBuffer): string
{
const s = base64EncArrBuff(aBytes).replace(/\+/g, "-").replace(/\//g, "_");
return s.replace(/=+$/g, "");
}
/**
* Encodes an ArrayBuffer/Uint8Array to Base64URL format.
* @param aBytes The byte array to encode.
* @returns The Base64URL encoded string.
*/
export function base64UrlEncIsomorphicBuffer(aBytes: IsomorphicBuffer): string
{
let s = base64EncIsomorphicBuffer(aBytes).replace(/\+/g, '-').replace(/\//g, '_');
while (s.endsWith('='))
s = s.substring(0, s.length - 1);
return s;
}
/**
* Decodes a Base64URL string into an ArrayBuffer/Uint8Array.
* @param s The Base64URL encoded string.
* @returns The decoded byte array.
*/
export function base64UrlDecToArr(s: string)
{
let t = s.replace(/-/g, "+").replace(/_/g, "/").replace(/\s+/g, "");
const rem = t.length % 4;
if (rem === 2) t += "==";
else if (rem === 3) t += "=";
else if (rem === 1) throw new Error("Invalid Base64URL length");
return base64DecToArr(t);
}
// --- PEM helpers (same names/signatures) ---
/**
* Extracts the binary content from a PEM-formatted private key.
* @param pem The PEM string containing the private key.
* @returns The decoded binary content of the private key.
*/
export function extractPrivateKey(pem: string)
{
const pemHeader = "-----BEGIN PRIVATE KEY-----";
const pemFooter = "-----END PRIVATE KEY-----";
const start = pem.indexOf(pemHeader);
const end = pem.indexOf(pemFooter);
if (start < 0 || end < 0 || end <= start)
throw new Error("Invalid PRIVATE KEY PEM");
const body = pem.slice(start + pemHeader.length, end).replace(/\s+/g, "");
return base64DecToArr(body);
}
/**
* Extracts the binary content from a PEM-formatted public key.
* @param pem The PEM string containing the public key.
* @returns The decoded binary content of the public key.
*/
export function extractPublicKey(pem: string)
{
const pemHeader = "-----BEGIN PUBLIC KEY-----";
const pemFooter = "-----END PUBLIC KEY-----";
const start = pem.indexOf(pemHeader);
const end = pem.indexOf(pemFooter);
if (start < 0 || end < 0 || end <= start)
throw new Error("Invalid PUBLIC KEY PEM");
const body = pem.slice(start + pemHeader.length, end).replace(/\s+/g, "");
return base64DecToArr(body);
}
/**
* Encodes an ArrayBuffer/Uint8Array to Base64 string.
* @param aBytes The byte array to encode.
* @param nBlocksSize Optional size for line breaks in the output.
* @returns The Base64 encoded string.
*/
export function base64EncArr(aBytes: Uint8Array, nBlocksSize?: number): string
{
let out: string;
if (typeof Buffer !== "undefined")
// Node, Bun
out = Buffer.from(aBytes).toString("base64");
else
{
out = "";
let i = 0;
while (i < aBytes.length)
{
const start = i;
const byte1 = aBytes[i++]!;
const byte2 = i < aBytes.length ? aBytes[i++]! : 0;
const byte3 = i < aBytes.length ? aBytes[i++]! : 0;
const triplet = (byte1 << 16) | (byte2 << 8) | byte3;
const bytesRead = i - start;
out += BASE64_CHARS[(triplet >> 18) & 63];
out += BASE64_CHARS[(triplet >> 12) & 63];
out += bytesRead > 1 ? BASE64_CHARS[(triplet >> 6) & 63] : "=";
out += bytesRead > 2 ? BASE64_CHARS[triplet & 63] : "=";
}
}
if (nBlocksSize && nBlocksSize > 0)
{
let s = "";
for (let j = 0; j < out.length; j += nBlocksSize)
{
s += out.slice(j, j + nBlocksSize) + "\n";
}
out = s.trimEnd();
}
return out;
}
/**
* Encodes an ArrayBuffer/Uint8Array to Base64 string.
* @param aBytes The byte array to encode.
* @param nBlocksSize Optional size for line breaks in the output.
* @returns The Base64 encoded string.
*/
export function base64EncArrBuff(aBytes: ArrayBuffer, nBlocksSize?: number): string
{
return base64EncArr(new Uint8Array(aBytes), nBlocksSize);
}
/**
* Encodes an ArrayBuffer/Uint8Array to Base64 string.
* @param aBytes The byte array to encode.
* @param nBlocksSize Optional size for line breaks in the output.
* @returns The Base64 encoded string.
*/
export function base64EncIsomorphicBuffer(aBytes: IsomorphicBuffer, nBlocksSize?: number): string
{
return base64EncArr(aBytes.toArray(), nBlocksSize);
}
// --- UTF-8 (native encoders/decoders) ---
/* UTF-8 array to JS string and vice versa */
/**
* Converts a UTF-8 encoded byte array to a JavaScript string.
* @param aBytes The UTF-8 encoded byte array.
* @returns The decoded string.
*/
export function UTF8ArrToStr(aBytes: Uint8Array): string
{
return new TextDecoder("utf-8").decode(aBytes);
}
/**
* IsomorphicBuffer UTF-8 -> string (same name/signature).
*/
/* UTF-8 array to JS string and vice versa */
/**
* Converts a UTF-8 encoded byte array to a JavaScript string.
* @param aBytes The UTF-8 encoded byte array.
* @returns The decoded string.
*/
export function UTF8IsomorphicBufferToStr(aBytes: IsomorphicBuffer): string
{
return UTF8ArrToStr(aBytes.toArray());
}
/**
* Compute UTF-8 byte length for a JS string (same name/signature).
* Uses correct UTF-8 ranges (1–4 bytes).
*/
export function strUTF8ByteLength(sDOMStr: string): number
{
let bytes = 0;
for (let i = 0; i < sDOMStr.length; i++)
{
const cp = sDOMStr.codePointAt(i)!;
if (cp <= 0x7F) bytes += 1;
else if (cp <= 0x7FF) bytes += 2;
else if (cp <= 0xFFFF) bytes += 3;
else { bytes += 4; i++; } // surrogate pair consumed
}
return bytes;
}
/**
* Converts a JavaScript string to a UTF-8 encoded ArrayBuffer.
* @param sDOMStr The input string to encode.
* @returns The UTF-8 encoded ArrayBuffer.
*/
export function strToUTF8Arr(sDOMStr: string)
{
return new TextEncoder().encode(sDOMStr) as Uint8Array<ArrayBuffer>;
}