Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master' into remove_text_part_…
Browse files Browse the repository at this point in the history
…sorting
  • Loading branch information
Awlex committed Nov 28, 2024
2 parents c0d91b7 + 0a1a51b commit 86db86b
Show file tree
Hide file tree
Showing 26 changed files with 1,374 additions and 400 deletions.
50 changes: 50 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Elixir CI

on:
push:
branches: [master]
pull_request:
branches: [master]

env:
MIX_ENV: test

permissions:
contents: read

jobs:
build:
name: Build and test
runs-on: ubuntu-latest

strategy:
matrix:
elixir: ['1.14.5', '1.15.4', '1.16.3', '1.17.3']
erlang: ['24.3', '25.3', '26.0', '27.1']
exclude:
- elixir: '1.14.5'
erlang: '27.1'
- elixir: '1.15.4'
erlang: '27.1'
- elixir: '1.16.3'
erlang: '27.1'
- elixir: '1.17.3'
erlang: '24.3'
steps:
- uses: actions/checkout@v3
- name: Set up Elixir
uses: erlef/setup-beam@v1
with:
version-type: 'loose'
elixir-version: ${{ matrix.elixir }}
otp-version: ${{ matrix.erlang }}
- name: Restore dependencies cache
uses: actions/cache@v2
with:
path: deps
key: ${{ runner.os }}-${{ matrix.erlang }}-${{ matrix.elixir }}-mix-${{ hashFiles('**/mix.lock') }}
restore-keys: ${{ runner.os }}-${{ matrix.erlang }}-${{ matrix.elixir }}-mix-
- name: Install dependencies
run: mix deps.get
- name: Run tests
run: mix test
42 changes: 41 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,45 @@
# Changelog

## 0.4.3 2024-11-15

* Update header parsing to decode encoded words after parsing the header (RFC 2047) https://github.com/DockYard/elixir-mail/pull/181
* Refactor RFC2822 datetime parser and assume unknown named timezones are UTC https://github.com/DockYard/elixir-mail/pull/183

## 0.4.2 2024-10-24

* Encoded strings that are not valid UTF-8 are now decoded to binary (as-is) rather than raising an error
* Add `charset_handler` option to `Mail.Parsers.RFC2822` to allow custom charset handling https://github.com/DockYard/elixir-mail/pull/178

## 0.4.1 2024-10-09

* Fix quoted-printable encoding of reserved characters https://github.com/DockYard/elixir-mail/pull/176
* Updates to documentation with doctests

## 0.4.0 2024-10-02

* Change date parser to return {:error, date_text} when date is unparsable https://github.com/DockYard/elixir-mail/pull/169
* Ignore space between two encoded words (as per RFC 2047) https://github.com/DockYard/elixir-mail/pull/168
* Change order of part lookup to match RFC 2046, §5.1.4 https://github.com/DockYard/elixir-mail/pull/167
* Fix 8-bit decoding to preserve line breaks but restore wrapping https://github.com/DockYard/elixir-mail/pull/166
* Fix 7-bit decoding to preserve line breaks but restore wrapping https://github.com/DockYard/elixir-mail/pull/164
* Fix decoding of encoded words with spaces https://github.com/DockYard/elixir-mail/pull/160
* Default to UTF-8 charset https://github.com/DockYard/elixir-mail/pull/162
* Add support for case insensitive headers https://github.com/DockYard/elixir-mail/pull/161

## 0.3.1 2023-08-04

* Fix get_attachments/1 for attachments without filename prop https://github.com/DockYard/elixir-mail/pull/154

## 0.3.0 2023-08-01

* Add DateTime and time zone support to date parsing/rendering
* Add Mail.parse/2 with default RFC2822 parser
* Use a binary accumulator in QuotedPrintable encoder to reduce memory https://github.com/DockYard/elixir-mail/pull/145
* Handle strings that appear to be quoted printable but are not https://github.com/DockYard/elixir-mail/pull/141
* Provide default charset when no charset specified https://github.com/DockYard/elixir-mail/pull/144
* Fix handling obsolete timezone (UT) in obsolete date/time format https://github.com/DockYard/elixir-mail/pull/143
* Split encoded words on headers https://github.com/DockYard/elixir-mail/pull/134

