Repository navigation
Regression in 2.9.0: non-ASCII (8bit) text bodies emit bare LF instead of CRLF (to_crlf removed from Body#initialize) #1662
Description
Summary
Since v2.9.0, Mail::Body#encoded returns bare LF (\n) line endings for non-ASCII text parts (those that fall back to 8bit/binary transfer
encoding), instead of the RFC-compliant CRLF (\r\n) produced by v2.8.1 and earlier.
Pure-ASCII bodies are unaffected (they use 7bit, and SevenBit.encode still calls to_crlf). The regression only manifests for messages whose body
contains non-ASCII characters (e.g. UTF-8 Japanese, accented Latin, etc.), which is exactly the common case for internationalized ActionMailer mail.
Nothing downstream restores CRLF either — Message#encoded, Mail::SMTPEnvelope, and the SMTP delivery method all pass the body through unchanged — so
this affects actually delivered mail, not just in-process inspection.
Environment
- mail: 2.9.0 (also present on
master) - Ruby: 3.4.7
Reproduction
require 'mail'
# Non-ASCII (UTF-8) body — typical internationalized ActionMailer content
m1 = Mail.new do
to 'a@example.com'; from 'b@example.com'; subject 'x'
text_part { body "Café\nSecond line\n" } # contains non-ASCII
end
puts m1.text_part.body.encoded.inspect
# 2.9.0 => "Café\nSecond line\n" <-- bare LF
# 2.8.1 => "Café\r\nSecond line\r\n" <-- CRLF (expected)
# Pure ASCII body — unaffected
m2 = Mail.new do
to 'a@example.com'; from 'b@example.com'; subject 'x'
text_part { body "Hello\nSecond line\n" }
end
puts m2.text_part.body.encoded.inspect
# 2.9.0 => "Hello\r\nSecond line\r\n" (CRLF, OK)Direct Body check:
Mail::Body.new("line1\nline2\n").raw_source
# 2.9.0 => "line1\nline2\n"
# 2.8.1 => "line1\r\nline2\r\n"Expected vs. Actual
- Expected:
Body#encoded(and therefore the on-the-wire message) uses CRLF line endings, as in 2.8.1, per RFC 5322 §2.3. - Actual: Non-ASCII text parts retain whatever line endings the caller passed in (typically bare LF from templates), and no layer normalizes them before
SMTP delivery.
Root cause
Mail::Body#initialize no longer normalizes the raw source to CRLF.
In 2.8.1 (lib/mail/body.rb):
@raw_source = ::Mail::Utilities.to_crlf(string.join(''))
# and
@raw_source = ::Mail::Utilities.to_crlf(string.to_s)In 2.9.0 / master (lib/mail/body.rb, initialize):
@raw_source = string.join('')
# and
@raw_source = string.to_sBody#encoded is unchanged between the two versions. For non-ASCII content the selected transfer encoding is 8bit → EightBit → Binary → Identity,
whose decode/encode are no-ops, so the (now un-normalized) raw_source is emitted verbatim. For ASCII content SevenBit.encode calls
Mail::Utilities.binary_unsafe_to_crlf, which is why ASCII bodies still come out as CRLF and the regression is easy to miss.
This change does not appear in the 2.9.0 CHANGELOG. It seems to be a side effect of the line-ending/charset cleanup work in this release line (related area:
PR #1113, issues #1174, #1190).
Impact
- Internationalized
text/plain(andtext/html) parts are transmitted with bare LF, which is non-compliant with RFC 5322 §2.3 / RFC 5321. Lenient MTAs
tolerate it; strict ones may reject or mishandle it (e.g. DKIM canonicalization, length checks). - Silent behavioral change for any app that upgrades 2.8.x → 2.9.0 and sends non-ASCII mail.
Question / suggested direction
Was removing CRLF normalization from Body#initialize intentional, with the expectation that the delivery method normalizes line endings? If so,
Mail::SMTPEnvelope / the bundled SMTP delivery method don't currently do that. If not, restoring CRLF normalization for 8bit/binary parts at encode
time (mirroring SevenBit.encode) would fix the regression without reintroducing the attachment-corruption problem PR #1113 addressed for binary
attachments.
Happy to provide a failing spec / PR if a preferred approach is indicated.
Activity