Added multi discovery

This commit is contained in:
M66B 2021-08-16 18:45:47 +02:00
parent 7eba0d82c0
commit 9a09474c3a
5 changed files with 344 additions and 296 deletions

View File

@ -57,6 +57,7 @@ import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Objects;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
@ -241,12 +242,12 @@ public class EmailProvider implements Parcelable {
} }
@NonNull @NonNull
static EmailProvider fromDomain(Context context, String domain, Discover discover) throws IOException { static List<EmailProvider> fromDomain(Context context, String domain, Discover discover) throws IOException {
return fromEmail(context, domain, discover); return fromEmail(context, domain, discover);
} }
@NonNull @NonNull
static EmailProvider fromEmail(Context context, String email, Discover discover) throws IOException { static List<EmailProvider> fromEmail(Context context, String email, Discover discover) throws IOException {
int at = email.indexOf('@'); int at = email.indexOf('@');
String domain = (at < 0 ? email : email.substring(at + 1)); String domain = (at < 0 ? email : email.substring(at + 1));
if (at < 0) if (at < 0)
@ -258,34 +259,23 @@ public class EmailProvider implements Parcelable {
if (PROPRIETARY.contains(domain)) if (PROPRIETARY.contains(domain))
throw new IllegalArgumentException(context.getString(R.string.title_no_standard)); throw new IllegalArgumentException(context.getString(R.string.title_no_standard));
if (BuildConfig.DEBUG && false)
try {
// Scan ports
EntityLog.log(context, "Provider from template domain=" + domain);
return fromTemplate(context, domain, discover);
} catch (Throwable ex) {
Log.w(ex);
throw new UnknownHostException(context.getString(R.string.title_setup_no_settings, domain));
}
List<EmailProvider> providers = loadProfiles(context); List<EmailProvider> providers = loadProfiles(context);
for (EmailProvider provider : providers) for (EmailProvider provider : providers)
if (provider.domain != null) if (provider.domain != null)
for (String d : provider.domain) for (String d : provider.domain)
if (domain.toLowerCase(Locale.ROOT).matches(d)) { if (domain.toLowerCase(Locale.ROOT).matches(d)) {
EntityLog.log(context, "Provider from domain=" + domain + " (" + d + ")"); EntityLog.log(context, "Provider from domain=" + domain + " (" + d + ")");
provider.log(context); return Arrays.asList(provider);
return provider;
} }
EmailProvider autoconfig = null; List<EmailProvider> result = new ArrayList<>();
try { for (EmailProvider provider : _fromDomain(context, domain.toLowerCase(Locale.ROOT), email, discover))
autoconfig = _fromDomain(context, domain.toLowerCase(Locale.ROOT), email, discover); if (result.contains(provider))
} catch (Throwable ex) { Log.i("Duplicate " + provider);
Log.w(ex); else
result.add(provider);
try { try {
// Retry at MX server addresses
DnsHelper.DnsRecord[] records = DnsHelper.lookup(context, domain, "mx"); DnsHelper.DnsRecord[] records = DnsHelper.lookup(context, domain, "mx");
for (DnsHelper.DnsRecord record : records) for (DnsHelper.DnsRecord record : records)
@ -295,50 +285,58 @@ public class EmailProvider implements Parcelable {
for (String mx : provider.mx) for (String mx : provider.mx)
if (record.name.toLowerCase(Locale.ROOT).matches(mx)) { if (record.name.toLowerCase(Locale.ROOT).matches(mx)) {
EntityLog.log(context, "Provider from mx=" + record.name + " domain=" + domain); EntityLog.log(context, "Provider from mx=" + record.name + " domain=" + domain);
provider.log(context); if (result.contains(provider))
return provider; Log.i("Duplicate " + provider);
else
result.add(provider);
break;
} }
String mxparent = UriHelper.getParentDomain(context, record.name); String mxparent = UriHelper.getParentDomain(context, record.name);
String pdomain = UriHelper.getParentDomain(context, provider.imap.host); String pdomain = UriHelper.getParentDomain(context, provider.imap.host);
if (mxparent.equalsIgnoreCase(pdomain)) { if (mxparent.equalsIgnoreCase(pdomain)) {
EntityLog.log(context, "Provider from mx=" + record.name + " host=" + provider.imap.host); EntityLog.log(context, "Provider from mx=" + record.name + " host=" + provider.imap.host);
provider.log(context); if (result.contains(provider))
return provider; Log.i("Duplicate " + provider);
else
result.add(provider);
break;
} }
} }
for (DnsHelper.DnsRecord record : records) { for (DnsHelper.DnsRecord record : records) {
String target = record.name; String target = record.name;
while (autoconfig == null && target != null && target.indexOf('.') > 0) { while (result.size() == 0 && target != null && target.indexOf('.') > 0) {
try { try {
autoconfig = _fromDomain(context, target.toLowerCase(Locale.ROOT), email, discover); for (EmailProvider provider : _fromDomain(context, target.toLowerCase(Locale.ROOT), email, discover))
} catch (Throwable ex1) { if (result.contains(provider))
Log.w(ex1); Log.i("Duplicate " + provider);
else
result.add(provider);
} catch (Throwable ex) {
Log.w(ex);
int dot = target.indexOf('.'); int dot = target.indexOf('.');
target = target.substring(dot + 1); target = target.substring(dot + 1);
} }
} }
if (autoconfig != null)
break;
}
} catch (Throwable ex1) {
Log.w(ex1);
} }
if (autoconfig == null) } catch (Throwable ex) {
throw ex; Log.w(ex);
} }
if (result.size() == 0)
throw new UnknownHostException(context.getString(R.string.title_setup_no_settings, domain));
for (EmailProvider autoconfig : result)
for (EmailProvider provider : providers) {
// Always prefer built-in profiles // Always prefer built-in profiles
// - ISPDB is not always correct // - ISPDB is not always correct
// - documentation links // - documentation links
for (EmailProvider provider : providers)
if (provider.imap.host.equals(autoconfig.imap.host) || if (provider.imap.host.equals(autoconfig.imap.host) ||
provider.smtp.host.equals(autoconfig.smtp.host)) { provider.smtp.host.equals(autoconfig.smtp.host)) {
EntityLog.log(context, "Replacing auto config by profile=" + provider.name); EntityLog.log(context, "Replacing auto config by profile=" + provider.name);
provider.log(context); return Arrays.asList(provider);
return provider;
} }
// https://help.dreamhost.com/hc/en-us/articles/214918038-Email-client-configuration-overview // https://help.dreamhost.com/hc/en-us/articles/214918038-Email-client-configuration-overview
@ -354,34 +352,40 @@ public class EmailProvider implements Parcelable {
if (autoconfig.imap.host != null && if (autoconfig.imap.host != null &&
autoconfig.imap.host.endsWith(".awsapps.com")) autoconfig.imap.host.endsWith(".awsapps.com"))
autoconfig.partial = false; autoconfig.partial = false;
}
return autoconfig; return result;
} }
@NonNull @NonNull
private static EmailProvider _fromDomain(Context context, String domain, String email, Discover discover) throws IOException { private static List<EmailProvider> _fromDomain(Context context, String domain, String email, Discover discover) throws IOException {
List<EmailProvider> result = new ArrayList<>();
try { try {
// Assume the provider knows best // Assume the provider knows best
Log.i("Provider from DNS domain=" + domain); Log.i("Provider from DNS domain=" + domain);
return fromDNS(context, domain, discover); result.add(fromDNS(context, domain, discover));
} catch (Throwable ex) { } catch (Throwable ex) {
Log.w(ex); Log.w(ex);
}
try { try {
// Check ISPDB // Check ISPDB
Log.i("Provider from ISPDB domain=" + domain); Log.i("Provider from ISPDB domain=" + domain);
return fromISPDB(context, domain, email); result.add(fromISPDB(context, domain, email));
} catch (Throwable ex1) { } catch (Throwable ex) {
Log.w(ex1); Log.w(ex);
}
try { try {
// Scan ports // Scan ports
Log.i("Provider from template domain=" + domain); Log.i("Provider from template domain=" + domain);
return fromTemplate(context, domain, discover); result.add(fromScan(context, domain, discover));
} catch (Throwable ex2) { } catch (Throwable ex) {
Log.w(ex2); Log.w(ex);
throw new UnknownHostException(context.getString(R.string.title_setup_no_settings, domain));
}
}
} }
return result;
} }
@NonNull @NonNull
@ -584,7 +588,6 @@ public class EmailProvider implements Parcelable {
} }
provider.validate(); provider.validate();
provider.log(context);
return provider; return provider;
} finally { } finally {
@ -643,13 +646,12 @@ public class EmailProvider implements Parcelable {
} }
provider.validate(); provider.validate();
provider.log(context);
return provider; return provider;
} }
@NonNull @NonNull
private static EmailProvider fromTemplate(Context context, String domain, Discover discover) private static EmailProvider fromScan(Context context, String domain, Discover discover)
throws ExecutionException, InterruptedException, UnknownHostException { throws ExecutionException, InterruptedException, UnknownHostException {
// https://tools.ietf.org/html/rfc8314 // https://tools.ietf.org/html/rfc8314
Server imap = null; Server imap = null;
@ -731,7 +733,6 @@ public class EmailProvider implements Parcelable {
if (smtp != null) if (smtp != null)
provider.smtp = smtp; provider.smtp = smtp;
provider.log(context);
return provider; return provider;
} }
@ -744,11 +745,6 @@ public class EmailProvider implements Parcelable {
provider.documentation.append("<a href=\"").append(href).append("\">").append(title).append("</a>"); provider.documentation.append("<a href=\"").append(href).append("\">").append(title).append("</a>");
} }
private void log(Context context) {
EntityLog.log(context, "imap=" + imap);
EntityLog.log(context, "smtp=" + smtp);
}
protected EmailProvider(Parcel in) { protected EmailProvider(Parcel in) {
if (in.readInt() == 0) if (in.readInt() == 0)
imap = null; imap = null;
@ -812,6 +808,21 @@ public class EmailProvider implements Parcelable {
} }
}; };
@Override
public boolean equals(Object obj) {
if (obj instanceof EmailProvider) {
EmailProvider other = (EmailProvider) obj;
return (Objects.equals(this.imap, other.imap) &&
Objects.equals(this.smtp, other.smtp));
} else
return false;
}
@Override
public int hashCode() {
return Objects.hash(imap, smtp);
}
@NonNull @NonNull
@Override @Override
public String toString() { public String toString() {
@ -986,6 +997,22 @@ public class EmailProvider implements Parcelable {
throw new SocketException("No STARTTLS"); throw new SocketException("No STARTTLS");
} }
@Override
public boolean equals(Object obj) {
if (obj instanceof Server) {
Server other = (Server) obj;
return (Objects.equals(this.host, other.host) &&
this.port == other.port &&
this.starttls == other.starttls);
} else
return false;
}
@Override
public int hashCode() {
return Objects.hash(host, port, starttls);
}
@NonNull @NonNull
@Override @Override
public String toString() { public String toString() {

View File

@ -591,7 +591,9 @@ public class FragmentAccount extends FragmentBase {
@Override @Override
protected EmailProvider onExecute(Context context, Bundle args) throws Throwable { protected EmailProvider onExecute(Context context, Bundle args) throws Throwable {
String domain = args.getString("domain"); String domain = args.getString("domain");
return EmailProvider.fromDomain(context, domain, EmailProvider.Discover.IMAP); return EmailProvider
.fromDomain(context, domain, EmailProvider.Discover.IMAP)
.get(0);
} }
@Override @Override

View File

@ -413,7 +413,9 @@ public class FragmentGmail extends FragmentBase {
int at = user.indexOf('@'); int at = user.indexOf('@');
String username = user.substring(0, at); String username = user.substring(0, at);
EmailProvider provider = EmailProvider.fromDomain(context, "gmail.com", EmailProvider.Discover.ALL); EmailProvider provider = EmailProvider
.fromDomain(context, "gmail.com", EmailProvider.Discover.ALL)
.get(0);
List<EntityFolder> folders; List<EntityFolder> folders;

View File

@ -584,7 +584,9 @@ public class FragmentIdentity extends FragmentBase {
@Override @Override
protected EmailProvider onExecute(Context context, Bundle args) throws Throwable { protected EmailProvider onExecute(Context context, Bundle args) throws Throwable {
String domain = args.getString("domain"); String domain = args.getString("domain");
return EmailProvider.fromDomain(context, domain, EmailProvider.Discover.SMTP); return EmailProvider
.fromDomain(context, domain, EmailProvider.Discover.SMTP)
.get(0);
} }
@Override @Override

View File

@ -277,16 +277,20 @@ public class FragmentQuickSetup extends FragmentBase {
if (TextUtils.isEmpty(password)) if (TextUtils.isEmpty(password))
throw new IllegalArgumentException(context.getString(R.string.title_no_password)); throw new IllegalArgumentException(context.getString(R.string.title_no_password));
int at = email.indexOf('@');
String username = email.substring(0, at);
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo ani = (cm == null ? null : cm.getActiveNetworkInfo()); NetworkInfo ani = (cm == null ? null : cm.getActiveNetworkInfo());
if (ani == null || !ani.isConnected()) if (ani == null || !ani.isConnected())
throw new IllegalArgumentException(context.getString(R.string.title_no_internet)); throw new IllegalArgumentException(context.getString(R.string.title_no_internet));
EmailProvider provider = EmailProvider.fromEmail(context, email, EmailProvider.Discover.ALL); Throwable fail = null;
args.putParcelable("provider", provider); List<EmailProvider> providers = EmailProvider.fromEmail(context, email, EmailProvider.Discover.ALL);
for (EmailProvider provider : providers)
int at = email.indexOf('@'); try {
String username = email.substring(0, at); EntityLog.log(context, "imap=" + provider.imap);
EntityLog.log(context, "smtp=" + provider.smtp);
String user = (provider.user == EmailProvider.UserType.EMAIL ? email : username); String user = (provider.user == EmailProvider.UserType.EMAIL ? email : username);
Log.i("User type=" + provider.user + " name=" + user); Log.i("User type=" + provider.user + " name=" + user);
@ -393,6 +397,7 @@ public class FragmentQuickSetup extends FragmentBase {
} }
if (check) { if (check) {
args.putParcelable("provider", provider);
args.putSerializable("imap_certificate", imap_certificate); args.putSerializable("imap_certificate", imap_certificate);
args.putSerializable("smtp_certificate", smtp_certificate); args.putSerializable("smtp_certificate", smtp_certificate);
return provider; return provider;
@ -480,6 +485,16 @@ public class FragmentQuickSetup extends FragmentBase {
db.endTransaction(); db.endTransaction();
} }
break;
} catch (Throwable ex) {
Log.w(ex);
if (fail == null)
fail = ex;
}
if (fail != null)
throw fail;
ServiceSynchronize.eval(context, "quick setup"); ServiceSynchronize.eval(context, "quick setup");
return null; return null;