From cc76eb209813c8956e08fb7a671f6c32f8193c0e Mon Sep 17 00:00:00 2001 From: Robert Virkus Date: Tue, 13 Jul 2021 16:30:59 +0200 Subject: [PATCH] add option to register onBadCertificate callback Resolves #167 --- lib/imap/imap_client.dart | 11 ++++-- lib/mail/mail_client.dart | 65 ++++++++++++++++++++++++----------- lib/pop/pop_client.dart | 11 ++++-- lib/smtp/smtp_client.dart | 11 ++++-- lib/src/util/client_base.dart | 17 +++++++-- 5 files changed, 83 insertions(+), 32 deletions(-) diff --git a/lib/imap/imap_client.dart b/lib/imap/imap_client.dart index 6b306810..69f77655 100644 --- a/lib/imap/imap_client.dart +++ b/lib/imap/imap_client.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:io'; import 'dart:typed_data'; import 'package:collection/collection.dart' show IterableExtension; import 'package:enough_mail/enough_mail.dart'; @@ -195,16 +196,20 @@ class ImapClient extends ClientBase { /// Set [isLogEnabled] to `true` for getting log outputs on the standard output. /// Optionally specify a [logName] that is given out at logs to differentiate between different imap clients. /// Set the [connectionTimeout] in case the connection connection should timeout automatically after the given time. + /// [onBadCertificate] is an optional handler for unverifiable certificates. The handler receives the [X509Certificate], and can inspect it and decide (or let the user decide) whether to accept the connection or not. The handler should return true to continue the [SecureSocket] connection. ImapClient({ EventBus? bus, bool isLogEnabled = false, String? logName, Duration? connectionTimeout, + bool Function(X509Certificate)? onBadCertificate, }) : _eventBus = bus ?? EventBus(), super( - isLogEnabled: isLogEnabled, - logName: logName, - connectionTimeout: connectionTimeout) { + isLogEnabled: isLogEnabled, + logName: logName, + connectionTimeout: connectionTimeout, + onBadCertificate: onBadCertificate, + ) { _imapResponseReader = ImapResponseReader(onServerResponse); } diff --git a/lib/mail/mail_client.dart b/lib/mail/mail_client.dart index 9c583a77..14cd53a8 100644 --- a/lib/mail/mail_client.dart +++ b/lib/mail/mail_client.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'package:collection/collection.dart' show IterableExtension; import 'package:enough_mail/enough_mail.dart'; @@ -107,22 +108,27 @@ class MailClient { /// Specify the account settings with [account]. /// Set [isLogEnabled] to true to debug connection issues. /// Specify the optional [downloadSizeLimit] in bytes to only download messages automatically that are this size or lower. - MailClient(MailAccount account, - {bool isLogEnabled = false, - int? downloadSizeLimit, - EventBus? eventBus, - String? logName}) - : _eventBus = eventBus ?? EventBus(), + /// [onBadCertificate] is an optional handler for unverifiable certificates. The handler receives the [X509Certificate], and can inspect it and decide (or let the user decide) whether to accept the connection or not. The handler should return true to continue the [SecureSocket] connection. + MailClient( + MailAccount account, { + bool isLogEnabled = false, + int? downloadSizeLimit, + EventBus? eventBus, + String? logName, + bool Function(X509Certificate)? onBadCertificate, + }) : _eventBus = eventBus ?? EventBus(), _account = account, _isLogEnabled = isLogEnabled, _downloadSizeLimit = downloadSizeLimit { final config = _account.incoming!; if (config.serverConfig!.type == ServerType.imap) { _incomingMailClient = _IncomingImapClient( - _downloadSizeLimit, _eventBus, _isLogEnabled, logName, config, this); + _downloadSizeLimit, _eventBus, _isLogEnabled, logName, config, this, + onBadCertificate: onBadCertificate); } else if (config.serverConfig!.type == ServerType.pop) { _incomingMailClient = _IncomingPopClient( - _downloadSizeLimit, _eventBus, _isLogEnabled, logName, config, this); + _downloadSizeLimit, _eventBus, _isLogEnabled, logName, config, this, + onBadCertificate: onBadCertificate); } else { throw StateError( 'Unsupported incoming server type [${config.serverConfig!.typeName}].'); @@ -133,12 +139,14 @@ class MailClient { 'Warning: unknown outgoing server type ${outgoingConfig.serverConfig!.typeName}.'); } _outgoingMailClient = _OutgoingSmtpClient( - this, - _account.outgoingClientDomain, - _eventBus, - _isLogEnabled, - 'SMTP-$logName', - outgoingConfig); + this, + _account.outgoingClientDomain, + _eventBus, + _isLogEnabled, + 'SMTP-$logName', + outgoingConfig, + onBadCertificate: onBadCertificate, + ); } /// Adds the specified mail event [filter]. @@ -1116,9 +1124,14 @@ class _IncomingImapClient extends _IncomingMailClient { bool isLogEnabled, String? logName, MailServerConfig config, - MailClient mailClient) + MailClient mailClient, + {bool Function(X509Certificate)? onBadCertificate}) : _imapClient = ImapClient( - bus: eventBus, isLogEnabled: isLogEnabled, logName: logName), + bus: eventBus, + isLogEnabled: isLogEnabled, + logName: logName, + onBadCertificate: onBadCertificate, + ), super(downloadSizeLimit, config, mailClient) { eventBus.on().listen(_onImapEvent); } @@ -2091,9 +2104,13 @@ class _IncomingPopClient extends _IncomingMailClient { bool isLogEnabled, String? logName, MailServerConfig config, - MailClient mailClient) + MailClient mailClient, + {bool Function(X509Certificate)? onBadCertificate}) : _popClient = PopClient( - bus: eventBus, isLogEnabled: isLogEnabled, logName: logName), + bus: eventBus, + isLogEnabled: isLogEnabled, + logName: logName, + onBadCertificate: onBadCertificate), super(downloadSizeLimit, config, mailClient); @override @@ -2353,9 +2370,15 @@ class _OutgoingSmtpClient extends _OutgoingMailClient { final MailServerConfig _mailConfig; _OutgoingSmtpClient(this.mailClient, outgoingClientDomain, EventBus? eventBus, - bool isLogEnabled, String logName, MailServerConfig mailConfig) - : _smtpClient = SmtpClient(outgoingClientDomain, - bus: eventBus, isLogEnabled: isLogEnabled, logName: logName), + bool isLogEnabled, String logName, MailServerConfig mailConfig, + {bool Function(X509Certificate)? onBadCertificate}) + : _smtpClient = SmtpClient( + outgoingClientDomain, + bus: eventBus, + isLogEnabled: isLogEnabled, + logName: logName, + onBadCertificate: onBadCertificate, + ), _mailConfig = mailConfig; Future _connectOutgoingIfRequired() async { diff --git a/lib/pop/pop_client.dart b/lib/pop/pop_client.dart index a77f0c7a..f48029c3 100644 --- a/lib/pop/pop_client.dart +++ b/lib/pop/pop_client.dart @@ -1,3 +1,4 @@ +import 'dart:io'; import 'dart:typed_data'; import 'package:enough_mail/enough_mail.dart'; @@ -43,16 +44,20 @@ class PopClient extends ClientBase { /// Set [isLogEnabled] to `true` to see log output. /// Set the [logName] for adding the name to each log entry. /// Set the [connectionTimeout] in case the connection connection should timeout automatically after the given time. + /// [onBadCertificate] is an optional handler for unverifiable certificates. The handler receives the [X509Certificate], and can inspect it and decide (or let the user decide) whether to accept the connection or not. The handler should return true to continue the [SecureSocket] connection. PopClient({ EventBus? bus, bool isLogEnabled = false, String? logName, Duration? connectionTimeout, + bool Function(X509Certificate)? onBadCertificate, }) : _eventBus = bus ?? EventBus(), super( - isLogEnabled: isLogEnabled, - logName: logName, - connectionTimeout: connectionTimeout); + isLogEnabled: isLogEnabled, + logName: logName, + connectionTimeout: connectionTimeout, + onBadCertificate: onBadCertificate, + ); @override void onConnectionEstablished( diff --git a/lib/smtp/smtp_client.dart b/lib/smtp/smtp_client.dart index a5adf291..9e8fd766 100644 --- a/lib/smtp/smtp_client.dart +++ b/lib/smtp/smtp_client.dart @@ -1,3 +1,4 @@ +import 'dart:io'; import 'dart:typed_data'; import 'package:enough_mail/mail_address.dart'; import 'package:enough_mail/mime_data.dart'; @@ -104,18 +105,22 @@ class SmtpClient extends ClientBase { /// Set [isLogEnabled] to `true` to see log output. /// Set the [logName] for adding the name to each log entry. /// Set the [connectionTimeout] in case the connection connection should timeout automatically after the given time. + /// [onBadCertificate] is an optional handler for unverifiable certificates. The handler receives the [X509Certificate], and can inspect it and decide (or let the user decide) whether to accept the connection or not. The handler should return true to continue the [SecureSocket] connection. SmtpClient( String clientDomain, { EventBus? bus, bool isLogEnabled = false, String? logName, Duration? connectionTimeout, + bool Function(X509Certificate)? onBadCertificate, }) : _eventBus = bus ?? EventBus(), _clientDomain = clientDomain, super( - isLogEnabled: isLogEnabled, - logName: logName, - connectionTimeout: connectionTimeout); + isLogEnabled: isLogEnabled, + logName: logName, + connectionTimeout: connectionTimeout, + onBadCertificate: onBadCertificate, + ); @override void onConnectionEstablished( diff --git a/lib/src/util/client_base.dart b/lib/src/util/client_base.dart index 6c526c14..29e9fb11 100644 --- a/lib/src/util/client_base.dart +++ b/lib/src/util/client_base.dart @@ -27,6 +27,9 @@ abstract class ClientBase { bool _isConnected = false; + /// [onBadCertificate] is an optional handler for unverifiable certificates. The handler receives the [X509Certificate], and can inspect it and decide (or let the user decide) whether to accept the connection or not. The handler should return true to continue the [SecureSocket] connection. + final bool Function(X509Certificate)? onBadCertificate; + void onDataReceived(Uint8List data); void onConnectionEstablished( ConnectionInfo connectionInfo, String serverGreeting); @@ -39,7 +42,13 @@ abstract class ClientBase { /// Set [isLogEnabled] to `true` to see log output. /// Set the [logName] for adding the name to each log entry. /// Set the [connectionTimeout] in case the connection connection should timeout automatically after the given time. - ClientBase({this.isLogEnabled = false, this.logName, this.connectionTimeout}); + /// [onBadCertificate] is an optional handler for unverifiable certificates. The handler receives the [X509Certificate], and can inspect it and decide (or let the user decide) whether to accept the connection or not. The handler should return true to continue the [SecureSocket] connection. + ClientBase({ + this.isLogEnabled = false, + this.logName, + this.connectionTimeout, + this.onBadCertificate, + }); /// Connects to the specified server. /// @@ -50,7 +59,11 @@ abstract class ClientBase { initial: initialApp); connectionInfo = ConnectionInfo(host, port, isSecure); final socket = isSecure - ? await SecureSocket.connect(host, port) + ? await SecureSocket.connect( + host, + port, + onBadCertificate: onBadCertificate, + ) : await Socket.connect(host, port); _greetingsCompleter = Completer(); _isServerGreetingDone = false;