fix(duckduckgo): guard out-of-range numeric HTML entities (#96583) · openclaw/openclaw@2720ac0
llagy009
·
2026-06-28
·
via Recent Commits to openclaw:main
File tree
extensions/duckduckgo/src
| Original file line number | Diff line number | Diff line change |
|---|
@@ -36,6 +36,10 @@ type DuckDuckGoResult = {
|
36 | 36 | snippet: string; |
37 | 37 | }; |
38 | 38 | |
| 39 | +function isDecodableCodePoint(cp: number): boolean { |
| 40 | +return Number.isInteger(cp) && cp >= 0 && cp <= 0x10ffff && (cp < 0xd800 || cp > 0xdfff); |
| 41 | +} |
| 42 | + |
39 | 43 | function decodeHtmlEntities(text: string): string { |
40 | 44 | return text.replace( |
41 | 45 | /&(?:lt|gt|quot|apos|#39|#x27|#x2F|nbsp|ndash|mdash|hellip|amp|#\d+|#x[0-9a-f]+);/gi, |
@@ -72,10 +76,12 @@ function decodeHtmlEntities(text: string): string {
|
72 | 76 | return "&"; |
73 | 77 | } |
74 | 78 | if (normalized.startsWith("&#x")) { |
75 | | -return String.fromCodePoint(Number.parseInt(normalized.slice(3, -1), 16)); |
| 79 | +const codePoint = Number.parseInt(normalized.slice(3, -1), 16); |
| 80 | +return isDecodableCodePoint(codePoint) ? String.fromCodePoint(codePoint) : entity; |
76 | 81 | } |
77 | 82 | if (normalized.startsWith("&#")) { |
78 | | -return String.fromCodePoint(Number.parseInt(normalized.slice(2, -1), 10)); |
| 83 | +const codePoint = Number.parseInt(normalized.slice(2, -1), 10); |
| 84 | +return isDecodableCodePoint(codePoint) ? String.fromCodePoint(codePoint) : entity; |
79 | 85 | } |
80 | 86 | return entity; |
81 | 87 | }, |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -205,6 +205,20 @@ describe("duckduckgo web search provider", () => {
|
205 | 205 | ); |
206 | 206 | }); |
207 | 207 | |
| 208 | +it("leaves out-of-range numeric html entities intact instead of throwing", () => { |
| 209 | +expect(() => ddgClientTesting.decodeHtmlEntities("Result � end")).not.toThrow(); |
| 210 | +expect(ddgClientTesting.decodeHtmlEntities("Result � end")).toBe( |
| 211 | +"Result � end", |
| 212 | +); |
| 213 | +expect(ddgClientTesting.decodeHtmlEntities("Hex � tail")).toBe("Hex � tail"); |
| 214 | +// Surrogate-range entities would decode to lone UTF-16 surrogates; keep them intact. |
| 215 | +expect(ddgClientTesting.decodeHtmlEntities("Bad � end")).toBe("Bad � end"); |
| 216 | +expect(ddgClientTesting.decodeHtmlEntities("Bad � end")).toBe("Bad � end"); |
| 217 | +expect(ddgClientTesting.decodeHtmlEntities("Bad � end")).toBe("Bad � end"); |
| 218 | +// A valid supplementary-plane entity still decodes. |
| 219 | +expect(ddgClientTesting.decodeHtmlEntities("Smile 😀")).toBe("Smile 😀"); |
| 220 | +}); |
| 221 | + |
208 | 222 | it("does not double-decode escaped entities (decodes & last)", () => { |
209 | 223 | // A result whose text literally shows "<" arrives double-encoded as |
210 | 224 | // "&lt;". Decoding & first would re-decode it into "<", corrupting |
|
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。