mirror of
https://github.com/thunderbird/thunderbird-android.git
synced 2024-09-20 12:12:15 +02:00
Merge pull request #6962 from thundernest/UserInputEmailAddressParser
Add `UserInputEmailAddressParser`
This commit is contained in:
commit
f649a6a7d9
@ -16,4 +16,5 @@ val viewModule = module {
|
||||
K9WebViewClient(clipboardManager = get(), attachmentResolver, onPageFinishedListener)
|
||||
}
|
||||
factory { WebViewClientFactory() }
|
||||
factory { UserInputEmailAddressParser() }
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ import android.widget.ListPopupWindow;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.fsck.k9.DI;
|
||||
import com.fsck.k9.K9;
|
||||
import com.fsck.k9.ui.R;
|
||||
import com.fsck.k9.activity.AlternateRecipientAdapter;
|
||||
@ -43,7 +44,6 @@ import com.fsck.k9.mail.Address;
|
||||
import com.fsck.k9.ui.compose.RecipientCircleImageView;
|
||||
import com.fsck.k9.view.RecipientSelectView.Recipient;
|
||||
import com.tokenautocomplete.TokenCompleteTextView;
|
||||
import org.apache.james.mime4j.util.CharsetUtil;
|
||||
import timber.log.Timber;
|
||||
import de.hdodenhof.circleimageview.CircleImageView;
|
||||
|
||||
@ -61,6 +61,8 @@ public class RecipientSelectView extends TokenCompleteTextView<Recipient> implem
|
||||
private static final int LOADER_ID_ALTERNATES = 1;
|
||||
|
||||
|
||||
private final UserInputEmailAddressParser emailAddressParser = DI.get(UserInputEmailAddressParser.class);
|
||||
|
||||
private RecipientAdapter adapter;
|
||||
@Nullable
|
||||
private String cryptoProvider;
|
||||
@ -171,17 +173,19 @@ public class RecipientSelectView extends TokenCompleteTextView<Recipient> implem
|
||||
|
||||
@Override
|
||||
protected Recipient defaultObject(String completionText) {
|
||||
Address[] parsedAddresses = Address.parse(completionText);
|
||||
if (!CharsetUtil.isASCII(completionText)) {
|
||||
try {
|
||||
List<Address> parsedAddresses = emailAddressParser.parse(completionText);
|
||||
|
||||
if (parsedAddresses.isEmpty()) {
|
||||
setError(getContext().getString(R.string.recipient_error_parse_failed));
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Recipient(parsedAddresses.get(0));
|
||||
} catch (NonAsciiEmailAddressException e) {
|
||||
setError(getContext().getString(R.string.recipient_error_non_ascii));
|
||||
return null;
|
||||
}
|
||||
if (parsedAddresses.length == 0 || parsedAddresses[0].getAddress() == null) {
|
||||
setError(getContext().getString(R.string.recipient_error_parse_failed));
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Recipient(parsedAddresses[0]);
|
||||
}
|
||||
|
||||
public void setLoaderManager(@Nullable LoaderManager loaderManager) {
|
||||
|
@ -0,0 +1,31 @@
|
||||
package com.fsck.k9.view
|
||||
|
||||
import com.fsck.k9.mail.Address
|
||||
import org.apache.james.mime4j.util.CharsetUtil
|
||||
|
||||
/**
|
||||
* Used to parse name & email address pairs entered by the user.
|
||||
*
|
||||
* TODO: Build a custom implementation that can deal with typical inputs from users who are not familiar with the
|
||||
* RFC 5322 address-list syntax. See (ignored) tests in `UserInputEmailAddressParserTest`.
|
||||
*/
|
||||
internal class UserInputEmailAddressParser {
|
||||
|
||||
@Throws(NonAsciiEmailAddressException::class)
|
||||
fun parse(input: String): List<Address> {
|
||||
return Address.parseUnencoded(input)
|
||||
.mapNotNull { address ->
|
||||
when {
|
||||
address.isIncomplete() -> null
|
||||
address.isNonAsciiAddress() -> throw NonAsciiEmailAddressException(address.address)
|
||||
else -> Address.parse(address.toEncodedString()).firstOrNull()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Address.isIncomplete() = hostname.isNullOrBlank()
|
||||
|
||||
private fun Address.isNonAsciiAddress() = !CharsetUtil.isASCII(address)
|
||||
}
|
||||
|
||||
internal class NonAsciiEmailAddressException(message: String) : Exception(message)
|
@ -0,0 +1,153 @@
|
||||
package com.fsck.k9.view
|
||||
|
||||
import assertk.assertFailure
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.containsExactly
|
||||
import assertk.assertions.isEmpty
|
||||
import assertk.assertions.isInstanceOf
|
||||
import com.fsck.k9.mail.Address
|
||||
import kotlin.test.Ignore
|
||||
import kotlin.test.Test
|
||||
|
||||
class UserInputEmailAddressParserTest {
|
||||
private val parser = UserInputEmailAddressParser()
|
||||
|
||||
@Test
|
||||
fun `plain email address`() {
|
||||
val addresses = parser.parse("user@domain.example")
|
||||
|
||||
assertThat(addresses).containsExactly(Address("user@domain.example"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `email address followed by space`() {
|
||||
val addresses = parser.parse("user@domain.example ")
|
||||
|
||||
assertThat(addresses).containsExactly(Address("user@domain.example"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `email address in angle brackets`() {
|
||||
val addresses = parser.parse("<user@domain.example>")
|
||||
|
||||
assertThat(addresses).containsExactly(Address("user@domain.example"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `simple name and address`() {
|
||||
val addresses = parser.parse("Name <user@domain.example>")
|
||||
|
||||
assertThat(addresses).containsExactly(Address("user@domain.example", "Name"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `name with quoted string and address`() {
|
||||
val addresses = parser.parse("\"Name\" <user@domain.example>")
|
||||
|
||||
assertThat(addresses).containsExactly(Address("user@domain.example", "Name"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `name with multiple words and address`() {
|
||||
val addresses = parser.parse("Firstname Lastname <user@domain.example>")
|
||||
|
||||
assertThat(addresses).containsExactly(Address("user@domain.example", "Firstname Lastname"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `name with non-ASCII characters and address`() {
|
||||
val addresses = parser.parse("Käthe Gehäusegröße <user@domain.example>")
|
||||
|
||||
assertThat(addresses).containsExactly(Address("user@domain.example", "Käthe Gehäusegröße"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `address with non-ASCII character in local part`() {
|
||||
assertFailure {
|
||||
parser.parse("müller@domain.example")
|
||||
}.isInstanceOf<NonAsciiEmailAddressException>()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `address with non-ASCII character in domain part`() {
|
||||
assertFailure {
|
||||
parser.parse("user@dömain.example")
|
||||
}.isInstanceOf<NonAsciiEmailAddressException>()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `multiple addresses separated by comma`() {
|
||||
val addresses = parser.parse("one@domain.example,<two@domain.example>")
|
||||
|
||||
assertThat(addresses).containsExactly(
|
||||
Address("one@domain.example"),
|
||||
Address("two@domain.example"),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Currently not supported")
|
||||
fun `multiple addresses separated by space`() {
|
||||
val addresses = parser.parse("one@domain.example two@domain.example")
|
||||
|
||||
assertThat(addresses).containsExactly(
|
||||
Address("one@domain.example"),
|
||||
Address("two@domain.example"),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `multiple addresses in angle brackets separated by space`() {
|
||||
val addresses = parser.parse("<one@domain.example>, <two@domain.example>")
|
||||
|
||||
assertThat(addresses).containsExactly(
|
||||
Address("one@domain.example"),
|
||||
Address("two@domain.example"),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `incomplete address should not return a result`() {
|
||||
val addresses = parser.parse("user")
|
||||
|
||||
assertThat(addresses).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `incomplete address ending in @ should not return a result`() {
|
||||
val addresses = parser.parse("user@")
|
||||
|
||||
assertThat(addresses).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `name and incomplete address should not return a result`() {
|
||||
val addresses = parser.parse("Name <user")
|
||||
|
||||
assertThat(addresses).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Currently not supported")
|
||||
fun `name followed by address not in angle brackets`() {
|
||||
val addresses = parser.parse("Firstname Lastname user@domain.example")
|
||||
|
||||
assertThat(addresses).containsExactly(Address("user@domain.example", "Firstname LastName"))
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Currently not supported")
|
||||
fun `name containing parenthesis`() {
|
||||
val addresses = parser.parse("Firstname (Nickname) Lastname <user@domain.example>")
|
||||
|
||||
assertThat(addresses).containsExactly(Address("user@domain.example", "Firstname (Nickname) LastName"))
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Currently not supported")
|
||||
fun `name containing double quotes in the middle`() {
|
||||
val addresses = parser.parse("Firstname \"Nickname\" Lastname <user@domain.example>")
|
||||
|
||||
assertThat(addresses).containsExactly(Address("user@domain.example", "Firstname \"Nickname\" LastName"))
|
||||
}
|
||||
}
|
@ -115,7 +115,8 @@ public class Address implements Serializable {
|
||||
for (Rfc822Token token : tokens) {
|
||||
String address = token.getAddress();
|
||||
if (!TextUtils.isEmpty(address)) {
|
||||
addresses.add(new Address(token.getAddress(), token.getName(), false));
|
||||
String name = TextUtils.isEmpty(token.getName()) ? null : token.getName();
|
||||
addresses.add(new Address(token.getAddress(), name, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user