diff --git a/src/eimap_command.erl b/src/eimap_command.erl index 86b529d..dc329fe 100644 --- a/src/eimap_command.erl +++ b/src/eimap_command.erl @@ -1,66 +1,78 @@ -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). + { FirstLine, ContinuationBytes } = eimap_utils:num_literal_continuation_bytes(Line), + process_line(ContinuationBytes, ParseTaggedLine, eimap_utils:is_tagged_response(FirstLine, Tag), Tag, LastPartialLine, FirstLine, 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). +process_line(0, ParseTaggedLine, untagged, Tag, LastPartialLine, Line, MoreLines, Acc, Module) -> + io:format("Calling it here with ~p~n~n...", [Line]), + process_lines(ParseTaggedLine, Tag, LastPartialLine, MoreLines, Module:process_line(Line, Acc), Module); +process_line(ContinuationBytes, ParseTaggedLine, _IsTagged, Tag, LastPartialLine, Line, [], Acc, Module) -> + %% the line was continued, but there is no more lines ... so this line is now our last partial line. more must be on its way + io:format("Missing lines!~p~n~n", [Acc]), + BytesAsBinary = integer_to_binary(ContinuationBytes), + process_lines(ParseTaggedLine, Tag, <>, [], Acc, Module); +process_line(_ContinuationBytes, ParseTaggedLine, IsTagged, Tag, LastPartialLine, Line, [NextLine|MoreLines], Acc, Module) -> + { StrippedNextLine, NextContinuationBytes } = eimap_utils:num_literal_continuation_bytes(NextLine), + io:format("Connected up the next line: ~p ~i~n", [StrippedNextLine, NextContinuationBytes]), + FullLine = <>, + process_line(NextContinuationBytes, ParseTaggedLine, IsTagged, Tag, LastPartialLine, FullLine, MoreLines, 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 index ad22774..dbe2a39 100644 --- a/test/eimap_command_tests.erl +++ b/test/eimap_command_tests.erl @@ -1,55 +1,65 @@ %% 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). +% c("test/eimap_command_tests.erl"). eunit:test(eimap_command). -process_line(Data, Acc) -> [Data | Acc]. +process_line(Data, Acc) -> [{ marked, 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">>, + <<"* STATUS 1 (MESSAGES 231 {14+}\r\nUIDNEXT 44292)\r\nabcd OK Begin TLS negotiation now\r\n">>, <<"abcd">>, - { fini, [<<"* STATUS blurdybloop (MESSAGES 231 UIDNEXT 44292)">>] } + { fini, [ { marked, <<"* STATUS 1 (MESSAGES 231 UIDNEXT 44292)">> } ] } }, { - <<"* STATUS blurdybloop (MESSAGES 231 {h14+}\r\nUIDNEXT 44292)\r\nabcd OK Begin TLS negotiation now\r\n">>, + <<"* STATUS 1a (MESSAGES 231 {14+}\r\nUIDNEXT 44292)\r\nabcd OK Begin TLS negotiation">>, <<"abcd">>, - { fini, [<<"* STATUS blurdybloop (MESSAGES 231 {h14+}">>, <<"UIDNEXT 44292)">>] } + { more, { <<"abcd OK Begin TLS negotiation">>, [ { marked, <<"* STATUS 1a (MESSAGES 231 UIDNEXT 44292)">> } ], ?MODULE } } }, { - <<"* STATUS blurdybloop (MESSAGES 231 \r\nUIDNEXT 44292)\r\nabcd OK Begin TLS negotiation now\r\n">>, + <<"* STATUS 2 (MESSAGES 231 {14+}\r\n">>, <<"abcd">>, - { fini, [<<"* STATUS blurdybloop (MESSAGES 231 ">>, <<"UIDNEXT 44292)">>] } + { more, { <<"* STATUS 2 (MESSAGES 231 {14+}">>, [], ?MODULE } } }, { - <<"* STATUS blurdybloop (MESSAGES 231 UIDNEXT 44292)\r\nabcd OK Begin TLS negotiation now\r\n">>, + <<"* STATUS 3 (MESSAGES 231 {h14+}\r\nUIDNEXT 44292)\r\nabcd OK Begin TLS negotiation now\r\n">>, <<"abcd">>, - { fini, [<<"* STATUS blurdybloop (MESSAGES 231 UIDNEXT 44292)">>] } + { fini, [ { marked, <<"* STATUS 3 (MESSAGES 231 {h14+}">> }, { marked, <<"UIDNEXT 44292)">> } ] } + }, + { + <<"* STATUS 4 (MESSAGES 231 \r\nUIDNEXT 44292)\r\nabcd OK Begin TLS negotiation now\r\n">>, + <<"abcd">>, + { fini, [ { marked, <<"* STATUS 4 (MESSAGES 231 ">> }, { marked, <<"UIDNEXT 44292)">> } ] } + }, + { + <<"* STATUS 5 (MESSAGES 231 UIDNEXT 44292)\r\nabcd OK Begin TLS negotiation now\r\n">>, + <<"abcd">>, + { fini, [ { marked, <<"* STATUS 5 (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). + lists:foldl(fun({ Binary, Tag, Result }, Acc) -> [?_assertEqual(Result, eimap_command:parse_response(multiline_response, Binary, Tag, ?MODULE))|Acc] end, [], Data).