Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Unverified Commit 6ff3d408 authored by cketti's avatar cketti Committed by GitHub
Browse files

Merge pull request #7113 from thundernest/wrap_pasted_url

Wrap pasted URI in angle brackets
parents fdc9052a 12a7b310
Loading
Loading
Loading
Loading
+16 −0
Original line number Diff line number Diff line
@@ -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
    }
}
+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 {
@@ -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()
@@ -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()
}
+3 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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
+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, "<")
            }
        }
    }
}