## 0.2.3 2021-06-28

* Add support for incorrect case in date parsing https://github.com/DockYard/elixir-mail/pull/132
Expand Down Expand Up @@ -36,7 +76,7 @@
* removed `Mail.Message.has_attachment?` and `Mail.Message.has_text_part?` https://github.com/DockYard/elixir-mail/pull/74
* added `Mail.has_attachments?` and `Mail.has_text_parts?` https://github.com/DockYard/elixir-mail/pull/74
* added `Mail.get_attachments` https://github.com/DockYard/elixir-mail/pull/75
* Allow RFC2822 email regex to be overriden by config https://github.com/DockYard/elixir-mail/pull/73
* Allow RFC2822 email regex to be overridden by config https://github.com/DockYard/elixir-mail/pull/73
* Allow `Mail.put_attachment` to use in-memory data in tuple https://github.com/DockYard/elixir-mail/pull/58
* Support obsolete timestamps https://github.com/DockYard/elixir-mail/pull/70
* Fix test suite for Elixir 1.4+ https://github.com/DockYard/elixir-mail/pull/67
Expand Down
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# Mail [![Build Status](https://secure.travis-ci.org/DockYard/elixir-mail.svg?branch=master)](https://travis-ci.org/DockYard/elixir-mail)
# Mail

