File tree Expand file tree Collapse file tree
main/java/ai/openclaw/app
test/java/ai/openclaw/app/ui/chat Expand file tree Collapse file tree Original file line number Diff line number Diff line change 1+ package ai.openclaw.app
2+
3+ internal fun String.takeUtf16Safe (maxChars : Int ): String {
4+ if (length <= maxChars) return this
5+ // Keep the code-unit cap without leaving a high surrogate at its boundary.
6+ val endsOnHighSurrogate = maxChars > 0 && Character .isHighSurrogate(this [maxChars - 1 ])
7+ return take(if (endsOnHighSurrogate) maxChars - 1 else maxChars)
8+ }
Original file line number Diff line number Diff line change @@ -4,6 +4,7 @@ import ai.openclaw.app.NotificationBurstLimiter
44import ai.openclaw.app.SecurePrefs
55import ai.openclaw.app.allowsPackage
66import ai.openclaw.app.isWithinQuietHours
7+ import ai.openclaw.app.takeUtf16Safe
78import android.app.Notification
89import android.app.NotificationManager
910import android.app.RemoteInput
@@ -30,17 +31,6 @@ internal fun sanitizeNotificationText(value: CharSequence?): String? {
3031 return normalized.takeUtf16Safe(MAX_NOTIFICATION_TEXT_CHARS ).ifEmpty { null }
3132}
3233
33- private fun String.takeUtf16Safe (maxChars : Int ): String {
34- if (length <= maxChars) return this
35- val end =
36- if (maxChars > 0 && Character .isHighSurrogate(this [maxChars - 1 ])) {
37- maxChars - 1
38- } else {
39- maxChars
40- }
41- return take(end)
42- }
43-
4434/* *
4535 * Stable notification snapshot entry exposed through the Android notifications command.
4636 */
Original file line number Diff line number Diff line change 11package ai.openclaw.app.ui.chat
22
3+ import ai.openclaw.app.takeUtf16Safe
34import android.graphics.Bitmap
45import android.graphics.BitmapFactory
56import android.util.LruCache
@@ -524,7 +525,7 @@ private fun sanitizeMetadataText(
524525 .filterNot(Character ::isISOControl)
525526 .replace(Regex (" \\ s+" ), " " )
526527 .trim()
527- return sanitized.take (maxChars).takeIf (String ::isNotEmpty)
528+ return sanitized.takeUtf16Safe (maxChars).takeIf (String ::isNotEmpty)
528529}
529530
530531private fun findTitle (html : String ): String? =
Original file line number Diff line number Diff line change @@ -164,6 +164,21 @@ class ChatLinkPreviewTest {
164164 )
165165 }
166166
167+ @Test
168+ fun metadataTruncationPreservesUtf16Boundaries () {
169+ val titlePrefix = " t" .repeat(LINK_PREVIEW_TITLE_MAX_CHARS - 1 )
170+ val descriptionPrefix = " d" .repeat(LINK_PREVIEW_DESCRIPTION_MAX_CHARS - 2 )
171+ val result =
172+ parseOpenGraph(
173+ " <meta property='og:title' content='$titlePrefix \uD83D\uDE80 trailing'>" +
174+ " <meta property='og:description' content='$descriptionPrefix \uD83D\uDE80 trailing'>" ,
175+ " https://example.com" ,
176+ ) as LinkPreviewResult .Loaded
177+
178+ assertEquals(titlePrefix, result.metadata.title)
179+ assertEquals(" $descriptionPrefix \uD83D\uDE80 " , result.metadata.description)
180+ }
181+
167182 @Test
168183 fun fetchesHtmlWithoutAmbientHeaders () =
169184 withServer { server ->
You can’t perform that action at this time.
0 commit comments