Skip to content

Commit

Permalink
implement auto-discover function, close #66
Browse files Browse the repository at this point in the history
  • Loading branch information
Robert Virkus committed Jun 10, 2020
1 parent 977443f commit 4209dc3
Show file tree
Hide file tree
Showing 9 changed files with 1,099 additions and 0 deletions.
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,32 @@ int smtpServerPort = 465;
bool isSmtpServerSecure = true;
void main() async {
await discoverExample();
await imapExample();
await smtpExample();
await popExample();
exit(0);
}
Future<void> discoverExample() async {
var email = '[email protected]';
var config = await Discover.discover(email, isLogEnabled: false);
if (config == null) {
print('Unable to discover settings for $email');
} else {
print('Settings for $email:');
for (var provider in config.emailProviders) {
print('provider: ${provider.displayName}');
print('provider-domains: ${provider.domains}');
print('documentation-url: ${provider.documentationUrl}');
print('Incoming:');
print(provider.preferredIncomingServer);
print('Outgoing:');
print(provider.preferredOutgoingServer);
}
}
}
Future<void> imapExample() async {
var client = ImapClient(isLogEnabled: false);
await client.connectToServer(imapServerHost, imapServerPort,
Expand Down
26 changes: 26 additions & 0 deletions example/enough_mail_example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,38 @@ int smtpServerPort = 465;
bool isSmtpServerSecure = true;

void main() async {
await discoverExample();
await imapExample();
await smtpExample();
await popExample();
exit(0);
}

Future<void> discoverExample() async {
var email = '[email protected]';
var config = await Discover.discover(email, isLogEnabled: false);
if (config == null) {
print('Unable to discover settings for $email');
} else {
print('Settings for $email:');
for (var provider in config.emailProviders) {
print('provider: ${provider.displayName}');
print('provider-domains: ${provider.domains}');
print('documentation-url: ${provider.documentationUrl}');
print('Incoming:');
// for (var server in provider.incomingServers) {
// print(server);
// }
print(provider.preferredIncomingServer);
print('Outgoing:');
// for (var server in provider.outgoingServers) {
// print(server);
// }
print(provider.preferredOutgoingServer);
}
}
}

Future<void> imapExample() async {
var client = ImapClient(isLogEnabled: false);
await client.connectToServer(imapServerHost, imapServerPort,
Expand Down
139 changes: 139 additions & 0 deletions lib/discover/client_config.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
class ClientConfig {
String version;
List<ConfigEmailProvider> emailProviders;

bool get isNotValid =>
emailProviders == null ||
emailProviders.isEmpty ||
emailProviders.first.preferredIncomingServer == null ||
emailProviders.first.preferredOutgoingServer == null;
bool get isValid => !isNotValid;

ClientConfig({this.version});

void addEmailProvider(ConfigEmailProvider provider) {
emailProviders ??= <ConfigEmailProvider>[];
emailProviders.add(provider);
}

ServerConfig get preferredIncomingServer => emailProviders?.isEmpty ?? true
? null
: emailProviders.first.preferredIncomingServer;
ServerConfig get preferredIncomingImapServer =>
emailProviders?.isEmpty ?? true
? null
: emailProviders.first.preferredIncomingImapServer;
ServerConfig get preferredIncomingPopServer => emailProviders?.isEmpty ?? true
? null
: emailProviders.first.preferredIncomingPopServer;
ServerConfig get preferredOutgoingServer => emailProviders?.isEmpty ?? true
? null
: emailProviders.first.preferredOutgoingServer;
ServerConfig get preferredOutgoingSmtpServer =>
emailProviders?.isEmpty ?? true
? null
: emailProviders.first.preferredOutgoingSmtpServer;
String get displayName =>
emailProviders?.isEmpty ?? true ? null : emailProviders.first.displayName;
}

class ConfigEmailProvider {
String id;
List<String> domains;
String displayName;
String displayShortName;
List<ServerConfig> incomingServers;
List<ServerConfig> outgoingServers;
String documentationUrl;
ServerConfig preferredIncomingServer;
ServerConfig preferredIncomingImapServer;
ServerConfig preferredIncomingPopServer;
ServerConfig preferredOutgoingServer;
ServerConfig preferredOutgoingSmtpServer;

ConfigEmailProvider(
{this.id,
this.domains,
this.displayName,
this.displayShortName,
this.incomingServers,
this.outgoingServers});

void addDomain(String name) {
domains ??= <String>[];
domains.add(name);
}

void addIncomingServer(ServerConfig server) {
incomingServers ??= <ServerConfig>[];
incomingServers.add(server);
preferredIncomingServer ??= server;
if (server.type == ServerType.imap && preferredIncomingImapServer == null) {
preferredIncomingImapServer = server;
}
if (server.type == ServerType.pop && preferredIncomingPopServer == null) {
preferredIncomingPopServer = server;
}
}

void addOutgoingServer(ServerConfig server) {
outgoingServers ??= <ServerConfig>[];
outgoingServers.add(server);
preferredOutgoingServer ??= server;
if (server.type == ServerType.smtp && preferredOutgoingSmtpServer == null) {
preferredOutgoingSmtpServer = server;
}
}
}

enum ServerType { imap, pop, smtp, unknown }

enum SocketType { plain, ssl, starttls, unknown }

enum Authentication {
oauth2,
passwordCleartext,
plain,
passwordEncrypted,
secure,
ntlm,
gsapi,
clientIpAddress,
tlsClientCert,
smtpAfterPop,
none,
unknown
}

enum UsernameType { emailAddress, emailLocalPart, realname, unknown }

class ServerConfig {
String typeName;
ServerType type;
String hostname;
int port;
SocketType socketType;
String get socketTypeName =>
socketType.toString().substring('socketType.'.length);
Authentication authentication;
Authentication authenticationAlternative;
String get authenticationName =>
authentication.toString().substring('authentication.'.length);
String username;
UsernameType usernameType;

bool get isSecureSocket => (socketType == SocketType.ssl);

ServerConfig(
{this.type,
this.hostname,
this.port,
this.socketType,
this.authentication,
this.username});

@override
String toString() {
return '$typeName:\n host: $hostname\n port: $port\n socket: $socketTypeName\n authentication: $authenticationName\n username: $username';
}
}
83 changes: 83 additions & 0 deletions lib/discover/discover.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import 'package:enough_mail/src/util/discover_helper.dart';

import 'client_config.dart';

class Discover {
static Future<ClientConfig> discover(String emailAddress,
{bool forceSslConnection = false, bool isLogEnabled = false}) async {
var config = await _discover(emailAddress, isLogEnabled);
if (forceSslConnection && config != null) {
if (config.preferredIncomingImapServer != null &&
!config.preferredIncomingImapServer.isSecureSocket) {
config.preferredIncomingImapServer.port = 993;
config.preferredIncomingImapServer.socketType = SocketType.ssl;
}
if (config.preferredIncomingPopServer != null &&
!config.preferredIncomingPopServer.isSecureSocket) {
config.preferredIncomingPopServer.port = 995;
config.preferredIncomingPopServer.socketType = SocketType.ssl;
}
if (config.preferredOutgoingSmtpServer != null &&
!config.preferredOutgoingSmtpServer.isSecureSocket) {
config.preferredOutgoingSmtpServer.port = 465;
config.preferredOutgoingSmtpServer.socketType = SocketType.ssl;
}
}
return config;
}

static Future<ClientConfig> _discover(
String emailAddress, bool isLogEnabled) async {
// [1] autodiscover from sub-domain, compare: https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration
var emailDomain = DiscoverHelper.getDomainFromEmail(emailAddress);
var config = await DiscoverHelper.discoverFromAutoConfigSubdomain(
emailAddress, emailDomain, isLogEnabled);
if (config != null) {
return _updateDisplayNames(config, emailDomain);
}
var mxDomain = await DiscoverHelper.discoverMxDomain(emailDomain);
_log('mxDomain for [$emailDomain] is [$mxDomain]', isLogEnabled);
if (mxDomain != null && mxDomain != emailDomain) {
config = await DiscoverHelper.discoverFromAutoConfigSubdomain(
emailAddress, mxDomain, isLogEnabled);
if (config != null) {
return _updateDisplayNames(config, emailDomain);
}
}
// TODO allow more autodiscover options:
// [2] https://docs.microsoft.com/en-us/previous-versions/office/office-2010/cc511507(v=office.14)
// [3] https://docs.microsoft.com/en-us/exchange/client-developer/exchange-web-services/autodiscover-for-exchange
// [4] https://docs.microsoft.com/en-us/exchange/architecture/client-access/autodiscover
// [6] by trying typical options like imap.$domain, mail.$domain, etc

//print('querying ISP DB for $mxDomain');

// [5] autodiscover from Mozilla ISP DB: https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration
config = await DiscoverHelper.discoverFromIspDb(mxDomain, isLogEnabled);
//print('got config $config for $mxDomain.');
return _updateDisplayNames(config, emailDomain);
}

static ClientConfig _updateDisplayNames(
ClientConfig config, String mailDomain) {
if (config?.emailProviders?.isNotEmpty ?? false) {
for (var provider in config.emailProviders) {
if (provider.displayName != null) {
provider.displayName =
provider.displayName.replaceFirst('%EMAILDOMAIN%', mailDomain);
}
if (provider.displayShortName != null) {
provider.displayShortName = provider.displayShortName
.replaceFirst('%EMAILDOMAIN%', mailDomain);
}
}
}
return config;
}

static void _log(String text, bool isLogEnabled) {
if (isLogEnabled) {
print(text);
}
}
}
3 changes: 3 additions & 0 deletions lib/enough_mail.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ export 'codecs/mail_codec.dart';
export 'codecs/base64_mail_codec.dart';
export 'codecs/quoted_printable_mail_codec.dart';

export 'discover/client_config.dart';
export 'discover/discover.dart';

export 'mail_address.dart';
export 'media_type.dart';
export 'message_builder.dart';
Expand Down
Loading

0 comments on commit 4209dc3

Please sign in to comment.