Loading app/core/src/main/java/com/fsck/k9/message/html/UriMatcher.kt +16 −0 Original line number Diff line number Diff line Loading @@ -31,4 +31,20 @@ object UriMatcher { parser.parseUri(text, startIndex) }.filterNotNull().toList() } @Suppress("ReturnCount") fun isValidUri(text: CharSequence): Boolean { val matchResult = URI_SCHEME.matchAt(text, 0) ?: return false val matchGroup = matchResult.groups[1]!! if (matchGroup.range.first != 0) { return false } val scheme = matchGroup.value.lowercase() val parser = SUPPORTED_URIS[scheme] ?: throw AssertionError("Scheme not found: $scheme") val uriMatch = parser.parseUri(text, startPos = 0) ?: return false return uriMatch.startIndex == 0 && uriMatch.endIndex == text.length } } app/core/src/test/java/com/fsck/k9/message/html/UriMatcherTest.kt +32 −0 Original line number Diff line number Diff line package com.fsck.k9.message.html import assertk.Assert import assertk.assertThat import assertk.assertions.hasSize import assertk.assertions.isEmpty import assertk.assertions.isEqualTo import assertk.assertions.isFalse import assertk.assertions.isNotEqualTo import assertk.assertions.isTrue import org.junit.Test class UriMatcherTest { Loading Loading @@ -82,6 +85,27 @@ class UriMatcherTest { ) } @Test fun `valid URIs`() { assertThat("http://domain.example").isValidUri() assertThat("https://domain.example/").isValidUri() assertThat("mailto:user@domain.example").isValidUri() } @Test fun `not URIs`() { assertThat("some text here").isNotValidUri() assertThat("some text including the string http:").isNotValidUri() assertThat("some text including an email address without URI scheme: user@domain.example").isNotValidUri() } @Test fun `valid URIs surrounded by other characters`() { assertThat("text https://domain.example/").isNotValidUri() assertThat("https://domain.example/ text").isNotValidUri() assertThat("<https://domain.example/>").isNotValidUri() } private fun assertNoMatch(text: String) { val uriMatches = UriMatcher.findUris(text) assertThat(uriMatches).isEmpty() Loading @@ -104,3 +128,11 @@ class UriMatcherTest { } } } private fun Assert<String>.isValidUri() = given { actual -> assertThat(UriMatcher.isValidUri(actual)).isTrue() } private fun Assert<String>.isNotValidUri() = given { actual -> assertThat(UriMatcher.isValidUri(actual)).isFalse() } app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageCompose.java +3 −0 Original line number Diff line number Diff line Loading @@ -109,6 +109,7 @@ import com.fsck.k9.search.LocalSearch; import com.fsck.k9.ui.R; import com.fsck.k9.ui.base.K9Activity; import com.fsck.k9.ui.base.ThemeManager; import com.fsck.k9.ui.compose.WrapUriTextWatcher; import com.fsck.k9.ui.compose.QuotedMessageMvpView; import com.fsck.k9.ui.compose.QuotedMessagePresenter; import com.fsck.k9.ui.helper.SizeFormatter; Loading Loading @@ -360,10 +361,12 @@ public class MessageCompose extends K9Activity implements OnClickListener, replyToView.addTextChangedListener(draftNeedsChangingTextWatcher); recipientMvpView.addTextChangedListener(draftNeedsChangingTextWatcher); quotedMessageMvpView.addTextChangedListener(draftNeedsChangingTextWatcher); quotedMessageMvpView.addTextChangedListener(new WrapUriTextWatcher()); subjectView.addTextChangedListener(draftNeedsChangingTextWatcher); messageContentView.addTextChangedListener(draftNeedsChangingTextWatcher); messageContentView.addTextChangedListener(new WrapUriTextWatcher()); /* * We set this to invisible by default. Other methods will turn it back on if it's Loading app/ui/legacy/src/main/java/com/fsck/k9/ui/compose/WrapUriTextWatcher.kt 0 → 100644 +41 −0 Original line number Diff line number Diff line package com.fsck.k9.ui.compose import android.text.Editable import android.text.TextWatcher import com.fsck.k9.message.html.UriMatcher private const val NO_INDEX = -1 private const val MINIMUM_URI_LENGTH = 2 // scheme name + colon /** * Wraps inserted URIs in angle brackets. */ class WrapUriTextWatcher : TextWatcher { private var insertedStartIndex = NO_INDEX private var insertedEndIndex = NO_INDEX override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { if (s == null || count < MINIMUM_URI_LENGTH) { insertedStartIndex = NO_INDEX } else { insertedStartIndex = start insertedEndIndex = start + count } } override fun afterTextChanged(s: Editable?) { // Changing s below will lead to this TextWatcher being invoked again. Keep necessary state local. val insertedStartIndex = insertedStartIndex val insertedEndIndex = insertedEndIndex if (s != null && insertedStartIndex != NO_INDEX) { val insertedText = s.subSequence(insertedStartIndex, insertedEndIndex) if (UriMatcher.isValidUri(insertedText)) { s.insert(insertedEndIndex, ">") s.insert(insertedStartIndex, "<") } } } } Loading
app/core/src/main/java/com/fsck/k9/message/html/UriMatcher.kt +16 −0 Original line number Diff line number Diff line Loading @@ -31,4 +31,20 @@ object UriMatcher { parser.parseUri(text, startIndex) }.filterNotNull().toList() } @Suppress("ReturnCount") fun isValidUri(text: CharSequence): Boolean { val matchResult = URI_SCHEME.matchAt(text, 0) ?: return false val matchGroup = matchResult.groups[1]!! if (matchGroup.range.first != 0) { return false } val scheme = matchGroup.value.lowercase() val parser = SUPPORTED_URIS[scheme] ?: throw AssertionError("Scheme not found: $scheme") val uriMatch = parser.parseUri(text, startPos = 0) ?: return false return uriMatch.startIndex == 0 && uriMatch.endIndex == text.length } }
app/core/src/test/java/com/fsck/k9/message/html/UriMatcherTest.kt +32 −0 Original line number Diff line number Diff line package com.fsck.k9.message.html import assertk.Assert import assertk.assertThat import assertk.assertions.hasSize import assertk.assertions.isEmpty import assertk.assertions.isEqualTo import assertk.assertions.isFalse import assertk.assertions.isNotEqualTo import assertk.assertions.isTrue import org.junit.Test class UriMatcherTest { Loading Loading @@ -82,6 +85,27 @@ class UriMatcherTest { ) } @Test fun `valid URIs`() { assertThat("http://domain.example").isValidUri() assertThat("https://domain.example/").isValidUri() assertThat("mailto:user@domain.example").isValidUri() } @Test fun `not URIs`() { assertThat("some text here").isNotValidUri() assertThat("some text including the string http:").isNotValidUri() assertThat("some text including an email address without URI scheme: user@domain.example").isNotValidUri() } @Test fun `valid URIs surrounded by other characters`() { assertThat("text https://domain.example/").isNotValidUri() assertThat("https://domain.example/ text").isNotValidUri() assertThat("<https://domain.example/>").isNotValidUri() } private fun assertNoMatch(text: String) { val uriMatches = UriMatcher.findUris(text) assertThat(uriMatches).isEmpty() Loading @@ -104,3 +128,11 @@ class UriMatcherTest { } } } private fun Assert<String>.isValidUri() = given { actual -> assertThat(UriMatcher.isValidUri(actual)).isTrue() } private fun Assert<String>.isNotValidUri() = given { actual -> assertThat(UriMatcher.isValidUri(actual)).isFalse() }
app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageCompose.java +3 −0 Original line number Diff line number Diff line Loading @@ -109,6 +109,7 @@ import com.fsck.k9.search.LocalSearch; import com.fsck.k9.ui.R; import com.fsck.k9.ui.base.K9Activity; import com.fsck.k9.ui.base.ThemeManager; import com.fsck.k9.ui.compose.WrapUriTextWatcher; import com.fsck.k9.ui.compose.QuotedMessageMvpView; import com.fsck.k9.ui.compose.QuotedMessagePresenter; import com.fsck.k9.ui.helper.SizeFormatter; Loading Loading @@ -360,10 +361,12 @@ public class MessageCompose extends K9Activity implements OnClickListener, replyToView.addTextChangedListener(draftNeedsChangingTextWatcher); recipientMvpView.addTextChangedListener(draftNeedsChangingTextWatcher); quotedMessageMvpView.addTextChangedListener(draftNeedsChangingTextWatcher); quotedMessageMvpView.addTextChangedListener(new WrapUriTextWatcher()); subjectView.addTextChangedListener(draftNeedsChangingTextWatcher); messageContentView.addTextChangedListener(draftNeedsChangingTextWatcher); messageContentView.addTextChangedListener(new WrapUriTextWatcher()); /* * We set this to invisible by default. Other methods will turn it back on if it's Loading
app/ui/legacy/src/main/java/com/fsck/k9/ui/compose/WrapUriTextWatcher.kt 0 → 100644 +41 −0 Original line number Diff line number Diff line package com.fsck.k9.ui.compose import android.text.Editable import android.text.TextWatcher import com.fsck.k9.message.html.UriMatcher private const val NO_INDEX = -1 private const val MINIMUM_URI_LENGTH = 2 // scheme name + colon /** * Wraps inserted URIs in angle brackets. */ class WrapUriTextWatcher : TextWatcher { private var insertedStartIndex = NO_INDEX private var insertedEndIndex = NO_INDEX override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { if (s == null || count < MINIMUM_URI_LENGTH) { insertedStartIndex = NO_INDEX } else { insertedStartIndex = start insertedEndIndex = start + count } } override fun afterTextChanged(s: Editable?) { // Changing s below will lead to this TextWatcher being invoked again. Keep necessary state local. val insertedStartIndex = insertedStartIndex val insertedEndIndex = insertedEndIndex if (s != null && insertedStartIndex != NO_INDEX) { val insertedText = s.subSequence(insertedStartIndex, insertedEndIndex) if (UriMatcher.isValidUri(insertedText)) { s.insert(insertedEndIndex, ">") s.insert(insertedStartIndex, "<") } } } }