From 0aa96cf852f55cb04474fcc105342daf29c10ee3 Mon Sep 17 00:00:00 2001 From: Sayeed Joy Date: Fri, 5 Dec 2025 21:12:28 +0600 Subject: [PATCH 1/3] New: Implement recursion depth limit in Wikitext parser to prevent stack overflow --- .../parser/wikitextToAnnotatedString.kt | 108 ++++++++++-------- 1 file changed, 63 insertions(+), 45 deletions(-) diff --git a/app/src/main/java/org/nsh07/wikireader/parser/wikitextToAnnotatedString.kt b/app/src/main/java/org/nsh07/wikireader/parser/wikitextToAnnotatedString.kt index 14b7c21..a931695 100644 --- a/app/src/main/java/org/nsh07/wikireader/parser/wikitextToAnnotatedString.kt +++ b/app/src/main/java/org/nsh07/wikireader/parser/wikitextToAnnotatedString.kt @@ -44,6 +44,11 @@ import kotlin.text.Typography.nbsp import kotlin.text.Typography.ndash private const val MAGIC_SEP = "{{!}}" +private const val MAX_WIKITEXT_RECURSION_DEPTH = 64 + +private object WikitextParserState { + val recursionDepth: ThreadLocal = ThreadLocal.withInitial { 0 } +} /** * Converts Wikitext source code into an [AnnotatedString] that can be rendered by [androidx.compose.material3.Text] @@ -57,39 +62,47 @@ fun String.toWikitextAnnotatedString( inIndentCode: Boolean = false, showRef: (String) -> Unit, ): AnnotatedString { - val hrChar = '─' - val input = this - var i = 0 - var number = 1 // Count for numbered lists - - var italic = false - var bold = false - - val twas: String.() -> AnnotatedString = { - this.toWikitextAnnotatedString( - colorScheme, - typography, - loadPage, - fontSize, - showRef = showRef - ) + val currentDepth = WikitextParserState.recursionDepth.get() + if (currentDepth >= MAX_WIKITEXT_RECURSION_DEPTH) { + // Fallback: avoid runaway recursion on pathological or malformed wikitext + return AnnotatedString(this) } - val twasNoNewline: String.() -> AnnotatedString = { - this.toWikitextAnnotatedString( - colorScheme, - typography, - loadPage, - fontSize, - newLine = false, - showRef = showRef - ) - } + WikitextParserState.recursionDepth.set(currentDepth + 1) + try { + val hrChar = '─' + val input = this + var i = 0 + var number = 1 // Count for numbered lists + + var italic = false + var bold = false + + val twas: String.() -> AnnotatedString = { + this.toWikitextAnnotatedString( + colorScheme, + typography, + loadPage, + fontSize, + showRef = showRef + ) + } + + val twasNoNewline: String.() -> AnnotatedString = { + this.toWikitextAnnotatedString( + colorScheme, + typography, + loadPage, + fontSize, + newLine = false, + showRef = showRef + ) + } - return buildAnnotatedString { - while (i < input.length) { - if (input[i] != '#') number = 1 - when (input[i]) { + return buildAnnotatedString { + while (i < input.length) { + if (input[i] != '#') number = 1 + when (input[i]) { ' ' -> if ((getOrNull(i - 1) == '\n' || i == 0) && !inIndentCode) { val curr = substring(i + 1).substringBefore('\n') @@ -342,7 +355,7 @@ fun String.toWikitextAnnotatedString( '{' -> if (input.getOrNull(i + 1) == '{') { - val currSubstring = + val currSubstring = substringMatchingParen('{', '}', i).substringBeforeLast("}}") when { refTemplates.fastAny { item -> @@ -530,7 +543,7 @@ fun String.toWikitextAnnotatedString( } currSubstring.startsWith("{{abbr", ignoreCase = true) -> { - val curr = currSubstring.substringAfter('|').substringBefore('|') + val curr = currSubstring.substringAfter('|', "").substringBefore('|') append(curr.twas()) } @@ -575,19 +588,19 @@ fun String.toWikitextAnnotatedString( } currSubstring.startsWith("{{val", ignoreCase = true) -> { - val curr = currSubstring.substringAfter('|').substringBefore('|') + val curr = currSubstring.substringAfter('|', "").substringBefore('|') append(curr.twas()) } currSubstring.startsWith("{{var", ignoreCase = true) -> { - val curr = currSubstring.substringAfter('|') + val curr = currSubstring.substringAfter('|', "") append("''$curr''".twas()) } arrayOf("{{small", "{{smaller", "{{petit", "{{hw-small", "{{sma").any { currSubstring.startsWith(it, ignoreCase = true) } -> { - val curr = currSubstring.substringAfter('|') + val curr = currSubstring.substringAfter('|', "") withStyle(SpanStyle(fontSize = (fontSize - 2).sp)) { append(curr.twas()) } @@ -652,7 +665,7 @@ fun String.toWikitextAnnotatedString( } currSubstring.startsWith("{{dfn", true) -> { - val curr = currSubstring.substringAfter('|') + val curr = currSubstring.substringAfter('|', "") append("'''$curr'''".twas()) } @@ -706,7 +719,8 @@ fun String.toWikitextAnnotatedString( } currSubstring.startsWith("{{format price", true) -> { - val curr = currSubstring.substringAfter('|').substringBefore('|') + val curr = + currSubstring.substringAfter('|', "").substringBefore('|') append( if (curr.contains('.')) curr.toDoubleOrNull() ?.formatToHumanReadable() ?: curr.twas() @@ -838,13 +852,13 @@ fun String.toWikitextAnnotatedString( currSubstring.startsWith("{{US$", true) -> { if (currSubstring.contains('|')) { - val curr = currSubstring.substringAfter('|') + val curr = currSubstring.substringAfter('|', "") append("US$${curr.twas()}") } else append("US$") } currSubstring.startsWith("{{hatnote", ignoreCase = true) -> { - val curr = currSubstring.substringAfter('|').replace('\n', ' ') + val curr = currSubstring.substringAfter('|', "").replace('\n', ' ') append("''$curr''".twas()) } @@ -957,18 +971,18 @@ fun String.toWikitextAnnotatedString( } currSubstring.startsWith("{{rp", true) -> { - val curr = currSubstring.substringAfter('|') + val curr = currSubstring.substringAfter('|', "") append(":$curr ".twas()) } currSubstring.startsWith("{{isbn", true) -> { - val curr = currSubstring.substringAfter('|').split('|') + val curr = currSubstring.substringAfter('|', "").split('|') .filterNot { it.contains('=') }.joinToString() append("[[ISBN]] $curr".twas()) } currSubstring.startsWith("{{sfrac") -> { - val curr = currSubstring.substringAfter('|') + val curr = currSubstring.substringAfter('|', "") val splitList = curr.split('|') when (splitList.size) { 3 -> append("${splitList[0]}${splitList[1]}/${splitList[2]}".twas()) @@ -992,7 +1006,8 @@ fun String.toWikitextAnnotatedString( } currSubstring.startsWith("{{unichar", ignoreCase = true) -> { - val curr = currSubstring.substringAfter('|').substringBefore('|') + val curr = + currSubstring.substringAfter('|', "").substringBefore('|') append("U+$curr ".twas()) try { append(Character.toString(curr.toInt(16))) @@ -1001,7 +1016,7 @@ fun String.toWikitextAnnotatedString( } currSubstring.startsWith("{{char", ignoreCase = true) -> { - append(currSubstring.substringAfter('|').twas()) + append(currSubstring.substringAfter('|', "").twas()) } currSubstring.startsWith("{{Nihongo", ignoreCase = true) -> { @@ -1029,7 +1044,7 @@ fun String.toWikitextAnnotatedString( } currSubstring.startsWith("{{noflag", ignoreCase = true) -> { - val curr = currSubstring.substringAfter('|') + val curr = currSubstring.substringAfter('|', "") append(curr.twas()) } @@ -1379,6 +1394,9 @@ fun String.toWikitextAnnotatedString( } i++ } + } + } finally { + WikitextParserState.recursionDepth.set(currentDepth) } } From 0956daf1b5afe8e10731feac04a0c98b21cc3008 Mon Sep 17 00:00:00 2001 From: Sayeed Joy <51468562+sayeedjoy@users.noreply.github.com> Date: Sun, 7 Dec 2025 13:41:20 +0600 Subject: [PATCH 2/3] Update app/src/main/java/org/nsh07/wikireader/parser/wikitextToAnnotatedString.kt Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../org/nsh07/wikireader/parser/wikitextToAnnotatedString.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/nsh07/wikireader/parser/wikitextToAnnotatedString.kt b/app/src/main/java/org/nsh07/wikireader/parser/wikitextToAnnotatedString.kt index a931695..98ec1c8 100644 --- a/app/src/main/java/org/nsh07/wikireader/parser/wikitextToAnnotatedString.kt +++ b/app/src/main/java/org/nsh07/wikireader/parser/wikitextToAnnotatedString.kt @@ -355,7 +355,7 @@ fun String.toWikitextAnnotatedString( '{' -> if (input.getOrNull(i + 1) == '{') { - val currSubstring = + val currSubstring = substringMatchingParen('{', '}', i).substringBeforeLast("}}") when { refTemplates.fastAny { item -> From 5e49c97e90c0fa7d3f12992329d03000331b790a Mon Sep 17 00:00:00 2001 From: Sayeed Joy <51468562+sayeedjoy@users.noreply.github.com> Date: Sun, 7 Dec 2025 13:41:32 +0600 Subject: [PATCH 3/3] Update app/src/main/java/org/nsh07/wikireader/parser/wikitextToAnnotatedString.kt Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../org/nsh07/wikireader/parser/wikitextToAnnotatedString.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/nsh07/wikireader/parser/wikitextToAnnotatedString.kt b/app/src/main/java/org/nsh07/wikireader/parser/wikitextToAnnotatedString.kt index 98ec1c8..9cfac90 100644 --- a/app/src/main/java/org/nsh07/wikireader/parser/wikitextToAnnotatedString.kt +++ b/app/src/main/java/org/nsh07/wikireader/parser/wikitextToAnnotatedString.kt @@ -1394,7 +1394,7 @@ fun String.toWikitextAnnotatedString( } i++ } - } + } } finally { WikitextParserState.recursionDepth.set(currentDepth) }