diff --git a/k9mail/src/main/java/com/fsck/k9/message/html/BitcoinUriParser.java b/k9mail/src/main/java/com/fsck/k9/message/html/BitcoinUriParser.java index 0d104e32a..f266da7a4 100644 --- a/k9mail/src/main/java/com/fsck/k9/message/html/BitcoinUriParser.java +++ b/k9mail/src/main/java/com/fsck/k9/message/html/BitcoinUriParser.java @@ -5,9 +5,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -/** - * Parses and "linkifies" bitcoin links. - */ 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$\\-_.+!*'(),%:@&=]*)?"); @@ -16,7 +13,6 @@ class BitcoinUriParser implements UriParser { public int linkifyUri(String text, int startPos, StringBuffer outputBuffer) { Matcher matcher = BITCOIN_URI_PATTERN.matcher(text); - // Skip not matching uris if (!matcher.find(startPos) || matcher.start() != startPos) { return startPos; } diff --git a/k9mail/src/main/java/com/fsck/k9/message/html/HttpUriParser.java b/k9mail/src/main/java/com/fsck/k9/message/html/HttpUriParser.java index 78bcc2897..c4070ed0a 100644 --- a/k9mail/src/main/java/com/fsck/k9/message/html/HttpUriParser.java +++ b/k9mail/src/main/java/com/fsck/k9/message/html/HttpUriParser.java @@ -10,8 +10,10 @@ import java.util.regex.Pattern; /** * Parses and "linkifies" http links. *

- * This class is in parts inspired by OkHttp's HttpUrl (https://github.com/square/okhttp/blob/master/okhttp/src/main/java/okhttp3/HttpUrl.java),s - * but leaving out much of the parsing part. + * This class is in parts inspired by OkHttp's + * HttpUrl. + * But much of the parsing parts have been left out. + *

