fix(synology-chat): truncate sanitized input on UTF-16 boundary (#96574) · openclaw/openclaw@e5c3c59
llagy009
·
2026-06-28
·
via Recent Commits to openclaw:main
File tree
extensions/synology-chat/src
| Original file line number | Diff line number | Diff line change |
|---|
@@ -433,6 +433,31 @@ describe("synology-chat security helpers", () => {
|
433 | 433 | expect(result).toContain("[truncated]"); |
434 | 434 | }); |
435 | 435 | |
| 436 | +it("truncates long inputs without splitting a surrogate pair", () => { |
| 437 | +const loneSurrogatePattern = |
| 438 | +/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:^|[^\uD800-\uDBFF])[\uDC00-\uDFFF]/u; |
| 439 | +const input = "a".repeat(3999) + "\u{1F600}" + "b".repeat(2000); |
| 440 | + |
| 441 | +const result = sanitizeInput(input); |
| 442 | + |
| 443 | +expect(result).toContain("[truncated]"); |
| 444 | +expect(result).not.toMatch(loneSurrogatePattern); |
| 445 | +expect(result).toBe(`${"a".repeat(3999)}... [truncated]`); |
| 446 | +}); |
| 447 | + |
| 448 | +it("keeps complete supplementary-plane characters that fit before truncation", () => { |
| 449 | +const loneSurrogatePattern = |
| 450 | +/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:^|[^\uD800-\uDBFF])[\uDC00-\uDFFF]/u; |
| 451 | +const emoji = "\u{1F600}"; |
| 452 | +const input = "a".repeat(3998) + emoji + "b".repeat(2000); |
| 453 | + |
| 454 | +const result = sanitizeInput(input); |
| 455 | + |
| 456 | +expect(result).toContain("[truncated]"); |
| 457 | +expect(result.startsWith(`${"a".repeat(3998)}${emoji}`)).toBe(true); |
| 458 | +expect(result).not.toMatch(loneSurrogatePattern); |
| 459 | +}); |
| 460 | + |
436 | 461 | it("rate limits per user and caps tracked state", () => { |
437 | 462 | const limiter = new RateLimiter(3, 60); |
438 | 463 | expect(limiter.check("user1")).toBe(true); |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -5,6 +5,7 @@
|
5 | 5 | import { resolveStableChannelMessageIngress } from "openclaw/plugin-sdk/channel-ingress-runtime"; |
6 | 6 | import { finiteSecondsToTimerSafeMilliseconds } from "openclaw/plugin-sdk/number-runtime"; |
7 | 7 | import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime"; |
| 8 | +import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime"; |
8 | 9 | import { |
9 | 10 | createFixedWindowRateLimiter, |
10 | 11 | type FixedWindowRateLimiter, |
@@ -64,7 +65,7 @@ export function sanitizeInput(text: string): string {
|
64 | 65 | |
65 | 66 | const maxLength = 4000; |
66 | 67 | if (sanitized.length > maxLength) { |
67 | | -sanitized = sanitized.slice(0, maxLength) + "... [truncated]"; |
| 68 | +sanitized = truncateUtf16Safe(sanitized, maxLength) + "... [truncated]"; |
68 | 69 | } |
69 | 70 | |
70 | 71 | return sanitized; |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -6,6 +6,7 @@
|
6 | 6 | import type { IncomingMessage, ServerResponse } from "node:http"; |
7 | 7 | import * as querystring from "node:querystring"; |
8 | 8 | import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime"; |
| 9 | +import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime"; |
9 | 10 | import { |
10 | 11 | beginWebhookRequestPipelineOrReject, |
11 | 12 | createWebhookInFlightLimiter, |
@@ -503,7 +504,7 @@ async function parseAndAuthorizeSynologyWebhook(params: {
|
503 | 504 | respondNoContent(params.res); |
504 | 505 | return { ok: false }; |
505 | 506 | } |
506 | | -const preview = cleanText.length > 100 ? `${cleanText.slice(0, 100)}...` : cleanText; |
| 507 | +const preview = cleanText.length > 100 ? `${truncateUtf16Safe(cleanText, 100)}...` : cleanText; |
507 | 508 | return { |
508 | 509 | ok: true, |
509 | 510 | message: { |
@@ -574,7 +575,7 @@ async function processAuthorizedSynologyWebhook(params: {
|
574 | 575 | deliveryUserId, |
575 | 576 | params.account.allowInsecureSsl, |
576 | 577 | ); |
577 | | -const replyPreview = reply.length > 100 ? `${reply.slice(0, 100)}...` : reply; |
| 578 | +const replyPreview = reply.length > 100 ? `${truncateUtf16Safe(reply, 100)}...` : reply; |
578 | 579 | params.log?.info?.( |
579 | 580 | `Reply sent to ${params.message.payload.username} (${deliveryUserId}): ${replyPreview}`, |
580 | 581 | ); |
|
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。