hot-updater
Version:
React Native OTA solution for self-hosted
2,041 lines • 591 kB
JavaScript
import { createRequire } from "node:module";
import fs from "fs";
import path from "path";
import { colors, getCwd, loadConfig, p } from "@hot-updater/cli-tools";
import crypto from "node:crypto";
import fs$1 from "node:fs/promises";
import path$1 from "node:path";
//#region \0rolldown/runtime.js
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __commonJSMin = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
key = keys[i];
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
get: ((k) => from[k]).bind(null, key),
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
});
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
value: mod,
enumerable: true
}) : target, mod));
var __require = /* @__PURE__ */ createRequire(import.meta.url);
//#endregion
//#region ../../node_modules/.pnpm/fast-xml-parser@5.7.2/node_modules/fast-xml-parser/src/util.js
const nameStartChar = ":A-Za-z_\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD";
nameStartChar + "";
const nameRegexp = "[" + nameStartChar + "][:A-Za-z_\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD\\-.\\d\\u00B7\\u0300-\\u036F\\u203F-\\u2040]*";
const regexName = new RegExp("^" + nameRegexp + "$");
function getAllMatches(string, regex) {
const matches = [];
let match = regex.exec(string);
while (match) {
const allmatches = [];
allmatches.startIndex = regex.lastIndex - match[0].length;
const len = match.length;
for (let index = 0; index < len; index++) allmatches.push(match[index]);
matches.push(allmatches);
match = regex.exec(string);
}
return matches;
}
const isName = function(string) {
const match = regexName.exec(string);
return !(match === null || typeof match === "undefined");
};
function isExist(v) {
return typeof v !== "undefined";
}
/**
* Dangerous property names that could lead to prototype pollution or security issues
*/
const DANGEROUS_PROPERTY_NAMES = [
"hasOwnProperty",
"toString",
"valueOf",
"__defineGetter__",
"__defineSetter__",
"__lookupGetter__",
"__lookupSetter__"
];
const criticalProperties = [
"__proto__",
"constructor",
"prototype"
];
//#endregion
//#region ../../node_modules/.pnpm/fast-xml-parser@5.7.2/node_modules/fast-xml-parser/src/validator.js
const defaultOptions$2 = {
allowBooleanAttributes: false,
unpairedTags: []
};
function validate(xmlData, options) {
options = Object.assign({}, defaultOptions$2, options);
const tags = [];
let tagFound = false;
let reachedRoot = false;
if (xmlData[0] === "") xmlData = xmlData.substr(1);
for (let i = 0; i < xmlData.length; i++) if (xmlData[i] === "<" && xmlData[i + 1] === "?") {
i += 2;
i = readPI(xmlData, i);
if (i.err) return i;
} else if (xmlData[i] === "<") {
let tagStartPos = i;
i++;
if (xmlData[i] === "!") {
i = readCommentAndCDATA(xmlData, i);
continue;
} else {
let closingTag = false;
if (xmlData[i] === "/") {
closingTag = true;
i++;
}
let tagName = "";
for (; i < xmlData.length && xmlData[i] !== ">" && xmlData[i] !== " " && xmlData[i] !== " " && xmlData[i] !== "\n" && xmlData[i] !== "\r"; i++) tagName += xmlData[i];
tagName = tagName.trim();
if (tagName[tagName.length - 1] === "/") {
tagName = tagName.substring(0, tagName.length - 1);
i--;
}
if (!validateTagName(tagName)) {
let msg;
if (tagName.trim().length === 0) msg = "Invalid space after '<'.";
else msg = "Tag '" + tagName + "' is an invalid name.";
return getErrorObject("InvalidTag", msg, getLineNumberForPosition(xmlData, i));
}
const result = readAttributeStr(xmlData, i);
if (result === false) return getErrorObject("InvalidAttr", "Attributes for '" + tagName + "' have open quote.", getLineNumberForPosition(xmlData, i));
let attrStr = result.value;
i = result.index;
if (attrStr[attrStr.length - 1] === "/") {
const attrStrStart = i - attrStr.length;
attrStr = attrStr.substring(0, attrStr.length - 1);
const isValid = validateAttributeString(attrStr, options);
if (isValid === true) tagFound = true;
else return getErrorObject(isValid.err.code, isValid.err.msg, getLineNumberForPosition(xmlData, attrStrStart + isValid.err.line));
} else if (closingTag) if (!result.tagClosed) return getErrorObject("InvalidTag", "Closing tag '" + tagName + "' doesn't have proper closing.", getLineNumberForPosition(xmlData, i));
else if (attrStr.trim().length > 0) return getErrorObject("InvalidTag", "Closing tag '" + tagName + "' can't have attributes or invalid starting.", getLineNumberForPosition(xmlData, tagStartPos));
else if (tags.length === 0) return getErrorObject("InvalidTag", "Closing tag '" + tagName + "' has not been opened.", getLineNumberForPosition(xmlData, tagStartPos));
else {
const otg = tags.pop();
if (tagName !== otg.tagName) {
let openPos = getLineNumberForPosition(xmlData, otg.tagStartPos);
return getErrorObject("InvalidTag", "Expected closing tag '" + otg.tagName + "' (opened in line " + openPos.line + ", col " + openPos.col + ") instead of closing tag '" + tagName + "'.", getLineNumberForPosition(xmlData, tagStartPos));
}
if (tags.length == 0) reachedRoot = true;
}
else {
const isValid = validateAttributeString(attrStr, options);
if (isValid !== true) return getErrorObject(isValid.err.code, isValid.err.msg, getLineNumberForPosition(xmlData, i - attrStr.length + isValid.err.line));
if (reachedRoot === true) return getErrorObject("InvalidXml", "Multiple possible root nodes found.", getLineNumberForPosition(xmlData, i));
else if (options.unpairedTags.indexOf(tagName) !== -1) {} else tags.push({
tagName,
tagStartPos
});
tagFound = true;
}
for (i++; i < xmlData.length; i++) if (xmlData[i] === "<") if (xmlData[i + 1] === "!") {
i++;
i = readCommentAndCDATA(xmlData, i);
continue;
} else if (xmlData[i + 1] === "?") {
i = readPI(xmlData, ++i);
if (i.err) return i;
} else break;
else if (xmlData[i] === "&") {
const afterAmp = validateAmpersand(xmlData, i);
if (afterAmp == -1) return getErrorObject("InvalidChar", "char '&' is not expected.", getLineNumberForPosition(xmlData, i));
i = afterAmp;
} else if (reachedRoot === true && !isWhiteSpace(xmlData[i])) return getErrorObject("InvalidXml", "Extra text at the end", getLineNumberForPosition(xmlData, i));
if (xmlData[i] === "<") i--;
}
} else {
if (isWhiteSpace(xmlData[i])) continue;
return getErrorObject("InvalidChar", "char '" + xmlData[i] + "' is not expected.", getLineNumberForPosition(xmlData, i));
}
if (!tagFound) return getErrorObject("InvalidXml", "Start tag expected.", 1);
else if (tags.length == 1) return getErrorObject("InvalidTag", "Unclosed tag '" + tags[0].tagName + "'.", getLineNumberForPosition(xmlData, tags[0].tagStartPos));
else if (tags.length > 0) return getErrorObject("InvalidXml", "Invalid '" + JSON.stringify(tags.map((t) => t.tagName), null, 4).replace(/\r?\n/g, "") + "' found.", {
line: 1,
col: 1
});
return true;
}
function isWhiteSpace(char) {
return char === " " || char === " " || char === "\n" || char === "\r";
}
/**
* Read Processing insstructions and skip
* @param {*} xmlData
* @param {*} i
*/
function readPI(xmlData, i) {
const start = i;
for (; i < xmlData.length; i++) if (xmlData[i] == "?" || xmlData[i] == " ") {
const tagname = xmlData.substr(start, i - start);
if (i > 5 && tagname === "xml") return getErrorObject("InvalidXml", "XML declaration allowed only at the start of the document.", getLineNumberForPosition(xmlData, i));
else if (xmlData[i] == "?" && xmlData[i + 1] == ">") {
i++;
break;
} else continue;
}
return i;
}
function readCommentAndCDATA(xmlData, i) {
if (xmlData.length > i + 5 && xmlData[i + 1] === "-" && xmlData[i + 2] === "-") {
for (i += 3; i < xmlData.length; i++) if (xmlData[i] === "-" && xmlData[i + 1] === "-" && xmlData[i + 2] === ">") {
i += 2;
break;
}
} else if (xmlData.length > i + 8 && xmlData[i + 1] === "D" && xmlData[i + 2] === "O" && xmlData[i + 3] === "C" && xmlData[i + 4] === "T" && xmlData[i + 5] === "Y" && xmlData[i + 6] === "P" && xmlData[i + 7] === "E") {
let angleBracketsCount = 1;
for (i += 8; i < xmlData.length; i++) if (xmlData[i] === "<") angleBracketsCount++;
else if (xmlData[i] === ">") {
angleBracketsCount--;
if (angleBracketsCount === 0) break;
}
} else if (xmlData.length > i + 9 && xmlData[i + 1] === "[" && xmlData[i + 2] === "C" && xmlData[i + 3] === "D" && xmlData[i + 4] === "A" && xmlData[i + 5] === "T" && xmlData[i + 6] === "A" && xmlData[i + 7] === "[") {
for (i += 8; i < xmlData.length; i++) if (xmlData[i] === "]" && xmlData[i + 1] === "]" && xmlData[i + 2] === ">") {
i += 2;
break;
}
}
return i;
}
const doubleQuote = "\"";
const singleQuote = "'";
/**
* Keep reading xmlData until '<' is found outside the attribute value.
* @param {string} xmlData
* @param {number} i
*/
function readAttributeStr(xmlData, i) {
let attrStr = "";
let startChar = "";
let tagClosed = false;
for (; i < xmlData.length; i++) {
if (xmlData[i] === doubleQuote || xmlData[i] === singleQuote) if (startChar === "") startChar = xmlData[i];
else if (startChar !== xmlData[i]) {} else startChar = "";
else if (xmlData[i] === ">") {
if (startChar === "") {
tagClosed = true;
break;
}
}
attrStr += xmlData[i];
}
if (startChar !== "") return false;
return {
value: attrStr,
index: i,
tagClosed
};
}
/**
* Select all the attributes whether valid or invalid.
*/
const validAttrStrRegxp = /* @__PURE__ */ new RegExp("(\\s*)([^\\s=]+)(\\s*=)?(\\s*(['\"])(([\\s\\S])*?)\\5)?", "g");
function validateAttributeString(attrStr, options) {
const matches = getAllMatches(attrStr, validAttrStrRegxp);
const attrNames = {};
for (let i = 0; i < matches.length; i++) {
if (matches[i][1].length === 0) return getErrorObject("InvalidAttr", "Attribute '" + matches[i][2] + "' has no space in starting.", getPositionFromMatch(matches[i]));
else if (matches[i][3] !== void 0 && matches[i][4] === void 0) return getErrorObject("InvalidAttr", "Attribute '" + matches[i][2] + "' is without value.", getPositionFromMatch(matches[i]));
else if (matches[i][3] === void 0 && !options.allowBooleanAttributes) return getErrorObject("InvalidAttr", "boolean attribute '" + matches[i][2] + "' is not allowed.", getPositionFromMatch(matches[i]));
const attrName = matches[i][2];
if (!validateAttrName(attrName)) return getErrorObject("InvalidAttr", "Attribute '" + attrName + "' is an invalid name.", getPositionFromMatch(matches[i]));
if (!Object.prototype.hasOwnProperty.call(attrNames, attrName)) attrNames[attrName] = 1;
else return getErrorObject("InvalidAttr", "Attribute '" + attrName + "' is repeated.", getPositionFromMatch(matches[i]));
}
return true;
}
function validateNumberAmpersand(xmlData, i) {
let re = /\d/;
if (xmlData[i] === "x") {
i++;
re = /[\da-fA-F]/;
}
for (; i < xmlData.length; i++) {
if (xmlData[i] === ";") return i;
if (!xmlData[i].match(re)) break;
}
return -1;
}
function validateAmpersand(xmlData, i) {
i++;
if (xmlData[i] === ";") return -1;
if (xmlData[i] === "#") {
i++;
return validateNumberAmpersand(xmlData, i);
}
let count = 0;
for (; i < xmlData.length; i++, count++) {
if (xmlData[i].match(/\w/) && count < 20) continue;
if (xmlData[i] === ";") break;
return -1;
}
return i;
}
function getErrorObject(code, message, lineNumber) {
return { err: {
code,
msg: message,
line: lineNumber.line || lineNumber,
col: lineNumber.col
} };
}
function validateAttrName(attrName) {
return isName(attrName);
}
function validateTagName(tagname) {
return isName(tagname);
}
function getLineNumberForPosition(xmlData, index) {
const lines = xmlData.substring(0, index).split(/\r?\n/);
return {
line: lines.length,
col: lines[lines.length - 1].length + 1
};
}
function getPositionFromMatch(match) {
return match.startIndex + match[1].length;
}
//#endregion
//#region ../../node_modules/.pnpm/@nodable+entities@2.1.0/node_modules/@nodable/entities/src/entities.js
/**
* Basic Latin & Special Characters
* @type {Record<string, string>}
*/
const BASIC_LATIN = {
amp: "&",
AMP: "&",
lt: "<",
LT: "<",
gt: ">",
GT: ">",
quot: "\"",
QUOT: "\"",
apos: "'",
lsquo: "‘",
rsquo: "’",
ldquo: "“",
rdquo: "”",
lsquor: "‚",
rsquor: "’",
ldquor: "„",
bdquo: "„",
comma: ",",
period: ".",
colon: ":",
semi: ";",
excl: "!",
quest: "?",
num: "#",
dollar: "$",
percent: "%",
amp: "&",
ast: "*",
commat: "@",
lowbar: "_",
verbar: "|",
vert: "|",
sol: "/",
bsol: "\\",
lbrace: "{",
rbrace: "}",
lbrack: "[",
rbrack: "]",
lpar: "(",
rpar: ")",
nbsp: "\xA0",
iexcl: "¡",
cent: "¢",
pound: "£",
curren: "¤",
yen: "¥",
brvbar: "¦",
sect: "§",
uml: "¨",
copy: "©",
COPY: "©",
ordf: "ª",
laquo: "«",
not: "¬",
shy: "",
reg: "®",
REG: "®",
macr: "¯",
deg: "°",
plusmn: "±",
sup2: "²",
sup3: "³",
acute: "´",
micro: "µ",
para: "¶",
middot: "·",
cedil: "¸",
sup1: "¹",
ordm: "º",
raquo: "»",
frac14: "¼",
frac12: "½",
half: "½",
frac34: "¾",
iquest: "¿",
times: "×",
div: "÷",
divide: "÷"
};
/**
* Latin Extended & Accented Letters (A-Z)
* @type {Record<string, string>}
*/
const LATIN_ACCENTS = {
Agrave: "À",
agrave: "à",
Aacute: "Á",
aacute: "á",
Acirc: "Â",
acirc: "â",
Atilde: "Ã",
atilde: "ã",
Auml: "Ä",
auml: "ä",
Aring: "Å",
aring: "å",
AElig: "Æ",
aelig: "æ",
Ccedil: "Ç",
ccedil: "ç",
Egrave: "È",
egrave: "è",
Eacute: "É",
eacute: "é",
Ecirc: "Ê",
ecirc: "ê",
Euml: "Ë",
euml: "ë",
Igrave: "Ì",
igrave: "ì",
Iacute: "Í",
iacute: "í",
Icirc: "Î",
icirc: "î",
Iuml: "Ï",
iuml: "ï",
ETH: "Ð",
eth: "ð",
Ntilde: "Ñ",
ntilde: "ñ",
Ograve: "Ò",
ograve: "ò",
Oacute: "Ó",
oacute: "ó",
Ocirc: "Ô",
ocirc: "ô",
Otilde: "Õ",
otilde: "õ",
Ouml: "Ö",
ouml: "ö",
Oslash: "Ø",
oslash: "ø",
Ugrave: "Ù",
ugrave: "ù",
Uacute: "Ú",
uacute: "ú",
Ucirc: "Û",
ucirc: "û",
Uuml: "Ü",
uuml: "ü",
Yacute: "Ý",
yacute: "ý",
THORN: "Þ",
thorn: "þ",
szlig: "ß",
yuml: "ÿ",
Yuml: "Ÿ"
};
/**
* Latin Extended (Letters with diacritics)
* @type {Record<string, string>}
*/
const LATIN_EXTENDED = {
Amacr: "Ā",
amacr: "ā",
Abreve: "Ă",
abreve: "ă",
Aogon: "Ą",
aogon: "ą",
Cacute: "Ć",
cacute: "ć",
Ccirc: "Ĉ",
ccirc: "ĉ",
Cdot: "Ċ",
cdot: "ċ",
Ccaron: "Č",
ccaron: "č",
Dcaron: "Ď",
dcaron: "ď",
Dstrok: "Đ",
dstrok: "đ",
Emacr: "Ē",
emacr: "ē",
Ecaron: "Ě",
ecaron: "ě",
Edot: "Ė",
edot: "ė",
Eogon: "Ę",
eogon: "ę",
Gcirc: "Ĝ",
gcirc: "ĝ",
Gbreve: "Ğ",
gbreve: "ğ",
Gdot: "Ġ",
gdot: "ġ",
Gcedil: "Ģ",
Hcirc: "Ĥ",
hcirc: "ĥ",
Hstrok: "Ħ",
hstrok: "ħ",
Itilde: "Ĩ",
itilde: "ĩ",
Imacr: "Ī",
imacr: "ī",
Iogon: "Į",
iogon: "į",
Idot: "İ",
IJlig: "IJ",
ijlig: "ij",
Jcirc: "Ĵ",
jcirc: "ĵ",
Kcedil: "Ķ",
kcedil: "ķ",
kgreen: "ĸ",
Lacute: "Ĺ",
lacute: "ĺ",
Lcedil: "Ļ",
lcedil: "ļ",
Lcaron: "Ľ",
lcaron: "ľ",
Lmidot: "Ŀ",
lmidot: "ŀ",
Lstrok: "Ł",
lstrok: "ł",
Nacute: "Ń",
nacute: "ń",
Ncaron: "Ň",
ncaron: "ň",
Ncedil: "Ņ",
ncedil: "ņ",
ENG: "Ŋ",
eng: "ŋ",
Omacr: "Ō",
omacr: "ō",
Odblac: "Ő",
odblac: "ő",
OElig: "Œ",
oelig: "œ",
Racute: "Ŕ",
racute: "ŕ",
Rcaron: "Ř",
rcaron: "ř",
Rcedil: "Ŗ",
rcedil: "ŗ",
Sacute: "Ś",
sacute: "ś",
Scirc: "Ŝ",
scirc: "ŝ",
Scedil: "Ş",
scedil: "ş",
Scaron: "Š",
scaron: "š",
Tcedil: "Ţ",
tcedil: "ţ",
Tcaron: "Ť",
tcaron: "ť",
Tstrok: "Ŧ",
tstrok: "ŧ",
Utilde: "Ũ",
utilde: "ũ",
Umacr: "Ū",
umacr: "ū",
Ubreve: "Ŭ",
ubreve: "ŭ",
Uring: "Ů",
uring: "ů",
Udblac: "Ű",
udblac: "ű",
Uogon: "Ų",
uogon: "ų",
Wcirc: "Ŵ",
wcirc: "ŵ",
Ycirc: "Ŷ",
ycirc: "ŷ",
Zacute: "Ź",
zacute: "ź",
Zdot: "Ż",
zdot: "ż",
Zcaron: "Ž",
zcaron: "ž"
};
/**
* Greek Letters
* @type {Record<string, string>}
*/
const GREEK = {
Alpha: "Α",
alpha: "α",
Beta: "Β",
beta: "β",
Gamma: "Γ",
gamma: "γ",
Delta: "Δ",
delta: "δ",
Epsilon: "Ε",
epsilon: "ε",
epsiv: "ϵ",
varepsilon: "ϵ",
Zeta: "Ζ",
zeta: "ζ",
Eta: "Η",
eta: "η",
Theta: "Θ",
theta: "θ",
thetasym: "ϑ",
vartheta: "ϑ",
Iota: "Ι",
iota: "ι",
Kappa: "Κ",
kappa: "κ",
kappav: "ϰ",
varkappa: "ϰ",
Lambda: "Λ",
lambda: "λ",
Mu: "Μ",
mu: "μ",
Nu: "Ν",
nu: "ν",
Xi: "Ξ",
xi: "ξ",
Omicron: "Ο",
omicron: "ο",
Pi: "Π",
pi: "π",
piv: "ϖ",
varpi: "ϖ",
Rho: "Ρ",
rho: "ρ",
rhov: "ϱ",
varrho: "ϱ",
Sigma: "Σ",
sigma: "σ",
sigmaf: "ς",
sigmav: "ς",
varsigma: "ς",
Tau: "Τ",
tau: "τ",
Upsilon: "Υ",
upsilon: "υ",
upsi: "υ",
Upsi: "ϒ",
upsih: "ϒ",
Phi: "Φ",
phi: "φ",
phiv: "ϕ",
varphi: "ϕ",
Chi: "Χ",
chi: "χ",
Psi: "Ψ",
psi: "ψ",
Omega: "Ω",
omega: "ω",
ohm: "Ω",
Gammad: "Ϝ",
gammad: "ϝ",
digamma: "ϝ"
};
/**
* Cyrillic Letters
* @type {Record<string, string>}
*/
const CYRILLIC = {
Afr: "𝔄",
afr: "𝔞",
Acy: "А",
acy: "а",
Bcy: "Б",
bcy: "б",
Vcy: "В",
vcy: "в",
Gcy: "Г",
gcy: "г",
Dcy: "Д",
dcy: "д",
IEcy: "Е",
iecy: "е",
IOcy: "Ё",
iocy: "ё",
ZHcy: "Ж",
zhcy: "ж",
Zcy: "З",
zcy: "з",
Icy: "И",
icy: "и",
Jcy: "Й",
jcy: "й",
Kcy: "К",
kcy: "к",
Lcy: "Л",
lcy: "л",
Mcy: "М",
mcy: "м",
Ncy: "Н",
ncy: "н",
Ocy: "О",
ocy: "о",
Pcy: "П",
pcy: "п",
Rcy: "Р",
rcy: "р",
Scy: "С",
scy: "с",
Tcy: "Т",
tcy: "т",
Ucy: "У",
ucy: "у",
Fcy: "Ф",
fcy: "ф",
KHcy: "Х",
khcy: "х",
TScy: "Ц",
tscy: "ц",
CHcy: "Ч",
chcy: "ч",
SHcy: "Ш",
shcy: "ш",
SHCHcy: "Щ",
shchcy: "щ",
HARDcy: "Ъ",
hardcy: "ъ",
Ycy: "Ы",
ycy: "ы",
SOFTcy: "Ь",
softcy: "ь",
Ecy: "Э",
ecy: "э",
YUcy: "Ю",
yucy: "ю",
YAcy: "Я",
yacy: "я",
DJcy: "Ђ",
djcy: "ђ",
GJcy: "Ѓ",
gjcy: "ѓ",
Jukcy: "Є",
jukcy: "є",
DScy: "Ѕ",
dscy: "ѕ",
Iukcy: "І",
iukcy: "і",
YIcy: "Ї",
yicy: "ї",
Jsercy: "Ј",
jsercy: "ј",
LJcy: "Љ",
ljcy: "љ",
NJcy: "Њ",
njcy: "њ",
TSHcy: "Ћ",
tshcy: "ћ",
KJcy: "Ќ",
kjcy: "ќ",
Ubrcy: "Ў",
ubrcy: "ў",
DZcy: "Џ",
dzcy: "џ"
};
/**
* Mathematical Operators & Relations
* @type {Record<string, string>}
*/
const MATH = {
plus: "+",
minus: "−",
mnplus: "∓",
mp: "∓",
pm: "±",
times: "×",
div: "÷",
divide: "÷",
sdot: "⋅",
star: "☆",
starf: "★",
bigstar: "★",
lowast: "∗",
ast: "*",
midast: "*",
compfn: "∘",
smallcircle: "∘",
bullet: "•",
bull: "•",
nbsp: "\xA0",
hellip: "…",
mldr: "…",
prime: "′",
Prime: "″",
tprime: "‴",
bprime: "‵",
backprime: "‵",
minus: "−",
minusd: "∸",
dotminus: "∸",
plusdo: "∔",
dotplus: "∔",
plusmn: "±",
minusplus: "∓",
mnplus: "∓",
mp: "∓",
setminus: "∖",
smallsetminus: "∖",
Backslash: "∖",
setmn: "∖",
ssetmn: "∖",
lowbar: "_",
verbar: "|",
vert: "|",
VerticalLine: "|",
colon: ":",
Colon: "∷",
Proportion: "∷",
ratio: "∶",
equals: "=",
ne: "≠",
nequiv: "≢",
equiv: "≡",
Congruent: "≡",
sim: "∼",
thicksim: "∼",
thksim: "∼",
sime: "≃",
simeq: "≃",
TildeEqual: "≃",
asymp: "≈",
approx: "≈",
thickapprox: "≈",
thkap: "≈",
TildeTilde: "≈",
ncong: "≇",
cong: "≅",
TildeFullEqual: "≅",
asympeq: "≍",
CupCap: "≍",
bump: "≎",
Bumpeq: "≎",
HumpDownHump: "≎",
bumpe: "≏",
bumpeq: "≏",
HumpEqual: "≏",
dotminus: "∸",
minusd: "∸",
plusdo: "∔",
dotplus: "∔",
le: "≤",
LessEqual: "≤",
ge: "≥",
GreaterEqual: "≥",
lesseqgtr: "⋚",
lesseqqgtr: "⪋",
greater: ">",
less: "<"
};
/**
* Mathematical Operators (Advanced)
* @type {Record<string, string>}
*/
const MATH_ADVANCED = {
alefsym: "ℵ",
aleph: "ℵ",
beth: "ℶ",
gimel: "ℷ",
daleth: "ℸ",
forall: "∀",
ForAll: "∀",
part: "∂",
PartialD: "∂",
exist: "∃",
Exists: "∃",
nexist: "∄",
nexists: "∄",
empty: "∅",
emptyset: "∅",
emptyv: "∅",
varnothing: "∅",
nabla: "∇",
Del: "∇",
isin: "∈",
isinv: "∈",
in: "∈",
Element: "∈",
notin: "∉",
notinva: "∉",
ni: "∋",
niv: "∋",
SuchThat: "∋",
ReverseElement: "∋",
notni: "∌",
notniva: "∌",
prod: "∏",
Product: "∏",
coprod: "∐",
Coproduct: "∐",
sum: "∑",
Sum: "∑",
minus: "−",
mp: "∓",
plusdo: "∔",
dotplus: "∔",
setminus: "∖",
lowast: "∗",
radic: "√",
Sqrt: "√",
prop: "∝",
propto: "∝",
Proportional: "∝",
varpropto: "∝",
infin: "∞",
infintie: "⧝",
ang: "∠",
angle: "∠",
angmsd: "∡",
measuredangle: "∡",
angsph: "∢",
mid: "∣",
VerticalBar: "∣",
nmid: "∤",
nsmid: "∤",
npar: "∦",
parallel: "∥",
spar: "∥",
nparallel: "∦",
nspar: "∦",
and: "∧",
wedge: "∧",
or: "∨",
vee: "∨",
cap: "∩",
cup: "∪",
int: "∫",
Integral: "∫",
conint: "∮",
ContourIntegral: "∮",
Conint: "∯",
DoubleContourIntegral: "∯",
Cconint: "∰",
there4: "∴",
therefore: "∴",
Therefore: "∴",
becaus: "∵",
because: "∵",
Because: "∵",
ratio: "∶",
Proportion: "∷",
minusd: "∸",
dotminus: "∸",
mDDot: "∺",
homtht: "∻",
sim: "∼",
bsimg: "∽",
backsim: "∽",
ac: "∾",
mstpos: "∾",
acd: "∿",
VerticalTilde: "≀",
wr: "≀",
wreath: "≀",
nsime: "≄",
nsimeq: "≄",
nsimeq: "≄",
ncong: "≇",
simne: "≆",
ncongdot: "⩭̸",
ngsim: "≵",
nsim: "≁",
napprox: "≉",
nap: "≉",
ngeq: "≱",
nge: "≱",
nleq: "≰",
nle: "≰",
ngtr: "≯",
ngt: "≯",
nless: "≮",
nlt: "≮",
nprec: "⊀",
npr: "⊀",
nsucc: "⊁",
nsc: "⊁"
};
/**
* Arrows
* @type {Record<string, string>}
*/
const ARROWS = {
larr: "←",
leftarrow: "←",
LeftArrow: "←",
uarr: "↑",
uparrow: "↑",
UpArrow: "↑",
rarr: "→",
rightarrow: "→",
RightArrow: "→",
darr: "↓",
downarrow: "↓",
DownArrow: "↓",
harr: "↔",
leftrightarrow: "↔",
LeftRightArrow: "↔",
varr: "↕",
updownarrow: "↕",
UpDownArrow: "↕",
nwarr: "↖",
nwarrow: "↖",
UpperLeftArrow: "↖",
nearr: "↗",
nearrow: "↗",
UpperRightArrow: "↗",
searr: "↘",
searrow: "↘",
LowerRightArrow: "↘",
swarr: "↙",
swarrow: "↙",
LowerLeftArrow: "↙",
lArr: "⇐",
Leftarrow: "⇐",
uArr: "⇑",
Uparrow: "⇑",
rArr: "⇒",
Rightarrow: "⇒",
dArr: "⇓",
Downarrow: "⇓",
hArr: "⇔",
Leftrightarrow: "⇔",
iff: "⇔",
vArr: "⇕",
Updownarrow: "⇕",
lAarr: "⇚",
Lleftarrow: "⇚",
rAarr: "⇛",
Rrightarrow: "⇛",
lrarr: "⇆",
leftrightarrows: "⇆",
rlarr: "⇄",
rightleftarrows: "⇄",
lrhar: "⇋",
leftrightharpoons: "⇋",
ReverseEquilibrium: "⇋",
rlhar: "⇌",
rightleftharpoons: "⇌",
Equilibrium: "⇌",
udarr: "⇅",
UpArrowDownArrow: "⇅",
duarr: "⇵",
DownArrowUpArrow: "⇵",
llarr: "⇇",
leftleftarrows: "⇇",
rrarr: "⇉",
rightrightarrows: "⇉",
ddarr: "⇊",
downdownarrows: "⇊",
har: "↽",
lhard: "↽",
leftharpoondown: "↽",
lharu: "↼",
leftharpoonup: "↼",
rhard: "⇁",
rightharpoondown: "⇁",
rharu: "⇀",
rightharpoonup: "⇀",
lsh: "↰",
Lsh: "↰",
rsh: "↱",
Rsh: "↱",
ldsh: "↲",
rdsh: "↳",
hookleftarrow: "↩",
hookrightarrow: "↪",
mapstoleft: "↤",
mapstoup: "↥",
map: "↦",
mapsto: "↦",
mapstodown: "↧",
crarr: "↵",
nwarrow: "↖",
nearrow: "↗",
searrow: "↘",
swarrow: "↙",
nleftarrow: "↚",
nleftrightarrow: "↮",
nrightarrow: "↛",
nrarr: "↛",
larrtl: "↢",
rarrtl: "↣",
leftarrowtail: "↢",
rightarrowtail: "↣",
twoheadleftarrow: "↞",
twoheadrightarrow: "↠",
Larr: "↞",
Rarr: "↠",
larrhk: "↩",
rarrhk: "↪",
larrlp: "↫",
looparrowleft: "↫",
rarrlp: "↬",
looparrowright: "↬",
harrw: "↭",
leftrightsquigarrow: "↭",
nrarrw: "↝̸",
rarrw: "↝",
rightsquigarrow: "↝",
larrbfs: "⤟",
rarrbfs: "⤠",
nvHarr: "⤄",
nvlArr: "⤂",
nvrArr: "⤃",
larrfs: "⤝",
rarrfs: "⤞",
Map: "⤅",
larrsim: "⥳",
rarrsim: "⥴",
harrcir: "⥈",
Uarrocir: "⥉",
lurdshar: "⥊",
ldrdhar: "⥧",
ldrushar: "⥋",
rdldhar: "⥩",
lrhard: "⥭",
rlhar: "⇌",
uharr: "↾",
uharl: "↿",
dharr: "⇂",
dharl: "⇃",
Uarr: "↟",
Darr: "↡",
zigrarr: "⇝",
nwArr: "⇖",
neArr: "⇗",
seArr: "⇘",
swArr: "⇙",
nharr: "↮",
nhArr: "⇎",
nlarr: "↚",
nlArr: "⇍",
nrarr: "↛",
nrArr: "⇏",
larrb: "⇤",
LeftArrowBar: "⇤",
rarrb: "⇥",
RightArrowBar: "⇥"
};
/**
* Geometric Shapes
* @type {Record<string, string>}
*/
const SHAPES = {
square: "□",
Square: "□",
squ: "□",
squf: "▪",
squarf: "▪",
blacksquar: "▪",
blacksquare: "▪",
FilledVerySmallSquare: "▪",
blk34: "▓",
blk12: "▒",
blk14: "░",
block: "█",
srect: "▭",
rect: "▭",
sdot: "⋅",
sdotb: "⊡",
dotsquare: "⊡",
triangle: "▵",
tri: "▵",
trine: "▵",
utri: "▵",
triangledown: "▿",
dtri: "▿",
tridown: "▿",
triangleleft: "◃",
ltri: "◃",
triangleright: "▹",
rtri: "▹",
blacktriangle: "▴",
utrif: "▴",
blacktriangledown: "▾",
dtrif: "▾",
blacktriangleleft: "◂",
ltrif: "◂",
blacktriangleright: "▸",
rtrif: "▸",
loz: "◊",
lozenge: "◊",
blacklozenge: "⧫",
lozf: "⧫",
bigcirc: "◯",
xcirc: "◯",
circ: "ˆ",
Circle: "○",
cir: "○",
o: "○",
bullet: "•",
bull: "•",
hellip: "…",
mldr: "…",
nldr: "‥",
boxh: "─",
HorizontalLine: "─",
boxv: "│",
boxdr: "┌",
boxdl: "┐",
boxur: "└",
boxul: "┘",
boxvr: "├",
boxvl: "┤",
boxhd: "┬",
boxhu: "┴",
boxvh: "┼",
boxH: "═",
boxV: "║",
boxdR: "╒",
boxDr: "╓",
boxDR: "╔",
boxDl: "╕",
boxdL: "╖",
boxDL: "╗",
boxuR: "╘",
boxUr: "╙",
boxUR: "╚",
boxUl: "╜",
boxuL: "╛",
boxUL: "╝",
boxvR: "╞",
boxVr: "╟",
boxVR: "╠",
boxVl: "╢",
boxvL: "╡",
boxVL: "╣",
boxHd: "╤",
boxhD: "╥",
boxHD: "╦",
boxHu: "╧",
boxhU: "╨",
boxHU: "╩",
boxvH: "╪",
boxVh: "╫",
boxVH: "╬"
};
/**
* Punctuation & Diacritics
* @type {Record<string, string>}
*/
const PUNCTUATION = {
excl: "!",
iexcl: "¡",
brvbar: "¦",
sect: "§",
uml: "¨",
copy: "©",
ordf: "ª",
laquo: "«",
not: "¬",
shy: "",
reg: "®",
macr: "¯",
deg: "°",
plusmn: "±",
sup2: "²",
sup3: "³",
acute: "´",
micro: "µ",
para: "¶",
middot: "·",
cedil: "¸",
sup1: "¹",
ordm: "º",
raquo: "»",
frac14: "¼",
frac12: "½",
frac34: "¾",
iquest: "¿",
nbsp: "\xA0",
comma: ",",
period: ".",
colon: ":",
semi: ";",
vert: "|",
Verbar: "‖",
verbar: "|",
dblac: "˝",
circ: "ˆ",
caron: "ˇ",
breve: "˘",
dot: "˙",
ring: "˚",
ogon: "˛",
tilde: "˜",
DiacriticalGrave: "`",
DiacriticalAcute: "´",
DiacriticalTilde: "˜",
DiacriticalDot: "˙",
DiacriticalDoubleAcute: "˝",
grave: "`",
acute: "´"
};
/**
* Currency Symbols
* @type {Record<string, string>}
*/
const CURRENCY = {
cent: "¢",
pound: "£",
curren: "¤",
yen: "¥",
euro: "€",
dollar: "$",
euro: "€",
fnof: "ƒ",
inr: "₹",
af: "؋",
birr: "ብር",
peso: "₱",
rub: "₽",
won: "₩",
yuan: "¥",
cedil: "¸"
};
/**
* Fractions
* @type {Record<string, string>}
*/
const FRACTIONS = {
frac12: "½",
half: "½",
frac13: "⅓",
frac14: "¼",
frac15: "⅕",
frac16: "⅙",
frac18: "⅛",
frac23: "⅔",
frac25: "⅖",
frac34: "¾",
frac35: "⅗",
frac38: "⅜",
frac45: "⅘",
frac56: "⅚",
frac58: "⅝",
frac78: "⅞",
frasl: "⁄"
};
/**
* Miscellaneous Symbols
* @type {Record<string, string>}
*/
const MISC_SYMBOLS = {
trade: "™",
TRADE: "™",
telrec: "⌕",
target: "⌖",
ulcorn: "⌜",
ulcorner: "⌜",
urcorn: "⌝",
urcorner: "⌝",
dlcorn: "⌞",
llcorner: "⌞",
drcorn: "⌟",
lrcorner: "⌟",
intercal: "⊺",
intcal: "⊺",
oplus: "⊕",
CirclePlus: "⊕",
ominus: "⊖",
CircleMinus: "⊖",
otimes: "⊗",
CircleTimes: "⊗",
osol: "⊘",
odot: "⊙",
CircleDot: "⊙",
oast: "⊛",
circledast: "⊛",
odash: "⊝",
circleddash: "⊝",
ocirc: "⊚",
circledcirc: "⊚",
boxplus: "⊞",
plusb: "⊞",
boxminus: "⊟",
minusb: "⊟",
boxtimes: "⊠",
timesb: "⊠",
boxdot: "⊡",
sdotb: "⊡",
veebar: "⊻",
vee: "∨",
barvee: "⊽",
and: "∧",
wedge: "∧",
Cap: "⋒",
Cup: "⋓",
Fork: "⋔",
pitchfork: "⋔",
epar: "⋕",
ltlarr: "⥶",
nvap: "≍⃒",
nvsim: "∼⃒",
nvge: "≥⃒",
nvle: "≤⃒",
nvlt: "<⃒",
nvgt: ">⃒",
nvltrie: "⊴⃒",
nvrtrie: "⊵⃒",
Vdash: "⊩",
dashv: "⊣",
vDash: "⊨",
Vdash: "⊩",
Vvdash: "⊪",
nvdash: "⊬",
nvDash: "⊭",
nVdash: "⊮",
nVDash: "⊯"
};
({
...BASIC_LATIN,
...LATIN_ACCENTS,
...LATIN_EXTENDED,
...GREEK,
...CYRILLIC,
...MATH,
...MATH_ADVANCED,
...ARROWS,
...SHAPES,
...PUNCTUATION,
...CURRENCY,
...FRACTIONS,
...MISC_SYMBOLS
});
const XML = {
amp: "&",
apos: "'",
gt: ">",
lt: "<",
quot: "\""
};
const COMMON_HTML = {
nbsp: "\xA0",
copy: "©",
reg: "®",
trade: "™",
mdash: "—",
ndash: "–",
hellip: "…",
laquo: "«",
raquo: "»",
lsquo: "‘",
rsquo: "’",
ldquo: "“",
rdquo: "”",
bull: "•",
para: "¶",
sect: "§",
deg: "°",
frac12: "½",
frac14: "¼",
frac34: "¾"
};
//#endregion
//#region ../../node_modules/.pnpm/@nodable+entities@2.1.0/node_modules/@nodable/entities/src/EntityDecoder.js
const SPECIAL_CHARS = /* @__PURE__ */ new Set("!?\\\\/[]$%{}^&*()<>|+");
/**
* Validate that an entity name contains no dangerous characters.
* @param {string} name
* @returns {string} the name, unchanged
* @throws {Error} on invalid characters
*/
function validateEntityName$1(name) {
if (name[0] === "#") throw new Error(`[EntityReplacer] Invalid character '#' in entity name: "${name}"`);
for (const ch of name) if (SPECIAL_CHARS.has(ch)) throw new Error(`[EntityReplacer] Invalid character '${ch}' in entity name: "${name}"`);
return name;
}
/**
* Merge one or more entity maps into a flat name→string map.
* Accepts either:
* - plain string values: { amp: '&' }
* - legacy {regex,val} / {regx,val}: { lt: { regex: /.../, val: '<' } }
*
* Values containing '&' are skipped (recursive expansion risk).
*
* @param {...object} maps
* @returns {Record<string, string>}
*/
function mergeEntityMaps(...maps) {
const out = Object.create(null);
for (const map of maps) {
if (!map) continue;
for (const key of Object.keys(map)) {
const raw = map[key];
if (typeof raw === "string") out[key] = raw;
else if (raw && typeof raw === "object" && raw.val !== void 0) {
const val = raw.val;
if (typeof val === "string") out[key] = val;
}
}
}
return out;
}
const LIMIT_TIER_EXTERNAL = "external";
const LIMIT_TIER_BASE = "base";
const LIMIT_TIER_ALL = "all";
/**
* Resolve `applyLimitsTo` option into a normalised Set of tier strings.
* Accepted values: 'external' | 'base' | 'all' | string[]
* Default: 'external' (only untrusted injected entities are counted).
* @param {string|string[]|undefined} raw
* @returns {Set<string>}
*/
function parseLimitTiers(raw) {
if (!raw || raw === LIMIT_TIER_EXTERNAL) return new Set([LIMIT_TIER_EXTERNAL]);
if (raw === LIMIT_TIER_ALL) return new Set([LIMIT_TIER_ALL]);
if (raw === LIMIT_TIER_BASE) return new Set([LIMIT_TIER_BASE]);
if (Array.isArray(raw)) return new Set(raw);
return new Set([LIMIT_TIER_EXTERNAL]);
}
const NCR_LEVEL = Object.freeze({
allow: 0,
leave: 1,
remove: 2,
throw: 3
});
const XML10_ALLOWED_C0 = new Set([
9,
10,
13
]);
/**
* Parse the `ncr` constructor option into flat, hot-path-friendly fields.
* @param {object|undefined} ncr
* @returns {{ xmlVersion: number, onLevel: number, nullLevel: number }}
*/
function parseNCRConfig(ncr) {
if (!ncr) return {
xmlVersion: 1,
onLevel: NCR_LEVEL.allow,
nullLevel: NCR_LEVEL.remove
};
const xmlVersion = ncr.xmlVersion === 1.1 ? 1.1 : 1;
const onLevel = NCR_LEVEL[ncr.onNCR] ?? NCR_LEVEL.allow;
const nullLevel = NCR_LEVEL[ncr.nullNCR] ?? NCR_LEVEL.remove;
return {
xmlVersion,
onLevel,
nullLevel: Math.max(nullLevel, NCR_LEVEL.remove)
};
}
/**
* Single-pass, zero-regex entity replacer for XML/HTML content.
*
* Algorithm: scan the string once for '&', read to ';', resolve via map
* or direct codepoint conversion, build output chunks, join once at the end.
*
* Entity lookup priority (highest → lowest):
* 1. input / runtime (DOCTYPE entities for current document)
* 2. persistent external (survive across documents)
* 3. base named map (DEFAULT_XML_ENTITIES + user-supplied namedEntities)
*
* Both input and external resolve as the 'external' tier for limit purposes.
* Base map entities resolve as the 'base' tier.
*
* Numeric / hex references (&#NNN; / &#xHH;) are resolved directly via
* String.fromCodePoint() — no map needed. They count as 'base' tier.
*
* @example
* const replacer = new EntityReplacer({ namedEntities: COMMON_HTML });
* replacer.setExternalEntities({ brand: 'Acme' });
*
* const instance = replacer.reset();
* instance.addInputEntities({ version: '1.0' });
* instance.encode('&brand; v&version; <'); // 'Acme v1.0 <'
*/
var EntityDecoder = class {
/**
* @param {object} [options]
* @param {object|null} [options.namedEntities] — extra named entities merged into base map
* @param {object} [options.limit] — security limits
* @param {number} [options.limit.maxTotalExpansions=0] — 0 = unlimited
* @param {number} [options.limit.maxExpandedLength=0] — 0 = unlimited
* @param {'external'|'base'|'all'|string[]} [options.limit.applyLimitsTo='external']
* Which entity tiers count against the security limits:
* - 'external' (default) — only input/runtime + persistent external entities
* - 'base' — only DEFAULT_XML_ENTITIES + namedEntities
* - 'all' — every entity regardless of tier
* - string[] — explicit combination, e.g. ['external', 'base']
* @param {((resolved: string, original: string) => string)|null} [options.postCheck=null]
* @param {string[]} [options.remove=[]] — entity names (e.g. ['nbsp', '#13']) to delete (replace with empty string)
* @param {string[]} [options.leave=[]] — entity names to keep as literal (unchanged in output)
* @param {object} [options.ncr] — Numeric Character Reference controls
* @param {1.0|1.1} [options.ncr.xmlVersion=1.0]
* XML version governing which codepoint ranges are restricted:
* - 1.0 — C0 controls U+0001–U+001F (except U+0009/000A/000D) are prohibited
* - 1.1 — C0 controls are allowed when written as NCRs; C1 (U+007F–U+009F) decoded as-is
* @param {'allow'|'leave'|'remove'|'throw'} [options.ncr.onNCR='allow']
* Base action for numeric references. Severity order: allow < leave < remove < throw.
* For codepoint ranges that carry a minimum level (surrogates → remove, XML 1.0 C0 → remove),
* the effective action is max(onNCR, rangeMinimum).
* @param {'remove'|'throw'} [options.ncr.nullNCR='remove']
* Action for U+0000 (null). 'allow' and 'leave' are clamped to 'remove' since null is never safe.
*/
constructor(options = {}) {
this._limit = options.limit || {};
this._maxTotalExpansions = this._limit.maxTotalExpansions || 0;
this._maxExpandedLength = this._limit.maxExpandedLength || 0;
this._postCheck = typeof options.postCheck === "function" ? options.postCheck : (r) => r;
this._limitTiers = parseLimitTiers(this._limit.applyLimitsTo ?? LIMIT_TIER_EXTERNAL);
this._numericAllowed = options.numericAllowed ?? true;
this._baseMap = mergeEntityMaps(XML, options.namedEntities || null);
/** @type {Record<string, string>} */
this._externalMap = Object.create(null);
/** @type {Record<string, string>} */
this._inputMap = Object.create(null);
this._totalExpansions = 0;
this._expandedLength = 0;
/** @type {Set<string>} */
this._removeSet = new Set(options.remove && Array.isArray(options.remove) ? options.remove : []);
/** @type {Set<string>} */
this._leaveSet = new Set(options.leave && Array.isArray(options.leave) ? options.leave : []);
const ncrCfg = parseNCRConfig(options.ncr);
this._ncrXmlVersion = ncrCfg.xmlVersion;
this._ncrOnLevel = ncrCfg.onLevel;
this._ncrNullLevel = ncrCfg.nullLevel;
}
/**
* Replace the full set of persistent external entities.
* All keys are validated — throws on invalid characters.
* @param {Record<string, string | { regex?: RegExp, val: string }>} map
*/
setExternalEntities(map) {
if (map) for (const key of Object.keys(map)) validateEntityName$1(key);
this._externalMap = mergeEntityMaps(map);
}
/**
* Add a single persistent external entity.
* @param {string} key
* @param {string} value
*/
addExternalEntity(key, value) {
validateEntityName$1(key);
if (typeof value === "string" && value.indexOf("&") === -1) this._externalMap[key] = value;
}
/**
* Inject DOCTYPE entities for the current document.
* Also resets per-document expansion counters.
* @param {Record<string, string | { regx?: RegExp, regex?: RegExp, val: string }>} map
*/
addInputEntities(map) {
this._totalExpansions = 0;
this._expandedLength = 0;
this._inputMap = mergeEntityMaps(map);
}
/**
* Wipe input/runtime entities and reset counters.
* Call this before processing each new document.
* @returns {this}
*/
reset() {
this._inputMap = Object.create(null);
this._totalExpansions = 0;
this._expandedLength = 0;
return this;
}
/**
* Update the XML version used for NCR classification.
* Call this as soon as the document's `<?xml version="...">` declaration is parsed.
* @param {1.0|1.1|number} version
*/
setXmlVersion(version) {
this._ncrXmlVersion = version === 1.1 ? 1.1 : 1;
}
/**
* Replace all entity references in `str` in a single pass.
*
* @param {string} str
* @returns {string}
*/
decode(str) {
if (typeof str !== "string" || str.length === 0) return str;
const original = str;
const chunks = [];
const len = str.length;
let last = 0;
let i = 0;
const limitExpansions = this._maxTotalExpansions > 0;
const limitLength = this._maxExpandedLength > 0;
const checkLimits = limitExpansions || limitLength;
while (i < len) {
if (str.charCodeAt(i) !== 38) {
i++;
continue;
}
let j = i + 1;
while (j < len && str.charCodeAt(j) !== 59 && j - i <= 32) j++;
if (j >= len || str.charCodeAt(j) !== 59) {
i++;
continue;
}
const token = str.slice(i + 1, j);
if (token.length === 0) {
i++;
continue;
}
let replacement;
let tier;
if (this._removeSet.has(token)) {
replacement = "";
if (tier === void 0) tier = LIMIT_TIER_EXTERNAL;
} else if (this._leaveSet.has(token)) {
i++;
continue;
} else if (token.charCodeAt(0) === 35) {
const ncrResult = this._resolveNCR(token);
if (ncrResult === void 0) {
i++;
continue;
}
replacement = ncrResult;
tier = LIMIT_TIER_BASE;
} else {
const resolved = this._resolveName(token);
replacement = resolved?.value;
tier = resolved?.tier;
}
if (replacement === void 0) {
i++;
continue;
}
if (i > last) chunks.push(str.slice(last, i));
chunks.push(replacement);
last = j + 1;
i = last;
if (checkLimits && this._tierCounts(tier)) {
if (limitExpansions) {
this._totalExpansions++;
if (this._totalExpansions > this._maxTotalExpansions) throw new Error(`[EntityReplacer] Entity expansion count limit exceeded: ${this._totalExpansions} > ${this._maxTotalExpansions}`);
}
if (limitLength) {
const delta = replacement.length - (token.length + 2);
if (delta > 0) {
this._expandedLength += delta;
if (this._expandedLength > this._maxExpandedLength) throw new Error(`[EntityReplacer] Expanded content length limit exceeded: ${this._expandedLength} > ${this._maxExpandedLength}`);
}
}
}
}
if (last < len) chunks.push(str.slice(last));
const result = chunks.length === 0 ? str : chunks.join("");
return this._postCheck(result, original);
}
/**
* Returns true if a resolved entity of the given tier should count
* against the expansion/length limits.
* @param {string} tier — LIMIT_TIER_EXTERNAL | LIMIT_TIER_BASE
* @returns {boolean}
*/
_tierCounts(tier) {
if (this._limitTiers.has(LIMIT_TIER_ALL)) return true;
return this._limitTiers.has(tier);
}
/**
* Resolve a named entity token (without & and ;).
* Priority: inputMap > externalMap > baseMap
* Returns the resolved value tagged with its limit tier.
*
* @param {string} name
* @returns {{ value: string, tier: string }|undefined}
*/
_resolveName(name) {
if (name in this._inputMap) return {
value: this._inputMap[name],
tier: LIMIT_TIER_EXTERNAL
};
if (name in this._externalMap) return {
value: this._externalMap[name],
tier: LIMIT_TIER_EXTERNAL
};
if (name in this._baseMap) return {
value: this._baseMap[name],
tier: LIMIT_TIER_BASE
};
}
/**
* Classify a codepoint and return the minimum action level that must be applied.
* Returns -1 when no minimum is imposed (normal allow path).
*
* Ranges checked (in priority order):
* 1. U+0000 — null, governed by nullNCR (always ≥ remove)
* 2. U+D800–U+DFFF — surrogates, always prohibited (min: remove)
* 3. U+0001–U+001F \ {0x09,0x0A,0x0D} — XML 1.0 restricted C0 (min: remove)
* (skipped in XML 1.1 — C0 controls are allowed when written as NCRs)
*
* @param {number} cp — codepoint
* @returns {number} — minimum NCR_LEVEL value, or -1 for no restriction
*/
_classifyNCR(cp) {
if (cp === 0) return this._ncrNullLevel;
if (cp >= 55296 && cp <= 57343) return NCR_LEVEL.remove;
if (this._ncrXmlVersion === 1) {
if (cp >= 1 && cp <= 31 && !XML10_ALLOWED_C0.has(cp)) return NCR_LEVEL.remove;
}
return -1;
}
/**
* Execute a resolved NCR action.
*
* @param {number} action — NCR_LEVEL value
* @param {string} token — raw token (e.g. '#38') for error messages
* @param {number} cp — codepoint, used only for error messages
* @returns {string|undefined}
* - decoded character string → 'allow'
* - '' → 'remove'
* - undefined → 'leave' (caller must skip past '&' only)
* - throws Error → 'throw'
*/
_applyNCRAction(action, token, cp) {
switch (action) {
case NCR_LEVEL.allow: return String.fromCodePoint(cp);
case NCR_LEVEL.remove: return "";
case NCR_LEVEL.leave: return;
case NCR_LEVEL.throw: throw new Error(`[EntityDecoder] Prohibited numeric character reference &${token}; (U+${cp.toString(16).toUpperCase().padStart(4, "0")})`);
default: return String.fromCodePoint(cp);
}
}
/**
* Full NCR resolution pipeline for a numeric token.
*
* Steps:
* 1. Parse the codepoint (decimal or hex).
* 2. Validate the raw codepoint range (NaN, <0, >0x10FFFF).
* 3. If numericAllowed is false and no minimum restriction applies → leave as-is.
* 4. Classify the codepoint to find the minimum required action level.
* 5. Resolve effective action = max(onNCR, minimum).
* 6. Apply and return.
*
* @param {string} token — e.g. '#38', '#x26', '#X26'
* @returns {string|undefined}
* - string (incl. '') — replacement ('' = remove)
* - undefined — leave original &token; as-is
*/
_resolveNCR(token) {
const second = token.charCodeAt(1);
let cp;
if (second === 120 || second === 88) cp = parseInt(token.slice(2), 16);
else cp = parseInt(token.slice(1), 10);
if (Number.isNaN(cp) || cp < 0 || cp > 1114111) return void 0;
const minimum = this._classifyNCR(cp);
if (!this._numericAllowed && minimum < NCR_LEVEL.remove) return void 0;
const effective = minimum === -1 ? this._ncrOnLevel : Math.max(this._ncrOnLevel, minimum);
return this._applyNCRAction(effective, token, cp);
}
};
//#endregion
//#region ../../node_modules/.pnpm/fast-xml-parser@5.7.2/node_modules/fast-xml-parser/src/xmlparser/OptionsBuilder.js
const defaultOnDangerousProperty = (name) => {
if (DANGEROUS_PROPERTY_NAMES.includes(name)) return "__" + name;
return name;
};
const defaultOptions$1 = {
preserveOrder: false,
attributeNamePrefix: "@_",
attributesGroupName: false,
textNodeName: "#text",
ignoreAttributes: true,
removeNSPrefix: false,
allowBooleanAttributes: false,
parseTagValue: true,
parseAttributeValue: false,
trimValues: true,
cdataPropName: false,
numberParseOptions: {
hex: true,
leadingZeros: true,
eNotation: true
},
tagValueProcessor: function(tagName, val) {
return val;
},
attributeValueProcessor: function(attrName, val) {
return val;
},
stopNodes: [],
alwaysCreateTextNode: false,
isArray: () => false,
commentPropName: false,
unpairedTags: [],
processEntities: true,
htmlEntities: false,
entityDecoder: null,
ignoreDeclaration: false,
ignorePiTags: false,
transformTagName: false,
transformAttributeName: false,
updateTag: function(tagName, jPath, attrs) {
return tagName;
},
captureMetaData: false,
maxNestedTags: 100,
strictReservedNames: true,
jPath: true,
onDangerousProperty: defaultOnDangerousProperty
};
/**
* Validates that a property name is safe to use
* @param {string} propertyName - The property name to validate
* @param {string} optionName - The option field name (for error message)
* @throws {Error} If property name is dangerous
*/
function validatePropertyName(propertyName, optionName) {
if (typeof propertyName !== "string") return;
const normalized = propertyName.toLowerCase();
if (DANGEROUS_PROPERTY_NAMES.some((dangerous) => normalized === dangerous.toLowerCase())) throw new Error(`[SECURITY] Invalid ${optionName}: "${propertyName}" is a reserved JavaScript keyword that could cause prototype pollution`);
if (criticalProperties.some((dangerous) => normalized === dangerous.toLowerCase())) throw new Error(`[SECURITY] Invalid ${optionName}: "${propertyName}" is a reserved JavaScript keyword that could cause prototype pollution`);
}
/**
* Normalizes processEntities option for backward compatibility
* @param {boolean|object} value
* @returns {object} Always returns normalized object
*/
function normalizeProcessEntities(value, htmlEntities) {
if (typeof value === "boolean") return {
enabled: value,
maxEntitySize: 1e4,
maxExpansionDepth: 1e4,
maxTotalExpansions: Infinity,
maxExpandedLength: 1e5,
maxEntityCount: 1e3,
allowedTags: null,
tagFilter: null,
appliesTo: "all"
};
if (typeof value === "object" && value !== null) return {
enabled: value.enabled !== false,
maxEntitySize: Math.max(1, value.maxEntitySize ?? 1e4),
maxExpansionDepth: Math.max(1, value.maxExpansionDepth ?? 1e4),
maxTotalExpansions: Math.max(1, value.maxTotalExpansions ?? Infinity),
maxExpandedLength: Math.max(1, value.maxExpandedLength ?? 1e5),
maxEntityCount: Math.max(1, value.maxEntityCount ?? 1e3),
allowedTags: value.allowedTags ?? null,
tagFilter: value.tagFilter ?? null,
appliesTo: value.appliesTo ?? "all"
};
return normalizeProcessEntities(true);
}
const buildOptions = function(options) {
const built = Object.assign({}, defaultOptions$1, options);
const propertyNameOptions = [
{
value: built.attributeNamePrefix,
name: "attributeNamePrefix"
},
{
value: built.attributesGroupName,
name: "attributesGroupName"
},
{
value: built.textNodeName,
name: "textNodeName"
},
{
value: built.cdataPropName,
name: "cdataPropName"
},
{
value: built.commentPropName,
name: "commentPropName"
}
];
for (const { value, name } of propertyNameOptions) if (value) validatePropertyName(value, name);
if (built.onDangerousProperty === null) built.onDangerousProperty = defaultOnDangerousProperty;
built.processEntities = normalizeProcessEntities(built.processEntities, built.htmlEntities);
built.unpairedTagsSet = new Set(built.unpairedTags);
if (built.stopNodes && Array.isArray(built.stopNodes)) built.stopNodes = built.stopNodes.map((node) => {
if (typeof node === "string" && node.startsWith("*.")) return ".." + node.substring(2);
return node;
});
return built;
};
//#endregion
//#region ../../node_modules/.pnpm/fast-xml-parser@5.7.2/node_modules/fast-xml-parser/src/xmlparser/xmlNode.js
let METADATA_SYMBOL$1;
if (typeof Symbol !== "function") METADATA_SYMBOL$1 = "@@xmlMetadata";
else METADATA_SYMBOL$1 = Symbol("XML Node Metadata");
var XmlNode = class {
constructor(tagname) {
this.tagname = tagname;
this.child = [];
this[":@"] = Object.create(null);
}
add(key, val) {
if (key === "__proto__") key = "#__proto__";
this.child.push({ [key]: val });
}
addChild(node, startIndex) {
if (node.tagname === "__proto__") node.tagname = "#__proto__";
if (node[":@"] && Object.keys(node[":@"]).length > 0) this.child.push({
[node.tagname]: node.child,
[":@"]: node[":@"]
});
else this.child.push({ [node.tagname]: node.child });
if (startIndex !== void 0) this.child[this.child.length - 1][METADATA_SYMBOL$1] = { startIndex };
}
/** symbol used for metadata */
static getMetaDataSymbol() {
return METADATA_SYMBOL$1;
}
};
//#endregion
//#region ../../node_modules/.pnpm/fast-xml-parser@5.7.2/node_modules/fast-xml-parser/src/xmlparser/DocTypeReader.js
var DocTypeReader = class {
constructor(options) {
this.suppressValidationErr = !options;
this.options = options;
}
readDocType(xmlData, i) {
const entities = Object.create(null);
let entityCount = 0;
if (xmlData[i + 3] === "O" && xmlData[i + 4] === "C" && xmlData[i + 5] === "T" && xmlData[i + 6] === "Y" && xmlData[i + 7] === "P" && xmlData[i + 8] === "E") {
i = i + 9;
let angleBracketsCount = 1;
let hasBody = false, comment = false;
let exp = "";
for (; i < xmlData.length; i++) if (xmlData[i] === "<" && !comment) {
if (hasBody && hasSeq(xmlData, "!ENTITY", i)) {
i += 7;
let entityName, val;
[entityName, val, i] = this.readEntityExp(xmlData, i + 1, this.suppressValidationErr);
if (val.indexOf("&") === -1) {
if (this.options.enabled !== false && this.options.maxEntityCount != null && entityCount >= this.options.maxEntityCount) throw new Error(`Entity count (${entityCount + 1}) exceeds maximum allowed (${this.options.maxEntityCount})`);
entities[entityName] = val;
entityCount++;
}
} else if (hasBody && hasSeq(xmlData, "!ELEMENT", i)) {
i += 8;
const { index } = this.readElementExp(xmlData, i + 1);
i = index;
} else if (hasBody && hasSeq(xmlData, "!ATTLIST", i)) i += 8;
else if