Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F118392819
D1144.1775794744.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
19 KB
Referenced Files
None
Subscribers
None
D1144.1775794744.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D1144: Review branch dev/mollekopf2 Avoid unnecessary explicit cast
Attached
Detach File
Event Timeline