diff --git a/src/eimap_command.erl b/src/eimap_command.erl index b01b94a..86b529d 100644 --- a/src/eimap_command.erl +++ b/src/eimap_command.erl @@ -1,63 +1,66 @@ -module(eimap_command). -export([ parse_response/4, formulate_response/2, process_status_line/2 ]). -type more_tuple() :: { more, ParseContinuation :: parse_continuation(), State :: term }. -type finished_tuple() :: { fini, Results :: term }. -type error_tuple() :: { error, Reason :: binary() }. -type parse_continuation() :: fun((Data :: binary(), Tag :: binary(), State :: term()) -> more_tuple() | finished_tuple() | error_tuple()). -callback new_command(Args :: any()) -> { binary(), single_line_response | multiline_reponse | blob_response }. % TODO:bring back when we can depend on OTP 18 which introduced optional_callback % also define parse/2 (for blob_response), formulate_response/2 %-callback process_line(Data :: binary(), Acc :: any()) -> % more_tuple() | finished_tuple() | error_tuple() | starttls. parse_response(multiline_response, Data, Tag, ParseState) -> multiline_parse(false, Data, Tag, ParseState); parse_response(all_multiline_response, Data, Tag, ParseState) -> multiline_parse(parse_tagged, Data, Tag, ParseState); parse_response(single_line_response, Data, Tag, Module) -> Module:formulate_response(Data, Tag); parse_response(blob_response, Data, Tag, { Continuation, ParseState }) -> Continuation(Data, Tag, ParseState); parse_response(blob_response, Data, Tag, Module) -> case Module:parse(Data, Tag) of { more, Continuation, ParseState } -> { more, { Continuation, ParseState } }; Response -> Response end. multiline_parse(ParseTaggedLine, Data, Tag, { LastPartialLine, Acc, Module }) -> FullBuffer = <>, { FullLinesBuffer, NewLastPartialLine } = eimap_utils:only_full_lines(FullBuffer), Lines = binary:split(FullLinesBuffer, <<"\r\n">>, [global]), process_lines(ParseTaggedLine, Tag, NewLastPartialLine, Lines, Acc, Module); multiline_parse(ParseTaggedLine, Data, Tag, Module) -> multiline_parse(ParseTaggedLine, Data, Tag, { <<>>, [], Module }). process_lines(_ParseTaggedLine, _Tag, LastPartialLine, [], Acc, Module) -> { more, { LastPartialLine, Acc, Module } }; process_lines(ParseTaggedLine, Tag, LastPartialLine, [Line|MoreLines], Acc, Module) -> process_line(ParseTaggedLine, eimap_utils:is_tagged_response(Line, Tag), Tag, LastPartialLine, Line, MoreLines, Acc, Module). -process_line(ParseTaggedLine, tagged, Tag, _LastPartialLine, Line, _MoreLines, Acc, Module) -> +process_line(ContinuationBytes, ParseTaggedLine, IsTagged, Tag, LastPartialLine, Line, [<<>>|MoreLines], Acc, Module) -> + %% skip empty lines + process_line(ContinuationBytes, ParseTaggedLine, IsTagged, Tag, LastPartialLine, Line, MoreLines, Acc, Module); +process_line(0, ParseTaggedLine, tagged, Tag, _LastPartialLine, Line, _MoreLines, Acc, Module) -> Checked = eimap_utils:check_response_for_failure(Line, Tag), Module:formulate_response(Checked, parse_tagged(Checked, ParseTaggedLine, Line, Acc, Module)); process_line(ParseTaggedLine, untagged, Tag, LastPartialLine, Line, MoreLines, Acc, Module) -> process_lines(ParseTaggedLine, Tag, LastPartialLine, MoreLines, Module:process_line(Line, Acc), Module). formulate_response(ok, Data) -> { fini, Data }; formulate_response({ _, Reason }, _Data) -> { error, Reason }. parse_tagged(_, false, _Line, Acc, _Module) -> Acc; % we are not passing the tagged line forward (no content, e.g) parse_tagged(ok, _, Line, Acc, Module) -> Module:process_tagged_line(Line, Acc); % success, so pass it forward parse_tagged(_Checked, _ParsedTaggedLine, _Line, Acc, _Module) -> Acc. % error, don't bother passing it forward -spec process_status_line(Line :: binary(), Acc :: list()) -> NewAcc :: list(). process_status_line(<<"* ", Rest/binary>>, Acc) -> process_matched_status_line(binary:split(Rest, <<" ">>, [global]), Acc); process_status_line(_, Acc) -> Acc. process_matched_status_line([Number, Key|_], Acc) -> [{ eimap_utils:binary_to_atom(Key), binary_to_integer(Number) }|Acc]; process_matched_status_line(_, Acc) -> Acc. diff --git a/test/eimap_command_tests.erl b/test/eimap_command_tests.erl new file mode 100644 index 0000000..ad22774 --- /dev/null +++ b/test/eimap_command_tests.erl @@ -0,0 +1,55 @@ +%% Copyright 2014 Kolab Systems AG (http://www.kolabsys.com) +%% +%% Aaron Seigo (Kolab Systems) +%% +%% This program is free software: you can redistribute it and/or modify +%% it under the terms of the GNU General Public License as published by +%% the Free Software Foundation, either version 3 of the License, or +%% (at your option) any later version. +%% +%% This program is distributed in the hope that it will be useful, +%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +%% GNU General Public License for more details. +%% +%% You should have received a copy of the GNU General Public License +%% along with this program. If not, see . + +-module(eimap_command_tests). +-include_lib("eunit/include/eunit.hrl"). +-export([process_line/2, formulate_response/2]). + +% c("test/eimap_uidset_tests.erl"). eunit:test(eimap_uidset). + +process_line(Data, Acc) -> [Data | Acc]. +formulate_response(Result, Data) -> eimap_command:formulate_response(Result, lists:reverse(Data)). + + +literal_continuations_test_() -> + Data = + [ + % input, output + % { Binary Response, Binary Tag, Parsed Results } + { + <<"* STATUS blurdybloop (MESSAGES 231 {14+}\r\nUIDNEXT 44292)\r\nabcd OK Begin TLS negotiation now\r\n">>, + <<"abcd">>, + { fini, [<<"* STATUS blurdybloop (MESSAGES 231 UIDNEXT 44292)">>] } + }, + { + <<"* STATUS blurdybloop (MESSAGES 231 {h14+}\r\nUIDNEXT 44292)\r\nabcd OK Begin TLS negotiation now\r\n">>, + <<"abcd">>, + { fini, [<<"* STATUS blurdybloop (MESSAGES 231 {h14+}">>, <<"UIDNEXT 44292)">>] } + }, + { + <<"* STATUS blurdybloop (MESSAGES 231 \r\nUIDNEXT 44292)\r\nabcd OK Begin TLS negotiation now\r\n">>, + <<"abcd">>, + { fini, [<<"* STATUS blurdybloop (MESSAGES 231 ">>, <<"UIDNEXT 44292)">>] } + }, + { + <<"* STATUS blurdybloop (MESSAGES 231 UIDNEXT 44292)\r\nabcd OK Begin TLS negotiation now\r\n">>, + <<"abcd">>, + { fini, [<<"* STATUS blurdybloop (MESSAGES 231 UIDNEXT 44292)">>] } + } + ], + lists:foldl(fun({ Binary, Tag, Result }, Acc) -> [?_assertEqual(Result, eimap_command:parse_response(multiline_response, Binary, Tag, eimap_command_tests))|Acc] end, [], Data). +