From 7a7a428a7356020715dfd9ca93cb50d2e5f0cd9c Mon Sep 17 00:00:00 2001 From: Gringo Date: Mon, 23 Mar 2026 20:33:06 +0100 Subject: [PATCH 1/7] fix: avoid improper folding in long email addresses --- lib/src/mime_message.dart | 86 +++++++++++++++++++++++++++---------- test/folding_test.dart | 17 ++++++++ test/mime_message_test.dart | 27 ++++-------- 3 files changed, 88 insertions(+), 42 deletions(-) create mode 100644 test/folding_test.dart diff --git a/lib/src/mime_message.dart b/lib/src/mime_message.dart index 29296bb8..c8461cf5 100644 --- a/lib/src/mime_message.dart +++ b/lib/src/mime_message.dart @@ -1344,50 +1344,90 @@ 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 || + char == AsciiRunes.runeSmallerThan) { + 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 || + char == AsciiRunes.runeSmallerThan) { + 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/test/folding_test.dart b/test/folding_test.dart new file mode 100644 index 00000000..a04c107a --- /dev/null +++ b/test/folding_test.dart @@ -0,0 +1,17 @@ +import 'package:enough_mail/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(null, longEmail)]; + final mimeMessage = builder.buildMimeMessage(); + final rendered = mimeMessage.renderMessage(); + + expect(rendered, contains(longEmail)); + }); +} diff --git a/test/mime_message_test.dart b/test/mime_message_test.dart index 8cbc6d85..1cfaf5f2 100644 --- a/test/mime_message_test.dart +++ b/test/mime_message_test.dart @@ -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], ''); }); }); From cfaf0fbb5af9edc20cb971ef44220c933a204a0c Mon Sep 17 00:00:00 2001 From: Gringo Date: Fri, 27 Mar 2026 17:45:40 +0100 Subject: [PATCH 2/7] fix: prevent header folding immediately after opening angle bracket --- lib/src/mime_message.dart | 6 ++---- test/folding_test.dart | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/src/mime_message.dart b/lib/src/mime_message.dart index c8461cf5..66c59e7e 100644 --- a/lib/src/mime_message.dart +++ b/lib/src/mime_message.dart @@ -1381,8 +1381,7 @@ class Header { char == AsciiRunes.runeClosingParentheses || char == AsciiRunes.runeClosingBracket || char == AsciiRunes.runeGreaterThan || - char == AsciiRunes.runeComma || - char == AsciiRunes.runeSmallerThan) { + char == AsciiRunes.runeComma) { chunkLength = i - startIndex + 1; foundFoldingPoint = true; break; @@ -1408,8 +1407,7 @@ class Header { char == AsciiRunes.runeClosingParentheses || char == AsciiRunes.runeClosingBracket || char == AsciiRunes.runeGreaterThan || - char == AsciiRunes.runeComma || - char == AsciiRunes.runeSmallerThan) { + char == AsciiRunes.runeComma) { chunkLength = i - startIndex + 1; foundFoldingPoint = true; break; diff --git a/test/folding_test.dart b/test/folding_test.dart index a04c107a..e6bef319 100644 --- a/test/folding_test.dart +++ b/test/folding_test.dart @@ -8,10 +8,10 @@ void main() { 'ail.uid.ovh'; final builder = MessageBuilder() - ..from = [const MailAddress(null, longEmail)]; + ..from = [const MailAddress('Test', longEmail)]; final mimeMessage = builder.buildMimeMessage(); final rendered = mimeMessage.renderMessage(); - expect(rendered, contains(longEmail)); + expect(rendered, contains('<$longEmail>')); }); } From de9e4aa8daa6a7d93a9042e7da96ec72512499a1 Mon Sep 17 00:00:00 2001 From: Gringo Date: Fri, 27 Mar 2026 18:27:50 +0100 Subject: [PATCH 3/7] feat: initial release of enough_mail_plus fork with header folding fix --- CHANGELOG.md | 8 ++++++-- README.md | 19 ++++++++++++------- example/discover.dart | 2 +- example/enough_mail_example.dart | 2 +- lib/src/mime_message.dart | 3 ++- pubspec.yaml | 14 +++++++------- test/codecs/base64_mail_codec_test.dart | 2 +- test/codecs/date_codec_test.dart | 2 +- test/codecs/folding_test.dart | 8 ++++---- test/codecs/mail_codec_test.dart | 2 +- test/codecs/modified_utf7_codec_test.dart | 2 +- .../quoted_printable_mail_codec_test.dart | 2 +- test/folding_test.dart | 2 +- test/imap/imap_client_test.dart | 4 ++-- test/imap/mailbox_test.dart | 2 +- test/imap/message_sequence_test.dart | 4 ++-- test/imap/qresync_test.dart | 2 +- test/imap/response_test.dart | 2 +- test/mail/mail_account_test.dart | 2 +- test/mail/results_test.dart | 2 +- test/message_builder_test.dart | 12 ++++++------ test/mime_message_test.dart | 10 +++++----- test/pop/pop_client_test.dart | 4 ++-- test/smtp/smtp_client_test.dart | 6 +++--- test/src/imap/fetch_parser_test.dart | 8 ++++---- test/src/imap/id_parser_test.dart | 10 +++++----- test/src/imap/imap_response_line_test.dart | 2 +- test/src/imap/imap_response_reader_test.dart | 4 ++-- test/src/imap/imap_response_test.dart | 4 ++-- test/src/imap/list_parser_test.dart | 14 +++++++------- test/src/imap/parser_helper_test.dart | 2 +- test/src/imap/search_parser_test.dart | 8 ++++---- test/src/imap/sort_parser_test.dart | 8 ++++---- test/src/imap/status_parser_test.dart | 8 ++++---- test/src/imap/thread_parser_test.dart | 8 ++++---- .../smtp_auth_cram_md5_command_test.dart | 4 ++-- test/src/util/discover_helper_test.dart | 4 ++-- test/src/util/uint8_list_reader_test.dart | 2 +- 38 files changed, 107 insertions(+), 97 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f39d0393..943e9681 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 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 +76,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 +304,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/lib/src/mime_message.dart b/lib/src/mime_message.dart index 66c59e7e..f98dff32 100644 --- a/lib/src/mime_message.dart +++ b/lib/src/mime_message.dart @@ -1388,7 +1388,8 @@ class Header { } } if (!foundFoldingPoint) { - // try to find a folding point after chunkLength up to messageLineMaxLength + // 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) { diff --git a/pubspec.yaml b/pubspec.yaml index aed7227b..72e795f7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,9 +1,9 @@ -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: Maintenance fork of enough_mail. 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.2.0 +repository: https://github.com/nogringo/enough_mail topics: - email - imap @@ -23,7 +23,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 index e6bef319..c546cb0e 100644 --- a/test/folding_test.dart +++ b/test/folding_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/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_message_test.dart b/test/mime_message_test.dart index 1cfaf5f2..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 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/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 ?? []); From b4c0a431d98334f1880d2cdda4a3da73a0ea16e4 Mon Sep 17 00:00:00 2001 From: Gringo Date: Fri, 27 Mar 2026 18:32:43 +0100 Subject: [PATCH 4/7] fix: remove default case --- lib/src/discover/client_config.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/src/discover/client_config.dart b/lib/src/discover/client_config.dart index e62dce82..52ad9bdd 100644 --- a/lib/src/discover/client_config.dart +++ b/lib/src/discover/client_config.dart @@ -350,8 +350,7 @@ class ServerConfig { return email.substring(lastAtIndex + 1); case UsernameType.realName: case UsernameType.unknown: - default: - return null; + return null; } } From ab00287ca4a2831354bf3e72f541f3aea6f70908 Mon Sep 17 00:00:00 2001 From: Gringo Date: Mon, 6 Apr 2026 01:25:42 +0200 Subject: [PATCH 5/7] fix: tolerate bare LF line endings in MIME parsing --- CHANGELOG.md | 3 ++ lib/src/mime_data.dart | 15 ++++-- pubspec.yaml | 2 +- test/mime_bare_lf_test.dart | 98 +++++++++++++++++++++++++++++++++++++ 4 files changed, 114 insertions(+), 4 deletions(-) create mode 100644 test/mime_bare_lf_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 943e9681..5dde89a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# 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. 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'(?\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('')); + }); + }); +} From 716d78f906f283ddec12a5b5595e5d3d260e344d Mon Sep 17 00:00:00 2001 From: Russell Date: Mon, 20 Apr 2026 16:24:53 +0200 Subject: [PATCH 6/7] fix: update version to 2.2.2 and add tests for header removal functionality --- CHANGELOG.md | 3 +++ lib/src/mime_message.dart | 11 +++++++++-- pubspec.yaml | 2 +- test/remove_header_test.dart | 20 ++++++++++++++++++++ 4 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 test/remove_header_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 5dde89a8..b82045b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# 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. diff --git a/lib/src/mime_message.dart b/lib/src/mime_message.dart index f98dff32..7ab091c9 100644 --- a/lib/src/mime_message.dart +++ b/lib/src/mime_message.dart @@ -33,6 +33,7 @@ class MimePart { List? 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); diff --git a/pubspec.yaml b/pubspec.yaml index 7b9a9e38..c84ec76e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: enough_mail_plus description: Maintenance fork of enough_mail. 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.2.1 +version: 2.2.2 repository: https://github.com/nogringo/enough_mail topics: - email 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); + }); +} From d645c0b32ee0ae5a9a7a0b442bb2ab19443e5004 Mon Sep 17 00:00:00 2001 From: Russell Date: Mon, 20 Apr 2026 23:48:45 +0200 Subject: [PATCH 7/7] chore: update changelog for version 2.2.3, add example file, and fix documentation comments --- CHANGELOG.md | 7 ++++ example/example.dart | 52 +++++++++++++++++++++++++++++ lib/codecs.dart | 2 ++ lib/discover.dart | 3 +- lib/highlevel.dart | 2 ++ lib/imap.dart | 2 ++ lib/mime.dart | 2 ++ lib/pop.dart | 3 +- lib/smtp.dart | 2 ++ lib/src/discover/client_config.dart | 2 +- lib/src/message_builder.dart | 2 +- pubspec.yaml | 6 ++-- 12 files changed, 77 insertions(+), 8 deletions(-) create mode 100644 example/example.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index b82045b0..638cd735 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# 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. 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 52ad9bdd..2d1a5a77 100644 --- a/lib/src/discover/client_config.dart +++ b/lib/src/discover/client_config.dart @@ -350,7 +350,7 @@ class ServerConfig { return email.substring(lastAtIndex + 1); case UsernameType.realName: case UsernameType.unknown: - return null; + 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/pubspec.yaml b/pubspec.yaml index c84ec76e..c57afecc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,8 +1,6 @@ name: enough_mail_plus -description: Maintenance fork of enough_mail. 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.2.2 +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