Files
openclaw/src/agents/utils/html.ts
Peter Steinberger f3e3b6985b fix: land nine small correctness fixes (#100204)
* 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>
2026-07-05 01:44:56 -07:00

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 "&AMP;" 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 "&#39x;" 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 };
}