Skip to content
Open
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
13 changes: 13 additions & 0 deletions lib/src/codecs/quoted_printable_mail_codec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,14 @@ class QuotedPrintableMailCodec extends MailCodec {
for (var i = 0; i < cleaned.length; i++) {
final char = cleaned[i];
if (char == '=') {
// A '=' within two characters of the end has no complete hex escape
// following it. RFC 2045 §6.7 permits consumers to preserve such
// malformed input verbatim; the alternative here is a RangeError
// from substring on the two lines below.
if (i + 3 > cleaned.length) {
buffer.write(cleaned.substring(i));
break;
}
final hexText = cleaned.substring(i + 1, i + 3);
var charCode = int.tryParse(hexText, radix: 16);
if (charCode == null) {
Expand All @@ -208,6 +216,11 @@ class QuotedPrintableMailCodec extends MailCodec {
} else {
final charCodes = [charCode];
while (cleaned.length > (i + 4) && cleaned[i + 3] == '=') {
// The while check only proves cleaned[i+3] exists; the substring
// below still reads cleaned[i+4..i+5], so stop if the trailing
// '=' has fewer than two hex chars after it and let the outer
// loop pick it up via the truncated-tail branch above.
if (i + 6 > cleaned.length) break;
i += 3;
final hexText = cleaned.substring(i + 1, i + 3);
charCode = int.parse(hexText, radix: 16);
Expand Down
31 changes: 31 additions & 0 deletions test/codecs/quoted_printable_mail_codec_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,37 @@ void main() {
'jeden Tag ändern können',
);
});

group('malformed truncated =XY escapes are preserved verbatim', () {
// Regression: a '=' within two characters of the end used to throw
// RangeError from substring(i+1, i+3). Per RFC 2045 §6.7 consumers may
// preserve the malformed tail; matches the existing behaviour for
// non-hex '=XY' where the raw text is written to the buffer.
test('trailing bare "="', () {
expect(
MailCodec.quotedPrintable.decodeText('hello=', convert.utf8),
'hello=',
);
});
test('trailing "=A"', () {
expect(
MailCodec.quotedPrintable.decodeText('hello=A', convert.utf8),
'hello=A',
);
});
test('lone "=A" at end of a short input', () {
expect(
MailCodec.quotedPrintable.decodeText('h=A', convert.utf8),
'h=A',
);
});
test('valid multi-byte escape followed by truncated "=A"', () {
expect(
MailCodec.quotedPrintable.decodeText('hi=C3=B6=A', convert.utf8),
'hiö=A',
);
});
});
});

group('Quoted Printable encoding', () {
Expand Down