test: add regression tests for PR #1907 URL crash fix

Tests cover the StringIndexOutOfBoundsException in Url.getPart() that
occurred when readEnd() trimmed trailing characters (e.g. ':') from a
detected URL but urlMarker indices remained pointing past the trimmed
string's end.

- UrlMarkerTest: three cases testing PORT/QUERY index at or beyond
  string length (the boundary conditions fixed by minOf clamping and
  the startIndex >= length guard)
- UrlsDetectorTest: regression for "今北産業" (no crash, 0 URLs) and
  for a bare "example.com:" host
- UrlParserTest: end-to-end regression for "今北産業" and "example.com:"

https://claude.ai/code/session_013rJ9iYndYVJgpYK2VazxL2
This commit is contained in:
Claude
2026-03-23 20:31:28 +00:00
parent 2b920fc47a
commit e31e3829a6
3 changed files with 109 additions and 0 deletions
@@ -281,6 +281,30 @@ class UrlParserTest {
Urls(withScheme = emptySet()),
)
/**
* Regression test for PR #1907: parsing a note whose content is only the Japanese phrase
* "今北産業" (a common internet abbreviation) must not throw a StringIndexOutOfBoundsException
* from Url.getPart() and must produce no detected URLs.
*/
@Test
fun testImakitaSangyo() =
test(
"今北産業",
Urls(),
)
/**
* Regression test for PR #1907: a URL that ends with ':' causes readEnd() to trim it,
* but the PORT marker index was already set to buffer.length (one past the trimmed URL).
* Accessing host/port must not throw StringIndexOutOfBoundsException.
*/
@Test
fun testUrlEndingWithColon() =
test(
"example.com:",
Urls(),
)
@Test
fun testHour() =
test(
@@ -41,4 +41,25 @@ class UrlsDetectorTest {
assertContains(detectedLinks, "https://mysite.xyz")
assertContains(detectedLinks, "https://myblog.xyz")
}
/**
* Regression test for PR #1907: the Japanese phrase "今北産業" must not crash the URL
* detector with a StringIndexOutOfBoundsException and must return no URLs.
*/
@Test
fun doesNotCrashOnJapaneseText() {
val detectedLinks = fastFindURLs("今北産業")
assertEquals(0, detectedLinks.size)
}
/**
* Regression test for PR #1907: a bare host ending with ':' triggers PORT marker placement
* at buffer.length, then readEnd() trims the ':', leaving PORT beyond the URL string.
* Url.getPart() must not throw StringIndexOutOfBoundsException.
*/
@Test
fun doesNotCrashOnUrlEndingWithColon() {
// Just verifying no exception is thrown; a bare "example.com:" is not a valid URL.
fastFindURLs("example.com:")
}
}
@@ -318,4 +318,68 @@ class UrlMarkerTest {
"#?@Sdsf",
intArrayOf(0, 8, 18, 33, 35, 36, 53),
)
/**
* Regression test for PR #1907: StringIndexOutOfBoundsException in Url.getPart() when a URL
* ending with a character in CANNOT_END_URLS_WITH (e.g. ':') is trimmed by readEnd(), but the
* urlMarker PORT index was set to buffer.length (one past the last valid index).
*
* After trimming, PORT index == originalUrl.length, so getPart(PORT) must return null
* and getPart(HOST) must clamp endIndex to originalUrl.length via minOf.
*
* Simulates what happens when "example.com:" is detected: the ':' is trimmed to produce
* "example.com" but PORT marker remains at index 11 (= trimmed string length).
*/
@Test
fun testPortIndexAtStringLength() =
testUrlMarker(
"example.com", // 11 chars simulates "example.com:" after trailing ':' is trimmed
"https",
"",
"",
"example.com", // host must not throw; endIndex clamped by minOf
-1, // PORT at index 11 == length → getPart returns null → falls back to scheme default → 443, but no scheme → -1
"/",
"",
"",
intArrayOf(-1, -1, 0, 11, -1, -1, -1), // HOST=0, PORT=11 (= string length)
)
/**
* Regression test for PR #1907: PORT index beyond the end of the trimmed URL.
* Simulates a marker that points one position past the string length.
*/
@Test
fun testPortIndexBeyondStringLength() =
testUrlMarker(
"example.com", // 11 chars
"https",
"",
"",
"example.com", // host must not throw
-1, // PORT at 12 > length → getPart(PORT) returns null → -1
"/",
"",
"",
intArrayOf(-1, -1, 0, 12, -1, -1, -1), // PORT=12 > string length 11
)
/**
* Regression test for PR #1907: QUERY index at string length (simulates "example.com/path?"
* after trailing '?' is trimmed, leaving urlMarker QUERY at the trimmed string's length).
*/
@Test
fun testQueryIndexAtStringLength() =
testUrlMarker(
"example.com/path", // 16 chars simulates "example.com/path?" after '?' trimmed
"https",
"",
"",
"example.com",
443,
"/path", // path must not throw; endIndex clamped by minOf
"", // QUERY at 16 == length → getPart(QUERY) returns null
"",
intArrayOf(-1, -1, 0, -1, 11, 16, -1), // HOST=0, PATH=11, QUERY=16 (= string length)
)
}