Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion example/example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@ void main() async {
await smtpClient.ehlo();
if (smtpClient.serverInfo.supportsAuth(AuthMechanism.plain)) {
await smtpClient.authenticate(
'user@example.com', 'password', AuthMechanism.plain);
'user@example.com',
'password',
AuthMechanism.plain,
);
}

final builder = MessageBuilder()
Expand Down
22 changes: 15 additions & 7 deletions lib/src/codecs/mail_codec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -410,16 +410,20 @@ abstract class MailCodec {
return text;
}
final buffer = StringBuffer();
final runes = text.runes;
final runes = text.runes.toList(growable: false);
int? lastRune;
int? lastSpaceIndex;
var currentLineLength = 0;
var currentLineStartIndex = 0;
for (var runeIndex = 0; runeIndex < runes.length; runeIndex++) {
final rune = runes.elementAt(runeIndex);
final rune = runes[runeIndex];
if (rune == AsciiRunes.runeLineFeed &&
lastRune == AsciiRunes.runeCarriageReturn) {
buffer.write(text.substring(currentLineStartIndex, runeIndex + 1));
buffer.write(
String.fromCharCodes(
runes.sublist(currentLineStartIndex, runeIndex + 1),
),
);
currentLineLength = 0;
currentLineStartIndex = runeIndex + 1;
lastSpaceIndex = null;
Expand All @@ -433,7 +437,7 @@ abstract class MailCodec {
// edge case: this could be in the middle of a \r\n sequence:
if (rune == AsciiRunes.runeCarriageReturn &&
runeIndex < runes.length - 1 &&
runes.elementAt(runeIndex + 1) == AsciiRunes.runeLineFeed) {
runes[runeIndex + 1] == AsciiRunes.runeLineFeed) {
lastRune = rune;
continue; // the break will be handled in the next loop iteration
}
Expand All @@ -444,7 +448,11 @@ abstract class MailCodec {
endIndex++;
}
buffer
..write(text.substring(currentLineStartIndex, endIndex))
..write(
String.fromCharCodes(
runes.sublist(currentLineStartIndex, endIndex),
),
)
..write('\r\n');
currentLineLength = 0;
currentLineStartIndex = endIndex;
Expand All @@ -454,8 +462,8 @@ abstract class MailCodec {
lastRune = rune;
}

if (currentLineStartIndex < text.length) {
buffer.write(text.substring(currentLineStartIndex));
if (currentLineStartIndex < runes.length) {
buffer.write(String.fromCharCodes(runes.sublist(currentLineStartIndex)));
}

return buffer.toString();
Expand Down
5 changes: 3 additions & 2 deletions lib/src/imap/imap_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ class ImapClient extends ClientBase {
// the continuation would hang forever since onConnectionError is not
// invoked on an expected disconnect.
_failPendingIdleContinuation('client disconnected');
_eventController.close();
await _eventController.close();

return super.disconnect();
}
Expand Down Expand Up @@ -2762,7 +2762,8 @@ class ImapClient extends ClientBase {
}
if (_isInIdleMode) {
// `+ idling` from the server -- IDLE mode is now truly active.
// Resolve any pending completer from idleStart(waitForContinuation: true).
// Resolve any pending completer from
// idleStart(waitForContinuation: true).
final completer = _idleContinuationCompleter;
if (completer != null && !completer.isCompleted) {
_idleContinuationCompleter = null;
Expand Down
13 changes: 9 additions & 4 deletions lib/src/message_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1437,10 +1437,13 @@ class MessageBuilder extends PartBuilder {
'Return-Receipt-To',
);
if (recipient == null || recipient.isEmpty) {
final headerValue = originalMessage.getHeaderValue(
MailConventions.headerDispositionNotificationTo,
);
throw InvalidArgumentException(
'Invalid header ${MailConventions.headerDispositionNotificationTo} '
'in message: '
'${originalMessage.getHeaderValue(MailConventions.headerDispositionNotificationTo)}',
'Invalid header '
'${MailConventions.headerDispositionNotificationTo} in message: '
'$headerValue',
);
}
}
Expand Down Expand Up @@ -1503,7 +1506,9 @@ class MessageBuilder extends PartBuilder {
return '>\r\n';
}

return '>${header.split('\r\n').join('\r\n>')}\r\n>${text.split('\r\n').join('\r\n>')}';
final quotedHeader = header.split('\r\n').join('\r\n>');
final quotedText = text.split('\r\n').join('\r\n>');
return '>$quotedHeader\r\n>$quotedText';
}

/// Generates a message ID
Expand Down
11 changes: 8 additions & 3 deletions lib/src/mime_message.dart
Original file line number Diff line number Diff line change
Expand Up @@ -614,9 +614,14 @@ class MimeMessage extends MimePart {
this.guid = guid;
}

int? xGmThrid; // X-GM-THRID
int? xGmMsgid; // X-GM-MSGID
List<String>? xGmLabels; // X-GM-LABELS
/// The Gmail thread ID (X-GM-THRID)
int? xGmThrid;

/// The Gmail message ID (X-GM-MSGID)
int? xGmMsgid;

/// The Gmail labels (X-GM-LABELS)
List<String>? xGmLabels;

/// The modifications sequence of this message.
///
Expand Down
4 changes: 3 additions & 1 deletion lib/src/smtp/smtp_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,9 @@ class SmtpClient extends ClientBase {
///
/// Usage:
/// ```dart
/// smtpClient.eventStream.whereType<SmtpConnectionLostEvent>().listen((event) {
/// smtpClient.eventStream
/// .whereType<SmtpConnectionLostEvent>()
/// .listen((event) {
/// _log(event.type);
/// });
/// ```
Expand Down
29 changes: 29 additions & 0 deletions test/message_builder_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1748,6 +1748,35 @@ END:VCARD\r
//expect(part?.decodeDispositionNotification())
});
});

group('performance', () {
test('buildMimeMessage should be fast with 100KB body', () {
final builder = MessageBuilder.prepareMultipartAlternativeMessage()
..from = [const MailAddress('Sender', 'sender@example.com')]
..to = [const MailAddress('Recipient', 'recipient@example.com')]
..subject = 'Large body perf test';

final largeBody = 'A' * (100 * 1024);

builder.addTextPlain(largeBody);

final sw = Stopwatch()..start();
final mime = builder.buildMimeMessage();
sw.stop();

// Sanity checks
expect(mime, isNotNull);
expect(mime.renderMessage().length, greaterThan(100000));

expect(
sw.elapsedMilliseconds,
lessThan(1000),
reason:
'buildMimeMessage() should complete in <1s, but currently '
'takes ${sw.elapsedMilliseconds}ms for a 100KB body. ',
);
});
});
}

const String complexMessageText = '''
Expand Down
Loading