Loading app/core/src/main/java/com/fsck/k9/message/html/BitcoinUriParser.javadeleted 100644 → 0 +0 −29 Original line number Diff line number Diff line package com.fsck.k9.message.html; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; class BitcoinUriParser implements UriParser { private static final Pattern BITCOIN_URI_PATTERN = Pattern.compile("bitcoin:[1-9a-km-zA-HJ-NP-Z]{27,34}(\\?[a-zA-Z0-9$\\-_.+!*'(),%:@&=]*)?"); @Nullable @Override public UriMatch parseUri(@NotNull CharSequence text, int startPos) { Matcher matcher = BITCOIN_URI_PATTERN.matcher(text); if (!matcher.find(startPos) || matcher.start() != startPos) { return null; } int startIndex = matcher.start(); int endIndex = matcher.end(); CharSequence uri = text.subSequence(startIndex, endIndex); return new UriMatch(startIndex, endIndex, uri); } } app/core/src/main/java/com/fsck/k9/message/html/EthereumUriParser.javadeleted 100644 → 0 +0 −33 Original line number Diff line number Diff line package com.fsck.k9.message.html; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** * Parses ERC-67 URIs * https://github.com/ethereum/EIPs/issues/67 */ class EthereumUriParser implements UriParser { private static final Pattern ETHEREUM_URI_PATTERN = Pattern.compile("ethereum:0x[0-9a-fA-F]*(\\?[a-zA-Z0-9$\\-_.+!*'(),%:@&=]*)?"); @Nullable @Override public UriMatch parseUri(@NotNull CharSequence text, int startPos) { Matcher matcher = ETHEREUM_URI_PATTERN.matcher(text); if (!matcher.find(startPos) || matcher.start() != startPos) { return null; } int startIndex = matcher.start(); int endIndex = matcher.end(); CharSequence uri = text.subSequence(startIndex, endIndex); return new UriMatch(startIndex, endIndex, uri); } } app/core/src/main/java/com/fsck/k9/message/html/GenericUriParser.kt 0 → 100644 +34 −0 Original line number Diff line number Diff line package com.fsck.k9.message.html import java.util.regex.Pattern /** * Matches the URI generic syntax. * * See [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986). */ class GenericUriParser : UriParser { override fun parseUri(text: CharSequence, startPos: Int): UriMatch? { val matcher = PATTERN.matcher(text) if (!matcher.find(startPos) || matcher.start() != startPos) return null val startIndex = matcher.start() val endIndex = matcher.end() val uri = text.subSequence(startIndex, endIndex) return UriMatch(startIndex, endIndex, uri) } companion object { private const val SCHEME = "[a-zA-Z][a-zA-Z0-9+.\\-]*" private const val AUTHORITY = "[a-zA-Z0-9\\-._~%!\$&'()*+,;=:\\[\\]@]*" private const val PATH = "[a-zA-Z0-9\\-._~%!\$&'()*+,;=:@/]*" private const val QUERY = "[a-zA-Z0-9\\-._~%!\$&'()*+,;=:@/?]*" private const val FRAGMENT = "[a-zA-Z0-9\\-._~%!\$&'()*+,;=:@/?]*" // This regular expression matches more than allowed by the generic URI syntax. So we might end up linkifying // text that is not a proper URI. We leave apps actually handling the URI when the user clicks on such a link // to deal with this case. private val PATTERN = Pattern.compile("$SCHEME:(?://$AUTHORITY)?(?:$PATH)?(?:\\?$QUERY)?(?:#$FRAGMENT)?") } } app/core/src/main/java/com/fsck/k9/message/html/UriMatcher.kt +5 −3 Original line number Diff line number Diff line Loading @@ -3,12 +3,14 @@ package com.fsck.k9.message.html object UriMatcher { private val SUPPORTED_URIS = run { val httpUriParser = HttpUriParser() val genericUriParser = GenericUriParser() mapOf( "ethereum:" to EthereumUriParser(), "bitcoin:" to BitcoinUriParser(), "http:" to httpUriParser, "https:" to httpUriParser, "rtsp:" to httpUriParser "mailto:" to genericUriParser, "matrix:" to genericUriParser, "rtsp:" to httpUriParser, "xmpp:" to genericUriParser, ) } Loading app/core/src/test/java/com/fsck/k9/message/html/BitcoinUriParserTest.javadeleted 100644 → 0 +0 −73 Original line number Diff line number Diff line package com.fsck.k9.message.html; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; public class BitcoinUriParserTest { BitcoinUriParser parser = new BitcoinUriParser(); @Test public void basicBitcoinUri() throws Exception { assertValidUri("bitcoin:19W6QZkx8SYPG7BBCS7odmWGRxqRph5jFU"); } @Test public void bitcoinUriWithAmount() throws Exception { assertValidUri("bitcoin:12A1MyfXbW6RhdRAZEqofac5jCQQjwEPBu?amount=1.2"); } @Test public void bitcoinUriWithQueryParameters() throws Exception { assertValidUri("bitcoin:12A1MyfXbW6RhdRAZEqofac5jCQQjwEPBu?amount=1.2" + "&message=Payment&label=Satoshi&extra=other-param"); } @Test public void uriInMiddleOfInput() throws Exception { String prefix = "prefix "; String uri = "bitcoin:12A1MyfXbW6RhdRAZEqofac5jCQQjwEPBu?amount=1.2"; String text = prefix + uri; UriMatch uriMatch = parser.parseUri(text, prefix.length()); assertUriMatch(uri, uriMatch, prefix.length()); } @Test public void invalidScheme() throws Exception { assertInvalidUri("bitcion:19W6QZkx8SYPG7BBCS7odmWGRxqRph5jFU"); } @Test public void invalidAddress() throws Exception { assertInvalidUri("bitcoin:[invalid]"); } private void assertValidUri(String uri) { UriMatch uriMatch = parser.parseUri(uri, 0); assertUriMatch(uri, uriMatch); } private void assertUriMatch(String uri, UriMatch uriMatch) { assertUriMatch(uri, uriMatch, 0); } private void assertUriMatch(String uri, UriMatch uriMatch, int offset) { assertNotNull(uriMatch); assertEquals(offset, uriMatch.getStartIndex()); assertEquals(uri.length() + offset, uriMatch.getEndIndex()); assertEquals(uri, uriMatch.getUri().toString()); } private void assertInvalidUri(String text) { UriMatch uriMatch = parser.parseUri(text, 0); assertNull(uriMatch); } } Loading
app/core/src/main/java/com/fsck/k9/message/html/BitcoinUriParser.javadeleted 100644 → 0 +0 −29 Original line number Diff line number Diff line package com.fsck.k9.message.html; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; class BitcoinUriParser implements UriParser { private static final Pattern BITCOIN_URI_PATTERN = Pattern.compile("bitcoin:[1-9a-km-zA-HJ-NP-Z]{27,34}(\\?[a-zA-Z0-9$\\-_.+!*'(),%:@&=]*)?"); @Nullable @Override public UriMatch parseUri(@NotNull CharSequence text, int startPos) { Matcher matcher = BITCOIN_URI_PATTERN.matcher(text); if (!matcher.find(startPos) || matcher.start() != startPos) { return null; } int startIndex = matcher.start(); int endIndex = matcher.end(); CharSequence uri = text.subSequence(startIndex, endIndex); return new UriMatch(startIndex, endIndex, uri); } }
app/core/src/main/java/com/fsck/k9/message/html/EthereumUriParser.javadeleted 100644 → 0 +0 −33 Original line number Diff line number Diff line package com.fsck.k9.message.html; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** * Parses ERC-67 URIs * https://github.com/ethereum/EIPs/issues/67 */ class EthereumUriParser implements UriParser { private static final Pattern ETHEREUM_URI_PATTERN = Pattern.compile("ethereum:0x[0-9a-fA-F]*(\\?[a-zA-Z0-9$\\-_.+!*'(),%:@&=]*)?"); @Nullable @Override public UriMatch parseUri(@NotNull CharSequence text, int startPos) { Matcher matcher = ETHEREUM_URI_PATTERN.matcher(text); if (!matcher.find(startPos) || matcher.start() != startPos) { return null; } int startIndex = matcher.start(); int endIndex = matcher.end(); CharSequence uri = text.subSequence(startIndex, endIndex); return new UriMatch(startIndex, endIndex, uri); } }
app/core/src/main/java/com/fsck/k9/message/html/GenericUriParser.kt 0 → 100644 +34 −0 Original line number Diff line number Diff line package com.fsck.k9.message.html import java.util.regex.Pattern /** * Matches the URI generic syntax. * * See [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986). */ class GenericUriParser : UriParser { override fun parseUri(text: CharSequence, startPos: Int): UriMatch? { val matcher = PATTERN.matcher(text) if (!matcher.find(startPos) || matcher.start() != startPos) return null val startIndex = matcher.start() val endIndex = matcher.end() val uri = text.subSequence(startIndex, endIndex) return UriMatch(startIndex, endIndex, uri) } companion object { private const val SCHEME = "[a-zA-Z][a-zA-Z0-9+.\\-]*" private const val AUTHORITY = "[a-zA-Z0-9\\-._~%!\$&'()*+,;=:\\[\\]@]*" private const val PATH = "[a-zA-Z0-9\\-._~%!\$&'()*+,;=:@/]*" private const val QUERY = "[a-zA-Z0-9\\-._~%!\$&'()*+,;=:@/?]*" private const val FRAGMENT = "[a-zA-Z0-9\\-._~%!\$&'()*+,;=:@/?]*" // This regular expression matches more than allowed by the generic URI syntax. So we might end up linkifying // text that is not a proper URI. We leave apps actually handling the URI when the user clicks on such a link // to deal with this case. private val PATTERN = Pattern.compile("$SCHEME:(?://$AUTHORITY)?(?:$PATH)?(?:\\?$QUERY)?(?:#$FRAGMENT)?") } }
app/core/src/main/java/com/fsck/k9/message/html/UriMatcher.kt +5 −3 Original line number Diff line number Diff line Loading @@ -3,12 +3,14 @@ package com.fsck.k9.message.html object UriMatcher { private val SUPPORTED_URIS = run { val httpUriParser = HttpUriParser() val genericUriParser = GenericUriParser() mapOf( "ethereum:" to EthereumUriParser(), "bitcoin:" to BitcoinUriParser(), "http:" to httpUriParser, "https:" to httpUriParser, "rtsp:" to httpUriParser "mailto:" to genericUriParser, "matrix:" to genericUriParser, "rtsp:" to httpUriParser, "xmpp:" to genericUriParser, ) } Loading
app/core/src/test/java/com/fsck/k9/message/html/BitcoinUriParserTest.javadeleted 100644 → 0 +0 −73 Original line number Diff line number Diff line package com.fsck.k9.message.html; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; public class BitcoinUriParserTest { BitcoinUriParser parser = new BitcoinUriParser(); @Test public void basicBitcoinUri() throws Exception { assertValidUri("bitcoin:19W6QZkx8SYPG7BBCS7odmWGRxqRph5jFU"); } @Test public void bitcoinUriWithAmount() throws Exception { assertValidUri("bitcoin:12A1MyfXbW6RhdRAZEqofac5jCQQjwEPBu?amount=1.2"); } @Test public void bitcoinUriWithQueryParameters() throws Exception { assertValidUri("bitcoin:12A1MyfXbW6RhdRAZEqofac5jCQQjwEPBu?amount=1.2" + "&message=Payment&label=Satoshi&extra=other-param"); } @Test public void uriInMiddleOfInput() throws Exception { String prefix = "prefix "; String uri = "bitcoin:12A1MyfXbW6RhdRAZEqofac5jCQQjwEPBu?amount=1.2"; String text = prefix + uri; UriMatch uriMatch = parser.parseUri(text, prefix.length()); assertUriMatch(uri, uriMatch, prefix.length()); } @Test public void invalidScheme() throws Exception { assertInvalidUri("bitcion:19W6QZkx8SYPG7BBCS7odmWGRxqRph5jFU"); } @Test public void invalidAddress() throws Exception { assertInvalidUri("bitcoin:[invalid]"); } private void assertValidUri(String uri) { UriMatch uriMatch = parser.parseUri(uri, 0); assertUriMatch(uri, uriMatch); } private void assertUriMatch(String uri, UriMatch uriMatch) { assertUriMatch(uri, uriMatch, 0); } private void assertUriMatch(String uri, UriMatch uriMatch, int offset) { assertNotNull(uriMatch); assertEquals(offset, uriMatch.getStartIndex()); assertEquals(uri.length() + offset, uriMatch.getEndIndex()); assertEquals(uri, uriMatch.getUri().toString()); } private void assertInvalidUri(String text) { UriMatch uriMatch = parser.parseUri(text, 0); assertNull(uriMatch); } }