Packages

A library to validate email address format.

Current section

Files

Jump to
email_validator src email_validator.erl
Raw

src/email_validator.erl

-module(email_validator).
-export([validate/1]).
-export([parse/1]).
-define(DOMAIN_MAX_SIZE, 255).
-define(LOCAL_MAX_SIZE, 64).
-define(ADDR_MAX_SIZE, ?LOCAL_MAX_SIZE + ?DOMAIN_MAX_SIZE + 1).
-type parse_result() :: #{
'local-part' := binary(),
'domain' := binary()
}.
-spec validate(binary() | string()) ->
ok | {error, term()}.
validate(Address) ->
case parse(Address) of
{ok, _Result} ->
ok;
{incomplete, Result} ->
{error, {parse_incomplete, Result}};
Error ->
Error
end.
-spec parse(binary() | string()) ->
{ok, parse_result()} | {incomplete, term()} | {error, term()}.
parse(Address) when is_list(Address) ->
case unicode:characters_to_binary(Address) of
Result when is_binary(Result) ->
parse(Result);
{error, Encoded, Rest} ->
{error, {unicode, {invalid, Encoded, Rest}}};
{incomplete, Encoded, Rest} ->
{error, {unicode, {incomplete, Encoded, Rest}}}
end;
parse(Address) when is_binary(Address) ->
parse_addr(Address);
parse(Address) ->
erlang:error(badarg, [Address]).
%%
% As per https://tools.ietf.org/html/rfc5321#section-4.5.3.1
% we need to validate maximum sizes
parse_addr(Address) when byte_size(Address) =< ?ADDR_MAX_SIZE ->
case parse_rule('mailbox', Address) of
{ok, ParsedAddr} ->
validate_sizes(ParsedAddr);
Error ->
Error
end;
parse_addr(Address) ->
{error, {'mailbox', {too_big, Address}}}.
validate_sizes(#{'local-part' := Local, 'domain' := Domain} = ParsedAddr) ->
case validate_part_sizes(Local, Domain) of
ok -> {ok, ParsedAddr};
Error -> Error
end.
validate_part_sizes(Local, Domain) ->
case validate_local_size(Local) of
ok -> validate_domain_size(Domain);
Error -> Error
end.
validate_local_size(Local) when byte_size(Local) =< ?LOCAL_MAX_SIZE ->
ok;
validate_local_size(Local) ->
{error, {'local-part', {too_big, Local}}}.
validate_domain_size(Domain) when byte_size(Domain) =< ?DOMAIN_MAX_SIZE ->
ok;
validate_domain_size(Domain) ->
{error, {'domain', {too_big, Domain}}}.
parse_rule(Rule, Data) ->
case email_validator_abnf:decode(Rule, Data) of
{ok, ParseResult, <<>>} ->
{ok, decode_parse_result(ParseResult)};
{ok, Incomplete, Remainder} ->
{incomplete, {Rule, decode_parse_result(Incomplete, Remainder)}};
fail ->
{error, {Rule, {parse_failed, Data}}}
end.
decode_parse_result([Local, $@, Domain]) ->
#{
'local-part' => decode_local_part(Local),
'domain' => decode_domain(Domain)
}.
decode_parse_result(Incomplete, Remainder) ->
{list_to_binary(lists:flatten(Incomplete)), Remainder}.
%TODO: More result processing, normalize IP addresses, etc
decode_local_part(Local) ->
list_to_binary(lists:flatten(Local)).
decode_domain(Domain) ->
list_to_binary(lists:flatten(Domain)).