diff --git a/app/src/main/java/org/dystopia/email/AdapterMessage.java b/app/src/main/java/org/dystopia/email/AdapterMessage.java index 4eb27f75..da09c856 100644 --- a/app/src/main/java/org/dystopia/email/AdapterMessage.java +++ b/app/src/main/java/org/dystopia/email/AdapterMessage.java @@ -333,9 +333,9 @@ public class AdapterMessage extends PagedListAdapter() { + @Override + protected EntityMessage onLoad(Context context, Bundle args) throws Throwable { + long id = args.getLong("id"); + int requestCode = args.getInt("requestCode"); + Uri uri = args.getParcelable("uri"); + + EntityMessage draft = null; + DB db = DB.getInstance(context); + + try (Cursor cursor = context.getContentResolver().query( + uri, + new String[]{ + ContactsContract.CommonDataKinds.Email.ADDRESS, + ContactsContract.Contacts.DISPLAY_NAME + }, + null, null, null)) { + if (cursor != null && cursor.moveToFirst()) { + int colEmail = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.ADDRESS); + int colName = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME); + String email = MessageHelper.sanitizeEmail(cursor.getString(colEmail)); + String name = cursor.getString(colName); + + try { + db.beginTransaction(); + + draft = db.message().getMessage(id); + if (draft == null) + return null; + + Address[] address = null; + if (requestCode == ActivityCompose.REQUEST_CONTACT_TO) { + address = draft.to; + } else if (requestCode == ActivityCompose.REQUEST_CONTACT_CC) { + address = draft.cc; + } else if (requestCode == ActivityCompose.REQUEST_CONTACT_BCC) { + address = draft.bcc; + } + + List
list = new ArrayList<>(); + if (address != null) + list.addAll(Arrays.asList(address)); + + list.add(new InternetAddress(email, name, StandardCharsets.UTF_8.name())); + + if (requestCode == ActivityCompose.REQUEST_CONTACT_TO) { + draft.to = list.toArray(new Address[0]); + } else if (requestCode == ActivityCompose.REQUEST_CONTACT_CC) { + draft.cc = list.toArray(new Address[0]); + } else if (requestCode == ActivityCompose.REQUEST_CONTACT_BCC) { + draft.bcc = list.toArray(new Address[0]); + } + db.message().updateMessage(draft); + + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } } - InternetAddress address = new InternetAddress(email, name); - StringBuilder sb = new StringBuilder(text); - sb.append(address.toString().replace(",", "")).append(", "); + return draft; + } - if (requestCode == ActivityCompose.REQUEST_CONTACT_TO) { - etTo.setText(sb.toString()); - } else if (requestCode == ActivityCompose.REQUEST_CONTACT_CC) { - etCc.setText(sb.toString()); - } else if (requestCode == ActivityCompose.REQUEST_CONTACT_BCC) { - etBcc.setText(sb.toString()); + @Override + protected void onLoaded(Bundle args, EntityMessage draft) { + if (draft != null) { + etTo.setText(MessageHelper.getAddressesCompose(draft.to)); + etCc.setText(MessageHelper.getAddressesCompose(draft.cc)); + etBcc.setText(MessageHelper.getAddressesCompose(draft.bcc)); } } - } catch (Throwable ex) { - Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex)); - Helper.unexpectedError(getContext(), ex); - } finally { - if (cursor != null) { - cursor.close(); + + @Override + protected void onException(Bundle args, Throwable ex) { + Helper.unexpectedError(getContext(), ex); } - } + }.load(this, args); } private void handleAddAttachment(Intent data, final boolean image) { @@ -1121,7 +1157,7 @@ public class FragmentCompose extends FragmentEx { String from = Helper.canonicalAddress(((InternetAddress) ref.from[0]).getAddress()); Log.i( Helper.TAG, - "From=" + from + " to=" + MessageHelper.getFormattedAddresses(ref.to, false)); + "From=" + from + " to=" + MessageHelper.getFormattedAddresses(ref.to, null)); for (EntityIdentity identity : identities) { String email = Helper.canonicalAddress(identity.email); if (from.equals(email)) { @@ -1229,7 +1265,7 @@ public class FragmentCompose extends FragmentEx { String.format( "

%s %s:

%s
", Html.escapeHtml(new Date(time).toString()), - Html.escapeHtml(MessageHelper.getFormattedAddresses(draft.to, true)), + Html.escapeHtml(MessageHelper.getFormattedAddresses(draft.to, MessageHelper.ADDRESS_FULL)), HtmlHelper.sanitize(ref.read(context))); } else if ("forward".equals(action)) { draft.subject = context.getString(R.string.title_subject_forward, ref.subject); @@ -1237,7 +1273,7 @@ public class FragmentCompose extends FragmentEx { String.format( "

%s %s:

%s
", Html.escapeHtml(new Date(time).toString()), - Html.escapeHtml(MessageHelper.getFormattedAddresses(ref.from, true)), + Html.escapeHtml(MessageHelper.getFormattedAddresses(ref.from, MessageHelper.ADDRESS_FULL)), HtmlHelper.sanitize(ref.read(context))); } @@ -1328,9 +1364,9 @@ public class FragmentCompose extends FragmentEx { setSubtitle(draft.account_name); - etTo.setText(MessageHelper.getFormattedAddresses(draft.to, true)); - etCc.setText(MessageHelper.getFormattedAddresses(draft.cc, true)); - etBcc.setText(MessageHelper.getFormattedAddresses(draft.bcc, true)); + etTo.setText(MessageHelper.getFormattedAddresses(draft.to, MessageHelper.ADDRESS_FULL)); + etCc.setText(MessageHelper.getFormattedAddresses(draft.cc, MessageHelper.ADDRESS_FULL)); + etBcc.setText(MessageHelper.getFormattedAddresses(draft.bcc, MessageHelper.ADDRESS_FULL)); etSubject.setText(draft.subject); etBody.setText(null); diff --git a/app/src/main/java/org/dystopia/email/MessageHelper.java b/app/src/main/java/org/dystopia/email/MessageHelper.java index c9c28306..0c70fc72 100644 --- a/app/src/main/java/org/dystopia/email/MessageHelper.java +++ b/app/src/main/java/org/dystopia/email/MessageHelper.java @@ -17,6 +17,7 @@ package org.dystopia.email; along with FairEmail. If not, see . Copyright 2018, Marcel Bokhorst (M66B) + Copyright 2018-2020, Distopico (dystopia project) and contributors */ import android.content.Context; @@ -38,6 +39,8 @@ import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Properties; +import java.util.regex.Pattern; + import javax.activation.DataHandler; import javax.activation.FileDataSource; import javax.activation.FileTypeMap; @@ -49,6 +52,7 @@ import javax.mail.MessagingException; import javax.mail.Multipart; import javax.mail.Part; import javax.mail.Session; +import javax.mail.internet.AddressException; import javax.mail.internet.ContentType; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeBodyPart; @@ -61,6 +65,10 @@ public class MessageHelper { private MimeMessage imessage; private String raw = null; + static final String ADDRESS_FULL = "full"; + static final String ADDRESS_NAME = "displayName"; + static final String ADDRESS_COMPOSE = "compose"; + static final int NETWORK_TIMEOUT = 60 * 1000; // milliseconds static Properties getSessionProperties(int auth_type, boolean insecure) { @@ -423,11 +431,13 @@ public class MessageHelper { * Get parsed email addresses. * * @param addresses list of email Address - * @param full true render the full format - * @param displayName true display name instead of email when is not 'full' - * @return email addresses as string + * @param formatType - the name type of format that will be perform , it can be + * {@link MessageHelper#ADDRESS_FULL} to display full email address and name + * {@link MessageHelper#ADDRESS_NAME} to only display name + * {@link MessageHelper#ADDRESS_COMPOSE} to display compose rfc822 format + * @return formatted addresses as string */ - static String getFormattedAddresses(Address[] addresses, boolean full, boolean displayName) { + static String getFormattedAddresses(Address[] addresses, String formatType) { if (addresses == null || addresses.length == 0) { return ""; } @@ -442,9 +452,25 @@ public class MessageHelper { } else { String email = a.getAddress(); personal = personal.replaceAll("[\\,\\<\\>]", ""); - if (full) { + + if (ADDRESS_COMPOSE.equals(formatType)) { + boolean quote = false; + personal = personal.replace("\"", ""); + for (int c = 0; c < personal.length(); c++) { + // https://tools.ietf.org/html/rfc822 + if ("()<>@,;:\\\".[]".indexOf(personal.charAt(c)) >= 0) { + quote = true; + break; + } + } + if (quote) { + personal = "\"" + personal + "\""; + } + } + + if (ADDRESS_FULL.equals(formatType) || ADDRESS_COMPOSE.equals(formatType)) { formatted.add(personal + " <" + email + ">"); - } else if (displayName) { + } else if (ADDRESS_NAME.equals(formatType)) { formatted.add(personal); } else { formatted.add(email); @@ -457,8 +483,35 @@ public class MessageHelper { return TextUtils.join(", ", formatted); } - static String getFormattedAddresses(Address[] addresses, boolean full) { - return getFormattedAddresses(addresses, full, true); + /** + * Get email addresses formatted and ready for email compose fields + * @param addresses - list of email address to perform format + * @return lists of emails addresses as string + */ + static String getAddressesCompose(Address[] addresses) { + String result = getFormattedAddresses(addresses, ADDRESS_COMPOSE); + if (!TextUtils.isEmpty(result)) + result += ", "; + return result; + } + + /** + * Sanitize/clean pre-format email address. + * e.g "User name " + * @param email - per-format address email + * @return lists of emails addresses as string + */ + static String sanitizeEmail(String email) { + if (Pattern.matches("<|>", email)) { + try { + InternetAddress address = new InternetAddress(email); + return address.getAddress(); + } catch (AddressException ignored) { + return email; + } + } + + return email; } String getHtml() throws MessagingException, IOException { diff --git a/app/src/main/java/org/dystopia/email/ServiceSynchronize.java b/app/src/main/java/org/dystopia/email/ServiceSynchronize.java index adf1fa79..a6727511 100644 --- a/app/src/main/java/org/dystopia/email/ServiceSynchronize.java +++ b/app/src/main/java/org/dystopia/email/ServiceSynchronize.java @@ -506,7 +506,7 @@ public class ServiceSynchronize extends LifecycleService { DateFormat df = SimpleDateFormat.getDateTimeInstance(SimpleDateFormat.SHORT, SimpleDateFormat.SHORT); StringBuilder sb = new StringBuilder(); for (EntityMessage message : messages) { - sb.append("").append(MessageHelper.getFormattedAddresses(message.from, false)).append(""); + sb.append("").append(MessageHelper.getFormattedAddresses(message.from, null)).append(""); if (!TextUtils.isEmpty(message.subject)) { sb.append(": ").append(message.subject); @@ -583,7 +583,7 @@ public class ServiceSynchronize extends LifecycleService { Notification.InboxStyle mstyle = new Notification.InboxStyle(); mbuilder.addExtras(mArgs).setSmallIcon(R.drawable.ic_mail_icon) - .setContentTitle(MessageHelper.getFormattedAddresses(message.from, true)).setContentIntent(piContent) + .setContentTitle(MessageHelper.getFormattedAddresses(message.from, MessageHelper.ADDRESS_FULL)).setContentIntent(piContent) .setDeleteIntent(piDelete).setSound(uri).setColor(groupColor) .setWhen(message.sent == null ? message.received : message.sent).setPriority(Notification.PRIORITY_DEFAULT) .setCategory(Notification.CATEGORY_STATUS).setVisibility(Notification.VISIBILITY_PRIVATE).setGroup(groupKey) @@ -598,7 +598,7 @@ public class ServiceSynchronize extends LifecycleService { mbuilder.setGroupAlertBehavior(Notification.GROUP_ALERT_CHILDREN); } - mstyle.setBigContentTitle(MessageHelper.getFormattedAddresses(message.from, false)).setSummaryText(accountName); + mstyle.setBigContentTitle(MessageHelper.getFormattedAddresses(message.from, null)).setSummaryText(accountName); mbuilder.setStyle(mstyle); notifications.add(mbuilder.build()); diff --git a/app/src/main/res/layout/fragment_account.xml b/app/src/main/res/layout/fragment_account.xml index d7a086e4..b2911d88 100644 --- a/app/src/main/res/layout/fragment_account.xml +++ b/app/src/main/res/layout/fragment_account.xml @@ -256,7 +256,7 @@ android:id="@+id/btnColor" android:layout_width="@dimen/color_pick" android:layout_height="@dimen/color_pick" - android:layout_gravity="center|right" + android:layout_gravity="center|end" android:layout_marginTop="@dimen/margin_lg" android:layout_marginEnd="@dimen/margin_sm" android:layout_marginBottom="@dimen/margin_lg"