From ed35dddceb2ab445c82de9796878b9b6f038f2ed Mon Sep 17 00:00:00 2001 From: M66B Date: Mon, 29 Jul 2019 11:17:12 +0200 Subject: [PATCH] Refactoring --- .../email/BoundaryCallbackMessages.java | 24 +-- .../eu/faircode/email/ConnectionHelper.java | 120 ------------- app/src/main/java/eu/faircode/email/Core.java | 19 +- .../eu/faircode/email/FragmentAccount.java | 21 +-- .../eu/faircode/email/FragmentIdentity.java | 35 +--- .../eu/faircode/email/FragmentQuickSetup.java | 20 +-- .../java/eu/faircode/email/MailService.java | 165 ++++++++++++++++++ .../java/eu/faircode/email/ServiceSend.java | 37 +--- .../eu/faircode/email/ServiceSynchronize.java | 24 +-- 9 files changed, 211 insertions(+), 254 deletions(-) create mode 100644 app/src/main/java/eu/faircode/email/MailService.java diff --git a/app/src/main/java/eu/faircode/email/BoundaryCallbackMessages.java b/app/src/main/java/eu/faircode/email/BoundaryCallbackMessages.java index cbf3357e2a..9cee6934cb 100644 --- a/app/src/main/java/eu/faircode/email/BoundaryCallbackMessages.java +++ b/app/src/main/java/eu/faircode/email/BoundaryCallbackMessages.java @@ -40,7 +40,6 @@ import java.text.Normalizer; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Properties; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -51,7 +50,6 @@ import javax.mail.FolderClosedException; import javax.mail.Message; import javax.mail.MessageRemovedException; import javax.mail.MessagingException; -import javax.mail.Session; import javax.mail.UIDFolder; import javax.mail.search.BodyTerm; import javax.mail.search.FlagTerm; @@ -79,7 +77,7 @@ public class BoundaryCallbackMessages extends PagedList.BoundaryCallback messages = null; - private ConnectionHelper.ServiceHolder iservice = null; + private MailService iservice = null; private IMAPFolder ifolder = null; private Message[] imessages = null; @@ -265,23 +263,11 @@ public class BoundaryCallbackMessages extends PagedList.BoundaryCallback id = new LinkedHashMap<>(); - id.put("name", context.getString(R.string.app_name)); - id.put("version", BuildConfig.VERSION_NAME); - Map sid = getStore().id(id); - if (sid != null) { - Map crumb = new HashMap<>(); - for (String key : sid.keySet()) { - crumb.put(key, sid.get(key)); - EntityLog.log(context, "Server " + key + "=" + sid.get(key)); - } - Bugsnag.leaveBreadcrumb("server", BreadcrumbType.LOG, crumb); - } - } catch (MessagingException ex) { - Log.w(ex); - } - - } else if ("smtp".equals(protocol) || "smtps".equals(protocol)) { - iservice = issesion.getTransport(protocol); - iservice.connect(host, port, user, password); - } else - throw new NoSuchProviderException(protocol); - } - - Session getSession() { - return this.issesion; - } - - IMAPStore getStore() { - return (IMAPStore) this.iservice; - } - - SMTPTransport getTransport() { - return (SMTPTransport) this.iservice; - } - - public void close() throws MessagingException { - if (iservice != null) - iservice.close(); - } - } } diff --git a/app/src/main/java/eu/faircode/email/Core.java b/app/src/main/java/eu/faircode/email/Core.java index cc20a1fd97..c9c7e1ac36 100644 --- a/app/src/main/java/eu/faircode/email/Core.java +++ b/app/src/main/java/eu/faircode/email/Core.java @@ -76,6 +76,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Properties; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; @@ -126,7 +127,7 @@ class Core { static void processOperations( Context context, EntityAccount account, EntityFolder folder, - Session isession, Store istore, Folder ifolder, + Store istore, Folder ifolder, State state) throws MessagingException, JSONException, IOException { try { @@ -209,15 +210,15 @@ class Core { " msg=" + op.message + " args=" + op.args); else - onAdd(context, jargs, folder, message, isession, (IMAPStore) istore, (IMAPFolder) ifolder); + onAdd(context, jargs, folder, message, (IMAPStore) istore, (IMAPFolder) ifolder); break; case EntityOperation.MOVE: - onMove(context, jargs, false, folder, message, isession, (IMAPStore) istore, (IMAPFolder) ifolder); + onMove(context, jargs, false, folder, message, (IMAPStore) istore, (IMAPFolder) ifolder); break; case EntityOperation.COPY: - onMove(context, jargs, true, folder, message, isession, (IMAPStore) istore, (IMAPFolder) ifolder); + onMove(context, jargs, true, folder, message, (IMAPStore) istore, (IMAPFolder) ifolder); break; case EntityOperation.DELETE: @@ -460,7 +461,7 @@ class Core { } } - private static void onAdd(Context context, JSONArray jargs, EntityFolder folder, EntityMessage message, Session isession, IMAPStore istore, IMAPFolder ifolder) throws MessagingException, JSONException, IOException { + private static void onAdd(Context context, JSONArray jargs, EntityFolder folder, EntityMessage message, IMAPStore istore, IMAPFolder ifolder) throws MessagingException, JSONException, IOException { // Add message DB db = DB.getInstance(context); @@ -483,6 +484,9 @@ class Core { db.message().setMessageMsgId(message.id, message.msgid); } + Properties props = MessageHelper.getSessionProperties(null, false); + Session isession = Session.getInstance(props, null); + // Get raw message MimeMessage imessage; if (folder.id.equals(message.folder)) { @@ -577,7 +581,7 @@ class Core { } } - private static void onMove(Context context, JSONArray jargs, boolean copy, EntityFolder folder, EntityMessage message, Session isession, IMAPStore istore, IMAPFolder ifolder) throws JSONException, MessagingException, IOException { + private static void onMove(Context context, JSONArray jargs, boolean copy, EntityFolder folder, EntityMessage message, IMAPStore istore, IMAPFolder ifolder) throws JSONException, MessagingException, IOException { // Move message DB db = DB.getInstance(context); @@ -604,6 +608,9 @@ class Core { imessage.writeTo(os); } + Properties props = MessageHelper.getSessionProperties(null, false); + Session isession = Session.getInstance(props, null); + Message icopy; try (InputStream is = new BufferedInputStream(new FileInputStream(file))) { icopy = new MimeMessage(isession, is); diff --git a/app/src/main/java/eu/faircode/email/FragmentAccount.java b/app/src/main/java/eu/faircode/email/FragmentAccount.java index fcb06589e0..590407be8d 100644 --- a/app/src/main/java/eu/faircode/email/FragmentAccount.java +++ b/app/src/main/java/eu/faircode/email/FragmentAccount.java @@ -67,10 +67,8 @@ import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Objects; -import java.util.Properties; import javax.mail.Folder; -import javax.mail.Session; import static android.app.Activity.RESULT_OK; import static com.google.android.material.textfield.TextInputLayout.END_ICON_NONE; @@ -531,12 +529,9 @@ public class FragmentAccount extends FragmentBase { result.folders = new ArrayList<>(); // Check IMAP server / get folders - Properties props = MessageHelper.getSessionProperties(realm, insecure); - Session isession = Session.getInstance(props, null); - isession.setDebug(true); - try (ConnectionHelper.ServiceHolder iservice = - new ConnectionHelper.ServiceHolder("imap" + (starttls ? "" : "s"), isession)) { - ConnectionHelper.connect(context, iservice, host, Integer.parseInt(port), user, password); + String protocol = "imap" + (starttls ? "" : "s"); + try (MailService iservice = new MailService(context, protocol, realm, insecure, true)) { + iservice.connect(host, Integer.parseInt(port), user, password); result.idle = iservice.getStore().hasCapability("IDLE"); @@ -888,13 +883,9 @@ public class FragmentAccount extends FragmentBase { // Check IMAP server EntityFolder inbox = null; if (check) { - Properties props = MessageHelper.getSessionProperties(realm, insecure); - Session isession = Session.getInstance(props, null); - isession.setDebug(true); - - try (ConnectionHelper.ServiceHolder iservice - = new ConnectionHelper.ServiceHolder("imap" + (starttls ? "" : "s"), isession)) { - ConnectionHelper.connect(context, iservice, host, Integer.parseInt(port), user, password); + String protocol = "imap" + (starttls ? "" : "s"); + try (MailService iservice = new MailService(context, protocol, realm, insecure, true)) { + iservice.connect(host, Integer.parseInt(port), user, password); for (Folder ifolder : iservice.getStore().getDefaultFolder().list("*")) { // Check folder attributes diff --git a/app/src/main/java/eu/faircode/email/FragmentIdentity.java b/app/src/main/java/eu/faircode/email/FragmentIdentity.java index c1617523e7..1e787d5d08 100644 --- a/app/src/main/java/eu/faircode/email/FragmentIdentity.java +++ b/app/src/main/java/eu/faircode/email/FragmentIdentity.java @@ -64,16 +64,10 @@ import androidx.lifecycle.Lifecycle; import com.google.android.material.snackbar.Snackbar; import com.google.android.material.textfield.TextInputLayout; -import java.net.Inet4Address; -import java.net.Inet6Address; -import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.Properties; - -import javax.mail.Session; import static android.app.Activity.RESULT_OK; import static com.google.android.material.textfield.TextInputLayout.END_ICON_NONE; @@ -678,32 +672,11 @@ public class FragmentIdentity extends FragmentBase { // Check SMTP server if (check) { - String protocol = (starttls ? "smtp" : "smtps"); - - // Get properties - Properties props = MessageHelper.getSessionProperties(realm, insecure); - - String haddr; - if (use_ip) { - InetAddress addr = InetAddress.getByName(host); - if (addr instanceof Inet4Address) - haddr = "[" + Inet4Address.getLocalHost().getHostAddress() + "]"; - else - haddr = "[IPv6:" + Inet6Address.getLocalHost().getHostAddress() + "]"; - } else - haddr = host; - - Log.i("Send localhost=" + haddr); - props.put("mail." + protocol + ".localhost", haddr); - - // Create session - Session isession = Session.getInstance(props, null); - isession.setDebug(true); - // Create transport - try (ConnectionHelper.ServiceHolder iservice = - new ConnectionHelper.ServiceHolder(protocol, isession)) { - ConnectionHelper.connect(context, iservice, host, Integer.parseInt(port), user, password); + String protocol = (starttls ? "smtp" : "smtps"); + try (MailService iservice = new MailService(context, protocol, realm, insecure, true)) { + iservice.setUseIp(use_ip, host); + iservice.connect(host, Integer.parseInt(port), user, password); } } diff --git a/app/src/main/java/eu/faircode/email/FragmentQuickSetup.java b/app/src/main/java/eu/faircode/email/FragmentQuickSetup.java index d001e6a550..d0e17fa691 100644 --- a/app/src/main/java/eu/faircode/email/FragmentQuickSetup.java +++ b/app/src/main/java/eu/faircode/email/FragmentQuickSetup.java @@ -57,10 +57,8 @@ import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Date; import java.util.List; -import java.util.Properties; import javax.mail.Folder; -import javax.mail.Session; import static android.app.Activity.RESULT_OK; @@ -251,12 +249,9 @@ public class FragmentQuickSetup extends FragmentBase { long now = new Date().getTime(); { - Properties props = MessageHelper.getSessionProperties(null, false); - Session isession = Session.getInstance(props, null); - isession.setDebug(true); - try (ConnectionHelper.ServiceHolder iservice = - new ConnectionHelper.ServiceHolder(provider.imap_starttls ? "imap" : "imaps", isession)) { - ConnectionHelper.connect(context, iservice, provider.imap_host, provider.imap_port, user, password); + String protocol = provider.imap_starttls ? "imap" : "imaps"; + try (MailService iservice = new MailService(context, protocol, null, false, true)) { + iservice.connect(provider.imap_host, provider.imap_port, user, password); boolean inbox = false; boolean drafts = false; @@ -316,12 +311,9 @@ public class FragmentQuickSetup extends FragmentBase { } { - Properties props = MessageHelper.getSessionProperties(null, false); - Session isession = Session.getInstance(props, null); - isession.setDebug(true); - try (ConnectionHelper.ServiceHolder iservice - = new ConnectionHelper.ServiceHolder(provider.smtp_starttls ? "smtp" : "smtps", isession)) { - ConnectionHelper.connect(context, iservice, provider.smtp_host, provider.smtp_port, user, password); + String protocol = provider.smtp_starttls ? "smtp" : "smtps"; + try (MailService iservice = new MailService(context, protocol, null, false, true)) { + iservice.connect(provider.smtp_host, provider.smtp_port, user, password); } } diff --git a/app/src/main/java/eu/faircode/email/MailService.java b/app/src/main/java/eu/faircode/email/MailService.java new file mode 100644 index 0000000000..d5904780a9 --- /dev/null +++ b/app/src/main/java/eu/faircode/email/MailService.java @@ -0,0 +1,165 @@ +package eu.faircode.email; + +import android.content.Context; + +import com.bugsnag.android.BreadcrumbType; +import com.bugsnag.android.Bugsnag; +import com.sun.mail.imap.IMAPStore; +import com.sun.mail.smtp.SMTPTransport; +import com.sun.mail.util.MailConnectException; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Properties; + +import javax.mail.MessagingException; +import javax.mail.NoSuchProviderException; +import javax.mail.Service; +import javax.mail.Session; + +public class MailService implements AutoCloseable { + private Context context; + private String protocol; + private boolean debug; + private Properties properties; + private Session isession; + private Service iservice; + + private static final String any4 = "0.0.0.0"; + + private MailService() { + } + + MailService(Context context, String protocol, String realm, boolean insecure, boolean debug) { + this.context = context; + this.protocol = protocol; + this.debug = debug; + this.properties = MessageHelper.getSessionProperties(realm, insecure); + } + + void setPartialFetch(boolean enabled) { + if (!enabled) + this.properties.put("mail." + this.protocol + ".partialfetch", "false"); + } + + void setUseIp(boolean enabled, String host) throws UnknownHostException { + String haddr; + if (enabled) { + InetAddress addr = InetAddress.getByName(host); + if (addr instanceof Inet4Address) + haddr = "[" + Inet4Address.getLocalHost().getHostAddress() + "]"; + else + haddr = "[IPv6:" + Inet6Address.getLocalHost().getHostAddress() + "]"; + } else + haddr = host; + + Log.i("Send localhost=" + haddr); + this.properties.put("mail." + this.protocol + ".localhost", haddr); + } + + void setSeparateStoreConnection() { + this.properties.put("mail." + this.protocol + ".separatestoreconnection", "true"); + } + + public void connect(EntityAccount account) throws MessagingException { + connect(account.host, account.port, account.user, account.password); + } + + public void connect(EntityIdentity identity) throws MessagingException { + connect(identity.host, identity.port, identity.user, identity.password); + } + + public void connect(String host, int port, String user, String password) throws MessagingException { + try { + _connect(context, host, port, user, password); + } catch (MailConnectException ex) { + if (!hasIPv6(host)) + throw ex; + + try { + Log.i("Binding to " + any4); + properties.put("mail.imap.localaddress", any4); + properties.put("mail.imaps.localaddress", any4); + properties.put("mail.smtp.localaddress", any4); + properties.put("mail.smtps.localaddress", any4); + _connect(context, host, port, user, password); + } catch (Throwable ex1) { + Log.w(ex1); + throw ex; + } + } + } + + private void _connect(Context context, String host, int port, String user, String password) throws MessagingException { + isession = Session.getInstance(properties, null); + isession.setDebug(debug); + //System.setProperty("mail.socket.debug", Boolean.toString(debug)); + + if ("imap".equals(protocol) || "imaps".equals(protocol)) { + iservice = isession.getStore(protocol); + iservice.connect(host, port, user, password); + + // https://www.ietf.org/rfc/rfc2971.txt + if (getStore().hasCapability("ID")) + try { + Map id = new LinkedHashMap<>(); + id.put("name", context.getString(R.string.app_name)); + id.put("version", BuildConfig.VERSION_NAME); + Map sid = getStore().id(id); + if (sid != null) { + Map crumb = new HashMap<>(); + for (String key : sid.keySet()) { + crumb.put(key, sid.get(key)); + EntityLog.log(context, "Server " + key + "=" + sid.get(key)); + } + Bugsnag.leaveBreadcrumb("server", BreadcrumbType.LOG, crumb); + } + } catch (MessagingException ex) { + Log.w(ex); + } + + } else if ("smtp".equals(protocol) || "smtps".equals(protocol)) { + iservice = isession.getTransport(protocol); + iservice.connect(host, port, user, password); + } else + throw new NoSuchProviderException(protocol); + } + + private static boolean hasIPv6(String host) { + boolean has = false; + try { + for (InetAddress iaddr : InetAddress.getAllByName(host)) { + Log.i(host + " resolves to " + iaddr); + if (iaddr instanceof Inet6Address) + has = true; + } + } catch (UnknownHostException ignored) { + } + return has; + } + + IMAPStore getStore() { + return (IMAPStore) this.iservice; + } + + SMTPTransport getTransport() { + return (SMTPTransport) this.iservice; + } + + public void close() throws MessagingException { + try { + if (iservice != null) + iservice.close(); + } finally { + this.context = null; + this.properties = null; + this.isession = null; + this.iservice = null; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/faircode/email/ServiceSend.java b/app/src/main/java/eu/faircode/email/ServiceSend.java index 24b4e1313b..f737f77c2e 100644 --- a/app/src/main/java/eu/faircode/email/ServiceSend.java +++ b/app/src/main/java/eu/faircode/email/ServiceSend.java @@ -38,9 +38,6 @@ import androidx.preference.PreferenceManager; import java.io.FileNotFoundException; import java.io.IOException; -import java.net.Inet4Address; -import java.net.Inet6Address; -import java.net.InetAddress; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; @@ -312,32 +309,9 @@ public class ServiceSend extends ServiceBase { if (ident == null) throw new IllegalArgumentException("Identity not found"); - String protocol = ident.getProtocol(); - - // Get properties - Properties props = MessageHelper.getSessionProperties(ident.realm, ident.insecure); - - String haddr; - if (ident.use_ip) { - InetAddress addr = InetAddress.getByName(ident.host); - if (addr instanceof Inet4Address) - haddr = "[" + Inet4Address.getLocalHost().getHostAddress() + "]"; - else { - // Inet6Address.getLocalHost() will return the IPv6 local host - byte[] LOOPBACK = new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; - haddr = "[IPv6:" + Inet6Address.getByAddress("ip6-localhost", LOOPBACK, 0).getHostAddress() + "]"; - } - } else - haddr = ident.host; - - EntityLog.log(this, "Send localhost=" + haddr); - props.put("mail." + protocol + ".localhost", haddr); - - // Create session - final Session isession = Session.getInstance(props, null); - isession.setDebug(debug); - // Create message + Properties props = MessageHelper.getSessionProperties(null, false); + Session isession = Session.getInstance(props, null); MimeMessage imessage = MessageHelper.from(this, message, ident, isession); // Add reply to @@ -365,10 +339,13 @@ public class ServiceSend extends ServiceBase { } // Create transport - try (ConnectionHelper.ServiceHolder iservice = new ConnectionHelper.ServiceHolder(protocol, isession)) { + try (MailService iservice = new MailService( + this, ident.getProtocol(), ident.realm, ident.insecure, debug)) { + iservice.setUseIp(ident.use_ip, ident.host); + // Connect transport db.identity().setIdentityState(ident.id, "connecting"); - ConnectionHelper.connect(this, iservice, ident); + iservice.connect(ident); db.identity().setIdentityState(ident.id, "connected"); // Send message diff --git a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java index 90202e1d51..046f8cc7c6 100644 --- a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java +++ b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java @@ -58,7 +58,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Properties; import java.util.Random; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -74,7 +73,6 @@ import javax.mail.MessageRemovedException; import javax.mail.MessagingException; import javax.mail.NoSuchProviderException; import javax.mail.ReadOnlyFolderException; -import javax.mail.Session; import javax.mail.StoreClosedException; import javax.mail.UIDFolder; import javax.mail.event.ConnectionAdapter; @@ -610,22 +608,10 @@ public class ServiceSynchronize extends ServiceBase { // Debug SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); boolean debug = (prefs.getBoolean("debug", false) || BuildConfig.DEBUG); - //System.setProperty("mail.socket.debug", Boolean.toString(debug)); - // Get properties - Properties props = MessageHelper.getSessionProperties(account.realm, account.insecure); - if (!account.partial_fetch) { - props.put("mail.imap.partialfetch", "false"); - props.put("mail.imaps.partialfetch", "false"); - } - - // Create session - final Session isession = Session.getInstance(props, null); - isession.setDebug(debug); - // adb -t 1 logcat | grep "fairemail\|System.out" - - final ConnectionHelper.ServiceHolder iservice = - new ConnectionHelper.ServiceHolder(account.getProtocol(), isession); + final MailService iservice = new MailService( + this, account.getProtocol(), account.realm, account.insecure, debug); + iservice.setPartialFetch(account.partial_fetch); final Map mapFolders = new HashMap<>(); List idlers = new ArrayList<>(); @@ -636,7 +622,7 @@ public class ServiceSynchronize extends ServiceBase { db.account().setAccountState(account.id, "connecting"); try { - ConnectionHelper.connect(this, iservice, account); + iservice.connect(account); } catch (Throwable ex) { if (ex instanceof AuthenticationFailedException) { NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); @@ -1089,7 +1075,7 @@ public class ServiceSynchronize extends ServiceBase { Core.processOperations(ServiceSynchronize.this, account, folder, - isession, iservice.getStore(), ifolder, + iservice.getStore(), ifolder, state); } catch (Throwable ex) {