TextToHtml: Change UriParser interface
This commit is contained in:
parent
6767f41505
commit
ace7557a6c
13 changed files with 376 additions and 268 deletions
|
@ -4,26 +4,26 @@ 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 int linkifyUri(String text, int startPos, StringBuffer outputBuffer) {
|
||||
public UriMatch parseUri(@NotNull CharSequence text, int startPos) {
|
||||
Matcher matcher = BITCOIN_URI_PATTERN.matcher(text);
|
||||
|
||||
if (!matcher.find(startPos) || matcher.start() != startPos) {
|
||||
return startPos;
|
||||
return null;
|
||||
}
|
||||
|
||||
String bitcoinUri = matcher.group();
|
||||
outputBuffer.append("<a href=\"")
|
||||
.append(bitcoinUri)
|
||||
.append("\">")
|
||||
.append(bitcoinUri)
|
||||
.append("</a>");
|
||||
|
||||
return matcher.end();
|
||||
int startIndex = matcher.start();
|
||||
int endIndex = matcher.end();
|
||||
CharSequence uri = text.subSequence(startIndex, endIndex);
|
||||
return new UriMatch(startIndex, endIndex, uri);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,10 @@ 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
|
||||
|
@ -12,21 +16,18 @@ 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 int linkifyUri(String text, int startPos, StringBuffer outputBuffer) {
|
||||
public UriMatch parseUri(@NotNull CharSequence text, int startPos) {
|
||||
Matcher matcher = ETHEREUM_URI_PATTERN.matcher(text);
|
||||
|
||||
if (!matcher.find(startPos) || matcher.start() != startPos) {
|
||||
return startPos;
|
||||
return null;
|
||||
}
|
||||
|
||||
String ethereumURI = matcher.group();
|
||||
outputBuffer.append("<a href=\"")
|
||||
.append(ethereumURI)
|
||||
.append("\">")
|
||||
.append(ethereumURI)
|
||||
.append("</a>");
|
||||
|
||||
return matcher.end();
|
||||
int startIndex = matcher.start();
|
||||
int endIndex = matcher.end();
|
||||
CharSequence uri = text.subSequence(startIndex, endIndex);
|
||||
return new UriMatch(startIndex, endIndex, uri);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,9 +4,12 @@ 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 and "linkifies" http links.
|
||||
* Parses http/https/rtsp URIs
|
||||
* <p>
|
||||
* This class is in parts inspired by OkHttp's
|
||||
* <a href="https://github.com/square/okhttp/blob/master/okhttp/src/main/java/okhttp3/HttpUrl.java">HttpUrl</a>.
|
||||
|
@ -16,6 +19,7 @@ import java.util.regex.Pattern;
|
|||
class HttpUriParser implements UriParser {
|
||||
// This string represent character group sub-delim as described in RFC 3986
|
||||
private static final String SUB_DELIM = "!$&'()*+,;=";
|
||||
private static final Pattern SCHEME_PATTERN = Pattern.compile("(https?|rtsp)://", Pattern.CASE_INSENSITIVE);
|
||||
private static final Pattern DOMAIN_PATTERN =
|
||||
Pattern.compile("[\\da-z](?:[\\da-z-]*[\\da-z])*(?:\\.[\\da-z](?:[\\da-z-]*[\\da-z])*)*(?::(\\d{0,5}))?",
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
|
@ -23,27 +27,20 @@ class HttpUriParser implements UriParser {
|
|||
Pattern.compile("(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})(:(\\d{0,5}))?");
|
||||
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public int linkifyUri(String text, int startPos, StringBuffer outputBuffer) {
|
||||
int currentPos = startPos;
|
||||
|
||||
// Scheme
|
||||
String shortScheme = text.substring(currentPos, Math.min(currentPos + 7, text.length()));
|
||||
String longScheme = text.substring(currentPos, Math.min(currentPos + 8, text.length()));
|
||||
if (longScheme.equalsIgnoreCase("https://")) {
|
||||
currentPos += "https://".length();
|
||||
} else if (shortScheme.equalsIgnoreCase("http://")) {
|
||||
currentPos += "http://".length();
|
||||
} else if (shortScheme.equalsIgnoreCase("rtsp://")) {
|
||||
currentPos += "rtsp://".length();
|
||||
} else {
|
||||
return startPos;
|
||||
public UriMatch parseUri(@NotNull CharSequence text, int startPos) {
|
||||
Matcher schemeMatcher = SCHEME_PATTERN.matcher(text);
|
||||
if (!schemeMatcher.find(startPos) || schemeMatcher.start() != startPos) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int currentPos = schemeMatcher.end();
|
||||
|
||||
// Authority
|
||||
int matchedAuthorityEnd = tryMatchAuthority(text, currentPos);
|
||||
if (matchedAuthorityEnd == currentPos) {
|
||||
return startPos;
|
||||
return null;
|
||||
}
|
||||
currentPos = matchedAuthorityEnd;
|
||||
|
||||
|
@ -62,18 +59,12 @@ class HttpUriParser implements UriParser {
|
|||
currentPos = matchUnreservedPCTEncodedSubDelimClassesGreedy(text, currentPos + 1, ":@/?");
|
||||
}
|
||||
|
||||
String httpUri = text.substring(startPos, currentPos);
|
||||
outputBuffer.append("<a href=\"")
|
||||
.append(httpUri)
|
||||
.append("\">")
|
||||
.append(httpUri)
|
||||
.append("</a>");
|
||||
|
||||
return currentPos;
|
||||
CharSequence uri = text.subSequence(startPos, currentPos);
|
||||
return new UriMatch(startPos, currentPos, uri);
|
||||
}
|
||||
|
||||
private int tryMatchAuthority(String text, int startPos) {
|
||||
int authorityLimit = text.indexOf('/', startPos);
|
||||
private int tryMatchAuthority(CharSequence text, int startPos) {
|
||||
int authorityLimit = indexOf(text, '/', startPos);
|
||||
if (authorityLimit == -1) {
|
||||
authorityLimit = text.length();
|
||||
}
|
||||
|
@ -97,8 +88,8 @@ class HttpUriParser implements UriParser {
|
|||
return startPos;
|
||||
}
|
||||
|
||||
private int tryMatchUserInfo(String text, int startPos, int limit) {
|
||||
int userInfoEnd = text.indexOf('@', startPos);
|
||||
private int tryMatchUserInfo(CharSequence text, int startPos, int limit) {
|
||||
int userInfoEnd = indexOf(text, '@', startPos);
|
||||
if (userInfoEnd != -1 && userInfoEnd < limit) {
|
||||
if (matchUnreservedPCTEncodedSubDelimClassesGreedy(text, startPos, ":") != userInfoEnd) {
|
||||
// Illegal character in user info
|
||||
|
@ -109,7 +100,7 @@ class HttpUriParser implements UriParser {
|
|||
return startPos;
|
||||
}
|
||||
|
||||
private int tryMatchDomainName(String text, int startPos) {
|
||||
private int tryMatchDomainName(CharSequence text, int startPos) {
|
||||
try {
|
||||
Matcher matcher = DOMAIN_PATTERN.matcher(text);
|
||||
if (!matcher.find(startPos) || matcher.start() != startPos) {
|
||||
|
@ -130,7 +121,7 @@ class HttpUriParser implements UriParser {
|
|||
}
|
||||
}
|
||||
|
||||
private int tryMatchIpv4Address(String text, int startPos, boolean portAllowed) {
|
||||
private int tryMatchIpv4Address(CharSequence text, int startPos, boolean portAllowed) {
|
||||
Matcher matcher = IPv4_PATTERN.matcher(text);
|
||||
if (!matcher.find(startPos) || matcher.start() != startPos) {
|
||||
return startPos;
|
||||
|
@ -158,12 +149,12 @@ class HttpUriParser implements UriParser {
|
|||
return matcher.end();
|
||||
}
|
||||
|
||||
private int tryMatchIpv6Address(String text, int startPos) {
|
||||
if (startPos == text.length() || text.codePointAt(startPos) != '[') {
|
||||
private int tryMatchIpv6Address(CharSequence text, int startPos) {
|
||||
if (startPos == text.length() || text.charAt(startPos) != '[') {
|
||||
return startPos;
|
||||
}
|
||||
|
||||
int addressEnd = text.indexOf(']');
|
||||
int addressEnd = indexOf(text, ']', startPos);
|
||||
if (addressEnd == -1) {
|
||||
return startPos;
|
||||
}
|
||||
|
@ -174,13 +165,13 @@ class HttpUriParser implements UriParser {
|
|||
int endSegmentsCount = 0;
|
||||
|
||||
// Handle :: separator and segments in front of it
|
||||
int compressionPos = text.indexOf("::");
|
||||
int compressionPos = indexOf(text, "::", currentPos);
|
||||
boolean compressionEnabled = compressionPos != -1 && compressionPos < addressEnd;
|
||||
if (compressionEnabled) {
|
||||
while (currentPos < compressionPos) {
|
||||
// Check segment separator
|
||||
if (beginSegmentsCount > 0) {
|
||||
if (text.codePointAt(currentPos) != ':') {
|
||||
if (text.charAt(currentPos) != ':') {
|
||||
return startPos;
|
||||
} else {
|
||||
++currentPos;
|
||||
|
@ -204,7 +195,7 @@ class HttpUriParser implements UriParser {
|
|||
while (currentPos < addressEnd && (beginSegmentsCount + endSegmentsCount) < 8) {
|
||||
// Check segment separator
|
||||
if (endSegmentsCount > 0) {
|
||||
if (text.codePointAt(currentPos) != ':') {
|
||||
if (text.charAt(currentPos) != ':') {
|
||||
return startPos;
|
||||
} else {
|
||||
++currentPos;
|
||||
|
@ -212,7 +203,7 @@ class HttpUriParser implements UriParser {
|
|||
}
|
||||
|
||||
// Small look ahead, do not run into IPv4 tail (7 is IPv4 minimum length)
|
||||
int nextColon = text.indexOf(':', currentPos);
|
||||
int nextColon = indexOf(text, ':', currentPos);
|
||||
if ((nextColon == -1 || nextColon > addressEnd) && (addressEnd - currentPos) >= 7) {
|
||||
break;
|
||||
}
|
||||
|
@ -246,14 +237,14 @@ class HttpUriParser implements UriParser {
|
|||
}
|
||||
|
||||
// Check optional port
|
||||
if (currentPos == text.length() || text.codePointAt(currentPos) != ':') {
|
||||
if (currentPos == text.length() || text.charAt(currentPos) != ':') {
|
||||
return currentPos;
|
||||
}
|
||||
++currentPos;
|
||||
|
||||
int port = 0;
|
||||
for (; currentPos < text.length(); currentPos++) {
|
||||
int c = text.codePointAt(currentPos);
|
||||
int c = text.charAt(currentPos);
|
||||
if (c < '0' || c > '9') {
|
||||
break;
|
||||
}
|
||||
|
@ -262,21 +253,22 @@ class HttpUriParser implements UriParser {
|
|||
return (port <= 65535) ? currentPos : startPos;
|
||||
}
|
||||
|
||||
private int parse16BitHexSegment(String text, int startPos, int endPos) {
|
||||
private int parse16BitHexSegment(CharSequence text, int startPos, int endPos) {
|
||||
int currentPos = startPos;
|
||||
while (isHexDigit(text.codePointAt(currentPos)) && currentPos < endPos) {
|
||||
while (isHexDigit(text.charAt(currentPos)) && currentPos < endPos) {
|
||||
++currentPos;
|
||||
}
|
||||
|
||||
return currentPos;
|
||||
}
|
||||
|
||||
private int matchUnreservedPCTEncodedSubDelimClassesGreedy(String text, int startPos, String additionalCharacters) {
|
||||
private int matchUnreservedPCTEncodedSubDelimClassesGreedy(CharSequence text, int startPos,
|
||||
String additionalCharacters) {
|
||||
String allowedCharacters = SUB_DELIM + "-._~" + additionalCharacters;
|
||||
int currentPos;
|
||||
int shouldBeHex = 0;
|
||||
for (currentPos = startPos; currentPos < text.length(); currentPos++) {
|
||||
int c = text.codePointAt(currentPos);
|
||||
int c = text.charAt(currentPos);
|
||||
|
||||
if (isHexDigit(c)) {
|
||||
shouldBeHex = Math.max(shouldBeHex - 1, 0);
|
||||
|
@ -299,4 +291,35 @@ class HttpUriParser implements UriParser {
|
|||
private boolean isHexDigit(int c) {
|
||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9');
|
||||
}
|
||||
|
||||
private int indexOf(CharSequence text, char ch, int fromIndex) {
|
||||
for (int i = fromIndex, end = text.length(); i < end; i++) {
|
||||
if (text.charAt(i) == ch) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private int indexOf(CharSequence text, String str, int fromIndex) {
|
||||
char ch = str.charAt(0);
|
||||
for (int i = fromIndex, end = text.length(); i < end; i++) {
|
||||
if (text.charAt(i) == ch) {
|
||||
boolean found = true;
|
||||
for (int j = 1, strLen = str.length(); j < strLen; j++) {
|
||||
if (text.charAt(i + j) != str.charAt(j)) {
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
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;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
|
||||
public class UriLinkifier {
|
||||
private static final Pattern URI_SCHEME;
|
||||
private static final Map<String, UriParser> SUPPORTED_URIS;
|
||||
private static final String SCHEME_SEPARATORS = " (\\n>";
|
||||
private static final String ALLOWED_SEPARATORS_PATTERN = "(?:^|[" + SCHEME_SEPARATORS + "])";
|
||||
|
||||
static {
|
||||
SUPPORTED_URIS = new HashMap<>();
|
||||
SUPPORTED_URIS.put("ethereum:", new EthereumUriParser());
|
||||
SUPPORTED_URIS.put("bitcoin:", new BitcoinUriParser());
|
||||
UriParser httpParser = new HttpUriParser();
|
||||
SUPPORTED_URIS.put("http:", httpParser);
|
||||
SUPPORTED_URIS.put("https:", httpParser);
|
||||
SUPPORTED_URIS.put("rtsp:", httpParser);
|
||||
|
||||
String allSchemes = TextUtils.join("|", SUPPORTED_URIS.keySet());
|
||||
String pattern = ALLOWED_SEPARATORS_PATTERN + "(" + allSchemes + ")";
|
||||
URI_SCHEME = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE);
|
||||
}
|
||||
|
||||
|
||||
public static void linkifyText(String text, StringBuffer outputBuffer) {
|
||||
int currentPos = 0;
|
||||
Matcher matcher = URI_SCHEME.matcher(text);
|
||||
|
||||
while (matcher.find(currentPos)) {
|
||||
int startPos = matcher.start(1);
|
||||
|
||||
String textBeforeMatch = text.substring(currentPos, startPos);
|
||||
outputBuffer.append(textBeforeMatch);
|
||||
|
||||
String scheme = matcher.group(1).toLowerCase(Locale.US);
|
||||
UriParser parser = SUPPORTED_URIS.get(scheme);
|
||||
int newPos = parser.linkifyUri(text, startPos, outputBuffer);
|
||||
|
||||
boolean uriWasNotLinkified = newPos <= startPos;
|
||||
if (uriWasNotLinkified) {
|
||||
outputBuffer.append(text.charAt(startPos));
|
||||
currentPos = startPos + 1;
|
||||
} else {
|
||||
currentPos = (newPos > currentPos) ? newPos : currentPos + 1;
|
||||
}
|
||||
|
||||
if (currentPos >= text.length()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
String textAfterLastMatch = text.substring(currentPos);
|
||||
outputBuffer.append(textAfterLastMatch);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package com.fsck.k9.message.html
|
||||
|
||||
|
||||
@Deprecated("Helper to be able to transition to the new text to HTML conversion in smaller steps")
|
||||
object UriLinkifier {
|
||||
@JvmStatic
|
||||
fun linkifyText(text: String, html: StringBuffer) {
|
||||
val uriMatches = UriMatcher.findUris(text)
|
||||
|
||||
var currentIndex = 0
|
||||
uriMatches.forEach { uriMatch ->
|
||||
append(html, text, currentIndex, uriMatch.startIndex)
|
||||
|
||||
html.append("<a href=\"")
|
||||
html.append(uriMatch.uri)
|
||||
html.append("\">")
|
||||
html.append(uriMatch.uri)
|
||||
html.append("</a>")
|
||||
|
||||
currentIndex = uriMatch.endIndex
|
||||
}
|
||||
|
||||
append(html, text, currentIndex, text.length)
|
||||
}
|
||||
|
||||
private fun append(html: StringBuffer, text: String, startIndex: Int, endIndex: Int) {
|
||||
for (i in startIndex until endIndex) {
|
||||
html.append(text[i])
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package com.fsck.k9.message.html
|
||||
|
||||
data class UriMatch(
|
||||
val startIndex: Int,
|
||||
val endIndex: Int,
|
||||
val uri: CharSequence
|
||||
)
|
35
k9mail/src/main/java/com/fsck/k9/message/html/UriMatcher.kt
Normal file
35
k9mail/src/main/java/com/fsck/k9/message/html/UriMatcher.kt
Normal file
|
@ -0,0 +1,35 @@
|
|||
package com.fsck.k9.message.html
|
||||
|
||||
import java.util.*
|
||||
|
||||
object UriMatcher {
|
||||
private val SUPPORTED_URIS = { httpUriParser: HttpUriParser ->
|
||||
mapOf(
|
||||
"ethereum:" to EthereumUriParser(),
|
||||
"bitcoin:" to BitcoinUriParser(),
|
||||
"http:" to httpUriParser,
|
||||
"https:" to httpUriParser,
|
||||
"rtsp:" to httpUriParser
|
||||
)
|
||||
}.invoke(HttpUriParser())
|
||||
|
||||
// FIXME: Remove > once the text to HTML code has been replaced
|
||||
private const val SCHEME_SEPARATORS = " (\\n<>"
|
||||
private const val ALLOWED_SEPARATORS_PATTERN = "(?:^|[$SCHEME_SEPARATORS])"
|
||||
private val URI_SCHEME = Regex(
|
||||
"$ALLOWED_SEPARATORS_PATTERN(${ SUPPORTED_URIS.keys.joinToString("|") })",
|
||||
RegexOption.IGNORE_CASE
|
||||
)
|
||||
|
||||
|
||||
fun findUris(text: CharSequence): List<UriMatch> {
|
||||
return URI_SCHEME.findAll(text).map { matchResult ->
|
||||
val matchGroup = matchResult.groups[1]!!
|
||||
val startIndex = matchGroup.range.start
|
||||
val scheme = matchGroup.value.toLowerCase(Locale.ROOT)
|
||||
val parser = SUPPORTED_URIS[scheme] ?: throw AssertionError("Scheme not found: $scheme")
|
||||
|
||||
parser.parseUri(text, startIndex)
|
||||
}.filterNotNull().toList()
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
package com.fsck.k9.message.html;
|
||||
|
||||
|
||||
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 {@code startPos} or smaller if no valid
|
||||
* URI was found.
|
||||
*/
|
||||
int linkifyUri(String text, int startPos, StringBuffer outputBuffer);
|
||||
}
|
14
k9mail/src/main/java/com/fsck/k9/message/html/UriParser.kt
Normal file
14
k9mail/src/main/java/com/fsck/k9/message/html/UriParser.kt
Normal file
|
@ -0,0 +1,14 @@
|
|||
package com.fsck.k9.message.html
|
||||
|
||||
|
||||
internal interface UriParser {
|
||||
/**
|
||||
* Parse scheme specific URI beginning from given position.
|
||||
*
|
||||
* @param text String to parse URI from.
|
||||
* @param startPos Position where URI starts (first letter of scheme).
|
||||
*
|
||||
* @return [UriMatch] if a valid URI was found. `null` otherwise.
|
||||
*/
|
||||
fun parseUri(text: CharSequence, startPos: Int): UriMatch?
|
||||
}
|
|
@ -3,28 +3,28 @@ 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;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
|
||||
public class BitcoinUriParserTest {
|
||||
BitcoinUriParser parser = new BitcoinUriParser();
|
||||
StringBuffer outputBuffer = new StringBuffer();
|
||||
|
||||
|
||||
@Test
|
||||
public void basicBitcoinUri() throws Exception {
|
||||
assertLinkify("bitcoin:19W6QZkx8SYPG7BBCS7odmWGRxqRph5jFU");
|
||||
assertValidUri("bitcoin:19W6QZkx8SYPG7BBCS7odmWGRxqRph5jFU");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bitcoinUriWithAmount() throws Exception {
|
||||
assertLinkify("bitcoin:12A1MyfXbW6RhdRAZEqofac5jCQQjwEPBu?amount=1.2");
|
||||
assertValidUri("bitcoin:12A1MyfXbW6RhdRAZEqofac5jCQQjwEPBu?amount=1.2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bitcoinUriWithQueryParameters() throws Exception {
|
||||
assertLinkify("bitcoin:12A1MyfXbW6RhdRAZEqofac5jCQQjwEPBu?amount=1.2" +
|
||||
assertValidUri("bitcoin:12A1MyfXbW6RhdRAZEqofac5jCQQjwEPBu?amount=1.2" +
|
||||
"&message=Payment&label=Satoshi&extra=other-param");
|
||||
}
|
||||
|
||||
|
@ -34,51 +34,40 @@ public class BitcoinUriParserTest {
|
|||
String uri = "bitcoin:12A1MyfXbW6RhdRAZEqofac5jCQQjwEPBu?amount=1.2";
|
||||
String text = prefix + uri;
|
||||
|
||||
parser.linkifyUri(text, prefix.length(), outputBuffer);
|
||||
UriMatch uriMatch = parser.parseUri(text, prefix.length());
|
||||
|
||||
assertLinkOnly(uri, outputBuffer);
|
||||
assertUriMatch(uri, uriMatch, prefix.length());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidScheme() throws Exception {
|
||||
assertNotLinkify("bitcion:19W6QZkx8SYPG7BBCS7odmWGRxqRph5jFU");
|
||||
assertInvalidUri("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());
|
||||
assertInvalidUri("bitcoin:[invalid]");
|
||||
}
|
||||
|
||||
|
||||
int linkify(String uri) {
|
||||
return parser.linkifyUri(uri, 0, outputBuffer);
|
||||
private void assertValidUri(String uri) {
|
||||
UriMatch uriMatch = parser.parseUri(uri, 0);
|
||||
assertUriMatch(uri, uriMatch);
|
||||
}
|
||||
|
||||
void assertLinkify(String uri) {
|
||||
linkify(uri);
|
||||
assertLinkOnly(uri, outputBuffer);
|
||||
private void assertUriMatch(String uri, UriMatch uriMatch) {
|
||||
assertUriMatch(uri, uriMatch, 0);
|
||||
}
|
||||
|
||||
void assertNotLinkify(String text) {
|
||||
int newPos = linkify(text);
|
||||
assertEquals(0, newPos);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,28 +2,29 @@ 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;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
|
||||
public class EthereumUriParserTest {
|
||||
EthereumUriParser parser = new EthereumUriParser();
|
||||
StringBuffer outputBuffer = new StringBuffer();
|
||||
|
||||
|
||||
@Test
|
||||
public void basicEthereumUri() throws Exception {
|
||||
assertLinkify("ethereum:0xfdf1210fc262c73d0436236a0e07be419babbbc4");
|
||||
assertValidUri("ethereum:0xfdf1210fc262c73d0436236a0e07be419babbbc4");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ethereumUriWithValue() throws Exception {
|
||||
assertLinkify("ethereum:0xfdf1210fc262c73d0436236a0e07be419babbbc4?value=42");
|
||||
assertValidUri("ethereum:0xfdf1210fc262c73d0436236a0e07be419babbbc4?value=42");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ethereumUriWithQueryParameters() throws Exception {
|
||||
assertLinkify("ethereum:0xfdf1210fc262c73d0436236a0e07be419babbbc4?value=42" +
|
||||
assertValidUri("ethereum:0xfdf1210fc262c73d0436236a0e07be419babbbc4?value=42" +
|
||||
"&gas=100000&bytecode=0xa9059cbb0000000000000000000000000000000dead");
|
||||
}
|
||||
|
||||
|
@ -33,51 +34,40 @@ public class EthereumUriParserTest {
|
|||
String uri = "ethereum:0xfdf1210fc262c73d0436236a0e07be419babbbc4?value=42";
|
||||
String text = prefix + uri;
|
||||
|
||||
parser.linkifyUri(text, prefix.length(), outputBuffer);
|
||||
UriMatch uriMatch = parser.parseUri(text, prefix.length());
|
||||
|
||||
assertLinkOnly(uri, outputBuffer);
|
||||
assertUriMatch(uri, uriMatch, prefix.length());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidScheme() throws Exception {
|
||||
assertNotLinkify("ethereMU:0xfdf1210fc262c73d0436236a0e07be419babbbc4");
|
||||
assertInvalidUri("ethereMU:0xfdf1210fc262c73d0436236a0e07be419babbbc4");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidAddress() throws Exception {
|
||||
assertNotLinkify("ethereum:[invalid]");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidEthereumUri_shouldReturnStartingPosition() throws Exception {
|
||||
String uri = "ethereum:[invalid]";
|
||||
|
||||
int newPos = linkify(uri);
|
||||
|
||||
assertEquals(0, newPos);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidEthereumUri_shouldNotWriteToOutputBuffer() throws Exception {
|
||||
String uri = "ethereum:[invalid]";
|
||||
|
||||
linkify(uri);
|
||||
|
||||
assertEquals(0, outputBuffer.length());
|
||||
assertInvalidUri("ethereum:[invalid]");
|
||||
}
|
||||
|
||||
|
||||
int linkify(String uri) {
|
||||
return parser.linkifyUri(uri, 0, outputBuffer);
|
||||
private void assertValidUri(String uri) {
|
||||
UriMatch uriMatch = parser.parseUri(uri, 0);
|
||||
assertUriMatch(uri, uriMatch);
|
||||
}
|
||||
|
||||
void assertLinkify(String uri) {
|
||||
linkify(uri);
|
||||
assertLinkOnly(uri, outputBuffer);
|
||||
private void assertUriMatch(String uri, UriMatch uriMatch) {
|
||||
assertUriMatch(uri, uriMatch, 0);
|
||||
}
|
||||
|
||||
void assertNotLinkify(String text) {
|
||||
int newPos = linkify(text);
|
||||
assertEquals(0, newPos);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,180 +1,177 @@
|
|||
package com.fsck.k9.message.html;
|
||||
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import static com.fsck.k9.message.html.UriParserTestHelper.assertLinkOnly;
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
|
||||
public class HttpUriParserTest {
|
||||
private final HttpUriParser parser = new HttpUriParser();
|
||||
private final StringBuffer outputBuffer = new StringBuffer();
|
||||
|
||||
|
||||
@Test
|
||||
public void emptyUriIgnored() {
|
||||
assertLinkIgnored("http://");
|
||||
assertInvalidUri("http://");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emptyAuthorityIgnored() {
|
||||
assertLinkIgnored("http:///");
|
||||
assertInvalidUri("http:///");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simpleDomain() {
|
||||
assertLinkify("http://www.google.com");
|
||||
assertValidUri("http://www.google.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simpleDomainWithHttps() {
|
||||
assertLinkify("https://www.google.com");
|
||||
assertValidUri("https://www.google.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simpleRtspUri() {
|
||||
assertLinkify("rtsp://example.com/media.mp4");
|
||||
assertValidUri("rtsp://example.com/media.mp4");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidDomainIgnored() {
|
||||
assertLinkIgnored("http://-www.google.com");
|
||||
assertInvalidUri("http://-www.google.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void domainWithTrailingSlash() {
|
||||
assertLinkify("http://www.google.com/");
|
||||
assertValidUri("http://www.google.com/");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void domainWithUserInfo() {
|
||||
assertLinkify("http://test@google.com/");
|
||||
assertValidUri("http://test@google.com/");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void domainWithFullUserInfo() {
|
||||
assertLinkify("http://test:secret@google.com/");
|
||||
assertValidUri("http://test:secret@google.com/");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void domainWithoutWww() {
|
||||
assertLinkify("http://google.com/");
|
||||
assertValidUri("http://google.com/");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void query() {
|
||||
assertLinkify("http://google.com/give/me/?q=mode&c=information");
|
||||
assertValidUri("http://google.com/give/me/?q=mode&c=information");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fragment() {
|
||||
assertLinkify("http://google.com/give/me#only-the-best");
|
||||
assertValidUri("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");
|
||||
assertValidUri("http://google.com/give/me/?q=mode&c=information#only-the-best");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ipv4Address() {
|
||||
assertLinkify("http://127.0.0.1");
|
||||
assertValidUri("http://127.0.0.1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ipv4AddressWithTrailingSlash() {
|
||||
assertLinkify("http://127.0.0.1/");
|
||||
assertValidUri("http://127.0.0.1/");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ipv4AddressWithEmptyPort() {
|
||||
assertLinkify("http://127.0.0.1:");
|
||||
assertValidUri("http://127.0.0.1:");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ipv4AddressWithPort() {
|
||||
assertLinkify("http://127.0.0.1:524/");
|
||||
assertValidUri("http://127.0.0.1:524/");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ipv6Address() {
|
||||
assertLinkify("http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]");
|
||||
assertValidUri("http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ipv6AddressWithPort() {
|
||||
assertLinkify("http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80");
|
||||
assertValidUri("http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ipv6AddressWithTrailingSlash() {
|
||||
assertLinkify("http://[1080:0:0:0:8:800:200C:417A]/");
|
||||
assertValidUri("http://[1080:0:0:0:8:800:200C:417A]/");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ipv6AddressWithEndCompression() {
|
||||
assertLinkify("http://[3ffe:2a00:100:7031::1]");
|
||||
assertValidUri("http://[3ffe:2a00:100:7031::1]");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ipv6AddressWithBeginCompression() {
|
||||
assertLinkify("http://[1080::8:800:200C:417A]/");
|
||||
assertValidUri("http://[1080::8:800:200C:417A]/");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ipv6AddressWithCompressionPort() {
|
||||
assertLinkify("http://[::FFFF:129.144.52.38]:80/");
|
||||
assertValidUri("http://[::FFFF:129.144.52.38]:80/");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ipv6AddressWithPrependedCompression() {
|
||||
assertLinkify("http://[::192.9.5.5]/");
|
||||
assertValidUri("http://[::192.9.5.5]/");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ipv6AddressWithTrailingIp4AndPort() {
|
||||
assertLinkify("http://[::192.9.5.5]:80/");
|
||||
assertValidUri("http://[::192.9.5.5]:80/");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ipv6WithoutClosingSquareBracketIgnored() {
|
||||
assertLinkIgnored("http://[1080:0:0:0:8:80:200C:417A/");
|
||||
assertInvalidUri("http://[1080:0:0:0:8:80:200C:417A/");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ipv6InvalidClosingSquareBracketIgnored() {
|
||||
assertLinkIgnored("http://[1080:0:0:0:8:800:270C:417A/]");
|
||||
assertInvalidUri("http://[1080:0:0:0:8:800:270C:417A/]");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void domainWithTrailingSpace() {
|
||||
String text = "http://google.com/ ";
|
||||
|
||||
int endPos = parser.linkifyUri(text, 0, outputBuffer);
|
||||
UriMatch uriMatch = parser.parseUri(text, 0);
|
||||
|
||||
assertLinkOnly("http://google.com/", outputBuffer);
|
||||
assertEquals(text.length() - 1, endPos);
|
||||
assertUriMatch("http://google.com/", uriMatch);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void domainWithTrailingNewline() {
|
||||
String text = "http://google.com/\n";
|
||||
|
||||
int endPos = parser.linkifyUri(text, 0, outputBuffer);
|
||||
UriMatch uriMatch = parser.parseUri(text, 0);
|
||||
|
||||
assertLinkOnly("http://google.com/", outputBuffer);
|
||||
assertEquals(text.length() - 1, endPos);
|
||||
assertUriMatch("http://google.com/", uriMatch);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void domainWithTrailingAngleBracket() {
|
||||
String text = "<http://google.com/>";
|
||||
|
||||
int endPos = parser.linkifyUri(text, 1, outputBuffer);
|
||||
UriMatch uriMatch = parser.parseUri(text, 1);
|
||||
|
||||
assertLinkOnly("http://google.com/", outputBuffer);
|
||||
assertEquals(text.length() - 1, endPos);
|
||||
assertUriMatch("http://google.com/", uriMatch, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -183,9 +180,9 @@ public class HttpUriParserTest {
|
|||
String uri = "http://google.com/";
|
||||
String text = prefix + uri;
|
||||
|
||||
parser.linkifyUri(text, prefix.length(), outputBuffer);
|
||||
UriMatch uriMatch = parser.parseUri(text, prefix.length());
|
||||
|
||||
assertLinkOnly(uri, outputBuffer);
|
||||
assertUriMatch("http://google.com/", uriMatch, prefix.length());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -195,25 +192,30 @@ public class HttpUriParserTest {
|
|||
String postfix = " postfix";
|
||||
String text = prefix + uri + postfix;
|
||||
|
||||
parser.linkifyUri(text, prefix.length(), outputBuffer);
|
||||
UriMatch uriMatch = parser.parseUri(text, prefix.length());
|
||||
|
||||
assertLinkOnly(uri, outputBuffer);
|
||||
assertUriMatch("http://google.com/", uriMatch, prefix.length());
|
||||
}
|
||||
|
||||
|
||||
int linkify(String uri) {
|
||||
return parser.linkifyUri(uri, 0, outputBuffer);
|
||||
private void assertValidUri(String uri) {
|
||||
UriMatch uriMatch = parser.parseUri(uri, 0);
|
||||
assertUriMatch(uri, uriMatch);
|
||||
}
|
||||
|
||||
void assertLinkify(String uri) {
|
||||
linkify(uri);
|
||||
assertLinkOnly(uri, outputBuffer);
|
||||
private void assertUriMatch(String uri, UriMatch uriMatch) {
|
||||
assertUriMatch(uri, uriMatch, 0);
|
||||
}
|
||||
|
||||
void assertLinkIgnored(String uri) {
|
||||
int endPos = linkify(uri);
|
||||
private void assertUriMatch(String uri, UriMatch uriMatch, int offset) {
|
||||
assertNotNull(uriMatch);
|
||||
Assert.assertEquals(offset, uriMatch.getStartIndex());
|
||||
Assert.assertEquals(uri.length() + offset, uriMatch.getEndIndex());
|
||||
Assert.assertEquals(uri, uriMatch.getUri().toString());
|
||||
}
|
||||
|
||||
assertEquals("", outputBuffer.toString());
|
||||
assertEquals(0, endPos);
|
||||
private void assertInvalidUri(String uri) {
|
||||
UriMatch uriMatch = parser.parseUri(uri, 0);
|
||||
assertNull(uriMatch);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
package com.fsck.k9.message.html;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
|
||||
public class UriMatcherTest {
|
||||
@Test
|
||||
public void emptyText() {
|
||||
assertNoMatch("");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void textWithoutUri() {
|
||||
assertNoMatch("some text here");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simpleUri() {
|
||||
assertUrisFound("http://example.org", "http://example.org");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void uriPrecededBySpace() {
|
||||
assertUrisFound(" http://example.org", "http://example.org");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void uriPrecededByOpeningParenthesis() {
|
||||
assertUrisFound("(http://example.org", "http://example.org");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void uriPrecededBySomeText() {
|
||||
assertUrisFound("Check out my fantastic URI: http://example.org", "http://example.org");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void uriWithTrailingText() {
|
||||
assertUrisFound("http://example.org/ is the best", "http://example.org/");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void uriEmbeddedInText() {
|
||||
assertUrisFound("prefix http://example.org/ suffix", "http://example.org/");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void uriWithUppercaseScheme() {
|
||||
assertUrisFound("HTTP://example.org/", "HTTP://example.org/");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void uriNotPrecededByValidSeparator() {
|
||||
assertNoMatch("myhttp://example.org");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void uriNotPrecededByValidSeparatorFollowedByValidUri() {
|
||||
assertUrisFound("myhttp: http://example.org", "http://example.org");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void schemaMatchWithInvalidUriInMiddleOfTextFollowedByValidUri() {
|
||||
assertUrisFound("prefix http:42 http://example.org", "http://example.org");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleValidUrisInRow() {
|
||||
assertUrisFound("prefix http://uri1.example.org some text http://uri2.example.org/path postfix",
|
||||
"http://uri1.example.org", "http://uri2.example.org/path");
|
||||
}
|
||||
|
||||
|
||||
private void assertNoMatch(String text) {
|
||||
List<UriMatch> uriMatches = UriMatcher.INSTANCE.findUris(text);
|
||||
assertThat(uriMatches).isEmpty();
|
||||
}
|
||||
|
||||
private void assertUrisFound(String text, String... uris) {
|
||||
List<UriMatch> uriMatches = UriMatcher.INSTANCE.findUris(text);
|
||||
assertThat(uriMatches).hasSize(uris.length);
|
||||
|
||||
for (int i = 0, end = uris.length; i < end; i++) {
|
||||
String uri = uris[i];
|
||||
int startIndex = text.indexOf(uri);
|
||||
assertThat(startIndex).isNotEqualTo(-1);
|
||||
|
||||
UriMatch uriMatch = uriMatches.get(i);
|
||||
assertThat(uriMatch.getStartIndex()).isEqualTo(startIndex);
|
||||
assertThat(uriMatch.getEndIndex()).isEqualTo(startIndex + uri.length());
|
||||
assertThat(uriMatch.getUri()).isEqualTo(uri);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue