Clean up URI parsing code and tests

This commit is contained in:
cketti 2017-03-17 03:17:16 +01:00
parent 0d3d9aab32
commit 0f9bc4867a
8 changed files with 370 additions and 292 deletions

View file

@ -5,9 +5,6 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
/**
* Parses and "linkifies" bitcoin links.
*/
class BitcoinUriParser implements UriParser { class BitcoinUriParser implements UriParser {
private static final Pattern BITCOIN_URI_PATTERN = private static final Pattern BITCOIN_URI_PATTERN =
Pattern.compile("bitcoin:[1-9a-km-zA-HJ-NP-Z]{27,34}(\\?[a-zA-Z0-9$\\-_.+!*'(),%:@&=]*)?"); 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) { public int linkifyUri(String text, int startPos, StringBuffer outputBuffer) {
Matcher matcher = BITCOIN_URI_PATTERN.matcher(text); Matcher matcher = BITCOIN_URI_PATTERN.matcher(text);
// Skip not matching uris
if (!matcher.find(startPos) || matcher.start() != startPos) { if (!matcher.find(startPos) || matcher.start() != startPos) {
return startPos; return startPos;
} }

View file

@ -10,8 +10,10 @@ import java.util.regex.Pattern;
/** /**
* Parses and "linkifies" http links. * Parses and "linkifies" http links.
* <p> * <p>
* 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 * This class is in parts inspired by OkHttp's
* but leaving out much of the parsing part. * <a href="https://github.com/square/okhttp/blob/master/okhttp/src/main/java/okhttp3/HttpUrl.java">HttpUrl</a>.
* But much of the parsing parts have been left out.
* </p>
*/ */
class HttpUriParser implements UriParser { class HttpUriParser implements UriParser {
// This string represent character group sub-delim as described in RFC 3986 // 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 = private static final Pattern IPv4_PATTERN =
Pattern.compile("(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})(:(\\d{0,5}))?"); Pattern.compile("(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})(:(\\d{0,5}))?");
@Override @Override
public int linkifyUri(String text, int startPos, StringBuffer outputBuffer) { public int linkifyUri(String text, int startPos, StringBuffer outputBuffer) {
int currentPos = startPos; int currentPos = startPos;
// Test scheme // Scheme
String shortScheme = text.substring(currentPos, Math.min(currentPos + 7, text.length())); String shortScheme = text.substring(currentPos, Math.min(currentPos + 7, text.length()));
String longScheme = text.substring(currentPos, Math.min(currentPos + 8, text.length())); String longScheme = text.substring(currentPos, Math.min(currentPos + 8, text.length()));
if (shortScheme.equalsIgnoreCase("https://")) { if (shortScheme.equalsIgnoreCase("https://")) {
@ -33,20 +36,17 @@ class HttpUriParser implements UriParser {
} else if (longScheme.equalsIgnoreCase("rtsp://")) { } else if (longScheme.equalsIgnoreCase("rtsp://")) {
currentPos += "rtsp://".length(); currentPos += "rtsp://".length();
} else { } else {
// Unsupported scheme
return startPos; return startPos;
} }
// Test authority // Authority
int authorityEnd = text.indexOf('/', currentPos); int authorityEnd = text.indexOf('/', currentPos);
if (authorityEnd == -1) { if (authorityEnd == -1) {
authorityEnd = text.length(); authorityEnd = text.length();
} }
// Authority: Take a look at user info if available
currentPos = matchUserInfoIfAvailable(text, currentPos, authorityEnd); currentPos = matchUserInfoIfAvailable(text, currentPos, authorityEnd);
// Authority: Take a look at host
if (!tryMatchDomainName(text, currentPos, authorityEnd) && if (!tryMatchDomainName(text, currentPos, authorityEnd) &&
!tryMatchIpv4Address(text, currentPos, authorityEnd, true) && !tryMatchIpv4Address(text, currentPos, authorityEnd, true) &&
!tryMatchIpv6Address(text, currentPos, authorityEnd)) { !tryMatchIpv6Address(text, currentPos, authorityEnd)) {
@ -54,24 +54,27 @@ class HttpUriParser implements UriParser {
} }
currentPos = authorityEnd; currentPos = authorityEnd;
// Test path // Path
if (currentPos < text.length() && text.charAt(currentPos) == '/') { if (currentPos < text.length() && text.charAt(currentPos) == '/') {
currentPos = matchUnreservedPCTEncodedSubDelimClassesGreedy(text, currentPos + 1, "/:@"); currentPos = matchUnreservedPCTEncodedSubDelimClassesGreedy(text, currentPos + 1, "/:@");
} }
// Test for query // Query
if (currentPos < text.length() && text.charAt(currentPos) == '?') { if (currentPos < text.length() && text.charAt(currentPos) == '?') {
currentPos = matchUnreservedPCTEncodedSubDelimClassesGreedy(text, currentPos + 1, ":@/?"); currentPos = matchUnreservedPCTEncodedSubDelimClassesGreedy(text, currentPos + 1, ":@/?");
} }
// Test for fragment. // Fragment
if (currentPos < text.length() && text.charAt(currentPos) == '#') { if (currentPos < text.length() && text.charAt(currentPos) == '#') {
currentPos = matchUnreservedPCTEncodedSubDelimClassesGreedy(text, currentPos + 1, ":@/?"); currentPos = matchUnreservedPCTEncodedSubDelimClassesGreedy(text, currentPos + 1, ":@/?");
} }
// Final link generation String httpUri = text.substring(startPos, currentPos);
String linkifiedUri = String.format("<a href=\"%1$s\">%1$s</a>", text.substring(startPos, currentPos)); outputBuffer.append("<a href=\"")
outputBuffer.append(linkifiedUri); .append(httpUri)
.append("\">")
.append(httpUri)
.append("</a>");
return currentPos; return currentPos;
} }
@ -89,7 +92,7 @@ class HttpUriParser implements UriParser {
} }
private boolean tryMatchDomainName(String text, int startPos, int authorityEnd) { 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 { try {
// Check for port // Check for port
int portPos = text.indexOf(':', startPos); int portPos = text.indexOf(':', startPos);
@ -143,7 +146,6 @@ class HttpUriParser implements UriParser {
return false; return false;
} }
// Validate segments
for (int i = 1; i <= 4; i++) { for (int i = 1; i <= 4; i++) {
int segment = Integer.parseInt(matcher.group(1)); int segment = Integer.parseInt(matcher.group(1));
if (segment > 255) { 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) { if (!portAllowed && matcher.group(5) != null) {
return false; return false;
} }
// Validate optional port
String portString = matcher.group(6); String portString = matcher.group(6);
if (portString != null && !portString.isEmpty()) { if (portString != null && !portString.isEmpty()) {
int port = Integer.parseInt(portString); int port = Integer.parseInt(portString);
@ -169,7 +169,6 @@ class HttpUriParser implements UriParser {
} }
private boolean tryMatchIpv6Address(String text, int startPos, int authorityEnd) { private boolean tryMatchIpv6Address(String text, int startPos, int authorityEnd) {
// General validation
if (text.codePointAt(startPos) != '[') { if (text.codePointAt(startPos) != '[') {
return false; return false;
} }

View file

@ -2,6 +2,7 @@ package com.fsck.k9.message.html;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -9,13 +10,7 @@ import java.util.regex.Pattern;
import android.text.TextUtils; import android.text.TextUtils;
/**
* Allows conversion of link in text to html link.
*/
public class UriLinkifier { 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 Pattern URI_SCHEME;
private static final Map<String, UriParser> SUPPORTED_URIS; private static final Map<String, UriParser> SUPPORTED_URIS;
private static final String SCHEME_SEPARATOR = " ("; private static final String SCHEME_SEPARATOR = " (";
@ -32,16 +27,8 @@ public class UriLinkifier {
URI_SCHEME = Pattern.compile(allSchemes, Pattern.CASE_INSENSITIVE); 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 public static void linkifyText(String text, StringBuffer outputBuffer) {
* <tt>outputBuffer</tt>. <tt>text</tt> 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) {
int currentPos = 0; int currentPos = 0;
Matcher matcher = URI_SCHEME.matcher(text); Matcher matcher = URI_SCHEME.matcher(text);
@ -51,31 +38,39 @@ public class UriLinkifier {
String textBeforeMatch = text.substring(currentPos, startPos); String textBeforeMatch = text.substring(currentPos, startPos);
outputBuffer.append(textBeforeMatch); outputBuffer.append(textBeforeMatch);
if (!textBeforeMatch.isEmpty() && if (!isPrecededByValidSeparator(textBeforeMatch)) {
!SCHEME_SEPARATOR.contains(textBeforeMatch.substring(textBeforeMatch.length() - 1))) {
outputBuffer.append(text.charAt(startPos)); outputBuffer.append(text.charAt(startPos));
currentPos = startPos + 1; currentPos = startPos + 1;
continue; continue;
} }
// Find responsible parser and let it do it's job String scheme = matcher.group().toLowerCase(Locale.US);
String scheme = matcher.group(); UriParser parser = SUPPORTED_URIS.get(scheme);
UriParser parser = SUPPORTED_URIS.get(scheme.toLowerCase());
int newPos = parser.linkifyUri(text, startPos, outputBuffer); int newPos = parser.linkifyUri(text, startPos, outputBuffer);
// Handle invalid uri, at least advance by one to prevent endless loop boolean uriWasNotLinkified = newPos <= startPos;
if (newPos <= startPos) { if (uriWasNotLinkified) {
outputBuffer.append(text.charAt(startPos)); outputBuffer.append(text.charAt(startPos));
currentPos++; currentPos++;
} else { } else {
currentPos = (newPos > currentPos) ? newPos : currentPos + 1; currentPos = (newPos > currentPos) ? newPos : currentPos + 1;
} }
if (currentPos >= text.length()) { if (currentPos >= text.length()) {
break; break;
} }
} }
// Copy rest String textAfterLastMatch = text.substring(currentPos);
outputBuffer.append(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);
} }
} }

View file

@ -1,15 +1,19 @@
package com.fsck.k9.message.html; package com.fsck.k9.message.html;
/**
* General framework to handle uris when parsing. Allows different handling depending on the scheme identifier.
*/
public interface UriParser { public interface UriParser {
/** /**
* Parse and linkify scheme specific uri beginning from given position. The result will be written to given buffer. * 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 text
* @param outputBuffer Buffer where linkified variant of uri is written to. * String to parse URI from.
* @return Index where parsed uri ends (first non-uri letter). Should be startPos or smaller if no valid uri was found. * @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); int linkifyUri(String text, int startPos, StringBuffer outputBuffer);
} }

View file

@ -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);
}
}

View file

@ -1,216 +1,155 @@
package com.fsck.k9.message.html; package com.fsck.k9.message.html;
import com.fsck.k9.K9RobolectricTestRunner;
import org.junit.Before;
import org.junit.Test; 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; import static junit.framework.Assert.assertEquals;
@RunWith(K9RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class HttpUriParserTest { public class HttpUriParserTest {
private HttpUriParser parser; private final HttpUriParser parser = new HttpUriParser();
private StringBuffer outputBuffer; private final StringBuffer outputBuffer = new StringBuffer();
@Before
public void setUp() { @Test
parser = new HttpUriParser(); public void simpleDomain() {
outputBuffer = new StringBuffer(); assertLinkify("http://www.google.com");
} }
@Test @Test
public void testSimpleDomain() { public void domainWithTrailingSlash() {
String text = "http://www.google.com"; assertLinkify("http://www.google.com/");
int endPos = parser.linkifyUri(text, 0, outputBuffer);
assertEquals(String.format("<a href=\"%1$s\">%1$s</a>", text), outputBuffer.toString());
assertEquals(text.length(), endPos);
} }
@Test @Test
public void testDomainWithTrailingSlash() { public void domainWithoutWww() {
String text = "http://www.google.com/"; assertLinkify("http://google.com/");
int endPos = parser.linkifyUri(text, 0, outputBuffer);
assertEquals(String.format("<a href=\"%1$s\">%1$s</a>", text), outputBuffer.toString());
assertEquals(text.length(), endPos);
} }
@Test @Test
public void testDomainWithoutWWW() { public void query() {
String text = "http://google.com/"; assertLinkify("http://google.com/give/me/?q=mode&c=information");
int endPos = parser.linkifyUri(text, 0, outputBuffer);
assertEquals(String.format("<a href=\"%1$s\">%1$s</a>", text), outputBuffer.toString());
assertEquals(text.length(), endPos);
} }
@Test @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/ "; String text = "http://google.com/ ";
int endPos = parser.linkifyUri(text, 0, outputBuffer); int endPos = parser.linkifyUri(text, 0, outputBuffer);
assertEquals("<a href=\"http://google.com/\">http://google.com/</a>", outputBuffer.toString());
assertLinkOnly("http://google.com/", outputBuffer);
assertEquals(text.length() - 1, endPos); assertEquals(text.length() - 1, endPos);
} }
@Test @Test
public void testDomainWithTrailingSpaceNewline() { public void domainWithTrailingNewline() {
String text = "http://google.com/ \n";
int endPos = parser.linkifyUri(text, 0, outputBuffer);
assertEquals("<a href=\"http://google.com/\">http://google.com/</a>", outputBuffer.toString());
assertEquals(text.length() - 2, endPos);
}
@Test
public void testDomainWithTrailingNewline() {
String text = "http://google.com/\n"; String text = "http://google.com/\n";
int endPos = parser.linkifyUri(text, 0, outputBuffer); int endPos = parser.linkifyUri(text, 0, outputBuffer);
assertEquals("<a href=\"http://google.com/\">http://google.com/</a>", outputBuffer.toString());
assertLinkOnly("http://google.com/", outputBuffer);
assertEquals(text.length() - 1, endPos); assertEquals(text.length() - 1, endPos);
} }
@Test @Test
public void testDomainsWithQueryAndFragment() { public void domainWithTrailingAngleBracket() {
String text = "http://google.com/give/me/?q=mode&c=information#only-the-best"; String text = "<http://google.com/>";
int endPos = parser.linkifyUri(text, 0, outputBuffer);
assertEquals(
"<a href=\"http://google.com/give/me/?q=mode&c=information#only-the-best\">http://google.com/give/me/?q=mode&c=information#only-the-best</a>",
outputBuffer.toString());
assertEquals(text.length(), endPos);
}
@Test int endPos = parser.linkifyUri(text, 1, outputBuffer);
public void testDomainsWithQuery() {
String text = "http://google.com/give/me/?q=mode&c=information";
int endPos = parser.linkifyUri(text, 0, outputBuffer);
assertEquals(
"<a href=\"http://google.com/give/me/?q=mode&c=information\">http://google.com/give/me/?q=mode&c=information</a>",
outputBuffer.toString());
assertEquals(text.length(), endPos);
}
@Test assertLinkOnly("http://google.com/", outputBuffer);
public void testDomainsWithFragment() {
String text = "http://google.com/give/me#only-the-best";
int endPos = parser.linkifyUri(text, 0, outputBuffer);
assertEquals(
"<a href=\"http://google.com/give/me#only-the-best\">http://google.com/give/me#only-the-best</a>",
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(
"<a href=\"http://google.com/give/me/?q=mode+c=information#only-the-best\">http://google.com/give/me/?q=mode+c=information#only-the-best</a>",
outputBuffer.toString());
assertEquals(text.length() - 1, endPos); assertEquals(text.length() - 1, endPos);
} }
@Test @Test
public void testIpv4Address() { public void uriInMiddleOfInput() throws Exception {
String text = "http://127.0.0.1"; String prefix = "prefix ";
int endPos = parser.linkifyUri(text, 0, outputBuffer); String uri = "http://google.com/";
assertEquals(String.format("<a href=\"%1$s\">%1$s</a>", text), outputBuffer.toString()); String text = prefix + uri;
assertEquals(text.length(), endPos);
parser.linkifyUri(text, prefix.length(), outputBuffer);
assertLinkOnly(uri, outputBuffer);
} }
@Test
public void testIpv4AddressWithTrailingSlash() { int linkify(String uri) {
String text = "http://127.0.0.1/"; return parser.linkifyUri(uri, 0, outputBuffer);
int endPos = parser.linkifyUri(text, 0, outputBuffer);
assertEquals(String.format("<a href=\"%1$s\">%1$s</a>", text), outputBuffer.toString());
assertEquals(text.length(), endPos);
} }
@Test void assertLinkify(String uri) {
public void testIpv4AddressWithEmptyPort() { linkify(uri);
String text = "http://127.0.0.1:"; assertLinkOnly(uri, outputBuffer);
int endPos = parser.linkifyUri(text, 0, outputBuffer);
assertEquals(String.format("<a href=\"%1$s\">%1$s</a>", 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("<a href=\"%1$s\">%1$s</a>", 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("<a href=\"%1$s\">%1$s</a>", 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("<a href=\"%1$s\">%1$s</a>", 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("<a href=\"%1$s\">%1$s</a>", 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("<a href=\"%1$s\">%1$s</a>", 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("<a href=\"%1$s\">%1$s</a>", 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("<a href=\"%1$s\">%1$s</a>", 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("<a href=\"%1$s\">%1$s</a>", 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("<a href=\"%1$s\">%1$s</a>", 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("<a href=\"%1$s\">%1$s</a>", text), outputBuffer.toString());
assertEquals(text.length(), endPos);
} }
} }

View file

@ -6,90 +6,113 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.robolectric.annotation.Config; import org.robolectric.annotation.Config;
import static com.fsck.k9.message.html.UriParserTestHelper.assertLinkOnly;
import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertEquals;
@RunWith(K9RobolectricTestRunner.class) @RunWith(K9RobolectricTestRunner.class)
@Config(manifest = Config.NONE) @Config(manifest = Config.NONE)
public class UriLinkifierTest { public class UriLinkifierTest {
@Test private StringBuffer outputBuffer = new StringBuffer();
public void testLinkifyBitcoinAndHttpUri() {
String text = "bitcoin:19W6QZkx8SYPG7BBCS7odmWGRxqRph5jFU http://example.com/";
StringBuffer outputBuffer = new StringBuffer();
UriLinkifier.linkifyText(text, outputBuffer);
assertEquals("<a href=\"bitcoin:19W6QZkx8SYPG7BBCS7odmWGRxqRph5jFU\">" +
"bitcoin:19W6QZkx8SYPG7BBCS7odmWGRxqRph5jFU" +
"</a> " +
"<a href=\"http://example.com/\">" +
"http://example.com/" +
"</a>", outputBuffer.toString());
}
@Test @Test
public void testSimpleHttpUri() { public void emptyText() {
String text = "http://www.google.com"; String text = "";
StringBuffer outputBuffer = new StringBuffer();
UriLinkifier.linkifyText(text, outputBuffer);
assertEquals("<a href=\"http://www.google.com\">http://www.google.com</a>", outputBuffer.toString());
}
@Test
public void testHttpUriWithTrailingSlash() {
String text = "http://www.google.com/";
StringBuffer outputBuffer = new StringBuffer();
UriLinkifier.linkifyText(text, outputBuffer); UriLinkifier.linkifyText(text, outputBuffer);
assertEquals("<a href=\"http://www.google.com/\">http://www.google.com/</a>",
outputBuffer.toString());
}
@Test
public void testHttpUriWithoutWWW() {
String text = "http://google.com/";
StringBuffer outputBuffer = new StringBuffer();
UriLinkifier.linkifyText(text, outputBuffer);
assertEquals("<a href=\"http://google.com/\">http://google.com/</a>", outputBuffer.toString());
}
@Test
public void testHttpUriWithTrailingSpace() {
String text = "http://google.com/ ";
StringBuffer outputBuffer = new StringBuffer();
UriLinkifier.linkifyText(text, outputBuffer);
assertEquals("<a href=\"http://google.com/\">http://google.com/</a> ", outputBuffer.toString());
}
@Test
public void testHttpUriWithTrailingSpaceNewline() {
String text = "http://google.com/ \n";
StringBuffer outputBuffer = new StringBuffer();
UriLinkifier.linkifyText(text, outputBuffer);
assertEquals("<a href=\"http://google.com/\">http://google.com/</a> \n",
outputBuffer.toString());
}
@Test
public void testHttpUriWithTrailingNewline() {
String text = "http://google.com/\n";
StringBuffer outputBuffer = new StringBuffer();
UriLinkifier.linkifyText(text, outputBuffer);
assertEquals("<a href=\"http://google.com/\">http://google.com/</a>\n", outputBuffer.toString());
}
@Test
public void testIgnorePartialHttpUriScheme() {
String text = "myhttp://example.org";
StringBuffer outputBuffer = new StringBuffer();
UriLinkifier.linkifyText(text, outputBuffer);
assertEquals(text, outputBuffer.toString()); assertEquals(text, outputBuffer.toString());
} }
@Test @Test
public void testPartialHttpUriSchemeWithSeparator() { public void textWithoutUri_shouldBeCopiedToOutputBuffer() {
String text = "(http://example.org"; String text = "some text here";
StringBuffer outputBuffer = new StringBuffer();
UriLinkifier.linkifyText(text, outputBuffer); 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(" <a href=\"http://example.org\">http://example.org</a>", outputBuffer.toString());
}
@Test
public void uriPrecededByOpeningParenthesis() {
String text = "(http://example.org";
UriLinkifier.linkifyText(text, outputBuffer);
assertEquals("(<a href=\"http://example.org\">http://example.org</a>", outputBuffer.toString()); assertEquals("(<a href=\"http://example.org\">http://example.org</a>", 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: <a href=\"http://example.org\">http://example.org</a>",
outputBuffer.toString());
}
@Test
public void uriWithTrailingText() {
String uri = "http://example.org/ is the best";
UriLinkifier.linkifyText(uri, outputBuffer);
assertEquals("<a href=\"http://example.org/\">http://example.org/</a> is the best", outputBuffer.toString());
}
@Test
public void uriEmbeddedInText() {
String uri = "prefix http://example.org/ suffix";
UriLinkifier.linkifyText(uri, outputBuffer);
assertEquals("prefix <a href=\"http://example.org/\">http://example.org/</a> suffix", outputBuffer.toString());
}
@Test
public void uriWithUppercaseScheme() {
String uri = "HTTP://example.org/";
UriLinkifier.linkifyText(uri, outputBuffer);
assertEquals("<a href=\"HTTP://example.org/\">HTTP://example.org/</a>", 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: <a href=\"http://example.org\">http://example.org</a>", outputBuffer.toString());
}
} }

View file

@ -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 <a> 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 <a> 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("<a> element is surrounded by text", document.body().textNodes().isEmpty());
}
}