Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'rb/ssl-fix-http-packet-decoding' into dev

* rb/ssl-fix-http-packet-decoding:
  Fix ssl to decode http packets in the same way as gen_tcp

OTP-8545 rb/ssl-fix-http-packet-decoding
  • Loading branch information...
commit 12da3796463c7d02999985ae15e98dcaa4d2bdb4 2 parents 91bec64 + b1d9fec
Erlang/OTP authored
Showing with 216 additions and 10 deletions.
  1. +43 −9 lib/ssl/src/ssl_connection.erl
  2. +173 −1 lib/ssl/test/ssl_packet_SUITE.erl
View
52 lib/ssl/src/ssl_connection.erl
@@ -1732,19 +1732,49 @@ get_data(#socket_options{active=Active, packet=Raw}, BytesToRead, Buffer)
end;
get_data(#socket_options{packet=Type, packet_size=Size}, _, Buffer) ->
PacketOpts = [{packet_size, Size}],
- case erlang:decode_packet(Type, Buffer, PacketOpts) of
+ case decode_packet(Type, Buffer, PacketOpts) of
{more, _} ->
{ok, <<>>, Buffer};
Decoded ->
Decoded
end.
-deliver_app_data(SO = #socket_options{active=once}, Data, Pid, From) ->
- send_or_reply(once, Pid, From, format_reply(SO, Data)),
- SO#socket_options{active=false};
-deliver_app_data(SO= #socket_options{active=Active}, Data, Pid, From) ->
- send_or_reply(Active, Pid, From, format_reply(SO, Data)),
- SO.
+decode_packet({http, headers}, Buffer, PacketOpts) ->
+ decode_packet(httph, Buffer, PacketOpts);
+decode_packet({http_bin, headers}, Buffer, PacketOpts) ->
+ decode_packet(httph_bin, Buffer, PacketOpts);
+decode_packet(Type, Buffer, PacketOpts) ->
+ erlang:decode_packet(Type, Buffer, PacketOpts).
+
+%% Just like with gen_tcp sockets, an ssl socket that has been configured with
+%% {packet, http} (or {packet, http_bin}) will automatically switch to expect
+%% HTTP headers after it sees a HTTP Request or HTTP Response line. We
+%% represent the current state as follows:
+%% #socket_options.packet =:= http: Expect a HTTP Request/Response line
+%% #socket_options.packet =:= {http, headers}: Expect HTTP Headers
+%% Note that if the user has explicitly configured the socket to expect
+%% HTTP headers using the {packet, httph} option, we don't do any automatic
+%% switching of states.
+deliver_app_data(SOpts = #socket_options{active=Active, packet=Type},
+ Data, Pid, From) ->
+ send_or_reply(Active, Pid, From, format_reply(SOpts, Data)),
+ SO = case Data of
+ {P, _, _, _} when ((P =:= http_request) or (P =:= http_response)),
+ ((Type =:= http) or (Type =:= http_bin)) ->
+ SOpts#socket_options{packet={Type, headers}};
+ http_eoh when tuple_size(Type) =:= 2 ->
+ % End of headers - expect another Request/Response line
+ {Type1, headers} = Type,
+ SOpts#socket_options{packet=Type1};
+ _ ->
+ SOpts
+ end,
+ case Active of
+ once ->
+ SO#socket_options{active=false};
+ _ ->
+ SO
+ end.
format_reply(#socket_options{active=false, mode=Mode, header=Header}, Data) ->
{ok, format_reply(Mode, Header, Data)};
@@ -1939,8 +1969,12 @@ get_socket_opts(Socket, [mode | Tags], SockOpts, Acc) ->
get_socket_opts(Socket, Tags, SockOpts,
[{mode, SockOpts#socket_options.mode} | Acc]);
get_socket_opts(Socket, [packet | Tags], SockOpts, Acc) ->
- get_socket_opts(Socket, Tags, SockOpts,
- [{packet, SockOpts#socket_options.packet} | Acc]);
+ case SockOpts#socket_options.packet of
+ {Type, headers} ->
+ get_socket_opts(Socket, Tags, SockOpts, [{packet, Type} | Acc]);
+ Type ->
+ get_socket_opts(Socket, Tags, SockOpts, [{packet, Type} | Acc])
+ end;
get_socket_opts(Socket, [header | Tags], SockOpts, Acc) ->
get_socket_opts(Socket, Tags, SockOpts,
[{header, SockOpts#socket_options.header} | Acc]);
View
174 lib/ssl/test/ssl_packet_SUITE.erl
@@ -144,7 +144,9 @@ all(suite) ->
packet_wait_passive, packet_wait_active,
packet_baddata_passive, packet_baddata_active,
packet_size_passive, packet_size_active,
- packet_erl_decode
+ packet_erl_decode,
+ packet_http_decode,
+ packet_http_bin_decode_multi
].
%% Test cases starts here.
@@ -1466,6 +1468,173 @@ client_packet_decode(Socket, CDR) ->
end,
ok.
+%%--------------------------------------------------------------------
+packet_http_decode(doc) ->
+ ["Test setting the packet option {packet, http}"];
+packet_http_decode(suite) ->
+ [];
+
+packet_http_decode(Config) when is_list(Config) ->
+ ClientOpts = ?config(client_opts, Config),
+ ServerOpts = ?config(server_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Request = "GET / HTTP/1.1\r\n"
+ "host: www.example.com\r\n"
+ "user-agent: HttpTester\r\n"
+ "\r\n",
+ Response = "HTTP/1.1 200 OK\r\n"
+ "\r\n"
+ "Hello!",
+
+ Server = ssl_test_lib:start_server([{node, ClientNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, server_http_decode, [Response]}},
+ {options, [{active, true}, binary, {packet, http} |
+ ServerOpts]}]),
+
+ Port = ssl_test_lib:inet_port(Server),
+ Client = ssl_test_lib:start_client([{node, ServerNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, client_http_decode, [Request]}},
+ {options, [{active, true}, binary, {packet, http} |
+ ClientOpts]}]),
+
+ ssl_test_lib:check_result(Server, ok, Client, ok),
+
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client).
+
+
+server_http_decode(Socket, HttpResponse) ->
+ assert_packet_opt(Socket, http),
+ receive
+ {ssl, Socket, {http_request, 'GET', _, {1,1}}} -> ok;
+ Other1 -> exit({?LINE, Other1})
+ end,
+ assert_packet_opt(Socket, http),
+ receive
+ {ssl, Socket, {http_header, _, 'Host', _, "www.example.com"}} -> ok;
+ Other2 -> exit({?LINE, Other2})
+ end,
+ assert_packet_opt(Socket, http),
+ receive
+ {ssl, Socket, {http_header, _, 'User-Agent', _, "HttpTester"}} -> ok;
+ Other3 -> exit({?LINE, Other3})
+ end,
+ assert_packet_opt(Socket, http),
+ receive
+ {ssl, Socket, http_eoh} -> ok;
+ Other4 -> exit({?LINE, Other4})
+ end,
+ assert_packet_opt(Socket, http),
+ ok = ssl:send(Socket, HttpResponse),
+ ok.
+
+client_http_decode(Socket, HttpRequest) ->
+ ok = ssl:send(Socket, HttpRequest),
+ receive
+ {ssl, Socket, {http_response, {1,1}, 200, "OK"}} -> ok;
+ Other1 -> exit({?LINE, Other1})
+ end,
+ receive
+ {ssl, Socket, http_eoh} -> ok;
+ Other2 -> exit({?LINE, Other2})
+ end,
+ ok = ssl:setopts(Socket, [{packet, 0}]),
+ receive
+ {ssl, Socket, <<"Hello!">>} -> ok;
+ Other3 -> exit({?LINE, Other3})
+ end,
+ ok.
+
+%%--------------------------------------------------------------------
+packet_http_bin_decode_multi(doc) ->
+ ["Test setting the packet option {packet, http_bin} with multiple requests"];
+packet_http_bin_decode_multi(suite) ->
+ [];
+
+packet_http_bin_decode_multi(Config) when is_list(Config) ->
+ ClientOpts = ?config(client_opts, Config),
+ ServerOpts = ?config(server_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Request = <<"GET / HTTP/1.1\r\n"
+ "host: www.example.com\r\n"
+ "user-agent: HttpTester\r\n"
+ "\r\n">>,
+ Response = <<"HTTP/1.1 200 OK\r\n"
+ "\r\n"
+ "Hello!">>,
+ NumMsgs = 3,
+
+ Server = ssl_test_lib:start_server([{node, ClientNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, server_http_bin_decode, [Response, NumMsgs]}},
+ {options, [{active, true}, binary, {packet, http_bin} |
+ ServerOpts]}]),
+
+ Port = ssl_test_lib:inet_port(Server),
+ Client = ssl_test_lib:start_client([{node, ServerNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, client_http_bin_decode, [Request, NumMsgs]}},
+ {options, [{active, true}, binary, {packet, http_bin} |
+ ClientOpts]}]),
+
+ ssl_test_lib:check_result(Server, ok, Client, ok),
+
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client).
+
+
+server_http_bin_decode(Socket, HttpResponse, Count) when Count > 0 ->
+ assert_packet_opt(Socket, http_bin),
+ receive
+ {ssl, Socket, {http_request, 'GET', _, {1,1}}} -> ok;
+ Other1 -> exit({?LINE, Other1})
+ end,
+ assert_packet_opt(Socket, http_bin),
+ receive
+ {ssl, Socket, {http_header, _, 'Host', _, <<"www.example.com">>}} -> ok;
+ Other2 -> exit({?LINE, Other2})
+ end,
+ assert_packet_opt(Socket, http_bin),
+ receive
+ {ssl, Socket, {http_header, _, 'User-Agent', _, <<"HttpTester">>}} -> ok;
+ Other3 -> exit({?LINE, Other3})
+ end,
+ assert_packet_opt(Socket, http_bin),
+ receive
+ {ssl, Socket, http_eoh} -> ok;
+ Other4 -> exit({?LINE, Other4})
+ end,
+ assert_packet_opt(Socket, http_bin),
+ ok = ssl:send(Socket, HttpResponse),
+ server_http_bin_decode(Socket, HttpResponse, Count - 1);
+server_http_bin_decode(_, _, _) ->
+ ok.
+
+client_http_bin_decode(Socket, HttpRequest, Count) when Count > 0 ->
+ ok = ssl:send(Socket, HttpRequest),
+ receive
+ {ssl, Socket, {http_response, {1,1}, 200, <<"OK">>}} -> ok;
+ Other1 -> exit({?LINE, Other1})
+ end,
+ receive
+ {ssl, Socket, http_eoh} -> ok;
+ Other2 -> exit({?LINE, Other2})
+ end,
+ ok = ssl:setopts(Socket, [{packet, 0}]),
+ receive
+ {ssl, Socket, <<"Hello!">>} -> ok;
+ Other3 -> exit({?LINE, Other3})
+ end,
+ ok = ssl:setopts(Socket, [{packet, http_bin}]),
+ client_http_bin_decode(Socket, HttpRequest, Count - 1);
+client_http_bin_decode(_, _, _) ->
+ ok.
%%--------------------------------------------------------------------
%% Internal functions
@@ -1572,3 +1741,6 @@ active_packet(Socket, Data, N) ->
Other ->
{other, Other, ssl:session_info(Socket),N}
end.
+
+assert_packet_opt(Socket, Type) ->
+ {ok, [{packet, Type}]} = ssl:getopts(Socket, [packet]).
Please sign in to comment.
Something went wrong with that request. Please try again.