Page MenuHomePhorge

D1144.1775794744.diff
No OneTemporary

Authored By
Unknown
Size
19 KB
Referenced Files
None
Subscribers
None

D1144.1775794744.diff

diff --git a/apps/kolab_guam/src/kolab_guam_session.hrl b/apps/kolab_guam/src/kolab_guam_session.hrl
new file mode 100644
--- /dev/null
+++ b/apps/kolab_guam/src/kolab_guam_session.hrl
@@ -0,0 +1,4 @@
+-record(state, { listen_socket, socket = undefined, super_pid, tls_config = [], client_implicit_tls = false, client_tls_active = false, server_config = [],
+ rules_active = [], rules_deciding = [], imap_session = undefined, inflator = undefined, deflator = undefined, buffered_client_data = <<>>,
+ current_command_split = undefined, command_split_reset_trigger = reset_for_next_client_command }).
+
diff --git a/apps/kolab_guam/src/kolab_guam_session.erl b/apps/kolab_guam/src/kolab_guam_session.erl
--- a/apps/kolab_guam/src/kolab_guam_session.erl
+++ b/apps/kolab_guam/src/kolab_guam_session.erl
@@ -25,10 +25,8 @@
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-%% state record definition
--record(state, { listen_socket, socket = undefined, super_pid, tls_config = [], client_implicit_tls = false, client_tls_active = false, server_config = [],
- rules_active = [], rules_deciding = [], imap_session = undefined, inflator, deflator, buffered_client_data = <<>>,
- current_command_split = undefined, command_split_reset_trigger = reset_for_next_client_command }).
+
+-include("kolab_guam_session.hrl").
%% public API
start_link(SupervisorPID, ListenSocket, ImapConfig, ImplicitTLS, TLSConfig, Rules) -> gen_server:start_link(?MODULE, [SupervisorPID, ListenSocket, ImapConfig, ImplicitTLS, TLSConfig, Rules], []).
@@ -99,7 +97,7 @@
%TODO: should we also support non-active rules doing imapy things here?
{ noreply, State#state{ rules_active = NewActiveRules } };
handle_info({ imap_server_response, Data }, #state{ socket = Socket, imap_session = ImapSession, client_tls_active = TLS, deflator = Deflator, rules_active = ActiveRules } = State) ->
- %lager:debug("FROM SERVER: ~s", [Data]),
+ lager:debug("FROM SERVER: ~p", [Data]),
{ ModifiedData, CurrentlyActiveRules } = apply_ruleset_serverside(ImapSession, Data, ActiveRules),
relay_response(Socket, postprocess_server_data(Deflator, ModifiedData), TLS),
NewSplitCommand = update_split_command_state(ModifiedData, State),
@@ -176,13 +174,44 @@
close_socket(true, _TLS, Socket) -> ssl:close(Socket);
close_socket(_ImplicitTLS, _TLS, Socket) -> gen_tcp:close(Socket).
-process_client_data(Socket, Data, #state{ rules_deciding = UndecidedRules, tls_config = TLSConfig, client_tls_active = TLS, rules_active = ActiveRules, socket = Socket, imap_session = ImapSession, inflator = Inflator, deflator = Deflator, server_config = ServerConfig, current_command_split = CurrentCommandSplit } = State) ->
- %%TODO: multipacket input from clients
+
+read_line(Data) ->
+ case binary:match(Data, <<"\r\n">>) of
+ nomatch ->
+ {Data, <<>>};
+ {SplitPos, SplitSize} ->
+ {binary:part(Data, 0, SplitPos + SplitSize), binary:part(Data, SplitPos + SplitSize, size(Data)-(SplitPos + SplitSize))}
+ end.
+
+
+process_preprocessed_client_data(Socket, PreprocessData, #state{ command_split_reset_trigger = CurrentCommandSplitResetTrigger, current_command_split = CurrentCommandSplit } = State) ->
+ NewSplitCommand = case CurrentCommandSplitResetTrigger of
+ reset_for_next_client_command -> undefined;
+ _ -> CurrentCommandSplit
+ end,
+
+ % TODO Maybe we should prepend buffered data here instead.
+ {Line, Remainder} = read_line(PreprocessData),
+ NewState = process_client_line(Line, Socket, State#state{ current_command_split = NewSplitCommand }),
+
+ case Remainder of
+ <<>> -> NewState;
+ _ -> process_preprocessed_client_data(Socket, Remainder, NewState)
+ end.
+
+
+% Client data is processed as it comes in, but split up by lines if more than one line comes in at a time.
+% The processing interanly buffers data as necessary if it encounters incomplete lines.
+process_client_data(Socket, Data, #state{ inflator = Inflator } = State) ->
% TODO: refactor so starttls and compress commands can be made into rules
PreprocessData = preprocess_client_data(Inflator, Data, State),
- %lager:info("FROM CLIENT: ~s", [PreprocessData]),
+ lager:debug("FROM CLIENT: ~p", [PreprocessData]),
+ { noreply, process_preprocessed_client_data(Socket, PreprocessData, State) }.
+
+
+process_client_line(Data, Socket, #state{ rules_deciding = UndecidedRules, tls_config = TLSConfig, client_tls_active = TLS, rules_active = ActiveRules, socket = Socket, imap_session = ImapSession, inflator = Inflator, deflator = Deflator, server_config = ServerConfig, current_command_split = CurrentCommandSplit } = State) ->
{ TLSActive, CurrentSocket, CurrentInflator, CurrentDeflator, CurrentUndecidedRules, CurrentActiveRules, DataToBuffer, SplitCommand, SplitResetTrigger } =
- case check_for_transmission_change_commands(TLS, TLSConfig, PreprocessData, Deflator, Socket) of
+ case check_for_transmission_change_commands(TLS, TLSConfig, Data, Deflator, Socket) of
{ socket_upgraded, SSLSocket } ->
%% if we have upgraded our socket, then do so to the backend if that hasn't happened auomatically
case proplists:get_value(implicit_tls, ServerConfig, false) of
@@ -194,46 +223,24 @@
eimap:compress(ImapSession), % TODO: make optional
{ TLS, Socket, NewInflator, NewDeflator, UndecidedRules, ActiveRules, <<>>, undefined, undefined };
nochange ->
- %%lager:debug("... now applying rules"),
- { ModifiedData, NewSplitCommand, NewSplitResetTrigger, NewUndecidedRules, NewActiveRules, PostAction } = apply_ruleset_clientside(ImapSession, Socket, PreprocessData, CurrentCommandSplit, UndecidedRules, ActiveRules),
- %%lager:info("The modified data is: ~s", [ModifiedData]),
- %lager:info("The post-processed data is: ~s", [PostProcessed]),
+ { ModifiedData, NewSplitCommand, NewSplitResetTrigger, NewUndecidedRules, NewActiveRules, PostAction } = apply_ruleset_clientside(ImapSession, Socket, Data, CurrentCommandSplit, UndecidedRules, ActiveRules),
BufferThisData =
case PostAction of
perform_passthrough ->
- %lager:info("sending (no buffer): ~s", [ModifiedData]),
eimap:passthrough_data(ImapSession, ModifiedData),
<<>>;
buffer_data ->
- % Originally Aaron uses Data here, but later on this buffer is assumed to be
- % already decoded, so we do have to use PreprocessData here, I think.
- case binary:matches(PreprocessData, <<"\r\n">>) of
- [] ->
- %lager:info("buffering: ~s", [PreprocessData]),
- PreprocessData;
- List ->
- {FoundPos, _} = lists:last(List),
- % I would like to have some binary:match for the last instead of the
- % first occurrence; but I'm really inexperienced in erlang so I don't
- % know how to solve this efficiently, so I'm using binary:matches with
- % using the last element only
- SplitPos = FoundPos + 2,
- eimap:passthrough_data(ImapSession, binary:part(PreprocessData, 0, SplitPos)),
- %lager:info("sending first part: ~s", [binary:part(PreprocessData, 0, SplitPos)] ),
- %lager:info("buffering second part: ~s", [binary:part(PreprocessData, SplitPos, size(PreprocessData)-SplitPos)]),
- binary:part(PreprocessData, SplitPos, size(PreprocessData)-SplitPos)
- end
+ Data
end,
{ TLS, Socket, Inflator, Deflator, NewUndecidedRules, NewActiveRules, BufferThisData, NewSplitCommand, NewSplitResetTrigger }
end,
set_socket_active(TLSActive, CurrentSocket),
- %buffered_client_data is already in DataToBuffer via preprocess_client_data
- { noreply, State#state{ rules_deciding = CurrentUndecidedRules, rules_active = CurrentActiveRules,
+ State#state{ rules_deciding = CurrentUndecidedRules, rules_active = CurrentActiveRules,
socket = CurrentSocket, client_tls_active = TLSActive,
inflator = CurrentInflator, deflator = CurrentDeflator,
- buffered_client_data = <<DataToBuffer/binary>>,
+ buffered_client_data = DataToBuffer,
current_command_split = SplitCommand,
- command_split_reset_trigger = SplitResetTrigger } }.
+ command_split_reset_trigger = SplitResetTrigger }.
preprocess_client_data(undefined, Data, #state{ buffered_client_data = Buffered }) ->
<<Buffered/binary, Data/binary>>;
@@ -281,15 +288,11 @@
{ PostAction, SplitCommand, SplitResetTrigger } =
case CurrentCommandSplit of
undefined ->
- %We first have to check whether the command is an empty line. In such a case split_command_into_components would return an empty command,
- %even though the command is complete.
- case ClientData of
- <<"\r\n">> -> { perform_passthrough, CurrentCommandSplit, reset_for_next_client_command };
- _ ->
- case eimap_utils:split_command_into_components(ClientData) of
- { _Tag, <<>>, <<>> } -> { buffer_data, undefined, reset_for_next_client_command };
- { _Tag, Command, _Data } = Split -> { perform_passthrough, Split, when_to_reset_split(Command) }
- end
+ case eimap_utils:split_command_into_components(ClientData) of
+ % In case of an empty line ("\r\n") the line is complete (doesn't need to be buffered), but there is also no command or tag.
+ { <<>>, <<>>, <<>> } -> { perform_passthrough, CurrentCommandSplit, reset_for_next_client_command };
+ { _Tag, <<>>, <<>> } -> { buffer_data, undefined, reset_for_next_client_command };
+ { _Tag, Command, _Data } = Split -> { perform_passthrough, Split, when_to_reset_split(Command) }
end;
_ -> { perform_passthrough, CurrentCommandSplit, reset_for_next_client_command }
end,
diff --git a/apps/kolab_guam/test/kolab_guam_session_SUITE.erl b/apps/kolab_guam/test/kolab_guam_session_SUITE.erl
new file mode 100644
--- /dev/null
+++ b/apps/kolab_guam/test/kolab_guam_session_SUITE.erl
@@ -0,0 +1,187 @@
+%% Copyright 2015 Kolab Systems AG (http://www.kolabsys.com)
+%%
+%% Christian Mollekopf (Kolab Systems) <mollekopf@kolabsystems.com>
+%%
+%% 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 <http://www.gnu.org/licenses/>.
+
+-module(kolab_guam_session_SUITE).
+
+% easier than exporting by name
+-compile(export_all).
+
+% required for common_test to work
+-include_lib("common_test/include/ct.hrl").
+-include("../src/kolab_guam_session.hrl").
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% common test callbacks %%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% Specify a list of all unit test functions
+all() -> [
+ kolab_guam_session_test
+ ].
+
+% required, but can just return Config. this is a suite level setup function.
+init_per_suite(Config) ->
+ Config.
+
+% required, but can just return Config. this is a suite level tear down function.
+end_per_suite(Config) ->
+ Config.
+
+% optional, can do function level setup for all functions,
+% or for individual functions by matching on TestCase.
+init_per_testcase(_TestCase, Config) ->
+ Config.
+
+% optional, can do function level tear down for all functions,
+% or for individual functions by matching on TestCase.
+end_per_testcase(_TestCase, Config) ->
+ Config.
+
+
+kolab_guam_session_test(_TestConfig) ->
+ %% setup boilerplate
+ ServerConfig = kolab_guam_sup:default_imap_server_config(),
+ { ok, ImapSession } = eimap:start_link(ServerConfig),
+ { ok, Socket } = gen_tcp:listen(9964, [ { reuseaddr, true }, {active, false}, inet6 ]),
+
+ lager:start(),
+ lager:set_loglevel(lager_console_backend, debug),
+
+ ActiveRules = [{ kolab_guam_rule_filter_groupware, kolab_guam_rule_filter_groupware:new({}) }],
+
+ State = #state{
+ socket = Socket,
+ server_config = ServerConfig,
+ imap_session = ImapSession
+ },
+
+
+ % Run without rules
+ {noreply, #state{
+ server_config = ServerConfig,
+ buffered_client_data = <<>>,
+ current_command_split = undefined,
+ command_split_reset_trigger = [] %FIXME ?
+ }} = kolab_guam_session:handle_info({tcp, Socket, <<"y1 ID (\"name\" \"Test\")\r\ny2 ENABLE QRESYNC\r\ny3 LIST \"\" \"%\" RETURN (SUBSCRIBED))\r\n">>}, State#state{ rules_deciding = [] }),
+
+
+ % Activate filtering
+ {noreply, #state{
+ server_config = ServerConfig,
+ buffered_client_data = <<>>,
+ current_command_split = {<<"y1">>,<<"ID">>,<<"(\"name\" \"Test\")">>},
+ command_split_reset_trigger = reset_for_next_client_command,
+ rules_deciding = [],
+ rules_active = [{kolab_guam_rule_filter_groupware, _}]
+ }} = kolab_guam_session:handle_info({tcp, Socket, <<"y1 ID (\"name\" \"Test\")\r\n">>}, State#state{ rules_deciding = ActiveRules }),
+
+
+ % Don't activate filtering
+ {noreply, #state{
+ server_config = ServerConfig,
+ buffered_client_data = <<>>,
+ current_command_split = {<<"y1">>,<<"ID">>,<<"(\"name\" \"Test/KOLAB\")">>},
+ command_split_reset_trigger = reset_for_next_client_command,
+ rules_deciding = [],
+ rules_active = []
+ }} = kolab_guam_session:handle_info({tcp, Socket, <<"y1 ID (\"name\" \"Test/KOLAB\")\r\n">>}, State#state{ rules_deciding = ActiveRules }),
+
+
+ % Lone tag in a packet. Can/Could happen with iPhone apparently. (See commit 89f9dc93757c68032ed17f42838858bdfaefa408)
+ {noreply, #state{ buffered_client_data = <<"y1">>, rules_active = [] } = IntermediateState1} = kolab_guam_session:handle_info({tcp, Socket, <<"y1">>}, State#state{ rules_deciding = ActiveRules }),
+ {noreply, #state{
+ server_config = ServerConfig,
+ buffered_client_data = <<>>,
+ current_command_split = {<<"y2">>,<<"ENABLE">>,<<"QRESYNC">>},
+ command_split_reset_trigger = reset_for_next_client_command,
+ rules_deciding = [],
+ rules_active = [{kolab_guam_rule_filter_groupware, _}]
+ }} = kolab_guam_session:handle_info({tcp, Socket, <<" ID (\"name\" \"Test\")\r\ny2 ENABLE QRESYNC\r\n">>}, IntermediateState1),
+
+
+ % Lone tag in a packet, but with a \r\n before. See guam-0.9.2-stalling-client-buffer-and-split-command-handling.patch (This triggers the odd List buffering case)
+ {noreply, #state{ buffered_client_data = <<"y1 ">>, rules_active = [] } = IntermediateState2} = kolab_guam_session:handle_info({tcp, Socket, <<"\r\ny1 ">>}, State#state{ rules_deciding = ActiveRules }),
+ {noreply, #state{
+ server_config = ServerConfig,
+ buffered_client_data = <<>>,
+ current_command_split = {<<"y2">>,<<"ENABLE">>,<<"QRESYNC">>},
+ command_split_reset_trigger = reset_for_next_client_command,
+ rules_deciding = [],
+ rules_active = [{kolab_guam_rule_filter_groupware, _}]
+ }} = kolab_guam_session:handle_info({tcp, Socket, <<"ID (\"name\" \"Test\")\r\ny2 ENABLE QRESYNC\r\n">>}, IntermediateState2),
+
+
+ % Empty line (should be considered a complete command and should not be buffered)
+ {noreply, #state{ buffered_client_data = <<>>, rules_active = [] } = IntermediateState3} = kolab_guam_session:handle_info({tcp, Socket, <<"\r\n">>}, State#state{ rules_deciding = ActiveRules }),
+ {noreply, #state{
+ server_config = ServerConfig,
+ buffered_client_data = <<>>,
+ current_command_split = {<<"y1">>,<<"ID">>,<<"(\"name\" \"Test\")">>},
+ command_split_reset_trigger = reset_for_next_client_command,
+ rules_deciding = [],
+ rules_active = [{kolab_guam_rule_filter_groupware, _}]
+ }} = kolab_guam_session:handle_info({tcp, Socket, <<"y1 ID (\"name\" \"Test\")\r\n">>}, IntermediateState3),
+
+
+ % Activate with multi-line packet
+ {noreply, #state{
+ server_config = ServerConfig,
+ buffered_client_data = <<>>,
+ current_command_split = {<<"y3">>,<<"LIST">>,<<"\"\" \"%\" RETURN (SUBSCRIBED))">>},
+ command_split_reset_trigger = reset_for_next_client_command,
+ rules_deciding = [],
+ % Make sure that we have processed y3 in here (and don't stop processing at y1)
+ rules_active = [{kolab_guam_rule_filter_groupware, {state,undefined,<<"y3">>,true,<<>>,[<<"LIST">>,<<"list">>,<<"XLIST">>,<<"xlist">>,<<"LSUB">>,<<"lsub">>]}}]
+ % rules_active = [{kolab_guam_rule_filter_groupware, _}]
+ }} = kolab_guam_session:handle_info({tcp, Socket, <<"y1 ID (\"name\" \"Test\")\r\ny2 ENABLE QRESYNC\r\ny3 LIST \"\" \"%\" RETURN (SUBSCRIBED))\r\n">>}, State#state{ rules_deciding = ActiveRules }),
+
+
+ % Test various packet splits
+ PacketsSplits = [
+ [<<"y1 ID (\"name\" \"Test\")\r\n">>, <<"y2 ENABLE QRESYNC\r\n">>, <<"y3 LIST \"\" \"%\" RETURN (SUBSCRIBED))\r\n">>],
+ [<<"y1 ID (\"name\" \"Test\")\r\ny2 ENABLE QRESYNC\r\ny3 LIST \"\" \"%\" RETURN (SUBSCRIBED))\r\n">>],
+ [<<"y1 ID (\"name\" \"Test\")">>, <<"\r\ny2">>, <<" ENABLE QRESYNC\r\ny3 LIST \"\" \"%\" RETURN (SUBSCRIBED))\r\n">>],
+ [<<"y1 ID (\"name\" \"Test\")">>, <<"\r\n">>, <<"y2 ENABLE QRESYNC\r\ny3 LIST \"\" \"%\" RETURN (SUBSCRIBED))\r\n">>],
+ [<<"y1">>, <<" ">>, <<"ID">>, <<" (\"name\" \"Test\")\r\n">>, <<"y2 ENABLE QRESYNC\r\ny3 LIST \"\" \"%\" RETURN (SUBSCRIBED))\r\n">>]
+ ],
+
+ lists:foldl(fun(Packets, _) ->
+ try_packets(Packets, Socket, ServerConfig, State#state{ rules_deciding = ActiveRules })
+ end,
+ undefined,
+ PacketsSplits),
+
+
+ lager:info("Done").
+
+
+try_packets(Packets, Socket, ServerConfig, State) ->
+ #state{
+ server_config = ServerConfig,
+ buffered_client_data = <<>>,
+ current_command_split = {<<"y3">>,<<"LIST">>,<<"\"\" \"%\" RETURN (SUBSCRIBED))">>},
+ command_split_reset_trigger = reset_for_next_client_command,
+ rules_deciding = [],
+ % Make sure that we have processed y3 in here (and don't stop processing at y1)
+ rules_active = [{kolab_guam_rule_filter_groupware, {state,undefined,<<"y3">>,true,<<>>,[<<"LIST">>,<<"list">>,<<"XLIST">>,<<"xlist">>,<<"LSUB">>,<<"lsub">>]}}]
+ } = lists:foldl(fun(Packet, _State) ->
+ {_, NewState} = kolab_guam_session:handle_info({tcp, Socket, Packet}, _State),
+ NewState
+ end,
+ State,
+ Packets).

File Metadata

Mime Type
text/plain
Expires
Fri, Apr 10, 4:19 AM (1 d, 6 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18854552
Default Alt Text
D1144.1775794744.diff (19 KB)

Event Timeline