%%
%% %CopyrightBegin%
%% 
%% Copyright Ericsson AB 2004-2011. 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(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(PROXY_URL, "http://www.erlang.org").
-define(PROXY, "www-proxy.ericsson.se").
-define(PROXY_PORT, 8080).
-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, proxy}, 
     {group, ssl}, 
     {group, stream}, 
     {group, ipv6}, 
     {group, tickets},
     initial_server_connect
    ].

groups() -> 
    [
     {proxy,   [], [proxy_options, 
		    proxy_head, 
		    proxy_get, 
		    proxy_trace,
		    proxy_post, 
		    proxy_put, 
		    proxy_delete, 
		    proxy_auth,
		    proxy_headers, 
		    proxy_emulate_lower_versions,
		    proxy_page_does_not_exist,
		    proxy_https_not_supported]}, 
     {ssl,     [], [ssl_head, 
		    essl_head, 
		    ssl_get, 
		    essl_get, 
		    ssl_trace, 
		    essl_trace]}, 
     {stream,  [], [http_stream,
		    http_stream_once, 
		    proxy_stream]}, 
     {tickets, [], [hexed_query_otp_6191, 
		    empty_body_otp_6243,
		    empty_response_header_otp_6830,
		    transfer_encoding_otp_6807, 
		    proxy_not_modified_otp_6821,
		    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]}
    ].


%%--------------------------------------------------------------------
%% 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) ->
    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}),

    [{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]);

	    "proxy_" ++ Rest ->
		io:format("init_per_testcase -> Rest: ~p~n", [Rest]),
		case Rest of			       
		    "https_not_supported" ->	
			tsp("init_per_testcase -> [proxy case] start inets"),
			inets:start(),
			tsp("init_per_testcase -> "
			    "[proxy case] start crypto, public_key and ssl"),
			try ?ENSURE_STARTED([crypto, public_key, ssl]) of
			    ok ->
				[{watchdog, Dog} | TmpConfig]
			catch 
			    throw:{error, {failed_starting, App, _}} ->
				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;

		    _ ->
			%% We use erlang.org for the proxy tests 
			%% and after the switch to erlang-web, many
			%% of the test cases no longer work (erlang.org
			%% previously run on Apache). 
			%% Until we have had time to update inets
			%% (and updated erlang.org to use that inets) 
			%% and the test cases, we simply skip the 
			%% problematic test cases. 
			%% This is not ideal, but I am busy....
			case is_proxy_available(?PROXY, ?PROXY_PORT) of
			    true ->
				BadCases = 
				    [
				     "delete", 
				     "get", 
				     "head", 
				     "not_modified_otp_6821", 
				     "options", 
				     "page_does_not_exist", 
				     "post", 
				     "put", 
				     "stream"
				    ],
				case lists:member(Rest, BadCases) of
				    true ->
					[skip("TC and server not compatible") | 
					 TmpConfig];
				    false ->
					inets:start(),
					[{watchdog, Dog} | TmpConfig]
				end;
			    false ->
				[skip("proxy not responding") | TmpConfig]
			end
		end;

	    "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),
			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;

	    _ -> 
		TmpConfig2 = lists:keydelete(local_server, 1, TmpConfig),
		%% Will start inets 
		Server = start_http_server(PrivDir, IpConfFile),
		[{watchdog, Dog}, {local_server, Server} | TmpConfig2]
	end,
    
    %% This will fail for the ipv6_ - cases (but that is ok)
    ProxyExceptions = ["localhost", ?IPV6_LOCAL_HOST], 
    httpc:set_options([{proxy, {{?PROXY, ?PROXY_PORT}, ProxyExceptions}}]),
    inets:enable_trace(max, io, httpc),
    %% inets:enable_trace(max, io, all),
    %% snmp:set_trace([gen_tcp]),
    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) ->
    case ?config(local_server, Config) of 
	ok ->
	    Port = ?config(local_port, Config),
	    URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html",
	    case httpc:request(head, {URL, []}, [], []) of
		{ok, {{_,200,_}, [_ | _], []}} ->
		    ok;
		{ok, WrongReply} ->
		    tsf({wrong_reply, WrongReply});
		Error ->
		    tsf({failed, Error})
	    end;
	  _ ->
	      {skip, "Failed to start local http-server"}
      end.  
