Improved header highlighting

pull/214/head
M66B 7 months ago
parent 2c336e0924
commit b172e217b2

@ -87,7 +87,7 @@ public class ActivityDSN extends ActivityBase {
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putParcelable("uri", uri); args.putParcelable("uri", uri);
new SimpleTask<Result>() { new SimpleTask<Spanned>() {
@Override @Override
protected void onPreExecute(Bundle args) { protected void onPreExecute(Bundle args) {
pbWait.setVisibility(View.VISIBLE); pbWait.setVisibility(View.VISIBLE);
@ -99,35 +99,29 @@ public class ActivityDSN extends ActivityBase {
} }
@Override @Override
protected Result onExecute(Context context, Bundle args) throws Throwable { protected Spanned onExecute(Context context, Bundle args) throws Throwable {
Uri uri = args.getParcelable("uri"); Uri uri = args.getParcelable("uri");
NoStreamException.check(uri, context); NoStreamException.check(uri, context);
Result result = new Result();
ContentResolver resolver = context.getContentResolver(); ContentResolver resolver = context.getContentResolver();
try (InputStream is = resolver.openInputStream(uri)) { try (InputStream is = resolver.openInputStream(uri)) {
if (is == null) if (is == null)
throw new FileNotFoundException(uri.toString()); throw new FileNotFoundException(uri.toString());
ByteArrayOutputStream bos = new ByteArrayOutputStream(); ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] buffer = new byte[Helper.BUFFER_SIZE]; Helper.copy(is, bos);
int length;
while ((length = is.read(buffer)) != -1)
bos.write(buffer, 0, length);
String headers = MessageHelper.decodeMime(bos.toString(StandardCharsets.UTF_8.name()));
result.headers = HtmlHelper.highlightHeaders(context,
null, null, null, headers, false);
}
return result; String headers = bos.toString(StandardCharsets.UTF_8.name());
headers = MessageHelper.decodeMime(headers);
return HtmlHelper.highlightHeaders(context,
null, null, null, headers, false, false);
}
} }
@Override @Override
protected void onExecuted(Bundle args, Result result) { protected void onExecuted(Bundle args, Spanned result) {
tvHeaders.setText(result.headers); tvHeaders.setText(result);
grpReady.setVisibility(View.VISIBLE); grpReady.setVisibility(View.VISIBLE);
} }
@ -140,8 +134,4 @@ public class ActivityDSN extends ActivityBase {
} }
}.execute(this, args, "disposition:decode"); }.execute(this, args, "disposition:decode");
} }
private class Result {
Spanned headers;
}
} }

@ -328,7 +328,11 @@ public class ActivityEML extends ActivityBase {
result.structure = ssb; result.structure = ssb;
result.headers = HtmlHelper.highlightHeaders(context, result.headers = HtmlHelper.highlightHeaders(context,
helper.getFrom(), helper.getTo(), helper.getReceivedHeader(), helper.getHeaders(), false); helper.getFrom(),
helper.getTo(),
helper.getReceivedHeader(),
helper.getHeaders(),
false, false);
return result; return result;
} }

