diff --git a/ATTRIBUTION.md b/ATTRIBUTION.md index e3c7f1c01e..f60a3a7c0f 100644 --- a/ATTRIBUTION.md +++ b/ATTRIBUTION.md @@ -28,3 +28,4 @@ FairEmail uses: * [Disconnect's tracker protection lists](https://github.com/disconnectme/disconnect-tracking-protection). Copyright 2010-2020 Disconnect, Inc. [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International license](https://github.com/disconnectme/disconnect-tracking-protection/blob/master/LICENSE). * [Over-Scroll Support For Android's RecyclerView, ListView, GridView, ScrollView ...](https://github.com/EverythingMe/overscroll-decor). Copyright (c) 2015, DoAT Media Ltd. [BSD-2-Clause License](https://github.com/EverythingMe/overscroll-decor/blob/master/LICENSE) * [Compact Encoding Detection](https://github.com/google/compact_enc_det). Copyright 2016 Google Inc. [Apache License 2.0](https://github.com/google/compact_enc_det/blob/master/LICENSE). +* [POI-HMEF](https://poi.apache.org/components/hmef/index.html). Copyright © 2001-2020 The Apache Software Foundation. [Apache Software License v2](https://poi.apache.org/devel/guidelines.html#The+Licensing). diff --git a/app/build.gradle b/app/build.gradle index 6565c895d2..de1bb20c7b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -276,6 +276,7 @@ dependencies { def overscroll_version = "1.1.0" def appauth_version = "0.7.1" def jcharset_version = "2.1" + def apache_poi = "4.1.2" // https://developer.android.com/jetpack/androidx/releases/ @@ -436,4 +437,8 @@ dependencies { // http://www.freeutils.net/source/jcharset/ // https://mvnrepository.com/artifact/net.freeutils/jcharset implementation "net.freeutils:jcharset:$jcharset_version" + + // https://poi.apache.org/components/hmef/index.html + // https://mvnrepository.com/artifact/org.apache.poi/poi-scratchpad + implementation "org.apache.poi:poi-scratchpad:$apache_poi" } diff --git a/app/src/main/assets/ATTRIBUTION.md b/app/src/main/assets/ATTRIBUTION.md index e3c7f1c01e..f60a3a7c0f 100644 --- a/app/src/main/assets/ATTRIBUTION.md +++ b/app/src/main/assets/ATTRIBUTION.md @@ -28,3 +28,4 @@ FairEmail uses: * [Disconnect's tracker protection lists](https://github.com/disconnectme/disconnect-tracking-protection). Copyright 2010-2020 Disconnect, Inc. [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International license](https://github.com/disconnectme/disconnect-tracking-protection/blob/master/LICENSE). * [Over-Scroll Support For Android's RecyclerView, ListView, GridView, ScrollView ...](https://github.com/EverythingMe/overscroll-decor). Copyright (c) 2015, DoAT Media Ltd. [BSD-2-Clause License](https://github.com/EverythingMe/overscroll-decor/blob/master/LICENSE) * [Compact Encoding Detection](https://github.com/google/compact_enc_det). Copyright 2016 Google Inc. [Apache License 2.0](https://github.com/google/compact_enc_det/blob/master/LICENSE). +* [POI-HMEF](https://poi.apache.org/components/hmef/index.html). Copyright © 2001-2020 The Apache Software Foundation. [Apache Software License v2](https://poi.apache.org/devel/guidelines.html#The+Licensing). diff --git a/app/src/main/java/eu/faircode/email/MessageHelper.java b/app/src/main/java/eu/faircode/email/MessageHelper.java index 2b1d098715..6a632301e0 100644 --- a/app/src/main/java/eu/faircode/email/MessageHelper.java +++ b/app/src/main/java/eu/faircode/email/MessageHelper.java @@ -1940,6 +1940,10 @@ public class MessageHelper { throw new IllegalArgumentException("Attachment not found"); downloadAttachment(context, index, local); + + if (Helper.isTnef(local.type) || + ("application/octet-stream".equals(local.type) && "winmail.dat".equals(local.name))) + decodeTNEF(context, local); } void downloadAttachment(Context context, int index, EntityAttachment local) throws MessagingException, IOException { @@ -2058,6 +2062,87 @@ public class MessageHelper { } } + private void decodeTNEF(Context context, EntityAttachment local) { + try { + DB db = DB.getInstance(context); + int subsequence = 0; + + // https://poi.apache.org/components/hmef/index.html + File file = local.getFile(context); + org.apache.poi.hmef.HMEFMessage msg = new org.apache.poi.hmef.HMEFMessage(new FileInputStream(file)); + + String subject = msg.getSubject(); + if (!TextUtils.isEmpty(subject)) { + EntityAttachment attachment = new EntityAttachment(); + attachment.message = local.message; + attachment.sequence = local.sequence; + attachment.subsequence = ++subsequence; + attachment.name = "subject.txt"; + attachment.type = "text/plain"; + attachment.disposition = Part.ATTACHMENT; + attachment.id = db.attachment().insertAttachment(attachment); + + Helper.writeText(attachment.getFile(context), subject); + db.attachment().setDownloaded(attachment.id, (long) subject.length()); + } + + String body = msg.getBody(); + if (!TextUtils.isEmpty(body)) { + EntityAttachment attachment = new EntityAttachment(); + attachment.message = local.message; + attachment.sequence = local.sequence; + attachment.subsequence = ++subsequence; + attachment.name = "document.rtf"; + attachment.type = "application/rtf"; + attachment.disposition = Part.ATTACHMENT; + attachment.id = db.attachment().insertAttachment(attachment); + + Helper.writeText(attachment.getFile(context), body); + db.attachment().setDownloaded(attachment.id, (long) body.length()); + } + + for (org.apache.poi.hmef.Attachment at : msg.getAttachments()) { + EntityAttachment attachment = new EntityAttachment(); + attachment.message = local.message; + attachment.sequence = local.sequence; + attachment.subsequence = ++subsequence; + attachment.name = at.getLongFilename(); + attachment.type = Helper.guessMimeType(attachment.name); + attachment.disposition = Part.ATTACHMENT; + attachment.id = db.attachment().insertAttachment(attachment); + + byte[] data = at.getContents(); + try (OutputStream os = new FileOutputStream(attachment.getFile(context))) { + os.write(data); + } + + db.attachment().setDownloaded(attachment.id, (long) data.length); + } + + StringBuilder sb = new StringBuilder(); + for (org.apache.poi.hmef.attribute.TNEFAttribute attr : msg.getMessageAttributes()) + sb.append(attr.toString()).append("\r\n"); + for (org.apache.poi.hmef.attribute.MAPIAttribute attr : msg.getMessageMAPIAttributes()) + if (!org.apache.poi.hsmf.datatypes.MAPIProperty.RTF_COMPRESSED.equals(attr.getProperty())) + sb.append(attr.toString()).append("\r\n"); + if (sb.length() > 0) { + EntityAttachment attachment = new EntityAttachment(); + attachment.message = local.message; + attachment.sequence = local.sequence; + attachment.subsequence = ++subsequence; + attachment.name = "attributes.txt"; + attachment.type = "text/plain"; + attachment.disposition = Part.ATTACHMENT; + attachment.id = db.attachment().insertAttachment(attachment); + + Helper.writeText(attachment.getFile(context), sb.toString()); + db.attachment().setDownloaded(attachment.id, (long) sb.length()); + } + } catch (Throwable ex) { + Log.w(ex); + } + } + String getWarnings(String existing) { if (existing != null) warnings.add(0, existing);