diff --git a/amethyst/src/main/res/values-pl-rPL/strings.xml b/amethyst/src/main/res/values-pl-rPL/strings.xml
index 5f2c6efd61..d956737d3e 100644
--- a/amethyst/src/main/res/values-pl-rPL/strings.xml
+++ b/amethyst/src/main/res/values-pl-rPL/strings.xml
@@ -71,7 +71,7 @@
Zacytuj
Sklonuj
Zaproponuj zmianę
- Nowa kwota w Satsach
+ Nowa kwota w satoszach
Dodaj
"odpowiadając do "
" i "
@@ -92,7 +92,7 @@
Lightning transfer
Wiadomość dla odbiorcy
Dziękuję bardzo!
- Kwota w Satsach
+ Kwota w satoszach
Wyślij
Kreator tajnych emoji
Dodaj emoji z ukrytą wiadomością do wpisu
@@ -1101,6 +1101,13 @@
Nowy wpis w społeczności
Nowy produkt
Nowy GEO-ekskluzywny Wpis
+ Nowy artykuł
+ Tytuł
+ Podsumowanie (opcjonalnie)
+ Adres URL miniaturki (opcjonalnie)
+ Napisz artykuł w formacie markdown…
+ Podgląd
+ Edytuj
Otwórz wszystkie odzewy na ten post
Zamknij wszystkie odzewy na ten post
Odpowiedź
@@ -1238,6 +1245,7 @@
OTS: %1$s
Potwierdzenie znacznika czasu
Istnieje dowód na to, że ten post został podpisany przed %1$s. Dowód został opatrzony pieczęcią w łańcuchu bloków Bitcoin w tym dniu i czasie.
+ Redaguj artykuł
Edytuj wpis
Propozycja ulepszenia wpisu
Podsumowanie zmian
@@ -1560,6 +1568,7 @@
wyszukaj, npub1…, alicja@domena.pl
Obsługuje npub, nprofile, NIP-05, hex, i namecoin (.bit, d/, id/)
Sprawdź listę obserwowanych
+ Porada
Znaleziono %1$d kont(a)
Wybrano: %1$d
Rozwiązane przez Namecoin
@@ -1659,4 +1668,11 @@
Biegłość w sprawdzaniu typów: %1$s
Certyfikat dla
Żądanie certyfikatu do
+ Przedział czasu
+ Od
+ Do
+ Teraz
+ Cały czas
+ Ostatnia synchronizacja %1$s
+ Od ostatniej synchronizacji
diff --git a/commons/src/commonTest/kotlin/com/vitorpamplona/amethyst/commons/richtext/UrlParserTest.kt b/commons/src/commonTest/kotlin/com/vitorpamplona/amethyst/commons/richtext/UrlParserTest.kt
index b9613c5cd7..600c0f27f0 100644
--- a/commons/src/commonTest/kotlin/com/vitorpamplona/amethyst/commons/richtext/UrlParserTest.kt
+++ b/commons/src/commonTest/kotlin/com/vitorpamplona/amethyst/commons/richtext/UrlParserTest.kt
@@ -281,6 +281,18 @@ 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(),
+ )
+
@Test
fun testHour() =
test(
diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/urldetector/Url.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/urldetector/Url.kt
index 538d9ea323..46927f771d 100644
--- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/urldetector/Url.kt
+++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/urldetector/Url.kt
@@ -228,11 +228,17 @@ class Url(
return null
}
+ val startIndex = urlMarker.indexOf(part)
+ if (startIndex < 0 || startIndex >= originalUrl.length) {
+ return null
+ }
+
val nextPart = nextExistingPart(part)
return if (nextPart == null) {
- originalUrl.substring(urlMarker.indexOf(part))
+ originalUrl.substring(startIndex)
} else {
- originalUrl.substring(urlMarker.indexOf(part), urlMarker.indexOf(nextPart))
+ val endIndex = urlMarker.indexOf(nextPart)
+ originalUrl.substring(startIndex, minOf(endIndex, originalUrl.length))
}
}
diff --git a/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nip10Notes/urls/UrlsDetectorTest.kt b/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nip10Notes/urls/UrlsDetectorTest.kt
index 0d526ab172..aae685bf2b 100644
--- a/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nip10Notes/urls/UrlsDetectorTest.kt
+++ b/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nip10Notes/urls/UrlsDetectorTest.kt
@@ -41,4 +41,14 @@ 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)
+ }
}
diff --git a/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/utils/urldetector/UrlTest.kt b/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/utils/urldetector/UrlTest.kt
new file mode 100644
index 0000000000..a29c5e99e3
--- /dev/null
+++ b/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/utils/urldetector/UrlTest.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2025 Vitor Pamplona
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+ * Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.vitorpamplona.quartz.utils.urldetector
+
+import com.vitorpamplona.quartz.utils.urldetector.detection.UrlDetector
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+
+/**
+ * Regression tests for PR #1907: StringIndexOutOfBoundsException in [Url.getPart] when
+ * processing the Japanese text "今北産業".
+ */
+class UrlTest {
+ /**
+ * Regression: detecting URLs in "今北産業" must not throw and must return no URLs.
+ */
+ @Test
+ fun detectingImakitaSangyoDoesNotThrow() {
+ val urls = UrlDetector("今北産業").detect()
+ assertEquals(0, urls.size)
+ }
+
+ /**
+ * Regression: constructing a Url with "今北産業" as the original string and a HOST
+ * marker at 0 with PORT at the string length (simulating a trimmed trailing character)
+ * must not throw StringIndexOutOfBoundsException when accessing any property.
+ *
+ * "今北産業" has length 4. PORT at 4 == length triggers the startIndex >= length guard
+ * added to getPart() in PR #1907.
+ */
+ @Test
+ fun urlPropertiesDoNotThrowForImakitaSangyoWithOutOfRangeMarker() {
+ val marker = UrlMarker()
+ marker.setIndex(UrlPart.HOST, 0)
+ marker.setIndex(UrlPart.PORT, 4) // == "今北産業".length
+ val url = marker.createUrl("今北産業")
+
+ assertNotNull(url.scheme)
+ assertNotNull(url.host)
+ assertNotNull(url.path)
+ assertNotNull(url.query)
+ assertNotNull(url.fragment)
+ assertEquals(-1, url.port) // getPart(PORT) returns null → -1
+ }
+}