diff --git a/CHANGELOG.md b/CHANGELOG.md index f39d0393..638cd735 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +# 2.2.3 +* Chore: Shorten package description to 60-180 characters for pub.dev score +* Docs: Add example file (example/example.dart) +* Fix: Resolve dangling library doc comments in all library files +* Fix: Replace angle brackets with placeholders in documentation comments +* Chore: Format all Dart files with dart format + +# 2.2.2 +* Fix: `removeHeader()` now correctly removes headers from rendered messages. Previously, headers modified via `removeHeader()`, `setHeader()`, or `addHeader()` were not reflected in `renderMessage()` output because the render method used original mimeData instead of modified headers. + +# 2.2.1 +* Fix: Tolerate bare LF (`\n`) line endings in MIME parsing. RFC 5322 mandates CRLF but some generators (e.g. Node.js mimetext on Linux) produce bare LF. This caused missing subjects and headers when parsing such MIME. + +# 2.2.0 +* Maintenance fork of `enough_mail` +* Fix: Prevent header folding immediately after opening angle bracket `<` in `From` and other headers to avoid SpamAssassin errors. + # 2.1.7 * chore: Update plugin and dependencies versions - thanks to [Dr-Usman](https://github.com/Dr-Usman)! * chore: pub upgrade to bring in intl 0.20.2 - thanks to [jpohhhh](https://github.com/jpohhhh)! @@ -72,7 +89,7 @@ OauthAuthentication now contains a complete OauthToken. * Simplify search API. Breaking changes: -* Package structure is simplified, so that imports of specific classes are not possible anymore. Instead either `import 'package:enough_mail/enough_mail.dart';` or one of the specializes sub-packages `codecs.dart`,`discover.dart`, `highlevel.dart`, `imap.dart`, `mime.dart`, `pop.dart` or `smtp.dart`. +* Package structure is simplified, so that imports of specific classes are not possible anymore. Instead either `import 'package:enough_mail_plus/enough_mail.dart';` or one of the specializes sub-packages `codecs.dart`,`discover.dart`, `highlevel.dart`, `imap.dart`, `mime.dart`, `pop.dart` or `smtp.dart`. * `Authentication.passwordCleartext` is renamed to `Authentication.passwordClearText` * `Mailbox` API has changed specifically when creating mailboxes yourself. @@ -300,7 +317,7 @@ Other: ## 0.0.30 - Thanks to [hpoul](https://github.com/hpoul) the XML library now works with both beta and stable flutter channels. - Thanks to [hydeparkk](https://github.com/hydeparkk) encoded mailbox paths are now used in copy, move, status and append/ -- Fix decoding message date headers +- Fix decoding bug for UTF8 8 bit encoded text - Fix handling mailboxes with a space in their path - Allow to easly serialize and deserialize [MailAccount](https://pub.dev/documentation/enough_mail/latest/mail_mail_account/MailAccount-class.html) to/from JSON. - Extended high level [MailClient API](https://pub.dev/documentation/enough_mail/latest/mail_mail_client/MailClient-class.html): diff --git a/README.md b/README.md index e1b56616..1cdecd6f 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,26 @@ +# enough_mail_plus + +**Maintenance fork of [enough_mail](https://pub.dev/packages/enough_mail)** + IMAP, POP3 and SMTP clients for Dart and Flutter email developers. Available under the commercial friendly [MPL Mozilla Public License 2.0](https://www.mozilla.org/en-US/MPL/). +## Key Fixes in this Fork +* **Header Folding Fix**: Prevents invalid folding after `<` in headers, resolving common SpamAssassin errors like "Leading whitespace after '<'" and "unbalanced angle brackets". ## Installation Add this dependency your pubspec.yaml file: -``` +```yaml dependencies: - enough_mail: ^2.1.7 + enough_mail_plus: ^2.2.0 ``` -The latest version or `enough_mail` is [![enough_mail version](https://img.shields.io/pub/v/enough_mail.svg)](https://pub.dartlang.org/packages/enough_mail). - ## API Documentation -Check out the full API documentation at https://pub.dev/documentation/enough_mail/latest/ +Check out the full API documentation at https://pub.dev/documentation/enough_mail_plus/latest/ + ## High Level API Usage @@ -24,7 +29,7 @@ A simple usage example for using the high level API: ```dart import 'dart:io'; -import 'package:enough_mail/enough_mail.dart'; +import 'package:enough_mail_plus/enough_mail.dart'; String userName = 'user.name'; String password = 'password'; @@ -112,7 +117,7 @@ A simple usage example for using the low level API: ```dart import 'dart:io'; -import 'package:enough_mail/enough_mail.dart'; +import 'package:enough_mail_plus/enough_mail.dart'; String userName = 'user.name'; String password = 'password'; diff --git a/example/discover.dart b/example/discover.dart index d1b618ac..546f1724 100644 --- a/example/discover.dart +++ b/example/discover.dart @@ -1,6 +1,6 @@ import 'dart:io'; -import 'package:enough_mail/discover.dart'; +import 'package:enough_mail_plus/discover.dart'; // ignore: avoid_void_async void main(List args) async { diff --git a/example/enough_mail_example.dart b/example/enough_mail_example.dart index 334dd6fe..047e3352 100644 --- a/example/enough_mail_example.dart +++ b/example/enough_mail_example.dart @@ -1,6 +1,6 @@ import 'dart:io'; -import 'package:enough_mail/enough_mail.dart'; +import 'package:enough_mail_plus/enough_mail.dart'; String userName = 'user.name'; String password = 'password'; diff --git a/example/example.dart b/example/example.dart new file mode 100644 index 00000000..57328ff7 --- /dev/null +++ b/example/example.dart @@ -0,0 +1,52 @@ +import 'package:enough_mail_plus/enough_mail.dart'; + +/// Simple example demonstrating basic email discovery and IMAP usage. +void main() async { + // Auto-discover email settings + const email = 'user@example.com'; + final config = await Discover.discover(email, isLogEnabled: false); + + if (config == null) { + print('Unable to discover settings for $email'); + return; + } + + print('Found settings for $email:'); + for (final provider in config.emailProviders ?? []) { + print('Provider: ${provider.displayName}'); + print('Domains: ${provider.domains}'); + } + + // IMAP example + final imapClient = ImapClient(isLogEnabled: false); + await imapClient.connectToServer('imap.example.com', 993, isSecure: true); + await imapClient.login('user@example.com', 'password'); + await imapClient.selectInbox(); + + final messages = await imapClient.fetchRecentMessages(messageCount: 5); + for (final message in messages.messages) { + print('From: ${message.from}'); + print('Subject: ${message.decodeSubject()}'); + } + + await imapClient.logout(); + + // SMTP example + final smtpClient = SmtpClient('example.com', isLogEnabled: false); + await smtpClient.connectToServer('smtp.example.com', 587, isSecure: false); + await smtpClient.ehlo(); + if (smtpClient.serverInfo.supportsAuth(AuthMechanism.plain)) { + await smtpClient.authenticate( + 'user@example.com', 'password', AuthMechanism.plain); + } + + final builder = MessageBuilder() + ..from = [const MailAddress('Sender', 'sender@example.com')] + ..to = [const MailAddress('Recipient', 'recipient@example.com')] + ..subject = 'Test Email' + ..text = 'Hello from enough_mail_plus!'; + + final message = builder.buildMimeMessage(); + await smtpClient.sendMessage(message); + print('Email sent successfully!'); +} diff --git a/lib/codecs.dart b/lib/codecs.dart index b7016e8b..e84010ef 100644 --- a/lib/codecs.dart +++ b/lib/codecs.dart @@ -1,3 +1,5 @@ +library codecs; + /// Email codec classes export 'mime.dart'; export 'src/codecs/base64_mail_codec.dart'; diff --git a/lib/discover.dart b/lib/discover.dart index 7b5ebb2c..e8afad5f 100644 --- a/lib/discover.dart +++ b/lib/discover.dart @@ -1,4 +1,5 @@ -/// Discovers email settings based on an email address. +library discover; +/// Discovers email settings based on an email address. export 'src/discover/client_config.dart'; export 'src/discover/discover.dart'; diff --git a/lib/highlevel.dart b/lib/highlevel.dart index 814ccc67..4355e444 100644 --- a/lib/highlevel.dart +++ b/lib/highlevel.dart @@ -1,3 +1,5 @@ +library highlevel; + /// Highlevel email API /// /// Start with `MailClient` to connect to any mail service. diff --git a/lib/imap.dart b/lib/imap.dart index a5869ac1..2a9b894a 100644 --- a/lib/imap.dart +++ b/lib/imap.dart @@ -1,3 +1,5 @@ +library imap; + /// Anything you need to fetch and process messages using the IMAP protocol. /// /// Use the `ImapClient` to connect to any IMAP compliant service. diff --git a/lib/mime.dart b/lib/mime.dart index 1c09851f..101a3d6c 100644 --- a/lib/mime.dart +++ b/lib/mime.dart @@ -1,3 +1,5 @@ +library mime; + /// Base email classes export 'src/exception.dart'; export 'src/imap/message_sequence.dart'; diff --git a/lib/pop.dart b/lib/pop.dart index 4c7e0408..23893fdc 100644 --- a/lib/pop.dart +++ b/lib/pop.dart @@ -1,7 +1,8 @@ +library pop; + /// Fetch messages using the POP3 protocol /// /// Start with the `PopClient` to connect to a POP3 enabled service. - export 'mime.dart'; export 'src/pop/pop_client.dart'; export 'src/pop/pop_events.dart'; diff --git a/lib/smtp.dart b/lib/smtp.dart index 059eaa21..9a69624d 100644 --- a/lib/smtp.dart +++ b/lib/smtp.dart @@ -1,3 +1,5 @@ +library smtp; + /// Everything you need to send messages using the SMTP protocol. /// /// With the `SmtpClient` you can connect to any SMTP service. diff --git a/lib/src/discover/client_config.dart b/lib/src/discover/client_config.dart index e62dce82..2d1a5a77 100644 --- a/lib/src/discover/client_config.dart +++ b/lib/src/discover/client_config.dart @@ -350,7 +350,6 @@ class ServerConfig { return email.substring(lastAtIndex + 1); case UsernameType.realName: case UsernameType.unknown: - default: return null; } } diff --git a/lib/src/message_builder.dart b/lib/src/message_builder.dart index a1beb0fc..5adc2a25 100644 --- a/lib/src/message_builder.dart +++ b/lib/src/message_builder.dart @@ -736,7 +736,7 @@ class MessageBuilder extends PartBuilder { /// /// You can also specify a custom [replyHeaderTemplate], which is only used /// when [quoteOriginalText] has been set to true. The default - /// replyHeaderTemplate is 'On wrote:'. + /// replyHeaderTemplate is 'On {date} {from} wrote:'. /// /// Set [replyToSimplifyReferences] to true if the References field /// should not contain the references of all messages in this thread. diff --git a/lib/src/mime_data.dart b/lib/src/mime_data.dart index dc5a3bda..136d3fb0 100644 --- a/lib/src/mime_data.dart +++ b/lib/src/mime_data.dart @@ -106,11 +106,20 @@ class TextMimeData extends MimeData { /// Creates a new text based mime data /// /// with the specified [text] and the [containsHeader] information. - TextMimeData(this.text, {required bool containsHeader}) - : super(containsHeader: containsHeader) { - _size = text.length; + /// + /// Line endings are automatically normalized to CRLF (`\r\n`) for + /// RFC 5322 compliance, tolerating bare LF from non-conformant MIME + /// generators (e.g. Node.js mimetext on Linux which uses `os.EOL`). + TextMimeData(String text, {required bool containsHeader}) + : text = _normalizeLineEndings(text), + super(containsHeader: containsHeader) { + _size = this.text.length; } + /// Normalizes bare LF to CRLF for RFC 5322 compliance. + static String _normalizeLineEndings(String text) => + text.replaceAll(RegExp(r'(?? parts; bool _isParsed = false; + bool _headersModified = false; String? _decodedText; DateTime? _decodedDate; ContentTypeHeader? _contentTypeHeader; @@ -116,6 +117,7 @@ class MimePart { } final header = Header(name, localValue, encoding); headers?.add(header); + _headersModified = true; } /// Sets a header with the specified [name], [value] and optional [encoding], @@ -140,6 +142,7 @@ class MimePart { } } headers?.add(Header(name, localValue, encoding)); + _headersModified = true; } /// Removes the header with the specified [name]. @@ -147,6 +150,7 @@ class MimePart { headers ??=
[]; final lowerCaseName = name.toLowerCase(); headers?.removeWhere((h) => h.lowerCaseName == lowerCaseName); + _headersModified = true; } /// Inserts the [part] at the beginning of all parts. @@ -421,6 +425,7 @@ class MimePart { /// Parses this and all children MIME parts. void parse() { _isParsed = true; + _headersModified = false; final mimeData = this.mimeData; final parts = this.parts; if (mimeData != null) { @@ -457,11 +462,13 @@ class MimePart { void render(StringBuffer buffer, {bool renderHeader = true}) { final mimeData = this.mimeData; if (mimeData != null) { - if (!mimeData.containsHeader && renderHeader) { + if ((!mimeData.containsHeader || _headersModified) && renderHeader) { _renderHeaders(buffer); buffer.write('\r\n'); } - mimeData.render(buffer); + // If headers have been modified, skip rendering headers from mimeData + final renderDataHeader = !_headersModified || !mimeData.containsHeader; + mimeData.render(buffer, renderHeader: renderDataHeader); } else { if (renderHeader) { _renderHeaders(buffer); @@ -1344,50 +1351,89 @@ class Header { /// Renders this header into a the [buffer] wrapping it if necessary. void render(StringBuffer buffer) { final value = this.value; - var length = name.length + ': '.length + (value?.length ?? 0); + if (value == null) { + buffer + ..write(name) + ..write(': \r\n'); + + return; + } + final totalLength = value.length; + var currentLineLength = name.length + ': '.length; buffer ..write(name) ..write(': '); - if (value == null || length < MailConventions.textLineMaxLength) { - if (value != null) { - buffer.write(value); - } - buffer.write('\r\n'); + if (currentLineLength + totalLength < MailConventions.textLineMaxLength) { + buffer + ..write(value) + ..write('\r\n'); return; } - var currentLineLength = name.length + ': '.length; - length -= name.length + ': '.length; - final runes = value.runes.toList(); var startIndex = 0; - while (length > 0) { + while (startIndex < totalLength) { var chunkLength = MailConventions.textLineMaxLength - currentLineLength; - if (startIndex + chunkLength >= value.length) { + if (startIndex + chunkLength >= totalLength) { // write reminder: buffer ..write(value.substring(startIndex).trim()) ..write('\r\n'); break; } - for (var runeIndex = startIndex + chunkLength; - runeIndex > startIndex; - runeIndex--) { - final rune = runes[runeIndex]; - if (rune == AsciiRunes.runeSemicolon || - rune == AsciiRunes.runeSpace || - rune == AsciiRunes.runeClosingParentheses || - rune == AsciiRunes.runeClosingBracket || - rune == AsciiRunes.runeGreaterThan) { - chunkLength = runeIndex - startIndex + 1; + var foundFoldingPoint = false; + for (var i = startIndex + chunkLength; i > startIndex; i--) { + final char = value.codeUnitAt(i); + if (char == AsciiRunes.runeSemicolon || + char == AsciiRunes.runeSpace || + char == AsciiRunes.runeClosingParentheses || + char == AsciiRunes.runeClosingBracket || + char == AsciiRunes.runeGreaterThan || + char == AsciiRunes.runeComma) { + chunkLength = i - startIndex + 1; + foundFoldingPoint = true; break; } } + if (!foundFoldingPoint) { + // try to find a folding point after chunkLength + // up to messageLineMaxLength + for (var i = startIndex + chunkLength + 1; i < totalLength; i++) { + if (currentLineLength + (i - startIndex) >= + MailConventions.messageLineMaxLength) { + chunkLength = i - startIndex; + // avoid splitting surrogate pairs + if (chunkLength > 0 && + value.codeUnitAt(startIndex + chunkLength - 1) >= 0xD800 && + value.codeUnitAt(startIndex + chunkLength - 1) <= 0xDBFF) { + chunkLength--; + } + break; + } + final char = value.codeUnitAt(i); + if (char == AsciiRunes.runeSemicolon || + char == AsciiRunes.runeSpace || + char == AsciiRunes.runeClosingParentheses || + char == AsciiRunes.runeClosingBracket || + char == AsciiRunes.runeGreaterThan || + char == AsciiRunes.runeComma) { + chunkLength = i - startIndex + 1; + foundFoldingPoint = true; + break; + } + } + if (!foundFoldingPoint && startIndex + chunkLength < totalLength) { + // check if we can just take the rest of the string if it's under 998: + if (currentLineLength + (totalLength - startIndex) < + MailConventions.messageLineMaxLength) { + chunkLength = totalLength - startIndex; + } + } + } buffer ..write(value.substring(startIndex, startIndex + chunkLength).trim()) ..write('\r\n'); - length -= chunkLength; startIndex += chunkLength; - if (length > 0) { + if (startIndex < totalLength) { buffer.writeCharCode(AsciiRunes.runeTab); currentLineLength = 1; } diff --git a/pubspec.yaml b/pubspec.yaml index aed7227b..c57afecc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,9 +1,7 @@ -name: enough_mail -description: IMAP, POP3 and SMTP for email developers. Choose between a low - level and a high level API for mailing. Parse and generate MIME messages. - Discover email settings. -version: 2.1.7 -homepage: https://github.com/Enough-Software/enough_mail +name: enough_mail_plus +description: "enough_mail fork - IMAP, POP3, SMTP email library with high/low-level APIs and MIME parsing" +version: 2.2.3 +repository: https://github.com/nogringo/enough_mail topics: - email - imap @@ -23,7 +21,7 @@ dependencies: encrypter_plus: ^5.1.0 enough_convert: ^1.6.0 event_bus: ^2.0.1 - intl: any + intl: ^0.20.2 json_annotation: ^4.9.0 pointycastle: ^4.0.0 synchronized: ^3.4.0 diff --git a/test/codecs/base64_mail_codec_test.dart b/test/codecs/base64_mail_codec_test.dart index 17851113..65bc41e0 100644 --- a/test/codecs/base64_mail_codec_test.dart +++ b/test/codecs/base64_mail_codec_test.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'package:enough_mail/src/codecs/mail_codec.dart'; +import 'package:enough_mail_plus/src/codecs/mail_codec.dart'; import 'package:test/test.dart'; // cSpell:disable diff --git a/test/codecs/date_codec_test.dart b/test/codecs/date_codec_test.dart index edc09d72..065bfa15 100644 --- a/test/codecs/date_codec_test.dart +++ b/test/codecs/date_codec_test.dart @@ -1,4 +1,4 @@ -import 'package:enough_mail/src/codecs/date_codec.dart'; +import 'package:enough_mail_plus/src/codecs/date_codec.dart'; import 'package:test/test.dart'; import 'package:timezone/data/latest.dart' as tz; import 'package:timezone/timezone.dart' as tz; diff --git a/test/codecs/folding_test.dart b/test/codecs/folding_test.dart index a523366b..84671bbe 100644 --- a/test/codecs/folding_test.dart +++ b/test/codecs/folding_test.dart @@ -1,7 +1,7 @@ -import 'package:enough_mail/src/codecs/mail_codec.dart'; -import 'package:enough_mail/src/mail_address.dart'; -import 'package:enough_mail/src/message_builder.dart'; -import 'package:enough_mail/src/mime_message.dart'; +import 'package:enough_mail_plus/src/codecs/mail_codec.dart'; +import 'package:enough_mail_plus/src/mail_address.dart'; +import 'package:enough_mail_plus/src/message_builder.dart'; +import 'package:enough_mail_plus/src/mime_message.dart'; import 'package:test/test.dart'; // cSpell:disable diff --git a/test/codecs/mail_codec_test.dart b/test/codecs/mail_codec_test.dart index 4a4b5287..2c8aa24b 100644 --- a/test/codecs/mail_codec_test.dart +++ b/test/codecs/mail_codec_test.dart @@ -1,4 +1,4 @@ -import 'package:enough_mail/src/codecs/mail_codec.dart'; +import 'package:enough_mail_plus/src/codecs/mail_codec.dart'; import 'package:test/test.dart'; // cSpell:disable diff --git a/test/codecs/modified_utf7_codec_test.dart b/test/codecs/modified_utf7_codec_test.dart index 8511c275..e72a6c84 100644 --- a/test/codecs/modified_utf7_codec_test.dart +++ b/test/codecs/modified_utf7_codec_test.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'package:enough_mail/src/codecs/modified_utf7_codec.dart'; +import 'package:enough_mail_plus/src/codecs/modified_utf7_codec.dart'; import 'package:test/test.dart'; void main() { diff --git a/test/codecs/quoted_printable_mail_codec_test.dart b/test/codecs/quoted_printable_mail_codec_test.dart index 5066667f..889ae145 100644 --- a/test/codecs/quoted_printable_mail_codec_test.dart +++ b/test/codecs/quoted_printable_mail_codec_test.dart @@ -1,6 +1,6 @@ import 'dart:convert' as convert; -import 'package:enough_mail/src/codecs/mail_codec.dart'; +import 'package:enough_mail_plus/src/codecs/mail_codec.dart'; import 'package:test/test.dart'; // cSpell:disable diff --git a/test/folding_test.dart b/test/folding_test.dart new file mode 100644 index 00000000..c546cb0e --- /dev/null +++ b/test/folding_test.dart @@ -0,0 +1,17 @@ +import 'package:enough_mail_plus/enough_mail.dart'; +import 'package:test/test.dart'; + +void main() { + test('Should not fold long email address in From header', () { + const longEmail = + 'npub1mlfgj95gv89tdjemt2jc4jqvn4dumhujeymzv20ljqywcfupwz5s09pe55@testnm' + 'ail.uid.ovh'; + + final builder = MessageBuilder() + ..from = [const MailAddress('Test', longEmail)]; + final mimeMessage = builder.buildMimeMessage(); + final rendered = mimeMessage.renderMessage(); + + expect(rendered, contains('<$longEmail>')); + }); +} diff --git a/test/imap/imap_client_test.dart b/test/imap/imap_client_test.dart index 37ce7068..5088fff6 100644 --- a/test/imap/imap_client_test.dart +++ b/test/imap/imap_client_test.dart @@ -4,8 +4,8 @@ import 'dart:async'; import 'dart:io' show Platform; -import 'package:enough_mail/enough_mail.dart'; -import 'package:enough_mail/src/private/util/client_base.dart'; +import 'package:enough_mail_plus/enough_mail.dart'; +import 'package:enough_mail_plus/src/private/util/client_base.dart'; import 'package:event_bus/event_bus.dart'; import 'package:test/test.dart'; diff --git a/test/imap/mailbox_test.dart b/test/imap/mailbox_test.dart index 2e4ac07b..c1c2521b 100644 --- a/test/imap/mailbox_test.dart +++ b/test/imap/mailbox_test.dart @@ -1,4 +1,4 @@ -import 'package:enough_mail/src/imap/mailbox.dart'; +import 'package:enough_mail_plus/src/imap/mailbox.dart'; import 'package:test/test.dart'; // cSpell:disable diff --git a/test/imap/message_sequence_test.dart b/test/imap/message_sequence_test.dart index 90c8cf7f..0d425191 100644 --- a/test/imap/message_sequence_test.dart +++ b/test/imap/message_sequence_test.dart @@ -1,5 +1,5 @@ -import 'package:enough_mail/src/exception.dart'; -import 'package:enough_mail/src/imap/message_sequence.dart'; +import 'package:enough_mail_plus/src/exception.dart'; +import 'package:enough_mail_plus/src/imap/message_sequence.dart'; import 'package:test/test.dart'; void main() { diff --git a/test/imap/qresync_test.dart b/test/imap/qresync_test.dart index b376f9a8..666684da 100644 --- a/test/imap/qresync_test.dart +++ b/test/imap/qresync_test.dart @@ -1,4 +1,4 @@ -import 'package:enough_mail/enough_mail.dart'; +import 'package:enough_mail_plus/enough_mail.dart'; import 'package:test/test.dart'; // cSpell:disable diff --git a/test/imap/response_test.dart b/test/imap/response_test.dart index a9294e34..5ddc9168 100644 --- a/test/imap/response_test.dart +++ b/test/imap/response_test.dart @@ -1,4 +1,4 @@ -import 'package:enough_mail/enough_mail.dart'; +import 'package:enough_mail_plus/enough_mail.dart'; import 'package:test/test.dart'; // cSpell:disable diff --git a/test/mail/mail_account_test.dart b/test/mail/mail_account_test.dart index 4c5f40f4..46dce154 100644 --- a/test/mail/mail_account_test.dart +++ b/test/mail/mail_account_test.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'package:enough_mail/enough_mail.dart'; +import 'package:enough_mail_plus/enough_mail.dart'; import 'package:test/test.dart'; void main() { diff --git a/test/mail/results_test.dart b/test/mail/results_test.dart index 9a47dd25..b64fad29 100644 --- a/test/mail/results_test.dart +++ b/test/mail/results_test.dart @@ -1,4 +1,4 @@ -import 'package:enough_mail/enough_mail.dart'; +import 'package:enough_mail_plus/enough_mail.dart'; import 'package:test/test.dart'; void main() { diff --git a/test/message_builder_test.dart b/test/message_builder_test.dart index 9dbca509..e1d8f31a 100644 --- a/test/message_builder_test.dart +++ b/test/message_builder_test.dart @@ -1,12 +1,12 @@ import 'dart:io'; import 'dart:typed_data'; -import 'package:enough_mail/src/mail_address.dart'; -import 'package:enough_mail/src/mail_conventions.dart'; -import 'package:enough_mail/src/media_type.dart'; -import 'package:enough_mail/src/message_builder.dart'; -import 'package:enough_mail/src/mime_data.dart'; -import 'package:enough_mail/src/mime_message.dart'; +import 'package:enough_mail_plus/src/mail_address.dart'; +import 'package:enough_mail_plus/src/mail_conventions.dart'; +import 'package:enough_mail_plus/src/media_type.dart'; +import 'package:enough_mail_plus/src/message_builder.dart'; +import 'package:enough_mail_plus/src/mime_data.dart'; +import 'package:enough_mail_plus/src/mime_message.dart'; import 'package:test/test.dart'; // cSpell:disable diff --git a/test/mime_bare_lf_test.dart b/test/mime_bare_lf_test.dart new file mode 100644 index 00000000..9108fa9f --- /dev/null +++ b/test/mime_bare_lf_test.dart @@ -0,0 +1,98 @@ +import 'package:enough_mail_plus/enough_mail.dart'; +import 'package:test/test.dart'; + +// cSpell:disable + +/// Real-world MIME generated by mimetext on Linux (bare LF line endings). +/// Source: nostr-mail-js output.eml +/// This tests that enough_mail can parse non-RFC-5322-compliant MIME +/// that uses bare `\n` instead of `\r\n`. +void main() { + group('MimeMessage with bare LF (nostr-mail-js output)', () { + // This MIME was generated by mimetext 3.0.28 on Linux (node:os.EOL = '\n') + const mimeWithBareLF = 'Date: Sun, 05 Apr 2026 22:33:05 +0000\n' + 'From: \n' + 'To: \n' + 'Message-ID: \n' + 'Subject: =?utf-8?B?bGllbg==?=\n' + 'MIME-Version: 1.0\n' + 'Content-Type: multipart/alternative; boundary=ht75jd93mzb\n' + '\n' + '--ht75jd93mzb\n' + 'Content-Type: text/plain; charset=UTF-8\n' + 'Content-Transfer-Encoding: 7bit\n' + '\n' + 'https://nostrmail.org\n' + '\n' + '--ht75jd93mzb\n' + 'Content-Type: text/html; charset=UTF-8\n' + 'Content-Transfer-Encoding: 7bit\n' + '\n' + 'https://nostrmail.org\n' + '\n' + '--ht75jd93mzb--\n'; + + test('parses headers with bare LF', () { + final message = MimeMessage.parseFromText(mimeWithBareLF); + + expect(message.decodeSubject(), 'lien', + reason: 'RFC 2047 subject should be decoded'); + expect(message.decodeDate(), isNotNull, + reason: 'Date header should be parsed'); + expect(message.decodeHeaderDateValue('date'), isA()); + }); + + test('parses From address with bare LF', () { + final message = MimeMessage.parseFromText(mimeWithBareLF); + final from = message.from; + + expect(from, isNotNull); + expect(from, hasLength(1)); + expect(from![0].email, 'test@nostr'); + }); + + test('parses multipart/alternative structure with bare LF', () { + final message = MimeMessage.parseFromText(mimeWithBareLF); + + expect(message.mediaType.isMultipart, isTrue); + expect(message.parts, isNotNull); + expect(message.parts, hasLength(2)); + }); + + test('extracts plain text part with bare LF', () { + final message = MimeMessage.parseFromText(mimeWithBareLF); + + final plainText = message.decodeTextPlainPart(); + expect(plainText, isNotNull); + expect(plainText, contains('https://nostrmail.org')); + }); + + test('extracts HTML part with bare LF', () { + final message = MimeMessage.parseFromText(mimeWithBareLF); + + final html = message.decodeTextHtmlPart(); + expect(html, isNotNull); + expect(html, contains('')); + }); + + test('parses boundary correctly with bare LF', () { + final message = MimeMessage.parseFromText(mimeWithBareLF); + final contentType = message.getHeaderContentType(); + + expect(contentType, isNotNull); + expect(contentType!.boundary, 'ht75jd93mzb'); + }); + + test('roundtrip: render and re-parse with bare LF', () { + final message = MimeMessage.parseFromText(mimeWithBareLF); + final rendered = message.renderMessage(); + + // After re-parsing, all data should be preserved + final reParsed = MimeMessage.parseFromText(rendered); + expect(reParsed.decodeSubject(), 'lien'); + expect(reParsed.decodeTextPlainPart(), contains('https://nostrmail.org')); + expect(reParsed.decodeTextHtmlPart(), + contains('')); + }); + }); +} diff --git a/test/mime_message_test.dart b/test/mime_message_test.dart index 8cbc6d85..2c29a8fb 100644 --- a/test/mime_message_test.dart +++ b/test/mime_message_test.dart @@ -3,11 +3,11 @@ import 'dart:typed_data'; import 'package:collection/collection.dart' show IterableExtension; import 'package:enough_convert/enough_convert.dart'; -import 'package:enough_mail/src/codecs/date_codec.dart'; -import 'package:enough_mail/src/codecs/mail_codec.dart'; -import 'package:enough_mail/src/mail_address.dart'; -import 'package:enough_mail/src/media_type.dart'; -import 'package:enough_mail/src/mime_message.dart'; +import 'package:enough_mail_plus/src/codecs/date_codec.dart'; +import 'package:enough_mail_plus/src/codecs/mail_codec.dart'; +import 'package:enough_mail_plus/src/mail_address.dart'; +import 'package:enough_mail_plus/src/media_type.dart'; +import 'package:enough_mail_plus/src/mime_message.dart'; import 'package:test/test.dart'; // cSpell:disable @@ -900,28 +900,17 @@ Content-type: text/plain; charset=ISO-8859-1\r }); test('header?.render() long line without split pos', () { - final header = Header( - 'Content-Type', - '1234567890123456789012345678901234567890123456789012345678901234' - '5678901234567890123456789012345678901234567890123456789012345678' - '90123456789012345678901234567890', - ); + const headerValue = + '1234567890123456789012345678901234567890123456789012345678901234' + '5678901234567890123456789012345678901234567890123456789012345678' + '90123456789012345678901234567890'; + final header = Header('Content-Type', headerValue); final buffer = StringBuffer(); header.render(buffer); final text = buffer.toString().split('\r\n'); - expect(text.length, 4); - expect( - text[0], - 'Content-Type: 123456789012345678901234567890123456789012345678901' - '23456789012', - ); - expect( - text[1], - '\t345678901234567890123456789012345678901234567890123456789012345' - '678901234567', - ); - expect(text[2], '\t89012345678901234567890'); - expect(text[3], ''); + expect(text.length, 2); + expect(text[0], 'Content-Type: $headerValue'); + expect(text[1], ''); }); }); diff --git a/test/pop/pop_client_test.dart b/test/pop/pop_client_test.dart index 2c66e707..5c0598b0 100644 --- a/test/pop/pop_client_test.dart +++ b/test/pop/pop_client_test.dart @@ -2,8 +2,8 @@ import 'dart:io'; -import 'package:enough_mail/enough_mail.dart'; -import 'package:enough_mail/src/private/util/client_base.dart'; +import 'package:enough_mail_plus/enough_mail.dart'; +import 'package:enough_mail_plus/src/private/util/client_base.dart'; import 'package:event_bus/event_bus.dart'; import 'package:test/test.dart'; diff --git a/test/remove_header_test.dart b/test/remove_header_test.dart new file mode 100644 index 00000000..0f6e955d --- /dev/null +++ b/test/remove_header_test.dart @@ -0,0 +1,20 @@ +import 'package:enough_mail_plus/enough_mail.dart'; +import 'package:test/test.dart'; + +void main() { + test('bcc cleanup', () { + final builder = MessageBuilder.prepareMultipartAlternativeMessage( + plainText: 'Hello world!', + htmlText: '

