Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48322f38cb |
@@ -11,6 +11,7 @@ import 'package:sharedinbox/core/repositories/sync_log_repository.dart';
|
|||||||
import 'package:sharedinbox/core/utils/logger.dart';
|
import 'package:sharedinbox/core/utils/logger.dart';
|
||||||
import 'package:sharedinbox/data/imap/imap_client_factory.dart'
|
import 'package:sharedinbox/data/imap/imap_client_factory.dart'
|
||||||
show ImapConnectFn, connectImap, verboseLogKey;
|
show ImapConnectFn, connectImap, verboseLogKey;
|
||||||
|
import 'package:sharedinbox/data/imap/tls_error.dart' show isTlsConfigError;
|
||||||
|
|
||||||
typedef OnNewMailCallback = Future<void> Function(String accountEmail);
|
typedef OnNewMailCallback = Future<void> Function(String accountEmail);
|
||||||
|
|
||||||
@@ -291,6 +292,7 @@ class _AccountSync implements _SyncLoop {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool _isPermanentError(Object e) {
|
bool _isPermanentError(Object e) {
|
||||||
|
if (isTlsConfigError(e)) return true;
|
||||||
final s = e.toString().toLowerCase();
|
final s = e.toString().toLowerCase();
|
||||||
// enough_mail doesn't always have typed exceptions for auth, so we check strings.
|
// enough_mail doesn't always have typed exceptions for auth, so we check strings.
|
||||||
return s.contains('invalid credentials') ||
|
return s.contains('invalid credentials') ||
|
||||||
@@ -528,6 +530,7 @@ class _JmapAccountSync implements _SyncLoop {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool _isPermanentError(Object e) {
|
bool _isPermanentError(Object e) {
|
||||||
|
if (isTlsConfigError(e)) return true;
|
||||||
final s = e.toString().toLowerCase();
|
final s = e.toString().toLowerCase();
|
||||||
return s.contains('invalid credentials') ||
|
return s.contains('invalid credentials') ||
|
||||||
s.contains('authentication failed') ||
|
s.contains('authentication failed') ||
|
||||||
|
|||||||
@@ -21,15 +21,52 @@ class TlsModeMismatchException implements Exception {
|
|||||||
'STARTTLS). Original error: $original';
|
'STARTTLS). Original error: $original';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If [error] is a TLS handshake failure caused by a wrong-version-number
|
/// Wraps a TLS certificate verification failure into a user-actionable message.
|
||||||
/// (i.e. the server is not speaking TLS), throw a [TlsModeMismatchException]
|
///
|
||||||
/// with [host]/[port] context. Otherwise rethrow [error] unchanged.
|
/// Thrown when the server's certificate cannot be verified — either because it
|
||||||
|
/// is self-signed, expired, or the CA chain has changed since the account was
|
||||||
|
/// set up.
|
||||||
|
class TlsCertificateException implements Exception {
|
||||||
|
TlsCertificateException(this.host, this.port, this.original);
|
||||||
|
final String host;
|
||||||
|
final int port;
|
||||||
|
final Object original;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() =>
|
||||||
|
'TLS certificate error on $host:$port — the server certificate could '
|
||||||
|
'not be verified. The certificate may have changed or expired. '
|
||||||
|
'Please re-check your account settings or contact your mail provider. '
|
||||||
|
'Original error: $original';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if [error] is a permanent TLS configuration error that will
|
||||||
|
/// not resolve on its own and requires user action.
|
||||||
|
bool isTlsConfigError(Object error) =>
|
||||||
|
error is TlsModeMismatchException || error is TlsCertificateException;
|
||||||
|
|
||||||
|
/// If [error] is a recognisable TLS handshake failure, wraps it in a typed
|
||||||
|
/// exception and throws it. Otherwise rethrows [error] unchanged.
|
||||||
|
///
|
||||||
|
/// Recognised patterns:
|
||||||
|
/// - `WRONG_VERSION_NUMBER` → [TlsModeMismatchException] (port/mode mismatch)
|
||||||
|
/// - `CERTIFICATE_VERIFY_FAILED` / `HandshakeException` → [TlsCertificateException]
|
||||||
Never rethrowAsTlsHint(Object error, StackTrace stack, String host, int port) {
|
Never rethrowAsTlsHint(Object error, StackTrace stack, String host, int port) {
|
||||||
if (error.toString().contains('WRONG_VERSION_NUMBER')) {
|
final s = error.toString();
|
||||||
|
if (s.contains('WRONG_VERSION_NUMBER')) {
|
||||||
Error.throwWithStackTrace(
|
Error.throwWithStackTrace(
|
||||||
TlsModeMismatchException(host, port, error),
|
TlsModeMismatchException(host, port, error),
|
||||||
stack,
|
stack,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (s.contains('CERTIFICATE_VERIFY_FAILED') ||
|
||||||
|
s.contains('HandshakeException') ||
|
||||||
|
s.contains('CERTIFICATE_EXPIRED') ||
|
||||||
|
s.contains('CERTIFICATE_UNKNOWN')) {
|
||||||
|
Error.throwWithStackTrace(
|
||||||
|
TlsCertificateException(host, port, error),
|
||||||
|
stack,
|
||||||
|
);
|
||||||
|
}
|
||||||
Error.throwWithStackTrace(error, stack);
|
Error.throwWithStackTrace(error, stack);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user