80 lines
2.4 KiB
TypeScript
80 lines
2.4 KiB
TypeScript
|
|
/**
|
||
|
|
* Encoding utilities for WHMCS helpers (domain-internal)
|
||
|
|
*
|
||
|
|
* Avoid hard Node `Buffer` dependency. Use it when available; otherwise fall back
|
||
|
|
* to pure JS UTF-8 + base64.
|
||
|
|
*/
|
||
|
|
|
||
|
|
const BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||
|
|
|
||
|
|
function encodeUtf8Fallback(value: string): Uint8Array {
|
||
|
|
const bytes: number[] = [];
|
||
|
|
for (let i = 0; i < value.length; i++) {
|
||
|
|
let codePoint = value.charCodeAt(i);
|
||
|
|
|
||
|
|
// Handle surrogate pairs
|
||
|
|
if (codePoint >= 0xd800 && codePoint <= 0xdbff && i + 1 < value.length) {
|
||
|
|
const next = value.charCodeAt(i + 1);
|
||
|
|
if (next >= 0xdc00 && next <= 0xdfff) {
|
||
|
|
codePoint = ((codePoint - 0xd800) << 10) + (next - 0xdc00) + 0x10000;
|
||
|
|
i++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (codePoint <= 0x7f) {
|
||
|
|
bytes.push(codePoint);
|
||
|
|
} else if (codePoint <= 0x7ff) {
|
||
|
|
bytes.push(0xc0 | (codePoint >> 6));
|
||
|
|
bytes.push(0x80 | (codePoint & 0x3f));
|
||
|
|
} else if (codePoint <= 0xffff) {
|
||
|
|
bytes.push(0xe0 | (codePoint >> 12));
|
||
|
|
bytes.push(0x80 | ((codePoint >> 6) & 0x3f));
|
||
|
|
bytes.push(0x80 | (codePoint & 0x3f));
|
||
|
|
} else {
|
||
|
|
bytes.push(0xf0 | (codePoint >> 18));
|
||
|
|
bytes.push(0x80 | ((codePoint >> 12) & 0x3f));
|
||
|
|
bytes.push(0x80 | ((codePoint >> 6) & 0x3f));
|
||
|
|
bytes.push(0x80 | (codePoint & 0x3f));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return new Uint8Array(bytes);
|
||
|
|
}
|
||
|
|
|
||
|
|
function encodeUtf8(value: string): Uint8Array {
|
||
|
|
if (typeof TextEncoder !== "undefined") {
|
||
|
|
return new TextEncoder().encode(value);
|
||
|
|
}
|
||
|
|
return encodeUtf8Fallback(value);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function byteLengthUtf8(value: string): number {
|
||
|
|
if (typeof Buffer !== "undefined") {
|
||
|
|
return Buffer.byteLength(value, "utf8");
|
||
|
|
}
|
||
|
|
return encodeUtf8(value).length;
|
||
|
|
}
|
||
|
|
|
||
|
|
function base64EncodeBytes(bytes: Uint8Array): string {
|
||
|
|
let out = "";
|
||
|
|
for (let i = 0; i < bytes.length; i += 3) {
|
||
|
|
const b1 = bytes[i] ?? 0;
|
||
|
|
const b2 = bytes[i + 1] ?? 0;
|
||
|
|
const b3 = bytes[i + 2] ?? 0;
|
||
|
|
|
||
|
|
const triplet = (b1 << 16) | (b2 << 8) | b3;
|
||
|
|
|
||
|
|
out += BASE64_ALPHABET[(triplet >> 18) & 0x3f];
|
||
|
|
out += BASE64_ALPHABET[(triplet >> 12) & 0x3f];
|
||
|
|
out += i + 1 < bytes.length ? BASE64_ALPHABET[(triplet >> 6) & 0x3f] : "=";
|
||
|
|
out += i + 2 < bytes.length ? BASE64_ALPHABET[triplet & 0x3f] : "=";
|
||
|
|
}
|
||
|
|
return out;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function utf8ToBase64(value: string): string {
|
||
|
|
if (typeof Buffer !== "undefined") {
|
||
|
|
return Buffer.from(value, "utf8").toString("base64");
|
||
|
|
}
|
||
|
|
return base64EncodeBytes(encodeUtf8(value));
|
||
|
|
}
|