Merge pull request #996

Extract code to decode mailto URIs

Fixes #964
This commit is contained in:
cketti 2016-01-13 11:02:28 +01:00
commit b30ee72d76
5 changed files with 357 additions and 67 deletions

View file

@ -35,7 +35,7 @@ dependencies {
testCompile 'org.robolectric:robolectric:3.0'
testCompile 'junit:junit:4.12'
testCompile 'org.mockito:mockito-all:1.10.19'
testCompile 'org.mockito:mockito-core:1.10.19'
}
android {

View file

@ -85,6 +85,7 @@ import com.fsck.k9.fragment.ProgressDialogFragment.CancelListener;
import com.fsck.k9.helper.Contacts;
import com.fsck.k9.helper.HtmlConverter;
import com.fsck.k9.helper.IdentityHelper;
import com.fsck.k9.helper.MailTo;
import com.fsck.k9.helper.SimpleTextWatcher;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.Address;
@ -815,8 +816,9 @@ public class MessageCompose extends K9Activity implements OnClickListener,
*/
if (intent.getData() != null) {
Uri uri = intent.getData();
if ("mailto".equals(uri.getScheme())) {
initializeFromMailto(uri);
if (MailTo.isMailTo(uri)) {
MailTo mailTo = MailTo.parse(uri);
initializeFromMailto(mailTo);
}
}
@ -2795,53 +2797,23 @@ public class MessageCompose extends K9Activity implements OnClickListener,
* When we are launched with an intent that includes a mailto: URI, we can actually
* gather quite a few of our message fields from it.
*
* @param mailtoUri
* The mailto: URI we use to initialize the message fields.
* @param mailTo
* The MailTo object we use to initialize message field
*/
private void initializeFromMailto(Uri mailtoUri) {
/*
* mailto URIs are not hierarchical. So calling getQueryParameters()
* will throw an UnsupportedOperationException. We avoid this by
* creating a new hierarchical dummy Uri object with the query
* parameters of the original URI.
*/
CaseInsensitiveParamWrapper uri = new CaseInsensitiveParamWrapper(
Uri.parse("foo://bar?" + mailtoUri.getEncodedQuery()));
private void initializeFromMailto(MailTo mailTo) {
recipientPresenter.initFromMailto(mailTo);
recipientPresenter.initFromMailto(mailtoUri, uri);
// Read subject from the "subject" parameter.
List<String> subject = uri.getQueryParameters("subject");
if (!subject.isEmpty()) {
mSubjectView.setText(subject.get(0));
String subject = mailTo.getSubject();
if (subject != null && !subject.isEmpty()) {
mSubjectView.setText(subject);
}
// Read message body from the "body" parameter.
List<String> body = uri.getQueryParameters("body");
if (!body.isEmpty()) {
mMessageContentView.setCharacters(body.get(0));
String body = mailTo.getBody();
if (body != null && !subject.isEmpty()) {
mMessageContentView.setCharacters(body);
}
}
static class CaseInsensitiveParamWrapper {
private final Uri uri;
private Set<String> mParamNames;
public CaseInsensitiveParamWrapper(Uri uri) {
this.uri = uri;
}
public List<String> getQueryParameters(String key) {
final List<String> params = new ArrayList<String>();
for (String paramName : uri.getQueryParameterNames()) {
if (paramName.equalsIgnoreCase(key)) {
params.addAll(uri.getQueryParameters(paramName));
}
}
return params;
}
}
private class SendMessageTask extends AsyncTask<Void, Void, Void> {
@Override

View file

@ -17,9 +17,9 @@ import android.view.Menu;
import com.fsck.k9.Account;
import com.fsck.k9.Identity;
import com.fsck.k9.R;
import com.fsck.k9.activity.MessageCompose.CaseInsensitiveParamWrapper;
import com.fsck.k9.activity.RecipientMvpView.CryptoStatusType;
import com.fsck.k9.helper.Contacts;
import com.fsck.k9.helper.MailTo;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Message;
@ -158,29 +158,10 @@ public class RecipientPresenter {
}
}
public void initFromMailto(Uri mailtoUri, CaseInsensitiveParamWrapper uri) {
String schemaSpecific = mailtoUri.getSchemeSpecificPart();
int end = schemaSpecific.indexOf('?');
if (end == -1) {
end = schemaSpecific.length();
}
// Extract the recipient's email address from the mailto URI if there's one.
String recipient = Uri.decode(schemaSpecific.substring(0, end));
// Read additional recipients from the "to" parameter.
List<String> to = uri.getQueryParameters("to");
if (recipient.length() != 0) {
to = new ArrayList<String>(to);
to.add(0, recipient);
}
addToAddresses(addressFromStringArray(to));
// Read carbon copy recipients from the "cc" parameter.
addCcAddresses(addressFromStringArray(uri.getQueryParameters("cc")));
// Read blind carbon copy recipients from the "bcc" parameter.
addBccAddresses(addressFromStringArray(uri.getQueryParameters("bcc")));
public void initFromMailto(MailTo mailTo) {
addToAddresses(mailTo.getTo());
addCcAddresses(mailTo.getCc());
addBccAddresses(mailTo.getBcc());
}
public void initFromSendOrViewIntent(Intent intent) {

View file

@ -0,0 +1,146 @@
package com.fsck.k9.helper;
import java.util.ArrayList;
import java.util.List;
import android.net.Uri;
import com.fsck.k9.mail.Address;
public final class MailTo {
private static final String MAILTO_SCHEME = "mailto";
private static final String TO = "to";
private static final String BODY = "body";
private static final String CC = "cc";
private static final String BCC = "bcc";
private static final String SUBJECT = "subject";
private static final Address[] EMPTY_ADDRESS_LIST = new Address[0];
private final Address[] toAddresses;
private final Address[] ccAddresses;
private final Address[] bccAddresses;
private final String subject;
private final String body;
public static boolean isMailTo(Uri uri) {
return uri != null && MAILTO_SCHEME.equals(uri.getScheme());
}
public static MailTo parse(Uri uri) throws NullPointerException, IllegalArgumentException {
if (uri == null || uri.toString() == null) {
throw new NullPointerException("Argument 'uri' must not be null");
}
if (!isMailTo(uri)) {
throw new IllegalArgumentException("Not a mailto scheme");
}
String schemaSpecific = uri.getSchemeSpecificPart();
int end = schemaSpecific.indexOf('?');
if (end == -1) {
end = schemaSpecific.length();
}
CaseInsensitiveParamWrapper params =
new CaseInsensitiveParamWrapper(Uri.parse("foo://bar?" + uri.getEncodedQuery()));
// Extract the recipient's email address from the mailto URI if there's one.
String recipient = Uri.decode(schemaSpecific.substring(0, end));
List<String> toList = params.getQueryParameters(TO);
if (recipient.length() != 0) {
toList.add(0, recipient);
}
List<String> ccList = params.getQueryParameters(CC);
List<String> bccList = params.getQueryParameters(BCC);
Address[] toAddresses = toAddressArray(toList);
Address[] ccAddresses = toAddressArray(ccList);
Address[] bccAddresses = toAddressArray(bccList);
String subject = getFirstParameterValue(params, SUBJECT);
String body = getFirstParameterValue(params, BODY);
return new MailTo(toAddresses, ccAddresses, bccAddresses, subject, body);
}
private static String getFirstParameterValue(CaseInsensitiveParamWrapper params, String paramName) {
List<String> paramValues = params.getQueryParameters(paramName);
return (paramValues.isEmpty()) ? null : paramValues.get(0);
}
private static Address[] toAddressArray(List<String> recipients) {
if (recipients.isEmpty()) {
return EMPTY_ADDRESS_LIST;
}
String addressList = toCommaSeparatedString(recipients);
return Address.parse(addressList);
}
private static String toCommaSeparatedString(List<String> list) {
StringBuilder stringBuilder = new StringBuilder();
for (String item : list) {
stringBuilder.append(item).append(',');
}
stringBuilder.setLength(stringBuilder.length() - 1);
return stringBuilder.toString();
}
private MailTo(Address[] toAddresses, Address[] ccAddresses, Address[] bccAddresses, String subject, String body) {
this.toAddresses = toAddresses;
this.ccAddresses = ccAddresses;
this.bccAddresses = bccAddresses;
this.subject = subject;
this.body = body;
}
public Address[] getTo() {
return toAddresses;
}
public Address[] getCc() {
return ccAddresses;
}
public Address[] getBcc() {
return bccAddresses;
}
public String getSubject() {
return subject;
}
public String getBody() {
return body;
}
static class CaseInsensitiveParamWrapper {
private final Uri uri;
public CaseInsensitiveParamWrapper(Uri uri) {
this.uri = uri;
}
public List<String> getQueryParameters(String key) {
List<String> params = new ArrayList<String>();
for (String paramName : uri.getQueryParameterNames()) {
if (paramName.equalsIgnoreCase(key)) {
params.addAll(uri.getQueryParameters(paramName));
}
}
return params;
}
}
}

View file

@ -0,0 +1,191 @@
package com.fsck.k9.helper;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import android.net.Uri;
import com.fsck.k9.helper.MailTo.CaseInsensitiveParamWrapper;
import com.fsck.k9.mail.Address;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE, sdk = 21)
public class MailToTest {
@Rule
public ExpectedException exception = ExpectedException.none();
@Test
public void testIsMailTo_validMailToURI() {
Uri uri = Uri.parse("mailto:nobody");
boolean result = MailTo.isMailTo(uri);
assertTrue(result);
}
@Test
public void testIsMailTo_invalidMailToUri() {
Uri uri = Uri.parse("http://example.org/");
boolean result = MailTo.isMailTo(uri);
assertFalse(result);
}
@SuppressWarnings("ConstantConditions")
@Test
public void testIsMailTo_nullArgument() {
Uri uri = null;
boolean result = MailTo.isMailTo(uri);
assertFalse(result);
}
@Test
public void parse_withNullArgument_shouldThrow() throws Exception {
exception.expect(NullPointerException.class);
exception.expectMessage("Argument 'uri' must not be null");
MailTo.parse(null);
}
@Test
public void parse_withoutMailtoUri_shouldThrow() throws Exception {
exception.expect(IllegalArgumentException.class);
exception.expectMessage("Not a mailto scheme");
Uri uri = Uri.parse("http://example.org/");
MailTo.parse(uri);
}
@Test
public void testGetTo_singleEmailAddress() {
Uri uri = Uri.parse("mailto:test@abc.com");
MailTo mailToHelper = MailTo.parse(uri);
Address[] emailAddressList = mailToHelper.getTo();
assertEquals(emailAddressList[0].getAddress(), "test@abc.com");
}
@Test
public void testGetTo_multipleEmailAddress() {
Uri uri = Uri.parse("mailto:test1@abc.com?to=test2@abc.com");
MailTo mailToHelper = MailTo.parse(uri);
Address[] emailAddressList = mailToHelper.getTo();
assertEquals(emailAddressList[0].getAddress(), "test1@abc.com");
assertEquals(emailAddressList[1].getAddress(), "test2@abc.com");
}
@Test
public void testGetCc_singleEmailAddress() {
Uri uri = Uri.parse("mailto:test1@abc.com?cc=test3@abc.com");
MailTo mailToHelper = MailTo.parse(uri);
Address[] emailAddressList = mailToHelper.getCc();
assertEquals(emailAddressList[0].getAddress(), "test3@abc.com");
}
@Test
public void testGetCc_multipleEmailAddress() {
Uri uri = Uri.parse("mailto:test1@abc.com?cc=test3@abc.com,test4@abc.com");
MailTo mailToHelper = MailTo.parse(uri);
Address[] emailAddressList = mailToHelper.getCc();
assertEquals(emailAddressList[0].getAddress(), "test3@abc.com");
assertEquals(emailAddressList[1].getAddress(), "test4@abc.com");
}
@Test
public void testGetBcc_singleEmailAddress() {
Uri uri = Uri.parse("mailto:?bcc=test3@abc.com");
MailTo mailToHelper = MailTo.parse(uri);
Address[] emailAddressList = mailToHelper.getBcc();
assertEquals(emailAddressList[0].getAddress(), "test3@abc.com");
}
@Test
public void testGetBcc_multipleEmailAddress() {
Uri uri = Uri.parse("mailto:?bcc=test3@abc.com&bcc=test4@abc.com");
MailTo mailToHelper = MailTo.parse(uri);
Address[] emailAddressList = mailToHelper.getBcc();
assertEquals(emailAddressList[0].getAddress(), "test3@abc.com");
assertEquals(emailAddressList[1].getAddress(), "test4@abc.com");
}
@Test
public void testGetSubject() {
Uri uri = Uri.parse("mailto:?subject=Hello");
MailTo mailToHelper = MailTo.parse(uri);
String subject = mailToHelper.getSubject();
assertEquals(subject, "Hello");
}
@Test
public void testGetBody() {
Uri uri = Uri.parse("mailto:?body=Test%20Body&something=else");
MailTo mailToHelper = MailTo.parse(uri);
String subject = mailToHelper.getBody();
assertEquals(subject, "Test Body");
}
@Test
public void testCaseInsensitiveParamWrapper() {
Uri uri = Uri.parse("scheme://authority?a=one&b=two&c=three");
CaseInsensitiveParamWrapper caseInsensitiveParamWrapper = new CaseInsensitiveParamWrapper(uri);
List<String> result = caseInsensitiveParamWrapper.getQueryParameters("b");
assertThat(Collections.singletonList("two"), is(result));
}
@Test
public void testCaseInsensitiveParamWrapper_multipleMatchingQueryParameters() {
Uri uri = Uri.parse("scheme://authority?xname=one&name=two&Name=Three&NAME=FOUR");
CaseInsensitiveParamWrapper caseInsensitiveParamWrapper = new CaseInsensitiveParamWrapper(uri);
List<String> result = caseInsensitiveParamWrapper.getQueryParameters("name");
assertThat(Arrays.asList("two", "Three", "FOUR"), is(result));
}
@Test
public void testCaseInsensitiveParamWrapper_withoutQueryParameters() {
Uri uri = Uri.parse("scheme://authority");
CaseInsensitiveParamWrapper caseInsensitiveParamWrapper = new CaseInsensitiveParamWrapper(uri);
List<String> result = caseInsensitiveParamWrapper.getQueryParameters("name");
assertThat(Collections.<String>emptyList(), is(result));
}
}