@ -2768,11 +2768,16 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
} }
if (show_headers && message.headers != null) { if (show_headers && message.headers != null) {
Spanned headers = HtmlHelper.highlightHeaders(context, SpannableStringBuilder ssb = HtmlHelper.highlightHeaders(context,
message.from, message.to, message.received, message.headers, message.from, message.to, message.received, message.headers,
message.blocklist != null && message.blocklist); message.blocklist != null && message.blocklist, true);
if (BuildConfig.DEBUG && headers instanceof SpannableStringBuilder) { if (BuildConfig.DEBUG) {
SpannableStringBuilder ssb = (SpannableStringBuilder) headers; float stroke = context.getResources().getDisplayMetrics().density;
ssb.append("\n\uFFFC"); // Object replacement character
ssb.setSpan(new LineSpan(colorSeparator, stroke, 0), ssb.length() - 1, ssb.length(), 0);
ssb.append('\n');
ssb.append('\n'); ssb.append('\n');
ssb.append("TLS=").append(message.tls == null ? "-" : (message.tls ? "✓" : "✗")); ssb.append("TLS=").append(message.tls == null ? "-" : (message.tls ? "✓" : "✗"));
ssb.append(" DKIM=").append(message.dkim == null ? "-" : (message.dkim ? "✓" : "✗")); ssb.append(" DKIM=").append(message.dkim == null ? "-" : (message.dkim ? "✓" : "✗"));
@ -2783,7 +2788,8 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
ssb.append(" BL=").append(message.blocklist == null ? "-" : (message.blocklist ? "✓" : "✗")); ssb.append(" BL=").append(message.blocklist == null ? "-" : (message.blocklist ? "✓" : "✗"));
ssb.append('\n'); ssb.append('\n');
} }
tvHeaders.setText(headers);
tvHeaders.setText(ssb);
ibCopyHeaders.setVisibility(View.VISIBLE); ibCopyHeaders.setVisibility(View.VISIBLE);
} else { } else {
tvHeaders.setText(null); tvHeaders.setText(null);

@ -2973,7 +2973,8 @@ public class HtmlHelper {
return ssb.toString(); return ssb.toString();
} }
static Spanned highlightHeaders(Context context, Address[] from, Address[] to, Long time, String headers, boolean blocklist) { static SpannableStringBuilder highlightHeaders(
Context context, Address[] from, Address[] to, Long time, String headers, boolean blocklist, boolean withReceived) {
SpannableStringBuilder ssb = new SpannableStringBuilderEx(headers.replaceAll("\\t", " ")); SpannableStringBuilder ssb = new SpannableStringBuilderEx(headers.replaceAll("\\t", " "));
int textColorLink = Helper.resolveColor(context, android.R.attr.textColorLink); int textColorLink = Helper.resolveColor(context, android.R.attr.textColorLink);
int colorVerified = Helper.resolveColor(context, R.attr.colorVerified); int colorVerified = Helper.resolveColor(context, R.attr.colorVerified);
@ -2991,157 +2992,155 @@ public class HtmlHelper {
index += line.length() + 1; index += line.length() + 1;
} }
ssb.append("\n\uFFFC"); // Object replacement character if (withReceived) {
ssb.setSpan(new LineSpan(colorSeparator, stroke, 0), ssb.length() - 1, ssb.length(), 0); ssb.append("\n\uFFFC"); // Object replacement character
ssb.append('\n'); ssb.setSpan(new LineSpan(colorSeparator, stroke, 0), ssb.length() - 1, ssb.length(), 0);
ssb.append('\n');
try {
// https://datatracker.ietf.org/doc/html/rfc2821#section-4.4
final DateFormat DTF = Helper.getDateTimeInstance(context, DateFormat.SHORT, DateFormat.MEDIUM);
MailDateFormat mdf = new MailDateFormat();
ByteArrayInputStream bis = new ByteArrayInputStream(headers.getBytes());
InternetHeaders iheaders = new InternetHeaders(bis, true);
Date tx = null;
String dh = iheaders.getHeader("Date", null);
try { try {
if (dh != null) // https://datatracker.ietf.org/doc/html/rfc2821#section-4.4
tx = mdf.parse(dh); final DateFormat DTF = Helper.getDateTimeInstance(context, DateFormat.SHORT, DateFormat.MEDIUM);
} catch (ParseException ex) {
Log.w(ex);
}
if (tx != null) { MailDateFormat mdf = new MailDateFormat();
ssb.append('\n'); ByteArrayInputStream bis = new ByteArrayInputStream(headers.getBytes());
int s = ssb.length(); InternetHeaders iheaders = new InternetHeaders(bis, true);
ssb.append(DTF.format(tx));
ssb.setSpan(new StyleSpan(Typeface.BOLD), s, ssb.length(), 0);
}
if (from != null) { Date tx = null;
ssb.append('\n');
int s = ssb.length();
ssb.append("from");
ssb.setSpan(new ForegroundColorSpan(textColorLink), s, ssb.length(), 0);
ssb.setSpan(new StyleSpan(Typeface.BOLD), s, ssb.length(), 0);
ssb.append(' ').append(MessageHelper.formatAddresses(from, true, false));
}
if (tx != null || from != null) String dh = iheaders.getHeader("Date", null);
ssb.append('\n'); try {
if (dh != null)
tx = mdf.parse(dh);
} catch (ParseException ex) {
Log.w(ex);
}
Date rx = null; if (tx != null) {
String[] received = iheaders.getHeader("Received");
if (received != null && received.length > 0) {
for (int i = received.length - 1; i >= 0; i--) {
ssb.append('\n'); ssb.append('\n');
String h = MimeUtility.unfold(received[i]); int s = ssb.length();
ssb.append(DTF.format(tx));
int semi = h.lastIndexOf(';'); ssb.setSpan(new StyleSpan(Typeface.BOLD), s, ssb.length(), 0);
if (semi > 0) { }
rx = mdf.parse(h, new ParsePosition(semi + 1));
h = h.substring(0, semi);
}
if (from != null) {
ssb.append('\n');
int s = ssb.length(); int s = ssb.length();
ssb.append('#').append(Integer.toString(received.length - i)); ssb.append("from");
if (rx != null) { ssb.setSpan(new ForegroundColorSpan(textColorLink), s, ssb.length(), 0);
ssb.append(' ').append(DTF.format(rx));
if (tx != null)
ssb.append(" \u0394")
.append(Helper.formatDuration(rx.getTime() - tx.getTime()));
}
ssb.setSpan(new StyleSpan(Typeface.BOLD), s, ssb.length(), 0); ssb.setSpan(new StyleSpan(Typeface.BOLD), s, ssb.length(), 0);
ssb.append(' ').append(MessageHelper.formatAddresses(from, true, false));
}
if (blocklist && i == received.length - 1) { if (tx != null || from != null)
Drawable d = ContextCompat.getDrawable(context, R.drawable.twotone_flag_24); ssb.append('\n');
int iconSize = context.getResources().getDimensionPixelSize(R.dimen.menu_item_icon_size); Date rx = null;
d.setBounds(0, 0, iconSize, iconSize); String[] received = iheaders.getHeader("Received");
d.setTint(colorWarning); if (received != null && received.length > 0) {
for (int i = received.length - 1; i >= 0; i--) {
ssb.append('\n');
String h = MimeUtility.unfold(received[i]);
ssb.append(" \uFFFC"); // Object replacement character int semi = h.lastIndexOf(';');
ssb.setSpan(new ImageSpan(d), ssb.length() - 1, ssb.length(), 0); if (semi > 0) {
rx = mdf.parse(h, new ParsePosition(semi + 1));
h = h.substring(0, semi);
}
if (!TextUtils.isEmpty(BuildConfig.MXTOOLBOX_URI)) { int s = ssb.length();
final String header = received[i]; ssb.append('#').append(Integer.toString(received.length - i));
ClickableSpan click = new ClickableSpan() { if (rx != null) {
@Override ssb.append(' ').append(DTF.format(rx));
public void onClick(@NonNull View widget) { if (tx != null)
DnsBlockList.show(widget.getContext(), header); ssb.append(" \u0394")
} .append(Helper.formatDuration(rx.getTime() - tx.getTime()));
};
ssb.setSpan(click, ssb.length() - 1, ssb.length(), 0);
} }
} ssb.setSpan(new StyleSpan(Typeface.BOLD), s, ssb.length(), 0);
ssb.append('\n'); if (blocklist && i == received.length - 1) {
Drawable d = ContextCompat.getDrawable(context, R.drawable.twotone_flag_24);
int iconSize = context.getResources().getDimensionPixelSize(R.dimen.menu_item_icon_size);
d.setBounds(0, 0, iconSize, iconSize);
d.setTint(colorWarning);
int j = 0; ssb.append(" \uFFFC"); // Object replacement character
boolean p = false; ssb.setSpan(new ImageSpan(d), ssb.length() - 1, ssb.length(), 0);
String[] w = h.split("\\s+");
while (j < w.length) { if (!TextUtils.isEmpty(BuildConfig.MXTOOLBOX_URI)) {
if (w[j].startsWith("(")) final String header = received[i];
p = true; ClickableSpan click = new ClickableSpan() {
@Override
if (j > 0) public void onClick(@NonNull View widget) {
ssb.append(' '); DnsBlockList.show(widget.getContext(), header);
}
s = ssb.length(); };
ssb.append(w[j]); ssb.setSpan(click, ssb.length() - 1, ssb.length(), 0);
if (!p && MessageHelper.RECEIVED_WORDS.contains(w[j].toLowerCase(Locale.ROOT))) { }
ssb.setSpan(new ForegroundColorSpan(textColorLink), s, ssb.length(), 0);
ssb.setSpan(new StyleSpan(Typeface.BOLD), s, ssb.length(), 0);
} }
if (w[j].endsWith(")")) ssb.append('\n');
p = false;
j++; int j = 0;
} boolean p = false;
String[] w = h.split("\\s+");
while (j < w.length) {
if (w[j].startsWith("("))
p = true;
if (j > 0)
ssb.append(' ');
s = ssb.length();
ssb.append(w[j]);
if (!p && MessageHelper.RECEIVED_WORDS.contains(w[j].toLowerCase(Locale.ROOT))) {
ssb.setSpan(new ForegroundColorSpan(textColorLink), s, ssb.length(), 0);
ssb.setSpan(new StyleSpan(Typeface.BOLD), s, ssb.length(), 0);
}
if (w[j].endsWith(")"))
p = false;
Boolean tls = MessageHelper.isTLS(h, i == received.length - 1); j++;
ssb.append(" TLS="); }
int t = ssb.length();
ssb.append(tls == null ? "?" : Boolean.toString(tls)); Boolean tls = MessageHelper.isTLS(h, i == received.length - 1);
if (tls != null) ssb.append(" TLS=");
ssb.setSpan(new ForegroundColorSpan(tls ? colorVerified : colorWarning), t, ssb.length(), 0); int t = ssb.length();
ssb.append(tls == null ? "?" : Boolean.toString(tls));
if (tls != null)
ssb.setSpan(new ForegroundColorSpan(tls ? colorVerified : colorWarning), t, ssb.length(), 0);
ssb.append("\n"); ssb.append("\n");
}
} }
}
if (time != null) { if (time != null) {
ssb.append('\n'); ssb.append('\n');
int s = ssb.length(); int s = ssb.length();
ssb.append(DTF.format(time)); ssb.append(DTF.format(time));
if (rx != null) if (rx != null)
ssb.append(" \u0394") ssb.append(" \u0394")
.append(Helper.formatDuration(time - rx.getTime())); .append(Helper.formatDuration(time - rx.getTime()));
ssb.setSpan(new StyleSpan(Typeface.BOLD), s, ssb.length(), 0); ssb.setSpan(new StyleSpan(Typeface.BOLD), s, ssb.length(), 0);
} }
if (to != null) { if (to != null) {
ssb.append('\n'); ssb.append('\n');
int s = ssb.length(); int s = ssb.length();
ssb.append("to"); ssb.append("to");
ssb.setSpan(new ForegroundColorSpan(textColorLink), s, ssb.length(), 0); ssb.setSpan(new ForegroundColorSpan(textColorLink), s, ssb.length(), 0);
ssb.setSpan(new StyleSpan(Typeface.BOLD), s, ssb.length(), 0); ssb.setSpan(new StyleSpan(Typeface.BOLD), s, ssb.length(), 0);
ssb.append(' ').append(MessageHelper.formatAddresses(to, true, false)); ssb.append(' ').append(MessageHelper.formatAddresses(to, true, false));
} }
if (time != null || to != null) if (time != null || to != null)
ssb.append('\n'); ssb.append('\n');
} catch (Throwable ex) { } catch (Throwable ex) {
Log.w(ex); Log.w(ex);
}
} }
ssb.append("\n\uFFFC"); // Object replacement character
ssb.setSpan(new LineSpan(colorSeparator, stroke, 0), ssb.length() - 1, ssb.length(), 0);
ssb.append('\n');
return ssb; return ssb;
} }

@ -239,7 +239,7 @@
android:id="@+id/vSeparatorBody" android:id="@+id/vSeparatorBody"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="1dp" android:layout_height="1dp"
android:layout_marginTop="3dp" android:layout_marginTop="12dp"
android:background="?attr/colorSeparator" android:background="?attr/colorSeparator"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
@ -249,7 +249,7 @@
android:id="@+id/tvBody" android:id="@+id/tvBody"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="3dp" android:layout_marginTop="12dp"
android:text="Body" android:text="Body"
android:textAppearance="@style/TextAppearance.AppCompat.Small" android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textIsSelectable="true" android:textIsSelectable="true"

Loading…
Cancel
Save