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:

Querying Addresses

Addresses can be queryied for their components:

Comparing and Encoding Addresses

Parsing Addresses

The module MailAddress.Parser contains parsing code.

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 - allow localhost 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

t()

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

Link to this type error() View Source
error() :: {:error, String.t()}

Error return type - a tuple containing :error and a reason string.

Represents an IPv4 or IPv6 address.

Link to this type success() View Source
success() ::
  {:ok,
   %MailAddress{
     address_literal: term(),
     domain: term(),
     local_part: term(),
     needs_quoting: term()
   }}

Success return type - a tuple containing :ok and a MailAddress struct.

Link to this type t() View Source
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.

Link to this function address_literal(mail_address) View Source
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}
Link to this function address_literal?(mail_address) View Source
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

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"
Link to this function domain?(mail_address) View Source
domain?(MailAddress.t()) :: boolean()

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
Link to this function domains_equal?(addr, a2) View Source
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
Link to this function encode(addr, bracket \\ true) View Source
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>"
Link to this function equal?(addr_1, addr_2) View Source
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
Link to this function local_part(mail_address) View Source
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"
Link to this function local_part?(mail_address) View Source
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
Link to this function local_parts_equal?(arg1, arg2) View Source
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
Link to this function localhost?(mail_address) View Source
localhost?(MailAddress.t()) :: boolean()

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
Link to this function localhost_string?(str) View Source
localhost_string?(String.t()) :: boolean()

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
Link to this function needs_quoting?(mail_address) View Source
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.

Link to this function new(arg1, arg2, options \\ %MailAddress.Options{}) View Source

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
Link to this function set_domain(addr, arg, options \\ %MailAddress.Options{}) View Source
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"
Link to this function set_local_part(addr, arg, options \\ %MailAddress.Options{}) View Source
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"