*/ class HttpUriParser implements UriParser { // This string represent character group sub-delim as described in RFC 3986 @@ -19,11 +21,12 @@ class HttpUriParser implements UriParser { private static final Pattern IPv4_PATTERN = Pattern.compile("(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})(:(\\d{0,5}))?"); + @Override public int linkifyUri(String text, int startPos, StringBuffer outputBuffer) { int currentPos = startPos; - // Test scheme + // Scheme String shortScheme = text.substring(currentPos, Math.min(currentPos + 7, text.length())); String longScheme = text.substring(currentPos, Math.min(currentPos + 8, text.length())); if (shortScheme.equalsIgnoreCase("https://")) { @@ -33,20 +36,17 @@ class HttpUriParser implements UriParser { } else if (longScheme.equalsIgnoreCase("rtsp://")) { currentPos += "rtsp://".length(); } else { - // Unsupported scheme return startPos; } - // Test authority + // Authority int authorityEnd = text.indexOf('/', currentPos); if (authorityEnd == -1) { authorityEnd = text.length(); } - // Authority: Take a look at user info if available currentPos = matchUserInfoIfAvailable(text, currentPos, authorityEnd); - // Authority: Take a look at host if (!tryMatchDomainName(text, currentPos, authorityEnd) && !tryMatchIpv4Address(text, currentPos, authorityEnd, true) && !tryMatchIpv6Address(text, currentPos, authorityEnd)) { @@ -54,24 +54,27 @@ class HttpUriParser implements UriParser { } currentPos = authorityEnd; - // Test path + // Path if (currentPos < text.length() && text.charAt(currentPos) == '/') { currentPos = matchUnreservedPCTEncodedSubDelimClassesGreedy(text, currentPos + 1, "/:@"); } - // Test for query + // Query if (currentPos < text.length() && text.charAt(currentPos) == '?') { currentPos = matchUnreservedPCTEncodedSubDelimClassesGreedy(text, currentPos + 1, ":@/?"); } - // Test for fragment. + // Fragment if (currentPos < text.length() && text.charAt(currentPos) == '#') { currentPos = matchUnreservedPCTEncodedSubDelimClassesGreedy(text, currentPos + 1, ":@/?"); } - // Final link generation - String linkifiedUri = String.format("%1$s", text.substring(startPos, currentPos)); - outputBuffer.append(linkifiedUri); + String httpUri = text.substring(startPos, currentPos); + outputBuffer.append("") + .append(httpUri) + .append(""); return currentPos; } @@ -89,7 +92,7 @@ class HttpUriParser implements UriParser { } private boolean tryMatchDomainName(String text, int startPos, int authorityEnd) { - // Partly from OkHttp's HttpUrl (https://github.com/square/okhttp/blob/master/okhttp/src/main/java/okhttp3/HttpUrl.java) + // Partly from OkHttp's HttpUrl try { // Check for port int portPos = text.indexOf(':', startPos); @@ -143,7 +146,6 @@ class HttpUriParser implements UriParser { return false; } - // Validate segments for (int i = 1; i <= 4; i++) { int segment = Integer.parseInt(matcher.group(1)); if (segment > 255) { @@ -151,12 +153,10 @@ class HttpUriParser implements UriParser { } } - // Make sure port does not exist if missing if (!portAllowed && matcher.group(5) != null) { return false; } - // Validate optional port String portString = matcher.group(6); if (portString != null && !portString.isEmpty()) { int port = Integer.parseInt(portString); @@ -169,7 +169,6 @@ class HttpUriParser implements UriParser { } private boolean tryMatchIpv6Address(String text, int startPos, int authorityEnd) { - // General validation if (text.codePointAt(startPos) != '[') { return false; } diff --git a/k9mail/src/main/java/com/fsck/k9/message/html/UriLinkifier.java b/k9mail/src/main/java/com/fsck/k9/message/html/UriLinkifier.java index 1c343d951..0fd97ac1c 100644 --- a/k9mail/src/main/java/com/fsck/k9/message/html/UriLinkifier.java +++ b/k9mail/src/main/java/com/fsck/k9/message/html/UriLinkifier.java @@ -2,6 +2,7 @@ package com.fsck.k9.message.html; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -9,13 +10,7 @@ import java.util.regex.Pattern; import android.text.TextUtils; -/** - * Allows conversion of link in text to html link. - */ public class UriLinkifier { - /** - * Regular expression pattern to match uri scheme and parsers for supported uris as defined in RFC 3987 - */ private static final Pattern URI_SCHEME; private static final Map SUPPORTED_URIS; private static final String SCHEME_SEPARATOR = " ("; @@ -32,16 +27,8 @@ public class UriLinkifier { URI_SCHEME = Pattern.compile(allSchemes, Pattern.CASE_INSENSITIVE); } - /** - * Searches for link-like text in a string and turn it into a link. Append the result to - * outputBuffer. text is not modified. - * - * @param text - * Plain text to be linkified. - * @param outputBuffer - * Buffer to append linked text to. - */ - public static void linkifyText(final String text, final StringBuffer outputBuffer) { + + public static void linkifyText(String text, StringBuffer outputBuffer) { int currentPos = 0; Matcher matcher = URI_SCHEME.matcher(text); @@ -51,31 +38,39 @@ public class UriLinkifier { String textBeforeMatch = text.substring(currentPos, startPos); outputBuffer.append(textBeforeMatch); - if (!textBeforeMatch.isEmpty() && - !SCHEME_SEPARATOR.contains(textBeforeMatch.substring(textBeforeMatch.length() - 1))) { + if (!isPrecededByValidSeparator(textBeforeMatch)) { outputBuffer.append(text.charAt(startPos)); currentPos = startPos + 1; continue; } - // Find responsible parser and let it do it's job - String scheme = matcher.group(); - UriParser parser = SUPPORTED_URIS.get(scheme.toLowerCase()); + String scheme = matcher.group().toLowerCase(Locale.US); + UriParser parser = SUPPORTED_URIS.get(scheme); int newPos = parser.linkifyUri(text, startPos, outputBuffer); - // Handle invalid uri, at least advance by one to prevent endless loop - if (newPos <= startPos) { + boolean uriWasNotLinkified = newPos <= startPos; + if (uriWasNotLinkified) { outputBuffer.append(text.charAt(startPos)); currentPos++; } else { currentPos = (newPos > currentPos) ? newPos : currentPos + 1; } + if (currentPos >= text.length()) { break; } } - // Copy rest - outputBuffer.append(text.substring(currentPos)); + String textAfterLastMatch = text.substring(currentPos); + outputBuffer.append(textAfterLastMatch); + } + + private static boolean isPrecededByValidSeparator(String textBeforeMatch) { + if (textBeforeMatch.isEmpty()) { + return true; + } + + String characterBeforeMatch = textBeforeMatch.substring(textBeforeMatch.length() - 1); + return SCHEME_SEPARATOR.contains(characterBeforeMatch); } } diff --git a/k9mail/src/main/java/com/fsck/k9/message/html/UriParser.java b/k9mail/src/main/java/com/fsck/k9/message/html/UriParser.java index d5cbefda7..ad08c0d14 100644 --- a/k9mail/src/main/java/com/fsck/k9/message/html/UriParser.java +++ b/k9mail/src/main/java/com/fsck/k9/message/html/UriParser.java @@ -1,15 +1,19 @@ package com.fsck.k9.message.html; -/** - * General framework to handle uris when parsing. Allows different handling depending on the scheme identifier. - */ + public interface UriParser { /** - * Parse and linkify scheme specific uri beginning from given position. The result will be written to given buffer. - * @param text String to parse uri from. - * @param startPos Position where uri starts (first letter of scheme). - * @param outputBuffer Buffer where linkified variant of uri is written to. - * @return Index where parsed uri ends (first non-uri letter). Should be startPos or smaller if no valid uri was found. + * Parse and linkify scheme specific URI beginning from given position. The result will be written to given buffer. + * + * @param text + * String to parse URI from. + * @param startPos + * Position where URI starts (first letter of scheme). + * @param outputBuffer + * Buffer where linkified variant of URI is written to. + * + * @return Index where parsed URI ends (first non-URI letter). Should be {@code startPos} or smaller if no valid + * URI was found. */ int linkifyUri(String text, int startPos, StringBuffer outputBuffer); } diff --git a/k9mail/src/test/java/com/fsck/k9/message/html/BitcoinUriParserTest.java b/k9mail/src/test/java/com/fsck/k9/message/html/BitcoinUriParserTest.java new file mode 100644 index 000000000..12b9f5bbf --- /dev/null +++ b/k9mail/src/test/java/com/fsck/k9/message/html/BitcoinUriParserTest.java @@ -0,0 +1,84 @@ +package com.fsck.k9.message.html; + + +import org.junit.Test; + +import static com.fsck.k9.message.html.UriParserTestHelper.assertLinkOnly; +import static org.junit.Assert.assertEquals; + + +public class BitcoinUriParserTest { + BitcoinUriParser parser = new BitcoinUriParser(); + StringBuffer outputBuffer = new StringBuffer(); + + + @Test + public void basicBitcoinUri() throws Exception { + assertLinkify("bitcoin:19W6QZkx8SYPG7BBCS7odmWGRxqRph5jFU"); + } + + @Test + public void bitcoinUriWithAmount() throws Exception { + assertLinkify("bitcoin:12A1MyfXbW6RhdRAZEqofac5jCQQjwEPBu?amount=1.2"); + } + + @Test + public void bitcoinUriWithQueryParameters() throws Exception { + assertLinkify("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; + + parser.linkifyUri(text, prefix.length(), outputBuffer); + + assertLinkOnly(uri, outputBuffer); + } + + @Test + public void invalidScheme() throws Exception { + assertNotLinkify("bitcion:19W6QZkx8SYPG7BBCS7odmWGRxqRph5jFU"); + } + + @Test + public void invalidAddress() throws Exception { + assertNotLinkify("bitcoin:[invalid]"); + } + + @Test + public void invalidBitcoinUri_shouldReturnStartingPosition() throws Exception { + String uri = "bitcoin:[invalid]"; + + int newPos = linkify(uri); + + assertEquals(0, newPos); + } + + @Test + public void invalidBitcoinUri_shouldNotWriteToOutputBuffer() throws Exception { + String uri = "bitcoin:[invalid]"; + + linkify(uri); + + assertEquals(0, outputBuffer.length()); + } + + + int linkify(String uri) { + return parser.linkifyUri(uri, 0, outputBuffer); + } + + void assertLinkify(String uri) { + linkify(uri); + assertLinkOnly(uri, outputBuffer); + } + + void assertNotLinkify(String text) { + int newPos = linkify(text); + assertEquals(0, newPos); + } +} diff --git a/k9mail/src/test/java/com/fsck/k9/message/html/HttpUriParserTest.java b/k9mail/src/test/java/com/fsck/k9/message/html/HttpUriParserTest.java index d930db8e7..159e4ddb1 100644 --- a/k9mail/src/test/java/com/fsck/k9/message/html/HttpUriParserTest.java +++ b/k9mail/src/test/java/com/fsck/k9/message/html/HttpUriParserTest.java @@ -1,216 +1,155 @@ package com.fsck.k9.message.html; -import com.fsck.k9.K9RobolectricTestRunner; -import org.junit.Before; import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.annotation.Config; +import static com.fsck.k9.message.html.UriParserTestHelper.assertLinkOnly; import static junit.framework.Assert.assertEquals; -@RunWith(K9RobolectricTestRunner.class) -@Config(manifest = Config.NONE) public class HttpUriParserTest { - private HttpUriParser parser; - private StringBuffer outputBuffer; + private final HttpUriParser parser = new HttpUriParser(); + private final StringBuffer outputBuffer = new StringBuffer(); - @Before - public void setUp() { - parser = new HttpUriParser(); - outputBuffer = new StringBuffer(); + + @Test + public void simpleDomain() { + assertLinkify("http://www.google.com"); } @Test - public void testSimpleDomain() { - String text = "http://www.google.com"; - int endPos = parser.linkifyUri(text, 0, outputBuffer); - assertEquals(String.format("%1$s", text), outputBuffer.toString()); - assertEquals(text.length(), endPos); + public void domainWithTrailingSlash() { + assertLinkify("http://www.google.com/"); } @Test - public void testDomainWithTrailingSlash() { - String text = "http://www.google.com/"; - int endPos = parser.linkifyUri(text, 0, outputBuffer); - assertEquals(String.format("%1$s", text), outputBuffer.toString()); - assertEquals(text.length(), endPos); + public void domainWithoutWww() { + assertLinkify("http://google.com/"); } @Test - public void testDomainWithoutWWW() { - String text = "http://google.com/"; - int endPos = parser.linkifyUri(text, 0, outputBuffer); - assertEquals(String.format("%1$s", text), outputBuffer.toString()); - assertEquals(text.length(), endPos); + public void query() { + assertLinkify("http://google.com/give/me/?q=mode&c=information"); } @Test - public void testDomainWithTrailingSpace() { + public void fragment() { + assertLinkify("http://google.com/give/me#only-the-best"); + } + + @Test + public void queryAndFragment() { + assertLinkify("http://google.com/give/me/?q=mode&c=information#only-the-best"); + } + + @Test + public void ipv4Address() { + assertLinkify("http://127.0.0.1"); + } + + @Test + public void ipv4AddressWithTrailingSlash() { + assertLinkify("http://127.0.0.1/"); + } + + @Test + public void ipv4AddressWithEmptyPort() { + assertLinkify("http://127.0.0.1:"); + } + + @Test + public void ipv4AddressWithPort() { + assertLinkify("http://127.0.0.1:524/"); + } + + @Test + public void ipv6Address() { + assertLinkify("http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]"); + } + + @Test + public void ipv6AddressWithPort() { + assertLinkify("http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80"); + } + + @Test + public void ipv6AddressWithTrailingSlash() { + assertLinkify("http://[1080:0:0:0:8:800:200C:417A]/"); + } + + @Test + public void ipv6AddressWithEndCompression() { + assertLinkify("http://[3ffe:2a00:100:7031::1]"); + } + + @Test + public void ipv6AddressWithBeginCompression() { + assertLinkify("http://[1080::8:800:200C:417A]/"); + } + + @Test + public void ipv6AddressWithCompressionPort() { + assertLinkify("http://[::FFFF:129.144.52.38]:80/"); + } + + @Test + public void ipv6AddressWithPrependedCompression() { + assertLinkify("http://[::192.9.5.5]/"); + } + + @Test + public void ipv6AddressWithTrailingIp4AndPort() { + assertLinkify("http://[::192.9.5.5]:80/"); + } + + @Test + public void domainWithTrailingSpace() { String text = "http://google.com/ "; + int endPos = parser.linkifyUri(text, 0, outputBuffer); - assertEquals("http://google.com/", outputBuffer.toString()); + + assertLinkOnly("http://google.com/", outputBuffer); assertEquals(text.length() - 1, endPos); } @Test - public void testDomainWithTrailingSpaceNewline() { - String text = "http://google.com/ \n"; - int endPos = parser.linkifyUri(text, 0, outputBuffer); - assertEquals("http://google.com/", outputBuffer.toString()); - assertEquals(text.length() - 2, endPos); - } - - @Test - public void testDomainWithTrailingNewline() { + public void domainWithTrailingNewline() { String text = "http://google.com/\n"; + int endPos = parser.linkifyUri(text, 0, outputBuffer); - assertEquals("http://google.com/", outputBuffer.toString()); + + assertLinkOnly("http://google.com/", outputBuffer); assertEquals(text.length() - 1, endPos); } @Test - public void testDomainsWithQueryAndFragment() { - String text = "http://google.com/give/me/?q=mode&c=information#only-the-best"; - int endPos = parser.linkifyUri(text, 0, outputBuffer); - assertEquals( - "http://google.com/give/me/?q=mode&c=information#only-the-best", - outputBuffer.toString()); - assertEquals(text.length(), endPos); - } + public void domainWithTrailingAngleBracket() { + String text = ""; - @Test - public void testDomainsWithQuery() { - String text = "http://google.com/give/me/?q=mode&c=information"; - int endPos = parser.linkifyUri(text, 0, outputBuffer); - assertEquals( - "http://google.com/give/me/?q=mode&c=information", - outputBuffer.toString()); - assertEquals(text.length(), endPos); - } + int endPos = parser.linkifyUri(text, 1, outputBuffer); - @Test - public void testDomainsWithFragment() { - String text = "http://google.com/give/me#only-the-best"; - int endPos = parser.linkifyUri(text, 0, outputBuffer); - assertEquals( - "http://google.com/give/me#only-the-best", - outputBuffer.toString()); - assertEquals(text.length(), endPos); - } - - @Test - public void testDomainsWithQueryAndFragmentWithoutWWWW() { - String text = "http://google.com/give/me/?q=mode+c=information#only-the-best\n"; - int endPos = parser.linkifyUri(text, 0, outputBuffer); - assertEquals( - "http://google.com/give/me/?q=mode+c=information#only-the-best", - outputBuffer.toString()); + assertLinkOnly("http://google.com/", outputBuffer); assertEquals(text.length() - 1, endPos); } @Test - public void testIpv4Address() { - String text = "http://127.0.0.1"; - int endPos = parser.linkifyUri(text, 0, outputBuffer); - assertEquals(String.format("%1$s", text), outputBuffer.toString()); - assertEquals(text.length(), endPos); + public void uriInMiddleOfInput() throws Exception { + String prefix = "prefix "; + String uri = "http://google.com/"; + String text = prefix + uri; + + parser.linkifyUri(text, prefix.length(), outputBuffer); + + assertLinkOnly(uri, outputBuffer); } - @Test - public void testIpv4AddressWithTrailingSlash() { - String text = "http://127.0.0.1/"; - int endPos = parser.linkifyUri(text, 0, outputBuffer); - assertEquals(String.format("%1$s", text), outputBuffer.toString()); - assertEquals(text.length(), endPos); + + int linkify(String uri) { + return parser.linkifyUri(uri, 0, outputBuffer); } - @Test - public void testIpv4AddressWithEmptyPort() { - String text = "http://127.0.0.1:"; - int endPos = parser.linkifyUri(text, 0, outputBuffer); - assertEquals(String.format("%1$s", text), outputBuffer.toString()); - assertEquals(text.length(), endPos); - } - - @Test - public void testIpv4AddressWithPort() { - String text = "http://127.0.0.1:524/"; - int endPos = parser.linkifyUri(text, 0, outputBuffer); - assertEquals(String.format("%1$s", text), outputBuffer.toString()); - assertEquals(text.length(), endPos); - } - - @Test - public void testIpv6Address() { - String text = "http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]"; - int endPos = parser.linkifyUri(text, 0, outputBuffer); - assertEquals(String.format("%1$s", text), outputBuffer.toString()); - assertEquals(text.length(), endPos); - } - - @Test - public void testIpv6AddressWithPort() { - String text = "http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80"; - int endPos = parser.linkifyUri(text, 0, outputBuffer); - assertEquals(String.format("%1$s", text), outputBuffer.toString()); - assertEquals(text.length(), endPos); - } - - @Test - public void testIpv6AddressShort() { - String text = "http://[1080:0:0:0:8:800:200C:417A]/"; - int endPos = parser.linkifyUri(text, 0, outputBuffer); - assertEquals(String.format("%1$s", text), outputBuffer.toString()); - assertEquals(text.length(), endPos); - } - - @Test - public void testIpv6AddressWithEndCompression() { - String text = "http://[3ffe:2a00:100:7031::1]"; - int endPos = parser.linkifyUri(text, 0, outputBuffer); - assertEquals(String.format("%1$s", text), outputBuffer.toString()); - assertEquals(text.length(), endPos); - } - - @Test - public void testIpv6AddressWithBeginCompression() { - String text = "http://[1080::8:800:200C:417A]/"; - int endPos = parser.linkifyUri(text, 0, outputBuffer); - assertEquals(String.format("%1$s", text), outputBuffer.toString()); - assertEquals(text.length(), endPos); - } - - @Test - public void testIpv6AddressWithPrependedCompression() { - String text = "http://[::192.9.5.5]/"; - int endPos = parser.linkifyUri(text, 0, outputBuffer); - assertEquals(String.format("%1$s", text), outputBuffer.toString()); - assertEquals(text.length(), endPos); - } - - @Test - public void testIpv6AddressWithCompressionPort() { - String text = "http://[::FFFF:129.144.52.38]:80/"; - int endPos = parser.linkifyUri(text, 0, outputBuffer); - assertEquals(String.format("%1$s", text), outputBuffer.toString()); - assertEquals(text.length(), endPos); - } - - @Test - public void testIpv6AddressWithTrailingIp4() { - String text = "http://[::192.9.5.5]/"; - int endPos = parser.linkifyUri(text, 0, outputBuffer); - assertEquals(String.format("%1$s", text), outputBuffer.toString()); - assertEquals(text.length(), endPos); - } - - @Test - public void testIpv6AddressWithTrailingIp4AndPort() { - String text = "http://[::192.9.5.5]:80/"; - int endPos = parser.linkifyUri(text, 0, outputBuffer); - assertEquals(String.format("%1$s", text), outputBuffer.toString()); - assertEquals(text.length(), endPos); + void assertLinkify(String uri) { + linkify(uri); + assertLinkOnly(uri, outputBuffer); } } diff --git a/k9mail/src/test/java/com/fsck/k9/message/html/UriLinkifierTest.java b/k9mail/src/test/java/com/fsck/k9/message/html/UriLinkifierTest.java index 40f6281a1..4d81baad5 100644 --- a/k9mail/src/test/java/com/fsck/k9/message/html/UriLinkifierTest.java +++ b/k9mail/src/test/java/com/fsck/k9/message/html/UriLinkifierTest.java @@ -6,90 +6,113 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.annotation.Config; +import static com.fsck.k9.message.html.UriParserTestHelper.assertLinkOnly; import static junit.framework.Assert.assertEquals; @RunWith(K9RobolectricTestRunner.class) @Config(manifest = Config.NONE) public class UriLinkifierTest { - @Test - public void testLinkifyBitcoinAndHttpUri() { - String text = "bitcoin:19W6QZkx8SYPG7BBCS7odmWGRxqRph5jFU http://example.com/"; + private StringBuffer outputBuffer = new StringBuffer(); - StringBuffer outputBuffer = new StringBuffer(); - UriLinkifier.linkifyText(text, outputBuffer); - - assertEquals("" + - "bitcoin:19W6QZkx8SYPG7BBCS7odmWGRxqRph5jFU" + - " " + - "" + - "http://example.com/" + - "", outputBuffer.toString()); - } @Test - public void testSimpleHttpUri() { - String text = "http://www.google.com"; - StringBuffer outputBuffer = new StringBuffer(); - UriLinkifier.linkifyText(text, outputBuffer); - assertEquals("http://www.google.com", outputBuffer.toString()); - } + public void emptyText() { + String text = ""; - @Test - public void testHttpUriWithTrailingSlash() { - String text = "http://www.google.com/"; - StringBuffer outputBuffer = new StringBuffer(); UriLinkifier.linkifyText(text, outputBuffer); - assertEquals("http://www.google.com/", - outputBuffer.toString()); - } - @Test - public void testHttpUriWithoutWWW() { - String text = "http://google.com/"; - StringBuffer outputBuffer = new StringBuffer(); - UriLinkifier.linkifyText(text, outputBuffer); - assertEquals("http://google.com/", outputBuffer.toString()); - } - - @Test - public void testHttpUriWithTrailingSpace() { - String text = "http://google.com/ "; - StringBuffer outputBuffer = new StringBuffer(); - UriLinkifier.linkifyText(text, outputBuffer); - assertEquals("http://google.com/ ", outputBuffer.toString()); - } - - @Test - public void testHttpUriWithTrailingSpaceNewline() { - String text = "http://google.com/ \n"; - StringBuffer outputBuffer = new StringBuffer(); - UriLinkifier.linkifyText(text, outputBuffer); - assertEquals("http://google.com/ \n", - outputBuffer.toString()); - } - - @Test - public void testHttpUriWithTrailingNewline() { - String text = "http://google.com/\n"; - StringBuffer outputBuffer = new StringBuffer(); - UriLinkifier.linkifyText(text, outputBuffer); - assertEquals("http://google.com/\n", outputBuffer.toString()); - } - - @Test - public void testIgnorePartialHttpUriScheme() { - String text = "myhttp://example.org"; - StringBuffer outputBuffer = new StringBuffer(); - UriLinkifier.linkifyText(text, outputBuffer); assertEquals(text, outputBuffer.toString()); } @Test - public void testPartialHttpUriSchemeWithSeparator() { - String text = "(http://example.org"; - StringBuffer outputBuffer = new StringBuffer(); + public void textWithoutUri_shouldBeCopiedToOutputBuffer() { + String text = "some text here"; + UriLinkifier.linkifyText(text, outputBuffer); + + assertEquals(text, outputBuffer.toString()); + } + + @Test + public void simpleUri() { + String uri = "http://example.org"; + + UriLinkifier.linkifyText(uri, outputBuffer); + + assertLinkOnly(uri, outputBuffer); + } + + @Test + public void uriPrecededBySpace() { + String text = " http://example.org"; + + UriLinkifier.linkifyText(text, outputBuffer); + + assertEquals(" http://example.org", outputBuffer.toString()); + } + + @Test + public void uriPrecededByOpeningParenthesis() { + String text = "(http://example.org"; + + UriLinkifier.linkifyText(text, outputBuffer); + assertEquals("(http://example.org", outputBuffer.toString()); } + + @Test + public void uriPrecededBySomeText() { + String uri = "Check out my fantastic URI: http://example.org"; + + UriLinkifier.linkifyText(uri, outputBuffer); + + assertEquals("Check out my fantastic URI: http://example.org", + outputBuffer.toString()); + } + + @Test + public void uriWithTrailingText() { + String uri = "http://example.org/ is the best"; + + UriLinkifier.linkifyText(uri, outputBuffer); + + assertEquals("http://example.org/ is the best", outputBuffer.toString()); + } + + @Test + public void uriEmbeddedInText() { + String uri = "prefix http://example.org/ suffix"; + + UriLinkifier.linkifyText(uri, outputBuffer); + + assertEquals("prefix http://example.org/ suffix", outputBuffer.toString()); + } + + @Test + public void uriWithUppercaseScheme() { + String uri = "HTTP://example.org/"; + + UriLinkifier.linkifyText(uri, outputBuffer); + + assertEquals("HTTP://example.org/", outputBuffer.toString()); + } + + @Test + public void uriNotPrecededByValidSeparator_shouldNotBeLinkified() { + String text = "myhttp://example.org"; + + UriLinkifier.linkifyText(text, outputBuffer); + + assertEquals(text, outputBuffer.toString()); + } + + @Test + public void uriNotPrecededByValidSeparatorFollowedByValidUri() { + String text = "myhttp: http://example.org"; + + UriLinkifier.linkifyText(text, outputBuffer); + + assertEquals("myhttp: http://example.org", outputBuffer.toString()); + } } diff --git a/k9mail/src/test/java/com/fsck/k9/message/html/UriParserTestHelper.java b/k9mail/src/test/java/com/fsck/k9/message/html/UriParserTestHelper.java new file mode 100644 index 000000000..a57ea4b48 --- /dev/null +++ b/k9mail/src/test/java/com/fsck/k9/message/html/UriParserTestHelper.java @@ -0,0 +1,38 @@ +package com.fsck.k9.message.html; + + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + + +public class UriParserTestHelper { + public static void assertContainsLink(String expected, StringBuffer actual) { + String linkifiedUri = actual.toString(); + Document document = Jsoup.parseBodyFragment(linkifiedUri); + Element anchorElement = document.select("a").first(); + assertNotNull("No element found", anchorElement); + assertEquals(expected, anchorElement.text()); + assertEquals(expected, anchorElement.attr("href")); + } + + public static void assertLinkOnly(String expected, StringBuffer actual) { + String linkifiedUri = actual.toString(); + Document document = Jsoup.parseBodyFragment(linkifiedUri); + Element anchorElement = document.select("a").first(); + assertNotNull("No element found", anchorElement); + assertEquals(expected, anchorElement.text()); + assertEquals(expected, anchorElement.attr("href")); + + assertAnchorElementIsSoleContent(document, anchorElement); + } + + private static void assertAnchorElementIsSoleContent(Document document, Element anchorElement) { + assertEquals(document.body(), anchorElement.parent()); + assertTrue(" element is surrounded by text", document.body().textNodes().isEmpty()); + } +}