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
15 changes: 12 additions & 3 deletions lib/mail/renderers/rfc_2822.ex
Original file line number Diff line number Diff line change
Expand Up @@ -207,13 +207,22 @@ defmodule Mail.Renderers.RFC2822 do
# As stated at https://datatracker.ietf.org/doc/html/rfc2047#section-2, encoded words must be
# split in 76 chars including its surroundings and delimmiters.
# Since enclosing starts with =?UTF-8?Q? and ends with ?=, max length should be 64
# Per RFC 2047, encoding is only required for non-ASCII characters.
# ASCII-only headers should not be encoded, regardless of length.
# Per RFC 2047, ASCII-only headers should not be encoded, regardless of length
defp encode_header_value(header_value, :quoted_printable) do
case Mail.Encoders.QuotedPrintable.encode(header_value, 64) do
^header_value -> header_value
encoded -> wrap_encoded_words(encoded)
if contains_non_ascii?(header_value) do
header_value |> Mail.Encoders.QuotedPrintable.encode(64) |> wrap_encoded_words()
else
header_value
end
end

# Check if a string contains any non-ASCII characters (bytes > 0x7F)
defp contains_non_ascii?(<<>>), do: false
defp contains_non_ascii?(<<byte, _rest::binary>>) when byte > 127, do: true
defp contains_non_ascii?(<<_byte, rest::binary>>), do: contains_non_ascii?(rest)

defp wrap_encoded_words(value) do
:binary.split(value, "=\r\n", [:global])
|> Enum.map(fn chunk -> <<"=?UTF-8?Q?", chunk::binary, "?=">> end)
Expand Down
22 changes: 22 additions & 0 deletions test/mail/renderers/rfc_2822_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,28 @@ defmodule Mail.Renderers.RFC2822Test do
assert header == "Content-Disposition: attachment; filename=\"my;test;file\""
end

test "encodes header if necessary" do
assert Mail.Renderers.RFC2822.render_header("Subject", [
"Hello World!"
]) == "Subject: Hello World!"

assert Mail.Renderers.RFC2822.render_header("Subject", [
String.duplicate("a", 73)
]) == "Subject: #{String.duplicate("a", 73)}"

assert Mail.Renderers.RFC2822.render_header("Subject", [
"Hello World 😀"
]) == "Subject: =?UTF-8?Q?Hello World =F0=9F=98=80?="

assert Mail.Renderers.RFC2822.render_header("Subject", [
"Café résumé"
]) == "Subject: =?UTF-8?Q?Caf=C3=A9 r=C3=A9sum=C3=A9?="

assert Mail.Renderers.RFC2822.render_header("Subject", [
"Hello 世界 World"
]) == "Subject: =?UTF-8?Q?Hello =E4=B8=96=E7=95=8C World?="
end

test "address headers renders list of recipients" do
header = Mail.Renderers.RFC2822.render_header("from", "user1@example.com")
assert header == "From: user1@example.com"
Expand Down