diff options
author | Ingela Anderton Andin <[email protected]> | 2013-01-31 15:34:51 +0100 |
---|---|---|
committer | Ingela Anderton Andin <[email protected]> | 2013-02-18 18:02:59 +0100 |
commit | baca1b6cf223b03786ea47fc101f4249bda3cb3c (patch) | |
tree | d5c733444f16e34188f7bc3d9bedf24c35ef63d5 /lib/inets/test/old_httpc_SUITE.erl | |
parent | 79f4745f6e7254a5399c63f9947dc39c1abd21ba (diff) | |
download | otp-baca1b6cf223b03786ea47fc101f4249bda3cb3c.tar.gz otp-baca1b6cf223b03786ea47fc101f4249bda3cb3c.tar.bz2 otp-baca1b6cf223b03786ea47fc101f4249bda3cb3c.zip |
inets: Improve ssl handling
httpc: CTfy test suite
httpd: Simplify ssl configuration OTP-10846
Diffstat (limited to 'lib/inets/test/old_httpc_SUITE.erl')
-rw-r--r-- | lib/inets/test/old_httpc_SUITE.erl | 3602 |
1 files changed, 3602 insertions, 0 deletions
diff --git a/lib/inets/test/old_httpc_SUITE.erl b/lib/inets/test/old_httpc_SUITE.erl new file mode 100644 index 0000000000..3d7de71052 --- /dev/null +++ b/lib/inets/test/old_httpc_SUITE.erl @@ -0,0 +1,3602 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2004-2013. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%% + +%% +%% ts:run(inets, httpc_SUITE, [batch]). +%% + +-module(old_httpc_SUITE). + +-include_lib("common_test/include/ct.hrl"). +-include("test_server_line.hrl"). + +-include_lib("kernel/include/file.hrl"). +-include("inets_test_lib.hrl"). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +%% Test server specific exports +-define(IP_PORT, 8998). +-define(SSL_PORT, 8999). +-define(NOT_IN_USE_PORT, 8997). +-define(LOCAL_HOST, {127,0,0,1}). +-define(IPV6_LOCAL_HOST, "0:0:0:0:0:0:0:1"). +-define(URL_START, "http://localhost:"). +-define(SSL_URL_START, "https://localhost:"). +-define(CR, $\r). +-define(LF, $\n). +-define(HTTP_MAX_HEADER_SIZE, 10240). + + +%%-------------------------------------------------------------------- +%% all(Arg) -> [Doc] | [Case] | {skip, Comment} +%% Arg - doc | suite +%% Doc - string() +%% Case - atom() +%% Name of a test case function. +%% Comment - string() +%% Description: Returns documentation/test cases in this test suite +%% or a skip tuple if the platform is not supported. +%%-------------------------------------------------------------------- + +suite() -> [{ct_hooks,[ts_install_cth]}]. + +all() -> + [ + http_options, + %% http_head, + %% http_get, + %%http_post, + %%http_post_streaming, + %% http_dummy_pipe, + %% http_inets_pipe, + %% http_trace, + %% http_async, + %% http_save_to_file, + %% http_save_to_file_async, + %%http_headers, + %%http_headers_dummy, + + %%http_bad_response, + + http_redirect, + http_redirect_loop, + + %%http_internal_server_error, + %%http_userinfo, + + %%http_cookie, + %%http_server_does_not_exist, + %%http_invalid_http, + %%http_emulate_lower_versions, + %%http_relaxed, + %%page_does_not_exist, + %%parse_url, + options, + %%headers_as_is, + selecting_session, + + %%{group, ssl}, + %%{group, stream}, + {group, ipv6}, + {group, tickets}, + initial_server_connect + ]. + +groups() -> + [ + %% {ssl, [], [ssl_head, + %% essl_head, + %% ssl_get, + %% essl_get, + %% ssl_trace, + %% essl_trace]}, + %% {stream, [], [http_stream, + %% http_stream_once]}, + {tickets, [], [%%hexed_query_otp_6191, + %%empty_body_otp_6243, + %%empty_response_header_otp_6830, + %%transfer_encoding_otp_6807, + %%no_content_204_otp_6982, + %%missing_CR_otp_7304, + %% {group, otp_7883}, + %% {group, otp_8154}, + %% {group, otp_8106}, + otp_8056, + otp_8352, + otp_8371, + otp_8739]}, + %% {otp_7883, [], [otp_7883_1, + %% otp_7883_2]}, + {otp_8154, [], [otp_8154_1]}, + %%{otp_8106, [], [otp_8106_pid, + %% otp_8106_fun, + %% otp_8106_mfa]}, + {ipv6, [], [ipv6_ipcomm, ipv6_essl]} + ]. + + +init_per_group(ipv6 = _GroupName, Config) -> + case inets_test_lib:has_ipv6_support() of + {ok, _} -> + Config; + _ -> + {skip, "Host does not support IPv6"} + end; +init_per_group(_GroupName, Config) -> + Config. + +end_per_group(_GroupName, Config) -> + Config. + + +%%-------------------------------------------------------------------- +%% Function: init_per_suite(Config) -> Config +%% Config - [tuple()] +%% A list of key/value pairs, holding the test case configuration. +%% Description: Initiation before the whole suite +%% +%% Note: This function is free to add any key/value pairs to the Config +%% variable, but should NOT alter/remove any existing entries. +%%-------------------------------------------------------------------- +init_per_suite(Config) -> + + ?PRINT_SYSTEM_INFO([]), + + PrivDir = ?config(priv_dir, Config), + DataDir = ?config(data_dir, Config), + ServerRoot = filename:join(PrivDir, "server_root"), + DocRoot = filename:join(ServerRoot, "htdocs"), + IpConfFile = integer_to_list(?IP_PORT) ++ ".conf", + SslConfFile = integer_to_list(?SSL_PORT) ++ ".conf", + + setup_server_dirs(ServerRoot, DocRoot, DataDir), + create_config(IpConfFile, ip_comm, ?IP_PORT, PrivDir, ServerRoot, + DocRoot, DataDir), + create_config(SslConfFile, ssl, ?SSL_PORT, PrivDir, ServerRoot, + DocRoot, DataDir), + + Cgi = case test_server:os_type() of + {win32, _} -> + filename:join([ServerRoot, "cgi-bin", "cgi_echo.exe"]); + _ -> + filename:join([ServerRoot, "cgi-bin", "cgi_echo"]) + end, + + {ok, FileInfo} = file:read_file_info(Cgi), + ok = file:write_file_info(Cgi, FileInfo#file_info{mode = 8#00755}), + + [{has_ipv6_support, inets_test_lib:has_ipv6_support()}, + {server_root, ServerRoot}, + {doc_root, DocRoot}, + {local_port, ?IP_PORT}, + {local_ssl_port, ?SSL_PORT} | Config]. + + +%%-------------------------------------------------------------------- +%% Function: end_per_suite(Config) -> _ +%% Config - [tuple()] +%% A list of key/value pairs, holding the test case configuration. +%% Description: Cleanup after the whole suite +%%-------------------------------------------------------------------- +end_per_suite(Config) -> + PrivDir = ?config(priv_dir, Config), + inets_test_lib:del_dirs(PrivDir), + application:stop(inets), + application:stop(ssl), + ok. + + +%%-------------------------------------------------------------------- +%% Function: init_per_testcase(Case, Config) -> Config +%% Case - atom() +%% Name of the test case that is about to be run. +%% Config - [tuple()] +%% A list of key/value pairs, holding the test case configuration. +%% +%% Description: Initiation before each test case +%% +%% Note: This function is free to add any key/value pairs to the Config +%% variable, but should NOT alter/remove any existing entries. +%%-------------------------------------------------------------------- + +init_per_testcase(otp_8154_1 = Case, Config) -> + init_per_testcase(Case, 5, Config); + +init_per_testcase(initial_server_connect = Case, Config) -> + %% Try to check if crypto actually exist or not, + %% this test case does not work unless it does + try + begin + ?ENSURE_STARTED([crypto, public_key, ssl]), + inets:start(), + Config + end + catch + throw:{error, {failed_starting, App, ActualError}} -> + tsp("init_per_testcase(~w) -> failed starting ~w: " + "~n ~p", [Case, App, ActualError]), + SkipString = + "Could not start " ++ atom_to_list(App), + skip(SkipString); + _:X -> + SkipString = + lists:flatten( + io_lib:format("Failed starting apps: ~p", [X])), + skip(SkipString) + end; + +init_per_testcase(Case, Config) -> + init_per_testcase(Case, 2, Config). + +init_per_testcase(Case, Timeout, Config) -> + io:format(user, + "~n~n*** INIT ~w:~w[~w] ***" + "~n~n", [?MODULE, Case, Timeout]), + + PrivDir = ?config(priv_dir, Config), + application:stop(inets), + Dog = test_server:timetrap(inets_test_lib:minutes(Timeout)), + TmpConfig = lists:keydelete(watchdog, 1, Config), + IpConfFile = integer_to_list(?IP_PORT) ++ ".conf", + SslConfFile = integer_to_list(?SSL_PORT) ++ ".conf", + + %% inets:enable_trace(max, io, httpd), + %% inets:enable_trace(max, io, httpc), + %% inets:enable_trace(max, io, all), + + NewConfig = + case atom_to_list(Case) of + [$s, $s, $l | _] -> + ?ENSURE_STARTED([crypto, public_key, ssl]), + init_per_testcase_ssl(ssl, PrivDir, SslConfFile, + [{watchdog, Dog} | TmpConfig]); + + [$e, $s, $s, $l | _] -> + ?ENSURE_STARTED([crypto, public_key, ssl]), + init_per_testcase_ssl(essl, PrivDir, SslConfFile, + [{watchdog, Dog} | TmpConfig]); + + "ipv6_" ++ _Rest -> + %% Ensure needed apps (crypto, public_key and ssl) are started + try ?ENSURE_STARTED([crypto, public_key, ssl]) of + ok -> + Profile = ipv6, + %% A stand-alone profile is represented by a pid() + {ok, ProfilePid} = + inets:start(httpc, + [{profile, Profile}, + {data_dir, PrivDir}], stand_alone), + ok = httpc:set_options([{ipfamily, inet6}], + ProfilePid), + tsp("httpc profile pid: ~p", [ProfilePid]), + [{watchdog, Dog}, {profile, ProfilePid}| TmpConfig] + catch + throw:{error, {failed_starting, App, ActualError}} -> + tsp("init_per_testcase(~w) -> failed starting ~w: " + "~n ~p", [Case, App, ActualError]), + SkipString = + "Could not start " ++ atom_to_list(App), + skip(SkipString); + _:X -> + SkipString = + lists:flatten( + io_lib:format("Failed starting apps: ~p", [X])), + skip(SkipString) + end; + + _ -> + %% Try inet6fb4 on windows... + %% No need? Since it is set above? + + %% tsp("init_per_testcase -> allways try IPv6 on windows"), + %% ?RUN_ON_WINDOWS( + %% fun() -> + %% tsp("init_per_testcase:set_options_fun -> " + %% "set-option ipfamily to inet6fb4"), + %% Res = httpc:set_options([{ipfamily, inet6fb4}]), + %% tsp("init_per_testcase:set_options_fun -> " + %% "~n Res: ~p", [Res]), + %% Res + %% end), + + TmpConfig2 = lists:keydelete(local_server, 1, TmpConfig), + %% Will start inets + tsp("init_per_testcase -> try start server"), + Server = start_http_server(PrivDir, IpConfFile), + [{watchdog, Dog}, {local_server, Server} | TmpConfig2] + end, + + %% <IPv6> + %% Set default ipfamily to the same as the main server has by default + %% This makes the client try w/ ipv6 before falling back to ipv4, + %% as that is what the server is configured to do. + %% Note that this is required for the tests to run on *BSD w/ ipv6 enabled + %% as well as on Windows. The Linux behaviour of allowing ipv4 connects + %% to ipv6 sockets is not required or even encouraged. + + tsp("init_per_testcase -> Options before ipfamily set: ~n~p", + [httpc:get_options(all)]), + ok = httpc:set_options([{ipfamily, inet6fb4}]), + tsp("init_per_testcase -> Options after ipfamily set: ~n~p", + [httpc:get_options(all)]), + + %% Note that the IPv6 test case(s) *must* use inet6, + %% so this value will be overwritten (see "ipv6_" below). + %% </IPv6> + + %% inets:enable_trace(max, io, all), + %% snmp:set_trace([gen_tcp]), + tsp("init_per_testcase(~w) -> done when" + "~n NewConfig: ~p" + "~n~n", [Case, NewConfig]), + NewConfig. + + +init_per_testcase_ssl(Tag, PrivDir, SslConfFile, Config) -> + tsp("init_per_testcase_ssl(~w) -> stop ssl", [Tag]), + application:stop(ssl), + Config2 = lists:keydelete(local_ssl_server, 1, Config), + %% Will start inets + tsp("init_per_testcase_ssl(~w) -> try start http server (including inets)", + [Tag]), + Server = inets_test_lib:start_http_server( + filename:join(PrivDir, SslConfFile), Tag), + tsp("init_per_testcase(~w) -> Server: ~p", [Tag, Server]), + [{local_ssl_server, Server} | Config2]. + +start_http_server(ConfDir, ConfFile) -> + inets_test_lib:start_http_server( filename:join(ConfDir, ConfFile) ). + + +%%-------------------------------------------------------------------- +%% Function: end_per_testcase(Case, Config) -> _ +%% Case - atom() +%% Name of the test case that is about to be run. +%% Config - [tuple()] +%% A list of key/value pairs, holding the test case configuration. +%% Description: Cleanup after each test case +%%-------------------------------------------------------------------- +end_per_testcase(http_save_to_file = Case, Config) -> + io:format(user, "~n~n*** END ~w:~w ***~n~n", + [?MODULE, Case]), + PrivDir = ?config(priv_dir, Config), + FullPath = filename:join(PrivDir, "dummy.html"), + file:delete(FullPath), + finish(Config); + +end_per_testcase(Case, Config) -> + io:format(user, "~n~n*** END ~w:~w ***~n~n", + [?MODULE, Case]), + case atom_to_list(Case) of + "ipv6_" ++ _Rest -> + tsp("end_per_testcase(~w) -> stop ssl", [Case]), + application:stop(ssl), + tsp("end_per_testcase(~w) -> stop public_key", [Case]), + application:stop(public_key), + tsp("end_per_testcase(~w) -> stop crypto", [Case]), + application:stop(crypto), + ProfilePid = ?config(profile, Config), + tsp("end_per_testcase(~w) -> stop httpc profile (~p)", + [Case, ProfilePid]), + unlink(ProfilePid), + inets:stop(stand_alone, ProfilePid), + tsp("end_per_testcase(~w) -> httpc profile (~p) stopped", + [Case, ProfilePid]), + ok; + _ -> + ok + end, + finish(Config). + +finish(Config) -> + Dog = ?config(watchdog, Config), + case Dog of + undefined -> + ok; + _ -> + tsp("finish -> stop watchdog (~p)", [Dog]), + test_server:timetrap_cancel(Dog) + end. + +%%------------------------------------------------------------------------- +%% Test cases starts here. +%%------------------------------------------------------------------------- + + + +%%------------------------------------------------------------------------- + +http_options(doc) -> + ["Test http options request against local server."]; +http_options(suite) -> + []; +http_options(Config) when is_list(Config) -> + skip("Not supported by httpd"). + +http_head(doc) -> + ["Test http head request against local server."]; +http_head(suite) -> + []; +http_head(Config) when is_list(Config) -> + tsp("http_head -> entry with" + "~n Config: ~p", [Config]), + Method = head, + Port = ?config(local_port, Config), + URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html", + Request = {URL, []}, + HttpOpts = [], + Opts = [], + VerifyResult = + fun({ok, {{_,200,_}, [_ | _], []}}) -> + ok; + ({ok, UnexpectedReply}) -> + tsp("http_head:verify_fun -> Unexpected Reply: " + "~n ~p", [UnexpectedReply]), + tsf({unexpected_reply, UnexpectedReply}); + ({error, Reason} = Error) -> + tsp("http_head:verify_fun -> Error reply: " + "~n Reason: ~p", [Reason]), + tsf({bad_reply, Error}) + end, + simple_request_and_verify(Config, + Method, Request, HttpOpts, Opts, VerifyResult). + + +%%------------------------------------------------------------------------- + +http_get(doc) -> + ["Test http get request against local server"]; +http_get(suite) -> + []; +http_get(Config) when is_list(Config) -> + tsp("http_get -> entry with" + "~n Config: ~p", [Config]), + case ?config(local_server, Config) of + ok -> + tsp("local-server running"), + Method = get, + Port = ?config(local_port, Config), + URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html", + Request = {URL, []}, + Timeout = timer:seconds(1), + ConnTimeout = Timeout + timer:seconds(1), + HttpOptions1 = [{timeout, Timeout}, + {connect_timeout, ConnTimeout}], + Options1 = [], + Body = + case httpc:request(Method, Request, HttpOptions1, Options1) of + {ok, {{_,200,_}, [_ | _], ReplyBody = [_ | _]}} -> + ReplyBody; + {ok, UnexpectedReply1} -> + tsf({unexpected_reply, UnexpectedReply1}); + {error, _} = Error1 -> + tsf({bad_reply, Error1}) + end, + + %% eqvivivalent to httpc:request(get, {URL, []}, [], []), + inets_test_lib:check_body(Body), + + HttpOptions2 = [], + Options2 = [{body_format, binary}], + case httpc:request(Method, Request, HttpOptions2, Options2) of + {ok, {{_,200,_}, [_ | _], Bin}} when is_binary(Bin) -> + ok; + {ok, {{_,200,_}, [_ | _], BadBin}} -> + tsf({body_format_not_binary, BadBin}); + {ok, UnexpectedReply2} -> + tsf({unexpected_reply, UnexpectedReply2}); + {error, _} = Error2 -> + tsf({bad_reply, Error2}) + end; + _ -> + skip("Failed to start local http-server") + end. + +%%------------------------------------------------------------------------- + +http_post(doc) -> + ["Test http post request against local server. We do in this case " + "only care about the client side of the the post. The server " + "script will not actually use the post data."]; +http_post(suite) -> + []; +http_post(Config) when is_list(Config) -> + case ?config(local_server, Config) of + ok -> + Port = ?config(local_port, Config), + + URL = case test_server:os_type() of + {win32, _} -> + ?URL_START ++ integer_to_list(Port) ++ + "/cgi-bin/cgi_echo.exe"; + _ -> + ?URL_START ++ integer_to_list(Port) ++ + "/cgi-bin/cgi_echo" + + end, + %% Cgi-script expects the body length to be 100 + Body = lists:duplicate(100, "1"), + + {ok, {{_,200,_}, [_ | _], [_ | _]}} = + httpc:request(post, {URL, [{"expect","100-continue"}], + "text/plain", Body}, [], []), + + {ok, {{_,504,_}, [_ | _], []}} = + httpc:request(post, {URL, [{"expect","100-continue"}], + "text/plain", "foobar"}, [], []); + _ -> + skip("Failed to start local http-server") + end. + +%%------------------------------------------------------------------------- +http_post_streaming(doc) -> + ["Test streaming http post request against local server. " + "We only care about the client side of the the post. " + "The server script will not actually use the post data."]; +http_post_streaming(suite) -> + []; +http_post_streaming(Config) when is_list(Config) -> + case ?config(local_server, Config) of + ok -> + Port = ?config(local_port, Config), + URL = case test_server:os_type() of + {win32, _} -> + ?URL_START ++ integer_to_list(Port) ++ + "/cgi-bin/cgi_echo.exe"; + _ -> + ?URL_START ++ integer_to_list(Port) ++ + "/cgi-bin/cgi_echo" + end, + %% Cgi-script expects the body length to be 100 + BodyFun = fun(0) -> + io:format("~w:http_post_streaming_fun -> " + "zero~n", [?MODULE]), + eof; + (LenLeft) -> + io:format("~w:http_post_streaming_fun -> " + "LenLeft: ~p~n", [?MODULE, LenLeft]), + {ok, lists:duplicate(10, "1"), LenLeft - 10} + end, + + {ok, {{_,200,_}, [_ | _], [_ | _]}} = + httpc:request(post, {URL, + [{"expect", "100-continue"}, + {"content-length", "100"}], + "text/plain", {BodyFun, 100}}, [], []), + + {ok, {{_,504,_}, [_ | _], []}} = + httpc:request(post, {URL, + [{"expect", "100-continue"}, + {"content-length", "10"}], + "text/plain", {BodyFun, 10}}, [], []); + + _ -> + skip("Failed to start local http-server") + end. + + +%%------------------------------------------------------------------------- +http_emulate_lower_versions(doc) -> + ["Perform request as 0.9 and 1.0 clients."]; +http_emulate_lower_versions(suite) -> + []; +http_emulate_lower_versions(Config) when is_list(Config) -> + case ?config(local_server, Config) of + ok -> + Port = ?config(local_port, Config), + URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html", + {ok, Body0} = + httpc:request(get, {URL, []}, [{version, "HTTP/0.9"}], []), + inets_test_lib:check_body(Body0), + {ok, {{"HTTP/1.0", 200, _}, [_ | _], Body1 = [_ | _]}} = + httpc:request(get, {URL, []}, [{version, "HTTP/1.0"}], []), + inets_test_lib:check_body(Body1), + {ok, {{"HTTP/1.1", 200, _}, [_ | _], Body2 = [_ | _]}} = + httpc:request(get, {URL, []}, [{version, "HTTP/1.1"}], []), + inets_test_lib:check_body(Body2); + _-> + skip("Failed to start local http-server") + end. + + +%%------------------------------------------------------------------------- + +http_relaxed(doc) -> + ["Test relaxed mode"]; +http_relaxed(suite) -> + []; +http_relaxed(Config) when is_list(Config) -> + ok = httpc:set_options([{ipv6, disabled}]), % also test the old option + %% ok = httpc:set_options([{ipfamily, inet}]), + {DummyServerPid, Port} = dummy_server(ipv4), + + URL = ?URL_START ++ integer_to_list(Port) ++ + "/missing_reason_phrase.html", + + {error, Reason} = + httpc:request(get, {URL, []}, [{relaxed, false}], []), + + test_server:format("Not relaxed: ~p~n", [Reason]), + + {ok, {{_, 200, _}, [_ | _], [_ | _]}} = + httpc:request(get, {URL, []}, [{relaxed, true}], []), + + DummyServerPid ! stop, + ok = httpc:set_options([{ipv6, enabled}]), + %% ok = httpc:set_options([{ipfamily, inet6fb4}]), + ok. + + +%%------------------------------------------------------------------------- +http_dummy_pipe(doc) -> + ["Test pipelining code."]; +http_dummy_pipe(suite) -> + []; +http_dummy_pipe(Config) when is_list(Config) -> + ok = httpc:set_options([{ipfamily, inet}]), + {DummyServerPid, Port} = dummy_server(ipv4), + + URL = ?URL_START ++ integer_to_list(Port) ++ "/foobar.html", + + test_pipeline(URL), + + DummyServerPid ! stop, + ok = httpc:set_options([{ipfamily, inet6fb4}]), + ok. + +http_inets_pipe(doc) -> + ["Test pipelining code."]; +http_inets_pipe(suite) -> + []; +http_inets_pipe(Config) when is_list(Config) -> + + case ?config(local_server, Config) of + ok -> + Port = ?config(local_port, Config), + URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html", + test_pipeline(URL); + _ -> + skip("Failed to start local http-server") + end. + + +test_pipeline(URL) -> + p("test_pipeline -> entry with" + "~n URL: ~p", [URL]), + + httpc:set_options([{pipeline_timeout, 50000}, + {max_pipeline_length, 5}]), + + p("test_pipeline -> issue (async) request 1" + "~n when profile info: ~p", [httpc:info()]), + {ok, RequestIdA1} = + httpc:request(get, {URL, []}, [], [{sync, false}]), + tsp("RequestIdA1: ~p", [RequestIdA1]), + p("test_pipeline -> RequestIdA1: ~p" + "~n when profile info: ~p", [RequestIdA1, httpc:info()]), + + %% Make sure pipeline is initiated + p("test_pipeline -> sleep some", []), + test_server:sleep(4000), + + p("test_pipeline -> issue (async) request A2, A3 and A4" + "~n when profile info: ~p", [httpc:info()]), + {ok, RequestIdA2} = + httpc:request(get, {URL, []}, [], [{sync, false}]), + {ok, RequestIdA3} = + httpc:request(get, {URL, []}, [], [{sync, false}]), + {ok, RequestIdA4} = + httpc:request(get, {URL, []}, [], [{sync, false}]), + tsp("RequestIdAs => A2: ~p, A3: ~p and A4: ~p", + [RequestIdA2, RequestIdA3, RequestIdA4]), + p("test_pipeline -> RequestIds => A2: ~p, A3: ~p and A4: ~p" + "~n when profile info: ~p", + [RequestIdA2, RequestIdA3, RequestIdA4, httpc:info()]), + + p("test_pipeline -> issue (sync) request 3"), + {ok, {{_,200,_}, [_ | _], [_ | _]}} = + httpc:request(get, {URL, []}, [], []), + + p("test_pipeline -> expect reply for (async) request A1, A2, A3 and A4" + "~n when profile info: ~p", [httpc:info()]), + pipeline_await_async_reply([{RequestIdA1, a1, 200}, + {RequestIdA2, a2, 200}, + {RequestIdA3, a3, 200}, + {RequestIdA4, a4, 200}], ?MINS(2)), + + p("test_pipeline -> sleep some" + "~n when profile info: ~p", [httpc:info()]), + test_server:sleep(4000), + + p("test_pipeline -> issue (async) request B1, B2, B3 and B4" + "~n when profile info: ~p", [httpc:info()]), + {ok, RequestIdB1} = + httpc:request(get, {URL, []}, [], [{sync, false}]), + {ok, RequestIdB2} = + httpc:request(get, {URL, []}, [], [{sync, false}]), + {ok, RequestIdB3} = + httpc:request(get, {URL, []}, [], [{sync, false}]), + {ok, RequestIdB4} = + httpc:request(get, {URL, []}, [], [{sync, false}]), + tsp("RequestIdBs => B1: ~p, B2: ~p, B3: ~p and B4: ~p", + [RequestIdB1, RequestIdB2, RequestIdB3, RequestIdB4]), + p("test_pipeline -> RequestIdBs => B1: ~p, B2: ~p, B3: ~p and B4: ~p" + "~n when profile info: ~p", + [RequestIdB1, RequestIdB2, RequestIdB3, RequestIdB4, httpc:info()]), + + p("test_pipeline -> cancel (async) request B2" + "~n when profile info: ~p", [httpc:info()]), + ok = httpc:cancel_request(RequestIdB2), + + p("test_pipeline -> " + "expect *no* reply for cancelled (async) request B2 (for 3 secs)" + "~n when profile info: ~p", [httpc:info()]), + receive + {http, {RequestIdB2, _}} -> + tsf(http_cancel_request_failed) + after 3000 -> + ok + end, + + p("test_pipeline -> expect reply for (async) request B1, B3 and B4" + "~n when profile info: ~p", [httpc:info()]), + Bodies = pipeline_await_async_reply([{RequestIdB1, b1, 200}, + {RequestIdB3, b3, 200}, + {RequestIdB4, b4, 200}], ?MINS(1)), + [{b1, Body}|_] = Bodies, + + p("test_pipeline -> check reply for (async) request B1" + "~n when profile info: ~p", [httpc:info()]), + inets_test_lib:check_body(binary_to_list(Body)), + + p("test_pipeline -> ensure no unexpected incomming" + "~n when profile info: ~p", [httpc:info()]), + receive + {http, Any} -> + tsf({unexpected_message, Any}) + after 500 -> + ok + end, + + p("test_pipeline -> done" + "~n when profile info: ~p", [httpc:info()]), + ok. + +pipeline_await_async_reply(ReqIds, Timeout) -> + pipeline_await_async_reply(ReqIds, Timeout, []). + +pipeline_await_async_reply([], _, Acc) -> + lists:keysort(1, Acc); +pipeline_await_async_reply(ReqIds, Timeout, Acc) when Timeout > 0 -> + T1 = inets_test_lib:timestamp(), + p("pipeline_await_async_reply -> await replies" + "~n ReqIds: ~p" + "~n Timeout: ~p", [ReqIds, Timeout]), + receive + {http, {RequestId, {{_, Status, _}, _, Body}}} -> + p("pipeline_await_async_reply -> received reply for" + "~n RequestId: ~p" + "~n Status: ~p", [RequestId, Status]), + case lists:keysearch(RequestId, 1, ReqIds) of + {value, {RequestId, N, Status}} -> + p("pipeline_await_async_reply -> " + "found expected request ~w", [N]), + ReqIds2 = lists:keydelete(RequestId, 1, ReqIds), + NewTimeout = Timeout - (inets_test_lib:timestamp()-T1), + pipeline_await_async_reply(ReqIds2, NewTimeout, + [{N, Body} | Acc]); + {value, {RequestId, N, WrongStatus}} -> + p("pipeline_await_async_reply -> " + "found request ~w with wrong status", [N]), + tsf({reply_with_unexpected_status, + {RequestId, N, WrongStatus}}); + false -> + tsf({unexpected_reply, {RequestId, Status}}) + end; + {http, Msg} -> + tsf({unexpected_reply, Msg}) + after Timeout -> + receive + Any -> + tsp("pipeline_await_async_reply -> " + "received unknown data after timeout: " + "~n ~p", [Any]), + tsf({timeout, {unknown, Any}}) + end + end; +pipeline_await_async_reply(ReqIds, _, Acc) -> + tsp("pipeline_await_async_reply -> " + "timeout: " + "~n ~p" + "~nwhen" + "~n ~p", [ReqIds, Acc]), + tsf({timeout, ReqIds, Acc}). + + + +%%------------------------------------------------------------------------- +http_trace(doc) -> + ["Perform a TRACE request."]; +http_trace(suite) -> + []; +http_trace(Config) when is_list(Config) -> + case ?config(local_server, Config) of + ok -> + Port = ?config(local_port, Config), + URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html", + case httpc:request(trace, {URL, []}, [], []) of + {ok, {{_,200,_}, [_ | _], "TRACE /dummy.html" ++ _}} -> + ok; + {ok, {{_,200,_}, [_ | _], WrongBody}} -> + tsf({wrong_body, WrongBody}); + {ok, WrongReply} -> + tsf({wrong_reply, WrongReply}); + Error -> + tsf({failed, Error}) + end; + _ -> + skip("Failed to start local http-server") + end. +%%------------------------------------------------------------------------- +http_async(doc) -> + ["Test an asynchrony http request."]; +http_async(suite) -> + []; +http_async(Config) when is_list(Config) -> + case ?config(local_server, Config) of + ok -> + Port = ?config(local_port, Config), + URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html", + {ok, RequestId} = + httpc:request(get, {URL, []}, [], [{sync, false}]), + + Body = + receive + {http, {RequestId, {{_, 200, _}, _, BinBody}}} -> + BinBody; + {http, Msg} -> + tsf(Msg) + end, + + inets_test_lib:check_body(binary_to_list(Body)), + + {ok, NewRequestId} = + httpc:request(get, {URL, []}, [], [{sync, false}]), + ok = httpc:cancel_request(NewRequestId), + receive + {http, {NewRequestId, _NewResult}} -> + tsf(http_cancel_request_failed) + after 3000 -> + ok + end; + _ -> + skip("Failed to start local http-server") + end. + +%%------------------------------------------------------------------------- +http_save_to_file(doc) -> + ["Test to save the http body to a file"]; +http_save_to_file(suite) -> + []; +http_save_to_file(Config) when is_list(Config) -> + case ?config(local_server, Config) of + ok -> + PrivDir = ?config(priv_dir, Config), + FilePath = filename:join(PrivDir, "dummy.html"), + Port = ?config(local_port, Config), + URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html", + {ok, saved_to_file} + = httpc:request(get, {URL, []}, [], [{stream, FilePath}]), + {ok, Bin} = file:read_file(FilePath), + {ok, {{_,200,_}, [_ | _], Body}} = httpc:request(URL), + Bin == Body; + _ -> + skip("Failed to start local http-server") + end. + + +%%------------------------------------------------------------------------- +http_save_to_file_async(doc) -> + ["Test to save the http body to a file"]; +http_save_to_file_async(suite) -> + []; +http_save_to_file_async(Config) when is_list(Config) -> + case ?config(local_server, Config) of + ok -> + PrivDir = ?config(priv_dir, Config), + FilePath = filename:join(PrivDir, "dummy.html"), + Port = ?config(local_port, Config), + URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html", + {ok, RequestId} = httpc:request(get, {URL, []}, [], + [{stream, FilePath}, + {sync, false}]), + receive + {http, {RequestId, saved_to_file}} -> + ok; + {http, Msg} -> + tsf(Msg) + end, + + {ok, Bin} = file:read_file(FilePath), + {ok, {{_,200,_}, [_ | _], Body}} = httpc:request(URL), + Bin == Body; + _ -> + skip("Failed to start local http-server") + end. +%%------------------------------------------------------------------------- +http_headers(doc) -> + ["Use as many request headers as possible not used in proxy_headers"]; +http_headers(suite) -> + []; +http_headers(Config) when is_list(Config) -> + + case ?config(local_server, Config) of + ok -> + Port = ?config(local_port, Config), + URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html", + DocRoot = ?config(doc_root, Config), + {ok, FileInfo} = + file:read_file_info(filename:join([DocRoot,"dummy.html"])), + CreatedSec = + calendar:datetime_to_gregorian_seconds( + FileInfo#file_info.mtime), + + Mod = httpd_util:rfc1123_date( + calendar:gregorian_seconds_to_datetime( + CreatedSec-1)), + + Date = httpd_util:rfc1123_date({date(), time()}), + + {ok, {{_,200,_}, [_ | _], [_ | _]}} = + httpc:request(get, {URL, [{"If-Modified-Since", + Mod}, + {"From","[email protected]"}, + {"Date", Date} + ]}, [], []), + + Mod1 = httpd_util:rfc1123_date( + calendar:gregorian_seconds_to_datetime( + CreatedSec+1)), + + {ok, {{_,200,_}, [_ | _], [_ | _]}} = + httpc:request(get, {URL, [{"If-UnModified-Since", + Mod1} + ]}, [], []), + + Tag = httpd_util:create_etag(FileInfo), + + + {ok, {{_,200,_}, [_ | _], [_ | _]}} = + httpc:request(get, {URL, [{"If-Match", + Tag} + ]}, [], []), + + {ok, {{_,200,_}, [_ | _], _}} = + httpc:request(get, {URL, [{"If-None-Match", + "NotEtag,NeihterEtag"}, + {"Connection", "Close"} + ]}, [], []), + ok; + _ -> + skip("Failed to start local http-server") + end. + +%%------------------------------------------------------------------------- +http_headers_dummy(doc) -> + ["Test the code for handling headers we do not want/can send " + "to a real server. Note it is not logical to send" + "all of these headers together, we only want to test that" + "the code for handling headers will not crash."]; +http_headers_dummy(suite) -> + []; +http_headers_dummy(Config) when is_list(Config) -> + ok = httpc:set_options([{ipfamily, inet}]), + {DummyServerPid, Port} = dummy_server(ipv4), + + URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy_headers.html", + + Foo = http_chunk:encode("foobar") ++ + binary_to_list(http_chunk:encode_last()), + FooBar = Foo ++ "\r\n\r\nOther:inets_test\r\n\r\n", + + UserPasswd = base64:encode_to_string("Alladin:Sesame"), + Auth = "Basic " ++ UserPasswd, + + %% The dummy server will ignore the headers, we only want to test + %% that the client header-handling code. This would not + %% be a vaild http-request! + {ok, {{_,200,_}, [_ | _], [_|_]}} = + httpc:request(post, + {URL, + [{"Via", + "1.0 fred, 1.1 nowhere.com (Apache/1.1)"}, + {"Warning","1#pseudonym foobar"}, + {"Vary","*"}, + {"Upgrade","HTTP/2.0"}, + {"Pragma", "1#no-cache"}, + {"Cache-Control", "no-cache"}, + {"Connection", "close"}, + {"Date", "Sat, 29 Oct 1994 19:43:31 GMT"}, + {"Accept", " text/plain; q=0.5, text/html"}, + {"Accept-Language", "en"}, + {"Accept-Encoding","chunked"}, + {"Accept-Charset", "ISO8859-1"}, + {"Authorization", Auth}, + {"Expect", "1#100-continue"}, + {"User-Agent","inets"}, + {"Transfer-Encoding","chunked"}, + {"Range", " bytes=0-499"}, + {"If-Range", "Sat, 29 Oct 1994 19:43:31 GMT"}, + {"If-Match", "*"}, + {"Content-Type", "text/plain"}, + {"Content-Encoding", "chunked"}, + {"Content-Length", "6"}, + {"Content-Language", "en"}, + {"Content-Location", "http://www.foobar.se"}, + {"Content-MD5", + "104528739076276072743283077410617235478"}, + {"Content-Range", "bytes 0-499/1234"}, + {"Allow", "GET"}, + {"Proxy-Authorization", Auth}, + {"Expires", "Sat, 29 Oct 1994 19:43:31 GMT"}, + {"Upgrade", "HTTP/2.0"}, + {"Last-Modified", "Sat, 29 Oct 1994 19:43:31 GMT"}, + {"Trailer","1#User-Agent"} + ], "text/plain", FooBar}, + [], []), + DummyServerPid ! stop, + ok = httpc:set_options([{ipfamily, inet6fb4}]), + ok. + + +%%------------------------------------------------------------------------- +http_bad_response(doc) -> + ["Test what happens when the server does not follow the protocol"]; +http_bad_response(suite) -> + []; +http_bad_response(Config) when is_list(Config) -> + ok = httpc:set_options([{ipfamily, inet}]), + {DummyServerPid, Port} = dummy_server(ipv4), + + URL = ?URL_START ++ integer_to_list(Port) ++ "/missing_crlf.html", + + URL1 = ?URL_START ++ integer_to_list(Port) ++ "/wrong_statusline.html", + + {error, timeout} = httpc:request(get, {URL, []}, [{timeout, 400}], []), + + {error, Reason} = httpc:request(URL1), + + test_server:format("Wrong Statusline: ~p~n", [Reason]), + + DummyServerPid ! stop, + ok = httpc:set_options([{ipfamily, inet6fb4}]), + ok. + + +%%------------------------------------------------------------------------- +ssl_head(doc) -> + ["Same as http_head/1 but over ssl sockets."]; +ssl_head(suite) -> + []; +ssl_head(Config) when is_list(Config) -> + ssl_head(ssl, Config). + +essl_head(doc) -> + ["Same as http_head/1 but over ssl sockets."]; +essl_head(suite) -> + []; +essl_head(Config) when is_list(Config) -> + ssl_head(essl, Config). + +ssl_head(SslTag, Config) -> + tsp("ssl_head -> entry with" + "~n SslTag: ~p" + "~n Config: ~p", [SslTag, Config]), + case ?config(local_ssl_server, Config) of + ok -> + DataDir = ?config(data_dir, Config), + Port = ?config(local_ssl_port, Config), + URL = ?SSL_URL_START ++ integer_to_list(Port) ++ "/dummy.html", + CertFile = filename:join(DataDir, "ssl_client_cert.pem"), + SSLOptions = [{certfile, CertFile}, {keyfile, CertFile}], + SSLConfig = + case SslTag of + ssl -> + SSLOptions; + essl -> + {essl, SSLOptions} + end, + tsp("ssl_head -> make request using: " + "~n URL: ~p" + "~n SslTag: ~p" + "~n SSLOptions: ~p", [URL, SslTag, SSLOptions]), + {ok, {{_,200, _}, [_ | _], []}} = + httpc:request(head, {URL, []}, [{ssl, SSLConfig}], []); + {ok, _} -> + skip("local http-server not started"); + _ -> + skip("SSL not started") + end. + + +%%------------------------------------------------------------------------- +ssl_get(doc) -> + ["Same as http_get/1 but over ssl sockets."]; +ssl_get(suite) -> + []; +ssl_get(Config) when is_list(Config) -> + ssl_get(ssl, Config). + +essl_get(doc) -> + ["Same as http_get/1 but over ssl sockets."]; +essl_get(suite) -> + []; +essl_get(Config) when is_list(Config) -> + ssl_get(essl, Config). + +ssl_get(SslTag, Config) when is_list(Config) -> + case ?config(local_ssl_server, Config) of + ok -> + DataDir = ?config(data_dir, Config), + Port = ?config(local_ssl_port, Config), + URL = ?SSL_URL_START ++ integer_to_list(Port) ++ "/dummy.html", + CertFile = filename:join(DataDir, "ssl_client_cert.pem"), + SSLOptions = [{certfile, CertFile}, {keyfile, CertFile}], + SSLConfig = + case SslTag of + ssl -> + SSLOptions; + essl -> + {essl, SSLOptions} + end, + tsp("ssl_get -> make request using: " + "~n URL: ~p" + "~n SslTag: ~p" + "~n SSLOptions: ~p", [URL, SslTag, SSLOptions]), + case httpc:request(get, {URL, []}, [{ssl, SSLConfig}], []) of + {ok, {{_,200, _}, [_ | _], Body = [_ | _]}} -> + inets_test_lib:check_body(Body), + ok; + {ok, {StatusLine, Headers, _Body}} -> + tsp("ssl_get -> unexpected result: " + "~n StatusLine: ~p" + "~n Headers: ~p", [StatusLine, Headers]), + tsf({unexpected_response, StatusLine, Headers}); + {error, Reason} -> + tsp("ssl_get -> request failed: " + "~n Reason: ~p", [Reason]), + tsf({request_failed, Reason}) + end; + {ok, _} -> + skip("local http-server not started"); + _ -> + skip("SSL not started") + end. + + +%%------------------------------------------------------------------------- +ssl_trace(doc) -> + ["Same as http_trace/1 but over ssl sockets."]; +ssl_trace(suite) -> + []; +ssl_trace(Config) when is_list(Config) -> + ssl_trace(ssl, Config). + +essl_trace(doc) -> + ["Same as http_trace/1 but over ssl sockets."]; +essl_trace(suite) -> + []; +essl_trace(Config) when is_list(Config) -> + ssl_trace(essl, Config). + +ssl_trace(SslTag, Config) when is_list(Config) -> + case ?config(local_ssl_server, Config) of + ok -> + DataDir = ?config(data_dir, Config), + Port = ?config(local_ssl_port, Config), + URL = ?SSL_URL_START ++ integer_to_list(Port) ++ "/dummy.html", + CertFile = filename:join(DataDir, "ssl_client_cert.pem"), + SSLOptions = [{certfile, CertFile}, {keyfile, CertFile}], + SSLConfig = + case SslTag of + ssl -> + SSLOptions; + essl -> + {essl, SSLOptions} + end, + tsp("ssl_trace -> make request using: " + "~n URL: ~p" + "~n SslTag: ~p" + "~n SSLOptions: ~p", [URL, SslTag, SSLOptions]), + case httpc:request(trace, {URL, []}, [{ssl, SSLConfig}], []) of + {ok, {{_,200, _}, [_ | _], "TRACE /dummy.html" ++ _}} -> + ok; + {ok, {{_,200,_}, [_ | _], WrongBody}} -> + tsf({wrong_body, WrongBody}); + {ok, WrongReply} -> + tsf({wrong_reply, WrongReply}); + Error -> + tsf({failed, Error}) + end; + {ok, _} -> + skip("local http-server not started"); + _ -> + skip("SSL not started") + end. + + +%%------------------------------------------------------------------------- +http_redirect(doc) -> + ["Test redirect with dummy server as httpd does not implement" + " server redirect"]; +http_redirect(suite) -> + []; +http_redirect(Config) when is_list(Config) -> + tsp("http_redirect -> entry with" + "~n Config: ~p", [Config]), + case ?config(local_server, Config) of + ok -> + %% tsp("http_redirect -> set ipfamily option to inet"), + %% ok = httpc:set_options([{ipfamily, inet}]), + + tsp("http_redirect -> start dummy server inet"), + {DummyServerPid, Port} = dummy_server(ipv4), + tsp("http_redirect -> server port = ~p", [Port]), + + URL300 = ?URL_START ++ integer_to_list(Port) ++ "/300.html", + + tsp("http_redirect -> issue request 1: " + "~n ~p", [URL300]), + {ok, {{_,200,_}, [_ | _], [_|_]}} + = httpc:request(get, {URL300, []}, [], []), + + tsp("http_redirect -> issue request 2: " + "~n ~p", [URL300]), + {ok, {{_,300,_}, [_ | _], _}} = + httpc:request(get, {URL300, []}, [{autoredirect, false}], []), + + URL301 = ?URL_START ++ integer_to_list(Port) ++ "/301.html", + + tsp("http_redirect -> issue request 3: " + "~n ~p", [URL301]), + {ok, {{_,200,_}, [_ | _], [_|_]}} + = httpc:request(get, {URL301, []}, [], []), + + tsp("http_redirect -> issue request 4: " + "~n ~p", [URL301]), + {ok, {{_,200,_}, [_ | _], []}} + = httpc:request(head, {URL301, []}, [], []), + + tsp("http_redirect -> issue request 5: " + "~n ~p", [URL301]), + {ok, {{_,301,_}, [_ | _], [_|_]}} + = httpc:request(post, {URL301, [],"text/plain", "foobar"}, + [], []), + + URL302 = ?URL_START ++ integer_to_list(Port) ++ "/302.html", + + tsp("http_redirect -> issue request 6: " + "~n ~p", [URL302]), + {ok, {{_,200,_}, [_ | _], [_|_]}} + = httpc:request(get, {URL302, []}, [], []), + case httpc:request(get, {URL302, []}, [], []) of + {ok, Reply7} -> + case Reply7 of + {{_,200,_}, [_ | _], [_|_]} -> + tsp("http_redirect -> " + "expected reply for request 7"), + ok; + {StatusLine, Headers, Body} -> + tsp("http_redirect -> " + "unexpected reply for request 7: " + "~n StatusLine: ~p" + "~n Headers: ~p" + "~n Body: ~p", + [StatusLine, Headers, Body]), + tsf({unexpected_reply, Reply7}) + end; + Error7 -> + tsp("http_redirect -> " + "unexpected result for request 7: " + "~n Error7: ~p", + [Error7]), + tsf({unexpected_result, Error7}) + end, + + tsp("http_redirect -> issue request 7: " + "~n ~p", [URL302]), + {ok, {{_,200,_}, [_ | _], []}} + = httpc:request(head, {URL302, []}, [], []), + + tsp("http_redirect -> issue request 8: " + "~n ~p", [URL302]), + {ok, {{_,302,_}, [_ | _], [_|_]}} + = httpc:request(post, {URL302, [],"text/plain", "foobar"}, + [], []), + + URL303 = ?URL_START ++ integer_to_list(Port) ++ "/303.html", + + tsp("http_redirect -> issue request 9: " + "~n ~p", [URL303]), + {ok, {{_,200,_}, [_ | _], [_|_]}} + = httpc:request(get, {URL303, []}, [], []), + + tsp("http_redirect -> issue request 10: " + "~n ~p", [URL303]), + {ok, {{_,200,_}, [_ | _], []}} + = httpc:request(head, {URL303, []}, [], []), + + tsp("http_redirect -> issue request 11: " + "~n ~p", [URL303]), + {ok, {{_,200,_}, [_ | _], [_|_]}} + = httpc:request(post, {URL303, [],"text/plain", "foobar"}, + [], []), + + URL307 = ?URL_START ++ integer_to_list(Port) ++ "/307.html", + + tsp("http_redirect -> issue request 12: " + "~n ~p", [URL307]), + {ok, {{_,200,_}, [_ | _], [_|_]}} + = httpc:request(get, {URL307, []}, [], []), + + tsp("http_redirect -> issue request 13: " + "~n ~p", [URL307]), + {ok, {{_,200,_}, [_ | _], []}} + = httpc:request(head, {URL307, []}, [], []), + + tsp("http_redirect -> issue request 14: " + "~n ~p", [URL307]), + {ok, {{_,307,_}, [_ | _], [_|_]}} + = httpc:request(post, {URL307, [],"text/plain", "foobar"}, + [], []), + + tsp("http_redirect -> stop dummy server"), + DummyServerPid ! stop, + tsp("http_redirect -> reset ipfamily option (to inet6fb4)"), + ok = httpc:set_options([{ipfamily, inet6fb4}]), + tsp("http_redirect -> done"), + ok; + + _ -> + skip("Failed to start local http-server") + end. + + + +%%------------------------------------------------------------------------- +http_redirect_loop(doc) -> + ["Test redirect loop detection"]; +http_redirect_loop(suite) -> + []; +http_redirect_loop(Config) when is_list(Config) -> + ok = httpc:set_options([{ipfamily, inet}]), + {DummyServerPid, Port} = dummy_server(ipv4), + + URL = ?URL_START ++ integer_to_list(Port) ++ "/redirectloop.html", + + {ok, {{_,300,_}, [_ | _], _}} + = httpc:request(get, {URL, []}, [], []), + DummyServerPid ! stop, + ok = httpc:set_options([{ipfamily, inet6fb4}]), + ok. + +%%------------------------------------------------------------------------- +http_internal_server_error(doc) -> + ["Test 50X codes"]; +http_internal_server_error(suite) -> + []; +http_internal_server_error(Config) when is_list(Config) -> + ok = httpc:set_options([{ipfamily, inet}]), + {DummyServerPid, Port} = dummy_server(ipv4), + + URL500 = ?URL_START ++ integer_to_list(Port) ++ "/500.html", + + {ok, {{_,500,_}, [_ | _], _}} + = httpc:request(get, {URL500, []}, [], []), + + + URL503 = ?URL_START ++ integer_to_list(Port) ++ "/503.html", + + %% Used to be able to make the service available after retry. + ets:new(unavailable, [named_table, public, set]), + ets:insert(unavailable, {503, unavailable}), + + {ok, {{_,200, _}, [_ | _], [_|_]}} = + httpc:request(get, {URL503, []}, [], []), + + ets:insert(unavailable, {503, long_unavailable}), + + {ok, {{_,503, _}, [_ | _], [_|_]}} = + httpc:request(get, {URL503, []}, [], []), + + ets:delete(unavailable), + DummyServerPid ! stop, + ok = httpc:set_options([{ipfamily, inet6fb4}]), + ok. + + +%%------------------------------------------------------------------------- +http_userinfo(doc) -> + ["Test user info e.i. http://user:passwd@host:port/"]; +http_userinfo(suite) -> + []; +http_userinfo(Config) when is_list(Config) -> + ok = httpc:set_options([{ipfamily, inet}]), + + {DummyServerPid, Port} = dummy_server(ipv4), + + URLAuth = "http://alladin:sesame@localhost:" + ++ integer_to_list(Port) ++ "/userinfo.html", + + {ok, {{_,200,_}, [_ | _], _}} + = httpc:request(get, {URLAuth, []}, [], []), + + URLUnAuth = "http://alladin:foobar@localhost:" + ++ integer_to_list(Port) ++ "/userinfo.html", + + {ok, {{_,401, _}, [_ | _], _}} = + httpc:request(get, {URLUnAuth, []}, [], []), + + DummyServerPid ! stop, + ok = httpc:set_options([{ipfamily, inet6fb4}]), + ok. + + +%%------------------------------------------------------------------------- +http_cookie(doc) -> + ["Test cookies."]; +http_cookie(suite) -> + []; +http_cookie(Config) when is_list(Config) -> + ok = httpc:set_options([{cookies, enabled}, {ipfamily, inet}]), + {DummyServerPid, Port} = dummy_server(ipv4), + + URLStart = ?URL_START + ++ integer_to_list(Port), + + URLCookie = URLStart ++ "/cookie.html", + + {ok, {{_,200,_}, [_ | _], [_|_]}} + = httpc:request(get, {URLCookie, []}, [], []), + + ets:new(cookie, [named_table, public, set]), + ets:insert(cookie, {cookies, true}), + + {ok, {{_,200,_}, [_ | _], [_|_]}} + = httpc:request(get, {URLStart ++ "/", []}, [], []), + + ets:delete(cookie), + + ok = httpc:set_options([{cookies, disabled}]), + DummyServerPid ! stop, + ok = httpc:set_options([{ipfamily, inet6fb4}]), + ok. + +%%------------------------------------------------------------------------- +http_server_does_not_exist(doc) -> + ["Test that we get an error message back when the server " + "does note exist."]; +http_server_does_not_exist(suite) -> + []; +http_server_does_not_exist(Config) when is_list(Config) -> + {error, _} = + httpc:request(get, {"http://localhost:" ++ + integer_to_list(?NOT_IN_USE_PORT) + ++ "/", []},[], []), + ok. + + +%%------------------------------------------------------------------------- +page_does_not_exist(doc) -> + ["Test that we get a 404 when the page is not found."]; +page_does_not_exist(suite) -> + []; +page_does_not_exist(Config) when is_list(Config) -> + Port = ?config(local_port, Config), + URL = ?URL_START ++ integer_to_list(Port) ++ "/doesnotexist.html", + {ok, {{_,404,_}, [_ | _], [_ | _]}} + = httpc:request(get, {URL, []}, [], []), + ok. + + +%%------------------------------------------------------------------------- + +http_stream(doc) -> + ["Test the option stream for asynchrony requests"]; +http_stream(suite) -> + []; +http_stream(Config) when is_list(Config) -> + Port = ?config(local_port, Config), + URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html", + {ok, {{_,200,_}, [_ | _], Body}} = + httpc:request(get, {URL, []}, [], []), + + {ok, RequestId} = + httpc:request(get, {URL, []}, [], [{sync, false}, + {stream, self}]), + + receive + {http, {RequestId, stream_start, _Headers}} -> + ok; + {http, Msg} -> + tsf(Msg) + end, + + StreamedBody = receive_streamed_body(RequestId, <<>>), + + Body == binary_to_list(StreamedBody). + + +%%------------------------------------------------------------------------- + +http_stream_once(doc) -> + ["Test the option stream for asynchrony requests"]; +http_stream_once(suite) -> + []; +http_stream_once(Config) when is_list(Config) -> + p("http_stream_once -> entry with" + "~n Config: ~p", [Config]), + + p("http_stream_once -> set ipfamily to inet", []), + ok = httpc:set_options([{ipfamily, inet}]), + p("http_stream_once -> start dummy server", []), + {DummyServerPid, Port} = dummy_server(ipv4), + + PortStr = integer_to_list(Port), + p("http_stream_once -> once", []), + once(?URL_START ++ PortStr ++ "/once.html"), + p("http_stream_once -> once_chunked", []), + once(?URL_START ++ PortStr ++ "/once_chunked.html"), + p("http_stream_once -> dummy", []), + once(?URL_START ++ PortStr ++ "/dummy.html"), + + p("http_stream_once -> stop dummy server", []), + DummyServerPid ! stop, + p("http_stream_once -> set ipfamily to inet6fb4", []), + ok = httpc:set_options([{ipfamily, inet6fb4}]), + p("http_stream_once -> done", []), + ok. + +once(URL) -> + p("once -> issue sync request for ~p", [URL]), + {ok, {{_,200,_}, [_ | _], Body}} = + httpc:request(get, {URL, []}, [], []), + + p("once -> issue async (self stream) request for ~p", [URL]), + {ok, RequestId} = + httpc:request(get, {URL, []}, [], [{sync, false}, + {stream, {self, once}}]), + + p("once -> await stream_start reply for (async) request ~p", [RequestId]), + NewPid = + receive + {http, {RequestId, stream_start, _Headers, Pid}} -> + p("once -> received stream_start reply for (async) request ~p: ~p", + [RequestId, Pid]), + Pid; + {http, Msg} -> + tsf(Msg) + end, + + tsp("once -> request handler: ~p", [NewPid]), + + p("once -> await stream reply for (async) request ~p", [RequestId]), + BodyPart = + receive + {http, {RequestId, stream, BinBodyPart}} -> + p("once -> received stream reply for (async) request ~p: " + "~n~p", [RequestId, binary_to_list(BinBodyPart)]), + BinBodyPart + end, + + tsp("once -> first body part '~p' received", [binary_to_list(BodyPart)]), + + StreamedBody = receive_streamed_body(RequestId, BinBodyPart, NewPid), + + Body = binary_to_list(StreamedBody), + + p("once -> done when Bode: ~p", [Body]), + ok. + + +%%------------------------------------------------------------------------- +parse_url(doc) -> + ["Test that an url is parsed correctly"]; +parse_url(suite) -> + []; +parse_url(Config) when is_list(Config) -> + %% ipv6 + {ok, {http,[],"2010:836B:4179::836B:4179",80,"/foobar.html",[]}} = + http_uri:parse("http://[2010:836B:4179::836B:4179]/foobar.html"), + {ok, {http,[],"[2010:836B:4179::836B:4179]",80,"/foobar.html",[]}} = + http_uri:parse("http://[2010:836B:4179::836B:4179]/foobar.html", + [{ipv6_host_with_brackets, true}]), + {ok, {http,[],"2010:836B:4179::836B:4179",80,"/foobar.html",[]}} = + http_uri:parse("http://[2010:836B:4179::836B:4179]/foobar.html", + [{ipv6_host_with_brackets, false}]), + {ok, {http,[],"2010:836B:4179::836B:4179",80,"/foobar.html",[]}} = + http_uri:parse("http://[2010:836B:4179::836B:4179]/foobar.html", + [{foo, false}]), + {error, + {malformed_url, _, "http://2010:836B:4179::836B:4179/foobar.html"}} = + http_uri:parse("http://2010:836B:4179::836B:4179/foobar.html"), + + %% ipv4 + {ok, {http,[],"127.0.0.1",80,"/foobar.html",[]}} = + http_uri:parse("http://127.0.0.1/foobar.html"), + + %% host + {ok, {http,[],"localhost",8888,"/foobar.html",[]}} = + http_uri:parse("http://localhost:8888/foobar.html"), + + %% Userinfo + {ok, {http,"nisse:foobar","localhost",8888,"/foobar.html",[]}} = + http_uri:parse("http://nisse:foobar@localhost:8888/foobar.html"), + + %% Scheme error + {error, no_scheme} = http_uri:parse("localhost/foobar.html"), + {error, {malformed_url, _, _}} = + http_uri:parse("localhost:8888/foobar.html"), + + %% Query + {ok, {http,[],"localhost",8888,"/foobar.html","?foo=bar&foobar=42"}} = + http_uri:parse("http://localhost:8888/foobar.html?foo=bar&foobar=42"), + + %% Esc chars + {ok, {http,[],"www.somedomain.com",80,"/%2Eabc",[]}} = + http_uri:parse("http://www.somedomain.com/%2Eabc"), + {ok, {http,[],"www.somedomain.com",80,"/%252Eabc",[]}} = + http_uri:parse("http://www.somedomain.com/%252Eabc"), + {ok, {http,[],"www.somedomain.com",80,"/%25abc",[]}} = + http_uri:parse("http://www.somedomain.com/%25abc"), + {ok, {http,[],"www.somedomain.com",80,"/%25abc", "?foo=bar"}} = + http_uri:parse("http://www.somedomain.com/%25abc?foo=bar"), + + + ok. + + +%%------------------------------------------------------------------------- + +ipv6_ipcomm() -> + [{require, ipv6_hosts}]. +ipv6_ipcomm(doc) -> + ["Test ip_comm ipv6."]; +ipv6_ipcomm(suite) -> + []; +ipv6_ipcomm(Config) when is_list(Config) -> + HTTPOptions = [], + SocketType = ip_comm, + Scheme = "http", + Extra = [], + ipv6(SocketType, Scheme, HTTPOptions, Extra, Config). + + +%%------------------------------------------------------------------------- + +ipv6_essl() -> + [{require, ipv6_hosts}]. +ipv6_essl(doc) -> + ["Test essl ipv6."]; +ipv6_essl(suite) -> + []; +ipv6_essl(Config) when is_list(Config) -> + DataDir = ?config(data_dir, Config), + CertFile = filename:join(DataDir, "ssl_client_cert.pem"), + SSLOptions = [{certfile, CertFile}, {keyfile, CertFile}], + SSLConfig = {essl, SSLOptions}, + tsp("ssl_ipv6 -> make request using: " + "~n SSLOptions: ~p", [SSLOptions]), + HTTPOptions = [{ssl, SSLConfig}], + SocketType = essl, + Scheme = "https", + Extra = SSLOptions, + ipv6(SocketType, Scheme, HTTPOptions, Extra, Config). + + +%%------------------------------------------------------------------------- + +ipv6(SocketType, Scheme, HTTPOptions, Extra, Config) -> + %% Check if we are a IPv6 host + tsp("ipv6 -> verify ipv6 support"), + case inets_test_lib:has_ipv6_support(Config) of + {ok, Addr} -> + tsp("ipv6 -> ipv6 supported: ~p", [Addr]), + {DummyServerPid, Port} = dummy_server(SocketType, ipv6, Extra), + Profile = ?config(profile, Config), + URL = + Scheme ++ + "://[" ++ http_transport:ipv6_name(Addr) ++ "]:" ++ + integer_to_list(Port) ++ "/foobar.html", + tsp("ipv6 -> issue request with: " + "~n URL: ~p" + "~n HTTPOptions: ~p", [URL, HTTPOptions]), + case httpc:request(get, {URL, []}, HTTPOptions, [], Profile) of + {ok, {{_,200,_}, [_ | _], [_|_]}} -> + tsp("ipv6 -> expected result"), + DummyServerPid ! stop, + ok; + {ok, Unexpected} -> + tsp("ipv6 -> unexpected result: " + "~n ~p", [Unexpected]), + DummyServerPid ! stop, + tsf({unexpected_result, Unexpected}); + {error, Reason} -> + tsp("ipv6 -> error: " + "~n Reason: ~p", [Reason]), + DummyServerPid ! stop, + tsf(Reason) + end, + ok; + _ -> + tsp("ipv6 -> ipv6 not supported"), + skip("Host does not support IPv6") + end. + + +%%------------------------------------------------------------------------- + +headers_as_is(doc) -> + ["Test the option headers_as_is"]; +headers_as_is(suite) -> + []; +headers_as_is(Config) when is_list(Config) -> + Port = ?config(local_port, Config), + URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html", + {ok, {{_,200,_}, [_|_], [_|_]}} = + httpc:request(get, {URL, [{"Host", "localhost"},{"Te", ""}]}, + [], [{headers_as_is, true}]), + + {ok, {{_,400,_}, [_|_], [_|_]}} = + httpc:request(get, {URL, [{"Te", ""}]},[], [{headers_as_is, true}]), + ok. + + +%%------------------------------------------------------------------------- + +selecting_session(doc) -> + ["Test selection of sessions - OTP-9847"]; +selecting_session(suite) -> + []; +selecting_session(Config) when is_list(Config) -> + tsp("selecting_session -> entry with" + "~n Config: ~p", [Config]), + + tsp("selecting_session -> set ipfamily to inet"), + ok = httpc:set_options([{ipfamily, inet}]), + + tsp("selecting_session -> start server"), + {ServerPid, Port} = otp_9847_server(), + + PortStr = integer_to_list(Port), + URL = ?URL_START ++ PortStr ++ "/index.html", + + tsp("selecting_session -> issue the first batch (three) requests"), + lists:foreach(fun(P) -> + tsp("selecting_session:fun1 -> " + "send stop request to ~p", [P]), + P ! stop + end, + reqs(URL, ServerPid, 3, 3, false)), + tsp("selecting_session -> sleep some (1) to make sure nothing lingers"), + ?SLEEP(5000), + tsp("selecting_session -> " + "instruct the server to reply to the first request"), + ServerPid ! {answer, true}, + receive + {answer, true} -> + tsp("selecting_session -> " + "received ack from server to reply to the first request"), + ok + end, + tsp("selecting_session -> issue the second batch (four) requests"), + lists:foreach(fun(P) -> + tsp("selecting_session:fun2 -> " + "send stop request to ~p", [P]), + P ! stop + end, + reqs(URL, ServerPid, 4, 1, true)), + tsp("selecting_session -> sleep some (2) to make sure nothing lingers"), + ?SLEEP(5000), + + tsp("selecting_session -> stop server"), + ServerPid ! stop, + tsp("selecting_session -> set ipfamily (back) to inet6fb4"), + ok = httpc:set_options([{ipfamily, inet6fb4}]), + tsp("selecting_session -> done"), + ok. + +reqs(URL, ServerPid, NumReqs, NumHandlers, InitialSync) -> + tsp("reqs -> entry with" + "~n URL: ~p" + "~n ServerPid: ~w" + "~n NumReqs: ~w" + "~n NumHandlers: ~w" + "~n InitialSync: ~w", + [URL, ServerPid, NumReqs, NumHandlers, InitialSync]), + Handlers = reqs2(URL, NumReqs, [], InitialSync), + tsp("reqs -> " + "~n Handlers: ~w", [Handlers]), + case length(Handlers) of + NumHandlers -> + tsp("reqs -> " + "~n NumHandlers: ~w", [NumHandlers]), + ServerPid ! num_handlers, + receive + {num_handlers, NumHandlers} -> + tsp("reqs -> received num_handlers with" + "~n NumHandlers: ~w", [NumHandlers]), + Handlers; + {num_handlers, WrongNumHandlers} -> + tsp("reqs -> received num_handlers with" + "~n WrongNumHandlers: ~w", [WrongNumHandlers]), + exit({wrong_num_handlers1, WrongNumHandlers, NumHandlers}) + end; + WrongNumHandlers -> + tsp("reqs -> " + "~n WrongNumHandlers: ~w", [WrongNumHandlers]), + exit({wrong_num_handlers2, WrongNumHandlers, NumHandlers}) + end. + + +reqs2(_URL, 0, Acc, _Sync) -> + lists:reverse(Acc); +reqs2(URL, Num, Acc, Sync) -> + tsp("reqs2 -> entry with" + "~n Num: ~w" + "~n Sync: ~w", [Num, Sync]), + case httpc:request(get, {URL, []}, [], [{sync, Sync}]) of + {ok, _Reply} -> + tsp("reqs2 -> successful request: ~p", [_Reply]), + receive + {handler, Handler, _Manager} -> + %% This is when a new handler is created + tsp("reqs2 -> received handler: ~p", [Handler]), + case lists:member(Handler, Acc) of + true -> + tsp("reqs2 -> duplicate handler"), + exit({duplicate_handler, Handler, Num, Acc}); + false -> + tsp("reqs2 -> wait for data ack"), + receive + {data_received, Handler} -> + tsp("reqs2 -> " + "received data ack from ~p", [Handler]), + case Sync of + true -> + reqs2(URL, Num-1, [Handler|Acc], + false); + false -> + reqs2(URL, Num-1, [Handler|Acc], + Sync) + end + end + end; + + {data_received, Handler} -> + tsp("reqs2 -> " + "received data ack from ~p", [Handler]), + reqs2(URL, Num-1, Acc, false) + + end; + + {error, Reason} -> + tsp("reqs2 -> request ~w failed: ~p", [Num, Reason]), + exit({request_failed, Reason, Num, Acc}) + end. + +otp_9847_server() -> + TC = self(), + Pid = spawn_link(fun() -> otp_9847_server_init(TC) end), + receive + {port, Port} -> + {Pid, Port} + end. + +otp_9847_server_init(TC) -> + tsp("otp_9847_server_init -> entry with" + "~n TC: ~p", [TC]), + {ok, ListenSocket} = + gen_tcp:listen(0, [binary, inet, {packet, 0}, + {reuseaddr,true}, + {active, false}]), + tsp("otp_9847_server_init -> listen socket created: " + "~n ListenSocket: ~p", [ListenSocket]), + {ok, Port} = inet:port(ListenSocket), + tsp("otp_9847_server_init -> Port: ~p", [Port]), + TC ! {port, Port}, + otp_9847_server_main(TC, ListenSocket, false, []). + +otp_9847_server_main(TC, ListenSocket, Answer, Handlers) -> + tsp("otp_9847_server_main -> entry with" + "~n TC: ~p" + "~n ListenSocket: ~p" + "~n Answer: ~p" + "~n Handlers: ~p", [TC, ListenSocket, Answer, Handlers]), + case gen_tcp:accept(ListenSocket, 1000) of + {ok, Sock} -> + tsp("otp_9847_server_main -> accepted" + "~n Sock: ~p", [Sock]), + {Handler, Mon, Port} = otp_9847_handler(TC, Sock, Answer), + tsp("otp_9847_server_main -> handler ~p created for ~w", + [Handler, Port]), + gen_tcp:controlling_process(Sock, Handler), + tsp("otp_9847_server_main -> control transfer"), + Handler ! owner, + tsp("otp_9847_server_main -> " + "handler ~p informed of owner transfer", [Handler]), + TC ! {handler, Handler, self()}, + tsp("otp_9847_server_main -> " + "TC ~p informed of handler ~p", [TC, Handler]), + otp_9847_server_main(TC, ListenSocket, Answer, + [{Handler, Mon, Sock, Port}|Handlers]); + + {error, timeout} -> + tsp("otp_9847_server_main -> timeout"), + receive + {answer, true} -> + tsp("otp_9847_server_main -> received answer request"), + TC ! {answer, true}, + otp_9847_server_main(TC, ListenSocket, true, Handlers); + + {'DOWN', _Mon, process, Pid, _Reason} -> + %% Could be one of the handlers + tsp("otp_9847_server_main -> received DOWN for ~p", [Pid]), + otp_9847_server_main(TC, ListenSocket, Answer, + lists:keydelete(Pid, 1, Handlers)); + + num_handlers -> + tsp("otp_9847_server_main -> " + "received request for number of handlers (~w)", + [length(Handlers)]), + TC ! {num_handlers, length(Handlers)}, + otp_9847_server_main(TC, ListenSocket, Answer, Handlers); + + stop -> + tsp("otp_9847_server_main -> received stop request"), + %% Stop all handlers (just in case) + Pids = [Handler || {Handler, _, _} <- Handlers], + lists:foreach(fun(Pid) -> Pid ! stop end, Pids), + exit(normal); + + Any -> + tsp("otp_9847_server_main -> received" + "~n Any: ~p", [Any]), + exit({crap, Any}) + + after 0 -> + tsp("otp_9847_server_main -> nothing in queue"), + otp_9847_server_main(TC, ListenSocket, Answer, Handlers) + end; + + Error -> + exit(Error) + end. + + +otp_9847_handler(TC, Sock, Answer) -> + tsp("otp_9847_handler -> entry with" + "~n TC: ~p" + "~n Sock: ~p" + "~n Answer: ~p", [TC, Sock, Answer]), + Self = self(), + {Pid, Mon} = + spawn_opt(fun() -> + otp_9847_handler_init(TC, Self, Sock, Answer) + end, + [monitor]), + receive + {port, Port} -> + tsp("otp_9847_handler -> received port message (from ~p)" + "~n Port: ~p", [Pid, Port]), + {Pid, Mon, Port} + end. + + +otp_9847_handler_init(TC, Server, Sock, Answer) -> + tsp("otp_9847_handler_init -> entry with" + "~n TC: ~p" + "~n Server: ~p" + "~n Sock: ~p" + "~n Answer: ~p", [TC, Server, Sock, Answer]), + {ok, Port} = inet:port(Sock), + Server ! {port, Port}, + receive + owner -> + tsp("otp_9847_handler_init -> " + "received owner message - activate socket"), + inet:setopts(Sock, [{active, true}]), + otp_9847_handler_main(TC, Server, Sock, Answer, [?HTTP_MAX_HEADER_SIZE]) + end. + +otp_9847_handler_main(TC, Server, Sock, Answer, ParseArgs) -> + tsp("otp_9847_handler_main -> entry with" + "~n TC: ~p" + "~n Server: ~p" + "~n Sock: ~p" + "~n Answer: ~p" + "~n ParseArgs: ~p", [TC, Server, Sock, Answer, ParseArgs]), + receive + stop -> + tsp("otp_9847_handler_main -> received stop request"), + exit(normal); + + {tcp, Sock, _Data} when Answer =:= false -> + tsp("otp_9847_handler_main -> received tcp data - no answer"), + TC ! {data_received, self()}, + inet:setopts(Sock, [{active, true}]), + %% Ignore all data + otp_9847_handler_main(TC, Server, Sock, Answer, ParseArgs); + + {tcp, Sock, Data} when Answer =:= true -> + tsp("otp_9847_handler_main -> received tcp data - answer"), + TC ! {data_received, self()}, + inet:setopts(Sock, [{active, true}]), + NewParseArgs = otp_9847_handler_request(Sock, [Data|ParseArgs]), + otp_9847_handler_main(TC, Server, Sock, Answer, NewParseArgs); + + {tcp_closed, Sock} -> + tsp("otp_9847_handler_main -> received tcp socket closed"), + exit(normal); + + {tcp_error, Sock, Reason} -> + tsp("otp_9847_handler_main -> socket error: ~p", [Reason]), + (catch gen_tcp:close(Sock)), + exit(normal) + + %% after 30000 -> + %% gen_tcp:close(Sock), + %% exit(normal) + end. + +otp_9847_handler_request(Sock, Args) -> + Msg = + case httpd_request:parse(Args) of + {ok, {_, "/index.html" = _RelUrl, _, _, _}} -> + B = + "<HTML><BODY>" ++ + "...some body part..." ++ + "</BODY></HTML>", + Len = integer_to_list(length(B)), + "HTTP/1.1 200 ok\r\n" ++ + "Content-Length:" ++ Len ++ "\r\n\r\n" ++ B + end, + gen_tcp:send(Sock, Msg), + [?HTTP_MAX_HEADER_SIZE]. + + + +%%------------------------------------------------------------------------- + +options(doc) -> + ["Test the option parameters."]; +options(suite) -> + []; +options(Config) when is_list(Config) -> + case ?config(local_server, Config) of + ok -> + Port = ?config(local_port, Config), + URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html", + {ok, {{_,200,_}, [_ | _], Bin}} + = httpc:request(get, {URL, []}, [{foo, bar}], + %% Ignore unknown options + [{body_format, binary}, {foo, bar}]), + + true = is_binary(Bin), + {ok, {200, [_|_]}} + = httpc:request(get, {URL, []}, [{timeout, infinity}], + [{full_result, false}]); + _ -> + skip("Failed to start local http-server") + end. + + +%%------------------------------------------------------------------------- + +http_invalid_http(doc) -> + ["Test parse error"]; +http_invalid_http(suite) -> + []; +http_invalid_http(Config) when is_list(Config) -> + ok = httpc:set_options([{ipfamily, inet}]), + {DummyServerPid, Port} = dummy_server(ipv4), + + URL = ?URL_START ++ integer_to_list(Port) ++ "/invalid_http.html", + + {error, {could_not_parse_as_http, _} = Reason} = + httpc:request(get, {URL, []}, [], []), + + test_server:format("Parse error: ~p ~n", [Reason]), + DummyServerPid ! stop, + ok = httpc:set_options([{ipfamily, inet6fb4}]), + ok. + + +%%------------------------------------------------------------------------- + +-define(GOOGLE, "www.google.com"). + +hexed_query_otp_6191(doc) -> + []; +hexed_query_otp_6191(suite) -> + []; +hexed_query_otp_6191(Config) when is_list(Config) -> + Google = ?GOOGLE, + GoogleSearch = "http://" ++ Google ++ "/search", + Search1 = "?hl=en&q=a%D1%85%D1%83%D0%B9&btnG=Google+Search", + URI1 = GoogleSearch ++ Search1, + Search2 = "?hl=en&q=%25%25", + URI2 = GoogleSearch ++ Search2, + Search3 = "?hl=en&q=%foo", + URI3 = GoogleSearch ++ Search3, + + Verify1 = + fun({http, [], ?GOOGLE, 80, "/search", _}) -> ok; + (_) -> error + end, + Verify2 = Verify1, + Verify3 = Verify1, + verify_uri(URI1, Verify1), + verify_uri(URI2, Verify2), + verify_uri(URI3, Verify3), + ok. + +verify_uri(URI, Verify) -> + case http_uri:parse(URI) of + {ok, ParsedURI} -> + case Verify(ParsedURI) of + ok -> + ok; + error -> + Reason = {unexpected_parse_result, URI, ParsedURI}, + ERROR = {error, Reason}, + throw(ERROR) + end; + {error, _} = ERROR -> + throw(ERROR) + end. + + +%%------------------------------------------------------------------------- + +empty_body_otp_6243(doc) -> + ["An empty body was not returned directly. There was a delay for several" + "seconds."]; +empty_body_otp_6243(suite) -> + []; +empty_body_otp_6243(Config) when is_list(Config) -> + Port = ?config(local_port, Config), + URL = ?URL_START ++ integer_to_list(Port) ++ "/empty.html", + {ok, {{_,200,_}, [_ | _], []}} = + httpc:request(get, {URL, []}, [{timeout, 500}], []). + + +%%------------------------------------------------------------------------- + +transfer_encoding_otp_6807(doc) -> + ["Transfer encoding is case insensitive"]; +transfer_encoding_otp_6807(suite) -> + []; +transfer_encoding_otp_6807(Config) when is_list(Config) -> + ok = httpc:set_options([{ipfamily, inet}]), + {DummyServerPid, Port} = dummy_server(ipv4), + + URL = ?URL_START ++ integer_to_list(Port) ++ + "/capital_transfer_encoding.html", + {ok, {{_,200,_}, [_|_], [_ | _]}} = httpc:request(URL), + DummyServerPid ! stop, + ok = httpc:set_options([{ipfamily, inet6fb4}]), + ok. + + +%%------------------------------------------------------------------------- + +empty_response_header_otp_6830(doc) -> + ["Test the case that the HTTP server does not send any headers"]; +empty_response_header_otp_6830(suite) -> + []; +empty_response_header_otp_6830(Config) when is_list(Config) -> + ok = httpc:set_options([{ipfamily, inet}]), + {DummyServerPid, Port} = dummy_server(ipv4), + + URL = ?URL_START ++ integer_to_list(Port) ++ "/no_headers.html", + {ok, {{_,200,_}, [], [_ | _]}} = httpc:request(URL), + DummyServerPid ! stop, + ok = httpc:set_options([{ipfamily, inet6fb4}]), + ok. + + +%%------------------------------------------------------------------------- + +no_content_204_otp_6982(doc) -> + ["Test the case that the HTTP 204 no content header"]; +no_content_204_otp_6982(suite) -> + []; +no_content_204_otp_6982(Config) when is_list(Config) -> + ok = httpc:set_options([{ipfamily, inet}]), + {DummyServerPid, Port} = dummy_server(ipv4), + + URL = ?URL_START ++ integer_to_list(Port) ++ "/no_content.html", + {ok, {{_,204,_}, [], []}} = httpc:request(URL), + DummyServerPid ! stop, + ok = httpc:set_options([{ipfamily, inet6fb4}]), + ok. + + +%%------------------------------------------------------------------------- + +missing_CR_otp_7304(doc) -> + ["Test the case that the HTTP server uses only LF instead of CRLF" + "as delimitor"]; +missing_CR_otp_7304(suite) -> + []; +missing_CR_otp_7304(Config) when is_list(Config) -> + ok = httpc:set_options([{ipfamily, inet}]), + {DummyServerPid, Port} = dummy_server(ipv4), + + URL = ?URL_START ++ integer_to_list(Port) ++ "/missing_CR.html", + {ok, {{_,200,_}, _, [_ | _]}} = httpc:request(URL), + DummyServerPid ! stop, + ok = httpc:set_options([{ipfamily, inet6fb4}]), + ok. + + +%%------------------------------------------------------------------------- + + +otp_7883_1(doc) -> + ["OTP-7883-sync"]; +otp_7883_1(suite) -> + []; +otp_7883_1(Config) when is_list(Config) -> + ok = httpc:set_options([{ipfamily, inet}]), + + {DummyServerPid, Port} = dummy_server(ipv4), + + URL = ?URL_START ++ integer_to_list(Port) ++ "/just_close.html", + {error, socket_closed_remotely} = httpc:request(URL), + DummyServerPid ! stop, + + ok = httpc:set_options([{ipfamily, inet6fb4}]), + ok. + +otp_7883_2(doc) -> + ["OTP-7883-async"]; +otp_7883_2(suite) -> + []; +otp_7883_2(Config) when is_list(Config) -> + ok = httpc:set_options([{ipfamily, inet}]), + + {DummyServerPid, Port} = dummy_server(ipv4), + + URL = ?URL_START ++ integer_to_list(Port) ++ "/just_close.html", + Method = get, + Request = {URL, []}, + HttpOptions = [], + Options = [{sync, false}], + Profile = httpc:default_profile(), + {ok, RequestId} = + httpc:request(Method, Request, HttpOptions, Options, Profile), + ok = + receive + {http, {RequestId, {error, socket_closed_remotely}}} -> + ok + end, + DummyServerPid ! stop, + + ok = httpc:set_options([{ipfamily, inet6fb4}]), + ok. + + +%%------------------------------------------------------------------------- + + +otp_8154_1(doc) -> + ["OTP-8154"]; +otp_8154_1(suite) -> + []; +otp_8154_1(Config) when is_list(Config) -> + start_inets(), + ReqSeqNumServer = start_sequence_number_server(), + RespSeqNumServer = start_sequence_number_server(), + {ok, Server, Port} = start_slow_server(RespSeqNumServer), + Clients = run_clients(105, Port, ReqSeqNumServer), + %% ok = wait_for_clients(Clients), + ok = wait4clients(Clients, timer:minutes(3)), + Server ! shutdown, + RespSeqNumServer ! shutdown, + ReqSeqNumServer ! shutdown, + ok. + +start_inets() -> + inets:start(), + ok. + + +%% ----------------------------------------------------- +%% A sequence number handler +%% The purpose is to be able to pair requests with responses. + +start_sequence_number_server() -> + proc_lib:spawn(fun() -> loop_sequence_number(1) end). + +loop_sequence_number(N) -> + receive + shutdown -> + ok; + {From, get_next} -> + From ! {next_is, N}, + loop_sequence_number(N + 1) + end. + +get_next_sequence_number(SeqNumServer) -> + SeqNumServer ! {self(), get_next}, + receive {next_is, N} -> N end. + +%% ----------------------------------------------------- +%% Client part +%% Sends requests randomly parallel + +run_clients(NumClients, ServerPort, SeqNumServer) -> + io:format("start clients when" + "~n NumClients: ~w" + "~n ServerPort: ~w" + "~n SeqNumServer: ~w" + "~n", [NumClients, ServerPort, SeqNumServer]), + set_random_seed(), + lists:map( + fun(Id) -> + io:format("starting client ~w~n", [Id]), + Req = f("req~3..0w", [get_next_sequence_number(SeqNumServer)]), + Url = f(?URL_START ++ "~w/~s", [ServerPort, Req]), + Pid = proc_lib:spawn( + fun() -> + io:format("[~w] client started - " + "issue request~n", [Id]), + case httpc:request(Url) of + {ok, {{_,200,_}, _, Resp}} -> + io:format("[~w] 200 response: " + "~p~n", [Id, Resp]), + case lists:prefix(Req++"->", Resp) of + true -> exit(normal); + false -> exit({bad_resp,Req,Resp}) + end; + {ok, {{_,EC,Reason},_,Resp}} -> + io:format("[~w] ~w response: " + "~s~n~s~n", + [Id, EC, Reason, Resp]), + exit({bad_resp,Req,Resp}); + Crap -> + io:format("[~w] bad response: ~p", + [Id, Crap]), + exit({bad_resp, Req, Crap}) + end + end), + MRef = erlang:monitor(process, Pid), + timer:sleep(10 + random:uniform(1334)), + {Id, Pid, MRef} + + end, + lists:seq(1, NumClients)). + +%% wait_for_clients(Clients) -> +%% lists:foreach( +%% fun({Id, Pid, MRef}) -> +%% io:format("waiting for client ~w termination~n", [Id]), +%% receive +%% {'DOWN', MRef, process, Pid, normal} -> +%% io:format("waiting for clients: " +%% "normal exit from ~w (~p)~n", +%% [Id, Pid]), +%% ok; +%% {'DOWN', MRef, process, Pid, Reason} -> +%% io:format("waiting for clients: " +%% "unexpected exit from ~w (~p):" +%% "~n Reason: ~p" +%% "~n", [Id, Pid, Reason]), +%% erlang:error(Reason) +%% end +%% end, +%% Clients). + + +wait4clients([], _Timeout) -> + ok; +wait4clients(Clients, Timeout) when Timeout > 0 -> + io:format("wait4clients -> entry with" + "~n length(Clients): ~w" + "~n Timeout: ~w" + "~n", [length(Clients), Timeout]), + T = t(), + receive + {'DOWN', _MRef, process, Pid, normal} -> + case lists:keysearch(Pid, 2, Clients) of + {value, {Id, _, _}} -> + io:format("receive normal exit message " + "from client ~p (~p)", [Id, Pid]), + NewClients = + lists:keydelete(Id, 1, Clients), + wait4clients(NewClients, + Timeout - (t() - T)); + false -> + io:format("receive normal exit message " + "from unknown process: ~p", [Pid]), + wait4clients(Clients, Timeout - (t() - T)) + end; + + {'DOWN', _MRef, process, Pid, Reason} -> + case lists:keysearch(Pid, 2, Clients) of + {value, {Id, _, _}} -> + io:format("receive bad exit message " + "from client ~p (~p):" + "~n ~p", [Id, Pid, Reason]), + erlang:error({bad_client_termination, Id, Reason}); + false -> + io:format("receive normal exit message " + "from unknown process: ~p", [Pid]), + wait4clients(Clients, Timeout - (t() - T)) + end + + after Timeout -> + erlang:error({client_timeout, Clients}) + end; +wait4clients(Clients, _) -> + erlang:error({client_timeout, Clients}). + + +%% Time in milli seconds +t() -> + {A,B,C} = erlang:now(), + A*1000000000+B*1000+(C div 1000). + + +%% ----------------------------------------------------- +%% Webserver part: +%% Implements a web server that sends responses one character +%% at a time, with random delays between the characters. + +start_slow_server(SeqNumServer) -> + io:format("start slow server when" + "~n SeqNumServer: ~w" + "~n", [SeqNumServer]), + proc_lib:start( + erlang, apply, [fun() -> init_slow_server(SeqNumServer) end, []]). + +init_slow_server(SeqNumServer) -> + io:format("[webserver ~w] init slow server" + "~n", [SeqNumServer]), + {ok, LSock} = gen_tcp:listen(0, [binary, {packet,0}, {active,true}, + {backlog, 100}]), + io:format("[webserver ~w] LSock: ~p" + "~n", [SeqNumServer, LSock]), + {ok, {_IP, Port}} = inet:sockname(LSock), + io:format("[webserver ~w] Port: ~w" + "~n", [SeqNumServer, Port]), + proc_lib:init_ack({ok, self(), Port}), + loop_slow_server(LSock, SeqNumServer). + +loop_slow_server(LSock, SeqNumServer) -> + io:format("[webserver ~w] entry with" + "~n LSock: ~p" + "~n", [SeqNumServer, LSock]), + Master = self(), + Acceptor = proc_lib:spawn( + fun() -> client_handler(Master, LSock, SeqNumServer) end), + io:format("[webserver ~w] acceptor started" + "~n Acceptor: ~p" + "~n", [SeqNumServer, Acceptor]), + receive + {accepted, Acceptor} -> + io:format("[webserver ~w] accepted" + "~n", [SeqNumServer]), + loop_slow_server(LSock, SeqNumServer); + shutdown -> + gen_tcp:close(LSock), + exit(Acceptor, kill) + end. + + +%% Handle one client connection +client_handler(Master, LSock, SeqNumServer) -> + io:format("[acceptor ~w] await accept" + "~n", [SeqNumServer]), + {ok, CSock} = gen_tcp:accept(LSock), + io:format("[acceptor ~w] accepted" + "~n CSock: ~p" + "~n", [SeqNumServer, CSock]), + Master ! {accepted, self()}, + set_random_seed(), + loop_client(1, CSock, SeqNumServer). + +loop_client(N, CSock, SeqNumServer) -> + %% Await request, don't bother parsing it too much, + %% assuming the entire request arrives in one packet. + io:format("[acceptor ~w] await request" + "~n N: ~p" + "~n", [SeqNumServer, N]), + receive + {tcp, CSock, Req} -> + ReqNum = parse_req_num(Req), + RespSeqNum = get_next_sequence_number(SeqNumServer), + Response = f("~s->resp~3..0w/~2..0w", [ReqNum, RespSeqNum, N]), + Txt = f("Slow server (~p) got ~p, answering with ~p", + [self(), Req, Response]), + io:format("~s...~n", [Txt]), + slowly_send_response(CSock, Response), + case parse_connection_type(Req) of + keep_alive -> + io:format("~s...done~n", [Txt]), + loop_client(N+1, CSock, SeqNumServer); + close -> + io:format("~s...done (closing)~n", [Txt]), + gen_tcp:close(CSock) + end + end. + +slowly_send_response(CSock, Answer) -> + Response = f("HTTP/1.1 200 OK\r\nContent-Length: ~w\r\n\r\n~s", + [length(Answer), Answer]), + lists:foreach( + fun(Char) -> + timer:sleep(random:uniform(500)), + gen_tcp:send(CSock, <<Char>>) + end, + Response). + +parse_req_num(Request) -> + Opts = [caseless,{capture,all_but_first,list}], + {match, [ReqNum]} = re:run(Request, "GET /(.*) HTTP", Opts), + ReqNum. + +parse_connection_type(Request) -> + Opts = [caseless,{capture,all_but_first,list}], + {match,[CType]} = re:run(Request, "connection: *(keep-alive|close)", Opts), + case string:to_lower(CType) of + "close" -> close; + "keep-alive" -> keep_alive + end. + + +set_random_seed() -> + {_, _, Micros} = now(), + A = erlang:phash2([make_ref(), self(), Micros]), + random:seed(A, A, A). + +f(F, A) -> lists:flatten(io_lib:format(F,A)). + + + + +%%------------------------------------------------------------------------- + + + +otp_8106_pid(doc) -> + ["OTP-8106 - deliver reply info using \"other\" pid"]; +otp_8106_pid(suite) -> + []; +otp_8106_pid(Config) when is_list(Config) -> + case ?config(local_server, Config) of + ok -> + ReceiverPid = create_receiver(pid), + Receiver = ReceiverPid, + + otp8106(ReceiverPid, Receiver, Config), + + stop_receiver(ReceiverPid), + + ok; + _ -> + skip("Failed to start local http-server") + end. + + +otp_8106_fun(doc) -> + ["OTP-8106 - deliver reply info using fun"]; +otp_8106_fun(suite) -> + []; +otp_8106_fun(Config) when is_list(Config) -> + case ?config(local_server, Config) of + ok -> + ReceiverPid = create_receiver(function), + Receiver = otp_8106_deliver_fun(ReceiverPid), + + otp8106(ReceiverPid, Receiver, Config), + + stop_receiver(ReceiverPid), + + ok; + _ -> + skip("Failed to start local http-server") + end. + + +otp_8106_mfa(doc) -> + ["OTP-8106 - deliver reply info using mfa callback"]; +otp_8106_mfa(suite) -> + []; +otp_8106_mfa(Config) when is_list(Config) -> + case ?config(local_server, Config) of + ok -> + ReceiverPid = create_receiver(mfa), + Receiver = {?MODULE, otp_8106_deliver, [mfa, ReceiverPid]}, + + otp8106(ReceiverPid, Receiver, Config), + + stop_receiver(ReceiverPid), + + ok; + _ -> + skip("Failed to start local http-server") + end. + + + otp8106(ReceiverPid, Receiver, Config) -> + Port = ?config(local_port, Config), + URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html", + Request = {URL, []}, + HTTPOptions = [], + Options = [{sync, false}, {receiver, Receiver}], + + {ok, RequestId} = + httpc:request(get, Request, HTTPOptions, Options), + + Body = + receive + {reply, ReceiverPid, {RequestId, {{_, 200, _}, _, B}}} -> + B; + {reply, ReceiverPid, Msg} -> + tsf(Msg); + {bad_reply, ReceiverPid, Msg} -> + tsf(Msg) + end, + + inets_test_lib:check_body(binary_to_list(Body)), + ok. + + +create_receiver(Type) -> + Parent = self(), + Receiver = fun() -> receiver(Type, Parent) end, + spawn_link(Receiver). + +stop_receiver(Pid) -> + Pid ! {stop, self()}. + +receiver(Type, Parent) -> + receive + {stop, Parent} -> + exit(normal); + + {http, ReplyInfo} when (Type =:= pid) -> + Parent ! {reply, self(), ReplyInfo}, + receiver(Type, Parent); + + {Type, ReplyInfo} -> + Parent ! {reply, self(), ReplyInfo}, + receiver(Type, Parent); + + Crap -> + Parent ! {reply, self(), {bad_reply, Crap}}, + receiver(Type, Parent) + end. + + +otp_8106_deliver_fun(ReceiverPid) -> + fun(ReplyInfo) -> otp_8106_deliver(ReplyInfo, function, ReceiverPid) end. + +otp_8106_deliver(ReplyInfo, Type, ReceiverPid) -> + ReceiverPid ! {Type, ReplyInfo}, + ok. + + + +%%------------------------------------------------------------------------- + +otp_8056(doc) -> + "OTP-8056"; +otp_8056(suite) -> + []; +otp_8056(Config) when is_list(Config) -> + Method = get, + Port = ?config(local_port, Config), + URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html", + Request = {URL, []}, + HTTPOptions = [], + Options1 = [{sync, true}, {stream, {self, once}}], + Options2 = [{sync, true}, {stream, self}], + {error, streaming_error} = httpc:request(Method, Request, + HTTPOptions, Options1), + tsp("request 1 failed as expected"), + {error, streaming_error} = httpc:request(Method, Request, + HTTPOptions, Options2), + tsp("request 2 failed as expected"), + ok. + + +%%------------------------------------------------------------------------- + +otp_8352(doc) -> + "OTP-8352"; +otp_8352(suite) -> + []; +otp_8352(Config) when is_list(Config) -> + tsp("otp_8352 -> entry with" + "~n Config: ~p", [Config]), + case ?config(local_server, Config) of + ok -> + tsp("local-server running"), + + tsp("initial profile info(1): ~p", [httpc:info()]), + + MaxSessions = 5, + MaxKeepAlive = 10, + KeepAliveTimeout = timer:minutes(2), + ConnOptions = [{max_sessions, MaxSessions}, + {max_keep_alive_length, MaxKeepAlive}, + {keep_alive_timeout, KeepAliveTimeout}], + httpc:set_options(ConnOptions), + + Method = get, + Port = ?config(local_port, Config), + URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html", + Request = {URL, []}, + Timeout = timer:seconds(1), + ConnTimeout = Timeout + timer:seconds(1), + HttpOptions1 = [{timeout, Timeout}, {connect_timeout, ConnTimeout}], + Options1 = [{socket_opts, [{tos, 87}, + {recbuf, 16#FFFF}, + {sndbuf, 16#FFFF}]}], + case httpc:request(Method, Request, HttpOptions1, Options1) of + {ok, {{_,200,_}, [_ | _], ReplyBody1 = [_ | _]}} -> + %% equivaliant to httpc:request(get, {URL, []}, [], []), + inets_test_lib:check_body(ReplyBody1); + {ok, UnexpectedReply1} -> + tsf({unexpected_reply, UnexpectedReply1}); + {error, _} = Error1 -> + tsf({bad_reply, Error1}) + end, + + tsp("profile info (2): ~p", [httpc:info()]), + + HttpOptions2 = [], + Options2 = [{socket_opts, [{tos, 84}, + {recbuf, 32#1FFFF}, + {sndbuf, 32#1FFFF}]}], + case httpc:request(Method, Request, HttpOptions2, Options2) of + {ok, {{_,200,_}, [_ | _], ReplyBody2 = [_ | _]}} -> + %% equivaliant to httpc:request(get, {URL, []}, [], []), + inets_test_lib:check_body(ReplyBody2); + {ok, UnexpectedReply2} -> + tsf({unexpected_reply, UnexpectedReply2}); + {error, _} = Error2 -> + tsf({bad_reply, Error2}) + end, + tsp("profile info (3): ~p", [httpc:info()]), + ok; + + _ -> + skip("Failed to start local http-server") + end. + + +%%------------------------------------------------------------------------- + +otp_8371(doc) -> + ["OTP-8371"]; +otp_8371(suite) -> + []; +otp_8371(Config) when is_list(Config) -> + ok = httpc:set_options([{ipv6, disabled}]), % also test the old option + {DummyServerPid, Port} = dummy_server(ipv4), + + URL = ?URL_START ++ integer_to_list(Port) ++ + "/ensure_host_header_with_port.html", + + case httpc:request(get, {URL, []}, [], []) of + {ok, Result} -> + case Result of + {{_, 200, _}, _Headers, Body} -> + tsp("expected response with" + "~n Body: ~p", [Body]), + ok; + {StatusLine, Headers, Body} -> + tsp("expected response with" + "~n StatusLine: ~p" + "~n Headers: ~p" + "~n Body: ~p", [StatusLine, Headers, Body]), + tsf({unexpected_result, + [{status_line, StatusLine}, + {headers, Headers}, + {body, Body}]}); + _ -> + tsf({unexpected_result, Result}) + end; + Error -> + tsf({request_failed, Error}) + end, + + DummyServerPid ! stop, + ok = httpc:set_options([{ipv6, enabled}]), + ok. + + +%%------------------------------------------------------------------------- + +otp_8739(doc) -> + ["OTP-8739"]; +otp_8739(suite) -> + []; +otp_8739(Config) when is_list(Config) -> + {_DummyServerPid, Port} = otp_8739_dummy_server(), + URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html", + Method = get, + Request = {URL, []}, + HttpOptions = [{connect_timeout, 500}, {timeout, 1}], + Options = [{sync, true}], + case httpc:request(Method, Request, HttpOptions, Options) of + {error, timeout} -> + %% And now we check the size of the handler db + Info = httpc:info(), + tsp("Info: ~p", [Info]), + {value, {handlers, Handlers}} = + lists:keysearch(handlers, 1, Info), + case Handlers of + [] -> + ok; + _ -> + tsf({unexpected_handlers, Handlers}) + end; + Unexpected -> + tsf({unexpected, Unexpected}) + end. + + +otp_8739_dummy_server() -> + Parent = self(), + Pid = spawn_link(fun() -> otp_8739_dummy_server_init(Parent) end), + receive + {port, Port} -> + {Pid, Port} + end. + +otp_8739_dummy_server_init(Parent) -> + {ok, ListenSocket} = + gen_tcp:listen(0, [binary, inet, {packet, 0}, + {reuseaddr,true}, + {active, false}]), + {ok, Port} = inet:port(ListenSocket), + Parent ! {port, Port}, + otp_8739_dummy_server_main(Parent, ListenSocket). + +otp_8739_dummy_server_main(_Parent, ListenSocket) -> + case gen_tcp:accept(ListenSocket) of + {ok, Sock} -> + %% Ignore the request, and simply wait for the socket to close + receive + {tcp_closed, Sock} -> + (catch gen_tcp:close(ListenSocket)), + exit(normal); + {tcp_error, Sock, Reason} -> + tsp("socket error: ~p", [Reason]), + (catch gen_tcp:close(ListenSocket)), + exit(normal) + after 10000 -> + %% Just in case + (catch gen_tcp:close(Sock)), + (catch gen_tcp:close(ListenSocket)), + exit(timeout) + end; + Error -> + exit(Error) + end. + + +%%------------------------------------------------------------------------- + +initial_server_connect(doc) -> + ["If this test cases times out the init of httpc_handler process is" + "blocking the manager/client process (implementation dependent which) but nither" + "should be blocked."]; +initial_server_connect(suite) -> + []; +initial_server_connect(Config) when is_list(Config) -> + DataDir = ?config(data_dir, Config), + ok = httpc:set_options([{ipfamily, inet}]), + + CertFile = filename:join(DataDir, "ssl_server_cert.pem"), + SSLOptions = [{certfile, CertFile}, {keyfile, CertFile}], + + {DummyServerPid, Port} = dummy_ssl_server_hang(self(), ipv4, SSLOptions), + + URL = ?SSL_URL_START ++ integer_to_list(Port) ++ "/index.html", + + httpc:request(get, {URL, []}, [{ssl,{essl,[]}}], [{sync, false}]), + + [{session_cookies,[]}] = httpc:which_cookies(), + + DummyServerPid ! stop, + ok = httpc:set_options([{ipfamily, inet6fb4}]). + +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- +setup_server_dirs(ServerRoot, DocRoot, DataDir) -> + ConfDir = filename:join(ServerRoot, "conf"), + CgiDir = filename:join(ServerRoot, "cgi-bin"), + ok = file:make_dir(ServerRoot), + ok = file:make_dir(DocRoot), + ok = file:make_dir(ConfDir), + ok = file:make_dir(CgiDir), + + {ok, Files} = file:list_dir(DataDir), + + lists:foreach(fun(File) -> case lists:suffix(".html", File) of + true -> + inets_test_lib:copy_file(File, + DataDir, + DocRoot); + false -> + ok + end + end, Files), + + Cgi = case test_server:os_type() of + {win32, _} -> + "cgi_echo.exe"; + _ -> + "cgi_echo" + end, + + inets_test_lib:copy_file(Cgi, DataDir, CgiDir), + inets_test_lib:copy_file("mime.types", DataDir, ConfDir). + +create_config(FileName, ComType, Port, PrivDir, ServerRoot, DocRoot, + SSLDir) -> + MaxHdrSz = io_lib:format("~p", [256]), + MaxHdrAct = io_lib:format("~p", [close]), + SSL = + case ComType of + ssl -> + [cline(["SSLCertificateFile ", + filename:join(SSLDir, "ssl_server_cert.pem")]), + cline(["SSLCertificateKeyFile ", + filename:join(SSLDir, "ssl_server_cert.pem")]), + cline(["SSLVerifyClient 0"])]; + _ -> + [] + end, + + Mod_order = "Modules mod_alias mod_auth mod_esi mod_actions mod_cgi" + " mod_include mod_dir mod_get mod_head" + " mod_log mod_disk_log mod_trace", + + %% BindAddress = "*|inet", % Force the use of IPv4 + BindAddress = "*", % This corresponds to using IpFamily inet6fb4 + + HttpConfig = [ + cline(["BindAddress ", BindAddress]), + cline(["Port ", integer_to_list(Port)]), + cline(["ServerName ", "httpc_test"]), + cline(["SocketType ", atom_to_list(ComType)]), + cline([Mod_order]), + cline(["ServerRoot ", ServerRoot]), + cline(["DocumentRoot ", DocRoot]), + cline(["MaxHeaderSize ",MaxHdrSz]), + cline(["MaxHeaderAction ",MaxHdrAct]), + cline(["DirectoryIndex ", "index.html "]), + cline(["DefaultType ", "text/plain"]), + cline(["ScriptAlias /cgi-bin/ ", + filename:join(ServerRoot, "cgi-bin"), "/"]), + SSL], + ConfigFile = filename:join([PrivDir,FileName]), + {ok, Fd} = file:open(ConfigFile, [write]), + ok = file:write(Fd, lists:flatten(HttpConfig)), + ok = file:close(Fd). + +cline(List) -> + lists:flatten([List, "\r\n"]). + +receive_streamed_body(RequestId, Body) -> + receive + {http, {RequestId, stream, BinBodyPart}} -> + receive_streamed_body(RequestId, + <<Body/binary, BinBodyPart/binary>>); + {http, {RequestId, stream_end, _Headers}} -> + Body; + {http, Msg} -> + tsf(Msg) + end. + +receive_streamed_body(RequestId, Body, Pid) -> + httpc:stream_next(Pid), + test_server:format("~p:receive_streamed_body -> requested next stream ~n", [?MODULE]), + receive + {http, {RequestId, stream, BinBodyPart}} -> + receive_streamed_body(RequestId, + <<Body/binary, BinBodyPart/binary>>, + Pid); + {http, {RequestId, stream_end, _Headers}} -> + Body; + {http, Msg} -> + tsf(Msg) + end. + +%% Perform a synchronous stop +dummy_server_stop(Pid) -> + Pid ! {stop, self()}, + receive + {stopped, Pid} -> + ok + end. + +dummy_server(IpV) -> + dummy_server(self(), ip_comm, IpV, []). + +dummy_server(SocketType, IpV, Extra) -> + dummy_server(self(), SocketType, IpV, Extra). + +dummy_server(Caller, SocketType, IpV, Extra) -> + Args = [Caller, SocketType, IpV, Extra], + Pid = spawn(httpc_SUITE, dummy_server_init, Args), + receive + {port, Port} -> + {Pid, Port} + end. + +dummy_server_init(Caller, ip_comm, IpV, _) -> + BaseOpts = [binary, {packet, 0}, {reuseaddr,true}, {active, false}], + {ok, ListenSocket} = + case IpV of + ipv4 -> + tsp("ip_comm ipv4 listen", []), + gen_tcp:listen(0, [inet | BaseOpts]); + ipv6 -> + tsp("ip_comm ipv6 listen", []), + gen_tcp:listen(0, [inet6 | BaseOpts]) + end, + {ok, Port} = inet:port(ListenSocket), + tsp("dummy_server_init(ip_comm) -> Port: ~p", [Port]), + Caller ! {port, Port}, + dummy_ipcomm_server_loop({httpd_request, parse, [?HTTP_MAX_HEADER_SIZE]}, + [], ListenSocket); +dummy_server_init(Caller, essl, IpV, SSLOptions) -> + BaseOpts = [{ssl_imp, new}, + {backlog, 128}, binary, {reuseaddr,true}, {active, false} | + SSLOptions], + dummy_ssl_server_init(Caller, BaseOpts, IpV). + +dummy_ssl_server_init(Caller, BaseOpts, IpV) -> + {ok, ListenSocket} = + case IpV of + ipv4 -> + tsp("dummy_ssl_server_init -> ssl ipv4 listen", []), + ssl:listen(0, [inet | BaseOpts]); + ipv6 -> + tsp("dummy_ssl_server_init -> ssl ipv6 listen", []), + ssl:listen(0, [inet6 | BaseOpts]) + end, + tsp("dummy_ssl_server_init -> ListenSocket: ~p", [ListenSocket]), + {ok, {_, Port}} = ssl:sockname(ListenSocket), + tsp("dummy_ssl_server_init -> Port: ~p", [Port]), + Caller ! {port, Port}, + dummy_ssl_server_loop({httpd_request, parse, [?HTTP_MAX_HEADER_SIZE]}, + [], ListenSocket). + +dummy_ipcomm_server_loop(MFA, Handlers, ListenSocket) -> + receive + stop -> + tsp("dummy_ipcomm_server_loop -> stop handlers", []), + lists:foreach(fun(Handler) -> Handler ! stop end, Handlers); + {stop, From} -> + tsp("dummy_ipcomm_server_loop -> " + "stop command from ~p for handlers (~p)", [From, Handlers]), + Stopper = fun(Handler) -> Handler ! stop end, + lists:foreach(Stopper, Handlers), + From ! {stopped, self()} + after 0 -> + tsp("dummy_ipcomm_server_loop -> await accept", []), + {ok, Socket} = gen_tcp:accept(ListenSocket), + tsp("dummy_ipcomm_server_loop -> accepted: ~p", [Socket]), + HandlerPid = dummy_request_handler(MFA, Socket), + tsp("dummy_icomm_server_loop -> handler created: ~p", [HandlerPid]), + gen_tcp:controlling_process(Socket, HandlerPid), + tsp("dummy_ipcomm_server_loop -> " + "control transfered to handler", []), + HandlerPid ! ipcomm_controller, + tsp("dummy_ipcomm_server_loop -> " + "handler informed about control transfer", []), + dummy_ipcomm_server_loop(MFA, [HandlerPid | Handlers], + ListenSocket) + end. + +dummy_ssl_server_loop(MFA, Handlers, ListenSocket) -> + receive + stop -> + tsp("dummy_ssl_server_loop -> stop handlers", []), + lists:foreach(fun(Handler) -> Handler ! stop end, Handlers); + {stop, From} -> + tsp("dummy_ssl_server_loop -> " + "stop command from ~p for handlers (~p)", [From, Handlers]), + Stopper = fun(Handler) -> Handler ! stop end, + lists:foreach(Stopper, Handlers), + From ! {stopped, self()} + after 0 -> + tsp("dummy_ssl_server_loop -> await accept", []), + {ok, Socket} = ssl:transport_accept(ListenSocket), + tsp("dummy_ssl_server_loop -> accepted: ~p", [Socket]), + HandlerPid = dummy_request_handler(MFA, Socket), + tsp("dummy_ssl_server_loop -> handler created: ~p", [HandlerPid]), + ssl:controlling_process(Socket, HandlerPid), + tsp("dummy_ssl_server_loop -> control transfered to handler", []), + HandlerPid ! ssl_controller, + tsp("dummy_ssl_server_loop -> " + "handler informed about control transfer", []), + dummy_ssl_server_loop(MFA, [HandlerPid | Handlers], + ListenSocket) + end. + +dummy_request_handler(MFA, Socket) -> + tsp("spawn request handler", []), + spawn(httpc_SUITE, dummy_request_handler_init, [MFA, Socket]). + +dummy_request_handler_init(MFA, Socket) -> + SockType = + receive + ipcomm_controller -> + tsp("dummy_request_handler_init -> " + "received ip_comm controller - activate", []), + inet:setopts(Socket, [{active, true}]), + ip_comm; + ssl_controller -> + tsp("dummy_request_handler_init -> " + "received ssl controller - activate", []), + ssl:setopts(Socket, [{active, true}]), + ssl + end, + dummy_request_handler_loop(MFA, SockType, Socket). + +dummy_request_handler_loop({Module, Function, Args}, SockType, Socket) -> + tsp("dummy_request_handler_loop -> entry with" + "~n Module: ~p" + "~n Function: ~p" + "~n Args: ~p", [Module, Function, Args]), + receive + {Proto, _, Data} when (Proto =:= tcp) orelse (Proto =:= ssl) -> + tsp("dummy_request_handler_loop -> [~w] Data ~p", [Proto, Data]), + case handle_request(Module, Function, [Data | Args], Socket, Proto) of + stop when Proto =:= tcp -> + gen_tcp:close(Socket); + stop when Proto =:= ssl -> + ssl:close(Socket); + NewMFA -> + dummy_request_handler_loop(NewMFA, SockType, Socket) + end; + stop when SockType =:= ip_comm -> + gen_tcp:close(Socket); + stop when SockType =:= ssl -> + ssl:close(Socket) + end. + + +mk_close(tcp) -> fun(Sock) -> gen_tcp:close(Sock) end; +mk_close(ssl) -> fun(Sock) -> ssl:close(Sock) end. + +mk_send(tcp) -> fun(Sock, Data) -> gen_tcp:send(Sock, Data) end; +mk_send(ssl) -> fun(Sock, Data) -> ssl:send(Sock, Data) end. + +handle_request(Module, Function, Args, Socket, Proto) -> + Close = mk_close(Proto), + Send = mk_send(Proto), + handle_request(Module, Function, Args, Socket, Close, Send). + +handle_request(Module, Function, Args, Socket, Close, Send) -> + tsp("handle_request -> entry with" + "~n Module: ~p" + "~n Function: ~p" + "~n Args: ~p", [Module, Function, Args]), + case Module:Function(Args) of + {ok, Result} -> + tsp("handle_request -> ok" + "~n Result: ~p", [Result]), + case (catch handle_http_msg(Result, Socket, Close, Send)) of + stop -> + stop; + <<>> -> + tsp("handle_request -> empty data"), + {httpd_request, parse, [[<<>>, ?HTTP_MAX_HEADER_SIZE]]}; + Data -> + handle_request(httpd_request, parse, + [Data |[?HTTP_MAX_HEADER_SIZE]], Socket, + Close, Send) + end; + NewMFA -> + tsp("handle_request -> " + "~n NewMFA: ~p", [NewMFA]), + NewMFA + end. + +handle_http_msg({_, RelUri, _, {_, Headers}, Body}, Socket, Close, Send) -> + tsp("handle_http_msg -> entry with: " + "~n RelUri: ~p" + "~n Headers: ~p" + "~n Body: ~p", [RelUri, Headers, Body]), + NextRequest = + case RelUri of + "/dummy_headers.html" -> + <<>>; + "/no_headers.html" -> + stop; + "/just_close.html" -> + stop; + _ -> + ContentLength = content_length(Headers), + case size(Body) - ContentLength of + 0 -> + <<>>; + _ -> + <<_BodyThisReq:ContentLength/binary, + Next/binary>> = Body, + Next + end + end, + + tsp("handle_http_msg -> NextRequest: ~p", [NextRequest]), + case (catch ets:lookup(cookie, cookies)) of + [{cookies, true}]-> + tsp("handle_http_msg -> check cookies ~p", []), + check_cookie(Headers); + _ -> + ok + end, + + DefaultResponse = "HTTP/1.1 200 ok\r\n" ++ + "Content-Length:32\r\n\r\n" + "<HTML><BODY>foobar</BODY></HTML>", + + Msg = + case RelUri of + "/just_close.html" -> + close; + "/no_content.html" -> + "HTTP/1.0 204 No Content\r\n\r\n"; + "/no_headers.html" -> + "HTTP/1.0 200 OK\r\n\r\nTEST"; + "/ensure_host_header_with_port.html" -> + %% tsp("handle_http_msg -> validate host with port"), + case ensure_host_header_with_port(Headers) of + true -> + B = + "<HTML><BODY>" ++ + "host with port" ++ + "</BODY></HTML>", + Len = integer_to_list(length(B)), + "HTTP/1.1 200 ok\r\n" ++ + "Content-Length:" ++ Len ++ "\r\n\r\n" ++ B; + false -> + B = + "<HTML><BODY>" ++ + "Internal Server Error - host without port" ++ + "</BODY></HTML>", + Len = integer_to_list(length(B)), + "HTTP/1.1 500 Internal Server Error\r\n" ++ + "Content-Length:" ++ Len ++ "\r\n\r\n" ++ B + end; + "/300.html" -> + NewUri = ?URL_START ++ + integer_to_list(?IP_PORT) ++ "/dummy.html", + "HTTP/1.1 300 Multiple Choices\r\n" ++ + "Location:" ++ NewUri ++ "\r\n" ++ + "Content-Length:0\r\n\r\n"; + "/301.html" -> + NewUri = ?URL_START ++ + integer_to_list(?IP_PORT) ++ "/dummy.html", + "HTTP/1.1 301 Moved Permanently\r\n" ++ + "Location:" ++ NewUri ++ "\r\n" ++ + "Content-Length:80\r\n\r\n" ++ + "<HTML><BODY><a href=" ++ NewUri ++ + ">New place</a></BODY></HTML>"; + "/302.html" -> + NewUri = ?URL_START ++ + integer_to_list(?IP_PORT) ++ "/dummy.html", + "HTTP/1.1 302 Found \r\n" ++ + "Location:" ++ NewUri ++ "\r\n" ++ + "Content-Length:80\r\n\r\n" ++ + "<HTML><BODY><a href=" ++ NewUri ++ + ">New place</a></BODY></HTML>"; + "/303.html" -> + NewUri = ?URL_START ++ + integer_to_list(?IP_PORT) ++ "/dummy.html", + "HTTP/1.1 303 See Other \r\n" ++ + "Location:" ++ NewUri ++ "\r\n" ++ + "Content-Length:80\r\n\r\n" ++ + "<HTML><BODY><a href=" ++ NewUri ++ + ">New place</a></BODY></HTML>"; + "/307.html" -> + NewUri = ?URL_START ++ + integer_to_list(?IP_PORT) ++ "/dummy.html", + "HTTP/1.1 307 Temporary Rediect \r\n" ++ + "Location:" ++ NewUri ++ "\r\n" ++ + "Content-Length:80\r\n\r\n" ++ + "<HTML><BODY><a href=" ++ NewUri ++ + ">New place</a></BODY></HTML>"; + "/500.html" -> + "HTTP/1.1 500 Internal Server Error\r\n" ++ + "Content-Length:47\r\n\r\n" ++ + "<HTML><BODY>Internal Server Error</BODY></HTML>"; + "/503.html" -> + case ets:lookup(unavailable, 503) of + [{503, unavailable}] -> + ets:insert(unavailable, {503, available}), + "HTTP/1.1 503 Service Unavailable\r\n" ++ + "Retry-After:5\r\n" ++ + "Content-Length:47\r\n\r\n" ++ + "<HTML><BODY>Internal Server Error</BODY></HTML>"; + [{503, available}] -> + DefaultResponse; + [{503, long_unavailable}] -> + "HTTP/1.1 503 Service Unavailable\r\n" ++ + "Retry-After:120\r\n" ++ + "Content-Length:47\r\n\r\n" ++ + "<HTML><BODY>Internal Server Error</BODY></HTML>" + end; + "/redirectloop.html" -> %% Create a potential endless loop! + {ok, Port} = inet:port(Socket), + NewUri = ?URL_START ++ + integer_to_list(Port) ++ "/redirectloop.html", + "HTTP/1.1 300 Multiple Choices\r\n" ++ + "Location:" ++ NewUri ++ "\r\n" ++ + "Content-Length:0\r\n\r\n"; + "/userinfo.html" -> + Challange = "HTTP/1.1 401 Unauthorized \r\n" ++ + "WWW-Authenticate:Basic" ++"\r\n" ++ + "Content-Length:0\r\n\r\n", + case auth_header(Headers) of + {ok, Value} -> + handle_auth(Value, Challange, DefaultResponse); + _ -> + Challange + end; + "/dummy_headers.html" -> + %% The client will only care about the Transfer-Encoding + %% header the rest of these headers are left to the + %% user to evaluate. This is not a valid response + %% it only tests that the header handling code works. + Head = "HTTP/1.1 200 ok\r\n" ++ + "Content-Length:32\r\n" ++ + "Pragma:1#no-cache\r\n" ++ + "Via:1.0 fred, 1.1 nowhere.com (Apache/1.1)\r\n" ++ + "Warning:1#pseudonym foobar\r\n" ++ + "Vary:*\r\n" ++ + "Trailer:Other:inets_test\r\n" ++ + "Upgrade:HTTP/2.0\r\n" ++ + "Age:4711\r\n" ++ + "Transfer-Encoding:chunked\r\n" ++ + "Content-Encoding:foo\r\n" ++ + "Content-Language:en\r\n" ++ + "Content-Location:http://www.foobar.se\r\n" ++ + "Content-MD5:104528739076276072743283077410617235478\r\n" + ++ + "Content-Range:Sat, 29 Oct 1994 19:43:31 GMT\r\n" ++ + "Expires:Sat, 29 Oct 1994 19:43:31 GMT\r\n" ++ + "Proxy-Authenticate:#1Basic" ++ + "\r\n\r\n", + Send(Socket, Head), + Send(Socket, http_chunk:encode("<HTML><BODY>fo")), + Send(Socket, http_chunk:encode("obar</BODY></HTML>")), + http_chunk:encode_last(); + "/capital_transfer_encoding.html" -> + Head = "HTTP/1.1 200 ok\r\n" ++ + "Transfer-Encoding:Chunked\r\n\r\n", + Send(Socket, Head), + Send(Socket, http_chunk:encode("<HTML><BODY>fo")), + Send(Socket, http_chunk:encode("obar</BODY></HTML>")), + http_chunk:encode_last(); + "/cookie.html" -> + "HTTP/1.1 200 ok\r\n" ++ + "set-cookie:" ++ "test_cookie=true; path=/;" ++ + "max-age=60000\r\n" ++ + "Content-Length:32\r\n\r\n"++ + "<HTML><BODY>foobar</BODY></HTML>"; + "/missing_crlf.html" -> + "HTTP/1.1 200 ok" ++ + "Content-Length:32\r\n" ++ + "<HTML><BODY>foobar</BODY></HTML>"; + "/wrong_statusline.html" -> + "ok 200 HTTP/1.1\r\n\r\n" ++ + "Content-Length:32\r\n\r\n" ++ + "<HTML><BODY>foobar</BODY></HTML>"; + "/once_chunked.html" -> + Head = "HTTP/1.1 200 ok\r\n" ++ + "Transfer-Encoding:Chunked\r\n\r\n", + Send(Socket, Head), + Send(Socket, http_chunk:encode("<HTML><BODY>fo")), + Send(Socket, + http_chunk:encode("obar</BODY></HTML>")), + http_chunk:encode_last(); + "/once.html" -> + Head = "HTTP/1.1 200 ok\r\n" ++ + "Content-Length:32\r\n\r\n", + Send(Socket, Head), + Send(Socket, "<HTML><BODY>fo"), + test_server:sleep(1000), + Send(Socket, "ob"), + test_server:sleep(1000), + Send(Socket, "ar</BODY></HTML>"); + "/invalid_http.html" -> + "HTTP/1.1 301\r\nDate:Sun, 09 Dec 2007 13:04:18 GMT\r\n" ++ + "Transfer-Encoding:chunked\r\n\r\n"; + "/missing_reason_phrase.html" -> + "HTTP/1.1 200\r\n" ++ + "Content-Length: 32\r\n\r\n" + "<HTML><BODY>foobar</BODY></HTML>"; + "/missing_CR.html" -> + "HTTP/1.1 200 ok\n" ++ + "Content-Length:32\r\n\n" + "<HTML><BODY>foobar</BODY></HTML>"; + _ -> + DefaultResponse + end, + + tsp("handle_http_msg -> Msg: ~p", [Msg]), + case Msg of + ok -> + %% Previously, this resulted in an {error, einval}. Now what? + ok; + close -> + %% Nothing to send, just close + Close(Socket); + _ when is_list(Msg) orelse is_binary(Msg) -> + Send(Socket, Msg) + end, + tsp("handle_http_msg -> done"), + NextRequest. + +ensure_host_header_with_port([]) -> + false; +ensure_host_header_with_port(["host: " ++ Host| _]) -> + case string:tokens(Host, [$:]) of + [ActualHost, Port] -> + tsp("ensure_host_header_with_port -> " + "~n ActualHost: ~p" + "~n Port: ~p", [ActualHost, Port]), + true; + _ -> + false + end; +ensure_host_header_with_port([_|T]) -> + ensure_host_header_with_port(T). + +auth_header([]) -> + auth_header_not_found; +auth_header(["authorization:" ++ Value | _]) -> + {ok, string:strip(Value)}; +auth_header([_ | Tail]) -> + auth_header(Tail). + +handle_auth("Basic " ++ UserInfo, Challange, DefaultResponse) -> + case string:tokens(base64:decode_to_string(UserInfo), ":") of + ["alladin", "sesame"] = Auth -> + test_server:format("Auth: ~p~n", [Auth]), + DefaultResponse; + Other -> + test_server:format("UnAuth: ~p~n", [Other]), + Challange + end. + +check_cookie([]) -> + tsf(no_cookie_header); +check_cookie(["cookie:" ++ _Value | _]) -> + ok; +check_cookie([_Head | Tail]) -> + check_cookie(Tail). + +content_length([]) -> + 0; +content_length(["content-length:" ++ Value | _]) -> + list_to_integer(string:strip(Value)); +content_length([_Head | Tail]) -> + content_length(Tail). + +%% ------------------------------------------------------------------------- + +simple_request_and_verify(Config, + Method, Request, HttpOpts, Opts, VerifyResult) + when (is_list(Config) andalso + is_atom(Method) andalso + is_list(HttpOpts) andalso + is_list(Opts) andalso + is_function(VerifyResult, 1)) -> + tsp("request_and_verify -> entry with" + "~n Method: ~p" + "~n Request: ~p" + "~n HttpOpts: ~p" + "~n Opts: ~p", [Method, Request, HttpOpts, Opts]), + case ?config(local_server, Config) of + ok -> + tsp("request_and_verify -> local-server running"), + Result = (catch httpc:request(Method, Request, HttpOpts, Opts)), + VerifyResult(Result); + _ -> + tsp("request_and_verify -> local-server *not* running - skip"), + hard_skip("Local http-server not running") + end. + + + + +not_implemented_yet() -> + exit(not_implemented_yet). + +p(F) -> + p(F, []). + +p(F, A) -> + io:format("~p ~w:" ++ F ++ "~n", [self(), ?MODULE | A]). + +tsp(F) -> + inets_test_lib:tsp("[~w]" ++ F, [?MODULE]). +tsp(F, A) -> + inets_test_lib:tsp("[~w]" ++ F, [?MODULE|A]). + +tsf(Reason) -> + test_server:fail(Reason). + + +dummy_ssl_server_hang(Caller, IpV, SslOpt) -> + Pid = spawn(httpc_SUITE, dummy_ssl_server_hang_init, [Caller, IpV, SslOpt]), + receive + {port, Port} -> + {Pid, Port} + end. + +dummy_ssl_server_hang_init(Caller, IpV, SslOpt) -> + {ok, ListenSocket} = + case IpV of + ipv4 -> + ssl:listen(0, [binary, inet, {packet, 0}, + {reuseaddr,true}, + {active, false}] ++ SslOpt); + ipv6 -> + ssl:listen(0, [binary, inet6, {packet, 0}, + {reuseaddr,true}, + {active, false}] ++ SslOpt) + end, + {ok, {_,Port}} = ssl:sockname(ListenSocket), + tsp("dummy_ssl_server_hang_init -> Port: ~p", [Port]), + Caller ! {port, Port}, + {ok, AcceptSocket} = ssl:transport_accept(ListenSocket), + dummy_ssl_server_hang_loop(AcceptSocket). + +dummy_ssl_server_hang_loop(_) -> + %% Do not do ssl:ssl_accept as we + %% want to time out the underlying gen_tcp:connect + receive + stop -> + ok + end. + +hard_skip(Reason) -> + throw(skip(Reason)). + +skip(Reason) -> + {skip, Reason}. |