%%-------------------------------------------------------------------------
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}]),

     p("test_pipeline -> issue (async) request 1"),
     {ok, RequestId1} =
	httpc:request(get, {URL, []}, [], [{sync, false}]),
     test_server:format("RequestId1: ~p~n", [RequestId1]),
     p("test_pipeline -> RequestId1: ~p", [RequestId1]),

     %% Make sure pipeline is initiated
     p("test_pipeline -> sleep some", []),
     test_server:sleep(4000),

     p("test_pipeline -> issue (async) request 2"),
     {ok, RequestId2} =
	httpc:request(get, {URL, []}, [], [{sync, false}]),
     tsp("RequestId2: ~p", [RequestId2]),
     p("test_pipeline -> RequestId2: ~p", [RequestId2]),

     p("test_pipeline -> issue (sync) request 3"),
     {ok, {{_,200,_}, [_ | _], [_ | _]}} =
	httpc:request(get, {URL, []}, [], []),

    p("test_pipeline -> expect reply for (async) request 1 or 2"),
    receive
	{http, {RequestId1, {{_, 200, _}, _, _}}} ->
	    p("test_pipeline -> received reply for (async) request 1 - now wait for 2"),
	    receive
		{http, {RequestId2, {{_, 200, _}, _, _}}} ->
		    p("test_pipeline -> received reply for (async) request 2"),
		    ok;
		{http, Msg1} ->
		    tsf(Msg1)
	    end;
	{http, {RequestId2, {{_, 200, _}, _, _}}} ->
	    io:format("test_pipeline -> received reply for (async) request 2 - now wait for 1"),
	    receive
		{http, {RequestId1, {{_, 200, _}, _, _}}} ->
		    io:format("test_pipeline -> received reply for (async) request 1"),
		    ok;
		{http, Msg2} ->
		    tsf(Msg2)
	    end;
	{http, Msg3} ->
	    tsf(Msg3)
    after 60000 ->
	    receive Any1 ->
		    tsp("received crap after timeout: ~n   ~p", [Any1]),
		    tsf({error, {timeout, Any1}})
	    end
    end,

     p("test_pipeline -> sleep some"),
     test_server:sleep(4000),

     p("test_pipeline -> issue (async) request 4"),
     {ok, RequestId3} =
	httpc:request(get, {URL, []}, [], [{sync, false}]),
     tsp("RequestId3: ~p", [RequestId3]),
     p("test_pipeline -> RequestId3: ~p", [RequestId3]),

     p("test_pipeline -> issue (async) request 5"),
     {ok, RequestId4} =
	httpc:request(get, {URL, []}, [], [{sync, false}]),
     tsp("RequestId4: ~p~n", [RequestId4]),
     p("test_pipeline -> RequestId4: ~p", [RequestId4]),

     p("test_pipeline -> cancel (async) request 4"),
     ok = httpc:cancel_request(RequestId3),

     p("test_pipeline -> expect *no* reply for cancelled (async) request 4 (for 3 secs)"),
     receive
	 {http, {RequestId3, _}} ->
	     tsf(http_cancel_request_failed)
     after 3000 ->
	     ok
     end,

     p("test_pipeline -> expect reply for (async) request 4"),
     Body =
	receive
	    {http, {RequestId4, {{_, 200, _}, _, BinBody4}}} = Res ->
		p("test_pipeline -> received reply for (async) request 5"),
		tsp("Receive : ~p", [Res]),
		BinBody4;
	    {http, Msg4} ->
		tsf(Msg4)
	after 60000 ->
		receive Any2 ->
			tsp("received crap after timeout: ~n   ~p", [Any2]),
			tsf({error, {timeout, Any2}})
		end
	end,

    p("test_pipeline -> check reply for (async) request 5"),
    inets_test_lib:check_body(binary_to_list(Body)),

    p("test_pipeline -> ensure no unexpected incomming"),
    receive
	{http, Any} ->
	    tsf({unexpected_message, Any})
    after 500 ->
	    ok
    end,

    p("test_pipeline -> done"),
    ok.

