Permalink
Browse files

More documentation!

  • Loading branch information...
Vagabond committed Mar 24, 2011
1 parent accfd88 commit 5d09f2f963cc96d5d54aee97b1020a41731d841e
Showing with 48 additions and 10 deletions.
  1. +2 −1 src/gen_smtp_server_session.erl
  2. +37 −5 src/mimemail.erl
  3. +9 −4 src/smtp_util.erl
@@ -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'.
@@ -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),
View
@@ -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).
@@ -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]),
@@ -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),
@@ -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 =
@@ -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).
@@ -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 ->
View
@@ -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
@@ -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])))).
@@ -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),
@@ -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]))], "=_"].

0 comments on commit 5d09f2f

Please sign in to comment.