Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update regular expression for parsing recipient values #193

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
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
45 changes: 42 additions & 3 deletions lib/mail/parsers/rfc_2822.ex
Original file line number Diff line number Diff line change
Expand Up @@ -318,10 +318,49 @@ defmodule Mail.Parsers.RFC2822 do
@spec parse_recipient_value(value :: String.t()) ::
[{String.t(), String.t()} | String.t()]
def parse_recipient_value(value) do
Regex.scan(~r/\s*("?)(.*?)\1\s*?<?([^<\s]+@[^\s>,]+)>?,?/, value)
Regex.scan(
~r/
\s*
(?:
# Quoted name
((?<!\\)")
(?<name>.*?)
\1
\s*
<
(?<email>[^<\s,]+@[^\s>,]+)
>
|
# Non-quoted name
(?<name2>[^\\",@]+?)
\s*?
<
(?<email2>[^<\s,]+@[^\s>,]+)
>
|
# Only email
<?(?<email3>[^<\s,]+@[^\s>,]+)>?
)
,?
/x,
value,
capture: :all_names
)
|> Enum.map(fn
# Scan is matching on named captures sorted alphabetically:
# [email, email2, email3, name, name2]
["", "", address, "", ""] ->
{"", address}

[address, "", "", name, ""] ->
{name, address}

["", address, "", "", name] ->
{name, address}
end)
|> Enum.map(fn
[_, _, "", address] -> address
[_, _, name, address] -> {name, address}
{"", address} -> address
{name, address} -> {String.replace(name, "\\", ""), address}
end)
end

Expand Down
84 changes: 63 additions & 21 deletions test/mail/parsers/rfc_2822_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -303,30 +303,59 @@ defmodule Mail.Parsers.RFC2822Test do
assert to_datetime("invalid date string") == {:error, "invalid date string"}
end

test "parse_recipient_value retrieves a list of name and addresses" do
recipient =
"The Dude <[email protected]>, [email protected], super<[email protected]>, \"[email protected]\" <[email protected]>"

retrieved_recipients = [
{"The Dude", "[email protected]"},
"[email protected]",
{"super", "[email protected]"},
{"[email protected]", "[email protected]"}
]

assert parse_recipient(recipient) == retrieved_recipients
end
describe "parse_recipient_value/1" do
test "parse_recipient_value retrieves a list of name and addresses" do
recipient =
~S|The Dude <[email protected]>, [email protected], super<[email protected]>, "[email protected]" <[email protected]>|

retrieved_recipients = [
{"The Dude", "[email protected]"},
"[email protected]",
{"super", "[email protected]"},
{"[email protected]", "[email protected]"}
]

assert parse_recipient(recipient) == retrieved_recipients
end

test "parse_recipient_value retrieves an empty list when recipient is empty" do
assert parse_recipient("") == []
end

test "parse_recipient_value retrieves an empty list when no \"address\" found" do
assert parse_recipient("NoEmail") == []
end

test "parse_recipient_value retrieves a list when only one \"address\" found" do
assert parse_recipient("[email protected]") == ["[email protected]"]
assert parse_recipient("<[email protected]>") == ["[email protected]"]
end

test "parse_recipient_value quoted name" do
assert parse_recipient(~S|"dude" <[email protected]>|) == [{"dude", "[email protected]"}]

assert parse_recipient(~S|"First, Second" <[email protected]>|) == [
{"First, Second", "[email protected]"}
]
end

test "parse_recipient_value retrieves an empty list when recipient is empty" do
assert parse_recipient("") == []
end
test "parse_recipient_value non-quoted name" do
assert parse_recipient(~S|The Dude <[email protected]>|) == [
{"The Dude", "[email protected]"}
]
end

test "parse_recipient_value retrieves an empty list when no \"address\" found" do
assert parse_recipient("NoEmail") == []
end
test "parse_recipient_value extra quoted name" do
assert parse_recipient(~S|"\"dude\"" <[email protected]>|) == [
{"\"dude\"", "[email protected]"}
]
end

test "parse_recipient_value retrieves a list when only one \"address\" found" do
assert parse_recipient("[email protected]") == ["[email protected]"]
test "parse_recipient_value extra test" do
assert parse_recipient(~S|"\"[email protected]\" " <[email protected]>|) == [
{~S|"[email protected]" |, "[email protected]"}
]
end
end

test "parses a nested multipart message with encoded part" do
Expand Down Expand Up @@ -570,6 +599,19 @@ defmodule Mail.Parsers.RFC2822Test do
assert message.headers["from"] == {"Lastname, First Names", "[email protected]"}
end

test "address name is an e-mail address with additiongal quotes" do
message =
parse_email("""
To: "User, Test" <[email protected]>
From: ""[email protected]"" <[email protected]>
Date: Fri, 1 Jan 2016 00:00:00 +0000
Subject: Blank body

""")

assert message.headers["from"] == {"\"[email protected]\"", "[email protected]"}
end

# See https://tools.ietf.org/html/rfc2047
test "parses headers with encoded word syntax" do
message =
Expand Down