Skip to content

Commit

Permalink
More documentation!
Browse files Browse the repository at this point in the history
  • Loading branch information
Vagabond committed Mar 24, 2011
1 parent accfd88 commit 5d09f2f
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 10 deletions.
3 changes: 2 additions & 1 deletion src/gen_smtp_server_session.erl
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
%%% (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
%%% SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

%% @doc A Per-connection SMTP server, extensible via a callback module. This
%% @doc Process representing a SMTP session, extensible via a callback module. This
%% module is implemented as a behaviour that the callback module should
%% implement. To see the details of the required callback functions to provide,
%% please see `smtp_server_example'.
Expand Down Expand Up @@ -106,6 +106,7 @@ start_link(Socket, Module, Options) ->
start(Socket, Module, Options) ->
gen_server:start(?MODULE, [Socket, Module, Options], []).

%% @private
-spec(init/1 :: (Args :: list()) -> {'ok', #state{}, ?TIMEOUT} | {'stop', any()} | 'ignore').
init([Socket, Module, Options]) ->
{ok, {PeerName, _Port}} = socket:peername(Socket),
Expand Down
42 changes: 37 additions & 5 deletions src/mimemail.erl
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,33 @@
%%% (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
%%% SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

%%% @doc A module for decoding/encoding MIME 1.0 email
%% @doc A module for decoding/encoding MIME 1.0 email.
%% The encoder and decoder operate on the same datastructure, which is as follows:
%% A 5-tuple with the following elements: `{Type, SubType, Headers, Parameters, Body}'.
%%
%% `Type' and `SubType' are the MIME type of the email, examples are `text/plain' or
%% `multipart/alternative'. The decoder splits these into 2 fields so you can filter by
%% the main type or by the subtype.
%%
%% `Headers' consists of a list of key/value pairs of binary values eg.
%% `{<<"From">>, <<"Andrew Thompson <andrew@hijacked.us>">>}'. There is no parsing of
%% the header aside from un-wrapping the lines and splitting the header name from the
%% header value.
%%
%% `Parameters' is a list of 3 key/value tuples. The 3 keys are `<<"content-type-params">>',
%% `<<"dispisition">>' and `<<"disposition-params">>'.
%% `content-type-params' is a key/value list of parameters on the content-type header, this
%% usually consists of things like charset and the format parameters. `disposition' indicates
%% how the data wants to be displayed, this is usually 'inline'. `disposition-params' is a list of
%% disposition information, eg. the filename this section should be saved as, the modification
%% date the file should be saved with, etc.
%%
%% Finally, `Body' can be one of several different types, depending on the structure of the email.
%% For a simple email, the body will usually be a binary consisting of the message body, In the
%% case of a multipart email, its a list of these 5-tuple MIME structures. The third possibility,
%% in the case of a message/rfc822 attachment, body can be a single 5-tuple MIME structure.
%%
%% You should see the relevant RFCs (2045, 2046, 2047, etc.) for more information.
-module(mimemail).

-ifdef(TEST).
Expand All @@ -36,17 +62,19 @@

-type(mimetuple() :: {binary(), binary(), [{binary(), binary()}], [{binary(), binary()}], binary() | [{binary(), binary(), [{binary(), binary()}], [{binary(), binary()}], binary() | [tuple()]}] | tuple()}).

-type(options() :: [{'encoding', binary()} | {'decode_attachment', boolean()}]).

-spec(decode/1 :: (Email :: binary()) -> mimetuple()).
%% @doc Decode a MIME email from a binary.
decode(All) ->
{Headers, Body} = parse_headers(All),
decode(Headers, Body, ?DEFAULT_OPTIONS).

-spec(decode/2 :: (Headers :: [{binary(), binary()}], Body :: binary()) -> mimetuple()).
-spec(decode/2 :: (Headers :: [{binary(), binary()}], Options :: options()) -> mimetuple()).
%% @doc Decode with custom options
decode(All, Options) when is_binary(All), is_list(Options) ->
{Headers, Body} = parse_headers(All),
decode(Headers, Body, Options);
decode(Headers, Body) when is_list(Headers), is_binary(Body) ->
decode(Headers, Body, ?DEFAULT_OPTIONS).
decode(Headers, Body, Options).

decode(OrigHeaders, Body, Options) ->
%io:format("headers: ~p~n", [Headers]),
Expand Down Expand Up @@ -78,6 +106,7 @@ decode(OrigHeaders, Body, Options) ->
end.

-spec(encode/1 :: (MimeMail :: mimetuple()) -> binary()).
%% @doc Encode a MIME tuple to a binary.
encode({Type, Subtype, Headers, ContentTypeParams, Parts}) ->
{FixedParams, FixedHeaders} = ensure_content_headers(Type, Subtype, ContentTypeParams, Headers, Parts, true),
FixedHeaders2 = check_headers(FixedHeaders),
Expand Down Expand Up @@ -178,6 +207,7 @@ decode_component(_Headers, _Body, Other, _Options) ->
erlang:error({mime_version, Other}).

-spec(get_header_value/3 :: (Needle :: binary(), Headers :: [{binary(), binary()}], Default :: any()) -> binary() | any()).
%% @doc Do a case-insensitive header lookup to return that header's value, or the specified default.
get_header_value(Needle, Headers, Default) ->
%io:format("Headers: ~p~n", [Headers]),
F =
Expand All @@ -193,6 +223,7 @@ get_header_value(Needle, Headers, Default) ->
end.

-spec(get_header_value/2 :: (Needle :: binary(), Headers :: [{binary(), binary()}]) -> binary() | 'undefined').
%% @doc Do a case-insensitive header lookup to return the header's value, or `undefined'.
get_header_value(Needle, Headers) ->
get_header_value(Needle, Headers, undefined).

Expand Down Expand Up @@ -300,6 +331,7 @@ split_body_by_boundary_(Body, Boundary, Acc) ->
end.

-spec(parse_headers/1 :: (Body :: binary()) -> {[{binary(), binary()}], binary()}).
%% @doc Parse the headers off of a message and return a list of headers and the trailing body.
parse_headers(Body) ->
case binstr:strpos(Body, "\r\n") of
0 ->
Expand Down
13 changes: 9 additions & 4 deletions src/smtp_util.erl
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@
%% @doc Module with some general utility functions for SMTP.

-module(smtp_util).
-compile(export_all).

-export([
mxlookup/1, guess_FQDN/0, compute_cram_digest/2, get_cram_string/1,
trim_crlf/1, rfc5322_timestamp/0, zone/0, generate_message_id/0,
generate_message_boundary/0]).
-include_lib("kernel/src/inet_dns.hrl").

%% @doc returns a sorted list of mx servers for `Domain', lowest distance first
Expand Down Expand Up @@ -62,6 +64,7 @@ compute_cram_digest(Key, Data) ->
Bin = crypto:md5_mac(Key, Data),
list_to_binary([io_lib:format("~2.16.0b", [X]) || <<X>> <= Bin]).

%% @doc Generate a seed string for CRAM.
-spec(get_cram_string/1 :: (Hostname :: string()) -> string()).
get_cram_string(Hostname) ->
binary_to_list(base64:encode(lists:flatten(io_lib:format("<~B.~B@~s>", [crypto:rand_uniform(0, 4294967295), crypto:rand_uniform(0, 4294967295), Hostname])))).
Expand All @@ -74,15 +77,15 @@ trim_crlf(String) ->
-define(DAYS, ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]).
-define(MONTHS, ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]).

%% @doc Generate a RFC 5322 timestamp based on the current time
rfc5322_timestamp() ->
{{Year, Month, Day}, {Hour, Minute, Second}} = calendar:local_time(),
NDay = calendar:day_of_the_week(Year, Month, Day),
DoW = lists:nth(NDay, ?DAYS),
MoY = lists:nth(Month, ?MONTHS),
io_lib:format("~s, ~b ~s ~b ~b:~b:~b ~s", [DoW, Day, MoY, Year, Hour, Minute, Second, zone()]).

% borrowed from YAWS
%% @doc Calculate the current timezone and format it like -0400. Borrowed from YAWS.
zone() ->
Time = erlang:universaltime(),
LocalTime = calendar:universal_time_to_local_time(Time),
Expand All @@ -97,11 +100,13 @@ zone(Val) when Val < 0 ->
zone(Val) when Val >= 0 ->
io_lib:format("+~4..0w", [trunc(abs(Val))]).

%% @doc Generate a unique message ID
generate_message_id() ->
FQDN = guess_FQDN(),
Md5 = [io_lib:format("~2.16.0b", [X]) || <<X>> <= erlang:md5(term_to_binary([erlang:now(), FQDN]))],
io_lib:format("<~s@~s>", [Md5, FQDN]).

%% @doc Generate a unique MIME message boundary
generate_message_boundary() ->
FQDN = guess_FQDN(),
["_=", [io_lib:format("~2.36.0b", [X]) || <<X>> <= erlang:md5(term_to_binary([erlang:now(), FQDN]))], "=_"].
Expand Down

0 comments on commit 5d09f2f

Please sign in to comment.