Skip to content

Commit

Permalink
Merge branch 'rb/ssl-fix-http-packet-decoding' into dev
Browse files Browse the repository at this point in the history
* 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
Erlang/OTP committed Apr 9, 2010
2 parents 91bec64 + b1d9fec commit 12da379
Show file tree
Hide file tree
Showing 2 changed files with 216 additions and 10 deletions.
52 changes: 43 additions & 9 deletions lib/ssl/src/ssl_connection.erl
Expand Up @@ -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)};
Expand Down Expand Up @@ -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]);
Expand Down
174 changes: 173 additions & 1 deletion lib/ssl/test/ssl_packet_SUITE.erl
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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]).

0 comments on commit 12da379

Please sign in to comment.