mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-20 22:01:39 +00:00
* fix(memory-wiki): preserve dollar notes on reimport Co-authored-by: 李兰 0668001394 <li.lan3@xydigit.com> * fix(voice-call): handle spawn error during tunnel cleanup Co-authored-by: 陈宪彪0668000387 <chen.xianbiao@xydigit.com> * fix(media): reject invalid response byte limits Co-authored-by: linhongkuan <linhk8@mail2.sysu.edu.cn> * fix(infra): match listening ports exactly Co-authored-by: 徐闻涵0668001344 <xu.wenhan1@xydigit.com> * fix(exec): preserve UTF-16 boundaries when truncating output Co-authored-by: liuhao1024 <sunsky.lau@gmail.com> * fix(agents): reject surrogate HTML entities Co-authored-by: mikasa0818 <0668001030@xydigit.com> * fix(anthropic): guard Claude Fable 5 prefix boundaries Co-authored-by: 黄剑雄0668001315 <huang.jianxiong@xydigit.com> * fix(models): render context windows in decimal K Co-authored-by: harjoth <harjoth.khara@gmail.com> * fix(clickclack): preserve websocket rejection diagnostics Co-authored-by: sunlit-deng <yang.jiajun1@xydigit.com> * fix(exec): use canonical UTF-16 slicing helper Co-authored-by: liuhao1024 <sunsky.lau@gmail.com> * style(exec): sort UTF-16 helper imports * style(exec): sort UTF-16 helper imports * style(exec): sort UTF-16 helper imports * style(infra): format response limit helper * style(infra): format response limit helper --------- Co-authored-by: 李兰 0668001394 <li.lan3@xydigit.com> Co-authored-by: 陈宪彪0668000387 <chen.xianbiao@xydigit.com> Co-authored-by: linhongkuan <linhk8@mail2.sysu.edu.cn> Co-authored-by: 徐闻涵0668001344 <xu.wenhan1@xydigit.com> Co-authored-by: liuhao1024 <sunsky.lau@gmail.com> Co-authored-by: mikasa0818 <0668001030@xydigit.com> Co-authored-by: 黄剑雄0668001315 <huang.jianxiong@xydigit.com> Co-authored-by: harjoth <harjoth.khara@gmail.com> Co-authored-by: sunlit-deng <yang.jiajun1@xydigit.com>
73 lines
2.2 KiB
TypeScript
73 lines
2.2 KiB
TypeScript
/**
|
|
* Minimal HTML entity decoding helpers.
|
|
*
|
|
* Syntax highlighting and terminal renderers use this to decode the small
|
|
* entity subset emitted by trusted HTML producers without parsing full HTML.
|
|
*/
|
|
/** Decoded entity text plus the source length consumed from the input. */
|
|
interface DecodedHtmlEntity {
|
|
text: string;
|
|
length: number;
|
|
}
|
|
|
|
function decodeCodePoint(codePoint: number): string | undefined {
|
|
if (
|
|
!Number.isInteger(codePoint) ||
|
|
codePoint < 0 ||
|
|
codePoint > 0x10ffff ||
|
|
(codePoint >= 0xd800 && codePoint <= 0xdfff)
|
|
) {
|
|
return undefined;
|
|
}
|
|
return String.fromCodePoint(codePoint);
|
|
}
|
|
|
|
/** Decodes a named or numeric HTML entity without the surrounding `&`/`;`. */
|
|
function decodeHtmlEntity(entity: string): string | undefined {
|
|
// Named entities match case-insensitively so callers keep the long-standing
|
|
// contract of decoding forms like "&" instead of leaking them as text.
|
|
switch (entity.toLowerCase()) {
|
|
case "amp":
|
|
return "&";
|
|
case "lt":
|
|
return "<";
|
|
case "gt":
|
|
return ">";
|
|
case "quot":
|
|
return '"';
|
|
case "apos":
|
|
return "'";
|
|
}
|
|
|
|
// Numeric references must be fully numeric. A bare Number.parseInt is lenient
|
|
// and would consume a malformed entity such as "'x;" as "'" by stopping at
|
|
// the first non-digit; require the whole token to be valid digits instead.
|
|
if (entity.startsWith("#x") || entity.startsWith("#X")) {
|
|
const hex = entity.slice(2);
|
|
return /^[0-9a-fA-F]+$/.test(hex) ? decodeCodePoint(Number.parseInt(hex, 16)) : undefined;
|
|
}
|
|
|
|
if (entity.startsWith("#")) {
|
|
const dec = entity.slice(1);
|
|
return /^[0-9]+$/.test(dec) ? decodeCodePoint(Number.parseInt(dec, 10)) : undefined;
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
/** Decodes an entity starting at `index` in an HTML string. */
|
|
export function decodeHtmlEntityAt(html: string, index: number): DecodedHtmlEntity | undefined {
|
|
const semicolonIndex = html.indexOf(";", index + 1);
|
|
if (semicolonIndex === -1 || semicolonIndex - index > 16) {
|
|
return undefined;
|
|
}
|
|
|
|
const entity = html.slice(index + 1, semicolonIndex);
|
|
const decoded = decodeHtmlEntity(entity);
|
|
if (decoded === undefined) {
|
|
return undefined;
|
|
}
|
|
|
|
return { text: decoded, length: semicolonIndex - index + 1 };
|
|
}
|