aboutsummaryrefslogblamecommitdiffstats
path: root/lib/inets/test/httpc_proxy_SUITE.erl
blob: 3ee798166026f9a3a40efe8a1d216ee3f882a3a0 (plain) (tree)
1
2
3
4
5


                   
                                                        
   










                                                                           











































                                                                      
                                                         










                      
                      





                                 


                            


                                                                      
                                            













                                                    
                             









































                                                                      
                                                 




























































































































                                                                           















                                                                                  























































































































































                                                                      


                                                     
                                                              










                                                                      


























                                                                      
                                                          
















                                                                   
                                                    
















                                                                           

                                                                    


















                                                                      
                                                                 












                                                                 

                                                    


























































                                                                        
%%
%% %CopyrightBegin%
%% 
%% Copyright Ericsson AB 2012-2016. All Rights Reserved.
%% 
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%% 
%% %CopyrightEnd%
%%
%%

%% 
%% ts:run(inets, httpc_proxy_SUITE, [batch]).
%% ct:run("../inets_test", httpc_proxy_SUITE).
%%

-module(httpc_proxy_SUITE).

-include_lib("common_test/include/ct.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).

-define(LOCAL_PROXY_SCRIPT, "server_proxy.sh").
-define(p(F, A), % Debug printout
	begin
	    io:format(
	      "~w ~w: " ++ begin F end,
	      [self(),?MODULE] ++ begin A end)
	end).

%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------

suite() ->
    [{ct_hooks,[ts_install_cth]}].

all() ->
    [{group,local_proxy},
     {group,local_proxy_https}].

groups() -> 
    [{local_proxy,[],
      [http_emulate_lower_versions
       |local_proxy_cases()]},
     {local_proxy_https,[],
      local_proxy_cases() ++ local_proxy_https_cases()}].

%% internal functions

local_proxy_cases() ->
    [http_head,
     http_get,
     http_options,
     http_trace,
     http_post,
     http_put,
     http_delete,
     http_delete_body,
     http_headers,
     http_proxy_auth,
     http_doesnotexist,
     http_stream,
     http_not_modified_otp_6821].

local_proxy_https_cases() ->
    [https_connect_error].

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

init_per_suite(Config0) ->
    case init_apps(suite_apps(), Config0) of
	Config when is_list(Config) ->
	    make_cert_files(dsa, "server-", Config),
	    Config;
	Other ->
	    Other
    end.

end_per_suite(_Config) ->
    [app_stop(App) || App <- r(suite_apps())],
    ok.

%% internal functions

suite_apps() ->
    [asn1,crypto,public_key].
    
%%--------------------------------------------------------------------

init_per_group(local_proxy, Config) ->
    init_local_proxy([{protocol,http}|Config]);
init_per_group(local_proxy_https, Config) ->
    init_local_proxy([{protocol,https}|Config]).

end_per_group(Group, Config)
  when
      Group =:= local_proxy;
      Group =:= local_proxy_https ->
    rcmd_local_proxy(["stop"], Config),
    Config;
end_per_group(_, Config) ->
    Config.

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

init_per_testcase(Case, Config0) ->
    ct:timetrap({seconds,30}),
    Apps = apps(Case, Config0),
    case init_apps(Apps, Config0) of
	Config when is_list(Config) ->
	    case app_start(inets, Config) of
		ok ->
		    Config;
		Error ->
		    [app_stop(N) || N <- [inets|r(Apps)]],
		    ct:fail({could_not_init_inets,Error})
	    end;
	E3 ->
	    E3
    end.

end_per_testcase(_Case, Config) ->
    app_stop(inets),
    Config.

%% internal functions

apps(_Case, Config) ->
    case proplists:get_value(protocol, Config) of
	https ->
	    [ssl];
	_ ->
	    []
    end.

%%--------------------------------------------------------------------
%% Test Cases --------------------------------------------------------
%%--------------------------------------------------------------------

http_head(doc) ->
    ["Test http/https HEAD request."];
http_head(Config) when is_list(Config) ->
    Method = head,
    URL = url("/index.html", Config),
    Request = {URL,[]},
    HttpOpts = [],
    Opts = [],
    {ok,{{_,200,_},[_|_],[]}} =
	httpc:request(Method, Request, HttpOpts, Opts),
    ok.

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

http_get(doc) ->
    ["Test http/https GET request."];
http_get(Config) when is_list(Config) ->
    Method = get,
    URL = url("/index.html", Config),
    Request = {URL,[]},
    Timeout = timer:seconds(1),
    ConnTimeout = Timeout + timer:seconds(1),

    HttpOpts1 = [{timeout,Timeout},{connect_timeout,ConnTimeout}],
    Opts1 = [],
    {ok,{{_,200,_},[_|_],[_|_]=B1}} =
	httpc:request(Method, Request, HttpOpts1, Opts1),
    inets_test_lib:check_body(B1),

    HttpOpts2 = [],
    Opts2 = [{body_format,binary}],
    {ok,{{_,200,_},[_|_],B2}} =
	httpc:request(Method, Request, HttpOpts2, Opts2),
    inets_test_lib:check_body(binary_to_list(B2)).

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

http_options(doc) ->
    ["Perform an OPTIONS request."];
http_options(Config) when is_list(Config) ->
    Method = options,
    URL = url("/index.html", Config),
    Request = {URL,[]},
    HttpOpts = [],
    Opts = [],
    {ok,{{_,200,_},Headers,_}} =
	httpc:request(Method, Request, HttpOpts, Opts),
    {value,_} = lists:keysearch("allow", 1, Headers),
    ok.

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

http_trace(doc) ->
    ["Perform a TRACE request."];
http_trace(Config) when is_list(Config) ->
    Method = trace,
    URL = url("/index.html", Config),
    Request = {URL,[]},
    HttpOpts = [],
    Opts = [],
    {ok,{{_,200,_},[_|_],"TRACE "++_}} =
	httpc:request(Method, Request, HttpOpts, Opts),
    ok.

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

http_post(doc) ->
    ["Perform a POST request that goes through a proxy. When the "
     "request goes to an ordinary file it seems the POST data "
     "is ignored."];
http_post(Config) when is_list(Config) ->
    Method = post,
    URL = url("/index.html", Config),
    Request = {URL,[],"text/plain","foobar"},
    HttpOpts = [],
    Opts = [],
    {ok,{{_,200,_},[_|_],[_|_]}} =
	httpc:request(Method, Request, HttpOpts, Opts),
    ok.

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

http_put(doc) ->
    ["Perform a PUT request. The server will not allow it "
     "but we only test sending the request."];
http_put(Config) when is_list(Config) ->
    Method = put,
    URL = url("/put.html", Config),
    Content =
	"<html><body> <h1>foo</h1> <p>bar</p> </body></html>",
    Request = {URL,[],"html",Content},
    HttpOpts = [],
    Opts = [],
    {ok,{{_,405,_},[_|_],[_|_]}} =
	httpc:request(Method, Request, HttpOpts, Opts),
    ok.

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

http_delete(doc) ->
    ["Perform a DELETE request that goes through a proxy. Note the server "
     "will reject the request with a 405 Method Not Allowed,"
     "but this is just a test of sending the request."];
http_delete(Config) when is_list(Config) ->
    Method = delete,
    URL = url("/delete.html", Config),
    Request = {URL,[]},
    HttpOpts = [],
    Opts = [],
    {ok,{{_,405,_},[_|_],[_|_]}} =
	httpc:request(Method, Request, HttpOpts, Opts),
    ok.

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

http_delete_body(doc) ->
    ["Perform a DELETE request with a content body. The server will not allow it "
     "but we only test sending the request."];
http_delete_body(Config) when is_list(Config) ->
    Method = delete,
    URL = url("/delete.html", Config),
    Content = "foo=bar",
    Request = {URL,[],"application/x-www-form-urlencoded",Content},
    HttpOpts = [],
    Opts = [],
    {ok,{{_,405,_},[_|_],[_|_]}} =
    httpc:request(Method, Request, HttpOpts, Opts),
    ok.

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

http_headers(doc) ->
    ["Use as many request headers as possible"];
http_headers(Config) when is_list(Config) ->
    Method = get,
    URL = url("/index.html", Config),
    Headers =
	[{"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"}],
    Request = {URL,Headers},
    HttpOpts = [],
    Opts = [],
    {ok,{{_,200,_},[_|_],[_|_]}} =
	httpc:request(Method, Request, HttpOpts, Opts),
    ok.

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

http_proxy_auth(doc) ->
    ["Test the code for sending of proxy authorization."];
http_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!
    Method = get,
    URL = url("/index.html", Config),
    Request = {URL,[]},
    HttpOpts = [{proxy_auth,{"foo","bar"}}],
    Opts = [],
    {ok,{{_,200,_},[_|_],[_|_]}} =
	httpc:request(Method, Request, HttpOpts, Opts),
    ok.

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

http_doesnotexist(doc) ->
    ["Test that we get a 404 when the page is not found."];
http_doesnotexist(Config) when is_list(Config) ->
    Method = get,
    URL = url("/doesnotexist.html", Config),
    Request = {URL,[]},
    HttpOpts = [{proxy_auth,{"foo","bar"}}],
    Opts = [],
    {ok,{{_,404,_},[_|_],[_|_]}} =
	httpc:request(Method, Request, HttpOpts, Opts),
    ok.

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

http_stream(doc) ->
    ["Test the option stream for asynchronous requests"];
http_stream(Config) when is_list(Config) ->
    Method = get,
    URL = url("/index.html", Config),
    Request = {URL,[]},
    HttpOpts = [],

    Opts1 = [{body_format,binary}],
    {ok,{{_,200,_},[_|_],Body}} =
	httpc:request(Method, Request, HttpOpts, Opts1),

    Opts2 = [{sync,false},{stream,self}],
    {ok,RequestId} =
	httpc:request(Method, Request, HttpOpts, Opts2),
    receive
	{http,{RequestId,stream_start,[_|_]}} ->
	    ok
    end,
    case http_stream(RequestId, <<>>) of
	Body -> ok
    end.
    %% StreamedBody = http_stream(RequestId, <<>>),
    %% Body =:= StreamedBody,
    %% ok.

http_stream(RequestId, Body) ->
    receive
	{http,{RequestId,stream,Bin}} ->
	    http_stream(RequestId, <<Body/binary,Bin/binary>>);
	{http,{RequestId,stream_end,_Headers}} ->
	    Body
    end.

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

http_emulate_lower_versions(doc) ->
    ["Perform requests as 0.9 and 1.0 clients."];
http_emulate_lower_versions(Config) when is_list(Config) ->
    Method = get,
    URL = url("/index.html", Config),
    Request = {URL,[]},
    Opts = [],

    HttpOpts1 = [{version,"HTTP/0.9"}],
    {ok,[_|_]=B1} =
	httpc:request(Method, Request, HttpOpts1, Opts),
    inets_test_lib:check_body(B1),

    HttpOpts2 = [{version,"HTTP/1.0"}],
    {ok,{{_,200,_},[_|_],[_|_]=B2}} =
	httpc:request(Method, Request, HttpOpts2, Opts),
    inets_test_lib:check_body(B2),

    HttpOpts3 = [{version,"HTTP/1.1"}],
    {ok,{{_,200,_},[_|_],[_|_]=B3}} =
	httpc:request(Method, Request, HttpOpts3, Opts),
    inets_test_lib:check_body(B3),

    ok.

%%--------------------------------------------------------------------
http_not_modified_otp_6821(doc) ->
    ["If unmodified no body should be returned"];
http_not_modified_otp_6821(Config) when is_list(Config) ->
    Method = get,
    URL = url("/index.html", Config),
    Opts = [],

    Request1 = {URL,[]},
    HttpOpts1 = [],
    {ok,{{_,200,_},ReplyHeaders,[_|_]}} =
	 httpc:request(Method, Request1, HttpOpts1, Opts),
     ETag = header_value("etag", ReplyHeaders),
     LastModified = header_value("last-modified", ReplyHeaders),

     Request2 =
	 {URL,
	  [{"If-None-Match",ETag},
	   {"If-Modified-Since",LastModified}]},
     HttpOpts2 = [{timeout,15000}], % Limit wait for bug result
     {ok,{{_,304,_},_,[]}} = % Page Unchanged
	 httpc:request(Method, Request2, HttpOpts2, Opts),

     ok.

header_value(Name, [{HeaderName,HeaderValue}|Headers]) ->
    case string:to_lower(HeaderName) of
	Name ->
	    HeaderValue;
	_ ->
	    header_value(Name, Headers)
    end.

%%--------------------------------------------------------------------
https_connect_error(doc) ->
    ["Error from CONNECT tunnel should be returned"];
https_connect_error(Config) when is_list(Config) ->
    {HttpServer,HttpPort} = proplists:get_value(http, Config),
    Method = get,
    %% using HTTPS scheme with HTTP port to trigger connection error
    URL = "https://" ++ HttpServer ++ ":" ++
        integer_to_list(HttpPort) ++ "/index.html",
    Opts = [],
    HttpOpts = [],
    Request = {URL,[]},
    {error,{failed_connect,[_,{tls,_,_}]}} =
	httpc:request(Method, Request, HttpOpts, Opts).

%%--------------------------------------------------------------------
%% Internal Functions ------------------------------------------------
%%--------------------------------------------------------------------

init_apps([], Config) ->
    Config;
init_apps([App|Apps], Config) ->
    case app_start(App, Config) of
	ok ->
	    init_apps(Apps, Config);
	Error ->
	    Msg =
		lists:flatten(
		  io_lib:format(
		    "Could not start ~p due to ~p.~n",
		    [App, Error])),
	    {skip,Msg}
    end.

app_start(App, Config) ->
    try
	case App of
	    crypto ->
		crypto:stop(),
		ok = crypto:start();
	    inets ->
		application:stop(App),
		ok = application:start(App),
		case proplists:get_value(proxy, Config) of
		    undefined -> ok;
		    {_,ProxySpec} ->
			ok = httpc:set_options([{proxy,ProxySpec}])
		end;
	    _ ->
		application:stop(App),
		ok = application:start(App)
	end
    catch
	Class:Reason ->
	    {exception,Class,Reason}
    end.

app_stop(App) ->
    application:stop(App).

make_cert_files(Alg, Prefix, Config) ->
    PrivDir = proplists:get_value(priv_dir, Config),
    CaInfo = {CaCert,_} = erl_make_certs:make_cert([{key,Alg}]),
    {Cert,CertKey} = erl_make_certs:make_cert([{key,Alg},{issuer,CaInfo}]),
    CaCertFile = filename:join(PrivDir, Prefix++"cacerts.pem"),
    CertFile = filename:join(PrivDir, Prefix++"cert.pem"),
    KeyFile = filename:join(PrivDir, Prefix++"key.pem"),
    der_to_pem(CaCertFile, [{'Certificate', CaCert, not_encrypted}]),
    der_to_pem(CertFile, [{'Certificate', Cert, not_encrypted}]),
    der_to_pem(KeyFile, [CertKey]),
    ok.

der_to_pem(File, Entries) ->
    PemBin = public_key:pem_encode(Entries),
    file:write_file(File, PemBin).



url(AbsPath, Config) ->
    Protocol = proplists:get_value(protocol, Config),
    {ServerName,ServerPort} = proplists:get_value(Protocol, Config),
    atom_to_list(Protocol) ++ "://" ++
	ServerName ++ ":" ++ integer_to_list(ServerPort) ++
	AbsPath.

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

init_local_proxy(Config) ->
    case os:type() of
	{unix,_} ->
	    case rcmd_local_proxy(["start"], Config) of
		{0,[":STARTED:"++String]} ->
		    init_local_proxy_string(String, Config);
		{_,[":SKIP:"++_|_]}=Reason ->
		    {skip,Reason};
		Error ->
		    rcmd_local_proxy(["stop"], Config),
		    ct:fail({local_proxy_start_failed,Error})
	    end;
	_ ->
	    {skip,"Platform cannot run local proxy start script"}
    end.

init_local_proxy_string(String, Config) ->
    {Proxy,Server} = split($|, String),
    {ProxyName,ProxyPort} = split($:, Proxy),
    {ServerName,ServerPorts} = split($:, Server),
    {ServerHttpPort,ServerHttpsPort} = split($:, ServerPorts),
    [{proxy,{local,{{ProxyName,list_to_integer(ProxyPort)},[]}}},
     {http,{ServerName,list_to_integer(ServerHttpPort)}},
     {https,{ServerName,list_to_integer(ServerHttpsPort)}}
     |Config].

rcmd_local_proxy(Args, Config) ->
    DataDir = proplists:get_value(data_dir, Config),
    PrivDir = proplists:get_value(priv_dir, Config),
    Script = filename:join(DataDir, ?LOCAL_PROXY_SCRIPT),
    rcmd(Script, Args, [{cd,PrivDir}]).

rcmd(Cmd, Args, Opts) ->
    Port =
	erlang:open_port(
	  {spawn_executable,Cmd},
	  [{args,Args},{line,80},exit_status,eof,hide|Opts]),
    rcmd_loop(Port, [], [], undefined, false).

rcmd_loop(Port, Lines, Buf, Exit, EOF) ->
    receive
	{Port,{data,{Flag,Line}}} ->
	    case Flag of
		noeol ->
		    rcmd_loop(Port, Lines, r(Line, Buf), Exit, EOF);
		eol ->
		    rcmd_loop(Port, [r(Buf, Line)|Lines], [], Exit, EOF)
	    end;
	{Port,{exit_status,Status}} when Exit =:= undefined ->
	    case EOF of
		true ->
		    rcmd_close(Port, Lines, Buf, Status);
		false ->
		    rcmd_loop(Port, Lines, Buf, Status, EOF)
	    end;
	{Port,eof} when EOF =:= false ->
	    case Exit of
		undefined ->
		    rcmd_loop(Port, Lines, Buf, Exit, true);
		Status ->
		    rcmd_close(Port, Lines, Buf, Status)
	    end;
	{Port,_}=Unexpected ->
	    ct:fail({unexpected_from_port,Unexpected})
    end.

rcmd_close(Port, Lines, Buf, Status) ->
    catch port_close(Port),
    case Buf of
	[] ->
	    {Status,Lines};
	_ ->
	    {Status,[r(Buf)|Lines]}
    end.

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

%% Split on first match of X in Ys, do not include X in neither part
split(X, Ys) ->
    split(X, Ys, []).
%%
split(X, [X|Ys], Rs) ->
    {r(Rs),Ys};
split(X, [Y|Ys], Rs) ->
    split(X, Ys, [Y|Rs]).

r(L) -> lists:reverse(L).
r(L, R) -> lists:reverse(L, R).