diff --git a/FAQ.md b/FAQ.md index ae91404a7b..7b1e8e3fca 100644 --- a/FAQ.md +++ b/FAQ.md @@ -2863,6 +2863,10 @@ Please see [this FAQ](#user-content-faq163) for details. Since this is an experimental feature, my advice is to start with just one folder. +*Send user unknown (version 1.1477+)* + +Send a [Delivery Status Notification](https://tools.ietf.org/html/rfc3464) *User unknown* via the answer menu. +
diff --git a/app/src/main/java/eu/faircode/email/AdapterMessage.java b/app/src/main/java/eu/faircode/email/AdapterMessage.java index fa87c88082..3f250fc5b2 100644 --- a/app/src/main/java/eu/faircode/email/AdapterMessage.java +++ b/app/src/main/java/eu/faircode/email/AdapterMessage.java @@ -3349,7 +3349,8 @@ public class AdapterMessage extends RecyclerView.Adapter= Build.VERSION_CODES.O && !BuildConfig.DEBUG) diff --git a/app/src/main/java/eu/faircode/email/EntityMessage.java b/app/src/main/java/eu/faircode/email/EntityMessage.java index d41ca5e8cf..5d87f4c58a 100644 --- a/app/src/main/java/eu/faircode/email/EntityMessage.java +++ b/app/src/main/java/eu/faircode/email/EntityMessage.java @@ -104,6 +104,7 @@ public class EntityMessage implements Serializable { static final Integer DSN_NONE = 0; static final Integer DSN_RECEIPT = 1; + static final Integer DSN_USER_UNKNOWN = 2; static final Long SWIPE_ACTION_ASK = -1L; static final Long SWIPE_ACTION_SEEN = -2L; diff --git a/app/src/main/java/eu/faircode/email/FragmentCompose.java b/app/src/main/java/eu/faircode/email/FragmentCompose.java index bb81b2d640..e78c4075d9 100644 --- a/app/src/main/java/eu/faircode/email/FragmentCompose.java +++ b/app/src/main/java/eu/faircode/email/FragmentCompose.java @@ -1184,6 +1184,7 @@ public class FragmentCompose extends FragmentBase { args.putLong("account", a.getLong("account", -1)); args.putLong("identity", a.getLong("identity", -1)); args.putLong("reference", a.getLong("reference", -1)); + args.putInt("dsn", a.getInt("dsn", -1)); args.putSerializable("ics", a.getSerializable("ics")); args.putString("status", a.getString("status")); args.putBoolean("raw", a.getBoolean("raw", false)); @@ -3388,6 +3389,7 @@ public class FragmentCompose extends FragmentBase { String action = args.getString("action"); long id = args.getLong("id", -1); long reference = args.getLong("reference", -1); + int dsn = args.getInt("dsn", EntityMessage.DSN_RECEIPT); File ics = (File) args.getSerializable("ics"); String status = args.getString("status"); long answer = args.getLong("answer", -1); @@ -3609,7 +3611,7 @@ public class FragmentCompose extends FragmentBase { // References if ("reply".equals(action) || "reply_all".equals(action) || "list".equals(action) || - "receipt".equals(action) || + "dsn".equals(action) || "participation".equals(action)) { data.draft.references = (ref.references == null ? "" : ref.references + " ") + ref.msgid; data.draft.inreplyto = ref.msgid; @@ -3617,7 +3619,7 @@ public class FragmentCompose extends FragmentBase { if ("list".equals(action) && ref.list_post != null) data.draft.to = ref.list_post; - else if ("receipt".equals(action) && ref.receipt_to != null) + else if ("dsn".equals(action) && ref.receipt_to != null) data.draft.to = ref.receipt_to; else { // Prevent replying to self @@ -3667,8 +3669,8 @@ public class FragmentCompose extends FragmentBase { if ("reply_all".equals(action)) data.draft.cc = ref.getAllRecipients(data.identities, ref.account); - else if ("receipt".equals(action)) { - data.draft.dsn = EntityMessage.DSN_RECEIPT; + else if ("dsn".equals(action)) { + data.draft.dsn = dsn; data.draft.receipt_request = false; } @@ -3727,10 +3729,17 @@ public class FragmentCompose extends FragmentBase { } } else if ("list".equals(action)) { data.draft.subject = ref.subject; - } else if ("receipt".equals(action)) { - data.draft.subject = context.getString(R.string.title_receipt_subject, subject); + } else if ("dsn".equals(action)) { + if (EntityMessage.DSN_USER_UNKNOWN.equals(dsn)) + data.draft.subject = context.getString(R.string.title_user_unknown_subject); + else + data.draft.subject = context.getString(R.string.title_receipt_subject, subject); - String[] texts = Helper.getStrings(context, ref.language, R.string.title_receipt_text); + String[] texts; + if (EntityMessage.DSN_USER_UNKNOWN.equals(dsn)) + texts = new String[]{context.getString(R.string.title_user_unknown_text)}; + else + texts = Helper.getStrings(context, ref.language, R.string.title_receipt_text); for (int i = 0; i < texts.length; i++) { if (i > 0) document.body() @@ -3782,7 +3791,7 @@ public class FragmentCompose extends FragmentBase { if (ref.content && !"editasnew".equals(action) && !("list".equals(action) && TextUtils.isEmpty(s)) && - !"receipt".equals(action)) { + !"dsn".equals(action)) { // Reply/forward Element reply = document.createElement("div"); reply.attr("fairemail", "reference"); diff --git a/app/src/main/java/eu/faircode/email/FragmentMessages.java b/app/src/main/java/eu/faircode/email/FragmentMessages.java index cfbec0c52d..c6151c8dd8 100644 --- a/app/src/main/java/eu/faircode/email/FragmentMessages.java +++ b/app/src/main/java/eu/faircode/email/FragmentMessages.java @@ -2410,6 +2410,10 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. if (data.identities == null) data.identities = new ArrayList<>(); + final Context context = getContext(); + if (context == null) + return; + final Address[] to = message.replySelf(data.identities, message.account) ? message.to @@ -2419,13 +2423,17 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. int answers = args.getInt("answers"); - PopupMenuLifecycle popupMenu = new PopupMenuLifecycle(getContext(), getViewLifecycleOwner(), anchor); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + boolean experiments = prefs.getBoolean("experiments", false); + + PopupMenuLifecycle popupMenu = new PopupMenuLifecycle(context, getViewLifecycleOwner(), anchor); popupMenu.inflate(R.menu.popup_reply); popupMenu.getMenu().findItem(R.id.menu_reply_to_all).setVisible(recipients.length > 0); popupMenu.getMenu().findItem(R.id.menu_reply_list).setVisible(message.list_post != null); popupMenu.getMenu().findItem(R.id.menu_reply_receipt).setVisible(message.receipt_to != null); + popupMenu.getMenu().findItem(R.id.menu_reply_user_unknown).setVisible(experiments); popupMenu.getMenu().findItem(R.id.menu_new_message).setVisible(to != null && to.length > 0); - popupMenu.getMenu().findItem(R.id.menu_reply_answer).setVisible(answers != 0 || !ActivityBilling.isPro(getContext())); + popupMenu.getMenu().findItem(R.id.menu_reply_answer).setVisible(answers != 0 || !ActivityBilling.isPro(context)); popupMenu.getMenu().findItem(R.id.menu_reply_to_sender).setEnabled(message.content); popupMenu.getMenu().findItem(R.id.menu_reply_to_all).setEnabled(message.content); @@ -2446,7 +2454,7 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. @Override public boolean onMenuItemClick(MenuItem target) { if (target.getGroupId() == 1) { - startActivity(new Intent(getContext(), ActivityCompose.class) + startActivity(new Intent(context, ActivityCompose.class) .putExtra("action", "reply") .putExtra("reference", message.id) .putExtra("answer", target.getIntent().getLongExtra("id", -1))); @@ -2465,7 +2473,10 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. onMenuReply(message, "list", selected); return true; case R.id.menu_reply_receipt: - onMenuReply(message, "receipt"); + onMenuDsn(message, EntityMessage.DSN_RECEIPT); + return true; + case R.id.menu_reply_user_unknown: + onMenuDsn(message, EntityMessage.DSN_USER_UNKNOWN); return true; case R.id.menu_forward: onMenuReply(message, "forward"); @@ -2506,6 +2517,14 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. startActivity(reply); } + private void onMenuDsn(TupleMessageEx message, int type) { + Intent reply = new Intent(getContext(), ActivityCompose.class) + .putExtra("action", "dsn") + .putExtra("reference", message.id) + .putExtra("dsn", type); + startActivity(reply); + } + private void onMenuNew(TupleMessageEx message, Address[] to) { Intent reply = new Intent(getContext(), ActivityCompose.class) .putExtra("action", "new") diff --git a/app/src/main/java/eu/faircode/email/MessageHelper.java b/app/src/main/java/eu/faircode/email/MessageHelper.java index f45d574a9f..4817e6481b 100644 --- a/app/src/main/java/eu/faircode/email/MessageHelper.java +++ b/app/src/main/java/eu/faircode/email/MessageHelper.java @@ -573,6 +573,43 @@ public class MessageHelper { //headersPart.setDisposition(Part.INLINE); //report.addBodyPart(headersPart); + imessage.setContent(report); + return; + } else if (EntityMessage.DSN_USER_UNKNOWN.equals(message.dsn)) { + // https://tools.ietf.org/html/rfc3464 + Multipart report = new MimeMultipart("report; report-type=delivery-status"); + + String html = Helper.readText(message.getFile(context)); + String plainContent = HtmlHelper.getText(context, html); + + BodyPart plainPart = new MimeBodyPart(); + plainPart.setContent(plainContent, "text/plain; charset=" + Charset.defaultCharset().name()); + report.addBodyPart(plainPart); + + String from = null; + if (message.from != null && message.from.length > 0) + from = ((InternetAddress) message.from[0]).getAddress(); + + StringBuilder sb = new StringBuilder(); + sb.append("Reporting-MTA: dns;").append("dummy.faircode.eu").append("\r\n"); + sb.append("\r\n"); + + if (from != null) + sb.append("Final-Recipient: rfc822;").append(from).append("\r\n"); + + sb.append("Action: failed").append("\r\n"); + sb.append("Status: 5.1.1").append("\r\n"); // https://tools.ietf.org/html/rfc3463 + sb.append("Diagnostic-Code: smtp; 550 user unknown").append("\r\n"); + + MailDateFormat mdf = new MailDateFormat(); + mdf.setTimeZone(TimeZone.getTimeZone("UTC")); + sb.append("Last-Attempt-Date: ").append(mdf.format(message.received)).append("\r\n"); + + BodyPart dnsPart = new MimeBodyPart(); + dnsPart.setContent(sb.toString(), "message/delivery-status"); + dnsPart.setDisposition(Part.INLINE); + report.addBodyPart(dnsPart); + imessage.setContent(report); return; } diff --git a/app/src/main/res/menu/popup_reply.xml b/app/src/main/res/menu/popup_reply.xml index 932139f8ac..5075c4c4f1 100644 --- a/app/src/main/res/menu/popup_reply.xml +++ b/app/src/main/res/menu/popup_reply.xml @@ -16,6 +16,10 @@ android:id="@+id/menu_reply_receipt" android:title="@string/title_reply_receipt" /> + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b66b4593e7..b9ec6be043 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -887,11 +887,15 @@ Reply to all Reply to list Send read receipt + Send user unknown Reply with template Moving to %1$s (%2$d) Open with %1$s authentication failed + Delivery Status Notification (Failure) + Your message wasn\'t delivered because the address couldn\'t be found. + Read receipt: %1$s This read receipt only acknowledges that the message was displayed. There is no guarantee that the recipient has read the message contents.