Packages

POP3 email client in pure Erlang, a.k.a. erlpop derived from the original epop package. Retrieve/stream mail. Implements the following commands in accordance with RFC-1939: APOP, DELE, LIST, NOOP, PASS, RETR, RSET, STAT, TOP, UIDL, USER, QUIT. Optionally check if an email address is RFC 5322 comp...

Current section

Files

Jump to
pop3client src epop_client_utils.erl
Raw

src/epop_client_utils.erl

-module(epop_client_utils).
-author('tobbe@serc.rmit.edu.au').
%%%---------------------------------------------------------------------
%%% File : epop_client_utils.erl
%%% Created : 10 Sep 2008 by harish.mallipeddi@gmail.com
%%% Origninal code was in epop.erl, see:
%%% https://github.com/gebi/jungerl/blob/master/lib/epop/src/epop.erl
%%% Function: Some helper utils for the client.
%%% ====================================================================
%%% Licensed under the Apache License, Version 2.0 (the "License");
%%% you may not use this file except in compliance with the License.
%%% You may obtain a copy of the License at
%%%
%%% http://www.apache.org/licenses/LICENSE-2.0
%%%
%%% Unless required by applicable law or agreed to in writing, software
%%% distributed under the License is distributed on an "AS IS" BASIS,
%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%%% See the License for the specific language governing permissions and
%%% limitations under the License.
%%%
%%% The Original Code is epop-2-3
%%%
%%% The Initial Developer of the Original Code is Ericsson Telecom
%%% AB. Portions created by Ericsson are Copyright (C), 1998, Ericsson
%%% Telecom AB. All Rights Reserved.
%%%---------------------------------------------------------------------
-export([recv_sl/1,recv_ml/1,recv_ml_on_ok/1,tokenize/1]).
-export([line_by_line_start/2,line_by_line_next/1,line_by_line_after/1,get_client_from_acc/1]).
-include("epop_client.hrl").
-define(CR, 13).
-define(LF, 10).
-ifndef(ssl_api).
-define(ssl_api, ssl).
-endif.
-ifndef(gen_tcp_api).
-define(gen_tcp_api, gen_tcp).
-endif.
%% ---------------------------------------------------------
%% If we are receiving a positive response, then receive
%% it as a multi-line response. Otherwise as a single-line.
%% ---------------------------------------------------------
recv_ml_on_ok(S) ->
case recv_3_chars(S) of
[$+,$O,$K|T] ->
recv_ml(S,[$+,$O,$K|T]);
Else ->
recv_sl(S,Else)
end.
%% accumulator type for chunked/stream retrieval of data
-type accumulator() :: {ok | halted, #sk{}, list(string()), any()}.
%% Create accumulator.
-spec line_by_line_start(#sk{}, any()) -> {ok, #sk{}, [], _}.
line_by_line_start(S, Data) ->
{ok, S, [], Data}. % return accumulator
%% return next line and new accumulator.
-spec line_by_line_next(accumulator()) -> {halt, accumulator()} | {string(), accumulator()}.
line_by_line_next(Acc) ->
{State, S, T, Eol} = Acc,
case State of
halted -> {halt, Acc};
ok -> {NewLine, NewAcc} = rml(1, S, T, [], Eol, stream_lines),
{NewLine ++ Eol, NewAcc}
end.
%% End streaming. Read from socket until CRLF.CRLF termination.
-spec line_by_line_after(accumulator()) -> ok.
line_by_line_after(Acc) ->
{State, S, T, Eol} = Acc,
case State of
halted -> ok;
ok -> try
rml(1,S,T,[],Eol,dev_null),
ok
catch
exit: {error,Reason} -> {error,Reason}
end
end.
-spec get_client_from_acc(accumulator()) -> #sk{}.
get_client_from_acc({_State, S, _Mline, _Data}) -> S.
recv_3_chars(S) -> recv_3_chars(S,recv(S)).
recv_3_chars(_,Cs) when length(Cs)>=3 -> Cs;
recv_3_chars(S,Cs) -> recv_3_chars(S,Cs ++ recv(S)).
%% ------------------------------------------
%% Receive a CRLF.CRLF terminated multi-line.
%% ------------------------------------------
recv_ml(S) ->
recv_ml(S,[]).
recv_ml(S,Cc) ->
{CharList, {halted, _S,T, [] }} = rml(1,S,Cc,[], [], char_list),
{CharList,T}.
%% A simple state-event machine to handle the byte stuffing
%% of the termination octet. See also page.2 in the RFC-1939.
%% Since we are using a raw socket we are using this
%% continuation based style of programming.
%%
%% State machine starts at state 1.
%% If a Carriage return + Line feed is followed by a dot, the dot will be removed, except when followed by another CR + LF.
%% A CR + LF + dot + CR + LF is the termination marker to end reading. In this case remove CR + LF + dot.
%% When type is stream_lines, return when a line is read (State 0). Return the line without the ending CR + LF.
%%
%% Schema: State Input New state
%% 0 -> 1
%% 1 CR -> 2
%% 1 -> 1
%% 2 CR -> 2
%% 2 LF -> 3
%% 3 . -> 4 remove dot
%% 3 -> 0
%% 4 CR -> 5
%% 4 -> 0
%% 5 LF -> 6 remove CR LF
%% 5 -> 0
%% 6 -> ready
%% If not enough input data -> call recv and continue at the same State
%% accept line
rml(0, S,T,[?LF,?CR|Mline],Data,stream_lines) -> {lists:reverse(Mline),{ok,S, T, Data}}; % return new line without CR + LF
rml(0, S,T,_Mline,_Data,dev_null) -> rml(1,S,T,[],[],dev_null); % eat new line & continue
rml(0, S,T,Mline,Data,char_list) -> rml(1,S,T,Mline,Data,char_list); % add new line & continue
%% start here
rml(1,S,[?CR|T],Mline,Data,Type) -> rml(2,S,T,[?CR|Mline],Data,Type); % goto next state
rml(1,S,[H|T],Mline,Data,Type) -> rml(1,S,T,[H|Mline],Data,Type); % stay
%% CR accepted
rml(2,S,[?LF|T],Mline,Data,Type) -> rml(3,S,T,[?LF|Mline],Data,Type); % goto next state
rml(2,S,[?CR|T],Mline,Data,Type) -> rml(2,S,T,[?CR|Mline],Data,Type); % stay
rml(2,S,[H|T],Mline,Data,Type) -> rml(1,S,[H|T],Mline,Data,Type); % continue
%% CR + LF accepted
rml(3,S,[$.|T],Mline,Data,Type) -> rml(4,S,T, Mline,Data,Type); % remove dot, goto next state
rml(3,S,[H|T],Mline,Data,Type) -> rml(0,S,[H|T], Mline,Data,Type); % accept line & continue
%% CR + LF + . accepted
rml(4,S,[?CR|T],Mline,Data,Type) -> rml(5,S,T,[?CR|Mline],Data,Type); % goto next state
rml(4,S,[H|T],Mline,Data,Type) -> rml(0,S,[H|T],Mline,Data,Type); % accept line & continue
%% CR + LF + . + CR accepted
rml(5,S,[?LF|T],[?CR|Mline],Data,Type) -> rml(6,S,T, Mline,Data,Type); % remove CR + LF, goto next state
rml(5,S,[H|T],[?CR|Mline],Data,Type) -> rml(0,S,[?CR,H|T],Mline,Data,Type); % CR back in input, accept line & continue
%% CR + LF + . + CR + LF accepted, terminate reading
rml(6,S,T, [?LF,?CR|Mline],Data,stream_lines) -> {lists:reverse(Mline),{halted,S, T, Data}}; %% return last line without CR + LF
rml(6,S,T, _Mline,Data,dev_null) -> {[], {halted,S,T, Data }};
rml(6,S,T, Mline,Data,char_list) -> {lists:reverse(Mline),{halted,S,T,Data} };
%% retrieve more data from socket
rml(State,S,[],Mline,Data,Type) -> rml(State,S,recv(S),Mline,Data,Type). % get more
%% -----------------------------------------------------
%% Receive a complete single-line (ended by a CRLF pair.
%% Returns: {Single-Line, Continuation-Characters (Cc) }
%% Where Cc is the characters next to be processed.
%% -----------------------------------------------------
recv_sl(S) ->
recv_sl(S,[]).
recv_sl(S,Cc) ->
complete_sl(S,Cc,[]).
complete_sl(S,[?CR|T],Line) ->
complete_sl_lf(S,T,[?CR|Line]);
complete_sl(S,[H|T],Line) ->
complete_sl(S,T,[H|Line]);
complete_sl(S,[],Line) ->
complete_sl(S,recv(S),Line).
complete_sl_lf(_,[?LF|T],Line) ->
{lists:reverse([?LF|Line]),T};
complete_sl_lf(S,[_|T],Line) ->
complete_sl(S,T,[?LF|Line]);
complete_sl_lf(S,[],Line) ->
complete_sl_lf(S,recv(S),Line).
recv(S) ->
case recv_proto(S) of
{ok,Packet} -> Packet;
Else -> exit(Else) %% for example Else = {error, timeout} or {error, closed}
end.
recv_proto(S) ->
case S#sk.ssl of
false -> ?gen_tcp_api:recv(S#sk.sockfd,0);
_ -> ?ssl_api:recv(S#sk.sockfd,0)
end.
%% -----------------------------------------------
%% Tokenize using \r\n as the two separators.
%% -----------------------------------------------
tokenize(L) ->
tokenize(skip(L),[]).
tokenize([],Acc) ->
lists:reverse(Acc);
tokenize(L,Acc) ->
{Token,Rest} = get_token(L),
tokenize(skip(Rest),[Token|Acc]).
skip([$\r,$\n|T]) -> skip(T);
skip(L) -> L.
get_token(L) -> get_token(L,[]).
get_token([H|T],Acc) when H=/=$\r,H=/=$\n ->
get_token(T,[H|Acc]);
get_token(L,Acc) -> {lists:reverse(Acc),L}.