Hello world!

', + )..bcc = [const MailAddress(null, 'bcc@domain.com')]; + + final message = builder.buildMimeMessage(); + final rawContent = message.renderMessage(); + + final tempMessage = MimeMessage.parseFromText(rawContent) + ..removeHeader('bcc'); + + expect( + tempMessage.renderMessage().contains('Bcc: bcc@domain.com'), isFalse); + }); +} diff --git a/test/smtp/smtp_client_test.dart b/test/smtp/smtp_client_test.dart index b972e9ea..3cf77fa0 100644 --- a/test/smtp/smtp_client_test.dart +++ b/test/smtp/smtp_client_test.dart @@ -1,8 +1,8 @@ import 'dart:io'; -import 'package:enough_mail/enough_mail.dart'; -import 'package:enough_mail/src/private/smtp/smtp_command.dart'; -import 'package:enough_mail/src/private/util/client_base.dart'; +import 'package:enough_mail_plus/enough_mail.dart'; +import 'package:enough_mail_plus/src/private/smtp/smtp_command.dart'; +import 'package:enough_mail_plus/src/private/util/client_base.dart'; import 'package:event_bus/event_bus.dart'; import 'package:test/test.dart'; diff --git a/test/src/imap/fetch_parser_test.dart b/test/src/imap/fetch_parser_test.dart index f0fcad9f..2481b99f 100644 --- a/test/src/imap/fetch_parser_test.dart +++ b/test/src/imap/fetch_parser_test.dart @@ -2,10 +2,10 @@ import 'dart:convert'; import 'dart:typed_data'; import 'package:enough_convert/enough_convert.dart'; -import 'package:enough_mail/enough_mail.dart'; -import 'package:enough_mail/src/private/imap/all_parsers.dart'; -import 'package:enough_mail/src/private/imap/imap_response.dart'; -import 'package:enough_mail/src/private/imap/imap_response_line.dart'; +import 'package:enough_mail_plus/enough_mail.dart'; +import 'package:enough_mail_plus/src/private/imap/all_parsers.dart'; +import 'package:enough_mail_plus/src/private/imap/imap_response.dart'; +import 'package:enough_mail_plus/src/private/imap/imap_response_line.dart'; import 'package:test/test.dart'; // cSpell:disable diff --git a/test/src/imap/id_parser_test.dart b/test/src/imap/id_parser_test.dart index a6006af4..75cdd25f 100644 --- a/test/src/imap/id_parser_test.dart +++ b/test/src/imap/id_parser_test.dart @@ -1,8 +1,8 @@ -import 'package:enough_mail/src/imap/id.dart'; -import 'package:enough_mail/src/imap/response.dart'; -import 'package:enough_mail/src/private/imap/id_parser.dart'; -import 'package:enough_mail/src/private/imap/imap_response.dart'; -import 'package:enough_mail/src/private/imap/imap_response_line.dart'; +import 'package:enough_mail_plus/src/imap/id.dart'; +import 'package:enough_mail_plus/src/imap/response.dart'; +import 'package:enough_mail_plus/src/private/imap/id_parser.dart'; +import 'package:enough_mail_plus/src/private/imap/imap_response.dart'; +import 'package:enough_mail_plus/src/private/imap/imap_response_line.dart'; import 'package:test/test.dart'; // cSpell:disable diff --git a/test/src/imap/imap_response_line_test.dart b/test/src/imap/imap_response_line_test.dart index 8612a83f..053759ce 100644 --- a/test/src/imap/imap_response_line_test.dart +++ b/test/src/imap/imap_response_line_test.dart @@ -1,4 +1,4 @@ -import 'package:enough_mail/src/private/imap/imap_response_line.dart'; +import 'package:enough_mail_plus/src/private/imap/imap_response_line.dart'; import 'package:test/test.dart'; void main() { diff --git a/test/src/imap/imap_response_reader_test.dart b/test/src/imap/imap_response_reader_test.dart index 94ba9174..fb2a6d26 100644 --- a/test/src/imap/imap_response_reader_test.dart +++ b/test/src/imap/imap_response_reader_test.dart @@ -1,7 +1,7 @@ import 'dart:typed_data'; -import 'package:enough_mail/src/private/imap/imap_response.dart'; -import 'package:enough_mail/src/private/imap/imap_response_reader.dart'; +import 'package:enough_mail_plus/src/private/imap/imap_response.dart'; +import 'package:enough_mail_plus/src/private/imap/imap_response_reader.dart'; import 'package:test/test.dart'; // cSpell:disable diff --git a/test/src/imap/imap_response_test.dart b/test/src/imap/imap_response_test.dart index 9e6207f5..9782c8b6 100644 --- a/test/src/imap/imap_response_test.dart +++ b/test/src/imap/imap_response_test.dart @@ -1,7 +1,7 @@ import 'dart:typed_data'; -import 'package:enough_mail/src/private/imap/imap_response.dart'; -import 'package:enough_mail/src/private/imap/imap_response_line.dart'; +import 'package:enough_mail_plus/src/private/imap/imap_response.dart'; +import 'package:enough_mail_plus/src/private/imap/imap_response_line.dart'; import 'package:test/test.dart'; // cSpell:disable diff --git a/test/src/imap/list_parser_test.dart b/test/src/imap/list_parser_test.dart index 7053dc44..5d23e2fe 100644 --- a/test/src/imap/list_parser_test.dart +++ b/test/src/imap/list_parser_test.dart @@ -1,10 +1,10 @@ -import 'package:enough_mail/src/imap/imap_client.dart'; -import 'package:enough_mail/src/imap/mailbox.dart'; -import 'package:enough_mail/src/imap/response.dart'; -import 'package:enough_mail/src/private/imap/imap_response.dart'; -import 'package:enough_mail/src/private/imap/imap_response_line.dart'; -import 'package:enough_mail/src/private/imap/list_parser.dart'; -import 'package:enough_mail/src/private/util/client_base.dart'; +import 'package:enough_mail_plus/src/imap/imap_client.dart'; +import 'package:enough_mail_plus/src/imap/mailbox.dart'; +import 'package:enough_mail_plus/src/imap/response.dart'; +import 'package:enough_mail_plus/src/private/imap/imap_response.dart'; +import 'package:enough_mail_plus/src/private/imap/imap_response_line.dart'; +import 'package:enough_mail_plus/src/private/imap/list_parser.dart'; +import 'package:enough_mail_plus/src/private/util/client_base.dart'; import 'package:test/test.dart'; // cSpell:disable diff --git a/test/src/imap/parser_helper_test.dart b/test/src/imap/parser_helper_test.dart index f31a5044..3edc2bca 100644 --- a/test/src/imap/parser_helper_test.dart +++ b/test/src/imap/parser_helper_test.dart @@ -1,4 +1,4 @@ -import 'package:enough_mail/src/private/imap/parser_helper.dart'; +import 'package:enough_mail_plus/src/private/imap/parser_helper.dart'; import 'package:test/test.dart'; // cSpell:disable diff --git a/test/src/imap/search_parser_test.dart b/test/src/imap/search_parser_test.dart index 1fa2d94f..eaccff6e 100644 --- a/test/src/imap/search_parser_test.dart +++ b/test/src/imap/search_parser_test.dart @@ -1,7 +1,7 @@ -import 'package:enough_mail/src/imap/response.dart'; -import 'package:enough_mail/src/private/imap/all_parsers.dart'; -import 'package:enough_mail/src/private/imap/imap_response.dart'; -import 'package:enough_mail/src/private/imap/imap_response_line.dart'; +import 'package:enough_mail_plus/src/imap/response.dart'; +import 'package:enough_mail_plus/src/private/imap/all_parsers.dart'; +import 'package:enough_mail_plus/src/private/imap/imap_response.dart'; +import 'package:enough_mail_plus/src/private/imap/imap_response_line.dart'; import 'package:test/test.dart'; // cSpell:disable diff --git a/test/src/imap/sort_parser_test.dart b/test/src/imap/sort_parser_test.dart index 60f92e97..599226c5 100644 --- a/test/src/imap/sort_parser_test.dart +++ b/test/src/imap/sort_parser_test.dart @@ -1,7 +1,7 @@ -import 'package:enough_mail/src/imap/response.dart'; -import 'package:enough_mail/src/private/imap/imap_response.dart'; -import 'package:enough_mail/src/private/imap/imap_response_line.dart'; -import 'package:enough_mail/src/private/imap/sort_parser.dart'; +import 'package:enough_mail_plus/src/imap/response.dart'; +import 'package:enough_mail_plus/src/private/imap/imap_response.dart'; +import 'package:enough_mail_plus/src/private/imap/imap_response_line.dart'; +import 'package:enough_mail_plus/src/private/imap/sort_parser.dart'; import 'package:test/test.dart'; // cSpell:disable diff --git a/test/src/imap/status_parser_test.dart b/test/src/imap/status_parser_test.dart index 6244118e..07046265 100644 --- a/test/src/imap/status_parser_test.dart +++ b/test/src/imap/status_parser_test.dart @@ -1,7 +1,7 @@ -import 'package:enough_mail/enough_mail.dart'; -import 'package:enough_mail/src/private/imap/all_parsers.dart'; -import 'package:enough_mail/src/private/imap/imap_response.dart'; -import 'package:enough_mail/src/private/imap/imap_response_line.dart'; +import 'package:enough_mail_plus/enough_mail.dart'; +import 'package:enough_mail_plus/src/private/imap/all_parsers.dart'; +import 'package:enough_mail_plus/src/private/imap/imap_response.dart'; +import 'package:enough_mail_plus/src/private/imap/imap_response_line.dart'; import 'package:test/test.dart'; // cSpell:disable diff --git a/test/src/imap/thread_parser_test.dart b/test/src/imap/thread_parser_test.dart index 293fe649..11ce33ab 100644 --- a/test/src/imap/thread_parser_test.dart +++ b/test/src/imap/thread_parser_test.dart @@ -1,7 +1,7 @@ -import 'package:enough_mail/enough_mail.dart'; -import 'package:enough_mail/src/private/imap/all_parsers.dart'; -import 'package:enough_mail/src/private/imap/imap_response.dart'; -import 'package:enough_mail/src/private/imap/imap_response_line.dart'; +import 'package:enough_mail_plus/enough_mail.dart'; +import 'package:enough_mail_plus/src/private/imap/all_parsers.dart'; +import 'package:enough_mail_plus/src/private/imap/imap_response.dart'; +import 'package:enough_mail_plus/src/private/imap/imap_response_line.dart'; import 'package:test/test.dart'; // cSpell:disable diff --git a/test/src/smtp/commands/smtp_auth_cram_md5_command_test.dart b/test/src/smtp/commands/smtp_auth_cram_md5_command_test.dart index 627b88e3..4ad3ee11 100644 --- a/test/src/smtp/commands/smtp_auth_cram_md5_command_test.dart +++ b/test/src/smtp/commands/smtp_auth_cram_md5_command_test.dart @@ -1,7 +1,7 @@ import 'dart:convert'; -import 'package:enough_mail/enough_mail.dart'; -import 'package:enough_mail/src/private/smtp/commands/all_commands.dart'; +import 'package:enough_mail_plus/enough_mail.dart'; +import 'package:enough_mail_plus/src/private/smtp/commands/all_commands.dart'; import 'package:test/test.dart'; // cSpell:disable diff --git a/test/src/util/discover_helper_test.dart b/test/src/util/discover_helper_test.dart index 43ad9374..c04521e0 100644 --- a/test/src/util/discover_helper_test.dart +++ b/test/src/util/discover_helper_test.dart @@ -1,5 +1,5 @@ -import 'package:enough_mail/src/discover/client_config.dart'; -import 'package:enough_mail/src/private/util/discover_helper.dart'; +import 'package:enough_mail_plus/src/discover/client_config.dart'; +import 'package:enough_mail_plus/src/private/util/discover_helper.dart'; import 'package:test/test.dart'; // cSpell:disable diff --git a/test/src/util/uint8_list_reader_test.dart b/test/src/util/uint8_list_reader_test.dart index a6265dd7..04bbebff 100644 --- a/test/src/util/uint8_list_reader_test.dart +++ b/test/src/util/uint8_list_reader_test.dart @@ -1,6 +1,6 @@ import 'dart:typed_data'; -import 'package:enough_mail/src/private/util/uint8_list_reader.dart'; +import 'package:enough_mail_plus/src/private/util/uint8_list_reader.dart'; import 'package:test/test.dart'; String _toString(Uint8List? bytes) => String.fromCharCodes(bytes ?? []);