diff options
Diffstat (limited to 'lib/inets')
-rw-r--r-- | lib/inets/doc/src/ftp.xml | 8 | ||||
-rw-r--r-- | lib/inets/doc/src/notes.xml | 80 | ||||
-rw-r--r-- | lib/inets/src/ftp/ftp.erl | 127 | ||||
-rw-r--r-- | lib/inets/src/http_client/httpc_handler.erl | 18 | ||||
-rw-r--r-- | lib/inets/src/http_server/httpd_example.erl | 8 | ||||
-rw-r--r-- | lib/inets/src/http_server/httpd_file.erl | 3 | ||||
-rw-r--r-- | lib/inets/src/http_server/httpd_response.erl | 8 | ||||
-rw-r--r-- | lib/inets/src/http_server/mod_esi.erl | 15 | ||||
-rw-r--r-- | lib/inets/test/httpc_SUITE.erl | 56 | ||||
-rw-r--r-- | lib/inets/test/httpd_SUITE.erl | 105 | ||||
-rw-r--r-- | lib/inets/vsn.mk | 2 |
11 files changed, 358 insertions, 72 deletions
diff --git a/lib/inets/doc/src/ftp.xml b/lib/inets/doc/src/ftp.xml index 42bece4d38..feea7b978a 100644 --- a/lib/inets/doc/src/ftp.xml +++ b/lib/inets/doc/src/ftp.xml @@ -548,11 +548,12 @@ <v>start_option() = {verbose, verbose()} | {debug, debug()}</v> <v>verbose() = boolean() (default is false)</v> <v>debug() = disable | debug | trace (default is disable)</v> - <v>open_option() = {ipfamily, ipfamily()} | {port, port()} | {mode, mode()} | {tls, tls_options()} | {timeout, timeout()} | {dtimeout, dtimeout()} | {progress, progress()}</v> + <v>open_option() = {ipfamily, ipfamily()} | {port, port()} | {mode, mode()} | {tls, tls_options()} | {timeout, timeout()} | {dtimeout, dtimeout()} | {progress, progress() | {sock_ctrl, sock_opts()} | {sock_data_act, sock_opts()} | {sock_data_pass, sock_opts()} }</v> <v>ipfamily() = inet | inet6 | inet6fb4 (default is inet)</v> <v>port() = integer() > 0 (default is 21)</v> <v>mode() = active | passive (default is passive)</v> <v>tls_options() = [<seealso marker="ssl:ssl#type-ssloption">ssl:ssloption()</seealso>]</v> + <v>sock_opts() = [<seealso marker="kernel:gen_tcp#type-option">gen_tcp:option()</seealso> except for ipv6_v6only, active, packet, mode, packet_size and header</v> <v>timeout() = integer() > 0 (default is 60000 milliseconds)</v> <v>dtimeout() = integer() > 0 | infinity (default is infinity)</v> <v>pogress() = ignore | {module(), function(), initial_data()} (default is ignore)</v> @@ -575,6 +576,11 @@ is used for securing both the control connection and the data sessions. </p> + <p>The options <c>sock_ctrl</c>, <c>sock_data_act</c> and <c>sock_data_pass</c> passes options down to + the underlying transport layer (tcp). The default value for <c>sock_ctrl</c> is <c>[]</c>. Both + <c>sock_data_act</c> and <c>sock_data_pass</c> uses the value of <c>sock_ctrl</c> as default value. + </p> + <p>A session opened in this way is closed using function <seealso marker="#close">close</seealso>.</p> diff --git a/lib/inets/doc/src/notes.xml b/lib/inets/doc/src/notes.xml index a6af1e834e..8374c0abef 100644 --- a/lib/inets/doc/src/notes.xml +++ b/lib/inets/doc/src/notes.xml @@ -33,7 +33,85 @@ <file>notes.xml</file> </header> - <section><title>Inets 6.5.2</title> + <section><title>Inets 6.5.2.4</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Do not use chunked-encoding with 1xx, 204 and 304 + responses when using mod_esi. Old behavior was not + compliant with HTTP/1.1 RFC and could cause clients to + hang when they received 1xx, 204 or 304 responses that + included an empty chunked-encoded body.</p> + <p> + Own Id: OTP-15241</p> + </item> + <item> + <p> + Add robust handling of chunked-encoded HTTP responses + with an empty body (1xx, 204, 304). Old behavior could + cause the client to hang when connecting to a faulty + server implementation.</p> + <p> + Own Id: OTP-15242</p> + </item> + </list> + </section> + +</section> + +<section><title>Inets 6.5.2.3</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Change status code for no mod found to handle request to + 501</p> + <p> + Own Id: OTP-15215</p> + </item> + </list> + </section> + +</section> + +<section><title>Inets 6.5.2.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Enhance error handling, that is mod_get will return 403 + if a path is a directory and not a file.</p> + <p> + Own Id: OTP-15192</p> + </item> + </list> + </section> + +</section> + +<section><title>Inets 6.5.2.1</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Options added for setting low-level properties on the + underlying TCP connections. The options are: + <c>sock_ctrl</c>, <c>sock_data_act</c> and + <c>sock_data_pass</c>. See the manual for details.</p> + <p> + Own Id: OTP-15120 Aux Id: ERIERL-192 </p> + </item> + </list> + </section> + +</section> + +<section><title>Inets 6.5.2</title> <section><title>Fixed Bugs and Malfunctions</title> <list> diff --git a/lib/inets/src/ftp/ftp.erl b/lib/inets/src/ftp/ftp.erl index e0430654eb..ffcccdefea 100644 --- a/lib/inets/src/ftp/ftp.erl +++ b/lib/inets/src/ftp/ftp.erl @@ -94,6 +94,9 @@ %% data needed further on. caller = undefined, % term() ipfamily, % inet | inet6 | inet6fb4 + sockopts_ctrl = [], + sockopts_data_passive = [], + sockopts_data_active = [], progress = ignore, % ignore | pid() dtimeout = ?DATA_ACCEPT_TIMEOUT, % non_neg_integer() | infinity tls_upgrading_data_connection = false, @@ -138,9 +141,10 @@ open({option_list, Options}) when is_list(Options) -> try {ok, StartOptions} = start_options(Options), {ok, OpenOptions} = open_options(Options), + {ok, SockOpts} = socket_options(Options), case ftp_sup:start_child([[[{client, self()} | StartOptions], []]]) of {ok, Pid} -> - call(Pid, {open, ip_comm, OpenOptions}, plain); + call(Pid, {open, ip_comm, OpenOptions, SockOpts}, plain); Error1 -> Error1 end @@ -168,9 +172,11 @@ open(Host, Opts) when is_list(Opts) -> ?fcrt("open", [{start_options, StartOptions}]), {ok, OpenOptions} = open_options([{host, Host}|Opts]), ?fcrt("open", [{open_options, OpenOptions}]), + {ok, SocketOptions} = socket_options(Opts), + ?fcrt("open", [{socket_options, SocketOptions}]), case start_link(StartOptions, []) of {ok, Pid} -> - do_open(Pid, OpenOptions, tls_options(Opts)); + do_open(Pid, OpenOptions, SocketOptions, tls_options(Opts)); Error1 -> ?fcrt("open - error", [{error1, Error1}]), Error1 @@ -181,8 +187,8 @@ open(Host, Opts) when is_list(Opts) -> Error2 end. -do_open(Pid, OpenOptions, TLSOpts) -> - case call(Pid, {open, ip_comm, OpenOptions}, plain) of +do_open(Pid, OpenOptions, SocketOptions, TLSOpts) -> + case call(Pid, {open, ip_comm, OpenOptions, SocketOptions}, plain) of {ok, Pid} -> maybe_tls_upgrade(Pid, TLSOpts); Error -> @@ -879,9 +885,10 @@ start_standalone(Options) -> try {ok, StartOptions} = start_options(Options), {ok, OpenOptions} = open_options(Options), + {ok, SocketOptions} = socket_options(Options), case start_link(StartOptions, []) of {ok, Pid} -> - call(Pid, {open, ip_comm, OpenOptions}, plain); + call(Pid, {open, ip_comm, OpenOptions, SocketOptions}, plain); Error1 -> Error1 end @@ -893,10 +900,11 @@ start_standalone(Options) -> start_service(Options) -> try {ok, StartOptions} = start_options(Options), - {ok, OpenOptions} = open_options(Options), + {ok, OpenOptions} = open_options(Options), + {ok, SocketOptions} = socket_options(Options), case ftp_sup:start_child([[[{client, self()} | StartOptions], []]]) of {ok, Pid} -> - call(Pid, {open, ip_comm, OpenOptions}, plain); + call(Pid, {open, ip_comm, OpenOptions, SocketOptions}, plain); Error1 -> Error1 end @@ -1041,6 +1049,34 @@ open_options(Options) -> {ftp_extension, ValidateFtpExtension, false, ?FTP_EXT_DEFAULT}], validate_options(Options, ValidOptions, []). + + +socket_options(Options) -> + CtrlOpts = proplists:get_value(sock_ctrl, Options, []), + DataActOpts = proplists:get_value(sock_data_act, Options, CtrlOpts), + DataPassOpts = proplists:get_value(sock_data_pass, Options, CtrlOpts), + case [O || O <- lists:usort(CtrlOpts++DataPassOpts++DataActOpts), + not valid_socket_option(O)] of + [] -> + {ok, {CtrlOpts, DataPassOpts, DataActOpts}}; + Invalid -> + throw({error,{sock_opts,Invalid}}) + end. + + +valid_socket_option(inet ) -> false; +valid_socket_option(inet6 ) -> false; +valid_socket_option({ipv6_v6only, _}) -> false; +valid_socket_option({active,_} ) -> false; +valid_socket_option({packet,_} ) -> false; +valid_socket_option({mode,_} ) -> false; +valid_socket_option(binary ) -> false; +valid_socket_option(list ) -> false; +valid_socket_option({header,_} ) -> false; +valid_socket_option({packet_size,_} ) -> false; +valid_socket_option(_) -> true. + + tls_options(Options) -> %% Options will be validated by ssl application proplists:get_value(tls, Options, undefined). @@ -1182,7 +1218,7 @@ handle_call({_,latest_ctrl_response}, _, #state{latest_ctrl_response=Resp} = Sta handle_call({Pid, _}, _, #state{owner = Owner} = State) when Owner =/= Pid -> {reply, {error, not_connection_owner}, State}; -handle_call({_, {open, ip_comm, Opts}}, From, State) -> +handle_call({_, {open, ip_comm, Opts, {CtrlOpts, DataPassOpts, DataActOpts}}}, From, State) -> ?fcrd("handle_call(open)", [{opts, Opts}]), case key_search(host, Opts, undefined) of undefined -> @@ -1200,6 +1236,9 @@ handle_call({_, {open, ip_comm, Opts}}, From, State) -> mode = Mode, progress = progress(Progress), ipfamily = IpFamily, + sockopts_ctrl = CtrlOpts, + sockopts_data_passive = DataPassOpts, + sockopts_data_active = DataActOpts, dtimeout = DTimeout, ftp_extension = FtpExt}, @@ -1218,28 +1257,6 @@ handle_call({_, {open, ip_comm, Opts}}, From, State) -> end end; -handle_call({_, {open, ip_comm, Host, Opts}}, From, State) -> - Mode = key_search(mode, Opts, ?DEFAULT_MODE), - Port = key_search(port, Opts, ?FTP_PORT), - Timeout = key_search(timeout, Opts, ?CONNECTION_TIMEOUT), - DTimeout = key_search(dtimeout, Opts, ?DATA_ACCEPT_TIMEOUT), - Progress = key_search(progress, Opts, ignore), - FtpExt = key_search(ftp_extension, Opts, ?FTP_EXT_DEFAULT), - - State2 = State#state{client = From, - mode = Mode, - progress = progress(Progress), - dtimeout = DTimeout, - ftp_extension = FtpExt}, - - case setup_ctrl_connection(Host, Port, Timeout, State2) of - {ok, State3, WaitTimeout} -> - {noreply, State3, WaitTimeout}; - {error, _Reason} -> - gen_server:reply(From, {error, ehost}), - {stop, normal, State2#state{client = undefined}} - end; - handle_call({_, {open, tls_upgrade, TLSOptions}}, From, State) -> send_ctrl_message(State, mk_cmd("AUTH TLS", [])), activate_ctrl_connection(State), @@ -1822,11 +1839,12 @@ handle_ctrl_result({pos_compl, Lines}, client = From, caller = {setup_data_connection, Caller}, csock = CSock, + sockopts_data_passive = SockOpts, timeout = Timeout} = State) -> [_, PortStr | _] = lists:reverse(string:tokens(Lines, "|")), {ok, {IP, _}} = peername(CSock), - case connect(IP, list_to_integer(PortStr), Timeout, State) of + case connect(IP, list_to_integer(PortStr), SockOpts, Timeout, State) of {ok, _, Socket} -> handle_caller(State#state{caller = Caller, dsock = {tcp, Socket}}); {error, _Reason} = Error -> @@ -1839,7 +1857,8 @@ handle_ctrl_result({pos_compl, Lines}, ipfamily = inet, client = From, caller = {setup_data_connection, Caller}, - timeout = Timeout, + timeout = Timeout, + sockopts_data_passive = SockOpts, ftp_extension = false} = State) -> {_, [?LEFT_PAREN | Rest]} = @@ -1853,7 +1872,7 @@ handle_ctrl_result({pos_compl, Lines}, Port = (P1 * 256) + P2, ?DBG('<--data tcp connect to ~p:~p, Caller=~p~n',[IP,Port,Caller]), - case connect(IP, Port, Timeout, State) of + case connect(IP, Port, SockOpts, Timeout, State) of {ok, _, Socket} -> handle_caller(State#state{caller = Caller, dsock = {tcp,Socket}}); {error, _Reason} = Error -> @@ -1868,13 +1887,14 @@ handle_ctrl_result({pos_compl, Lines}, caller = {setup_data_connection, Caller}, csock = CSock, timeout = Timeout, + sockopts_data_passive = SockOpts, ftp_extension = true} = State) -> [_, PortStr | _] = lists:reverse(string:tokens(Lines, "|")), {ok, {IP, _}} = peername(CSock), ?DBG('<--data tcp connect to ~p:~p, Caller=~p~n',[IP,PortStr,Caller]), - case connect(IP, list_to_integer(PortStr), Timeout, State) of + case connect(IP, list_to_integer(PortStr), SockOpts, Timeout, State) of {ok, _, Socket} -> handle_caller(State#state{caller = Caller, dsock = {tcp, Socket}}); {error, _Reason} = Error -> @@ -2238,9 +2258,9 @@ handle_caller(#state{caller = {transfer_data, {Cmd, Bin, RemoteFile}}} = %% Connect to FTP server at Host (default is TCP port 21) %% in order to establish a control connection. -setup_ctrl_connection(Host, Port, Timeout, State) -> +setup_ctrl_connection(Host, Port, Timeout, #state{sockopts_ctrl = SockOpts} = State) -> MsTime = erlang:monotonic_time(), - case connect(Host, Port, Timeout, State) of + case connect(Host, Port, SockOpts, Timeout, State) of {ok, IpFam, CSock} -> NewState = State#state{csock = {tcp, CSock}, ipfamily = IpFam}, activate_ctrl_connection(NewState), @@ -2258,12 +2278,15 @@ setup_ctrl_connection(Host, Port, Timeout, State) -> setup_data_connection(#state{mode = active, caller = Caller, csock = CSock, + sockopts_data_active = SockOpts, ftp_extension = FtpExt} = State) -> case (catch sockname(CSock)) of - {ok, {{_, _, _, _, _, _, _, _} = IP, _}} -> + {ok, {{_, _, _, _, _, _, _, _} = IP0, _}} -> + IP = proplists:get_value(ip, SockOpts, IP0), {ok, LSock} = gen_tcp:listen(0, [{ip, IP}, {active, false}, - inet6, binary, {packet, 0}]), + inet6, binary, {packet, 0} | + lists:keydelete(ip,1,SockOpts)]), {ok, {_, Port}} = sockname({tcp,LSock}), IpAddress = inet_parse:ntoa(IP), Cmd = mk_cmd("EPRT |2|~s|~p|", [IpAddress, Port]), @@ -2271,9 +2294,11 @@ setup_data_connection(#state{mode = active, activate_ctrl_connection(State), {noreply, State#state{caller = {setup_data_connection, {LSock, Caller}}}}; - {ok, {{_,_,_,_} = IP, _}} -> + {ok, {{_,_,_,_} = IP0, _}} -> + IP = proplists:get_value(ip, SockOpts, IP0), {ok, LSock} = gen_tcp:listen(0, [{ip, IP}, {active, false}, - binary, {packet, 0}]), + binary, {packet, 0} | + lists:keydelete(ip,1,SockOpts)]), {ok, Port} = inet:port(LSock), case FtpExt of false -> @@ -2312,41 +2337,41 @@ setup_data_connection(#state{mode = passive, ipfamily = inet, activate_ctrl_connection(State), {noreply, State#state{caller = {setup_data_connection, Caller}}}. -connect(Host, Port, Timeout, #state{ipfamily = inet = IpFam}) -> - connect2(Host, Port, IpFam, Timeout); +connect(Host, Port, SockOpts, Timeout, #state{ipfamily = inet = IpFam}) -> + connect2(Host, Port, IpFam, SockOpts, Timeout); -connect(Host, Port, Timeout, #state{ipfamily = inet6 = IpFam}) -> - connect2(Host, Port, IpFam, Timeout); +connect(Host, Port, SockOpts, Timeout, #state{ipfamily = inet6 = IpFam}) -> + connect2(Host, Port, IpFam, SockOpts, Timeout); -connect(Host, Port, Timeout, #state{ipfamily = inet6fb4}) -> +connect(Host, Port, SockOpts, Timeout, #state{ipfamily = inet6fb4}) -> case inet:getaddr(Host, inet6) of {ok, {0, 0, 0, 0, 0, 16#ffff, _, _} = IPv6} -> case inet:getaddr(Host, inet) of {ok, IPv4} -> IpFam = inet, - connect2(IPv4, Port, IpFam, Timeout); + connect2(IPv4, Port, IpFam, SockOpts, Timeout); _ -> IpFam = inet6, - connect2(IPv6, Port, IpFam, Timeout) + connect2(IPv6, Port, IpFam, SockOpts, Timeout) end; {ok, IPv6} -> IpFam = inet6, - connect2(IPv6, Port, IpFam, Timeout); + connect2(IPv6, Port, IpFam, SockOpts, Timeout); _ -> case inet:getaddr(Host, inet) of {ok, IPv4} -> IpFam = inet, - connect2(IPv4, Port, IpFam, Timeout); + connect2(IPv4, Port, IpFam, SockOpts, Timeout); Error -> Error end end. -connect2(Host, Port, IpFam, Timeout) -> - Opts = [IpFam, binary, {packet, 0}, {active, false}], +connect2(Host, Port, IpFam, SockOpts, Timeout) -> + Opts = [IpFam, binary, {packet, 0}, {active, false} | SockOpts], case gen_tcp:connect(Host, Port, Opts, Timeout) of {ok, Sock} -> {ok, IpFam, Sock}; diff --git a/lib/inets/src/http_client/httpc_handler.erl b/lib/inets/src/http_client/httpc_handler.erl index 26e4f4e699..1a2ce277bf 100644 --- a/lib/inets/src/http_client/httpc_handler.erl +++ b/lib/inets/src/http_client/httpc_handler.erl @@ -980,13 +980,23 @@ handle_http_body(_, #state{status = {ssl_tunnel, Request}, NewState = answer_request(Request, ClientErrMsg, State), {stop, normal, NewState}; -handle_http_body(<<>>, #state{status_line = {_,304, _}} = State) -> +%% All 1xx (informational), 204 (no content), and 304 (not modified) +%% responses MUST NOT include a message-body, and thus are always +%% terminated by the first empty line after the header fields. +%% This implies that chunked encoding MUST NOT be used for these +%% status codes. +handle_http_body(<<>>, #state{headers = Headers, + status_line = {_,StatusCode, _}} = State) + when Headers#http_response_h.'transfer-encoding' =/= "chunked" andalso + (StatusCode =:= 204 orelse %% No Content + StatusCode =:= 304 orelse %% Not Modified + 100 =< StatusCode andalso StatusCode =< 199) -> %% Informational handle_response(State#state{body = <<>>}); -handle_http_body(<<>>, #state{status_line = {_,204, _}} = State) -> - handle_response(State#state{body = <<>>}); -handle_http_body(<<>>, #state{request = #request{method = head}} = State) -> +handle_http_body(<<>>, #state{headers = Headers, + request = #request{method = head}} = State) + when Headers#http_response_h.'transfer-encoding' =/= "chunked" -> handle_response(State#state{body = <<>>}); handle_http_body(Body, #state{headers = Headers, diff --git a/lib/inets/src/http_server/httpd_example.erl b/lib/inets/src/http_server/httpd_example.erl index 47a8c48d01..dbf7c44ad3 100644 --- a/lib/inets/src/http_server/httpd_example.erl +++ b/lib/inets/src/http_server/httpd_example.erl @@ -22,7 +22,7 @@ -export([print/1]). -export([get/2, put/2, post/2, yahoo/2, test1/2, get_bin/2, peer/2,new_status_and_location/2]). --export([newformat/3, post_chunked/3]). +-export([newformat/3, post_chunked/3, post_204/3]). %% These are used by the inets test-suite -export([delay/1, chunk_timeout/3]). @@ -151,6 +151,12 @@ post_chunked(SessionID, _Env, {last, _Body, undefined} = _Bodychunk) -> post_chunked(_, _, _Body) -> exit(body_not_chunked). +post_204(SessionID, _Env, _Input) -> + mod_esi:deliver(SessionID, + ["Status: 204 No Content" ++ "\r\n\r\n"]), + mod_esi:deliver(SessionID, []). + + newformat(SessionID,_,_) -> mod_esi:deliver(SessionID, "Content-Type:text/html\r\n\r\n"), mod_esi:deliver(SessionID, top("new esi format test")), diff --git a/lib/inets/src/http_server/httpd_file.erl b/lib/inets/src/http_server/httpd_file.erl index 4d419172d0..fb71834e95 100644 --- a/lib/inets/src/http_server/httpd_file.erl +++ b/lib/inets/src/http_server/httpd_file.erl @@ -33,6 +33,9 @@ handle_error(enoent, Op, ModData, Path) -> handle_error(enotdir, Op, ModData, Path) -> handle_error(404, Op, ModData, Path, ": A component of the file name is not a directory"); +handle_error(eisdir, Op, ModData, Path) -> + handle_error(403, Op, ModData, Path, + ":Ilegal operation expected a file not a directory"); handle_error(emfile, Op, _ModData, Path) -> handle_error(500, Op, none, Path, ": Too many open files"); handle_error({enfile,_}, Op, _ModData, Path) -> diff --git a/lib/inets/src/http_server/httpd_response.erl b/lib/inets/src/http_server/httpd_response.erl index 6b9053fda6..0bcdee1e16 100644 --- a/lib/inets/src/http_server/httpd_response.erl +++ b/lib/inets/src/http_server/httpd_response.erl @@ -61,8 +61,12 @@ generate_and_send_response(#mod{config_db = ConfigDB} = ModData) -> {StatusCode, Response} -> %% Old way send_response_old(ModData, StatusCode, Response), ok; - undefined -> - send_status(ModData, 500, none), + undefined -> + %% Happens when no mod_* + %% handles the request + send_status(ModData, 501, {ModData#mod.method, + ModData#mod.request_uri, + ModData#mod.http_version}), ok end end diff --git a/lib/inets/src/http_server/mod_esi.erl b/lib/inets/src/http_server/mod_esi.erl index 3206d957d9..9fbedce6aa 100644 --- a/lib/inets/src/http_server/mod_esi.erl +++ b/lib/inets/src/http_server/mod_esi.erl @@ -394,7 +394,16 @@ deliver_webpage_chunk(#mod{config_db = Db} = ModData, Pid, Timeout) -> Continue; {Headers, Body} -> {ok, NewHeaders, StatusCode} = httpd_esi:handle_headers(Headers), - IsDisableChunkedSend = httpd_response:is_disable_chunked_send(Db), + %% All 1xx (informational), 204 (no content), and 304 (not modified) + %% responses MUST NOT include a message-body, and thus are always + %% terminated by the first empty line after the header fields. + %% This implies that chunked encoding MUST NOT be used for these + %% status codes. + IsDisableChunkedSend = + httpd_response:is_disable_chunked_send(Db) orelse + StatusCode =:= 204 orelse %% No Content + StatusCode =:= 304 orelse %% Not Modified + (100 =< StatusCode andalso StatusCode =< 199), %% Informational case (ModData#mod.http_version =/= "HTTP/1.1") or (IsDisableChunkedSend) of true -> @@ -405,8 +414,8 @@ deliver_webpage_chunk(#mod{config_db = Db} = ModData, Pid, Timeout) -> send_headers(ModData, StatusCode, [{"transfer-encoding", "chunked"} | NewHeaders]) - end, - handle_body(Pid, ModData, Body, Timeout, length(Body), + end, + handle_body(Pid, ModData, Body, Timeout, length(Body), IsDisableChunkedSend); timeout -> send_headers(ModData, 504, [{"connection", "close"}]), diff --git a/lib/inets/test/httpc_SUITE.erl b/lib/inets/test/httpc_SUITE.erl index d723fd0460..f3898e1b74 100644 --- a/lib/inets/test/httpc_SUITE.erl +++ b/lib/inets/test/httpc_SUITE.erl @@ -157,7 +157,8 @@ misc() -> [ server_does_not_exist, timeout_memory_leak, - wait_for_whole_response + wait_for_whole_response, + post_204_chunked ]. %%-------------------------------------------------------------------- @@ -1243,6 +1244,59 @@ wait_for_whole_response(Config) when is_list(Config) -> ReqSeqNumServer ! shutdown. %%-------------------------------------------------------------------- +post_204_chunked() -> + [{doc,"Test that chunked encoded 204 responses do not freeze the http client"}]. +post_204_chunked(_Config) -> + Msg = "HTTP/1.1 204 No Content\r\n" ++ + "Date: Thu, 23 Aug 2018 13:36:29 GMT\r\n" ++ + "Content-Type: text/html\r\n" ++ + "Server: inets/6.5.2.3\r\n" ++ + "Cache-Control: no-cache\r\n" ++ + "Pragma: no-cache\r\n" ++ + "Expires: Fri, 24 Aug 2018 07:49:35 GMT\r\n" ++ + "Transfer-Encoding: chunked\r\n" ++ + "\r\n", + Chunk = "0\r\n\r\n", + + {ok, ListenSocket} = gen_tcp:listen(0, [{active,once}, binary]), + {ok,{_,Port}} = inet:sockname(ListenSocket), + spawn(fun () -> custom_server(Msg, Chunk, ListenSocket) end), + + {ok,Host} = inet:gethostname(), + End = "/cgi-bin/erl/httpd_example:post_204", + URL = ?URL_START ++ Host ++ ":" ++ integer_to_list(Port) ++ End, + {ok, _} = httpc:request(post, {URL, [], "text/html", []}, [], []), + timer:sleep(500), + %% Second request times out in the faulty case. + {ok, _} = httpc:request(post, {URL, [], "text/html", []}, [], []). + +custom_server(Msg, Chunk, ListenSocket) -> + {ok, Accept} = gen_tcp:accept(ListenSocket), + receive_packet(), + send_response(Msg, Chunk, Accept), + custom_server_loop(Msg, Chunk, Accept). + +custom_server_loop(Msg, Chunk, Accept) -> + receive_packet(), + send_response(Msg, Chunk, Accept), + custom_server_loop(Msg, Chunk, Accept). + +send_response(Msg, Chunk, Socket) -> + inet:setopts(Socket, [{active, once}]), + gen_tcp:send(Socket, Msg), + timer:sleep(250), + gen_tcp:send(Socket, Chunk). + +receive_packet() -> + receive + {tcp, _, Msg} -> + ct:log("Message received: ~p", [Msg]) + after + 1000 -> + ct:fail("Timeout: did not recive packet") + end. + +%%-------------------------------------------------------------------- stream_fun_server_close() -> [{doc, "Test that an error msg is received when using a receiver fun as stream target"}]. stream_fun_server_close(Config) when is_list(Config) -> diff --git a/lib/inets/test/httpd_SUITE.erl b/lib/inets/test/httpd_SUITE.erl index 9a85c51d24..90192e633c 100644 --- a/lib/inets/test/httpd_SUITE.erl +++ b/lib/inets/test/httpd_SUITE.erl @@ -75,6 +75,8 @@ all() -> {group, http_mime_types}, {group, http_logging}, {group, http_post}, + {group, http_not_sup}, + {group, https_not_sup}, mime_types_format ]. @@ -102,6 +104,8 @@ groups() -> {http_reload, [], [{group, reload}]}, {https_reload, [], [{group, reload}]}, {http_post, [], [{group, post}]}, + {http_not_sup, [], [{group, not_sup}]}, + {https_not_sup, [], [{group, not_sup}]}, {http_mime_types, [], [alias_1_1, alias_1_0, alias_0_9]}, {limit, [], [max_clients_1_1, max_clients_1_0, max_clients_0_9]}, {custom, [], [customize, add_default]}, @@ -114,7 +118,7 @@ groups() -> disturbing_1_0, disturbing_0_9 ]}, - {post, [], [chunked_post, chunked_chunked_encoded_post]}, + {post, [], [chunked_post, chunked_chunked_encoded_post, post_204]}, {basic_auth, [], [basic_auth_1_1, basic_auth_1_0, basic_auth_0_9]}, {auth_api, [], [auth_api_1_1, auth_api_1_0, auth_api_0_9 ]}, @@ -131,7 +135,8 @@ groups() -> trace, range, if_modified_since, mod_esi_chunk_timeout, esi_put, esi_post] ++ http_head() ++ http_get() ++ load()}, {http_1_0, [], [host, cgi, trace] ++ http_head() ++ http_get() ++ load()}, - {http_0_9, [], http_head() ++ http_get() ++ load()} + {http_0_9, [], http_head() ++ http_get() ++ load()}, + {not_sup, [], [put_not_sup]} ]. basic_groups ()-> @@ -203,7 +208,8 @@ init_per_group(Group, Config0) when Group == https_basic; Group == https_auth_api_dets; Group == https_auth_api_mnesia; Group == https_security; - Group == https_reload + Group == https_reload; + Group == https_not_sup -> catch crypto:stop(), try crypto:start() of @@ -222,6 +228,7 @@ init_per_group(Group, Config0) when Group == http_basic; Group == http_auth_api_mnesia; Group == http_security; Group == http_reload; + Group == http_not_sup; Group == http_post; Group == http_mime_types -> @@ -268,6 +275,8 @@ init_per_group(http_logging, Config) -> ServerRoot = proplists:get_value(server_root, Config1), Path = ServerRoot ++ "/httpd_log_transfer", [{transfer_log, Path} | Config1]; +init_per_group(not_sup, Config) -> + [{http_version, "HTTP/1.1"} | Config]; init_per_group(_, Config) -> Config. @@ -441,8 +450,19 @@ get(Config) when is_list(Config) -> {header, "Content-Type", "text/html"}, {header, "Date"}, {header, "Server"}, + {version, Version}]), + + ok = httpd_test_lib:verify_request(proplists:get_value(type, Config), Host, + proplists:get_value(port, Config), + transport_opts(Type, Config), + proplists:get_value(node, Config), + http_request("GET /open/ ", Version, Host), + [{statuscode, 403}, + {header, "Content-Type", "text/html"}, + {header, "Date"}, + {header, "Server"}, {version, Version}]). - + basic_auth_1_1(Config) when is_list(Config) -> basic_auth([{http_version, "HTTP/1.1"} | Config]). @@ -726,6 +746,42 @@ chunked_chunked_encoded_post(Config) when is_list(Config) -> [{http_version, "HTTP/1.1"} | Config], [{statuscode, 200}]). +%%------------------------------------------------------------------------- +post_204() -> + [{doc,"Test that 204 responses are not chunk encoded"}]. +post_204(Config) -> + Host = proplists:get_value(host, Config), + Port = proplists:get_value(port, Config), + SockType = proplists:get_value(type, Config), + TranspOpts = transport_opts(SockType, Config), + Request = "POST /cgi-bin/erl/httpd_example:post_204 ", + + try inets_test_lib:connect_bin(SockType, Host, Port, TranspOpts) of + {ok, Socket} -> + RequestStr = http_request(Request, "HTTP/1.1", Host), + ok = inets_test_lib:send(SockType, Socket, RequestStr), + receive + {tcp, Socket, Data} -> + case binary:match(Data, <<"chunked">>,[]) of + nomatch -> + ok; + {_, _} -> + ct:fail("Chunked encoding detected.") + end + after 2000 -> + ct:fail(connection_timed_out) + end; + ConnectError -> + ct:fail({connect_error, ConnectError, + [SockType, Host, Port, TranspOpts]}) + catch + T:E -> + ct:fail({connect_failure, + [{type, T}, + {error, E}, + {stacktrace, erlang:get_stacktrace()}, + {args, [SockType, Host, Port, TranspOpts]}]}) + end. %%------------------------------------------------------------------------- htaccess_1_1(Config) when is_list(Config) -> @@ -891,6 +947,33 @@ max_clients_0_9() -> max_clients_0_9(Config) when is_list(Config) -> do_max_clients([{http_version, "HTTP/0.9"} | Config]). + + +%%------------------------------------------------------------------------- +put_not_sup() -> + [{doc, "Test unhandled request"}]. + +put_not_sup(Config) when is_list(Config) -> + ok = http_status("PUT /index.html ", + {"Content-Length:100 \r\n", + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"}, + Config, [{statuscode, 501}]). %%------------------------------------------------------------------------- esi() -> [{doc, "Test mod_esi"}]. @@ -1738,7 +1821,8 @@ start_apps(Group) when Group == https_basic; Group == https_auth_api_mnesia; Group == https_htaccess; Group == https_security; - Group == https_reload + Group == https_reload; + Group == https_not_sup -> inets_test_lib:start_apps([inets, asn1, crypto, public_key, ssl]); start_apps(Group) when Group == http_basic; @@ -1753,6 +1837,8 @@ start_apps(Group) when Group == http_basic; Group == http_logging; Group == http_reload; Group == http_post; + Group == http_mime_types; + Group == http_not_sup; Group == http_mime_types-> inets_test_lib:start_apps([inets]). @@ -1797,6 +1883,10 @@ server_config(http_basic, Config) -> basic_conf() ++ server_config(http, Config); server_config(https_basic, Config) -> basic_conf() ++ server_config(https, Config); +server_config(http_not_sup, Config) -> + not_sup_conf() ++ server_config(http, Config); +server_config(https_not_sup, Config) -> + not_sup_conf() ++ server_config(https, Config); server_config(http_reload, Config) -> [{keep_alive_timeout, 2}] ++ server_config(http, Config); server_config(http_post, Config) -> @@ -1878,7 +1968,6 @@ server_config(http, Config) -> {erl_script_alias, {"/cgi-bin/erl", [httpd_example, io]}}, {eval_script_alias, {"/eval", [httpd_example, io]}} ]; - server_config(https, Config) -> PrivDir = proplists:get_value(priv_dir, Config), [{socket_type, {essl, @@ -1930,7 +2019,9 @@ head_status(_) -> basic_conf() -> [{modules, [mod_alias, mod_range, mod_responsecontrol, - mod_trace, mod_esi, mod_cgi, mod_dir, mod_get, mod_head]}]. + mod_trace, mod_esi, mod_cgi, mod_get, mod_head]}]. +not_sup_conf() -> + [{modules, [mod_get]}]. auth_access_conf() -> [{modules, [mod_alias, mod_htaccess, mod_dir, mod_get, mod_head]}, diff --git a/lib/inets/vsn.mk b/lib/inets/vsn.mk index 9bbcd06914..3259a8a195 100644 --- a/lib/inets/vsn.mk +++ b/lib/inets/vsn.mk @@ -19,6 +19,6 @@ # %CopyrightEnd% APPLICATION = inets -INETS_VSN = 6.5.2 +INETS_VSN = 6.5.2.4 PRE_VSN = APP_VSN = "$(APPLICATION)-$(INETS_VSN)$(PRE_VSN)" |