From 759ccecdfd3ce3fdcc0c674558ac49075211bfcd Mon Sep 17 00:00:00 2001 From: Flying-Toast <38232168+Flying-Toast@users.noreply.github.com> Date: Thu, 26 Oct 2023 14:26:06 -0400 Subject: [PATCH] Make the "name" on addresses accessible; make "From" public --- lib/yugo.ex | 16 ++++++++++++++-- lib/yugo/client.ex | 3 +-- lib/yugo/filter.ex | 2 +- lib/yugo/parser.ex | 4 ++-- mix.exs | 2 +- test/yugo/client_test.exs | 40 +++++++++++++++++++++++---------------- test/yugo/filter_test.exs | 13 ++++++++++--- test/yugo/parser_test.exs | 10 +++++----- 8 files changed, 58 insertions(+), 32 deletions(-) diff --git a/lib/yugo.ex b/lib/yugo.ex index e63ccf7..09c4323 100644 --- a/lib/yugo.ex +++ b/lib/yugo.ex @@ -5,7 +5,12 @@ defmodule Yugo do alias Yugo.{Filter, Client} - @type address :: String.t() + @typedoc """ + The first field is the name associated with the address, and the second field is the address itself. + + e.g. `{"Bart", "bart@simpsons.family"}` + """ + @type address :: {nil | String.t(), String.t()} @typedoc """ e.g. `"text/html"`, `"image/png"`, `"text/plain"`, etc. @@ -20,7 +25,7 @@ defmodule Yugo do A "onepart" body is a tuple in the form `{mime_type, params, content}`, where `mime_type` is a [`mime_type`](`t:mime_type/0`), `params` is a string->string map, and `content` is a [`binary`](`t:binary/0`). - A "multipart" body consists of a *list* of [`body`s](`t:body/0`). + A "multipart" body consists of a *list* of [`body`s](`t:body/0`), which can themselves be either onepart or multipart. """ @type body :: {mime_type, %{optional(String.t()) => String.t()}, binary} | [body] @@ -28,6 +33,12 @@ defmodule Yugo do @typedoc """ An email message sent to a subscribed process. + + # Difference between `sender` and `from` + Both "sender" and "from" are fields used by IMAP to indicate the origin of the email. + They are *usually* the same, but they can be different and they have different meanings: + - `from` - the author who physically wrote the email. `From` is typically the address you see when viewing the message in an email client. + - `sender` - the person who sent the email, potentially different than `from` if someone else sent the email on behalf of the author. """ @type email :: %{ bcc: [address], @@ -39,6 +50,7 @@ defmodule Yugo do message_id: nil | String.t(), reply_to: [address], sender: [address], + from: [address], subject: nil | String.t(), to: [address] } diff --git a/lib/yugo/client.ex b/lib/yugo/client.ex index f17ee36..e7bdef7 100644 --- a/lib/yugo/client.ex +++ b/lib/yugo/client.ex @@ -466,8 +466,7 @@ defmodule Yugo.Client do defp package_message(msg) do msg |> Map.merge(msg.envelope) - # From is too easily spoofed, drop it so users use :sender instead - |> Map.drop([:from, :fetched, :body_structure, :envelope]) + |> Map.drop([:fetched, :body_structure, :envelope]) |> Map.put(:body, normalize_structure(msg.body, msg.body_structure)) end diff --git a/lib/yugo/filter.ex b/lib/yugo/filter.ex index a4b4ad3..164d81a 100644 --- a/lib/yugo/filter.ex +++ b/lib/yugo/filter.ex @@ -48,7 +48,7 @@ defmodule Yugo.Filter do (message.envelope.subject != nil && Regex.match?(filter.subject_regex, message.envelope.subject)), !filter.sender_regex || - Enum.any?(message.envelope.sender, &Regex.match?(filter.sender_regex, &1)), + Enum.any?(message.envelope.sender, &Regex.match?(filter.sender_regex, elem(&1, 1))), filter.has_flags == [] || Enum.all?(filter.has_flags, &Enum.member?(message.flags, &1)), filter.lacks_flags == [] || Enum.all?(filter.lacks_flags, &(!Enum.member?(message.flags, &1))) diff --git a/lib/yugo/parser.ex b/lib/yugo/parser.ex index 265998f..2c7f11d 100644 --- a/lib/yugo/parser.ex +++ b/lib/yugo/parser.ex @@ -284,10 +284,10 @@ defmodule Yugo.Parser do end defp parse_address(rest) do - {[_name, _adl, mailbox, host], rest} = + {[name, _adl, mailbox, host], rest} = parse_list(rest, [&parse_nstring/1, &parse_nstring/1, &parse_nstring/1, &parse_nstring/1]) - {"#{String.downcase(mailbox)}@#{String.downcase(host)}", rest} + {{name, "#{String.downcase(mailbox)}@#{String.downcase(host)}"}, rest} end defp parse_address_list(rest) do diff --git a/mix.exs b/mix.exs index 4b2823d..afe2e0f 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule Yugo.MixProject do def project do [ app: :yugo, - version: "0.3.1", + version: "1.0.0", elixir: "~> 1.14", start_permanent: Mix.env() == :prod, deps: deps(), diff --git a/test/yugo/client_test.exs b/test/yugo/client_test.exs index e3ce8a8..f8729e2 100644 --- a/test/yugo/client_test.exs +++ b/test/yugo/client_test.exs @@ -47,10 +47,11 @@ defmodule Yugo.ClientTest do flags: [:seen], in_reply_to: nil, message_id: "fjaelwkjfi oaf<$ ))) \"", - reply_to: ["marge@simpsons-family.com"], - sender: ["marge@simpsons-family.com"], + reply_to: [{"Marge", "marge@simpsons-family.com"}], + sender: [{"Marge Simpson", "marge@simpsons-family.com"}], subject: nil, - to: ["homer@simpsons-family.com"] + to: [{"HOMIEEEE", "homer@simpsons-family.com"}], + from: [{"Marge Simpson", "marge@simpsons-family.com"}] } end end @@ -90,10 +91,11 @@ defmodule Yugo.ClientTest do flags: [], in_reply_to: nil, message_id: "Fjaewlk jflkewajf i3ajf0943aF $#AF $#FA#$ F#AF {123}", - reply_to: ["bobjones@example.org"], - sender: ["bobjones@example.org"], + reply_to: [{"Bob Jones", "bobjones@example.org"}], + sender: [{"Bob Jones", "bobjones@example.org"}], subject: "Foo Bar Baz Buzz Biz Boz", - to: ["foo@bar.com"] + to: [{nil, "foo@bar.com"}], + from: [{"Bob Jones", "bobjones@example.org"}] } end end @@ -120,15 +122,19 @@ defmodule Yugo.ClientTest do %{ bcc: [], body: {"text/plain", %{}, "hello"}, - cc: ["foo@bar.com", "bar@foo.com", "fizz@buzz.com"], + cc: [{nil, "foo@bar.com"}, {"barfoo", "bar@foo.com"}, {"", "fizz@buzz.com"}], date: ~U[2022-12-07 13:02:41Z], flags: [], in_reply_to: "123 abc 456", message_id: "", - reply_to: ["marge@simpsons-family.com"], - sender: ["marge@simpsons-family.com", "bob@bobs-email.com"], + reply_to: [{"Marge", "marge@simpsons-family.com"}], + sender: [ + {"Marge Simpson", "marge@simpsons-family.com"}, + {nil, "bob@bobs-email.com"} + ], subject: "Hello! (subject)", - to: ["homer@simpsons-family.com"] + to: [{"HOMIEEEE", "homer@simpsons-family.com"}], + from: [{"Marge Simpson", "marge@simpsons-family.com"}] } end end @@ -170,10 +176,11 @@ defmodule Yugo.ClientTest do flags: [], in_reply_to: nil, message_id: "<><><><><>", - reply_to: ["foo@bar.com"], - sender: ["person@domain.com"], + reply_to: [{nil, "foo@bar.com"}], + sender: [{nil, "person@domain.com"}], subject: "An HTML email", - to: ["bar@foo.com"] + to: [{nil, "bar@foo.com"}], + from: [{"Aych T. Emmel", "person@domain.com"}] } end end @@ -223,10 +230,11 @@ defmodule Yugo.ClientTest do flags: [], in_reply_to: nil, message_id: nil, - reply_to: ["marge@simpsons-family.com"], - sender: ["marge@simpsons-family.com"], + reply_to: [{"Marge", "marge@simpsons-family.com"}], + sender: [{"Marge Simpson", "marge@simpsons-family.com"}], subject: nil, - to: ["homer@simpsons-family.com"] + to: [{"HOMIEEEE", "homer@simpsons-family.com"}], + from: [{"Marge Simpson", "marge@simpsons-family.com"}] } end end diff --git a/test/yugo/filter_test.exs b/test/yugo/filter_test.exs index a64f7b4..1e8389f 100644 --- a/test/yugo/filter_test.exs +++ b/test/yugo/filter_test.exs @@ -72,8 +72,15 @@ defmodule Yugo.FilterTest do |> Filter.sender_matches(~r/bob/) assert not Filter.accepts?(f, %{envelope: %{sender: []}}) - assert not Filter.accepts?(f, %{envelope: %{sender: ["foo@bar.com", "baz@biz.com"]}}) - assert Filter.accepts?(f, %{envelope: %{sender: ["foo@bar.com", "bob@example.com"]}}) - assert Filter.accepts?(f, %{envelope: %{sender: ["zzz@foo.bob"]}}) + + assert not Filter.accepts?(f, %{ + envelope: %{sender: [{nil, "foo@bar.com"}, {"bizbaz", "baz@biz.com"}]} + }) + + assert Filter.accepts?(f, %{ + envelope: %{sender: [{nil, "foo@bar.com"}, {nil, "bob@example.com"}]} + }) + + assert Filter.accepts?(f, %{envelope: %{sender: [{nil, "zzz@foo.bob"}]}}) end end diff --git a/test/yugo/parser_test.exs b/test/yugo/parser_test.exs index ce5ddec..24c0013 100644 --- a/test/yugo/parser_test.exs +++ b/test/yugo/parser_test.exs @@ -63,15 +63,15 @@ defmodule Yugo.ParserTest do {12, :envelope, %{ bcc: [], - cc: ["minutes@cnri.reston.va.us", "klensin@mit.edu"], + cc: [{nil, "minutes@cnri.reston.va.us"}, {"John Klensin", "klensin@mit.edu"}], date: ~U[1996-07-16 19:23:25Z], - from: ["gray@cac.washington.edu"], + from: [{"Terry Gray", "gray@cac.washington.edu"}], in_reply_to: nil, message_id: "", - reply_to: ["gray@cac.washington.edu"], - sender: ["gray@cac.washington.edu"], + reply_to: [{"Terry Gray", "gray@cac.washington.edu"}], + sender: [{"Terry Gray", "gray@cac.washington.edu"}], subject: "IMAP4rev1 WG mtg summary and minutes", - to: ["imap@cac.washington.edu"] + to: [{nil, "imap@cac.washington.edu"}] }}, fetch: {12, :flags, ["\\Seen"]} ] =