%%-------------------------------------------------------------------------
http_trace(doc) ->
    ["Perform a TRACE request that goes through a proxy."];
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","webmaster@erlang.se"},
					 {"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]),
	    {ok, {{_,200, _}, [_ | _], Body = [_ | _]}} =
		httpc:request(get, {URL, []}, [{ssl, SSLConfig}], []),
	    inets_test_lib:check_body(Body);
	 {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"},
			       [], []),
   
	    URL307 = ?URL_START ++ integer_to_list(Port) ++ "/307.html",

	    tsp("http_redirect -> issue request 9: "
		"~n   ~p", [URL307]),
	    {ok, {{_,200,_}, [_ | _], [_|_]}} 
 		= httpc:request(get, {URL307, []}, [], []),
	
	    tsp("http_redirect -> issue request 10: "
		"~n   ~p", [URL307]),
	    {ok, {{_,200,_}, [_ | _], []}} 
 		= httpc:request(head, {URL307, []}, [], []),
	    
	    tsp("http_redirect -> issue request 11: "
		"~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.

%%-------------------------------------------------------------------------
proxy_options(doc) ->
    ["Perform a OPTIONS request that goes through a proxy."];
proxy_options(suite) ->
    [];
proxy_options(Config) when is_list(Config) ->
    %% As of 2011-03-24, erlang.org (which is used as server) 
    %% does no longer run Apache, but instead runs inets, which 
    %% do not implement "options".
    case ?config(skip, Config) of 
        undefined ->
	    case httpc:request(options, {?PROXY_URL, []}, [], []) of
		{ok, {{_,200,_}, Headers, _}} ->
		    case lists:keysearch("allow", 1, Headers) of
			{value, {"allow", _}} ->
			    ok;
			_ ->
			    tsf(http_options_request_failed)
		    end;
		Unexpected ->
		    tsf({unexpected_result, Unexpected})
	    end;
	Reason ->
	    {skip, Reason}
    end.


%%-------------------------------------------------------------------------
proxy_head(doc) ->
     ["Perform a HEAD request that goes through a proxy."];
proxy_head(suite) ->
    [];
proxy_head(Config) when is_list(Config) ->
    %% As of 2011-03-24, erlang.org (which is used as server) 
    %% does no longer run Apache, but instead runs inets.
    case ?config(skip, Config) of 
	undefined ->
	    case httpc:request(head, {?PROXY_URL, []}, [], []) of
		{ok, {{_,200, _}, [_ | _], []}} ->
		    ok;
		Unexpected ->
		    tsf({unexpected_result, Unexpected})
	    end;
	Reason ->
	    {skip, Reason}
    end.


%%-------------------------------------------------------------------------
proxy_get(doc) ->
    ["Perform a GET request that goes through a proxy."];
proxy_get(suite) ->
    [];
proxy_get(Config) when is_list(Config) ->
    case ?config(skip, Config) of 
	undefined ->
	    case httpc:request(get, {?PROXY_URL, []}, [], []) of
		{ok, {{_,200,_}, [_ | _], Body = [_ | _]}} ->
		    inets_test_lib:check_body(Body);
		Unexpected ->
		    tsf({unexpected_result, Unexpected})
	    end;
	Reason ->
	    {skip, Reason}
    end.

%%-------------------------------------------------------------------------
proxy_emulate_lower_versions(doc) ->
    ["Perform requests as 0.9 and 1.0 clients."];
proxy_emulate_lower_versions(suite) ->
    [];
proxy_emulate_lower_versions(Config) when is_list(Config) ->
    case ?config(skip, Config) of 
	undefined ->
	    Result09 = pelv_get("HTTP/0.9"), 
	    case Result09 of
		{ok, [_| _] = Body0} ->
		    inets_test_lib:check_body(Body0),
		    ok;
		_ ->
		    tsf({unexpected_result, "HTTP/0.9", Result09})
	    end,
	    
	    %% We do not check the version here as many servers
	    %% do not behave according to the rfc and send
	    %% 1.1 in its response.
	    Result10 = pelv_get("HTTP/1.0"), 
	    case Result10 of
		{ok,{{_, 200, _}, [_ | _], Body1 = [_ | _]}} ->
		    inets_test_lib:check_body(Body1),
		    ok;
		_ ->
		    tsf({unexpected_result, "HTTP/1.0", Result10})
	    end,

	    Result11 = pelv_get("HTTP/1.1"), 
	    case Result11 of
		{ok, {{"HTTP/1.1", 200, _}, [_ | _], Body2 = [_ | _]}} ->
		    inets_test_lib:check_body(Body2);
		_ ->
		    tsf({unexpected_result, "HTTP/1.1", Result11})
	    end;
		
	Reason ->
	    {skip, Reason}
    end.

pelv_get(Version) ->
    httpc:request(get, {?PROXY_URL, []}, [{version, Version}], []).

%%-------------------------------------------------------------------------
proxy_trace(doc) ->
    ["Perform a TRACE request that goes through a proxy."];
proxy_trace(suite) ->
    [];
proxy_trace(Config) when is_list(Config) ->
    %%{ok, {{_,200,_}, [_ | _], "TRACE " ++ _}} =
    %%	httpc:request(trace, {?PROXY_URL, []}, [], []),
    {skip, "HTTP TRACE is no longer allowed on the ?PROXY_URL server due "
     "to security reasons"}.


%%-------------------------------------------------------------------------
proxy_post(doc) ->
    ["Perform a POST request that goes through a proxy. Note the server"
     " will reject the request this is a test of the sending of the"
     " request."];
proxy_post(suite) ->
    [];
proxy_post(Config) when is_list(Config) ->
    %% As of 2011-03-24, erlang.org (which is used as server) 
    %% does no longer run Apache, but instead runs inets.
    case ?config(skip, Config) of 
	undefined ->
	    case httpc:request(post, {?PROXY_URL, [], 
				     "text/plain", "foobar"}, [],[]) of
		{ok, {{_,405,_}, [_ | _], [_ | _]}} ->
		    ok;
		Unexpected ->
		    tsf({unexpected_result, Unexpected})
	    end;
	Reason ->
	    {skip, Reason}
    end.


%%-------------------------------------------------------------------------
proxy_put(doc) ->
    ["Perform a PUT request that goes through a proxy. Note the server"
     " will reject the request this is a test of the sending of the"
     " request."];
proxy_put(suite) ->
    [];
proxy_put(Config) when is_list(Config) ->
    %% As of 2011-03-24, erlang.org (which is used as server) 
    %% does no longer run Apache, but instead runs inets.
    case ?config(skip, Config) of 
	undefined -> 
	    case httpc:request(put, {"http://www.erlang.org/foobar.html", [], 
				    "html", "<html> <body><h1> foo </h1>" 
				    "<p>bar</p> </body></html>"}, [], []) of
		{ok, {{_,405,_}, [_ | _], [_ | _]}} ->
		    ok;
		Unexpected ->
		    tsf({unexpected_result, Unexpected})
	    end;
	Reason ->
	    {skip, Reason}
    end.


%%-------------------------------------------------------------------------
proxy_delete(doc) ->
    ["Perform a DELETE request that goes through a proxy. Note the server"
     " will reject the request this is a test of the sending of the"
     " request. But as the file does not exist the return code will"
     " be 404 not found."];
proxy_delete(suite) ->
    [];
proxy_delete(Config) when is_list(Config) ->
    %% As of 2011-03-24, erlang.org (which is used as server) 
    %% does no longer run Apache, but instead runs inets.
    case ?config(skip, Config) of 
	undefined -> 
	    URL = ?PROXY_URL ++ "/foobar.html",
	    case httpc:request(delete, {URL, []}, [], []) of
		{ok, {{_,404,_}, [_ | _], [_ | _]}} ->
		    ok;
		Unexpected ->
		    tsf({unexpected_result, Unexpected})
	    end;
	Reason ->
	    {skip, Reason}
    end.


%%-------------------------------------------------------------------------
proxy_headers(doc) ->
    ["Use as many request headers as possible"];
proxy_headers(suite) ->
    [];
proxy_headers(Config) when is_list(Config) ->
    case ?config(skip, Config) of 
	undefined ->
	    {ok, {{_,200,_}, [_ | _], [_ | _]}} 
		= httpc:request(get, {?PROXY_URL,
				     [
				      {"Accept",
				       "text/*, text/html,"
				       " text/html;level=1,"
				       " */*"}, 
				      {"Accept-Charset", 
				       "iso-8859-5, unicode-1-1;"
				       "q=0.8"},
				      {"Accept-Encoding", "*"},
				      {"Accept-Language", 
				       "sv, en-gb;q=0.8,"
				       " en;q=0.7"},
				      {"User-Agent", "inets"},
				      {"Max-Forwards","5"},
				      {"Referer", 
				       "http://otp.ericsson.se:8000"
				       "/product/internal"}
			     ]}, [], []),
	    ok;
	Reason ->
	    {skip, Reason}
    end.

%%-------------------------------------------------------------------------
proxy_auth(doc) ->
    ["Test the code for sending of proxy authorization."];
proxy_auth(suite) ->
    [];
proxy_auth(Config) when is_list(Config) ->
    %% Our proxy seems to ignore the header, however our proxy
    %% does not requirer an auth header, but we want to know
    %% atleast the code for sending the header does not crash!
    case ?config(skip, Config) of 
	undefined ->	    
	    case httpc:request(get, {?PROXY_URL, []}, 
			      [{proxy_auth, {"foo", "bar"}}], []) of
		{ok, {{_,200, _}, [_ | _], [_|_]}} ->
		    ok;
		Unexpected ->
		    tsf({unexpected_result, Unexpected})
	    end;
	Reason ->
	    {skip, Reason}
    end.  


%%-------------------------------------------------------------------------
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.


%%-------------------------------------------------------------------------
proxy_page_does_not_exist(doc) ->
    ["Test that we get a 404 when the page is not found."];
proxy_page_does_not_exist(suite) ->
    [];
proxy_page_does_not_exist(Config) when is_list(Config) ->
    case ?config(skip, Config) of 
	undefined ->
	    URL = ?PROXY_URL ++ "/doesnotexist.html",
	    {ok, {{_,404,_}, [_ | _], [_ | _]}} = 
		httpc:request(get, {URL, []}, [], []),
	    ok;
	Reason ->
	    {skip, Reason}
    end.


%%-------------------------------------------------------------------------

proxy_https_not_supported(doc) ->
    [];
proxy_https_not_supported(suite) ->
    [];
proxy_https_not_supported(Config) when is_list(Config) ->
    Result = httpc:request(get, {"https://login.yahoo.com", []}, [], []),
    case Result of
	{error, https_through_proxy_is_not_currently_supported} ->
	    ok;
	_ ->
	    tsf({unexpected_reason, Result})
    end.

%%-------------------------------------------------------------------------

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.


%%-------------------------------------------------------------------------
proxy_stream(doc) ->
    ["Test the option stream for asynchrony requests"];
proxy_stream(suite) ->
    [];
proxy_stream(Config) when is_list(Config) ->
    case ?config(skip, Config) of 
	undefined ->
	    {ok, {{_,200,_}, [_ | _], Body}} = 
		httpc:request(get, {?PROXY_URL, []}, [], []),
	    
	    {ok, RequestId} =
		httpc:request(get, {?PROXY_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);
	Reason ->
	    {skip, Reason}
    end.


%%-------------------------------------------------------------------------
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,{not_supported_scheme,localhost}} =
	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.


%%-------------------------------------------------------------------------

proxy_not_modified_otp_6821(doc) ->
    ["If unmodified no body should be returned"];
proxy_not_modified_otp_6821(suite) ->
    [];
proxy_not_modified_otp_6821(Config) when is_list(Config) ->
    case ?config(skip, Config) of 
	undefined ->
	    provocate_not_modified_bug(?PROXY_URL);
	Reason ->
	    {skip, Reason}
    end.


%%-------------------------------------------------------------------------

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",
	    
    HttpConfig = [
		  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"]).

is_proxy_available(Proxy, Port) ->
    case gen_tcp:connect(Proxy, Port, []) of
	{ok, Socket} ->
	    gen_tcp:close(Socket),
	    true;
	_ ->
	    false
    end.

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>";
	    "/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).

provocate_not_modified_bug(Url) ->
    Timeout = 15000, %% 15s should be plenty

    {ok, {{_, 200, _}, ReplyHeaders, _Body}} =
	httpc:request(get, {Url, []}, [{timeout, Timeout}], []),
    Etag = pick_header(ReplyHeaders, "ETag"),
    Last = pick_header(ReplyHeaders, "last-modified"),
    
    case httpc:request(get, {Url, [{"If-None-Match", Etag},
				  {"If-Modified-Since", Last}]},
		      [{timeout, 15000}],
		      []) of
	{ok, {{_, 304, _}, _, _}} -> %% The expected reply
	    page_unchanged;
	{ok, {{_, 200, _}, _, _}} -> 
	    %% If the page has changed since the	
	    %% last request we retry to
	    %% trigger the bug
	    provocate_not_modified_bug(Url);
	{error, timeout} ->
	    %% Not what we expected. Tcpdump can be used to
	    %% verify that we receive the complete http-reply
	    %% but still time out.
	    incorrect_result
    end.

pick_header(Headers, Name) ->
    case lists:keysearch(string:to_lower(Name), 1,
			 [{string:to_lower(X), Y} || {X, Y} <- Headers]) of
	false ->
	    [];
	{value, {_Key, Val}} ->
	    Val
    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(F).
tsp(F, A) ->
    inets_test_lib:tsp(F, 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.


skip(Reason) ->
    {skip, Reason}.