MailAddress v1.0.1 MailAddress View Source
Functions to handle RFC5321 Mail Addresses.
The library implements functions for handling email addresses as specified mostly by RFC5321. A large chunk of the address syntax is implemented, with a few exceptions:
- Handling of general address literals in domains (IPv4 and IPv6 address literals are supported).
- Handling of internationalized addresses (UTF8, punycode etc).
The address parser is slightly more permissive than RFC5321 allows, as it will tolerate backslash quoted characters in the local part of addresses outside of quoted strings - this is technically against the RFC5321 grammar, but there are examples everywhere of this sort of address. Despite this, encoded addresses produced by the library are always quoted correctly.
Creating Addresses
Addresses may be created a number of ways:
%MailAddress{}
- this will create a null address.Calling
new/3
- this will directly assign a local and domain part.Calling
MailAddress.Parser.parse/2
- this will parse a string into an address.
Examples
iex> %MailAddress{}
#MailAddress<>
iex> {:ok, addr} = MailAddress.new("test", "example.org")
iex> addr
#MailAddress<test@example.org>
iex> {:ok, addr, ""} = MailAddress.Parser.parse("test@example.org")
iex> addr
#MailAddress<test@example.org>
Modifying Addresses
Addresses can be modified by a number of functions, which return a new address with the appropriate update:
set_domain/3
- updates domain.set_local_part/3
- updates local part of address.
Querying Addresses
Addresses can be queryied for their components:
address_literal?/1
- checks if the address has an address literal domain set.address_literal/1
- returns address literal domain (ornil
if none).domain?/1
- checks if the address has a domain set.domain/1
- returns the address domain.local_part?/1
- checks if the address has a local part set.local_part/1
- returns the address local part.needs_quoting?/1
- checks if the address local part needs quoting.null?/1
- returns true if the address is null (no local or domain parts).
Comparing and Encoding Addresses
domains_equal?/2
- compares address domains.encode/2
- encodes address as string, taking care of quoting etc.equal?/2
- compares two addresses.local_parts_equal?/2
- compares address local parts.
Parsing Addresses
The module MailAddress.Parser contains parsing code.
MailAddress.Parser.parse/2
- parses a string into an address.MailAddress.Parser.valid?/2
- determines if address has valid syntax.
Specifying Options
The MailAddress.Options
struct is used to store options for configuring
the library. Checks are applied after every change/creation operation.
Protocols
The library implements the Inspect
and String.Chars
protocols for
MailAddress
structs.
The Inspect
protocol is used in the iex
shell and by inspect/2
to
pretty-print the MailAddress
struct contents.
The String.Chars
protocol enables a MailAddress
struct to be directly
converted into an encoded string.
Usage with Ecto
MailAddress provides cast/dump/load callbacks so that it can be used as
an Ecto
type:
defmodule EctoExample do
use Ecto.Schema
schema "emailtest" do
field :email, MailAddress
end
end
In migrations any MailAddress field should be defined as a type which
can hold a large enough (up to 256 chars) string, for example :text
.
Addresses converted using from strings using cast/4 will be checked for validity before they are accepted into the database.
Note that casting is done by default with a permissive set of options (allowing null addresses etc) - if you wish to be stricter then you can change the defaults in your config.exs (see below), or apply some further validation yourself.
Note that if you wish to supply a default value for an address field, then it should be specified as a %MailAddress{} struct, NOT a string.
config.exs configuration when using with Ecto
The library uses :mail_address
as the application name, and the
following boolean keys, which are either true
to enable, or false
to disable.
:ecto_allow_address_literal
- IP address literal domains.:ecto_allow_localhost
- allowlocalhost
as domain.:ecto_allow_null
- allow empty (null) addresses.:ecto_downcase_domain
- force domain name to lower case.:ecto_require_domain
- require domain part to be present in non-null addresses.
Usage with JSON libraries
MailAddress can be used with JSON libraries, and typically requires
implementation of the correct protocols to work, for example to
encode email addresses with Poison
, the following needs to be
done:
defimpl Poison.Encoder, for: MailAddress do
def encode(%MailAddress{} = addr, options) do
Poison.Encoder.BitString.encode(MailAddress.encode(addr, false), options)
end
end
This will encode any MailAddress structs as encoded strings.
Link to this section Summary
Types
Error return type - a tuple containing :error
and a reason string
Represents an IPv4 or IPv6 address
Success return type - a tuple containing :ok
and a MailAddress
struct
The MailAddress
struct
Functions
Address struct
Returns the decoded address literal domain (if any), or nil otherwise
Checks whether address has an address literal domain part
Applies checks and optional domain downcasing to given address using passed options
Returns the domain part of the address
Checks whether address has a domain part
Compares domain of given address with domain
(case-insensitively).
Returns true
if the domains are the same, or false
otherwise
Returns address safely encoded, optionally (and by default) bracketed
Checks whether addr_1
and addr_2
are the same.
The local parts are compared case sensitively, whilst the domain parts
are compare case insensitively
Returns the local part of the address
Checks whether address has local part set
Compares address local parts (case-sensitively).
The second parameter may be either a string or a MailAddress
struct.
Returns true
if the local parts are the same, or false
otherwise
Checks whether domain part of address is ‘localhost’, or the domain is an address literal and is [127.0.0.1] or [IPv6:::1]
Checks to see if the given string is “localhost” or equivalent ([127.0.0.1] or [IPv6:::1])
Checks whether the local part of the given address needs quoting
Creates a new MailAddress
setting both local and domain parts at
the same time using the provided (or default) options
Checks whether the address in null (no local part and no domain)
Sets the domain part of the address using the provided (or default) options
Sets the local part of the address using the provided (or default) options
Link to this section Types
Error return type - a tuple containing :error
and a reason string.
ip_address() :: :inet.ip4_address() | :inet.ip6_address()
Represents an IPv4 or IPv6 address.
Success return type - a tuple containing :ok
and a MailAddress
struct.
t() :: %MailAddress{ address_literal: nil | ip_address(), domain: String.t(), local_part: String.t(), needs_quoting: boolean() }
The MailAddress
struct.
Link to this section Functions
Address struct.
The struct SHOULD be treated as opaque
and not tampered with directly as it may change, and the needs_quoting
field is cached.
Callers should use the appropriate functions to get/set fields which ensures that everything remains in-sync and valid.
address_literal(MailAddress.t()) :: nil | ip_address()
Returns the decoded address literal domain (if any), or nil otherwise.
Examples
iex> MailAddress.address_literal(%MailAddress{})
nil
iex> {:ok, addr} = MailAddress.new("test", "[192.168.0.1]", %MailAddress.Options{allow_address_literal: true})
iex> MailAddress.address_literal(addr)
{192, 168, 0, 1}
address_literal?(MailAddress.t()) :: boolean()
Checks whether address has an address literal domain part.
Examples
iex> MailAddress.address_literal?(%MailAddress{})
false
iex> {:ok, addr} = MailAddress.new("test", "[192.168.0.1]", %MailAddress.Options{allow_address_literal: true})
iex> MailAddress.address_literal?(addr)
true
check(MailAddress.t(), MailAddress.Options.t()) :: {:ok, MailAddress.t()} | error()
Applies checks and optional domain downcasing to given address using passed options.
This function is automatically called as required by other functions
in the package, so doesn’t normally need to be called unless you
are messing with the MailAddress
struct directly (which isn’t
a good idea).
If successful, returns {:ok, new_address}
, otherwise returns
{:error, error_message}
.
Returns the domain part of the address.
Examples
iex> MailAddress.domain(%MailAddress{})
""
iex> {:ok, addr} = MailAddress.new("test", "example.org")
iex> MailAddress.domain(addr)
"example.org"
Checks whether address has a domain part.
Examples
iex> MailAddress.domain?(%MailAddress{})
false
iex> {:ok, addr} = MailAddress.new("test", "example.org")
iex> MailAddress.domain?(addr)
true
domains_equal?(MailAddress.t(), String.t() | MailAddress.t()) :: boolean()
Compares domain of given address with domain
(case-insensitively).
Returns true
if the domains are the same, or false
otherwise.
Examples
iex> {:ok, addr_1} = MailAddress.new("test", "example.org")
iex> {:ok, addr_2} = MailAddress.new("another", "example.org")
iex> {:ok, addr_3} = MailAddress.new("test", "localhost", %MailAddress.Options{allow_localhost: true})
iex> MailAddress.domains_equal?(addr_1, "example.org")
true
iex> MailAddress.domains_equal?(addr_2, "EXAMPLE.ORG")
true
iex> MailAddress.domains_equal?(addr_1, "something_else")
false
iex> MailAddress.domains_equal?(addr_1, addr_2)
true
iex> MailAddress.domains_equal?(addr_1, %MailAddress{})
false
iex> MailAddress.domains_equal?(addr_3, "localhost")
true
iex> MailAddress.domains_equal?(addr_3, "[127.0.0.1]")
true
iex> MailAddress.domains_equal?(addr_3, "[IPv6:::1]")
true
encode(MailAddress.t(), boolean()) :: String.t()
Returns address safely encoded, optionally (and by default) bracketed.
Examples
iex> MailAddress.encode(%MailAddress{}, false)
""
iex> MailAddress.encode(%MailAddress{}, true)
"<>"
iex> {:ok, addr, ""} = MailAddress.Parser.parse("test@example.org")
iex> MailAddress.encode(addr, true)
"<test@example.org>"
iex> {:ok, addr, ""} = MailAddress.Parser.parse("\"@test\"@example.org")
iex> MailAddress.encode(addr, true)
"<\"\\@test\"@example.org>"
equal?(nil | MailAddress.t() | String.t(), nil | MailAddress.t() | String.t()) :: boolean()
Checks whether addr_1
and addr_2
are the same.
The local parts are compared case sensitively, whilst the domain parts
are compare case insensitively.
Examples
iex> {:ok, addr_1} = MailAddress.new("test", "example.org")
iex> {:ok, addr_2} = MailAddress.new("test", "ExAmPlE.ORG")
iex> MailAddress.equal?(addr_1, addr_2)
true
iex> {:ok, addr_3} = MailAddress.new("fred", "ExAmPlE.ORG")
iex> MailAddress.equal?(addr_1, addr_3)
false
local_part(MailAddress.t()) :: String.t()
Returns the local part of the address.
Examples
iex> {:ok, addr} = MailAddress.new("test", "example.org")
iex> MailAddress.local_part(addr)
"test"
local_part?(MailAddress.t()) :: boolean()
Checks whether address has local part set.
Examples
iex> MailAddress.local_part?(%MailAddress{})
false
iex> {:ok, addr} = MailAddress.new("test", "example.org")
iex> MailAddress.local_part?(addr)
true
local_parts_equal?(MailAddress.t(), MailAddress.t()) :: boolean()
Compares address local parts (case-sensitively).
The second parameter may be either a string or a MailAddress
struct.
Returns true
if the local parts are the same, or false
otherwise.
Examples
iex> {:ok, addr_1} = MailAddress.new("test", "example.org")
iex> {:ok, addr_2} = MailAddress.new("test", "example.com")
iex> MailAddress.local_parts_equal?(addr_1, addr_2)
true
iex> MailAddress.local_parts_equal?(addr_1, "test")
true
iex> MailAddress.local_parts_equal?(addr_2, "TEST")
false
Checks whether domain part of address is ‘localhost’, or the domain is an address literal and is [127.0.0.1] or [IPv6:::1].
Examples
iex> {:ok, addr_1} = MailAddress.new("test", "example.org")
iex> MailAddress.localhost?(addr_1)
false
iex> {:ok, addr_2} = MailAddress.new("test", "localhost", %MailAddress.Options{allow_localhost: true})
iex> MailAddress.localhost?(addr_2)
true
iex> {:ok, addr_3} = MailAddress.new("test", "[127.0.0.1]", %MailAddress.Options{allow_address_literal: true, allow_localhost: true})
iex> MailAddress.localhost?(addr_3)
true
iex> {:ok, addr_4} = MailAddress.new("test", "[192.168.0.1]", %MailAddress.Options{allow_address_literal: true, allow_localhost: true})
iex> MailAddress.localhost?(addr_4)
false
iex> {:ok, addr_5} = MailAddress.new("test", "[IPv6:::1]", %MailAddress.Options{allow_address_literal: true, allow_localhost: true})
iex> MailAddress.localhost?(addr_5)
true
Checks to see if the given string is “localhost” or equivalent ([127.0.0.1] or [IPv6:::1]).
Examples:
iex> MailAddress.localhost_string?("test")
false
iex> MailAddress.localhost_string?("LOCALHOST")
true
iex> MailAddress.localhost_string?("[127.0.0.1]")
true
iex> MailAddress.localhost_string?("[127.0.0.1")
false
iex> MailAddress.localhost_string?("[192.168.0.1]")
false
iex> MailAddress.localhost_string?("[IPv6:::1]")
true
needs_quoting?(MailAddress.t()) :: boolean()
Checks whether the local part of the given address needs quoting.
The needs_quoting
flag on the address is updated when the address
is changed, so calling this function is inexpensive.
new(String.t(), String.t(), MailAddress.Options.t()) :: {:ok, MailAddress.t()} | error()
Creates a new MailAddress
setting both local and domain parts at
the same time using the provided (or default) options
.
NOTE: the local part isn’t parsed - it is just checked to ensure that it only contains valid characters. This means that the local part should be raw rather than quoted form.
Returns either {:ok, new_address}
or {:error, error_reason}
.
Examples
iex> {:ok, addr} = MailAddress.new("test", "example.org")
iex> addr
#MailAddress<test@example.org>
iex> {:ok, addr} = MailAddress.new("@test", "example.org")
iex> addr
#MailAddress<"\@test"@example.org>
iex> MailAddress.new("test", "example.org!")
{:error, "invalid domain"}
Checks whether the address in null (no local part and no domain).
Examples
iex> MailAddress.null?(%MailAddress{})
true
iex> {:ok, addr} = MailAddress.new("test", "example.org")
iex> MailAddress.null?(addr)
false
iex> {:ok, addr} = MailAddress.new("", "", %MailAddress.Options{allow_null: true})
iex> MailAddress.null?(addr)
true
set_domain(MailAddress.t(), String.t(), MailAddress.Options.t()) :: {:ok, MailAddress.t()} | error()
Sets the domain part of the address using the provided (or default) options.
Returns either {:ok, new_address}
or {:error, error_reason}
.
Examples
iex> {:ok, addr} = MailAddress.set_domain(%MailAddress{}, "test", %MailAddress.Options{allow_null_local_part: true})
iex> MailAddress.domain(addr)
"test"
iex> {:ok, addr} = MailAddress.new("test", "example.com")
iex> MailAddress.domain(addr)
"example.com"
iex> {:ok, addr} = MailAddress.set_domain(addr, "example.org")
iex> MailAddress.domain(addr)
"example.org"
set_local_part(MailAddress.t(), String.t(), MailAddress.Options.t()) :: {:ok, MailAddress.t()} | error()
Sets the local part of the address using the provided (or default) options.
NOTE: the local part isn’t parsed - it is just checked to ensure that it only contains valid characters, consequently it should be in raw unquoted format.
Returns either {:ok, new_address}
or {:error, error_reason}
.
Examples
iex> {:ok, addr} = MailAddress.set_local_part(%MailAddress{}, "test", %MailAddress.Options{require_domain: false})
iex> MailAddress.local_part(addr)
"test"
iex> MailAddress.set_domain(%MailAddress{}, "test", %MailAddress.Options{allow_null_local_part: false})
{:error, "local part can't be null"}
iex> {:ok, addr} = MailAddress.new("test", "example.org")
iex> MailAddress.local_part(addr)
"test"
iex> {:ok, addr} = MailAddress.set_local_part(addr, "other")
iex> MailAddress.local_part(addr)
"other"