![Build Status](https://github.com/DockYard/elixir-mail/actions/workflows/main.yml/badge.svg)

An RFC2822 implementation in Elixir, built for composability.

Expand All @@ -10,7 +12,7 @@ An RFC2822 implementation in Elixir, built for composability.
def deps do
[
# Get from hex
{:mail, "~> 0.2"},
{:mail, "~> 0.4"},

# Or use the latest from master
{:mail, github: "DockYard/elixir-mail"}
Expand Down Expand Up @@ -49,7 +51,7 @@ message =
After you have built your message you can render it:

```elixir
rendered_message = Mail.Renderers.RFC2822.render(message)
rendered_message = Mail.render(message)
```

## Parsing
Expand All @@ -58,7 +60,7 @@ If you'd like to parse an already rendered message back into
a data model:

```elixir
Mail.Parsers.RFC2822.parse(rendered_message)
%Mail.Message{} = message = Mail.parse(rendered_message)
```

[There are more functions described in the docs](https://hexdocs.pm/mail/Mail.html)
Expand Down
30 changes: 0 additions & 30 deletions config/config.exs

This file was deleted.

110 changes: 72 additions & 38 deletions lib/mail.ex
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ defmodule Mail do
def build_multipart,
do: %Mail.Message{multipart: true}

@doc """
Primary hook for parsing
You can pass in your own custom parse module. That module
must have a `parse/1` function that accepts a string or list of lines
By default the `parser` will be `Mail.Parsers.RFC2822`
"""
def parse(message, parser \\ Mail.Parsers.RFC2822) do
parser.parse(message)
end

@doc """
Add a plaintext part to the message
Expand Down Expand Up @@ -58,7 +70,7 @@ defmodule Mail do
["text/plain", {"charset", charset}]

_else ->
"text/plain"
["text/plain", {"charset", default_charset()}]
end

Mail.Message.put_body(message, body)
Expand All @@ -67,23 +79,22 @@ defmodule Mail do
end

@doc """
Find the text part of a given mail
Find the last text part of a given mail
RFC 2046, §5.1.4 “In general, the best choice is the LAST part of a type supported by the recipient system's local environment.”
If single part with `content-type` "text/plain", returns itself
If single part without `content-type` "text/plain", returns `nil`
If multipart with part having `content-type` "text/plain" will return that part
If multipart without part having `content-type` "text/plain" will return `nil`
"""
def get_text(%Mail.Message{multipart: true} = message) do
Enum.reduce_while(message.parts, nil, fn sub_message, acc ->
Enum.reduce_while(Enum.reverse(message.parts), nil, fn sub_message, acc ->
text_part = get_text(sub_message)
if text_part, do: {:halt, text_part}, else: {:cont, acc}
end)
end

def get_text(%Mail.Message{headers: %{"content-type" => "text/plain" <> _}} = message),
do: message

def get_text(%Mail.Message{headers: %{"content-type" => ["text/plain" | _]}} = message),
do: message

Expand Down Expand Up @@ -120,7 +131,7 @@ defmodule Mail do
["text/html", {"charset", charset}]

_else ->
"text/html"
["text/html", {"charset", default_charset()}]
end

Mail.Message.put_body(message, body)
Expand All @@ -129,15 +140,17 @@ defmodule Mail do
end

@doc """
Find the html part of a given mail
Find the last html part of a given mail
RFC 2046, §5.1.4 “In general, the best choice is the LAST part of a type supported by the recipient system's local environment.”
If single part with `content-type` "text/html", returns itself
If single part without `content-type` "text/html", returns `nil`
If multipart with part having `content-type` "text/html" will return that part
If multipart without part having `content-type` "text/html" will return `nil`
"""
def get_html(%Mail.Message{multipart: true} = message) do
Enum.reduce_while(message.parts, nil, fn sub_message, acc ->
Enum.reduce_while(Enum.reverse(message.parts), nil, fn sub_message, acc ->
html_part = get_html(sub_message)
if html_part, do: {:halt, html_part}, else: {:cont, acc}
end)
Expand All @@ -150,6 +163,10 @@ defmodule Mail do

def get_html(%Mail.Message{}), do: nil

defp default_charset do
"UTF-8"
end

@doc """
Add an attachment part to the message
Expand Down Expand Up @@ -212,8 +229,13 @@ defmodule Mail do
walk_parts([message], {:cont, []}, fn message, acc ->
case Mail.Message.is_attachment?(message) do
true ->
["attachment", {"filename", filename} | _] =
Mail.Message.get_header(message, :content_disposition)
filename =
case List.wrap(Mail.Message.get_header(message, :content_disposition)) do
["attachment" | properties] ->
Enum.find_value(properties, "Unknown", fn {key, value} ->
key == "filename" && value
end)
end

{:cont, List.insert_at(acc, -1, {filename, message.body})}

Expand All @@ -236,8 +258,10 @@ defmodule Mail do
@doc """
Add a new `subject` header
Mail.put_subject(%Mail.Message{}, "Welcome to DockYard!")
%Mail.Message{headers: %{subject: "Welcome to DockYard!"}}
## Examples
iex> Mail.put_subject(%Mail.Message{}, "Welcome to DockYard!")
%Mail.Message{headers: %{"subject" => "Welcome to DockYard!"}}
"""
def put_subject(message, subject),
do: Mail.Message.put_header(message, "subject", subject)
Expand All @@ -254,15 +278,17 @@ defmodule Mail do
Recipients can be added as a single string or a list of strings.
The list of recipients will be concated to the previous value.
Mail.put_to(%Mail.Message{}, "[email protected]")
%Mail.Message{headers: %{to: ["[email protected]"]}}
## Examples
iex> Mail.put_to(%Mail.Message{}, "[email protected]")
%Mail.Message{headers: %{"to" => ["[email protected]"]}}
Mail.put_to(%Mail.Message{}, ["[email protected]", "[email protected]"])
%Mail.Message{headers: %{to: ["[email protected]", "[email protected]"]}}
iex> Mail.put_to(%Mail.Message{}, ["[email protected]", "[email protected]"])
%Mail.Message{headers: %{"to" => ["[email protected]", "[email protected]"]}}
Mail.put_to(%Mail.Message{}, "[email protected]")
|> Mail.put_to(["[email protected]", "[email protected]"])
%Mail.Message{headers: %{to: ["[email protected]", "[email protected]", "[email protected]"]}}
iex> Mail.put_to(%Mail.Message{}, "[email protected]")
iex> |> Mail.put_to(["[email protected]", "[email protected]"])
%Mail.Message{headers: %{"to" => ["[email protected]", "[email protected]", "[email protected]"]}}
The value of a recipient must conform to either a string value or a tuple with two elements,
otherwise an `ArgumentError` is raised.
Expand Down Expand Up @@ -294,15 +320,17 @@ defmodule Mail do
Recipients can be added as a single string or a list of strings.
The list of recipients will be concated to the previous value.
Mail.put_cc(%Mail.Message{}, "[email protected]")
%Mail.Message{headers: %{cc: ["[email protected]"]}}
## Examples
Mail.put_cc(%Mail.Message{}, ["[email protected]", "[email protected]"])
%Mail.Message{headers: %{cc: ["one@example.com", "two@example.com"]}}
iex> Mail.put_cc(%Mail.Message{}, "[email protected]")
%Mail.Message{headers: %{"cc" => ["[email protected]"]}}
Mail.put_cc(%Mail.Message{}, "[email protected]")
|> Mail.put_cc(["[email protected]", "[email protected]"])
%Mail.Message{headers: %{cc: ["[email protected]", "[email protected]", "[email protected]"]}}
iex> Mail.put_cc(%Mail.Message{}, ["[email protected]", "[email protected]"])
%Mail.Message{headers: %{"cc" => ["[email protected]", "[email protected]"]}}
iex> Mail.put_cc(%Mail.Message{}, "[email protected]")
iex> |> Mail.put_cc(["[email protected]", "[email protected]"])
%Mail.Message{headers: %{"cc" => ["[email protected]", "[email protected]", "[email protected]"]}}
The value of a recipient must conform to either a string value or a tuple with two elements,
otherwise an `ArgumentError` is raised.
Expand Down Expand Up @@ -334,15 +362,17 @@ defmodule Mail do
Recipients can be added as a single string or a list of strings.
The list of recipients will be concated to the previous value.
Mail.put_bcc(%Mail.Message{}, "[email protected]")
%Mail.Message{headers: %{bcc: ["[email protected]"]}}
## Examples
iex> Mail.put_bcc(%Mail.Message{}, "[email protected]")
%Mail.Message{headers: %{"bcc" => ["[email protected]"]}}
Mail.put_bcc(%Mail.Message{}, ["[email protected]", "[email protected]"])
%Mail.Message{headers: %{bcc: ["[email protected]", "[email protected]"]}}
iex> Mail.put_bcc(%Mail.Message{}, ["[email protected]", "[email protected]"])
%Mail.Message{headers: %{"bcc" => ["[email protected]", "[email protected]"]}}
Mail.put_bcc(%Mail.Message{}, "[email protected]")
|> Mail.put_bcc(["[email protected]", "[email protected]"])
%Mail.Message{headers: %{bcc: ["[email protected]", "[email protected]", "[email protected]"]}}
iex> Mail.put_bcc(%Mail.Message{}, "[email protected]")
iex> |> Mail.put_bcc(["[email protected]", "[email protected]"])
%Mail.Message{headers: %{"bcc" => ["[email protected]", "[email protected]", "[email protected]"]}}
The value of a recipient must conform to either a string value or a tuple with two elements,
otherwise an `ArgumentError` is raised.
Expand Down Expand Up @@ -371,8 +401,10 @@ defmodule Mail do
@doc """
Add a new `from` header
Mail.put_from(%Mail.Message{}, "[email protected]")
%Mail.Message{headers: %{from: "[email protected]"}}
## Examples
iex> Mail.put_from(%Mail.Message{}, "[email protected]")
%Mail.Message{headers: %{"from" => "[email protected]"}}
"""
def put_from(message, sender),
do: Mail.Message.put_header(message, "from", sender)
Expand All @@ -386,8 +418,10 @@ defmodule Mail do
@doc """
Add a new `reply-to` header
Mail.put_reply_to(%Mail.Message{}, "[email protected]")
%Mail.Message{headers: %{reply_to: "[email protected]"}}
## Examples
iex> Mail.put_reply_to(%Mail.Message{}, "[email protected]")
%Mail.Message{headers: %{"reply-to" => "[email protected]"}}
"""
def put_reply_to(message, reply_address),
do: Mail.Message.put_header(message, "reply-to", reply_address)
Expand Down
Loading

0 comments on commit 86db86b

Please sign in to comment.