diff --git a/app/src/main/java/eu/faircode/email/MessageHelper.java b/app/src/main/java/eu/faircode/email/MessageHelper.java index 5da817404f..0c6670a3f5 100644 --- a/app/src/main/java/eu/faircode/email/MessageHelper.java +++ b/app/src/main/java/eu/faircode/email/MessageHelper.java @@ -1094,6 +1094,15 @@ public class MessageHelper { this.imessage = message; } + boolean isReport() { + try { + return imessage.isMimeType("multipart/report"); + } catch (Throwable ex) { + Log.w(ex); + return false; + } + } + boolean getSeen() throws MessagingException { return imessage.isSet(Flags.Flag.SEEN); } @@ -1336,25 +1345,17 @@ public class MessageHelper { } boolean subject_threading = prefs.getBoolean("subject_threading", false); - if (subject_threading) { - boolean dsn = false; - try { - dsn = imessage.isMimeType("multipart/report"); - } catch (Throwable ex) { - Log.w(ex); - } - if (!dsn) { - String sender = getSortKey(getFrom()); - String subject = getSubject(); - long since = new Date().getTime() - MAX_SUBJECT_AGE * 3600 * 1000L; - if (!TextUtils.isEmpty(sender) && !TextUtils.isEmpty(subject)) { - List subjects = db.message().getMessagesBySubject(account, sender, subject, since); - for (EntityMessage message : subjects) - if (!thread.equals(message.thread)) { - Log.w("Updating subject thread from " + message.thread + " to " + thread); - db.message().updateMessageThread(message.account, message.thread, thread, since); - } - } + if (subject_threading && !isReport()) { + String sender = getSortKey(getFrom()); + String subject = getSubject(); + long since = new Date().getTime() - MAX_SUBJECT_AGE * 3600 * 1000L; + if (!TextUtils.isEmpty(sender) && !TextUtils.isEmpty(subject)) { + List subjects = db.message().getMessagesBySubject(account, sender, subject, since); + for (EntityMessage message : subjects) + if (!thread.equals(message.thread)) { + Log.w("Updating subject thread from " + message.thread + " to " + thread); + db.message().updateMessageThread(message.account, message.thread, thread, since); + } } } @@ -2311,9 +2312,9 @@ public class MessageHelper { return "text/html".equalsIgnoreCase(contentType.getBaseType()); } - boolean isDSN() { - return ("message/delivery-status".equalsIgnoreCase(contentType.getBaseType()) || - "message/disposition-notification".equalsIgnoreCase(contentType.getBaseType())); + boolean isReport() { + String ct = contentType.getBaseType(); + return Report.isDeliveryStatus(ct) || Report.isDispositionNotification(ct); } } @@ -2581,11 +2582,13 @@ public class MessageHelper { } } } - } else if (h.isDSN()) { - DeliveryReport report = new DeliveryReport(result); + } else if (h.isReport()) { + Report report = new Report(h.contentType.getBaseType(), result); result = report.html; - if (report.isNonDelivery() && report.diag != null) - warnings.add(report.diag); + if (!report.isDelivered() && report.diagnostic != null) + warnings.add(report.diagnostic); + if (!report.isDisplayed() && report.disposition != null) + warnings.add(report.disposition); } else Log.w("Unexpected content type=" + h.contentType); @@ -3714,13 +3717,18 @@ public class MessageHelper { } } - static class DeliveryReport { + static class Report { + String type; + String reporter; String action; + String recipient; String status; - String diag; + String diagnostic; + String disposition; String html; - DeliveryReport(String content) { + Report(String type, String content) { + this.type = type; StringBuilder report = new StringBuilder(); report.append("
"); content = content.replaceAll("(\\r?\\n)+", "\n"); @@ -3740,17 +3748,38 @@ public class MessageHelper { .append(TextUtils.htmlEncode(value)) .append("
"); - // https://datatracker.ietf.org/doc/html/rfc3464#section-2.3 - switch (name) { - case "Action": - this.action = value; - break; - case "Status": - this.status = value; - break; - case "Diagnostic-Code": - this.diag = value; - break; + if (isDeliveryStatus(type)) { + // https://datatracker.ietf.org/doc/html/rfc3464#section-2.3 + switch (name) { + case "Reporting-MTA": + this.reporter = value; + break; + case "Action": + this.action = value; + break; + case "Final-Recipient": + this.recipient = value; + break; + case "Status": + this.status = value; + break; + case "Diagnostic-Code": + this.diagnostic = value; + break; + } + } else if (isDispositionNotification(type)) { + //https://datatracker.ietf.org/doc/html/rfc3798#section-3.2.6 + switch (name) { + case "Reporting-UA": + this.reporter = value; + break; + case "Original-Recipient": + this.recipient = value; + break; + case "Disposition": + this.disposition = value; + break; + } } } } catch (Throwable ex) { @@ -3761,12 +3790,35 @@ public class MessageHelper { this.html = report.toString(); } - boolean isDelivery() { + boolean isDelivered() { return ("delivered".equals(action) || "relayed".equals(action) || "expanded".equals(action)); } - boolean isNonDelivery() { - return ("failed".equals(action) || "delayed".equals(action)); + boolean isDisplayed() { + return isType("displayed"); + } + + boolean isDeleted() { + return isType("deleted"); + } + + private boolean isType(String t) { + // manual-action/MDN-sent-manually; displayed + if (disposition == null) + return false; + int semi = disposition.lastIndexOf(';'); + if (semi < 0) + return false; + String type = disposition.substring(semi + 1).trim(); + return t.equals(type); + } + + static boolean isDeliveryStatus(String type) { + return "message/delivery-status".equalsIgnoreCase(type); + } + + static boolean isDispositionNotification(String type) { + return "message/disposition-notification".equalsIgnoreCase(type); } } }