fix(android): filter unsafe markdown links · openclaw/openclaw@4712931
obviyus
·
2026-05-18
·
via Recent Commits to openclaw:main
File tree
main/java/ai/openclaw/app/ui/chat
test/java/ai/openclaw/app/ui/chat
| Original file line number | Diff line number | Diff line change |
|---|
@@ -75,6 +75,8 @@ import org.commonmark.node.SoftLineBreak
|
75 | 75 | import org.commonmark.node.StrongEmphasis |
76 | 76 | import org.commonmark.node.ThematicBreak |
77 | 77 | import org.commonmark.parser.Parser |
| 78 | +import java.net.URI |
| 79 | +import java.util.Locale |
78 | 80 | import org.commonmark.node.Image as MarkdownImage |
79 | 81 | import org.commonmark.node.Text as MarkdownTextNode |
80 | 82 | |
@@ -548,15 +550,13 @@ private fun AnnotatedString.Builder.appendLinkNode(
|
548 | 550 | color = linkColor, |
549 | 551 | textDecoration = TextDecoration.Underline, |
550 | 552 | ) |
551 | | -if (destination.isEmpty()) { |
552 | | - withStyle(linkStyle) { |
553 | | - appendInlineNode( |
554 | | - link.firstChild, |
555 | | - inlineCodeBg = inlineCodeBg, |
556 | | - inlineCodeColor = inlineCodeColor, |
557 | | - linkColor = linkColor, |
558 | | - ) |
559 | | - } |
| 553 | +if (destination.isEmpty() || !isSafeMarkdownLinkDestination(destination)) { |
| 554 | + appendInlineNode( |
| 555 | + link.firstChild, |
| 556 | + inlineCodeBg = inlineCodeBg, |
| 557 | + inlineCodeColor = inlineCodeColor, |
| 558 | + linkColor = linkColor, |
| 559 | + ) |
560 | 560 | return |
561 | 561 | } |
562 | 562 | |
@@ -570,6 +570,14 @@ private fun AnnotatedString.Builder.appendLinkNode(
|
570 | 570 | } |
571 | 571 | } |
572 | 572 | |
| 573 | +private fun isSafeMarkdownLinkDestination(destination: String): Boolean { |
| 574 | +val scheme = |
| 575 | + runCatching { URI(destination).scheme?.lowercase(Locale.US) } |
| 576 | + .getOrNull() |
| 577 | +?: return false |
| 578 | +return scheme == "http" || scheme == "https" |
| 579 | +} |
| 580 | + |
573 | 581 | internal fun buildChatInlineMarkdown( |
574 | 582 | text: String, |
575 | 583 | linkColor: Color = Color.Blue, |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -33,6 +33,22 @@ class ChatMarkdownTest {
|
33 | 33 | assertEquals("https://docs.openclaw.ai/help/testing", (links.single().item as LinkAnnotation.Url).url) |
34 | 34 | } |
35 | 35 | |
| 36 | + @Test |
| 37 | +fun markdownLinksDropUnsafeDestinations() { |
| 38 | +listOf( |
| 39 | +"intent://example/#Intent;scheme=openclaw;end", |
| 40 | +"file:///sdcard/Download/x", |
| 41 | +"content://downloads/public_downloads/1", |
| 42 | +"tel:+15551234567", |
| 43 | +"javascript:alert(1)", |
| 44 | + ).forEach { destination -> |
| 45 | +val annotated = buildChatInlineMarkdown("Open [settings]($destination)") |
| 46 | + |
| 47 | + assertEquals("Open settings", annotated.text) |
| 48 | + assertTrue(annotated.getLinkAnnotations(0, annotated.length).isEmpty()) |
| 49 | + } |
| 50 | + } |
| 51 | + |
36 | 52 | @Test |
37 | 53 | fun plainTextDoesNotAddLinkAnnotations() { |
38 | 54 | val annotated = buildChatInlineMarkdown("No link here") |
|
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。