diff options
Diffstat (limited to 'lib/inets')
45 files changed, 4115 insertions, 4986 deletions
diff --git a/lib/inets/doc/src/http_uri.xml b/lib/inets/doc/src/http_uri.xml index bd31ae42d2..d9e8587bbf 100644 --- a/lib/inets/doc/src/http_uri.xml +++ b/lib/inets/doc/src/http_uri.xml @@ -4,7 +4,7 @@ <erlref> <header> <copyright> - <year>2012</year><year>2012</year> + <year>2012</year><year>2013</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -55,7 +55,8 @@ string() = list of ASCII characters <p>For more information about URI, see RFC 3986. </p> <code type="none"><![CDATA[ -uri() = string() - Syntax according to the URI definition in rfc 3986, ex: "http://www.erlang.org/" +uri() = string() - Syntax according to the URI definition in rfc 3986, + e.g.: "http://www.erlang.org/" user_info() = string() scheme() = atom() - Example: http, https host() = string() diff --git a/lib/inets/doc/src/httpc.xml b/lib/inets/doc/src/httpc.xml index 14ce3cbe7f..741f2abaef 100644 --- a/lib/inets/doc/src/httpc.xml +++ b/lib/inets/doc/src/httpc.xml @@ -43,8 +43,12 @@ cookies and other options that can be applied to more than one request. </p> - <p>If the scheme - https is used the ssl application needs to be started.</p> + <p>If the scheme https is used the ssl application needs to be + started. When https links needs to go through a proxy the + CONNECT method extension to HTTP-1.1 is used to establish a + tunnel and then the connection is upgraded to TLS, + however "TLS upgrade" according to RFC 2817 is not + supported.</p> <p>Also note that pipelining will only be used if the pipeline timeout is set, otherwise persistent connections without @@ -449,7 +453,8 @@ apply(Module, Function, [ReplyInfo | Args]) <type> <v>Options = [Option]</v> <v>Option = {proxy, {Proxy, NoProxy}} | - {max_sessions, MaxSessions} | + {https_proxy, {Proxy, NoProxy}} | + {max_sessions, MaxSessions} | {max_keep_alive_length, MaxKeepAlive} | {keep_alive_timeout, KeepAliveTimeout} | {max_pipeline_length, MaxPipeline} | @@ -460,25 +465,23 @@ apply(Module, Function, [ReplyInfo | Args]) {port, Port} | {socket_opts, socket_opts()} | {verbose, VerboseMode} </v> + <v>Proxy = {Hostname, Port}</v> <v>Hostname = string() </v> <d>ex: "localhost" or "foo.bar.se"</d> <v>Port = integer()</v> <d>ex: 8080 </d> - <v>socket_opts() = [socket_opt()]</v> - <d>The options are appended to the socket options used by the - client. </d> - <d>These are the default values when a new request handler - is started (for the initial connect). They are passed directly - to the underlying transport (gen_tcp or ssl) <em>without</em> - verification! </d> <v>NoProxy = [NoProxyDesc]</v> <v>NoProxyDesc = DomainDesc | HostName | IPDesc</v> <v>DomainDesc = "*.Domain"</v> <d>ex: "*.ericsson.se"</d> <v>IpDesc = string()</v> <d>ex: "134.138" or "[FEDC:BA98" (all IP-addresses starting with 134.138 or FEDC:BA98), "66.35.250.150" or "[2010:836B:4179::836B:4179]" (a complete IP-address).</d> - <v>MaxSessions = integer() </v> + + <d>proxy defaults to {undefined, []} e.i. no proxy is configured and https_proxy defaults to + the value of proxy.</d> + + <v>MaxSessions = integer() </v> <d>Default is <c>2</c>. Maximum number of persistent connections to a host.</d> <v>MaxKeepAlive = integer() </v> @@ -520,6 +523,13 @@ apply(Module, Function, [ReplyInfo | Args]) <v>Port = integer() </v> <d>Specify which local port number to use. See <seealso marker="kernel:gen_tcp#connect">gen_tcp:connect/3,4</seealso> for more info. </d> + <v>socket_opts() = [socket_opt()]</v> + <d>The options are appended to the socket options used by the + client. </d> + <d>These are the default values when a new request handler + is started (for the initial connect). They are passed directly + to the underlying transport (gen_tcp or ssl) <em>without</em> + verification! </d> <v>VerboseMode = false | verbose | debug | trace </v> <d>Default is <c>false</c>. This option is used to switch on (or off) @@ -554,7 +564,8 @@ apply(Module, Function, [ReplyInfo | Args]) <fsummary>Gets the currently used options.</fsummary> <type> <v>OptionItems = all | [option_item()]</v> - <v>option_item() = proxy | + <v>option_item() = proxy | + https_proxy max_sessions | keep_alive_timeout | max_keep_alive_length | diff --git a/lib/inets/doc/src/httpd.xml b/lib/inets/doc/src/httpd.xml index 3fced5dfcd..8438961511 100644 --- a/lib/inets/doc/src/httpd.xml +++ b/lib/inets/doc/src/httpd.xml @@ -4,7 +4,7 @@ <erlref> <header> <copyright> - <year>1997</year><year>2012</year> + <year>1997</year><year>2013</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -163,11 +163,9 @@ </item> <marker id="prop_socket_type"></marker> - <tag>{socket_type, ip_comm | ssl | essl}</tag> + <tag>{socket_type, ip_comm | {essl, Config::proplist()}}</tag> <item> - <p>When using ssl, there are currently only one alternative. - <c>essl</c> specifically uses the Erlang based SSL. - <c>ssl</c> defaults to <c>essl</c>. </p> + <p> For ssl configuration options see <seealso marker="ssl:ssl#listen-2">ssl:listen/2</seealso> </p> <p>Defaults to <c>ip_comm</c>. </p> </item> @@ -395,71 +393,7 @@ bytes </item> </taglist> - - <marker id="props_ssl"></marker> - <p><em>ssl properties</em></p> - <taglist> - <marker id="prop_ssl_ca_cert_file"></marker> - <tag>{ssl_ca_certificate_file, path()}</tag> - <item> - <p>Used as cacertfile option in ssl:listen/2 see - <seealso marker="ssl:ssl">ssl(3)</seealso>. </p> - </item> - - <marker id="prop_ssl_cert_file"></marker> - <tag>{ssl_certificate_file, path()}</tag> - <item> - <p>Used as certfile option in ssl:listen/2 see - <seealso marker="ssl:ssl">ssl(3)</seealso>. </p> - </item> - - <marker id="prop_ssl_ciphers"></marker> - <tag>{ssl_ciphers, list()}</tag> - <item> - <p>Used as ciphers option in ssl:listen/2 see - <seealso marker="ssl:ssl">ssl(3)</seealso>. </p> - </item> - - <marker id="prop_ssl_verify_client"></marker> - <tag>{ssl_verify_client, integer()}</tag> - <item> - <p>Used as verify option in ssl:listen/2 see - <seealso marker="ssl:ssl">ssl(3)</seealso>. </p> - </item> - - <marker id="prop_ssl_verify_depth"></marker> - <tag>{ssl_verify_depth, integer()}</tag> - <item> - <p>Used as depth option in ssl:listen/2 see - <seealso marker="ssl:ssl">ssl(3)</seealso>. </p> - </item> - - <marker id="prop_ssl_passwd_callback_funct"></marker> - <tag>{ssl_password_callback_function, atom()}</tag> - <item> - <p>Used together with ssl_password_callback_module - to retrieve a value to use as password option to ssl:listen/2 - see <seealso marker="ssl:ssl">ssl(3)</seealso>. </p> - </item> - - <marker id="prop_ssl_passwd_callback_args"></marker> - <tag>{ssl_password_callback_arguments, list()}</tag> - <item> - <p>Used together with ssl_password_callback_function to supply a - list of arguments to the callback function. If not specified - the callback function will be assumed to have arity 0. </p> - </item> - - <marker id="prop_ssl_passwd_callback_mod"></marker> - <tag>{ssl_password_callback_module, atom()}</tag> - <item> - <p>Used together with ssl_password_callback_function - to retrieve a value to use as password option to ssl:listen/2 - see <seealso marker="ssl:ssl">ssl(3)</seealso>. </p> - </item> - - </taglist> - + <marker id="props_alias"></marker> <p><em>URL aliasing properties - requires mod_alias</em></p> <taglist> diff --git a/lib/inets/doc/src/notes.xml b/lib/inets/doc/src/notes.xml index 80c06ffadd..f6bb2cca49 100644 --- a/lib/inets/doc/src/notes.xml +++ b/lib/inets/doc/src/notes.xml @@ -4,7 +4,7 @@ <chapter> <header> <copyright> - <year>2002</year><year>2012</year> + <year>2002</year><year>2013</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -32,37 +32,156 @@ <file>notes.xml</file> </header> - - <section><title>Inets 5.9.2.2</title> + <section><title>Inets 5.9.5</title> + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Reverted incorrect commit that broke cookie handling when + using httpc-profiles.</p> + <p> + Own Id: OTP-10956</p> + </item> + </list> + </section> <section><title>Improvements and New Features</title> <list> <item> <p> - Make log_alert configurable as option in ssl, SSLLogLevel - added as option to inets conf file</p> + Fix http_request:http_headers/1 to send content-length + when length is zero. Thanks to CA Meijer.</p> <p> - Own Id: OTP-11259</p> + Own Id: OTP-10934</p> + </item> + <item> + <p> + Integrate elliptic curve contribution from Andreas + Schultz </p> + <p> + In order to be able to support elliptic curve cipher + suites in SSL/TLS, additions to handle elliptic curve + infrastructure has been added to public_key and crypto.</p> + <p> + This also has resulted in a rewrite of the crypto API to + gain consistency and remove unnecessary overhead. All OTP + applications using crypto has been updated to use the new + API.</p> + <p> + Impact: Elliptic curve cryptography (ECC) offers + equivalent security with smaller key sizes than other + public key algorithms. Smaller key sizes result in + savings for power, memory, bandwidth, and computational + cost that make ECC especially attractive for constrained + environments.</p> + <p> + Own Id: OTP-11009</p> + </item> + <item> + <p> + Fix {stream, {self, once}} in httpc to work as expected. + Thanks to Masatake Daimon</p> + <p> + Own Id: OTP-11122</p> </item> </list> </section> </section> -<section><title>Inets 5.9.2.1</title> +<section><title>Inets 5.9.4</title> + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + httpd: The modules option now defaults to the documented + value.</p> + <p> + Own Id: OTP-10844</p> + </item> + <item> + <p> + httpc: Fixed persistent connection implementation that + was broken by a patch to R13. The patch made persisten + connections behaved the same way as pipelining.</p> + <p> + Own Id: OTP-10845</p> + </item> + <item> + <p> + httpd: Simplified configuration of ssl in httpd, this + also enables all ssl options to be configured. The old + and limited way is no longer documented but will be + supported for backwards comatibility for some time.</p> + <p> + Own Id: OTP-10846</p> + </item> + <item> + <p> + Handle correctly the "No files found or file unavailable" + error code. Thanks to Serge Aleynikov</p> + <p> + Own Id: OTP-10886</p> + </item> + </list> + </section> + +</section> + +<section><title>Inets 5.9.3</title> <section><title>Improvements and New Features</title> <list> <item> <p> - Fixed obsolete error report in inets.</p> + httpc: The HTTP client now supports HTTPS through proxies</p> <p> - Own Id: OTP-11185 Aux Id: seq12357 </p> + Own Id: OTP-10256 Aux Id: kunagi-2 + [ce2e800e-c99f-4050-a1c4-f47023d9c7aa-1] </p> + </item> + <item> + <p> Some examples overflowing the width of PDF pages have + been corrected. </p> + <p> + Own Id: OTP-10665</p> + </item> + <item> + <p> + Fix autoredirect for POST requests responding 303. Thanks + to Hans Svensson.</p> + <p> + Own Id: OTP-10765</p> </item> </list> </section> </section> +<section><title>Inets 5.9.2.2</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Make log_alert configurable as option in ssl, SSLLogLevel + added as option to inets conf file</p> + <p> + Own Id: OTP-11259</p> + </item> + </list> + </section> +</section> +<section><title>Inets 5.9.2.1</title> + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Fixed obsolete error report in inets.</p> + <p> + Own Id: OTP-11185 Aux Id: seq12357 </p> + </item> + </list> + </section> +</section> <section><title>Inets 5.9.2</title> diff --git a/lib/inets/src/ftp/ftp.erl b/lib/inets/src/ftp/ftp.erl index fe25c23316..132a384a49 100644 --- a/lib/inets/src/ftp/ftp.erl +++ b/lib/inets/src/ftp/ftp.erl @@ -1899,6 +1899,10 @@ ctrl_result_response(pos_compl, #state{client = From} = State, _) -> gen_server:reply(From, ok), {noreply, State#state{client = undefined, caller = undefined}}; +ctrl_result_response(enofile, #state{client = From} = State, _) -> + gen_server:reply(From, {error, enofile}), + {noreply, State#state{client = undefined, caller = undefined}}; + ctrl_result_response(Status, #state{client = From} = State, _) when (Status =:= etnospc) orelse (Status =:= epnospc) orelse diff --git a/lib/inets/src/ftp/ftp_response.erl b/lib/inets/src/ftp/ftp_response.erl index faeacb31ab..4bf788e946 100644 --- a/lib/inets/src/ftp/ftp_response.erl +++ b/lib/inets/src/ftp/ftp_response.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2009. All Rights Reserved. +%% Copyright Ericsson AB 2005-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -162,6 +162,7 @@ error_string(epath) -> "No such file or directory, already exists, " error_string(etype) -> "No such type."; error_string(euser) -> "User name or password not valid."; error_string(etnospc) -> "Insufficient storage space in system."; +error_string(enofile) -> "No files found or file unavailable"; error_string(epnospc) -> "Exceeded storage allocation " "(for current directory or dataset)."; error_string(efnamena) -> "File name not allowed."; @@ -180,6 +181,8 @@ interpret_status(?POS_COMPL,_,_) -> pos_compl; interpret_status(?POS_INTERM,?AUTH_ACC,2) -> pos_interm_acct; %% Positive Intermediate Reply interpret_status(?POS_INTERM,_,_) -> pos_interm; +%% No files found or file not available +interpret_status(?TRANS_NEG_COMPL,?FILE_SYSTEM,0) -> enofile; %% No storage area no action taken interpret_status(?TRANS_NEG_COMPL,?FILE_SYSTEM,2) -> etnospc; %% Temporary Error, no action taken diff --git a/lib/inets/src/http_client/httpc.erl b/lib/inets/src/http_client/httpc.erl index b6e7708353..41bba7995e 100644 --- a/lib/inets/src/http_client/httpc.erl +++ b/lib/inets/src/http_client/httpc.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2009-2012. All Rights Reserved. +%% Copyright Ericsson AB 2009-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -163,8 +163,13 @@ request(Method, {error, Reason} -> {error, Reason}; {ok, ParsedUrl} -> - handle_request(Method, Url, ParsedUrl, Headers, [], [], - HTTPOptions, Options, Profile) + case header_parse(Headers) of + {error, Reason} -> + {error, Reason}; + _ -> + handle_request(Method, Url, ParsedUrl, Headers, [], [], + HTTPOptions, Options, Profile) + end end; request(Method, @@ -917,6 +922,10 @@ validate_options([{proxy, Proxy} = Opt| Tail], Acc) -> validate_proxy(Proxy), validate_options(Tail, [Opt | Acc]); +validate_options([{https_proxy, Proxy} = Opt| Tail], Acc) -> + validate_https_proxy(Proxy), + validate_options(Tail, [Opt | Acc]); + validate_options([{max_sessions, Value} = Opt| Tail], Acc) -> validate_max_sessions(Value), validate_options(Tail, [Opt | Acc]); @@ -979,6 +988,14 @@ validate_proxy({{ProxyHost, ProxyPort}, NoProxy} = Proxy) validate_proxy(BadProxy) -> bad_option(proxy, BadProxy). +validate_https_proxy({{ProxyHost, ProxyPort}, NoProxy} = Proxy) + when is_list(ProxyHost) andalso + is_integer(ProxyPort) andalso + is_list(NoProxy) -> + Proxy; +validate_https_proxy(BadProxy) -> + bad_option(https_proxy, BadProxy). + validate_max_sessions(Value) when is_integer(Value) andalso (Value >= 0) -> Value; validate_max_sessions(BadValue) -> @@ -1235,7 +1252,12 @@ uri_parse(URI, Opts) -> %%-------------------------------------------------------------------------- - +header_parse([]) -> + ok; +header_parse([{Field, Value}|T]) when is_list(Field), is_list(Value) -> + header_parse(T); +header_parse(_) -> + {error, {headers_error, not_strings}}. child_name2info(undefined) -> {error, no_such_service}; child_name2info(httpc_manager) -> diff --git a/lib/inets/src/http_client/httpc_handler.erl b/lib/inets/src/http_client/httpc_handler.erl index 923213d34d..55794f57dc 100644 --- a/lib/inets/src/http_client/httpc_handler.erl +++ b/lib/inets/src/http_client/httpc_handler.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2002-2012. All Rights Reserved. +%% Copyright Ericsson AB 2002-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -29,44 +29,43 @@ %%-------------------------------------------------------------------- %% Internal Application API -export([ - start_link/4, - %% connect_and_send/2, - send/2, - cancel/3, - stream/3, - stream_next/1, - info/1 - ]). + start_link/4, + %% connect_and_send/2, + send/2, + cancel/3, + stream_next/1, + info/1 + ]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3]). + terminate/2, code_change/3]). -record(timers, - { - request_timers = [], % [ref()] - queue_timer % ref() - }). + { + request_timers = [], % [ref()] + queue_timer % ref() + }). -record(state, - { - request, % #request{} - session, % #session{} - status_line, % {Version, StatusCode, ReasonPharse} - headers, % #http_response_h{} - body, % binary() - mfa, % {Module, Function, Args} - pipeline = queue:new(), % queue() - keep_alive = queue:new(), % queue() - status, % undefined | new | pipeline | keep_alive | close | ssl_tunnel - canceled = [], % [RequestId] - max_header_size = nolimit, % nolimit | integer() - max_body_size = nolimit, % nolimit | integer() - options, % #options{} - timers = #timers{}, % #timers{} - profile_name, % atom() - id of httpc_manager process. - once % send | undefined - }). + { + request, % #request{} + session, % #session{} + status_line, % {Version, StatusCode, ReasonPharse} + headers, % #http_response_h{} + body, % binary() + mfa, % {Module, Function, Args} + pipeline = queue:new(), % queue() + keep_alive = queue:new(), % queue() + status, % undefined | new | pipeline | keep_alive | close | {ssl_tunnel, Request} + canceled = [], % [RequestId] + max_header_size = nolimit, % nolimit | integer() + max_body_size = nolimit, % nolimit | integer() + options, % #options{} + timers = #timers{}, % #timers{} + profile_name, % atom() - id of httpc_manager process. + once = inactive % inactive | once + }). %%==================================================================== @@ -75,8 +74,8 @@ %%-------------------------------------------------------------------- %% Function: start_link(Request, Options, ProfileName) -> {ok, Pid} %% -%% Request = #request{} -%% Options = #options{} +%% Request = #request{} +%% Options = #options{} %% ProfileName = atom() - id of httpc manager process %% %% Description: Starts a http-request handler process. Intended to be @@ -96,11 +95,11 @@ start_link(Parent, Request, Options, ProfileName) -> {ok, proc_lib:start_link(?MODULE, init, [[Parent, Request, Options, - ProfileName]])}. + ProfileName]])}. %%-------------------------------------------------------------------- %% Function: send(Request, Pid) -> ok -%% Request = #request{} +%% Request = #request{} %% Pid = pid() - the pid of the http-request handler process. %% %% Description: Uses this handlers session to send a request. Intended @@ -112,7 +111,7 @@ send(Request, Pid) -> %%-------------------------------------------------------------------- %% Function: cancel(RequestId, Pid) -> ok -%% RequestId = ref() +%% RequestId = ref() %% Pid = pid() - the pid of the http-request handler process. %% %% Description: Cancels a request. Intended to be called by the httpc @@ -142,12 +141,16 @@ stream_next(Pid) -> %% Used for debugging and testing %%-------------------------------------------------------------------- info(Pid) -> - call(info, Pid). - + try + call(info, Pid) + catch + _:_ -> + [] + end. %%-------------------------------------------------------------------- %% Function: stream(BodyPart, Request, Code) -> _ -%% BodyPart = binary() +%% BodyPart = binary() %% Request = #request{} %% Code = integer() %% @@ -167,7 +170,7 @@ stream(BodyPart, #request{stream = Self} = Request, Code) ((Self =:= self) orelse (Self =:= {self, once})) -> ?hcrt("stream - self", [{stream, Self}, {code, Code}]), httpc_response:send(Request#request.from, - {Request#request.id, stream, BodyPart}), + {Request#request.id, stream, BodyPart}), {<<>>, Request}; %% Stream to file @@ -177,11 +180,11 @@ stream(BodyPart, #request{stream = Filename} = Request, Code) when ((Code =:= 200) orelse (Code =:= 206)) andalso is_list(Filename) -> ?hcrt("stream - filename", [{stream, Filename}, {code, Code}]), case file:open(Filename, [write, raw, append, delayed_write]) of - {ok, Fd} -> - ?hcrt("stream - file open ok", [{fd, Fd}]), - stream(BodyPart, Request#request{stream = Fd}, 200); - {error, Reason} -> - exit({stream_to_file_failed, Reason}) + {ok, Fd} -> + ?hcrt("stream - file open ok", [{fd, Fd}]), + stream(BodyPart, Request#request{stream = Fd}, 200); + {error, Reason} -> + exit({stream_to_file_failed, Reason}) end; %% Stream to file @@ -189,10 +192,10 @@ stream(BodyPart, #request{stream = Fd} = Request, Code) when ((Code =:= 200) orelse (Code =:= 206)) -> ?hcrt("stream to file", [{stream, Fd}, {code, Code}]), case file:write(Fd, BodyPart) of - ok -> - {<<>>, Request}; - {error, Reason} -> - exit({stream_to_file_failed, Reason}) + ok -> + {<<>>, Request}; + {error, Reason} -> + exit({stream_to_file_failed, Reason}) end; stream(BodyPart, Request,_) -> % only 200 and 206 responses can be streamed @@ -208,7 +211,7 @@ stream(BodyPart, Request,_) -> % only 200 and 206 responses can be streamed %% Function: init([Options, ProfileName]) -> {ok, State} | %% {ok, State, Timeout} | ignore | {stop, Reason} %% -%% Options = #options{} +%% Options = #options{} %% ProfileName = atom() - id of httpc manager process %% %% Description: Initiates the httpc_handler process @@ -224,20 +227,21 @@ init([Parent, Request, Options, ProfileName]) -> %% Do not let initial tcp-connection block the manager-process proc_lib:init_ack(Parent, self()), handle_verbose(Options#options.verbose), - Address = handle_proxy(Request#request.address, Options#options.proxy), + ProxyOptions = handle_proxy_options(Request#request.scheme, Options), + Address = handle_proxy(Request#request.address, ProxyOptions), {ok, State} = - case {Address /= Request#request.address, Request#request.scheme} of - {true, https} -> - Error = https_through_proxy_is_not_currently_supported, - self() ! {init_error, - Error, httpc_response:error(Request, Error)}, - {ok, #state{request = Request, options = Options, - status = ssl_tunnel}}; - {_, _} -> - connect_and_send_first_request(Address, Request, - #state{options = Options, - profile_name = ProfileName}) - end, + %% #state.once should initially be 'inactive' because we + %% activate the socket at first regardless of the state. + case {Address /= Request#request.address, Request#request.scheme} of + {true, https} -> + connect_and_send_upgrade_request(Address, Request, + #state{options = Options, + profile_name = ProfileName}); + {_, _} -> + connect_and_send_first_request(Address, Request, + #state{options = Options, + profile_name = ProfileName}) + end, gen_server:enter_loop(?MODULE, [], State). %%-------------------------------------------------------------------- @@ -250,147 +254,133 @@ init([Parent, Request, Options, ProfileName]) -> %% Description: Handling call messages %%-------------------------------------------------------------------- handle_call(#request{address = Addr} = Request, _, - #state{status = Status, - session = #session{type = pipeline} = Session, - timers = Timers, - options = #options{proxy = Proxy} = _Options, - profile_name = ProfileName} = State) + #state{status = Status, + session = #session{type = pipeline} = Session, + timers = Timers, + options = #options{proxy = Proxy} = _Options, + profile_name = ProfileName} = State0) when Status =/= undefined -> ?hcrv("new request on a pipeline session", - [{request, Request}, - {profile, ProfileName}, - {status, Status}, - {timers, Timers}]), + [{request, Request}, + {profile, ProfileName}, + {status, Status}, + {timers, Timers}]), Address = handle_proxy(Addr, Proxy), case httpc_request:send(Address, Session, Request) of ok -> - ?hcrd("request sent", []), - - %% Activate the request time out for the new request - NewState = - activate_request_timeout(State#state{request = Request}), - - ClientClose = - httpc_request:is_client_closing(Request#request.headers), - - case State#state.request of - #request{} -> %% Old request not yet finished - ?hcrd("old request still not finished", []), - %% Make sure to use the new value of timers in state - NewTimers = NewState#state.timers, - NewPipeline = queue:in(Request, State#state.pipeline), - NewSession = - Session#session{queue_length = - %% Queue + current - queue:len(NewPipeline) + 1, - client_close = ClientClose}, - insert_session(NewSession, ProfileName), - ?hcrd("session updated", []), - {reply, ok, State#state{pipeline = NewPipeline, - session = NewSession, - timers = NewTimers}}; - undefined -> - %% Note: tcp-message receiving has already been - %% activated by handle_pipeline/2. - ?hcrd("no current request", []), - cancel_timer(Timers#timers.queue_timer, - timeout_queue), - NewSession = - Session#session{queue_length = 1, - client_close = ClientClose}, - httpc_manager:insert_session(NewSession, ProfileName), - Relaxed = - (Request#request.settings)#http_options.relaxed, - MFA = {httpc_response, parse, - [State#state.max_header_size, Relaxed]}, - NewTimers = Timers#timers{queue_timer = undefined}, - ?hcrd("session created", []), - {reply, ok, NewState#state{request = Request, - session = NewSession, - mfa = MFA, - timers = NewTimers}} - end; - {error, Reason} -> - ?hcri("failed sending request", [{reason, Reason}]), - {reply, {pipeline_failed, Reason}, State} + ?hcrd("request sent", []), + + %% Activate the request time out for the new request + State1 = + activate_request_timeout(State0#state{request = Request}), + + ClientClose = + httpc_request:is_client_closing(Request#request.headers), + + case State0#state.request of + #request{} = OldRequest -> %% Old request not yet finished + ?hcrd("old request still not finished", []), + %% Make sure to use the new value of timers in state + NewTimers = State1#state.timers, + NewPipeline = queue:in(Request, State1#state.pipeline), + NewSession = + Session#session{queue_length = + %% Queue + current + queue:len(NewPipeline) + 1, + client_close = ClientClose}, + insert_session(NewSession, ProfileName), + ?hcrd("session updated", []), + {reply, ok, State1#state{ + request = OldRequest, + pipeline = NewPipeline, + session = NewSession, + timers = NewTimers}}; + undefined -> + %% Note: tcp-message receiving has already been + %% activated by handle_pipeline/2. + ?hcrd("no current request", []), + cancel_timer(Timers#timers.queue_timer, + timeout_queue), + NewSession = + Session#session{queue_length = 1, + client_close = ClientClose}, + httpc_manager:insert_session(NewSession, ProfileName), + NewTimers = Timers#timers{queue_timer = undefined}, + ?hcrd("session created", []), + State = init_wait_for_response_state(Request, State1#state{session = NewSession, + timers = NewTimers}), + {reply, ok, State} + end; + {error, Reason} -> + ?hcri("failed sending request", [{reason, Reason}]), + {reply, {pipeline_failed, Reason}, State0} end; handle_call(#request{address = Addr} = Request, _, - #state{status = Status, - session = #session{type = keep_alive} = Session, - timers = Timers, - options = #options{proxy = Proxy} = _Options, - profile_name = ProfileName} = State) + #state{status = Status, + session = #session{type = keep_alive} = Session, + timers = Timers, + options = #options{proxy = Proxy} = _Options, + profile_name = ProfileName} = State0) when Status =/= undefined -> ?hcrv("new request on a keep-alive session", - [{request, Request}, - {profile, ProfileName}, - {status, Status}]), - - Address = handle_proxy(Addr, Proxy), - case httpc_request:send(Address, Session, Request) of - ok -> - - ?hcrd("request sent", []), - - %% Activate the request time out for the new request - NewState = - activate_request_timeout(State#state{request = Request}), - - ClientClose = - httpc_request:is_client_closing(Request#request.headers), + [{request, Request}, + {profile, ProfileName}, + {status, Status}]), + + ClientClose = httpc_request:is_client_closing(Request#request.headers), + + case State0#state.request of + #request{} -> %% Old request not yet finished + %% Make sure to use the new value of timers in state + ?hcrd("old request still not finished", []), + NewKeepAlive = queue:in(Request, State0#state.keep_alive), + NewSession = + Session#session{queue_length = + %% Queue + current + queue:len(NewKeepAlive) + 1, + client_close = ClientClose}, + insert_session(NewSession, ProfileName), + ?hcrd("session updated", []), + {reply, ok, State0#state{keep_alive = NewKeepAlive, + session = NewSession}}; + undefined -> + %% Note: tcp-message reciving has already been + %% activated by handle_pipeline/2. + ?hcrd("no current request", []), + cancel_timer(Timers#timers.queue_timer, + timeout_queue), + Address = handle_proxy(Addr, Proxy), + case httpc_request:send(Address, Session, Request) of + ok -> + ?hcrd("request sent", []), - case State#state.request of - #request{} -> %% Old request not yet finished - %% Make sure to use the new value of timers in state - ?hcrd("old request still not finished", []), - NewTimers = NewState#state.timers, - NewKeepAlive = queue:in(Request, State#state.keep_alive), - NewSession = - Session#session{queue_length = - %% Queue + current - queue:len(NewKeepAlive) + 1, - client_close = ClientClose}, - insert_session(NewSession, ProfileName), - ?hcrd("session updated", []), - {reply, ok, State#state{keep_alive = NewKeepAlive, - session = NewSession, - timers = NewTimers}}; - undefined -> - %% Note: tcp-message reciving has already been - %% activated by handle_pipeline/2. - ?hcrd("no current request", []), - cancel_timer(Timers#timers.queue_timer, - timeout_queue), - NewSession = + %% Activate the request time out for the new request + State1 = + activate_request_timeout(State0#state{request = Request}), + NewTimers = State1#state.timers, + NewSession = Session#session{queue_length = 1, client_close = ClientClose}, insert_session(NewSession, ProfileName), - Relaxed = - (Request#request.settings)#http_options.relaxed, - MFA = {httpc_response, parse, - [State#state.max_header_size, Relaxed]}, - {reply, ok, NewState#state{request = Request, - session = NewSession, - mfa = MFA}} - end; - - {error, Reason} -> - ?hcri("failed sending request", [{reason, Reason}]), - {reply, {request_failed, Reason}, State} + State = init_wait_for_response_state(Request, State1#state{session = NewSession, + timers = NewTimers}), + {reply, ok, State}; + {error, Reason} -> + ?hcri("failed sending request", [{reason, Reason}]), + {reply, {request_failed, Reason}, State0} + end end; - handle_call(info, _, State) -> Info = handler_info(State), {reply, Info, State}. - %%-------------------------------------------------------------------- %% Function: handle_cast(Msg, State) -> {noreply, State} | %% {noreply, State, Timeout} | @@ -411,32 +401,34 @@ handle_call(info, _, State) -> %% request as if it was never issued as in this case the request will %% not have been sent. handle_cast({cancel, RequestId, From}, - #state{request = #request{id = RequestId} = Request, - profile_name = ProfileName, - canceled = Canceled} = State) -> + #state{request = #request{id = RequestId} = Request, + profile_name = ProfileName, + canceled = Canceled} = State) -> ?hcrv("cancel current request", [{request_id, RequestId}, - {profile, ProfileName}, - {canceled, Canceled}]), + {profile, ProfileName}, + {canceled, Canceled}]), httpc_manager:request_canceled(RequestId, ProfileName, From), ?hcrv("canceled", []), {stop, normal, State#state{canceled = [RequestId | Canceled], - request = Request#request{from = answer_sent}}}; + request = Request#request{from = answer_sent}}}; handle_cast({cancel, RequestId, From}, - #state{profile_name = ProfileName, - request = #request{id = CurrId}, - canceled = Canceled} = State) -> + #state{profile_name = ProfileName, + request = #request{id = CurrId}, + canceled = Canceled} = State) -> ?hcrv("cancel", [{request_id, RequestId}, - {curr_req_id, CurrId}, - {profile, ProfileName}, - {canceled, Canceled}]), + {curr_req_id, CurrId}, + {profile, ProfileName}, + {canceled, Canceled}]), httpc_manager:request_canceled(RequestId, ProfileName, From), ?hcrv("canceled", []), {noreply, State#state{canceled = [RequestId | Canceled]}}; handle_cast(stream_next, #state{session = Session} = State) -> activate_once(Session), - {noreply, State#state{once = once}}. + %% Inactivate the #state.once here because we don't want + %% next_body_chunk/1 to activate the socket twice. + {noreply, State#state{once = inactive}}. %%-------------------------------------------------------------------- @@ -446,94 +438,129 @@ handle_cast(stream_next, #state{session = Session} = State) -> %% Description: Handling all non call/cast messages %%-------------------------------------------------------------------- handle_info({Proto, _Socket, Data}, - #state{mfa = {Module, Function, Args}, - request = #request{method = Method, - stream = Stream} = Request, - session = Session, - status_line = StatusLine} = State) + #state{mfa = {Module, Function, Args}, + request = #request{method = Method, + stream = Stream} = Request, + session = Session, + status_line = StatusLine} = State) when (Proto =:= tcp) orelse (Proto =:= ssl) orelse (Proto =:= httpc_handler) -> ?hcri("received data", [{proto, Proto}, - {module, Module}, - {function, Function}, - {method, Method}, - {stream, Stream}, - {session, Session}, - {status_line, StatusLine}]), + {module, Module}, + {function, Function}, + {method, Method}, + {stream, Stream}, + {session, Session}, + {status_line, StatusLine}]), FinalResult = - try Module:Function([Data | Args]) of - {ok, Result} -> - ?hcrd("data processed - ok", []), - handle_http_msg(Result, State); - {_, whole_body, _} when Method =:= head -> - ?hcrd("data processed - whole body", []), - handle_response(State#state{body = <<>>}); - {Module, whole_body, [Body, Length]} -> - ?hcrd("data processed - whole body", [{length, Length}]), - {_, Code, _} = StatusLine, - {NewBody, NewRequest} = stream(Body, Request, Code), - %% When we stream we will not keep the already - %% streamed data, that would be a waste of memory. - NewLength = - case Stream of - none -> - Length; - _ -> - Length - size(Body) - end, - - NewState = next_body_chunk(State), - NewMFA = {Module, whole_body, [NewBody, NewLength]}, - {noreply, NewState#state{mfa = NewMFA, - request = NewRequest}}; - NewMFA -> - ?hcrd("data processed - new mfa", []), - activate_once(Session), - {noreply, State#state{mfa = NewMFA}} - catch - exit:_Exit -> - ?hcrd("data processing exit", [{exit, _Exit}]), - ClientReason = {could_not_parse_as_http, Data}, - ClientErrMsg = httpc_response:error(Request, ClientReason), - NewState = answer_request(Request, ClientErrMsg, State), - {stop, normal, NewState}; - error:_Error -> - ?hcrd("data processing error", [{error, _Error}]), - ClientReason = {could_not_parse_as_http, Data}, - ClientErrMsg = httpc_response:error(Request, ClientReason), - NewState = answer_request(Request, ClientErrMsg, State), - {stop, normal, NewState} - - end, + try Module:Function([Data | Args]) of + {ok, Result} -> + ?hcrd("data processed - ok", []), + handle_http_msg(Result, State); + {_, whole_body, _} when Method =:= head -> + ?hcrd("data processed - whole body", []), + handle_response(State#state{body = <<>>}); + {Module, whole_body, [Body, Length]} -> + ?hcrd("data processed - whole body", [{length, Length}]), + {_, Code, _} = StatusLine, + {NewBody, NewRequest} = stream(Body, Request, Code), + %% When we stream we will not keep the already + %% streamed data, that would be a waste of memory. + NewLength = + case Stream of + none -> + Length; + _ -> + Length - size(Body) + end, + + NewState = next_body_chunk(State), + NewMFA = {Module, whole_body, [NewBody, NewLength]}, + {noreply, NewState#state{mfa = NewMFA, + request = NewRequest}}; + {Module, decode_size, + [TotalChunk, HexList, + {MaxBodySize, BodySoFar, AccLength, MaxHeaderSize}]} + when BodySoFar =/= <<>> -> + ?hcrd("data processed - decode_size", []), + %% The response body is chunk-encoded. Steal decoded + %% chunks as much as possible to stream. + {_, Code, _} = StatusLine, + {NewBody, NewRequest} = stream(BodySoFar, Request, Code), + NewState = next_body_chunk(State), + NewMFA = {Module, decode_size, + [TotalChunk, HexList, + {MaxBodySize, NewBody, AccLength, MaxHeaderSize}]}, + {noreply, NewState#state{mfa = NewMFA, + request = NewRequest}}; + {Module, decode_data, + [ChunkSize, TotalChunk, + {MaxBodySize, BodySoFar, AccLength, MaxHeaderSize}]} + when TotalChunk =/= <<>> orelse BodySoFar =/= <<>> -> + ?hcrd("data processed - decode_data", []), + %% The response body is chunk-encoded. Steal decoded + %% chunks as much as possible to stream. + ChunkSizeToSteal = min(ChunkSize, byte_size(TotalChunk)), + <<StolenChunk:ChunkSizeToSteal/binary, NewTotalChunk/binary>> = TotalChunk, + StolenBody = <<BodySoFar/binary, StolenChunk/binary>>, + NewChunkSize = ChunkSize - ChunkSizeToSteal, + {_, Code, _} = StatusLine, + + {NewBody, NewRequest} = stream(StolenBody, Request, Code), + NewState = next_body_chunk(State), + NewMFA = {Module, decode_data, + [NewChunkSize, NewTotalChunk, + {MaxBodySize, NewBody, AccLength, MaxHeaderSize}]}, + {noreply, NewState#state{mfa = NewMFA, + request = NewRequest}}; + NewMFA -> + ?hcrd("data processed - new mfa", []), + activate_once(Session), + {noreply, State#state{mfa = NewMFA}} + catch + exit:_Exit -> + ?hcrd("data processing exit", [{exit, _Exit}]), + ClientReason = {could_not_parse_as_http, Data}, + ClientErrMsg = httpc_response:error(Request, ClientReason), + NewState = answer_request(Request, ClientErrMsg, State), + {stop, normal, NewState}; + error:_Error -> + ?hcrd("data processing error", [{error, _Error}]), + ClientReason = {could_not_parse_as_http, Data}, + ClientErrMsg = httpc_response:error(Request, ClientReason), + NewState = answer_request(Request, ClientErrMsg, State), + {stop, normal, NewState} + + end, ?hcri("data processed", [{final_result, FinalResult}]), FinalResult; handle_info({Proto, Socket, Data}, - #state{mfa = MFA, - request = Request, - session = Session, - status = Status, - status_line = StatusLine, - profile_name = Profile} = State) + #state{mfa = MFA, + request = Request, + session = Session, + status = Status, + status_line = StatusLine, + profile_name = Profile} = State) when (Proto =:= tcp) orelse (Proto =:= ssl) orelse (Proto =:= httpc_handler) -> error_logger:warning_msg("Received unexpected ~p data on ~p" - "~n Data: ~p" - "~n MFA: ~p" - "~n Request: ~p" - "~n Session: ~p" - "~n Status: ~p" - "~n StatusLine: ~p" - "~n Profile: ~p" - "~n", - [Proto, Socket, Data, MFA, - Request, Session, Status, StatusLine, Profile]), + "~n Data: ~p" + "~n MFA: ~p" + "~n Request: ~p" + "~n Session: ~p" + "~n Status: ~p" + "~n StatusLine: ~p" + "~n Profile: ~p" + "~n", + [Proto, Socket, Data, MFA, + Request, Session, Status, StatusLine, Profile]), {noreply, State}; @@ -572,45 +599,45 @@ handle_info({ssl_error, _, _} = Reason, State) -> %% Internally, to a request handling process, a request timeout is %% seen as a canceled request. handle_info({timeout, RequestId}, - #state{request = #request{id = RequestId} = Request, - canceled = Canceled, - profile_name = ProfileName} = State) -> + #state{request = #request{id = RequestId} = Request, + canceled = Canceled, + profile_name = ProfileName} = State) -> ?hcri("timeout of current request", [{id, RequestId}]), httpc_response:send(Request#request.from, - httpc_response:error(Request, timeout)), + httpc_response:error(Request, timeout)), httpc_manager:request_done(RequestId, ProfileName), ?hcrv("response (timeout) sent - now terminate", []), {stop, normal, State#state{request = Request#request{from = answer_sent}, - canceled = [RequestId | Canceled]}}; + canceled = [RequestId | Canceled]}}; handle_info({timeout, RequestId}, - #state{canceled = Canceled, - profile_name = ProfileName} = State) -> + #state{canceled = Canceled, + profile_name = ProfileName} = State) -> ?hcri("timeout", [{id, RequestId}]), Filter = - fun(#request{id = Id, from = From} = Request) when Id =:= RequestId -> - ?hcrv("found request", [{id, Id}, {from, From}]), - %% Notify the owner - httpc_response:send(From, - httpc_response:error(Request, timeout)), - httpc_manager:request_done(RequestId, ProfileName), - ?hcrv("response (timeout) sent", []), - [Request#request{from = answer_sent}]; - (_) -> - true - end, + fun(#request{id = Id, from = From} = Request) when Id =:= RequestId -> + ?hcrv("found request", [{id, Id}, {from, From}]), + %% Notify the owner + httpc_response:send(From, + httpc_response:error(Request, timeout)), + httpc_manager:request_done(RequestId, ProfileName), + ?hcrv("response (timeout) sent", []), + [Request#request{from = answer_sent}]; + (_) -> + true + end, case State#state.status of - pipeline -> - ?hcrd("pipeline", []), - Pipeline = queue:filter(Filter, State#state.pipeline), - {noreply, State#state{canceled = [RequestId | Canceled], - pipeline = Pipeline}}; - keep_alive -> - ?hcrd("keep_alive", []), - KeepAlive = queue:filter(Filter, State#state.keep_alive), - {noreply, State#state{canceled = [RequestId | Canceled], - keep_alive = KeepAlive}} + pipeline -> + ?hcrd("pipeline", []), + Pipeline = queue:filter(Filter, State#state.pipeline), + {noreply, State#state{canceled = [RequestId | Canceled], + pipeline = Pipeline}}; + keep_alive -> + ?hcrd("keep_alive", []), + KeepAlive = queue:filter(Filter, State#state.keep_alive), + {noreply, State#state{canceled = [RequestId | Canceled], + keep_alive = KeepAlive}} end; handle_info(timeout_queue, State = #state{request = undefined}) -> @@ -619,11 +646,11 @@ handle_info(timeout_queue, State = #state{request = undefined}) -> %% Timing was such as the pipeline_timout was not canceled! handle_info(timeout_queue, #state{timers = Timers} = State) -> {noreply, State#state{timers = - Timers#timers{queue_timer = undefined}}}; + Timers#timers{queue_timer = undefined}}}; %% Setting up the connection to the server somehow failed. handle_info({init_error, Tag, ClientErrMsg}, - State = #state{request = Request}) -> + State = #state{request = Request}) -> ?hcrv("init error", [{tag, Tag}, {client_error, ClientErrMsg}]), NewState = answer_request(Request, ClientErrMsg, State), {stop, normal, NewState}; @@ -647,21 +674,21 @@ handle_info({'EXIT', _, _}, State) -> %% Init error there is no socket to be closed. terminate(normal, - #state{request = Request, - session = {send_failed, AReason} = Reason} = State) -> + #state{request = Request, + session = {send_failed, AReason} = Reason} = State) -> ?hcrd("terminate", [{send_reason, AReason}, {request, Request}]), maybe_send_answer(Request, - httpc_response:error(Request, Reason), - State), + httpc_response:error(Request, Reason), + State), ok; terminate(normal, - #state{request = Request, - session = {connect_failed, AReason} = Reason} = State) -> + #state{request = Request, + session = {connect_failed, AReason} = Reason} = State) -> ?hcrd("terminate", [{connect_reason, AReason}, {request, Request}]), maybe_send_answer(Request, - httpc_response:error(Request, Reason), - State), + httpc_response:error(Request, Reason), + State), ok; terminate(normal, #state{session = undefined}) -> @@ -670,21 +697,21 @@ terminate(normal, #state{session = undefined}) -> %% Init error sending, no session information has been setup but %% there is a socket that needs closing. terminate(normal, - #state{session = #session{id = undefined} = Session}) -> + #state{session = #session{id = undefined} = Session}) -> close_socket(Session); %% Socket closed remotely terminate(normal, - #state{session = #session{socket = {remote_close, Socket}, - socket_type = SocketType, - id = Id}, - profile_name = ProfileName, - request = Request, - timers = Timers, - pipeline = Pipeline, - keep_alive = KeepAlive} = State) -> + #state{session = #session{socket = {remote_close, Socket}, + socket_type = SocketType, + id = Id}, + profile_name = ProfileName, + request = Request, + timers = Timers, + pipeline = Pipeline, + keep_alive = KeepAlive} = State) -> ?hcrt("terminate(normal) - remote close", - [{id, Id}, {profile, ProfileName}]), + [{id, Id}, {profile, ProfileName}]), %% Clobber session (catch httpc_manager:delete_session(Id, ProfileName)), @@ -702,15 +729,15 @@ terminate(normal, http_transport:close(SocketType, Socket); terminate(Reason, #state{session = #session{id = Id, - socket = Socket, - socket_type = SocketType}, - request = undefined, - profile_name = ProfileName, - timers = Timers, - pipeline = Pipeline, - keep_alive = KeepAlive} = State) -> + socket = Socket, + socket_type = SocketType}, + request = undefined, + profile_name = ProfileName, + timers = Timers, + pipeline = Pipeline, + keep_alive = KeepAlive} = State) -> ?hcrt("terminate", - [{id, Id}, {profile, ProfileName}, {reason, Reason}]), + [{id, Id}, {profile, ProfileName}, {reason, Reason}]), %% Clobber session (catch httpc_manager:delete_session(Id, ProfileName)), @@ -728,16 +755,16 @@ terminate(Reason, #state{request = undefined}) -> terminate(Reason, #state{request = Request} = State) -> ?hcrd("terminate", [{reason, Reason}, {request, Request}]), NewState = maybe_send_answer(Request, - httpc_response:error(Request, Reason), - State), + httpc_response:error(Request, Reason), + State), terminate(Reason, NewState#state{request = undefined}). maybe_retry_queue(Q, State) -> case queue:is_empty(Q) of - false -> - retry_pipeline(queue:to_list(Q), State); - true -> - ok + false -> + retry_pipeline(queue:to_list(Q), State); + true -> + ok end. maybe_send_answer(#request{from = answer_sent}, _Reason, State) -> @@ -761,44 +788,44 @@ deliver_answer(Request) -> %%-------------------------------------------------------------------- code_change(_, - #state{session = OldSession, - profile_name = ProfileName} = State, - upgrade_from_pre_5_8_1) -> + #state{session = OldSession, + profile_name = ProfileName} = State, + upgrade_from_pre_5_8_1) -> case OldSession of - {session, - Id, ClientClose, Scheme, Socket, SocketType, QueueLen, Type} -> - NewSession = #session{id = Id, - client_close = ClientClose, - scheme = Scheme, - socket = Socket, - socket_type = SocketType, - queue_length = QueueLen, - type = Type}, - insert_session(NewSession, ProfileName), - {ok, State#state{session = NewSession}}; - _ -> - {ok, State} + {session, + Id, ClientClose, Scheme, Socket, SocketType, QueueLen, Type} -> + NewSession = #session{id = Id, + client_close = ClientClose, + scheme = Scheme, + socket = Socket, + socket_type = SocketType, + queue_length = QueueLen, + type = Type}, + insert_session(NewSession, ProfileName), + {ok, State#state{session = NewSession}}; + _ -> + {ok, State} end; code_change(_, - #state{session = OldSession, - profile_name = ProfileName} = State, - downgrade_to_pre_5_8_1) -> + #state{session = OldSession, + profile_name = ProfileName} = State, + downgrade_to_pre_5_8_1) -> case OldSession of - #session{id = Id, - client_close = ClientClose, - scheme = Scheme, - socket = Socket, - socket_type = SocketType, - queue_length = QueueLen, - type = Type} -> - NewSession = {session, - Id, ClientClose, Scheme, Socket, SocketType, - QueueLen, Type}, - insert_session(NewSession, ProfileName), - {ok, State#state{session = NewSession}}; - _ -> - {ok, State} + #session{id = Id, + client_close = ClientClose, + scheme = Scheme, + socket = Socket, + socket_type = SocketType, + queue_length = QueueLen, + type = Type} -> + NewSession = {session, + Id, ClientClose, Scheme, Socket, SocketType, + QueueLen, Type}, + insert_session(NewSession, ProfileName), + {ok, State#state{session = NewSession}}; + _ -> + {ok, State} end; code_change(_, State, _) -> @@ -806,22 +833,22 @@ code_change(_, State, _) -> %% new_http_options({http_options, TimeOut, AutoRedirect, SslOpts, -%% Auth, Relaxed}) -> +%% Auth, Relaxed}) -> %% {http_options, "HTTP/1.1", TimeOut, AutoRedirect, SslOpts, %% Auth, Relaxed}. %% old_http_options({http_options, _, TimeOut, AutoRedirect, -%% SslOpts, Auth, Relaxed}) -> +%% SslOpts, Auth, Relaxed}) -> %% {http_options, TimeOut, AutoRedirect, SslOpts, Auth, Relaxed}. %% new_queue(Queue, Fun) -> %% List = queue:to_list(Queue), %% NewList = -%% lists:map(fun(Request) -> -%% Settings = -%% Fun(Request#request.settings), -%% Request#request{settings = Settings} -%% end, List), +%% lists:map(fun(Request) -> +%% Settings = +%% Fun(Request#request.settings), +%% Request#request{settings = Settings} +%% end, List), %% queue:from_list(NewList). @@ -830,97 +857,121 @@ code_change(_, State, _) -> %%%-------------------------------------------------------------------- connect(SocketType, ToAddress, - #options{ipfamily = IpFamily, - ip = FromAddress, - port = FromPort, - socket_opts = Opts0}, Timeout) -> + #options{ipfamily = IpFamily, + ip = FromAddress, + port = FromPort, + socket_opts = Opts0}, Timeout) -> Opts1 = - case FromPort of - default -> - Opts0; - _ -> - [{port, FromPort} | Opts0] - end, + case FromPort of + default -> + Opts0; + _ -> + [{port, FromPort} | Opts0] + end, Opts2 = - case FromAddress of - default -> - Opts1; - _ -> - [{ip, FromAddress} | Opts1] - end, + case FromAddress of + default -> + Opts1; + _ -> + [{ip, FromAddress} | Opts1] + end, case IpFamily of - inet6fb4 -> - Opts3 = [inet6 | Opts2], - case http_transport:connect(SocketType, - ToAddress, Opts3, Timeout) of - {error, Reason6} -> - Opts4 = [inet | Opts2], - case http_transport:connect(SocketType, - ToAddress, Opts4, Timeout) of - {error, Reason4} -> - {error, {failed_connect, - [{to_address, ToAddress}, - {inet6, Opts3, Reason6}, - {inet, Opts4, Reason4}]}}; - OK -> - OK - end; - OK -> - OK - end; - _ -> - Opts3 = [IpFamily | Opts2], - case http_transport:connect(SocketType, ToAddress, Opts3, Timeout) of - {error, Reason} -> - {error, {failed_connect, [{to_address, ToAddress}, - {IpFamily, Opts3, Reason}]}}; - Else -> - Else - end + inet6fb4 -> + Opts3 = [inet6 | Opts2], + case http_transport:connect(SocketType, + ToAddress, Opts3, Timeout) of + {error, Reason6} -> + Opts4 = [inet | Opts2], + case http_transport:connect(SocketType, + ToAddress, Opts4, Timeout) of + {error, Reason4} -> + {error, {failed_connect, + [{to_address, ToAddress}, + {inet6, Opts3, Reason6}, + {inet, Opts4, Reason4}]}}; + OK -> + OK + end; + OK -> + OK + end; + _ -> + Opts3 = [IpFamily | Opts2], + case http_transport:connect(SocketType, ToAddress, Opts3, Timeout) of + {error, Reason} -> + {error, {failed_connect, [{to_address, ToAddress}, + {IpFamily, Opts3, Reason}]}}; + Else -> + Else + end end. connect_and_send_first_request(Address, Request, #state{options = Options} = State) -> SocketType = socket_type(Request), ConnTimeout = (Request#request.settings)#http_options.connect_timeout, ?hcri("connect", - [{address, Address}, {request, Request}, {options, Options}]), + [{address, Address}, {request, Request}, {options, Options}]), case connect(SocketType, Address, Options, ConnTimeout) of - {ok, Socket} -> - ClientClose = - httpc_request:is_client_closing( - Request#request.headers), + {ok, Socket} -> + ClientClose = + httpc_request:is_client_closing( + Request#request.headers), + SessionType = httpc_manager:session_type(Options), + SocketType = socket_type(Request), + Session = #session{id = {Request#request.address, self()}, + scheme = Request#request.scheme, + socket = Socket, + socket_type = SocketType, + client_close = ClientClose, + type = SessionType}, + ?hcri("connected - now send first request", [{socket, Socket}]), + + case httpc_request:send(Address, Session, Request) of + ok -> + ?hcri("first request sent", []), + TmpState = State#state{request = Request, + session = Session, + mfa = init_mfa(Request, State), + status_line = + init_status_line(Request), + headers = undefined, + body = undefined, + status = new}, + http_transport:setopts(SocketType, + Socket, [{active, once}]), + NewState = activate_request_timeout(TmpState), + {ok, NewState}; + {error, Reason} -> + self() ! {init_error, error_sending, + httpc_response:error(Request, Reason)}, + {ok, State#state{request = Request, + session = + #session{socket = Socket}}} + end; + {error, Reason} -> + self() ! {init_error, error_connecting, + httpc_response:error(Request, Reason)}, + {ok, State#state{request = Request}} + end. + +connect_and_send_upgrade_request(Address, Request, #state{options = Options} = State) -> + ConnTimeout = (Request#request.settings)#http_options.connect_timeout, + SocketType = ip_comm, + case connect(SocketType, Address, Options, ConnTimeout) of + {ok, Socket} -> SessionType = httpc_manager:session_type(Options), - SocketType = socket_type(Request), - Session = #session{id = {Request#request.address, self()}, - scheme = Request#request.scheme, - socket = Socket, + Session = #session{socket = Socket, socket_type = SocketType, - client_close = ClientClose, + id = {Request#request.address, self()}, + scheme = http, + client_close = false, type = SessionType}, - ?hcri("connected - now send first request", [{socket, Socket}]), - - case httpc_request:send(Address, Session, Request) of - ok -> - ?hcri("first request sent", []), - TmpState = State#state{request = Request, - session = Session, - mfa = init_mfa(Request, State), - status_line = - init_status_line(Request), - headers = undefined, - body = undefined, - status = new}, - http_transport:setopts(SocketType, - Socket, [{active, once}]), - NewState = activate_request_timeout(TmpState), - {ok, NewState}; - {error, Reason} -> - self() ! {init_error, error_sending, - httpc_response:error(Request, Reason)}, - {ok, State#state{request = Request, - session = - #session{socket = Socket}}} - end; + ErrorHandler = + fun(ERequest, EState, EReason) -> + self() ! {init_error, error_sending, + httpc_response:error(ERequest, EReason)}, + {ok, EState#state{request = ERequest}} end, + tls_tunnel(Address, Request, State#state{session = Session}, ErrorHandler); {error, Reason} -> self() ! {init_error, error_connecting, httpc_response:error(Request, Reason)}, @@ -1014,25 +1065,39 @@ handle_http_msg({Version, StatusCode, ReasonPharse, Headers, Body}, status_line = StatusLine, headers = Headers}) end; -handle_http_msg({ChunkedHeaders, Body}, #state{headers = Headers} = State) -> +handle_http_msg({ChunkedHeaders, Body}, + #state{status_line = {_, Code, _}, headers = Headers} = State) -> ?hcrt("handle_http_msg", [{chunked_headers, ChunkedHeaders}, {headers, Headers}]), NewHeaders = http_chunk:handle_headers(Headers, ChunkedHeaders), - handle_response(State#state{headers = NewHeaders, body = Body}); + {NewBody, NewRequest} = stream(Body, State#state.request, Code), + handle_response(State#state{headers = NewHeaders, + body = NewBody, + request = NewRequest}); handle_http_msg(Body, #state{status_line = {_,Code, _}} = State) -> ?hcrt("handle_http_msg", [{code, Code}]), {NewBody, NewRequest} = stream(Body, State#state.request, Code), handle_response(State#state{body = NewBody, request = NewRequest}). -handle_http_body(<<>>, State = #state{status_line = {_,304, _}}) -> +handle_http_body(_, #state{status = {ssl_tunnel, _}, + status_line = {_,200, _}} = State) -> + tls_upgrade(State); + +handle_http_body(_, #state{status = {ssl_tunnel, Request}, + status_line = StatusLine} = State) -> + ClientErrMsg = httpc_response:error(Request,{could_no_establish_ssh_tunnel, StatusLine}), + NewState = answer_request(Request, ClientErrMsg, State), + {stop, normal, NewState}; + +handle_http_body(<<>>, #state{status_line = {_,304, _}} = State) -> ?hcrt("handle_http_body - 304", []), handle_response(State#state{body = <<>>}); -handle_http_body(<<>>, State = #state{status_line = {_,204, _}}) -> +handle_http_body(<<>>, #state{status_line = {_,204, _}} = State) -> ?hcrt("handle_http_body - 204", []), handle_response(State#state{body = <<>>}); -handle_http_body(<<>>, State = #state{request = #request{method = head}}) -> +handle_http_body(<<>>, #state{request = #request{method = head}} = State) -> ?hcrt("handle_http_body - head", []), handle_response(State#state{body = <<>>}); @@ -1047,8 +1112,7 @@ handle_http_body(Body, #state{headers = Headers, "chunked" -> ?hcrt("handle_http_body - chunked", []), case http_chunk:decode(Body, State#state.max_body_size, - State#state.max_header_size, - {Code, Request}) of + State#state.max_header_size) of {Module, Function, Args} -> ?hcrt("handle_http_body - new mfa", [{module, Module}, @@ -1119,7 +1183,7 @@ handle_response(#state{request = Request, {session, Session}, {status_line, StatusLine}]), - handle_cookies(Headers, Request, Options, ProfileName), + handle_cookies(Headers, Request, Options, ProfileName), case httpc_response:result({StatusLine, Headers, Body}, Request) of %% 100-continue continue -> @@ -1202,8 +1266,7 @@ handle_queue(#state{status = pipeline} = State, Data) -> handle_pipeline(#state{status = pipeline, session = Session, profile_name = ProfileName, - options = #options{pipeline_timeout = TimeOut}} = - State, + options = #options{pipeline_timeout = TimeOut}} = State, Data) -> ?hcrd("handle pipeline", [{profile, ProfileName}, @@ -1213,25 +1276,7 @@ handle_pipeline(#state{status = pipeline, case queue:out(State#state.pipeline) of {empty, _} -> ?hcrd("pipeline queue empty", []), - - %% The server may choose too teminate an idle pipeline - %% in this case we want to receive the close message - %% at once and not when trying to pipeline the next - %% request. - activate_once(Session), - - %% If a pipeline that has been idle for some time is not - %% closed by the server, the client may want to close it. - NewState = activate_queue_timeout(TimeOut, State), - update_session(ProfileName, Session, #session.queue_length, 0), - %% Note mfa will be initilized when a new request - %% arrives. - {noreply, - NewState#state{request = undefined, - mfa = undefined, - status_line = undefined, - headers = undefined, - body = undefined}}; + handle_empty_queue(Session, ProfileName, TimeOut, State); {{value, NextRequest}, Pipeline} -> ?hcrd("pipeline queue non-empty", []), case lists:member(NextRequest#request.id, @@ -1249,38 +1294,17 @@ handle_pipeline(#state{status = pipeline, Session#session{queue_length = %% Queue + current queue:len(Pipeline) + 1}, - insert_session(NewSession, ProfileName), - Relaxed = - (NextRequest#request.settings)#http_options.relaxed, - MFA = {httpc_response, - parse, - [State#state.max_header_size, Relaxed]}, - NewState = - State#state{pipeline = Pipeline, - request = NextRequest, - mfa = MFA, - status_line = undefined, - headers = undefined, - body = undefined}, - case Data of - <<>> -> - activate_once(Session), - {noreply, NewState}; - _ -> - %% If we already received some bytes of - %% the next response - handle_info({httpc_handler, dummy, Data}, - NewState) - end + receive_response(NextRequest, + NewSession, Data, + State#state{pipeline = Pipeline}) end end. -handle_keep_alive_queue( - #state{status = keep_alive, - session = Session, - profile_name = ProfileName, - options = #options{keep_alive_timeout = TimeOut}} = State, - Data) -> +handle_keep_alive_queue(#state{status = keep_alive, + session = Session, + profile_name = ProfileName, + options = #options{keep_alive_timeout = TimeOut}} = State, + Data) -> ?hcrd("handle keep_alive", [{profile, ProfileName}, {session, Session}, @@ -1289,25 +1313,7 @@ handle_keep_alive_queue( case queue:out(State#state.keep_alive) of {empty, _} -> ?hcrd("keep_alive queue empty", []), - %% The server may choose too terminate an idle keep_alive session - %% in this case we want to receive the close message - %% at once and not when trying to send the next - %% request. - activate_once(Session), - %% If a keep_alive session has been idle for some time is not - %% closed by the server, the client may want to close it. - NewState = activate_queue_timeout(TimeOut, State), - update_session(ProfileName, Session, #session.queue_length, 0), - %% Note mfa will be initilized when a new request - %% arrives. - {noreply, - NewState#state{request = undefined, - mfa = undefined, - status_line = undefined, - headers = undefined, - body = undefined - } - }; + handle_empty_queue(Session, ProfileName, TimeOut, State); {{value, NextRequest}, KeepAlive} -> ?hcrd("keep_alive queue non-empty", []), case lists:member(NextRequest#request.id, @@ -1318,30 +1324,61 @@ handle_keep_alive_queue( State#state{keep_alive = KeepAlive}, Data); false -> ?hcrv("next request", [{request, NextRequest}]), - Relaxed = - (NextRequest#request.settings)#http_options.relaxed, - MFA = {httpc_response, parse, - [State#state.max_header_size, Relaxed]}, - NewState = - State#state{request = NextRequest, - keep_alive = KeepAlive, - mfa = MFA, - status_line = undefined, - headers = undefined, - body = undefined}, - case Data of - <<>> -> - activate_once(Session), - {noreply, NewState}; - _ -> - %% If we already received some bytes of - %% the next response - handle_info({httpc_handler, dummy, Data}, - NewState) + #request{address = Address} = NextRequest, + case httpc_request:send(Address, Session, NextRequest) of + ok -> + receive_response(NextRequest, + Session, <<>>, + State#state{keep_alive = KeepAlive}); + {error, Reason} -> + {reply, {keep_alive_failed, Reason}, State} end end end. +handle_empty_queue(Session, ProfileName, TimeOut, State) -> + %% The server may choose too terminate an idle pipline| keep_alive session + %% in this case we want to receive the close message + %% at once and not when trying to send the next + %% request. + activate_once(Session), + %% If a pipline | keep_alive session has been idle for some time is not + %% closed by the server, the client may want to close it. + NewState = activate_queue_timeout(TimeOut, State), + update_session(ProfileName, Session, #session.queue_length, 0), + %% Note mfa will be initilized when a new request + %% arrives. + {noreply, + NewState#state{request = undefined, + mfa = undefined, + status_line = undefined, + headers = undefined, + body = undefined + } + }. + +receive_response(Request, Session, Data, State) -> + NewState = init_wait_for_response_state(Request, State), + gather_data(Data, Session, NewState). + +init_wait_for_response_state(Request, State) -> + Relaxed = + (Request#request.settings)#http_options.relaxed, + MFA = {httpc_response, parse, + [State#state.max_header_size, Relaxed]}, + State#state{request = Request, + mfa = MFA, + status_line = undefined, + headers = undefined, + body = undefined}. + +gather_data(<<>>, Session, State) -> + activate_once(Session), + {noreply, State}; +gather_data(Data, _, State) -> + %% If we already received some bytes of + %% the next response + handle_info({httpc_handler, dummy, Data}, State). case_insensitive_header(Str) when is_list(Str) -> http_util:to_lower(Str); @@ -1503,6 +1540,12 @@ retry_pipeline([Request | PipeLine], end, retry_pipeline(PipeLine, NewState). +handle_proxy_options(https, #options{https_proxy = {HttpsProxy, _} = HttpsProxyOpt}) when + HttpsProxy =/= undefined -> + HttpsProxyOpt; +handle_proxy_options(_, #options{proxy = Proxy}) -> + Proxy. + %%% Check to see if the given {Host,Port} tuple is in the NoProxyList %%% Returns an eventually updated {Host,Port} tuple, with the proxy address handle_proxy(HostPort = {Host, _Port}, {Proxy, NoProxy}) -> @@ -1696,6 +1739,96 @@ send_raw(SocketType, Socket, ProcessBody, Acc) -> end end. +tls_tunnel(Address, Request, #state{session = #session{socket = Socket, + socket_type = SocketType} = Session} = State, + ErrorHandler) -> + UpgradeRequest = tls_tunnel_request(Request), + case httpc_request:send(Address, Session, UpgradeRequest) of + ok -> + TmpState = State#state{request = UpgradeRequest, + %% session = Session, + mfa = init_mfa(UpgradeRequest, State), + status_line = + init_status_line(UpgradeRequest), + headers = undefined, + body = undefined}, + http_transport:setopts(SocketType, + Socket, [{active, once}]), + NewState = activate_request_timeout(TmpState), + {ok, NewState#state{status = {ssl_tunnel, Request}}}; + {error, Reason} -> + ErrorHandler(Request, State, Reason) + end. + +tls_tunnel_request(#request{headers = Headers, + settings = Options, + address = {Host, Port}= Adress, + ipv6_host_with_brackets = IPV6}) -> + + URI = Host ++":" ++ integer_to_list(Port), + + #request{ + id = make_ref(), + from = self(), + scheme = http, %% Use tcp-first and then upgrade! + address = Adress, + path = URI, + pquery = "", + method = connect, + headers = #http_request_h{host = host_header(Headers, URI), + te = "", + pragma = "no-cache", + other = [{"Proxy-Connection", " Keep-Alive"}]}, + settings = Options, + abs_uri = URI, + stream = false, + userinfo = "", + headers_as_is = [], + started = http_util:timestamp(), + ipv6_host_with_brackets = IPV6 + }. + +host_header(#http_request_h{host = Host}, _) -> + Host; + +%% Handles header_as_is +host_header(_, URI) -> + {ok, {_, _, Host, _, _, _}} = http_uri:parse(URI), + Host. + +tls_upgrade(#state{status = + {ssl_tunnel, + #request{settings = + #http_options{ssl = {_, TLSOptions} = SocketType}} = Request}, + session = #session{socket = TCPSocket} = Session0, + options = Options} = State) -> + + case ssl:connect(TCPSocket, TLSOptions) of + {ok, TLSSocket} -> + Address = Request#request.address, + ClientClose = httpc_request:is_client_closing(Request#request.headers), + SessionType = httpc_manager:session_type(Options), + Session = Session0#session{ + scheme = https, + socket = TLSSocket, + socket_type = SocketType, + type = SessionType, + client_close = ClientClose}, + httpc_request:send(Address, Session, Request), + http_transport:setopts(SocketType, TLSSocket, [{active, once}]), + NewState = State#state{session = Session, + request = Request, + mfa = init_mfa(Request, State), + status_line = + init_status_line(Request), + headers = undefined, + body = undefined, + status = new + }, + {noreply, activate_request_timeout(NewState)}; + {error, _Reason} -> + {stop, normal, State#state{request = Request}} + end. %% --------------------------------------------------------------------- %% Session wrappers diff --git a/lib/inets/src/http_client/httpc_internal.hrl b/lib/inets/src/http_client/httpc_internal.hrl index 8af752546c..30e2742e9d 100644 --- a/lib/inets/src/http_client/httpc_internal.hrl +++ b/lib/inets/src/http_client/httpc_internal.hrl @@ -37,6 +37,7 @@ -define(HTTP_MAX_REDIRECTS, 4). -define(HTTP_KEEP_ALIVE_TIMEOUT, 120000). -define(HTTP_KEEP_ALIVE_LENGTH, 5). +-define(TLS_UPGRADE_TOKEN, "TLS/1.0"). %%% HTTP Client per request settings -record(http_options, @@ -72,6 +73,7 @@ -record(options, { proxy = {undefined, []}, % {{ProxyHost, ProxyPort}, [NoProxy]}, + https_proxy = {undefined, []}, % {{ProxyHost, ProxyPort}, [NoProxy]} %% 0 means persistent connections are used without pipelining pipeline_timeout = ?HTTP_PIPELINE_TIMEOUT, max_pipeline_length = ?HTTP_PIPELINE_LENGTH, diff --git a/lib/inets/src/http_client/httpc_manager.erl b/lib/inets/src/http_client/httpc_manager.erl index 3612b331e7..c45dcab802 100644 --- a/lib/inets/src/http_client/httpc_manager.erl +++ b/lib/inets/src/http_client/httpc_manager.erl @@ -577,6 +577,7 @@ handle_cast({set_options, Options}, State = #state{options = OldOptions}) -> ?hcrv("set options", [{options, Options}, {old_options, OldOptions}]), NewOptions = #options{proxy = get_proxy(Options, OldOptions), + https_proxy = get_https_proxy(Options, OldOptions), pipeline_timeout = get_pipeline_timeout(Options, OldOptions), max_pipeline_length = get_max_pipeline_length(Options, OldOptions), max_keep_alive_length = get_max_keep_alive_length(Options, OldOptions), @@ -741,7 +742,7 @@ get_manager_info(#state{handler_db = HDB, SessionInfo = which_sessions2(SDB), OptionsInfo = [{Item, get_option(Item, Options)} || - Item <- record_info(fields, options)], + Item <- record_info(fields, options)], CookieInfo = httpc_cookie:which_cookies(CDB), [{handlers, HandlerInfo}, {sessions, SessionInfo}, @@ -769,20 +770,7 @@ get_handler_info(Tab) -> Pattern = {'$2', '$1', '_'}, Handlers1 = [{Pid, Id} || [Pid, Id] <- ets:match(Tab, Pattern)], Handlers2 = sort_handlers(Handlers1), - Handlers3 = [{Pid, Reqs, - try - begin - httpc_handler:info(Pid) - end - catch - _:_ -> - %% Why would this crash? - %% Only if the process has died, but we don't - %% know about it? - [] - end} || {Pid, Reqs} <- Handlers2], - Handlers3. - + [{Pid, Reqs, httpc_handler:info(Pid)} || {Pid, Reqs} <- Handlers2]. handle_request(#request{settings = #http_options{version = "HTTP/0.9"}} = Request, @@ -1001,6 +989,8 @@ cast(ProfileName, Msg) -> get_option(proxy, #options{proxy = Proxy}) -> Proxy; +get_option(https_proxy, #options{https_proxy = Proxy}) -> + Proxy; get_option(pipeline_timeout, #options{pipeline_timeout = Timeout}) -> Timeout; get_option(max_pipeline_length, #options{max_pipeline_length = Length}) -> @@ -1027,6 +1017,9 @@ get_option(socket_opts, #options{socket_opts = SocketOpts}) -> get_proxy(Opts, #options{proxy = Default}) -> proplists:get_value(proxy, Opts, Default). +get_https_proxy(Opts, #options{https_proxy = Default}) -> + proplists:get_value(https_proxy, Opts, Default). + get_pipeline_timeout(Opts, #options{pipeline_timeout = Default}) -> proplists:get_value(pipeline_timeout, Opts, Default). diff --git a/lib/inets/src/http_client/httpc_response.erl b/lib/inets/src/http_client/httpc_response.erl index 04976447cc..9107dfbf05 100644 --- a/lib/inets/src/http_client/httpc_response.erl +++ b/lib/inets/src/http_client/httpc_response.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2012. All Rights Reserved. +%% Copyright Ericsson AB 2004-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -124,6 +124,11 @@ result(Response = {{_, Code, _}, _, _}, (Code =:= 303) orelse (Code =:= 307) -> redirect(Response, Request); +result(Response = {{_, 303, _}, _, _}, + Request = #request{settings = + #http_options{autoredirect = true}, + method = post}) -> + redirect(Response, Request#request{method = get}); result(Response = {{_,503,_}, _, _}, Request) -> @@ -425,8 +430,6 @@ format_response({StatusLine, Headers, Body}) -> Length = list_to_integer(Headers#http_response_h.'content-length'), {NewBody, Data} = case Length of - 0 -> - {Body, <<>>}; -1 -> % When no lenght indicator is provided {Body, <<>>}; Length when (Length =< size(Body)) -> diff --git a/lib/inets/src/http_lib/http_chunk.erl b/lib/inets/src/http_lib/http_chunk.erl index 57647438e9..d15d7ba49a 100644 --- a/lib/inets/src/http_lib/http_chunk.erl +++ b/lib/inets/src/http_lib/http_chunk.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2011. All Rights Reserved. +%% Copyright Ericsson AB 2004-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -24,7 +24,7 @@ -include("http_internal.hrl"). %% API --export([decode/3, decode/4, encode/1, encode_last/0, handle_headers/2]). +-export([decode/3, encode/1, encode_last/0, handle_headers/2]). %% Callback API - used for example if the chunkedbody is received a %% little at a time on a socket. -export([decode_size/1, ignore_extensions/1, decode_data/1, decode_trailer/1]). @@ -34,20 +34,14 @@ %%% API %%%========================================================================= %%------------------------------------------------------------------------- -%% decode(ChunkedBody, MaxBodySize, MaxHeaderSize, <Stream>) -> +%% decode(ChunkedBody, MaxBodySize, MaxHeaderSize) -> %% {ok, {Headers, Body}} | {Module, Function, Args} %% %% Headers = ["Header:Value"] %% ChunkedBody = binary() %% MaxBodySize = integer() %% MaxHeaderSize = integer() -%% Stream = {Code, Request} - if Request#request.stream =/= none -%% and Code == 200 the side effect of sending each decode chunk to the -%% client/file before the whole body is received will take place. %% -%% Note: decode/4 should only be used from httpc_handler module. -%% Otherwhise use the side effect free decode/3. -%% %% Description: Decodes a body encoded by the chunked transfer %% encoding. If the ChunkedBody is not compleate it returns {Module, %% Function, Args} so that decoding can be continued when more of the @@ -61,12 +55,9 @@ %% the next pass in the loop. %%------------------------------------------------------------------------- decode(ChunkedBody, MaxBodySize, MaxHeaderSize) -> - decode(ChunkedBody, MaxBodySize, MaxHeaderSize, false). - -decode(ChunkedBody, MaxBodySize, MaxHeaderSize, Stream) -> %% Note decode_size will call decode_data. - decode_size([ChunkedBody, <<>>, [], - {MaxBodySize, <<>>, 0, MaxHeaderSize, Stream}]). + decode_size([ChunkedBody, <<>>, [], + {MaxBodySize, <<>>, 0, MaxHeaderSize}]). %%------------------------------------------------------------------------- %% encode(Chunk) -> EncodedChunk @@ -150,7 +141,7 @@ decode_size(<<>>, HexList, Info) -> decode_size(Data = <<?CR, ?LF, ChunkRest/binary>>, HexList, {MaxBodySize, Body, AccLength, - MaxHeaderSize, Stream}) -> + MaxHeaderSize}) -> ChunkSize = http_util:hexlist_to_integer(lists:reverse(HexList)), case ChunkSize of 0 -> % Last chunk, there was no data @@ -164,7 +155,7 @@ decode_size(Data = <<?CR, ?LF, ChunkRest/binary>>, HexList, %% to this function comes in. decode_data(ChunkSize, ChunkRest, {MaxBodySize, Body, ChunkSize + AccLength , - MaxHeaderSize, Stream}) + MaxHeaderSize}) end; decode_size(<<";", Rest/binary>>, HexList, Info) -> %% Note ignore_extensions will call decode_size/1 again when @@ -189,50 +180,42 @@ ignore_extensions(<<_Octet, Rest/binary>>, NextFunction) -> ignore_extensions(Rest, NextFunction). decode_data(ChunkSize, TotalChunk, - Info = {MaxBodySize, BodySoFar, AccLength, MaxHeaderSize, Stream}) + Info = {MaxBodySize, BodySoFar, AccLength, MaxHeaderSize}) when ChunkSize =< size(TotalChunk) -> case TotalChunk of %% Last chunk <<Data:ChunkSize/binary, ?CR, ?LF, "0", ";">> -> %% Note ignore_extensions will call decode_trailer/1 %% once it ignored all extensions. - {NewBody, _} = - stream(<<BodySoFar/binary, Data/binary>>, Stream), {?MODULE, ignore_extensions, [<<>>, {?MODULE, decode_trailer, [<<>>, [],[], MaxHeaderSize, - NewBody, + <<BodySoFar/binary, Data/binary>>, integer_to_list(AccLength)]}]}; <<Data:ChunkSize/binary, ?CR, ?LF, "0", ";", Rest/binary>> -> %% Note ignore_extensions will call decode_trailer/1 %% once it ignored all extensions. - {NewBody, _} = stream(<<BodySoFar/binary, Data/binary>>, Stream), ignore_extensions(Rest, {?MODULE, decode_trailer, [<<>>, [],[], MaxHeaderSize, - NewBody, + <<BodySoFar/binary, Data/binary>>, integer_to_list(AccLength)]}); <<Data:ChunkSize/binary, ?CR, ?LF, "0", ?CR, ?LF>> -> - {NewBody, _} = stream(<<BodySoFar/binary, Data/binary>>, Stream), {?MODULE, decode_trailer, [<<?CR, ?LF>>, [],[], MaxHeaderSize, - NewBody, + <<BodySoFar/binary, Data/binary>>, integer_to_list(AccLength)]}; <<Data:ChunkSize/binary, ?CR, ?LF, "0", ?CR, ?LF, Rest/binary>> -> - {NewBody,_}= stream(<<BodySoFar/binary, Data/binary>>, Stream), decode_trailer(<<?CR, ?LF, Rest/binary>>, [],[], MaxHeaderSize, - NewBody, + <<BodySoFar/binary, Data/binary>>, integer_to_list(AccLength)); %% There are more chunks, so here we go agin... <<Data:ChunkSize/binary, ?CR, ?LF>> -> - {NewBody, NewStream} = - stream(<<BodySoFar/binary, Data/binary>>, Stream), - {?MODULE, decode_size, [<<>>, [], {MaxBodySize, NewBody, AccLength, MaxHeaderSize, NewStream}]}; + NewBody = <<BodySoFar/binary, Data/binary>>, + {?MODULE, decode_size, [<<>>, [], {MaxBodySize, NewBody, AccLength, MaxHeaderSize}]}; <<Data:ChunkSize/binary, ?CR, ?LF, Rest/binary>> when (AccLength < MaxBodySize) or (MaxBodySize == nolimit) -> - {NewBody, NewStream} = - stream(<<BodySoFar/binary, Data/binary>>, Stream), decode_size(Rest, [], - {MaxBodySize, NewBody, - AccLength, MaxHeaderSize, NewStream}); + {MaxBodySize, <<BodySoFar/binary, Data/binary>>, + AccLength, MaxHeaderSize}); <<_:ChunkSize/binary, ?CR, ?LF, _/binary>> -> throw({error, body_too_big}); _ -> @@ -286,9 +269,3 @@ decode_trailer(<<Octet, Rest/binary>>, Header, Headers, MaxHeaderSize, Body, BodyLength) -> decode_trailer(Rest, [Octet | Header], Headers, MaxHeaderSize, Body, BodyLength). - -stream(BodyPart, false) -> - {BodyPart, false}; -stream(BodyPart, {Code, Request}) -> - {NewBody, NewRequest} = httpc_handler:stream(BodyPart, Request, Code), - {NewBody, {Code, NewRequest}}. diff --git a/lib/inets/src/http_lib/http_request.erl b/lib/inets/src/http_lib/http_request.erl index c214aca4a4..aa9f9f6774 100644 --- a/lib/inets/src/http_lib/http_request.erl +++ b/lib/inets/src/http_lib/http_request.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2009. All Rights Reserved. +%% Copyright Ericsson AB 2005-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -248,13 +248,8 @@ key_value_str(Key = 'content-language', Headers) -> key_value_str(atom_to_list(Key), Headers#http_request_h.'content-language'); key_value_str(Key = 'content-length', Headers) -> - case Headers#http_request_h.'content-length' of - "0" -> - undefined; - _ -> - key_value_str(atom_to_list(Key), - Headers#http_request_h.'content-length') - end; + key_value_str(atom_to_list(Key), + Headers#http_request_h.'content-length'); key_value_str(Key = 'content-location', Headers) -> key_value_str(atom_to_list(Key), Headers#http_request_h.'content-location'); diff --git a/lib/inets/src/http_lib/http_transport.erl b/lib/inets/src/http_lib/http_transport.erl index 8b103628d7..7e679531cf 100644 --- a/lib/inets/src/http_lib/http_transport.erl +++ b/lib/inets/src/http_lib/http_transport.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2011. All Rights Reserved. +%% Copyright Ericsson AB 2004-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -23,7 +23,7 @@ -export([ start/1, connect/3, connect/4, - listen/2, listen/3, listen/4, + listen/4, listen/5, accept/2, accept/3, close/2, send/3, @@ -155,21 +155,25 @@ connect({essl, SslConfig}, {Host, Port}, Opts0, Timeout) -> %% reason for this to enable a HTTP-server not running as root to use %% port 80. %%------------------------------------------------------------------------- -listen(SocketType, Port) -> - listen(SocketType, undefined, Port). +listen(ip_comm = _SocketType, Addr, Port, Fd, IpFamily) -> + listen_ip_comm(Addr, Port, Fd, IpFamily); + +listen({essl, SSLConfig}, Addr, Port, Fd, IpFamily) -> + listen_ssl(Addr, Port, Fd, SSLConfig, IpFamily, []). -listen(ip_comm = _SocketType, Addr, Port) -> - listen_ip_comm(Addr, Port, undefined); +listen(ip_comm = _SocketType, Addr, Port, IpFamily) -> + listen_ip_comm(Addr, Port, undefined, IpFamily); %% Wrapper for backaward compatibillity -listen({ssl, SSLConfig}, Addr, Port) -> +listen({ssl, SSLConfig}, Addr, Port, IpFamily) -> ?hlrt("listen (wrapper)", [{addr, Addr}, {port, Port}, {ssl_config, SSLConfig}]), - listen({?HTTP_DEFAULT_SSL_KIND, SSLConfig}, Addr, Port); + listen({?HTTP_DEFAULT_SSL_KIND, SSLConfig}, Addr, Port, IpFamily); -listen({essl, SSLConfig}, Addr, Port) -> + +listen({essl, SSLConfig}, Addr, Port, IpFamily) -> ?hlrt("listen (essl)", [{addr, Addr}, {port, Port}, @@ -180,22 +184,18 @@ listen({essl, SSLConfig}, Addr, Port) -> LogAlert -> {proplists:delete(log_alert, SSLConfig), [{log_alert, LogAlert}]} end, - listen_ssl(Addr, Port, [{ssl_imp, new}, {reuseaddr, true} | SSLConfig2], ExtraOpts). - + listen_ssl(Addr, Port, undefined, SSLConfig2, IpFamily, ExtraOpts). -listen(ip_comm, Addr, Port, Fd) -> - listen_ip_comm(Addr, Port, Fd). - -listen_ip_comm(Addr, Port, Fd) -> - case (catch do_listen_ip_comm(Addr, Port, Fd)) of +listen_ip_comm(Addr, Port, Fd, IpFamily) -> + case (catch do_listen_ip_comm(Addr, Port, Fd, IpFamily)) of {'EXIT', Reason} -> {error, {exit, Reason}}; Else -> Else end. -do_listen_ip_comm(Addr, Port, Fd) -> - {NewPort, Opts, IpFamily} = get_socket_info(Addr, Port, Fd), +do_listen_ip_comm(Addr, Port, Fd, IpFamily) -> + {NewPort, Opts} = get_socket_info(Addr, Port, Fd), case IpFamily of inet6fb4 -> Opts2 = [inet6 | Opts], @@ -227,11 +227,9 @@ do_listen_ip_comm(Addr, Port, Fd) -> gen_tcp:listen(NewPort, Opts2) end. - -listen_ssl(Addr, Port, Opts0, ExtraOpts) -> - IpFamily = ipfamily_default(Addr, Port), - BaseOpts = [{backlog, 128}, {reuseaddr, true} | Opts0], - Opts = sock_opts(Addr, BaseOpts), +listen_ssl(Addr, Port, Fd, Opts0, IpFamily, ExtraOpts) -> + {NewPort, SockOpt} = get_socket_info(Addr, Port, Fd), + Opts = SockOpt ++ Opts0, case IpFamily of inet6fb4 -> Opts2 = [inet6 | Opts] ++ ExtraOpts, @@ -242,13 +240,13 @@ listen_ssl(Addr, Port, Opts0, ExtraOpts) -> Opts3 = [inet | Opts] ++ ExtraOpts, ?hlrt("ipv6 listen failed - try ipv4 instead", [{reason, Reason}, {opts, Opts3}]), - ssl:listen(Port, Opts3); + ssl:listen(NewPort, Opts3); {'EXIT', Reason} -> Opts3 = [inet | Opts] ++ ExtraOpts, ?hlrt("ipv6 listen exit - try ipv4 instead", [{reason, Reason}, {opts, Opts3}]), - ssl:listen(Port, Opts3); + ssl:listen(NewPort, Opts3); Other -> ?hlrt("ipv6 listen done", [{other, Other}]), @@ -258,61 +256,21 @@ listen_ssl(Addr, Port, Opts0, ExtraOpts) -> _ -> Opts2 = [IpFamily | Opts], ?hlrt("listen", [{opts, Opts2}]), - ssl:listen(Port, Opts2 ++ ExtraOpts) + ssl:listen(NewPort, Opts2 ++ ExtraOpts) end. -ipfamily_default(Addr, Port) -> - httpd_conf:lookup(Addr, Port, ipfamily, inet6fb4). -get_socket_info(Addr, Port, Fd0) -> +get_socket_info(Addr, Port, Fd) -> BaseOpts = [{backlog, 128}, {reuseaddr, true}], - IpFamilyDefault = ipfamily_default(Addr, Port), %% The presence of a file descriptor takes precedence - case get_fd(Port, Fd0, IpFamilyDefault) of - {Fd, IpFamily} -> - {0, sock_opts(Addr, [{fd, Fd} | BaseOpts]), IpFamily}; + case Fd of undefined -> - {Port, sock_opts(Addr, BaseOpts), IpFamilyDefault} + {Port, sock_opts(Addr, BaseOpts)}; + Fd -> + {0, sock_opts(Addr, [{fd, Fd} | BaseOpts])} end. -get_fd(Port, undefined = _Fd, IpFamilyDefault) -> - FdKey = list_to_atom("httpd_" ++ integer_to_list(Port)), - case init:get_argument(FdKey) of - {ok, [[Value]]} -> - case string:tokens(Value, [$|]) of - [FdStr, IpFamilyStr] -> - {fd_of(FdStr), ip_family_of(IpFamilyStr)}; - [FdStr] -> - {fd_of(FdStr), IpFamilyDefault}; - _ -> - throw({error, {bad_descriptor, Value}}) - end; - error -> - undefined - end; -get_fd(_Port, Fd, IpFamilyDefault) -> - {Fd, IpFamilyDefault}. - - -fd_of(FdStr) -> - case (catch list_to_integer(FdStr)) of - Fd when is_integer(Fd) -> - Fd; - _ -> - throw({error, {bad_descriptor, FdStr}}) - end. - -ip_family_of(IpFamilyStr) -> - IpFamily = list_to_atom(IpFamilyStr), - case lists:member(IpFamily, [inet, inet6, inet6fb4]) of - true -> - IpFamily; - false -> - throw({error, {bad_ipfamily, IpFamilyStr}}) - end. - - %%------------------------------------------------------------------------- %% accept(SocketType, ListenSocket) -> {ok, Socket} | {error, Reason} %% accept(SocketType, ListenSocket, Timeout) -> ok | {error, Reason} diff --git a/lib/inets/src/http_server/httpd_acceptor.erl b/lib/inets/src/http_server/httpd_acceptor.erl index 08ee9ee0d0..1bffcc1f12 100644 --- a/lib/inets/src/http_server/httpd_acceptor.erl +++ b/lib/inets/src/http_server/httpd_acceptor.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2001-2011. All Rights Reserved. +%% Copyright Ericsson AB 2001-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -24,10 +24,10 @@ -include("inets_internal.hrl"). %% Internal application API --export([start_link/5, start_link/6]). +-export([start_link/6, start_link/7]). %% Other exports (for spawn's etc.) --export([acceptor_init/6, acceptor_init/7, acceptor_loop/5]). +-export([acceptor_init/7, acceptor_init/8, acceptor_loop/6]). %% %% External API @@ -35,27 +35,27 @@ %% start_link -start_link(Manager, SocketType, Addr, Port, ConfigDb, AcceptTimeout) -> +start_link(Manager, SocketType, Addr, Port, IpFamily, ConfigDb, AcceptTimeout) -> ?hdrd("start link", [{manager, Manager}, {socket_type, SocketType}, {address, Addr}, {port, Port}, {timeout, AcceptTimeout}]), - Args = [self(), Manager, SocketType, Addr, Port, ConfigDb, AcceptTimeout], + Args = [self(), Manager, SocketType, Addr, Port, IpFamily, ConfigDb, AcceptTimeout], proc_lib:start_link(?MODULE, acceptor_init, Args). -start_link(Manager, SocketType, ListenSocket, ConfigDb, AcceptTimeout) -> +start_link(Manager, SocketType, ListenSocket, IpFamily, ConfigDb, AcceptTimeout) -> ?hdrd("start link", [{manager, Manager}, {socket_type, SocketType}, {listen_socket, ListenSocket}, {timeout, AcceptTimeout}]), - Args = [self(), Manager, SocketType, ListenSocket, + Args = [self(), Manager, SocketType, ListenSocket, IpFamily, ConfigDb, AcceptTimeout], proc_lib:start_link(?MODULE, acceptor_init, Args). -acceptor_init(Parent, Manager, SocketType, {ListenOwner, ListenSocket}, +acceptor_init(Parent, Manager, SocketType, {ListenOwner, ListenSocket}, IpFamily, ConfigDb, AcceptTimeout) -> ?hdrd("acceptor init", [{parent, Parent}, @@ -66,9 +66,9 @@ acceptor_init(Parent, Manager, SocketType, {ListenOwner, ListenSocket}, {timeout, AcceptTimeout}]), link(ListenOwner), proc_lib:init_ack(Parent, {ok, self()}), - acceptor_loop(Manager, SocketType, ListenSocket, ConfigDb, AcceptTimeout). + acceptor_loop(Manager, SocketType, ListenSocket, IpFamily, ConfigDb, AcceptTimeout). -acceptor_init(Parent, Manager, SocketType, Addr, Port, +acceptor_init(Parent, Manager, SocketType, Addr, Port, IpFamily, ConfigDb, AcceptTimeout) -> ?hdrd("acceptor init", [{parent, Parent}, @@ -77,20 +77,20 @@ acceptor_init(Parent, Manager, SocketType, Addr, Port, {address, Addr}, {port, Port}, {timeout, AcceptTimeout}]), - case (catch do_init(SocketType, Addr, Port)) of + case (catch do_init(SocketType, Addr, Port, IpFamily)) of {ok, ListenSocket} -> proc_lib:init_ack(Parent, {ok, self()}), acceptor_loop(Manager, SocketType, - ListenSocket, ConfigDb, AcceptTimeout); + ListenSocket, IpFamily,ConfigDb, AcceptTimeout); Error -> proc_lib:init_ack(Parent, Error), error end. -do_init(SocketType, Addr, Port) -> +do_init(SocketType, Addr, Port, IpFamily) -> ?hdrt("do init", []), do_socket_start(SocketType), - ListenSocket = do_socket_listen(SocketType, Addr, Port), + ListenSocket = do_socket_listen(SocketType, Addr, Port, IpFamily), {ok, ListenSocket}. @@ -105,9 +105,9 @@ do_socket_start(SocketType) -> end. -do_socket_listen(SocketType, Addr, Port) -> +do_socket_listen(SocketType, Addr, Port, IpFamily) -> ?hdrt("do socket listen", []), - case http_transport:listen(SocketType, Addr, Port) of + case http_transport:listen(SocketType, Addr, Port, IpFamily) of {ok, ListenSocket} -> ListenSocket; {error, Reason} -> @@ -121,7 +121,7 @@ do_socket_listen(SocketType, Addr, Port) -> %% acceptor -acceptor_loop(Manager, SocketType, ListenSocket, ConfigDb, AcceptTimeout) -> +acceptor_loop(Manager, SocketType, ListenSocket, IpFamily, ConfigDb, AcceptTimeout) -> ?hdrd("awaiting accept", [{manager, Manager}, {socket_type, SocketType}, @@ -133,12 +133,12 @@ acceptor_loop(Manager, SocketType, ListenSocket, ConfigDb, AcceptTimeout) -> handle_connection(Manager, ConfigDb, AcceptTimeout, SocketType, Socket), ?MODULE:acceptor_loop(Manager, SocketType, - ListenSocket, ConfigDb,AcceptTimeout); + ListenSocket, IpFamily, ConfigDb,AcceptTimeout); {error, Reason} -> ?hdri("accept failed", [{reason, Reason}]), handle_error(Reason, ConfigDb), ?MODULE:acceptor_loop(Manager, SocketType, ListenSocket, - ConfigDb, AcceptTimeout); + IpFamily, ConfigDb, AcceptTimeout); {'EXIT', Reason} -> ?hdri("accept exited", [{reason, Reason}]), ReasonString = diff --git a/lib/inets/src/http_server/httpd_acceptor_sup.erl b/lib/inets/src/http_server/httpd_acceptor_sup.erl index 8b1e4b6c4f..df837b5a24 100644 --- a/lib/inets/src/http_server/httpd_acceptor_sup.erl +++ b/lib/inets/src/http_server/httpd_acceptor_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2001-2009. All Rights Reserved. +%% Copyright Ericsson AB 2001-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -27,7 +27,7 @@ -behaviour(supervisor). %% API --export([start_link/2, start_acceptor/5, start_acceptor/6, stop_acceptor/2]). +-export([start_link/2, start_acceptor/6, start_acceptor/7, stop_acceptor/2]). %% Supervisor callback -export([init/1]). @@ -43,11 +43,11 @@ start_link(Addr, Port) -> %% Function: [start|stop]_acceptor/5 %% Description: Starts/stops an [auth | security] worker (child) process %%---------------------------------------------------------------------- -start_acceptor(SocketType, Addr, Port, ConfigDb, AcceptTimeout) -> - start_worker(httpd_acceptor, SocketType, Addr, Port, +start_acceptor(SocketType, Addr, Port, IpFamily, ConfigDb, AcceptTimeout) -> + start_worker(httpd_acceptor, SocketType, Addr, Port, IpFamily, ConfigDb, AcceptTimeout, self(), []). -start_acceptor(SocketType, Addr, Port, ConfigDb, AcceptTimeout, ListenSocket) -> - start_worker(httpd_acceptor, SocketType, Addr, Port, +start_acceptor(SocketType, Addr, Port, IpFamily, ConfigDb, AcceptTimeout, ListenSocket) -> + start_worker(httpd_acceptor, SocketType, Addr, Port, IpFamily, ConfigDb, AcceptTimeout, ListenSocket, self(), []). @@ -69,18 +69,18 @@ init(_) -> make_name(Addr,Port) -> httpd_util:make_name("httpd_acc_sup", Addr, Port). -start_worker(M, SocketType, Addr, Port, ConfigDB, AcceptTimeout, Manager, Modules) -> +start_worker(M, SocketType, Addr, Port, IpFamily, ConfigDB, AcceptTimeout, Manager, Modules) -> SupName = make_name(Addr, Port), - Args = [Manager, SocketType, Addr, Port, ConfigDB, AcceptTimeout], + Args = [Manager, SocketType, Addr, Port, IpFamily, ConfigDB, AcceptTimeout], Spec = {{M, Addr, Port}, {M, start_link, Args}, permanent, timer:seconds(1), worker, [M] ++ Modules}, supervisor:start_child(SupName, Spec). -start_worker(M, SocketType, Addr, Port, ConfigDB, AcceptTimeout, ListenSocket, +start_worker(M, SocketType, Addr, Port, IpFamily, ConfigDB, AcceptTimeout, ListenSocket, Manager, Modules) -> SupName = make_name(Addr, Port), - Args = [Manager, SocketType, ListenSocket, ConfigDB, AcceptTimeout], + Args = [Manager, SocketType, ListenSocket, IpFamily, ConfigDB, AcceptTimeout], Spec = {{M, Addr, Port}, {M, start_link, Args}, permanent, timer:seconds(1), worker, [M] ++ Modules}, diff --git a/lib/inets/src/http_server/httpd_conf.erl b/lib/inets/src/http_server/httpd_conf.erl index 190967f656..b3ca13e2fe 100644 --- a/lib/inets/src/http_server/httpd_conf.erl +++ b/lib/inets/src/http_server/httpd_conf.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1997-2012. All Rights Reserved. +%% Copyright Ericsson AB 1997-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -589,11 +589,17 @@ validate_config_params([{server_tokens, {private, Value}} | Rest]) validate_config_params([{server_tokens, Value} | _]) -> throw({server_tokens, Value}); +validate_config_params([{socket_type, ip_comm} | Rest]) -> + validate_config_params(Rest); + validate_config_params([{socket_type, Value} | Rest]) - when (Value =:= ip_comm) orelse - (Value =:= ssl) orelse - (Value =:= essl) -> + when Value == ssl; Value == essl -> validate_config_params(Rest); + +validate_config_params([{socket_type, {Value, _}} | Rest]) + when Value == essl orelse Value == ssl -> + validate_config_params(Rest); + validate_config_params([{socket_type, Value} | _]) -> throw({socket_type, Value}); @@ -847,9 +853,7 @@ os_info(Info) -> {OsFamily, _OsName} when Info =:= partial -> lists:flatten(io_lib:format("(~w)", [OsFamily])); {OsFamily, OsName} -> - lists:flatten(io_lib:format("(~w/~w)", [OsFamily, OsName])); - OsFamily -> - lists:flatten(io_lib:format("(~w)", [OsFamily])) + lists:flatten(io_lib:format("(~w/~w)", [OsFamily, OsName])) end. otp_release() -> @@ -925,6 +929,8 @@ lookup_socket_type(ConfigDB) -> case httpd_util:lookup(ConfigDB, socket_type, ip_comm) of ip_comm -> ip_comm; + {Tag, Conf} -> + {Tag, Conf}; SSL when (SSL =:= ssl) orelse (SSL =:= essl) -> SSLTag = if diff --git a/lib/inets/src/http_server/httpd_manager.erl b/lib/inets/src/http_server/httpd_manager.erl index 7bafc3f7f3..00384fa108 100644 --- a/lib/inets/src/http_server/httpd_manager.erl +++ b/lib/inets/src/http_server/httpd_manager.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2000-2010. All Rights Reserved. +%% Copyright Ericsson AB 2000-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -264,11 +264,12 @@ init([ConfigFile, ConfigList, AcceptTimeout, Addr, Port, ListenInfo]) -> end. do_init(ConfigFile, ConfigList, AcceptTimeout, Addr, Port) -> + IpFamily = proplists:get_value(ipfamily, ConfigList, inet6fb4), NewConfigFile = proplists:get_value(file, ConfigList, ConfigFile), ConfigDB = do_initial_store(ConfigList), SocketType = httpd_conf:lookup_socket_type(ConfigDB), case httpd_acceptor_sup:start_acceptor(SocketType, Addr, - Port, ConfigDB, AcceptTimeout) of + Port, IpFamily, ConfigDB, AcceptTimeout) of {ok, _Pid} -> Status = [{max_conn, 0}, {last_heavy_load, never}, @@ -284,11 +285,12 @@ do_init(ConfigFile, ConfigList, AcceptTimeout, Addr, Port) -> end. do_init(ConfigFile, ConfigList, AcceptTimeout, Addr, Port, ListenInfo) -> + IpFamily = proplists:get_value(ipfamily, ConfigList, inet6fb4), NewConfigFile = proplists:get_value(file, ConfigList, ConfigFile), ConfigDB = do_initial_store(ConfigList), SocketType = httpd_conf:lookup_socket_type(ConfigDB), case httpd_acceptor_sup:start_acceptor(SocketType, Addr, - Port, ConfigDB, + Port, IpFamily, ConfigDB, AcceptTimeout, ListenInfo) of {ok, _Pid} -> Status = [{max_conn,0}, {last_heavy_load,never}, @@ -660,11 +662,11 @@ handle_unblock(S, FromA) -> handle_unblock(S, _FromA, unblocked) -> {ok,S}; handle_unblock(S, FromA, _AdminState) -> - stop_block_tmr(S#state.blocking_tmr), case S#state.blocking_tmr of - {_Tmr,FromB,Ref} -> + {Tmr,FromB,Ref} -> %% Another process is trying to unblock %% Inform the blocker + stop_block_tmr(Tmr), FromB ! {block_reply, {error,{unblocked,FromA}},Ref}; _ -> ok diff --git a/lib/inets/src/http_server/httpd_response.erl b/lib/inets/src/http_server/httpd_response.erl index 2dedb088e4..6b6532266b 100644 --- a/lib/inets/src/http_server/httpd_response.erl +++ b/lib/inets/src/http_server/httpd_response.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1997-2011. All Rights Reserved. +%% Copyright Ericsson AB 1997-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -35,8 +35,7 @@ generate_and_send_response(#mod{init_data = #init_data{peername = {_,"unknown"}}}) -> ok; generate_and_send_response(#mod{config_db = ConfigDB} = ModData) -> - Modules = httpd_util:lookup(ConfigDB,modules, - [mod_get, mod_head, mod_log]), + Modules = httpd_util:lookup(ConfigDB,modules, ?DEFAULT_MODS), case traverse_modules(ModData, Modules) of done -> ok; @@ -71,7 +70,6 @@ traverse_modules(ModData,[Module|Rest]) -> ?hdrd("traverse modules", [{callback_module, Module}]), case (catch apply(Module, do, [ModData])) of {'EXIT', Reason} -> - ?hdrd("traverse modules - exit", [{reason, Reason}]), String = lists:flatten( io_lib:format("traverse exit from apply: ~p:do => ~n~p", diff --git a/lib/inets/src/http_server/httpd_sup.erl b/lib/inets/src/http_server/httpd_sup.erl index 8f3e8f9500..3b1e16cf78 100644 --- a/lib/inets/src/http_server/httpd_sup.erl +++ b/lib/inets/src/http_server/httpd_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2012. All Rights Reserved. +%% Copyright Ericsson AB 2004-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -196,7 +196,8 @@ httpd_child_spec(ConfigFile, AcceptTimeoutDef, DebugDef) -> end. httpd_child_spec(Config, AcceptTimeout, Debug, Addr, Port) -> - case (Port =:= 0) orelse proplists:is_defined(fd, Config) of + Fd = proplists:get_value(fd, Config, undefined), + case Port == 0 orelse Fd =/= undefined of true -> httpd_child_spec_listen(Config, AcceptTimeout, Debug, Addr, Port); false -> @@ -242,21 +243,27 @@ error_msg(F, A) -> error_logger:error_msg(F ++ "~n", A). listen(Address, Port, Config) -> - SocketType = proplists:get_value(socket_type, Config, ip_comm), - case http_transport:start(SocketType) of - ok -> - Fd = proplists:get_value(fd, Config), - case http_transport:listen(SocketType, Address, Port, Fd) of - {ok, ListenSocket} -> - NewConfig = proplists:delete(port, Config), - {ok, NewPort} = inet:port(ListenSocket), - {NewPort, [{port, NewPort} | NewConfig], ListenSocket}; + try socket_type(Config) of + SocketType -> + case http_transport:start(SocketType) of + ok -> + Fd = proplists:get_value(fd, Config), + IpFamily = proplists:get_value(ipfamily, Config, inet6fb4), + case http_transport:listen(SocketType, Address, Port, Fd, IpFamily) of + {ok, ListenSocket} -> + NewConfig = proplists:delete(port, Config), + {NewPort, _} = http_transport:sockname(SocketType, ListenSocket), + {NewPort, [{port, NewPort} | NewConfig], ListenSocket}; + {error, Reason} -> + {error, {listen, Reason}} + end; {error, Reason} -> - {error, {listen, Reason}} - end; - {error, Reason} -> + {error, {socket_start_failed, Reason}} + end + catch + _:Reason -> {error, {socket_start_failed, Reason}} - end. + end. start_listen(Address, Port, Config) -> Pid = listen_owner(Address, Port, Config), @@ -280,7 +287,82 @@ listen_loop() -> ok end. +socket_type(Config) -> + SocketType = proplists:get_value(socket_type, Config, ip_comm), + socket_type(SocketType, Config). + +socket_type(ip_comm = SocketType, _) -> + SocketType; +socket_type({essl, _} = SocketType, _) -> + SocketType; +socket_type(_, Config) -> + {essl, ssl_config(Config)}. + +%%% Backwards compatibility +ssl_config(Config) -> + ssl_certificate_key_file(Config) ++ + ssl_verify_client(Config) ++ + ssl_ciphers(Config) ++ + ssl_password(Config) ++ + ssl_verify_depth(Config) ++ + ssl_ca_certificate_file(Config). + +ssl_certificate_key_file(Config) -> + case proplists:get_value(ssl_certificate_key_file, Config) of + undefined -> + []; + SSLCertificateKeyFile -> + [{keyfile,SSLCertificateKeyFile}] + end. +ssl_verify_client(Config) -> + case proplists:get_value(ssl_verify_client, Config) of + undefined -> + []; + SSLVerifyClient -> + [{verify,SSLVerifyClient}] + end. +ssl_ciphers(Config) -> + case proplists:get_value(ssl_ciphers, Config) of + undefined -> + []; + Ciphers -> + [{ciphers, Ciphers}] + end. +ssl_password(Config) -> + case proplists:get_value(ssl_password_callback_module, Config) of + undefined -> + []; + Module -> + case proplists:get_value(ssl_password_callback_function, Config) of + undefined -> + []; + Function -> + Args = case proplists:get_value(ssl_password_callback_arguments, Config) of + undefined -> + []; + Arguments -> + [Arguments] + end, + Password = apply(Module, Function, Args), + [{password, Password}] + end + end. +ssl_verify_depth(Config) -> + case proplists:get_value(ssl_verify_client_depth, Config) of + undefined -> + []; + Depth -> + [{depth, Depth}] + end. + +ssl_ca_certificate_file(Config) -> + case proplists:get_value(ssl_ca_certificate_file, Config) of + undefined -> + []; + File -> + [{cacertfile, File}] + end. diff --git a/lib/inets/src/inets_app/Makefile b/lib/inets/src/inets_app/Makefile index 7d68145287..22426eee79 100644 --- a/lib/inets/src/inets_app/Makefile +++ b/lib/inets/src/inets_app/Makefile @@ -99,10 +99,10 @@ docs: # ---------------------------------------------------- $(APP_TARGET): $(APP_SRC) ../../vsn.mk - sed -e 's;%VSN%;$(VSN);' $< > $@ + $(vsn_verbose)sed -e 's;%VSN%;$(VSN);' $< > $@ $(APPUP_TARGET): $(APPUP_SRC) ../../vsn.mk - sed -e 's;%VSN%;$(VSN);' $< > $@ + $(vsn_verbose)sed -e 's;%VSN%;$(VSN);' $< > $@ # ---------------------------------------------------- diff --git a/lib/inets/src/inets_app/inets.appup.src b/lib/inets/src/inets_app/inets.appup.src index 3e005379bb..c63dcafa6c 100644 --- a/lib/inets/src/inets_app/inets.appup.src +++ b/lib/inets/src/inets_app/inets.appup.src @@ -1,7 +1,7 @@ %% This is an -*- erlang -*- file. %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1999-2012. All Rights Reserved. +%% Copyright Ericsson AB 1999-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -18,129 +18,8 @@ {"%VSN%", [ - {"5.9.2.1", - [ - {restart_application, inets} - ] - }, - {"5.9.2", - [ - {load_module, httpd_manager, soft_purge, soft_purge, []} - ] - }, - {"5.9.1", - [ - {load_module, httpd_request_handler, soft_purge, soft_purge, []} - ] - }, - {"5.9", - [ - {load_module, httpd_request_handler, soft_purge, soft_purge, []}, - {load_module, tftp, soft_purge, soft_purge, [inets_service]}, - {load_module, inets_service, soft_purge, soft_purge, []}, - {load_module, httpc, soft_purge, soft_purge, [httpc_manager]}, - {update, httpc_handler, soft, soft_purge, soft_purge, [httpc_manager]}, - {update, httpc_manager, soft, soft_purge, soft_purge, []} - ] - }, - {"5.8.1", - [ - {load_module, httpd_request_handler, soft_purge, soft_purge, []}, - {load_module, tftp, soft_purge, soft_purge, [inets_service]}, - {load_module, inets_service, soft_purge, soft_purge, []}, - - {load_module, http_uri, soft_purge, soft_purge, []}, - {load_module, httpc_response, soft_purge, soft_purge, [http_uri]}, - - {load_module, httpc, soft_purge, soft_purge, - [http_uri, httpc_manager]}, - - {load_module, inets_app, soft_purge, soft_purge, [inets_sup]}, - {update, inets_sup, soft, soft_purge, soft_purge, []}, - - {load_module, httpd_conf, soft_purge, soft_purge, []}, - {load_module, httpd_response, soft_purge, soft_purge, []}, - {load_module, httpd_script_env, soft_purge, soft_purge, []}, - - {load_module, inets, soft_purge, soft_purge, [inets_trace]}, - {update, httpc_manager, soft, soft_purge, soft_purge, [http_uri]}, - {update, httpc_handler, soft, soft_purge, soft_purge, [httpc_manager]}, - {update, httpd_sup, soft, soft_purge, soft_purge, []}, - {add_module, inets_trace} - ] - }, - {"5.8", - [ - {restart_application, inets} - ] - }, - {"5.7.2", - [ - {restart_application, inets} - ] - } - ], + {<<"5\\.*">>, [{restart_application, inets}]} + ], [ - {"5.9.2.1", - [ - {restart_application, inets} - ] - }, - {"5.9.2", - [ - {load_module, httpd_manager, soft_purge, soft_purge, []} - ] - }, - {"5.9.1", - [ - {load_module, httpd_request_handler, soft_purge, soft_purge, []} - ] - }, - {"5.9", - [ - {load_module, httpd_request_handler, soft_purge, soft_purge, []}, - {load_module, tftp, soft_purge, soft_purge, [inets_service]}, - {load_module, inets_service, soft_purge, soft_purge, []}, - {load_module, httpc, soft_purge, soft_purge, [httpc_manager]}, - {update, httpc_handler, soft, soft_purge, soft_purge, [httpc_manager]}, - {update, httpc_manager, soft, soft_purge, soft_purge, []} - ] - }, - {"5.8.1", - [ - {load_module, httpd_request_handler, soft_purge, soft_purge, []}, - {load_module, tftp, soft_purge, soft_purge, [inets_service]}, - {load_module, inets_service, soft_purge, soft_purge, []}, - - {load_module, http_uri, soft_purge, soft_purge, []}, - {load_module, httpc_response, soft_purge, soft_purge, [http_uri]}, - - {load_module, httpc, soft_purge, soft_purge, - [http_uri, httpc_manager]}, - - {load_module, inets_app, soft_purge, soft_purge, [inets_sup]}, - {update, inets_sup, soft, soft_purge, soft_purge, []}, - - {load_module, httpd_conf, soft_purge, soft_purge, []}, - {load_module, httpd_response, soft_purge, soft_purge, []}, - {load_module, httpd_script_env, soft_purge, soft_purge, []}, - - {load_module, inets, soft_purge, soft_purge, []}, - {update, httpc_manager, soft, soft_purge, soft_purge, [http_uri]}, - {update, httpc_handler, soft, soft_purge, soft_purge, [httpc_manager]}, - {update, httpd_sup, soft, soft_purge, soft_purge, []}, - {remove, {inets_trace, soft_purge, brutal_purge}} - ] - }, - {"5.8", - [ - {restart_application, inets} - ] - }, - {"5.7.2", - [ - {restart_application, inets} - ] - } - ] -}. + {<<"5\\.*">>, [{restart_application, inets}]} +]}. diff --git a/lib/inets/src/inets_app/inets.erl b/lib/inets/src/inets_app/inets.erl index f33e0abe27..ed8082534f 100644 --- a/lib/inets/src/inets_app/inets.erl +++ b/lib/inets/src/inets_app/inets.erl @@ -274,13 +274,8 @@ sys_info() -> os_info() -> V = os:version(), - case os:type() of - {OsFam, OsName} -> - [{fam, OsFam}, {name, OsName}, {ver, V}]; - OsFam -> - [{fam, OsFam}, {ver, V}] - end. - + {OsFam, OsName} = os:type(), + [{fam, OsFam}, {name, OsName}, {ver, V}]. print_mods_info(Versions) -> case key1search(mod_info, Versions) of diff --git a/lib/inets/test/Makefile b/lib/inets/test/Makefile index 0fc98eff6f..dfa86906fd 100644 --- a/lib/inets/test/Makefile +++ b/lib/inets/test/Makefile @@ -1,7 +1,7 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 1997-2012. All Rights Reserved. +# Copyright Ericsson AB 1997-2013. All Rights Reserved. # # The contents of this file are subject to the Erlang Public License, # Version 1.1, (the "License"); you may not use this file except in @@ -149,6 +149,7 @@ INETS_ROOT = ../../inets MODULES = \ inets_test_lib \ + erl_make_certs \ ftp_SUITE \ ftp_format_SUITE \ ftp_solaris8_sparc_test \ @@ -169,6 +170,7 @@ MODULES = \ http_format_SUITE \ httpc_SUITE \ httpc_cookie_SUITE \ + httpc_proxy_SUITE \ httpd_SUITE \ httpd_basic_SUITE \ httpd_mod \ @@ -182,7 +184,8 @@ MODULES = \ inets_app_test \ inets_appup_test \ tftp_test_lib \ - tftp_SUITE + tftp_SUITE \ + uri_SUITE EBIN = . @@ -213,7 +216,7 @@ INETS_FILES = inets.config $(INETS_SPECS) INETS_DATADIRS = inets_SUITE_data inets_sup_SUITE_data HTTPD_DATADIRS = httpd_test_data httpd_SUITE_data -HTTPC_DATADIRS = httpc_SUITE_data +HTTPC_DATADIRS = httpc_SUITE_data httpc_proxy_SUITE_data FTP_DATADIRS = ftp_SUITE_data DATADIRS = $(INETS_DATADIRS) $(HTTPD_DATADIRS) $(HTTPC_DATADIRS) $(FTP_DATADIRS) diff --git a/lib/inets/test/erl_make_certs.erl b/lib/inets/test/erl_make_certs.erl new file mode 100644 index 0000000000..22dc951ac1 --- /dev/null +++ b/lib/inets/test/erl_make_certs.erl @@ -0,0 +1,482 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2011-2013. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% Create test certificates + +-module(erl_make_certs). +-include_lib("public_key/include/public_key.hrl"). + +-export([make_cert/1, gen_rsa/1, verify_signature/3, write_pem/3]). +-compile(export_all). + +%%-------------------------------------------------------------------- +%% @doc Create and return a der encoded certificate +%% Option Default +%% ------------------------------------------------------- +%% digest sha1 +%% validity {date(), date() + week()} +%% version 3 +%% subject [] list of the following content +%% {name, Name} +%% {email, Email} +%% {city, City} +%% {state, State} +%% {org, Org} +%% {org_unit, OrgUnit} +%% {country, Country} +%% {serial, Serial} +%% {title, Title} +%% {dnQualifer, DnQ} +%% issuer = {Issuer, IssuerKey} true (i.e. a ca cert is created) +%% (obs IssuerKey migth be {Key, Password} +%% key = KeyFile|KeyBin|rsa|dsa|ec Subject PublicKey rsa, dsa or ec generates key +%% +%% +%% (OBS: The generated keys are for testing only) +%% @spec ([{::atom(), ::term()}]) -> {Cert::binary(), Key::binary()} +%% @end +%%-------------------------------------------------------------------- + +make_cert(Opts) -> + SubjectPrivateKey = get_key(Opts), + {TBSCert, IssuerKey} = make_tbs(SubjectPrivateKey, Opts), + Cert = public_key:pkix_sign(TBSCert, IssuerKey), + true = verify_signature(Cert, IssuerKey, undef), %% verify that the keys where ok + {Cert, encode_key(SubjectPrivateKey)}. + +%%-------------------------------------------------------------------- +%% @doc Writes pem files in Dir with FileName ++ ".pem" and FileName ++ "_key.pem" +%% @spec (::string(), ::string(), {Cert,Key}) -> ok +%% @end +%%-------------------------------------------------------------------- +write_pem(Dir, FileName, {Cert, Key = {_,_,not_encrypted}}) when is_binary(Cert) -> + ok = der_to_pem(filename:join(Dir, FileName ++ ".pem"), + [{'Certificate', Cert, not_encrypted}]), + ok = der_to_pem(filename:join(Dir, FileName ++ "_key.pem"), [Key]). + +%%-------------------------------------------------------------------- +%% @doc Creates a rsa key (OBS: for testing only) +%% the size are in bytes +%% @spec (::integer()) -> {::atom(), ::binary(), ::opaque()} +%% @end +%%-------------------------------------------------------------------- +gen_rsa(Size) when is_integer(Size) -> + Key = gen_rsa2(Size), + {Key, encode_key(Key)}. + +%%-------------------------------------------------------------------- +%% @doc Creates a dsa key (OBS: for testing only) +%% the sizes are in bytes +%% @spec (::integer()) -> {::atom(), ::binary(), ::opaque()} +%% @end +%%-------------------------------------------------------------------- +gen_dsa(LSize,NSize) when is_integer(LSize), is_integer(NSize) -> + Key = gen_dsa2(LSize, NSize), + {Key, encode_key(Key)}. + +%%-------------------------------------------------------------------- +%% @doc Creates a ec key (OBS: for testing only) +%% the sizes are in bytes +%% @spec (::integer()) -> {::atom(), ::binary(), ::opaque()} +%% @end +%%-------------------------------------------------------------------- +gen_ec(Curve) when is_atom(Curve) -> + Key = gen_ec2(Curve), + {Key, encode_key(Key)}. + +%%-------------------------------------------------------------------- +%% @doc Verifies cert signatures +%% @spec (::binary(), ::tuple()) -> ::boolean() +%% @end +%%-------------------------------------------------------------------- +verify_signature(DerEncodedCert, DerKey, _KeyParams) -> + Key = decode_key(DerKey), + case Key of + #'RSAPrivateKey'{modulus=Mod, publicExponent=Exp} -> + public_key:pkix_verify(DerEncodedCert, + #'RSAPublicKey'{modulus=Mod, publicExponent=Exp}); + #'DSAPrivateKey'{p=P, q=Q, g=G, y=Y} -> + public_key:pkix_verify(DerEncodedCert, {Y, #'Dss-Parms'{p=P, q=Q, g=G}}); + #'ECPrivateKey'{version = _Version, privateKey = _PrivKey, + parameters = Params, publicKey = {0, PubKey}} -> + public_key:pkix_verify(DerEncodedCert, {#'ECPoint'{point = PubKey}, Params}) + end. + +%%%%%%%%%%%%%%%%%%%%%%%%% Implementation %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +get_key(Opts) -> + case proplists:get_value(key, Opts) of + undefined -> make_key(rsa, Opts); + rsa -> make_key(rsa, Opts); + dsa -> make_key(dsa, Opts); + ec -> make_key(ec, Opts); + Key -> + Password = proplists:get_value(password, Opts, no_passwd), + decode_key(Key, Password) + end. + +decode_key({Key, Pw}) -> + decode_key(Key, Pw); +decode_key(Key) -> + decode_key(Key, no_passwd). + + +decode_key(#'RSAPublicKey'{} = Key,_) -> + Key; +decode_key(#'RSAPrivateKey'{} = Key,_) -> + Key; +decode_key(#'DSAPrivateKey'{} = Key,_) -> + Key; +decode_key(#'ECPrivateKey'{} = Key,_) -> + Key; +decode_key(PemEntry = {_,_,_}, Pw) -> + public_key:pem_entry_decode(PemEntry, Pw); +decode_key(PemBin, Pw) -> + [KeyInfo] = public_key:pem_decode(PemBin), + decode_key(KeyInfo, Pw). + +encode_key(Key = #'RSAPrivateKey'{}) -> + {ok, Der} = 'OTP-PUB-KEY':encode('RSAPrivateKey', Key), + {'RSAPrivateKey', Der, not_encrypted}; +encode_key(Key = #'DSAPrivateKey'{}) -> + {ok, Der} = 'OTP-PUB-KEY':encode('DSAPrivateKey', Key), + {'DSAPrivateKey', Der, not_encrypted}; +encode_key(Key = #'ECPrivateKey'{}) -> + {ok, Der} = 'OTP-PUB-KEY':encode('ECPrivateKey', Key), + {'ECPrivateKey', Der, not_encrypted}. + +make_tbs(SubjectKey, Opts) -> + Version = list_to_atom("v"++integer_to_list(proplists:get_value(version, Opts, 3))), + + IssuerProp = proplists:get_value(issuer, Opts, true), + {Issuer, IssuerKey} = issuer(IssuerProp, Opts, SubjectKey), + + {Algo, Parameters} = sign_algorithm(IssuerKey, Opts), + + SignAlgo = #'SignatureAlgorithm'{algorithm = Algo, + parameters = Parameters}, + Subject = case IssuerProp of + true -> %% Is a Root Ca + Issuer; + _ -> + subject(proplists:get_value(subject, Opts),false) + end, + + {#'OTPTBSCertificate'{serialNumber = trunc(random:uniform()*100000000)*10000 + 1, + signature = SignAlgo, + issuer = Issuer, + validity = validity(Opts), + subject = Subject, + subjectPublicKeyInfo = publickey(SubjectKey), + version = Version, + extensions = extensions(Opts) + }, IssuerKey}. + +issuer(true, Opts, SubjectKey) -> + %% Self signed + {subject(proplists:get_value(subject, Opts), true), SubjectKey}; +issuer({Issuer, IssuerKey}, _Opts, _SubjectKey) when is_binary(Issuer) -> + {issuer_der(Issuer), decode_key(IssuerKey)}; +issuer({File, IssuerKey}, _Opts, _SubjectKey) when is_list(File) -> + {ok, [{cert, Cert, _}|_]} = pem_to_der(File), + {issuer_der(Cert), decode_key(IssuerKey)}. + +issuer_der(Issuer) -> + Decoded = public_key:pkix_decode_cert(Issuer, otp), + #'OTPCertificate'{tbsCertificate=Tbs} = Decoded, + #'OTPTBSCertificate'{subject=Subject} = Tbs, + Subject. + +subject(undefined, IsRootCA) -> + User = if IsRootCA -> "RootCA"; true -> user() end, + Opts = [{email, User ++ "@erlang.org"}, + {name, User}, + {city, "Stockholm"}, + {country, "SE"}, + {org, "erlang"}, + {org_unit, "testing dep"}], + subject(Opts); +subject(Opts, _) -> + subject(Opts). + +user() -> + case os:getenv("USER") of + false -> + "test_user"; + User -> + User + end. + +subject(SubjectOpts) when is_list(SubjectOpts) -> + Encode = fun(Opt) -> + {Type,Value} = subject_enc(Opt), + [#'AttributeTypeAndValue'{type=Type, value=Value}] + end, + {rdnSequence, [Encode(Opt) || Opt <- SubjectOpts]}. + +%% Fill in the blanks +subject_enc({name, Name}) -> {?'id-at-commonName', {printableString, Name}}; +subject_enc({email, Email}) -> {?'id-emailAddress', Email}; +subject_enc({city, City}) -> {?'id-at-localityName', {printableString, City}}; +subject_enc({state, State}) -> {?'id-at-stateOrProvinceName', {printableString, State}}; +subject_enc({org, Org}) -> {?'id-at-organizationName', {printableString, Org}}; +subject_enc({org_unit, OrgUnit}) -> {?'id-at-organizationalUnitName', {printableString, OrgUnit}}; +subject_enc({country, Country}) -> {?'id-at-countryName', Country}; +subject_enc({serial, Serial}) -> {?'id-at-serialNumber', Serial}; +subject_enc({title, Title}) -> {?'id-at-title', {printableString, Title}}; +subject_enc({dnQualifer, DnQ}) -> {?'id-at-dnQualifier', DnQ}; +subject_enc(Other) -> Other. + + +extensions(Opts) -> + case proplists:get_value(extensions, Opts, []) of + false -> + asn1_NOVALUE; + Exts -> + lists:flatten([extension(Ext) || Ext <- default_extensions(Exts)]) + end. + +default_extensions(Exts) -> + Def = [{key_usage,undefined}, + {subject_altname, undefined}, + {issuer_altname, undefined}, + {basic_constraints, default}, + {name_constraints, undefined}, + {policy_constraints, undefined}, + {ext_key_usage, undefined}, + {inhibit_any, undefined}, + {auth_key_id, undefined}, + {subject_key_id, undefined}, + {policy_mapping, undefined}], + Filter = fun({Key, _}, D) -> lists:keydelete(Key, 1, D) end, + Exts ++ lists:foldl(Filter, Def, Exts). + +extension({_, undefined}) -> []; +extension({basic_constraints, Data}) -> + case Data of + default -> + #'Extension'{extnID = ?'id-ce-basicConstraints', + extnValue = #'BasicConstraints'{cA=true}, + critical=true}; + false -> + []; + Len when is_integer(Len) -> + #'Extension'{extnID = ?'id-ce-basicConstraints', + extnValue = #'BasicConstraints'{cA=true, pathLenConstraint=Len}, + critical=true}; + _ -> + #'Extension'{extnID = ?'id-ce-basicConstraints', + extnValue = Data} + end; +extension({Id, Data, Critical}) -> + #'Extension'{extnID = Id, extnValue = Data, critical = Critical}. + + +publickey(#'RSAPrivateKey'{modulus=N, publicExponent=E}) -> + Public = #'RSAPublicKey'{modulus=N, publicExponent=E}, + Algo = #'PublicKeyAlgorithm'{algorithm= ?rsaEncryption, parameters='NULL'}, + #'OTPSubjectPublicKeyInfo'{algorithm = Algo, + subjectPublicKey = Public}; +publickey(#'DSAPrivateKey'{p=P, q=Q, g=G, y=Y}) -> + Algo = #'PublicKeyAlgorithm'{algorithm= ?'id-dsa', + parameters={params, #'Dss-Parms'{p=P, q=Q, g=G}}}, + #'OTPSubjectPublicKeyInfo'{algorithm = Algo, subjectPublicKey = Y}; +publickey(#'ECPrivateKey'{version = _Version, + privateKey = _PrivKey, + parameters = Params, + publicKey = {0, PubKey}}) -> + Algo = #'PublicKeyAlgorithm'{algorithm= ?'id-ecPublicKey', parameters=Params}, + #'OTPSubjectPublicKeyInfo'{algorithm = Algo, + subjectPublicKey = #'ECPoint'{point = PubKey}}. + +validity(Opts) -> + DefFrom0 = calendar:gregorian_days_to_date(calendar:date_to_gregorian_days(date())-1), + DefTo0 = calendar:gregorian_days_to_date(calendar:date_to_gregorian_days(date())+7), + {DefFrom, DefTo} = proplists:get_value(validity, Opts, {DefFrom0, DefTo0}), + Format = fun({Y,M,D}) -> lists:flatten(io_lib:format("~w~2..0w~2..0w000000Z",[Y,M,D])) end, + #'Validity'{notBefore={generalTime, Format(DefFrom)}, + notAfter ={generalTime, Format(DefTo)}}. + +sign_algorithm(#'RSAPrivateKey'{}, Opts) -> + Type = case proplists:get_value(digest, Opts, sha1) of + sha1 -> ?'sha1WithRSAEncryption'; + sha512 -> ?'sha512WithRSAEncryption'; + sha384 -> ?'sha384WithRSAEncryption'; + sha256 -> ?'sha256WithRSAEncryption'; + md5 -> ?'md5WithRSAEncryption'; + md2 -> ?'md2WithRSAEncryption' + end, + {Type, 'NULL'}; +sign_algorithm(#'DSAPrivateKey'{p=P, q=Q, g=G}, _Opts) -> + {?'id-dsa-with-sha1', {params,#'Dss-Parms'{p=P, q=Q, g=G}}}; +sign_algorithm(#'ECPrivateKey'{}, Opts) -> + Type = case proplists:get_value(digest, Opts, sha1) of + sha1 -> ?'ecdsa-with-SHA1'; + sha512 -> ?'ecdsa-with-SHA512'; + sha384 -> ?'ecdsa-with-SHA384'; + sha256 -> ?'ecdsa-with-SHA256' + end, + {Type, 'NULL'}. + +make_key(rsa, _Opts) -> + %% (OBS: for testing only) + gen_rsa2(64); +make_key(dsa, _Opts) -> + gen_dsa2(128, 20); %% Bytes i.e. {1024, 160} +make_key(ec, _Opts) -> + %% (OBS: for testing only) + gen_ec2(secp256k1). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% RSA key generation (OBS: for testing only) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-define(SMALL_PRIMES, [65537,97,89,83,79,73,71,67,61,59,53, + 47,43,41,37,31,29,23,19,17,13,11,7,5,3]). + +gen_rsa2(Size) -> + P = prime(Size), + Q = prime(Size), + N = P*Q, + Tot = (P - 1) * (Q - 1), + [E|_] = lists:dropwhile(fun(Candidate) -> (Tot rem Candidate) == 0 end, ?SMALL_PRIMES), + {D1,D2} = extended_gcd(E, Tot), + D = erlang:max(D1,D2), + case D < E of + true -> + gen_rsa2(Size); + false -> + {Co1,Co2} = extended_gcd(Q, P), + Co = erlang:max(Co1,Co2), + #'RSAPrivateKey'{version = 'two-prime', + modulus = N, + publicExponent = E, + privateExponent = D, + prime1 = P, + prime2 = Q, + exponent1 = D rem (P-1), + exponent2 = D rem (Q-1), + coefficient = Co + } + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% DSA key generation (OBS: for testing only) +%% See http://en.wikipedia.org/wiki/Digital_Signature_Algorithm +%% and the fips_186-3.pdf +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +gen_dsa2(LSize, NSize) -> + Q = prime(NSize), %% Choose N-bit prime Q + X0 = prime(LSize), + P0 = prime((LSize div 2) +1), + + %% Choose L-bit prime modulus P such that p-1 is a multiple of q. + case dsa_search(X0 div (2*Q*P0), P0, Q, 1000) of + error -> + gen_dsa2(LSize, NSize); + P -> + G = crypto:mod_pow(2, (P-1) div Q, P), % Choose G a number whose multiplicative order modulo p is q. + %% such that This may be done by setting g = h^(p-1)/q mod p, commonly h=2 is used. + + X = prime(20), %% Choose x by some random method, where 0 < x < q. + Y = crypto:mod_pow(G, X, P), %% Calculate y = g^x mod p. + + #'DSAPrivateKey'{version=0, p = P, q = Q, + g = crypto:bytes_to_integer(G), y = crypto:bytes_to_integer(Y), x = X} + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EC key generation (OBS: for testing only) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +gen_ec2(CurveId) -> + {PubKey, PrivKey} = crypto:generate_key(ecdh, CurveId), + + #'ECPrivateKey'{version = 1, + privateKey = binary_to_list(PrivKey), + parameters = {namedCurve, pubkey_cert_records:namedCurves(CurveId)}, + publicKey = {0, PubKey}}. + +%% See fips_186-3.pdf +dsa_search(T, P0, Q, Iter) when Iter > 0 -> + P = 2*T*Q*P0 + 1, + case is_prime(P, 50) of + true -> P; + false -> dsa_search(T+1, P0, Q, Iter-1) + end; +dsa_search(_,_,_,_) -> + error. + + +%%%%%%% Crypto Math %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +prime(ByteSize) -> + Rand = odd_rand(ByteSize), + prime_odd(Rand, 0). + +prime_odd(Rand, N) -> + case is_prime(Rand, 50) of + true -> + Rand; + false -> + prime_odd(Rand+2, N+1) + end. + +%% see http://en.wikipedia.org/wiki/Fermat_primality_test +is_prime(_, 0) -> true; +is_prime(Candidate, Test) -> + CoPrime = odd_rand(10000, Candidate), + Result = crypto:mod_pow(CoPrime, Candidate, Candidate) , + is_prime(CoPrime, crypto:bytes_to_integer(Result), Candidate, Test). + +is_prime(CoPrime, CoPrime, Candidate, Test) -> + is_prime(Candidate, Test-1); +is_prime(_,_,_,_) -> + false. + +odd_rand(Size) -> + Min = 1 bsl (Size*8-1), + Max = (1 bsl (Size*8))-1, + odd_rand(Min, Max). + +odd_rand(Min,Max) -> + Rand = crypto:rand_uniform(Min,Max), + case Rand rem 2 of + 0 -> + Rand + 1; + _ -> + Rand + end. + +extended_gcd(A, B) -> + case A rem B of + 0 -> + {0, 1}; + N -> + {X, Y} = extended_gcd(B, N), + {Y, X-Y*(A div B)} + end. + +pem_to_der(File) -> + {ok, PemBin} = file:read_file(File), + public_key:pem_decode(PemBin). + +der_to_pem(File, Entries) -> + PemBin = public_key:pem_encode(Entries), + file:write_file(File, PemBin). + diff --git a/lib/inets/test/ftp_format_SUITE.erl b/lib/inets/test/ftp_format_SUITE.erl index cbc1b04bbb..16e5cdb4bc 100644 --- a/lib/inets/test/ftp_format_SUITE.erl +++ b/lib/inets/test/ftp_format_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2011. All Rights Reserved. +%% Copyright Ericsson AB 2005-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -273,7 +273,7 @@ ftp_other_status_codes(Config) when is_list(Config) -> %% 4XX {trans_neg_compl, _ } = ftp_response:interpret("421 Foobar\r\n"), {trans_neg_compl, _ } = ftp_response:interpret("426 Foobar\r\n"), - {trans_neg_compl, _ } = ftp_response:interpret("450 Foobar\r\n"), + {enofile, _ } = ftp_response:interpret("450 Foobar\r\n"), {trans_neg_compl, _ } = ftp_response:interpret("451 Foobar\r\n"), {etnospc, _ } = ftp_response:interpret("452 Foobar\r\n"), diff --git a/lib/inets/test/ftp_suite_lib.erl b/lib/inets/test/ftp_suite_lib.erl index ffb58c91b6..35f21cc74d 100644 --- a/lib/inets/test/ftp_suite_lib.erl +++ b/lib/inets/test/ftp_suite_lib.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2011. All Rights Reserved. +%% Copyright Ericsson AB 2005-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -206,7 +206,6 @@ init_per_testcase(Case, Config) init_per_testcase(Case, Config) -> put(ftp_testcase, Case), - inets:enable_trace(max, io, ftpc), do_init_per_testcase(Case, Config). do_init_per_testcase(Case, Config) diff --git a/lib/inets/test/http_format_SUITE.erl b/lib/inets/test/http_format_SUITE.erl index 04c7358715..c5920a3968 100644 --- a/lib/inets/test/http_format_SUITE.erl +++ b/lib/inets/test/http_format_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2011. All Rights Reserved. +%% Copyright Ericsson AB 2004-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -35,14 +35,15 @@ chunk_decode_trailer/1, http_response/1, http_request/1, validate_request_line/1, esi_parse_headers/1, cgi_parse_headers/1, - is_absolut_uri/1, convert_netscapecookie_date/1]). + is_absolut_uri/1, convert_netscapecookie_date/1, + check_content_length_encoding/1]). suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> [{group, chunk}, http_response, http_request, validate_request_line, {group, script}, is_absolut_uri, - convert_netscapecookie_date]. + convert_netscapecookie_date, check_content_length_encoding]. groups() -> [{script, [], [esi_parse_headers, cgi_parse_headers]}, @@ -456,6 +457,25 @@ validate_request_line(Config) when is_list(Config) -> httpd_request:validate("GET", NewForbiddenUri1, "HTTP/1.1"), ok. + +%%------------------------------------------------------------------------- +check_content_length_encoding(doc) -> + ["Test http_request:headers/2. Check that the content-length is" + " encoded even when it is zero." ]; +check_content_length_encoding(suite) -> + []; +check_content_length_encoding(Config) when is_list(Config) -> + + %% Check that the content-length is preserved. + %% Sanity check. + Header1 = http_request:http_headers(#http_request_h{'content-length'="123"}), + true = (string:str(Header1, "content-length: 123\r\n") > 0), + %% Check that content-length=0 is handled correctly. + Header2 = http_request:http_headers(#http_request_h{'content-length'="0"}), + true = (string:str(Header2, "content-length: 0\r\n") > 0), + + ok. + %%------------------------------------------------------------------------- esi_parse_headers(doc) -> ["Test httpd_esi:*. All header values are received in the same" diff --git a/lib/inets/test/httpc_SUITE.erl b/lib/inets/test/httpc_SUITE.erl index 1cdd96f0b0..0c35f284f7 100644 --- a/lib/inets/test/httpc_SUITE.erl +++ b/lib/inets/test/httpc_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2012. All Rights Reserved. +%% Copyright Ericsson AB 2004-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -19,3057 +19,953 @@ %% %% -%% ts:run(inets, httpc_SUITE, [batch]). -%% +%% ct:run("../inets_test", httpc_SUITE). +%% -module(httpc_SUITE). --include_lib("common_test/include/ct.hrl"). --include("test_server_line.hrl"). - -include_lib("kernel/include/file.hrl"). +-include_lib("common_test/include/ct.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(URL_START, "http://"). +-define(TLS_URL_START, "https://"). -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). - - +-record(sslsocket, {fd = nil, pid = nil}). %%-------------------------------------------------------------------- -%% 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. +%% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- +suite() -> + [{ct_hooks,[ts_install_cth]}]. -suite() -> [{ct_hooks,[ts_install_cth]}]. - -all() -> +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 + {group, http}, + {group, sim_http}, + {group, https}, + {group, sim_https}, + {group, misc} ]. -groups() -> +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]} + {http, [], real_requests()}, + {sim_http, [], only_simulated()}, + {https, [], real_requests()}, + {sim_https, [], only_simulated()}, + {misc, [], misc()} ]. +real_requests()-> + [ + head, + get, + post, + post_stream, + async, + pipeline, + persistent_connection, + save_to_file, + save_to_file_async, + headers_as_is, + page_does_not_exist, + emulate_lower_versions, + headers, + headers_as_is, + empty_body, + stream, + stream_to_pid, + stream_through_fun, + stream_through_mfa, + streaming_error, + inet_opts, + invalid_headers + ]. -init_per_group(ipv6 = _GroupName, Config) -> - case inets_test_lib:has_ipv6_support() of - {ok, _} -> - Config; - _ -> - {skip, "Host does not support IPv6"} - end; -init_per_group(_GroupName, Config) -> - Config. - -end_per_group(_GroupName, Config) -> - Config. +only_simulated() -> + [ + cookie, + cookie_profile, + trace, + stream_once, + no_content_204, + tolerate_missing_CR, + userinfo, + bad_response, + internal_server_error, + invalid_http, + headers_dummy, + empty_response_header, + remote_socket_close, + remote_socket_close_async, + transfer_encoding, + redirect_loop, + redirect_moved_permanently, + redirect_multiple_choises, + redirect_found, + redirect_see_other, + redirect_temporary_redirect, + port_in_host_header, + relaxed + ]. +misc() -> + [ + server_does_not_exist, + timeout_memory_leak, + wait_for_whole_response + ]. %%-------------------------------------------------------------------- -%% Function: init_per_suite(Config) -> Config -%% Config - [tuple()] -%% A list of key/value pairs, holding the test case configuration. -%% Description: Initiation before the whole suite -%% -%% Note: This function is free to add any key/value pairs to the Config -%% variable, but should NOT alter/remove any existing entries. -%%-------------------------------------------------------------------- -init_per_suite(Config) -> - - ?PRINT_SYSTEM_INFO([]), +init_per_suite(Config) -> PrivDir = ?config(priv_dir, Config), DataDir = ?config(data_dir, Config), + inets_test_lib:start_apps([inets]), 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), + [{server_root, ServerRoot}, {doc_root, DocRoot} | Config]. - Cgi = case test_server:os_type() of - {win32, _} -> - filename:join([ServerRoot, "cgi-bin", "cgi_echo.exe"]); - _ -> - filename:join([ServerRoot, "cgi-bin", "cgi_echo"]) - end, - - {ok, FileInfo} = file:read_file_info(Cgi), - ok = file:write_file_info(Cgi, FileInfo#file_info{mode = 8#00755}), - - [{has_ipv6_support, inets_test_lib:has_ipv6_support()}, - {server_root, ServerRoot}, - {doc_root, DocRoot}, - {local_port, ?IP_PORT}, - {local_ssl_port, ?SSL_PORT} | Config]. - - -%%-------------------------------------------------------------------- -%% Function: end_per_suite(Config) -> _ -%% Config - [tuple()] -%% A list of key/value pairs, holding the test case configuration. -%% Description: Cleanup after the whole suite -%%-------------------------------------------------------------------- end_per_suite(Config) -> - PrivDir = ?config(priv_dir, Config), + inets_test_lib:stop_apps([inets]), + 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_group(misc = Group, Config) -> + start_apps(Group), + Inet = inet_version(), + ok = httpc:set_options([{ipfamily, Inet}]), + Config; + +init_per_group(Group, Config0) -> + start_apps(Group), + Config = proplists:delete(port, Config0), + Port = server_start(Group, server_config(Group, Config)), + [{port, Port} | Config]. + +end_per_group(_, _Config) -> + ok. -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(pipeline, Config) -> + inets:start(httpc, [{profile, pipeline}]), + httpc:set_options([{pipeline_timeout, 50000}, + {max_pipeline_length, 3}], pipeline), -init_per_testcase(Case, Config) -> - init_per_testcase(Case, 2, Config). + Config; +init_per_testcase(persistent_connection, Config) -> + inets:start(httpc, [{profile, persistent}]), + httpc:set_options([{keep_alive_timeout, 50000}, + {max_keep_alive_length, 3}], persistent_connection), -init_per_testcase(Case, Timeout, Config) -> - io:format(user, - "~n~n*** INIT ~w:~w[~w] ***" - "~n~n", [?MODULE, Case, Timeout]), + Config; - 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; +init_per_testcase(_Case, Config) -> + Config. - _ -> - %% 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), - ok = httpc:set_options([{ipfamily, inet6}], - ProfilePid), - tsp("httpc profile pid: ~p", [ProfilePid]), - [{watchdog, Dog}, {profile, ProfilePid}| TmpConfig] - catch - throw:{error, {failed_starting, App, ActualError}} -> - tsp("init_per_testcase(~w) -> failed starting ~w: " - "~n ~p", [Case, App, ActualError]), - SkipString = - "Could not start " ++ atom_to_list(App), - skip(SkipString); - _:X -> - SkipString = - lists:flatten( - io_lib:format("Failed starting apps: ~p", [X])), - skip(SkipString) - end; - - _ -> - %% Try inet6fb4 on windows... - %% No need? Since it is set above? - - %% tsp("init_per_testcase -> allways try IPv6 on windows"), - %% ?RUN_ON_WINDOWS( - %% fun() -> - %% tsp("init_per_testcase:set_options_fun -> " - %% "set-option ipfamily to inet6fb4"), - %% Res = httpc:set_options([{ipfamily, inet6fb4}]), - %% tsp("init_per_testcase:set_options_fun -> " - %% "~n Res: ~p", [Res]), - %% Res - %% end), - - TmpConfig2 = lists:keydelete(local_server, 1, TmpConfig), - %% Will start inets - tsp("init_per_testcase -> try start server"), - Server = start_http_server(PrivDir, IpConfFile), - [{watchdog, Dog}, {local_server, Server} | TmpConfig2] - end, - - %% <IPv6> - %% Set default ipfamily to the same as the main server has by default - %% This makes the client try w/ ipv6 before falling back to ipv4, - %% as that is what the server is configured to do. - %% Note that this is required for the tests to run on *BSD w/ ipv6 enabled - %% as well as on Windows. The Linux behaviour of allowing ipv4 connects - %% to ipv6 sockets is not required or even encouraged. - - tsp("init_per_testcase -> Options before ipfamily set: ~n~p", - [httpc:get_options(all)]), - ok = httpc:set_options([{ipfamily, inet6fb4}]), - tsp("init_per_testcase -> Options after ipfamily set: ~n~p", - [httpc:get_options(all)]), - - %% Note that the IPv6 test case(s) *must* use inet6, - %% so this value will be overwritten (see "ipv6_" below). - %% </IPv6> - - %% This will fail for the ipv6_ - cases (but that is ok) - ProxyExceptions = ["localhost", ?IPV6_LOCAL_HOST], - tsp("init_per_testcase -> Options before proxy set: ~n~p", - [httpc:get_options(all)]), - ok = httpc:set_options([{proxy, {{?PROXY, ?PROXY_PORT}, ProxyExceptions}}]), - tsp("init_per_testcase -> Options after proxy set: ~n~p", - [httpc:get_options(all)]), - inets:enable_trace(max, io, httpc), - %% inets:enable_trace(max, io, all), - %% snmp:set_trace([gen_tcp]), - tsp("init_per_testcase(~w) -> done when" - "~n NewConfig: ~p" - "~n~n", [Case, NewConfig]), - NewConfig. - - -init_per_testcase_ssl(Tag, PrivDir, SslConfFile, Config) -> - tsp("init_per_testcase_ssl(~w) -> stop ssl", [Tag]), - application:stop(ssl), - Config2 = lists:keydelete(local_ssl_server, 1, Config), - %% Will start inets - tsp("init_per_testcase_ssl(~w) -> try start http server (including inets)", - [Tag]), - Server = inets_test_lib:start_http_server( - filename:join(PrivDir, SslConfFile), Tag), - tsp("init_per_testcase(~w) -> Server: ~p", [Tag, Server]), - [{local_ssl_server, Server} | Config2]. - -start_http_server(ConfDir, ConfFile) -> - inets_test_lib:start_http_server( filename:join(ConfDir, ConfFile) ). +end_per_testcase(pipeline, _Config) -> + inets:stop(httpc, pipeline); +end_per_testcase(persistent_connection, _Config) -> + inets:stop(httpc, persistent); +end_per_testcase(_Case, _Config) -> + ok. %%-------------------------------------------------------------------- -%% 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 +%% Test Cases -------------------------------------------------------- %%-------------------------------------------------------------------- -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]), - dbg:stop(), % ? - 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. -%%------------------------------------------------------------------------- +head() -> + [{doc, "Test http head request against local server."}]. +head(Config) when is_list(Config) -> + Request = {url(group_name(Config), "/dummy.html", Config), []}, + {ok, {{_,200,_}, [_ | _], []}} = httpc:request(head, Request, [], []). +%%-------------------------------------------------------------------- +get() -> + [{doc, "Test http get request against local server"}]. +get(Config) when is_list(Config) -> + Request = {url(group_name(Config), "/dummy.html", Config), []}, + {ok, {{_,200,_}, [_ | _], Body = [_ | _]}} = httpc:request(get, Request, [], []), + inets_test_lib:check_body(Body), -%%------------------------------------------------------------------------- + {ok, {{_,200,_}, [_ | _], BinBody}} = httpc:request(get, Request, [], [{body_format, binary}]), + true = is_binary(BinBody). +%%-------------------------------------------------------------------- +post() -> + [{"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."}]. +post(Config) when is_list(Config) -> + CGI = case test_server:os_type() of + {win32, _} -> + "/cgi-bin/cgi_echo.exe"; + _ -> + "/cgi-bin/cgi_echo" + end, -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"). + URL = url(group_name(Config), CGI, Config), -http_head(doc) -> - ["Test http head request against local server."]; -http_head(suite) -> - []; -http_head(Config) when is_list(Config) -> - tsp("http_head -> entry with" - "~n Config: ~p", [Config]), - Method = head, - Port = ?config(local_port, Config), - URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html", - Request = {URL, []}, - HttpOpts = [], - Opts = [], - VerifyResult = - fun({ok, {{_,200,_}, [_ | _], []}}) -> - ok; - ({ok, UnexpectedReply}) -> - tsp("http_head:verify_fun -> Unexpected Reply: " - "~n ~p", [UnexpectedReply]), - tsf({unexpected_reply, UnexpectedReply}); - ({error, Reason} = Error) -> - tsp("http_head:verify_fun -> Error reply: " - "~n Reason: ~p", [Reason]), - tsf({bad_reply, Error}) - end, - simple_request_and_verify(Config, - Method, Request, HttpOpts, Opts, VerifyResult). + %% 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"}, [], []). -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. +%%-------------------------------------------------------------------- +post_stream() -> + [{"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."}]. +post_stream(Config) when is_list(Config) -> + CGI = case test_server:os_type() of + {win32, _} -> + "/cgi-bin/cgi_echo.exe"; + _ -> + "/cgi-bin/cgi_echo" + end, -%%------------------------------------------------------------------------- + URL = url(group_name(Config), CGI, Config), -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. + %% Cgi-script expects the body length to be 100 + BodyFun = fun(0) -> + eof; + (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}}, [], []), -%%------------------------------------------------------------------------- -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. + {ok, {{_,504,_}, [_ | _], []}} = + httpc:request(post, {URL, + [{"expect", "100-continue"}, + {"content-length", "10"}], + "text/plain", {BodyFun, 10}}, [], []). +%%-------------------------------------------------------------------- +trace() -> + [{doc, "Perform a TRACE request."}]. +trace(Config) when is_list(Config) -> + Request = {url(group_name(Config), "/trace.html", Config), []}, + case httpc:request(trace, Request, [], []) of + {ok, {{_,200,_}, [_ | _], "TRACE /trace.html" ++ _}} -> + ok; + Other -> + ct:fail({unexpected, Other}) + 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), +pipeline(Config) when is_list(Config) -> + Request = {url(group_name(Config), "/dummy.html", Config), []}, + {ok, _} = httpc:request(get, Request, [], [], pipeline), - URL = ?URL_START ++ integer_to_list(Port) ++ - "/missing_reason_phrase.html", - - {error, Reason} = - httpc:request(get, {URL, []}, [{relaxed, false}], []), + %% Make sure pipeline session is registerd + test_server:sleep(4000), + keep_alive_requests(Request, pipeline). - 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. +persistent_connection(Config) when is_list(Config) -> + Request = {url(group_name(Config), "/dummy.html", Config), []}, + {ok, _} = httpc:request(get, Request, [], [], persistent), + %% Make sure pipeline session is registerd + test_server:sleep(4000), + keep_alive_requests(Request, persistent). %%------------------------------------------------------------------------- -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. +async() -> + [{doc, "Test an asynchrony http request."}]. +async(Config) when is_list(Config) -> + Request = {url(group_name(Config), "/dummy.html", Config), []}, + {ok, RequestId} = + httpc:request(get, Request, [], [{sync, false}]), + Body = + receive + {http, {RequestId, {{_, 200, _}, _, BinBody}}} -> + BinBody; + {http, Msg} -> + ct:fail(Msg) + end, + inets_test_lib:check_body(binary_to_list(Body)), -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" - "~n when profile info: ~p", [httpc:info()]), - {ok, RequestIdA1} = - httpc:request(get, {URL, []}, [], [{sync, false}]), - tsp("RequestIdA1: ~p", [RequestIdA1]), - p("test_pipeline -> RequestIdA1: ~p" - "~n when profile info: ~p", [RequestIdA1, httpc:info()]), - - %% Make sure pipeline is initiated - p("test_pipeline -> sleep some", []), - test_server:sleep(4000), - - p("test_pipeline -> issue (async) request A2, A3 and A4" - "~n when profile info: ~p", [httpc:info()]), - {ok, RequestIdA2} = - httpc:request(get, {URL, []}, [], [{sync, false}]), - {ok, RequestIdA3} = - httpc:request(get, {URL, []}, [], [{sync, false}]), - {ok, RequestIdA4} = - httpc:request(get, {URL, []}, [], [{sync, false}]), - tsp("RequestIdAs => A2: ~p, A3: ~p and A4: ~p", - [RequestIdA2, RequestIdA3, RequestIdA4]), - p("test_pipeline -> RequestIds => A2: ~p, A3: ~p and A4: ~p" - "~n when profile info: ~p", - [RequestIdA2, RequestIdA3, RequestIdA4, httpc:info()]), - - p("test_pipeline -> issue (sync) request 3"), - {ok, {{_,200,_}, [_ | _], [_ | _]}} = - httpc:request(get, {URL, []}, [], []), - - p("test_pipeline -> expect reply for (async) request A1, A2, A3 and A4" - "~n when profile info: ~p", [httpc:info()]), - pipeline_await_async_reply([{RequestIdA1, a1, 200}, - {RequestIdA2, a2, 200}, - {RequestIdA3, a3, 200}, - {RequestIdA4, a4, 200}], ?MINS(1)), - - p("test_pipeline -> sleep some" - "~n when profile info: ~p", [httpc:info()]), - test_server:sleep(4000), - - p("test_pipeline -> issue (async) request B1, B2, B3 and B4" - "~n when profile info: ~p", [httpc:info()]), - {ok, RequestIdB1} = - httpc:request(get, {URL, []}, [], [{sync, false}]), - {ok, RequestIdB2} = - httpc:request(get, {URL, []}, [], [{sync, false}]), - {ok, RequestIdB3} = - httpc:request(get, {URL, []}, [], [{sync, false}]), - {ok, RequestIdB4} = - httpc:request(get, {URL, []}, [], [{sync, false}]), - tsp("RequestIdBs => B1: ~p, B2: ~p, B3: ~p and B4: ~p", - [RequestIdB1, RequestIdB2, RequestIdB3, RequestIdB4]), - p("test_pipeline -> RequestIdBs => B1: ~p, B2: ~p, B3: ~p and B4: ~p" - "~n when profile info: ~p", - [RequestIdB1, RequestIdB2, RequestIdB3, RequestIdB4, httpc:info()]), - - p("test_pipeline -> cancel (async) request B2" - "~n when profile info: ~p", [httpc:info()]), - ok = httpc:cancel_request(RequestIdB2), - - p("test_pipeline -> " - "expect *no* reply for cancelled (async) request B2 (for 3 secs)" - "~n when profile info: ~p", [httpc:info()]), + {ok, NewRequestId} = + httpc:request(get, Request, [], [{sync, false}]), + ok = httpc:cancel_request(NewRequestId), receive - {http, {RequestIdB2, _}} -> - tsf(http_cancel_request_failed) + {http, {NewRequestId, _}} -> + ct:fail(http_cancel_request_failed) after 3000 -> ok - end, - - p("test_pipeline -> expect reply for (async) request B1, B3 and B4" - "~n when profile info: ~p", [httpc:info()]), - Bodies = pipeline_await_async_reply([{RequestIdB1, b1, 200}, - {RequestIdB3, b3, 200}, - {RequestIdB4, b4, 200}], ?MINS(1)), - [{b1, Body}|_] = Bodies, - - p("test_pipeline -> check reply for (async) request B1" - "~n when profile info: ~p", [httpc:info()]), - inets_test_lib:check_body(binary_to_list(Body)), - - p("test_pipeline -> ensure no unexpected incomming" - "~n when profile info: ~p", [httpc:info()]), - receive - {http, Any} -> - tsf({unexpected_message, Any}) - after 500 -> - ok - end, - - p("test_pipeline -> done" - "~n when profile info: ~p", [httpc:info()]), - ok. - -pipeline_await_async_reply(ReqIds, Timeout) -> - pipeline_await_async_reply(ReqIds, Timeout, []). - -pipeline_await_async_reply([], _, Acc) -> - lists:keysort(1, Acc); -pipeline_await_async_reply(ReqIds, Timeout, Acc) when Timeout > 0 -> - T1 = inets_test_lib:timestamp(), - p("pipeline_await_async_reply -> await replies" - "~n ReqIds: ~p" - "~n Timeout: ~p", [ReqIds, Timeout]), + end. +%%------------------------------------------------------------------------- +save_to_file() -> + [{doc, "Test to save the http body to a file"}]. +save_to_file(Config) when is_list(Config) -> + PrivDir = ?config(priv_dir, Config), + FilePath = filename:join(PrivDir, "dummy.html"), + URL = url(group_name(Config), "/dummy.html", Config), + Request = {URL, []}, + {ok, saved_to_file} + = httpc:request(get, Request, [], [{stream, FilePath}]), + {ok, Bin} = file:read_file(FilePath), + {ok, {{_,200,_}, [_ | _], Body}} = httpc:request(URL), + Bin == Body. + +%%------------------------------------------------------------------------- +save_to_file_async() -> + [{doc,"Test to save the http body to a file"}]. +save_to_file_async(Config) when is_list(Config) -> + PrivDir = ?config(priv_dir, Config), + FilePath = filename:join(PrivDir, "dummy.html"), + URL = url(group_name(Config), "/dummy.html", Config), + Request = {URL, []}, + {ok, RequestId} = httpc:request(get, Request, [], + [{stream, FilePath}, + {sync, false}]), receive - {http, {RequestId, {{_, Status, _}, _, Body}}} -> - p("pipeline_await_async_reply -> received reply for" - "~n RequestId: ~p" - "~n Status: ~p", [RequestId, Status]), - case lists:keysearch(RequestId, 1, ReqIds) of - {value, {RequestId, N, Status}} -> - p("pipeline_await_async_reply -> " - "found expected request ~w", [N]), - ReqIds2 = lists:keydelete(RequestId, 1, ReqIds), - NewTimeout = Timeout - (inets_test_lib:timestamp()-T1), - pipeline_await_async_reply(ReqIds2, NewTimeout, - [{N, Body} | Acc]); - {value, {RequestId, N, WrongStatus}} -> - p("pipeline_await_async_reply -> " - "found request ~w with wrong status", [N]), - tsf({reply_with_unexpected_status, - {RequestId, N, WrongStatus}}); - false -> - tsf({unexpected_reply, {RequestId, Status}}) - end; + {http, {RequestId, saved_to_file}} -> + ok; {http, Msg} -> - tsf({unexpected_reply, Msg}) - after Timeout -> - receive - Any -> - tsp("pipeline_await_async_reply -> " - "received unknown data after timeout: " - "~n ~p", [Any]), - tsf({timeout, {unknown, Any}}) - end - end; -pipeline_await_async_reply(ReqIds, _, Acc) -> - tsp("pipeline_await_async_reply -> " - "timeout: " - "~n ~p" - "~nwhen" - "~n ~p", [ReqIds, Acc]), - tsf({timeout, ReqIds, Acc}). - + ct:fail(Msg) + end, - + {ok, Bin} = file:read_file(FilePath), + {ok, {{_,200,_}, [_ | _], Body}} = httpc:request(URL), + Bin == Body. %%------------------------------------------------------------------------- -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. +stream() -> + [{doc, "Test the option stream for asynchrony requests"}]. +stream(Config) when is_list(Config) -> + Request = {url(group_name(Config), "/dummy.html", Config), []}, + stream_test(Request, {stream, self}). %%------------------------------------------------------------------------- -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. +stream_once() -> + [{doc, "Test the option stream for asynchrony requests"}]. +stream_once(Config) when is_list(Config) -> + Request0 = {url(group_name(Config), "/dummy.html", Config), []}, + stream_test(Request0, {stream, {self, once}}), -%%------------------------------------------------------------------------- -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. + Request1 = {url(group_name(Config), "/once.html", Config), []}, + stream_test(Request1, {stream, {self, once}}), + Request2 = {url(group_name(Config), "/once_chunked.html", Config), []}, + stream_test(Request2, {stream, {self, once}}). %%------------------------------------------------------------------------- -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, +redirect_multiple_choises() -> + [{doc, "The user agent, selection of the most appropriate choice MAY " + "be performed automatically."}]. +redirect_multiple_choises(Config) when is_list(Config) -> + URL300 = url(group_name(Config), "/300.html", Config), - {ok, Bin} = file:read_file(FilePath), - {ok, {{_,200,_}, [_ | _], Body}} = httpc:request(URL), - Bin == Body; - _ -> - skip("Failed to start local http-server") - end. -%%------------------------------------------------------------------------- -http_headers(doc) -> - ["Use as many request headers as possible not used in proxy_headers"]; -http_headers(suite) -> - []; -http_headers(Config) when is_list(Config) -> - - case ?config(local_server, Config) of - ok -> - Port = ?config(local_port, Config), - URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html", - DocRoot = ?config(doc_root, Config), - {ok, FileInfo} = - file:read_file_info(filename:join([DocRoot,"dummy.html"])), - CreatedSec = - calendar:datetime_to_gregorian_seconds( - FileInfo#file_info.mtime), - - Mod = httpd_util:rfc1123_date( - calendar:gregorian_seconds_to_datetime( - CreatedSec-1)), - - Date = httpd_util:rfc1123_date({date(), time()}), - - {ok, {{_,200,_}, [_ | _], [_ | _]}} = - httpc:request(get, {URL, [{"If-Modified-Since", - Mod}, - {"From","[email protected]"}, - {"Date", Date} - ]}, [], []), - - Mod1 = httpd_util:rfc1123_date( - calendar:gregorian_seconds_to_datetime( - CreatedSec+1)), - - {ok, {{_,200,_}, [_ | _], [_ | _]}} = - httpc:request(get, {URL, [{"If-UnModified-Since", - Mod1} - ]}, [], []), - - Tag = httpd_util:create_etag(FileInfo), - - - {ok, {{_,200,_}, [_ | _], [_ | _]}} = - httpc:request(get, {URL, [{"If-Match", - Tag} - ]}, [], []), - - {ok, {{_,200,_}, [_ | _], _}} = - httpc:request(get, {URL, [{"If-None-Match", - "NotEtag,NeihterEtag"}, - {"Connection", "Close"} - ]}, [], []), - ok; - _ -> - skip("Failed to start local http-server") - end. + catch {ok, {{_,200,_}, [_ | _], [_|_]}} + = httpc:request(get, {URL300, []}, [], []), + {ok, {{_,300,_}, [_ | _], _}} = + httpc:request(get, {URL300, []}, [{autoredirect, false}], []). %%------------------------------------------------------------------------- -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", +redirect_moved_permanently() -> + [{doc, "If the 301 status code is received in response to a request other " + "than GET or HEAD, the user agent MUST NOT automatically redirect the request " + "unless it can be confirmed by the user, since this might change " + "the conditions under which the request was issued."}]. +redirect_moved_permanently(Config) when is_list(Config) -> - UserPasswd = base64:encode_to_string("Alladin:Sesame"), - Auth = "Basic " ++ UserPasswd, + URL301 = url(group_name(Config), "/301.html", Config), - %% 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. + {ok, {{_,200,_}, [_ | _], [_|_]}} + = httpc:request(get, {URL301, []}, [], []), + {ok, {{_,200,_}, [_ | _], []}} + = httpc:request(head, {URL301, []}, [], []), + {ok, {{_,301,_}, [_ | _], [_|_]}} + = httpc:request(post, {URL301, [],"text/plain", "foobar"}, + [], []). %%------------------------------------------------------------------------- -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). +redirect_found() -> + [{doc," If the 302 status code is received in response to a request other " + "than GET or HEAD, the user agent MUST NOT automatically redirect the " + "request unless it can be confirmed by the user, since this might change " + "the conditions under which the request was issued."}]. +redirect_found(Config) when is_list(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. + URL302 = url(group_name(Config), "/302.html", Config), - -%%------------------------------------------------------------------------- -ssl_get(doc) -> - ["Same as http_get/1 but over ssl sockets."]; -ssl_get(suite) -> - []; -ssl_get(Config) when is_list(Config) -> - ssl_get(ssl, Config). - -essl_get(doc) -> - ["Same as http_get/1 but over ssl sockets."]; -essl_get(suite) -> - []; -essl_get(Config) when is_list(Config) -> - ssl_get(essl, Config). - -ssl_get(SslTag, Config) when is_list(Config) -> - case ?config(local_ssl_server, Config) of - ok -> - DataDir = ?config(data_dir, Config), - Port = ?config(local_ssl_port, Config), - URL = ?SSL_URL_START ++ integer_to_list(Port) ++ "/dummy.html", - CertFile = filename:join(DataDir, "ssl_client_cert.pem"), - SSLOptions = [{certfile, CertFile}, {keyfile, CertFile}], - SSLConfig = - case SslTag of - ssl -> - SSLOptions; - essl -> - {essl, SSLOptions} - end, - tsp("ssl_get -> make request using: " - "~n URL: ~p" - "~n SslTag: ~p" - "~n SSLOptions: ~p", [URL, SslTag, SSLOptions]), - case httpc:request(get, {URL, []}, [{ssl, SSLConfig}], []) of - {ok, {{_,200, _}, [_ | _], Body = [_ | _]}} -> - inets_test_lib:check_body(Body), - ok; - {ok, {StatusLine, Headers, _Body}} -> - tsp("ssl_get -> unexpected result: " - "~n StatusLine: ~p" - "~n Headers: ~p", [StatusLine, Headers]), - tsf({unexpected_response, StatusLine, Headers}); - {error, Reason} -> - tsp("ssl_get -> request failed: " - "~n Reason: ~p", [Reason]), - tsf({request_failed, Reason}) - end; - {ok, _} -> - skip("local http-server not started"); - _ -> - skip("SSL not started") - end. + {ok, {{_,200,_}, [_ | _], [_|_]}} + = httpc:request(get, {URL302, []}, [], []), + {ok, {{_,200,_}, [_ | _], []}} + = httpc:request(head, {URL302, []}, [], []), + {ok, {{_,302,_}, [_ | _], [_|_]}} + = httpc:request(post, {URL302, [],"text/plain", "foobar"}, + [], []). %%------------------------------------------------------------------------- -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). +redirect_see_other() -> + [{doc, "The different URI SHOULD be given by the Location field in the response. " + "Unless the request method was HEAD, the entity of the response SHOULD contain a short " + "hypertext note with a hyperlink to the new URI(s). "}]. +redirect_see_other(Config) when is_list(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). + URL303 = url(group_name(Config), "/303.html", 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. + {ok, {{_,200,_}, [_ | _], [_|_]}} + = httpc:request(get, {URL303, []}, [], []), + {ok, {{_,200,_}, [_ | _], []}} + = httpc:request(head, {URL303, []}, [], []), + {ok, {{_,200,_}, [_ | _], [_|_]}} + = httpc:request(post, {URL303, [],"text/plain", "foobar"}, + [], []). %%------------------------------------------------------------------------- -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}]), +redirect_temporary_redirect() -> + [{doc," If the 307 status code is received in response to a request other " + "than GET or HEAD, the user agent MUST NOT automatically redirect the request " + "unless it can be confirmed by the user, since this might change " + "the conditions under which the request was issued."}]. +redirect_temporary_redirect(Config) when is_list(Config) -> - 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; + URL307 = url(group_name(Config), "/307.html", Config), - _ -> - skip("Failed to start local http-server") - end. + {ok, {{_,200,_}, [_ | _], [_|_]}} + = httpc:request(get, {URL307, []}, [], []), + {ok, {{_,200,_}, [_ | _], []}} + = httpc:request(head, {URL307, []}, [], []), + {ok, {{_,307,_}, [_ | _], [_|_]}} + = httpc:request(post, {URL307, [],"text/plain", "foobar"}, + [], []). %%------------------------------------------------------------------------- -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, []}, [], []), - +redirect_loop() -> + [{"doc, Test redirect loop detection"}]. +redirect_loop(Config) when is_list(Config) -> - 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. + URL = url(group_name(Config), "/redirectloop.html", Config), + {ok, {{_,300,_}, [_ | _], _}} + = httpc:request(get, {URL, []}, [], []). %%------------------------------------------------------------------------- -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. +cookie() -> + [{doc, "Test cookies."}]. +cookie(Config) when is_list(Config) -> + ok = httpc:set_options([{cookies, enabled}]), + Request0 = {url(group_name(Config), "/cookie.html", Config), []}, -%%------------------------------------------------------------------------- -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, []}, [], []), + {ok, {{_,200,_}, [_ | _], [_|_]}} + = httpc:request(get, Request0, [], []), + %% Populate table to be used by the "dummy" server ets:new(cookie, [named_table, public, set]), ets:insert(cookie, {cookies, true}), - {ok, {{_,200,_}, [_ | _], [_|_]}} - = httpc:request(get, {URLStart ++ "/", []}, [], []), - - ets:delete(cookie), + Request1 = {url(group_name(Config), "/", Config), []}, - ok = httpc:set_options([{cookies, disabled}]), - DummyServerPid ! stop, - ok = httpc:set_options([{ipfamily, inet6fb4}]), - ok. + {ok, {{_,200,_}, [_ | _], [_|_]}} + = httpc:request(get, Request1, [], []), -%%------------------------------------------------------------------------- -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. + [{session_cookies, [_|_]}] = httpc:which_cookies(httpc:default_profile()), + ets:delete(cookie), + ok = httpc:set_options([{cookies, disabled}]). -%%------------------------------------------------------------------------- -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. +cookie_profile() -> + [{doc, "Test cookies on a non default profile."}]. +cookie_profile(Config) when is_list(Config) -> + inets:start(httpc, [{profile, cookie_test}]), + ok = httpc:set_options([{cookies, enabled}], cookie_test), -%%------------------------------------------------------------------------- -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, + Request0 = {url(group_name(Config), "/cookie.html", Config), []}, - 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}], []). + {ok, {{_,200,_}, [_ | _], [_|_]}} + = httpc:request(get, Request0, [], [], cookie_test), + %% Populate table to be used by the "dummy" server + ets:new(cookie, [named_table, public, set]), + ets:insert(cookie, {cookies, true}), -%%------------------------------------------------------------------------- -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"). + Request1 = {url(group_name(Config), "/", Config), []}, + {ok, {{_,200,_}, [_ | _], [_|_]}} + = httpc:request(get, Request1, [], [], cookie_test), -%%------------------------------------------------------------------------- -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. - + ets:delete(cookie), + inets:stop(httpc, cookie_test). %%------------------------------------------------------------------------- -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. - +headers_as_is(doc) -> + ["Test the option headers_as_is"]; +headers_as_is(Config) when is_list(Config) -> + URL = url(group_name(Config), "/dummy.html", Config), + {ok, {{_,200,_}, [_|_], [_|_]}} = + httpc:request(get, {URL, [{"Host", "localhost"},{"Te", ""}]}, + [], [{headers_as_is, true}]), + {ok, {{_,400,_}, [_|_], [_|_]}} = + httpc:request(get, {URL, [{"Te", ""}]},[], [{headers_as_is, true}]). %%------------------------------------------------------------------------- -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. +userinfo(doc) -> + [{doc, "Test user info e.i. http://user:passwd@host:port/"}]; +userinfo(Config) when is_list(Config) -> + + {ok,Host} = inet:gethostname(), + + URLAuth = url(group_name(Config), "alladin:sesame@" ++ Host ++ ":","/userinfo.html", Config), -%%------------------------------------------------------------------------- -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. + {ok, {{_,200,_}, [_ | _], _}} + = httpc:request(get, {URLAuth, []}, [], []), + URLUnAuth = url(group_name(Config), "alladin:foobar@" ++ Host ++ ":","/userinfo.html", Config), -%%------------------------------------------------------------------------- -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. - + {ok, {{_,401, _}, [_ | _], _}} = + httpc:request(get, {URLUnAuth, []}, [], []). %%------------------------------------------------------------------------- -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. - - + URL = url(group_name(Config), "/doesnotexist.html", Config), + {ok, {{_,404,_}, [_ | _], [_ | _]}} + = httpc:request(get, {URL, []}, [], []). %%------------------------------------------------------------------------- -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. +streaming_error(doc) -> + [{doc, "Only async requests can be stremed - Solves OTP-8056"}]; +streaming_error(Config) when is_list(Config) -> + Method = get, + Request = {url(group_name(Config), "/dummy.html", Config), []}, + {error, streaming_error} = httpc:request(Method, Request, + [], [{sync, true}, {stream, {self, once}}]), + {error, streaming_error} = httpc:request(Method, Request, + [], [{sync, true}, {stream, self}]). %%------------------------------------------------------------------------- -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). - - +server_does_not_exist(doc) -> + [{doc, "Test that we get an error message back when the server " + "does note exist."}]; +server_does_not_exist(Config) when is_list(Config) -> + {error, _} = + httpc:request(get, {"http://localhost:" ++ + integer_to_list(?NOT_IN_USE_PORT) + ++ "/", []},[], []). %%------------------------------------------------------------------------- -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. - +no_content_204(doc) -> + ["Test the case that the HTTP 204 no content header - Solves OTP 6982"]; +no_content_204(Config) when is_list(Config) -> + URL = url(group_name(Config), "/no_content.html", Config), + {ok, {{_,204,_}, [], []}} = httpc:request(URL). %%------------------------------------------------------------------------- -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. - +tolerate_missing_CR() -> + [{doc, "Test the case that the HTTP server uses only LF instead of CRLF" + "as delimitor. Solves OTP-7304"}]. +tolerate_missing_CR(Config) when is_list(Config) -> + URL = url(group_name(Config), "/missing_CR.html", Config), + {ok, {{_,200,_}, _, [_ | _]}} = httpc:request(URL). %%------------------------------------------------------------------------- -parse_url(doc) -> - ["Test that an url is parsed correctly"]; -parse_url(suite) -> - []; -parse_url(Config) when is_list(Config) -> - %% ipv6 - {ok, {http,[],"2010:836B:4179::836B:4179",80,"/foobar.html",[]}} = - http_uri:parse("http://[2010:836B:4179::836B:4179]/foobar.html"), - {ok, {http,[],"[2010:836B:4179::836B:4179]",80,"/foobar.html",[]}} = - http_uri:parse("http://[2010:836B:4179::836B:4179]/foobar.html", - [{ipv6_host_with_brackets, true}]), - {ok, {http,[],"2010:836B:4179::836B:4179",80,"/foobar.html",[]}} = - http_uri:parse("http://[2010:836B:4179::836B:4179]/foobar.html", - [{ipv6_host_with_brackets, false}]), - {ok, {http,[],"2010:836B:4179::836B:4179",80,"/foobar.html",[]}} = - http_uri:parse("http://[2010:836B:4179::836B:4179]/foobar.html", - [{foo, false}]), - {error, - {malformed_url, _, "http://2010:836B:4179::836B:4179/foobar.html"}} = - http_uri:parse("http://2010:836B:4179::836B:4179/foobar.html"), - - %% ipv4 - {ok, {http,[],"127.0.0.1",80,"/foobar.html",[]}} = - http_uri:parse("http://127.0.0.1/foobar.html"), - - %% host - {ok, {http,[],"localhost",8888,"/foobar.html",[]}} = - http_uri:parse("http://localhost:8888/foobar.html"), - - %% Userinfo - {ok, {http,"nisse:foobar","localhost",8888,"/foobar.html",[]}} = - http_uri:parse("http://nisse:foobar@localhost:8888/foobar.html"), - - %% Scheme error - {error, no_scheme} = http_uri:parse("localhost/foobar.html"), - {error, {malformed_url, _, _}} = - http_uri:parse("localhost:8888/foobar.html"), - - %% Query - {ok, {http,[],"localhost",8888,"/foobar.html","?foo=bar&foobar=42"}} = - http_uri:parse("http://localhost:8888/foobar.html?foo=bar&foobar=42"), - - %% Esc chars - {ok, {http,[],"www.somedomain.com",80,"/%2Eabc",[]}} = - http_uri:parse("http://www.somedomain.com/%2Eabc"), - {ok, {http,[],"www.somedomain.com",80,"/%252Eabc",[]}} = - http_uri:parse("http://www.somedomain.com/%252Eabc"), - {ok, {http,[],"www.somedomain.com",80,"/%25abc",[]}} = - http_uri:parse("http://www.somedomain.com/%25abc"), - {ok, {http,[],"www.somedomain.com",80,"/%25abc", "?foo=bar"}} = - http_uri:parse("http://www.somedomain.com/%25abc?foo=bar"), - - - ok. +empty_body() -> + [{doc, "An empty body was not returned directly. There was a delay for several" + "seconds. Solves OTP-6243."}]. +empty_body(Config) when is_list(Config) -> + URL = url(group_name(Config), "/empty.html", Config), + {ok, {{_,200,_}, [_ | _], []}} = + httpc:request(get, {URL, []}, [{timeout, 500}], []). %%------------------------------------------------------------------------- -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). - +transfer_encoding() -> + [{doc, "Transfer encoding is case insensitive. Solves OTP-6807"}]. +transfer_encoding(Config) when is_list(Config) -> + URL = url(group_name(Config), "/capital_transfer_encoding.html", Config), + {ok, {{_,200,_}, [_|_], [_ | _]}} = httpc:request(URL). %%------------------------------------------------------------------------- -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). - +empty_response_header() -> + [{doc, "Test the case that the HTTP server does not send any headers. Solves OTP-6830"}]. +empty_response_header(Config) when is_list(Config) -> + URL = url(group_name(Config), "/no_headers.html", Config), + {ok, {{_,200,_}, [], [_ | _]}} = httpc:request(URL). %%------------------------------------------------------------------------- -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. - +bad_response(doc) -> + [{doc, "Test what happens when the server does not follow the protocol"}]; -%%------------------------------------------------------------------------- +bad_response(Config) when is_list(Config) -> -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. + URL0 = url(group_name(Config), "/missing_crlf.html", Config), + URL1 = url(group_name(Config), "/wrong_statusline.html", Config), + {error, timeout} = httpc:request(get, {URL0, []}, [{timeout, 400}], []), + {error, Reason} = httpc:request(URL1), + ct:print("Wrong Statusline: ~p~n", [Reason]). %%------------------------------------------------------------------------- -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); +internal_server_error(doc) -> + ["Test 50X codes"]; +internal_server_error(Config) when is_list(Config) -> - 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); + URL500 = url(group_name(Config), "/500.html", Config), - Any -> - tsp("otp_9847_server_main -> received" - "~n Any: ~p", [Any]), - exit({crap, Any}) + {ok, {{_,500,_}, [_ | _], _}} + = httpc:request(get, {URL500, []}, [], []), - after 0 -> - tsp("otp_9847_server_main -> nothing in queue"), - otp_9847_server_main(TC, ListenSocket, Answer, Handlers) - end; + URL503 = url(group_name(Config), "/503.html", Config), - Error -> - exit(Error) - end. + %% 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, []}, [], []), -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. - + ets:insert(unavailable, {503, long_unavailable}), -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]. - + {ok, {{_,503, _}, [_ | _], [_|_]}} = + httpc:request(get, {URL503, []}, [], []), + ets:delete(unavailable). %%------------------------------------------------------------------------- -options(doc) -> - ["Test the option parameters."]; -options(suite) -> +invalid_http(doc) -> + ["Test parse error"]; +invalid_http(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. - +invalid_http(Config) when is_list(Config) -> -%%------------------------------------------------------------------------- + URL = url(group_name(Config), "/invalid_http.html", Config), -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. + ct:print("Parse error: ~p ~n", [Reason]). %%------------------------------------------------------------------------- +emulate_lower_versions(doc) -> + [{doc, "Perform request as 0.9 and 1.0 clients."}]; +emulate_lower_versions(Config) when is_list(Config) -> --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. + URL = url(group_name(Config), "/dummy.html", Config), + {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). %%------------------------------------------------------------------------- -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}], []). +relaxed(doc) -> + ["Test relaxed mode"]; +relaxed(Config) when is_list(Config) -> + URL = url(group_name(Config), "/missing_reason_phrase.html", Config), -%%------------------------------------------------------------------------- + {error, Reason} = + httpc:request(get, {URL, []}, [{relaxed, false}], []), -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. + ct:print("Not relaxed: ~p~n", [Reason]), + {ok, {{_, 200, _}, [_ | _], [_ | _]}} = + httpc:request(get, {URL, []}, [{relaxed, true}], []). %%------------------------------------------------------------------------- -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. +headers() -> + [{doc,"Use as many request headers as possible not used in proxy_headers"}]. +headers(Config) when is_list(Config) -> + URL = url(group_name(Config), "/dummy.html", Config), + 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), -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. + Mod = httpd_util:rfc1123_date( + calendar:gregorian_seconds_to_datetime( + CreatedSec-1)), + Date = httpd_util:rfc1123_date({date(), time()}), -%%------------------------------------------------------------------------- + {ok, {{_,200,_}, [_ | _], [_ | _]}} = + httpc:request(get, {URL, [{"If-Modified-Since", + Mod}, + {"From","[email protected]"}, + {"Date", Date} + ]}, [], []), -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. + 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), -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. + {ok, {{_,200,_}, [_ | _], [_ | _]}} = + httpc:request(get, {URL, [{"If-Match", + Tag} + ]}, [], []), + {ok, {{_,200,_}, [_ | _], _}} = + httpc:request(get, {URL, [{"If-None-Match", + "NotEtag,NeihterEtag"}, + {"Connection", "Close"} + ]}, [], []). %%------------------------------------------------------------------------- +headers_dummy() -> + ["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."]. +headers_dummy(Config) when is_list(Config) -> -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, + URL = url(group_name(Config), "/dummy_headers.html", Config), - 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}]), + Foo = http_chunk:encode("foobar") ++ + binary_to_list(http_chunk:encode_last()), + FooBar = Foo ++ "\r\n\r\nOther:inets_test\r\n\r\n", - {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, + UserPasswd = base64:encode_to_string("Alladin:Sesame"), + Auth = "Basic " ++ UserPasswd, - ok = httpc:set_options([{ipfamily, inet6fb4}]), - ok. + %% 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}, + [], []). %%------------------------------------------------------------------------- +invalid_headers(Config) -> + Request = {url(group_name(Config), "/dummy.html", Config), [{"cookie", undefined}]}, + {error, _} = httpc:request(get, Request, [], []). -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. +remote_socket_close(Config) when is_list(Config) -> + URL = url(group_name(Config), "/just_close.html", Config), + {error, socket_closed_remotely} = httpc:request(URL). -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]), +remote_socket_close_async(Config) when is_list(Config) -> + Request = {url(group_name(Config), "/just_close.html", Config), []}, + Options = [{sync, false}], + Profile = httpc:default_profile(), + {ok, RequestId} = + httpc:request(get, Request, [], Options, Profile), 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 + {http, {RequestId, {error, socket_closed_remotely}}} -> + ok 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. +stream_to_pid(Config) when is_list(Config) -> + ReceiverPid = create_receiver(pid), + Receiver = ReceiverPid, -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. + stream(ReceiverPid, Receiver, Config), + stop_receiver(ReceiverPid). -set_random_seed() -> - {_, _, Micros} = now(), - A = erlang:phash2([make_ref(), self(), Micros]), - random:seed(A, A, A). +stream_through_fun(Config) when is_list(Config) -> + ReceiverPid = create_receiver(function), + Receiver = stream_deliver_fun(ReceiverPid), -f(F, A) -> lists:flatten(io_lib:format(F,A)). + stream(ReceiverPid, Receiver, Config), + stop_receiver(ReceiverPid). +stream_through_mfa(Config) when is_list(Config) -> + ReceiverPid = create_receiver(mfa), + Receiver = {?MODULE, stream_deliver, [mfa, ReceiverPid]}, + stream(ReceiverPid, Receiver, Config). %%------------------------------------------------------------------------- +inet_opts(Config) when is_list(Config) -> + 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), + Request = {url(group_name(Config), "/dummy.html", Config), []}, + Timeout = timer:seconds(1), + ConnTimeout = Timeout + timer:seconds(1), + HttpOptions = [{timeout, Timeout}, {connect_timeout, ConnTimeout}], + Options0 = [{socket_opts, [{tos, 87}, + {recbuf, 16#FFFF}, + {sndbuf, 16#FFFF}]}], -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), + {ok, {{_,200,_}, [_ | _], ReplyBody0 = [_ | _]}} = httpc:request(get, Request, HttpOptions, Options0), + inets_test_lib:check_body(ReplyBody0), - stop_receiver(ReceiverPid), - - ok; - _ -> - skip("Failed to start local http-server") - end. + Options1 = [{socket_opts, [{tos, 84}, + {recbuf, 32#1FFFF}, + {sndbuf, 32#1FFFF}]}], + {ok, {{_,200,_}, [_ | _], ReplyBody1 = [_ | _]}} = httpc:request(get, Request, [], Options1), + inets_test_lib:check_body(ReplyBody1). +%%------------------------------------------------------------------------- +port_in_host_header(Config) when is_list(Config) -> -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), + Request = {url(group_name(Config), "/ensure_host_header_with_port.html", Config), []}, + {ok, {{_, 200, _}, _, Body}} = httpc:request(get, Request, [], []), + inets_test_lib:check_body(Body). - stop_receiver(ReceiverPid), - - ok; - _ -> - skip("Failed to start local http-server") +%%------------------------------------------------------------------------- +timeout_memory_leak() -> + [{doc, "Check OTP-8739"}]. +timeout_memory_leak(Config) when is_list(Config) -> + {_DummyServerPid, Port} = otp_8739_dummy_server(), + {ok,Host} = inet:gethostname(), + Request = {?URL_START ++ Host ++ ":" ++ integer_to_list(Port) ++ "/dummy.html", []}, + case httpc:request(get, Request, [{connect_timeout, 500}, {timeout, 1}], [{sync, true}]) of + {error, timeout} -> + %% And now we check the size of the handler db + Info = httpc:info(), + ct:print("Info: ~p", [Info]), + {value, {handlers, Handlers}} = + lists:keysearch(handlers, 1, Info), + case Handlers of + [] -> + ok; + _ -> + ct:fail({unexpected_handlers, Handlers}) + end; + Unexpected -> + ct:fail({unexpected, Unexpected}) 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}], +wait_for_whole_response() -> + [{doc, "Check OTP-8154"}]. +wait_for_whole_response(Config) when is_list(Config) -> - {ok, RequestId} = - httpc:request(get, Request, HTTPOptions, Options), + ReqSeqNumServer = start_sequence_number_server(), + RespSeqNumServer = start_sequence_number_server(), + {ok, Server, Port} = start_slow_server(RespSeqNumServer), + Clients = run_clients(105, Port, ReqSeqNumServer), + ok = wait4clients(Clients, timer:minutes(3)), + Server ! shutdown, + RespSeqNumServer ! shutdown, + ReqSeqNumServer ! shutdown. - Body = - receive +%%-------------------------------------------------------------------- +%% Internal Functions ------------------------------------------------ +%%-------------------------------------------------------------------- +stream(ReceiverPid, Receiver, Config) -> + Request = {url(group_name(Config), "/dummy.html", Config), []}, + Options = [{sync, false}, {receiver, Receiver}], + {ok, RequestId} = + httpc:request(get, Request, [], Options), + Body = + receive {reply, ReceiverPid, {RequestId, {{_, 200, _}, _, B}}} -> B; {reply, ReceiverPid, Msg} -> - tsf(Msg); + ct:fail(Msg); {bad_reply, ReceiverPid, Msg} -> - tsf(Msg) + ct:fail(Msg) end, - inets_test_lib:check_body(binary_to_list(Body)), - ok. - + inets_test_lib:check_body(binary_to_list(Body)). create_receiver(Type) -> - Parent = self(), + Parent = self(), Receiver = fun() -> receiver(Type, Parent) end, spawn_link(Receiver). @@ -3079,8 +975,7 @@ stop_receiver(Pid) -> receiver(Type, Parent) -> receive {stop, Parent} -> - exit(normal); - + ok; {http, ReplyInfo} when (Type =:= pid) -> Parent ! {reply, self(), ReplyInfo}, receiver(Type, Parent); @@ -3088,258 +983,116 @@ receiver(Type, Parent) -> {Type, ReplyInfo} -> Parent ! {reply, self(), ReplyInfo}, receiver(Type, Parent); - + Crap -> Parent ! {reply, self(), {bad_reply, Crap}}, receiver(Type, Parent) end. +stream_deliver_fun(ReceiverPid) -> + fun(ReplyInfo) -> stream_deliver(ReplyInfo, function, ReceiverPid) end. -otp_8106_deliver_fun(ReceiverPid) -> - fun(ReplyInfo) -> otp_8106_deliver(ReplyInfo, function, ReceiverPid) end. - -otp_8106_deliver(ReplyInfo, Type, ReceiverPid) -> +stream_deliver(ReplyInfo, Type, ReceiverPid) -> ReceiverPid ! {Type, ReplyInfo}, ok. +stream_test(Request, To) -> + {ok, {{_,200,_}, [_ | _], Body}} = + httpc:request(get, Request, [], []), + {ok, RequestId} = + httpc:request(get, Request, [], [{sync, false}, To]), + StreamedBody = + receive + {http, {RequestId, stream_start, _Headers}} -> + receive_streamed_body(RequestId, <<>>); + {http, {RequestId, stream_start, _Headers, Pid}} -> + receive_streamed_body(RequestId, <<>>, Pid); + {http, Msg} -> + ct:fail(Msg) + end, -%%------------------------------------------------------------------------- - -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, + Body == binary_to_list(StreamedBody). - DummyServerPid ! stop, - ok = httpc:set_options([{ipv6, enabled}]), +url(http, End, Config) -> + Port = ?config(port, Config), + {ok,Host} = inet:gethostname(), + ?URL_START ++ Host ++ ":" ++ integer_to_list(Port) ++ End; +url(https, End, Config) -> + Port = ?config(port, Config), + {ok,Host} = inet:gethostname(), + ?TLS_URL_START ++ Host ++ ":" ++ integer_to_list(Port) ++ End; +url(sim_http, End, Config) -> + url(http, End, Config); +url(sim_https, End, Config) -> + url(https, End, Config). +url(http, UserInfo, End, Config) -> + Port = ?config(port, Config), + ?URL_START ++ UserInfo ++ integer_to_list(Port) ++ End; +url(https, UserInfo, End, Config) -> + Port = ?config(port, Config), + ?TLS_URL_START ++ UserInfo ++ integer_to_list(Port) ++ End; +url(sim_http, UserInfo, End, Config) -> + url(http, UserInfo, End, Config); +url(sim_https, UserInfo, End, Config) -> + url(https, UserInfo, End, Config). + +group_name(Config) -> + GroupProp = ?config(tc_group_properties, Config), + proplists:get_value(name, GroupProp). + +server_start(sim_http, _) -> + Inet = inet_version(), + ok = httpc:set_options([{ipfamily, Inet}]), + {_Pid, Port} = dummy_server(Inet), + Port; + +server_start(sim_https, SslConfig) -> + Inet = inet_version(), + ok = httpc:set_options([{ipfamily, Inet}]), + {_Pid, Port} = dummy_server(ssl, Inet, SslConfig), + Port; + +server_start(_, HttpdConfig) -> + {ok, Pid} = inets:start(httpd, HttpdConfig), + Serv = inets:services_info(), + {value, {_, _, Info}} = lists:keysearch(Pid, 2, Serv), + proplists:get_value(port, Info). + +server_config(http, Config) -> + ServerRoot = ?config(server_root, Config), + [{port, 0}, + {server_name,"httpc_test"}, + {server_root, ServerRoot}, + {document_root, ?config(doc_root, Config)}, + {bind_address, any}, + {ipfamily, inet_version()}, + {mime_type, "text/plain"}, + {script_alias, {"/cgi-bin/", filename:join(ServerRoot, "cgi-bin") ++ "/"}} + ]; + +server_config(https, Config) -> + [{socket_type, {essl, ssl_config(Config)}} | server_config(http, Config)]; +server_config(sim_https, Config) -> + ssl_config(Config); +server_config(_, _) -> + []. + +start_apps(https) -> + inets_test_lib:start_apps([crypto, public_key, ssl]); +start_apps(_) -> 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) -> +ssl_config(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(), + [{certfile, filename:join(DataDir, "ssl_server_cert.pem")}, + {verify, verify_none} + ]. - 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), @@ -3362,86 +1115,41 @@ setup_server_dirs(ServerRoot, DocRoot, DataDir) -> 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, + AbsCgi = filename:join([CgiDir, Cgi]), + {ok, FileInfo} = file:read_file_info(AbsCgi), + ok = file:write_file_info(AbsCgi, FileInfo#file_info{mode = 8#00755}). - Mod_order = "Modules mod_alias mod_auth mod_esi mod_actions mod_cgi" - " mod_include mod_dir mod_get mod_head" - " mod_log mod_disk_log mod_trace", - - %% BindAddress = "*|inet", % Force the use of IPv4 - BindAddress = "*", % This corresponds to using IpFamily inet6fb4 - - HttpConfig = [ - cline(["BindAddress ", BindAddress]), - cline(["Port ", integer_to_list(Port)]), - cline(["ServerName ", "httpc_test"]), - cline(["SocketType ", atom_to_list(ComType)]), - cline([Mod_order]), - cline(["ServerRoot ", ServerRoot]), - cline(["DocumentRoot ", DocRoot]), - cline(["MaxHeaderSize ",MaxHdrSz]), - cline(["MaxHeaderAction ",MaxHdrAct]), - cline(["DirectoryIndex ", "index.html "]), - cline(["DefaultType ", "text/plain"]), - cline(["ScriptAlias /cgi-bin/ ", - filename:join(ServerRoot, "cgi-bin"), "/"]), - SSL], - ConfigFile = filename:join([PrivDir,FileName]), - {ok, Fd} = file:open(ConfigFile, [write]), - ok = file:write(Fd, lists:flatten(HttpConfig)), - ok = file:close(Fd). - -cline(List) -> - lists:flatten([List, "\r\n"]). - -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. +keep_alive_requests(Request, Profile) -> + {ok, RequestIdA0} = + httpc:request(get, Request, [], [{sync, false}], Profile), + {ok, RequestIdA1} = + httpc:request(get, Request, [], [{sync, false}], Profile), + {ok, RequestIdA2} = + httpc:request(get, Request, [], [{sync, false}], Profile), -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) + receive_replys([RequestIdA0, RequestIdA1, RequestIdA2]), + + {ok, RequestIdB0} = + httpc:request(get, Request, [], [{sync, false}], Profile), + {ok, RequestIdB1} = + httpc:request(get, Request, [], [{sync, false}], Profile), + {ok, RequestIdB2} = + httpc:request(get, Request, [], [{sync, false}], Profile), + + ok = httpc:cancel_request(RequestIdB1, Profile), + ct:print("Cancel ~p~n", [RequestIdB1]), + receive_replys([RequestIdB0, RequestIdB2]). + + +receive_replys([]) -> + ok; +receive_replys([ID|IDs]) -> + receive + {http, {ID, {{_, 200, _}, [_|_], _}}} -> + receive_replys(IDs); + {http, {Other, {{_, 200, _}, [_|_], _}}} -> + ct:fail({recived_canceld_id, Other}) end. %% Perform a synchronous stop @@ -3452,55 +1160,46 @@ dummy_server_stop(Pid) -> ok end. -dummy_server(IpV) -> - dummy_server(self(), ip_comm, IpV, []). +inet_version() -> + inet. %% Just run inet for now + %% case gen_tcp:listen(0,[inet6]) of + %% {ok, S} -> + %% gen_tcp:close(S), + %% inet6; + %% _ -> + %% inet + %%end. + +dummy_server(Inet) -> + dummy_server(self(), ip_comm, Inet, []). -dummy_server(SocketType, IpV, Extra) -> - dummy_server(self(), SocketType, IpV, Extra). +dummy_server(SocketType, Inet, Extra) -> + dummy_server(self(), SocketType, Inet, Extra). -dummy_server(Caller, SocketType, IpV, Extra) -> - Args = [Caller, SocketType, IpV, Extra], +dummy_server(Caller, SocketType, Inet, Extra) -> + Args = [Caller, SocketType, Inet, Extra], Pid = spawn(httpc_SUITE, dummy_server_init, Args), receive {port, Port} -> {Pid, Port} end. -dummy_server_init(Caller, ip_comm, IpV, _) -> +dummy_server_init(Caller, ip_comm, Inet, _) -> 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, ListenSocket} = gen_tcp:listen(0, [Inet | BaseOpts]), {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} | + +dummy_server_init(Caller, ssl, Inet, SSLOptions) -> + BaseOpts = [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]), + dummy_ssl_server_init(Caller, BaseOpts, Inet). + +dummy_ssl_server_init(Caller, BaseOpts, Inet) -> + {ok, ListenSocket} = ssl:listen(0, [Inet | BaseOpts]), {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). @@ -3508,85 +1207,56 @@ dummy_ssl_server_init(Caller, BaseOpts, IpV) -> 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) + 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 + case handle_request(Module, Function, [Data | Args], Socket) of stop when Proto =:= tcp -> gen_tcp:close(Socket); stop when Proto =:= ssl -> @@ -3600,49 +1270,26 @@ dummy_request_handler_loop({Module, Function, Args}, SockType, Socket) -> 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]), +handle_request(Module, Function, Args, Socket) -> 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 + case handle_http_msg(Result, Socket) 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) + [Data |[?HTTP_MAX_HEADER_SIZE]], Socket) 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]), +handle_http_msg({Method, RelUri, _, {_, Headers}, Body}, Socket) -> + + ct:print("Request: ~p ~p", [Method, RelUri]), + NextRequest = case RelUri of "/dummy_headers.html" -> @@ -3663,217 +1310,69 @@ handle_http_msg({_, RelUri, _, {_, Headers}, Body}, Socket, Close, Send) -> 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, - + + {ok, {_, Port}} = sockname(Socket), + + 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]), + Msg = handle_uri(Method,RelUri, Port, Headers, Socket, DefaultResponse), + case Msg of ok -> - %% Previously, this resulted in an {error, einval}. Now what? ok; close -> %% Nothing to send, just close - Close(Socket); + close(Socket); _ when is_list(Msg) orelse is_binary(Msg) -> - Send(Socket, Msg) + case Msg of + [] -> + ct:print("Empty Msg", []); + _ -> + ct:print("Response: ~p", [Msg]), + send(Socket, Msg) + end end, - tsp("handle_http_msg -> done"), NextRequest. +dummy_ssl_server_hang(Caller, Inet, SslOpt) -> + Pid = spawn(httpc_SUITE, dummy_ssl_server_hang_init, [Caller, Inet, SslOpt]), + receive + {port, Port} -> + {Pid, Port} + end. + +dummy_ssl_server_hang_init(Caller, Inet, SslOpt) -> + {ok, ListenSocket} = + ssl:listen(0, [binary, Inet, {packet, 0}, + {reuseaddr,true}, + {active, false}] ++ SslOpt), + {ok, {_,Port}} = ssl:sockname(ListenSocket), + 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. + 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]), + [_ActualHost, _Port] -> true; _ -> false @@ -3891,15 +1390,15 @@ 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]), + ct:print("Auth: ~p~n", [Auth]), DefaultResponse; Other -> - test_server:format("UnAuth: ~p~n", [Other]), + ct:print("UnAuth: ~p~n", [Other]), Challange end. check_cookie([]) -> - tsf(no_cookie_header); + ct:fail(no_cookie_header); check_cookie(["cookie:" ++ _Value | _]) -> ok; check_cookie([_Head | Tail]) -> @@ -3912,122 +1411,541 @@ content_length(["content-length:" ++ Value | _]) -> content_length([_Head | Tail]) -> content_length(Tail). -provocate_not_modified_bug(Url) -> - Timeout = 15000, %% 15s should be plenty +handle_uri(_,"/just_close.html",_,_,_,_) -> + close; +handle_uri(_,"/no_content.html",_,_,_,_) -> + "HTTP/1.0 204 No Content\r\n\r\n"; + +handle_uri(_,"/no_headers.html",_,_,_,_) -> + "HTTP/1.0 200 OK\r\n\r\nTEST"; + +handle_uri("TRACE","/trace.html",_,_,_,_) -> + Body = "TRACE /trace.html simulate HTTP TRACE ", + "HTTP/1.1 200 OK\r\n" ++ "Content-Length:" ++ integer_to_list(length(Body)) ++ "\r\n\r\n" ++ Body; + +handle_uri(_,"/ensure_host_header_with_port.html",_,Headers,_,_) -> + 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; - {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 +handle_uri(_,"/300.html",Port,_,Socket,_) -> + NewUri = url_start(Socket) ++ + integer_to_list(Port) ++ "/dummy.html", + Body = "<HTML><BODY><a href=" ++ NewUri ++ + ">New place</a></BODY></HTML>", + "HTTP/1.1 300 Multiple Choices\r\n" ++ + "Location:" ++ NewUri ++ "\r\n" ++ + "Content-Length:" ++ integer_to_list(length(Body)) + ++ "\r\n\r\n" ++ Body; + +handle_uri("HEAD","/301.html",Port,_,Socket,_) -> + NewUri = url_start(Socket) ++ + integer_to_list(Port) ++ "/dummy.html", + "HTTP/1.1 301 Moved Permanently\r\n" ++ + "Location:" ++ NewUri ++ "\r\n" ++ + "Content-Length:0\r\n\r\n"; + +handle_uri(_,"/301.html",Port,_,Socket,_) -> + NewUri = url_start(Socket) ++ + integer_to_list(Port) ++ "/dummy.html", + Body = "<HTML><BODY><a href=" ++ NewUri ++ + ">New place</a></BODY></HTML>", + "HTTP/1.1 301 Moved Permanently\r\n" ++ + "Location:" ++ NewUri ++ "\r\n" ++ + "Content-Length:" ++ integer_to_list(length(Body)) + ++ "\r\n\r\n" ++ Body; + +handle_uri("HEAD","/302.html",Port,_,Socket,_) -> + NewUri = url_start(Socket) ++ + integer_to_list(Port) ++ "/dummy.html", + "HTTP/1.1 302 Found \r\n" ++ + "Location:" ++ NewUri ++ "\r\n" ++ + "Content-Length:0\r\n\r\n"; + +handle_uri(_,"/302.html",Port, _,Socket,_) -> + NewUri = url_start(Socket) ++ + integer_to_list(Port) ++ "/dummy.html", + Body = "<HTML><BODY><a href=" ++ NewUri ++ + ">New place</a></BODY></HTML>", + "HTTP/1.1 302 Found \r\n" ++ + "Location:" ++ NewUri ++ "\r\n" ++ + "Content-Length:" ++ integer_to_list(length(Body)) + ++ "\r\n\r\n" ++ Body; + +handle_uri("HEAD","/303.html",Port,_,Socket,_) -> + NewUri = url_start(Socket) ++ + integer_to_list(Port) ++ "/dummy.html", + "HTTP/1.1 302 See Other \r\n" ++ + "Location:" ++ NewUri ++ "\r\n" ++ + "Content-Length:0\r\n\r\n"; +handle_uri(_,"/303.html",Port,_,Socket,_) -> + NewUri = url_start(Socket) ++ + integer_to_list(Port) ++ "/dummy.html", + Body = "<HTML><BODY><a href=" ++ NewUri ++ + ">New place</a></BODY></HTML>", + "HTTP/1.1 303 See Other \r\n" ++ + "Location:" ++ NewUri ++ "\r\n" ++ + "Content-Length:" ++ integer_to_list(length(Body)) + ++ "\r\n\r\n" ++ Body; +handle_uri("HEAD","/307.html",Port,_,Socket,_) -> + NewUri = url_start(Socket) ++ + integer_to_list(Port) ++ "/dummy.html", + "HTTP/1.1 307 Temporary Rediect \r\n" ++ + "Location:" ++ NewUri ++ "\r\n" ++ + "Content-Length:0\r\n\r\n"; +handle_uri(_,"/307.html",Port,_,Socket,_) -> + NewUri = url_start(Socket) ++ + integer_to_list(Port) ++ "/dummy.html", + Body = "<HTML><BODY><a href=" ++ NewUri ++ + ">New place</a></BODY></HTML>", + "HTTP/1.1 307 Temporary Rediect \r\n" ++ + "Location:" ++ NewUri ++ "\r\n" ++ + "Content-Length:" ++ integer_to_list(length(Body)) + ++ "\r\n\r\n" ++ Body; + +handle_uri(_,"/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>"; + +handle_uri(_,"/503.html",_,_,_,DefaultResponse) -> + 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; + +handle_uri(_,"/redirectloop.html",Port,_,Socket,_) -> + %% Create a potential endless loop! + NewUri = url_start(Socket) ++ + integer_to_list(Port) ++ "/redirectloop.html", + Body = "<HTML><BODY><a href=" ++ NewUri ++ + ">New place</a></BODY></HTML>", + "HTTP/1.1 300 Multiple Choices\r\n" ++ + "Location:" ++ NewUri ++ "\r\n" ++ + "Content-Length:" ++ integer_to_list(length(Body)) + ++ "\r\n\r\n" ++ Body; + +handle_uri(_,"/userinfo.html", _,Headers,_, DefaultResponse) -> + 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; + +handle_uri(_,"/dummy_headers.html",_,_,Socket,_) -> + %% 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(); + +handle_uri(_,"/capital_transfer_encoding.html",_,_,Socket,_) -> + 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(); + +handle_uri(_,"/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>"; + +handle_uri(_,"/missing_crlf.html",_,_,_,_) -> + "HTTP/1.1 200 ok" ++ + "Content-Length:32\r\n" ++ + "<HTML><BODY>foobar</BODY></HTML>"; + +handle_uri(_,"/wrong_statusline.html",_,_,_,_) -> + "ok 200 HTTP/1.1\r\n\r\n" ++ + "Content-Length:32\r\n\r\n" ++ + "<HTML><BODY>foobar</BODY></HTML>"; + +handle_uri(_,"/once_chunked.html",_,_,Socket,_) -> + 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(); + +handle_uri(_,"/once.html",_,_,Socket,_) -> + 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>"); + +handle_uri(_,"/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"; + +handle_uri(_,"/missing_reason_phrase.html",_,_,_,_) -> + "HTTP/1.1 200\r\n" ++ + "Content-Length: 32\r\n\r\n" + "<HTML><BODY>foobar</BODY></HTML>"; + +handle_uri(_,"/missing_CR.html",_,_,_,_) -> + "HTTP/1.1 200 ok\n" ++ + "Content-Length:32\r\n\n" ++ + "<HTML><BODY>foobar</BODY></HTML>"; + +handle_uri("HEAD",_,_,_,_,_) -> + "HTTP/1.1 200 ok\r\n" ++ + "Content-Length:0\r\n\r\n"; +handle_uri(_,_,_,_,_,DefaultResponse) -> + DefaultResponse. + +url_start(#sslsocket{}) -> + {ok,Host} = inet:gethostname(), + ?TLS_URL_START ++ Host ++ ":"; +url_start(_) -> + {ok,Host} = inet:gethostname(), + ?URL_START ++ Host ++ ":". + +send(#sslsocket{} = S, Msg) -> + ssl:send(S, Msg); +send(S, Msg) -> + gen_tcp:send(S, Msg). + +close(#sslsocket{} = S) -> + ssl:close(S); +close(S) -> + gen_tcp:close(S). + +sockname(#sslsocket{}= S) -> + ssl:sockname(S); +sockname(S) -> + inet:sockname(S). + +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} -> + ct:fail(Msg) 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 +receive_streamed_body(RequestId, Body, Pid) -> + httpc:stream_next(Pid), + ct:print("~p:receive_streamed_body -> requested next stream ~n", [?MODULE]), + receive + {http, {RequestId, stream, BinBodyPart}} -> + %% Make sure the httpc hasn't sent us the next 'stream' + %% without our request. + receive + {http, {RequestId, stream, _}} = Msg -> + ct:fail({unexpected_flood_of_stream, Msg}) + after + 1000 -> + ok + end, + receive_streamed_body(RequestId, + <<Body/binary, BinBodyPart/binary>>, + Pid); + {http, {RequestId, stream_end, _Headers}} -> + Body; + {http, Msg} -> + ct:fail(Msg) end. +%% ----------------------------------------------------- +%% 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). -%% ------------------------------------------------------------------------- - -simple_request_and_verify(Config, - Method, Request, HttpOpts, Opts, VerifyResult) - when (is_list(Config) andalso - is_atom(Method) andalso - is_list(HttpOpts) andalso - is_list(Opts) andalso - is_function(VerifyResult, 1)) -> - tsp("request_and_verify -> entry with" - "~n Method: ~p" - "~n Request: ~p" - "~n HttpOpts: ~p" - "~n Opts: ~p", [Method, Request, HttpOpts, Opts]), - case ?config(local_server, Config) of - ok -> - tsp("request_and_verify -> local-server running"), - Result = (catch httpc:request(Method, Request, HttpOpts, Opts)), - VerifyResult(Result); - _ -> - tsp("request_and_verify -> local-server *not* running - skip"), - hard_skip("Local http-server not running") +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) -> + {ok,Host} = inet:gethostname(), + set_random_seed(), + lists:map( + fun(Id) -> + Req = lists:flatten(io_lib:format("req~3..0w", [get_next_sequence_number(SeqNumServer)])), + Url = ?URL_START ++ Host ++ ":" ++ integer_to_list(ServerPort) ++ "/" ++ Req, + Pid = proc_lib:spawn( + fun() -> + case httpc:request(Url) of + {ok, {{_,200,_}, _, Resp}} -> + ct:print("[~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}} -> + ct:print("[~w] ~w response: " + "~s~n~s~n", + [Id, EC, Reason, Resp]), + exit({bad_resp,Req,Resp}); + Crap -> + ct:print("[~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)). + +wait4clients([], _Timeout) -> + ok; +wait4clients(Clients, Timeout) when Timeout > 0 -> + Time = now_ms(), + receive + {'DOWN', _MRef, process, Pid, normal} -> + {value, {Id, _, _}} = lists:keysearch(Pid, 2, Clients), + NewClients = lists:keydelete(Id, 1, Clients), + wait4clients(NewClients, Timeout - (now_ms() - Time)); + {'DOWN', _MRef, process, Pid, Reason} -> + {value, {Id, _, _}} = lists:keysearch(Pid, 2, Clients), + ct:fail({bad_client_termination, Id, Reason}) + after Timeout -> + ct:fail({client_timeout, Clients}) + end; +wait4clients(Clients, _) -> + ct:fail({client_timeout, Clients}). -not_implemented_yet() -> - exit(not_implemented_yet). -p(F) -> - p(F, []). +%% ----------------------------------------------------- +%% Webserver part: +%% Implements a web server that sends responses one character +%% at a time, with random delays between the characters. + +start_slow_server(SeqNumServer) -> + proc_lib:start( + erlang, apply, [fun() -> init_slow_server(SeqNumServer) end, []]). -p(F, A) -> - io:format("~p ~w:" ++ F ++ "~n", [self(), ?MODULE | A]). +init_slow_server(SeqNumServer) -> + Inet = inet_version(), + {ok, LSock} = gen_tcp:listen(0, [binary, Inet, {packet,0}, {active,true}, + {backlog, 100}]), + {ok, {_IP, Port}} = inet:sockname(LSock), + proc_lib:init_ack({ok, self(), Port}), + loop_slow_server(LSock, SeqNumServer). -tsp(F) -> - inets_test_lib:tsp("[~w]" ++ F, [?MODULE]). -tsp(F, A) -> - inets_test_lib:tsp("[~w]" ++ F, [?MODULE|A]). +loop_slow_server(LSock, SeqNumServer) -> + Master = self(), + Acceptor = proc_lib:spawn( + fun() -> client_handler(Master, LSock, SeqNumServer) end), + receive + {accepted, Acceptor} -> + loop_slow_server(LSock, SeqNumServer); + shutdown -> + gen_tcp:close(LSock), + exit(Acceptor, kill) + end. -tsf(Reason) -> - test_server:fail(Reason). +%% Handle one client connection +client_handler(Master, LSock, SeqNumServer) -> + {ok, CSock} = gen_tcp:accept(LSock), + Master ! {accepted, self()}, + set_random_seed(), + loop_client(1, CSock, SeqNumServer). -dummy_ssl_server_hang(Caller, IpV, SslOpt) -> - Pid = spawn(httpc_SUITE, dummy_ssl_server_hang_init, [Caller, IpV, SslOpt]), +loop_client(N, CSock, SeqNumServer) -> + %% Await request, don't bother parsing it too much, + %% assuming the entire request arrives in one packet. receive - {port, Port} -> - {Pid, Port} + {tcp, CSock, Req} -> + ReqNum = parse_req_num(Req), + RespSeqNum = get_next_sequence_number(SeqNumServer), + Response = lists:flatten(io_lib:format("~s->resp~3..0w/~2..0w", [ReqNum, RespSeqNum, N])), + Txt = lists:flatten(io_lib:format("Slow server (~p) got ~p, answering with ~p", + [self(), Req, Response])), + ct:print("~s...~n", [Txt]), + slowly_send_response(CSock, Response), + case parse_connection_type(Req) of + keep_alive -> + ct:print("~s...done~n", [Txt]), + loop_client(N+1, CSock, SeqNumServer); + close -> + ct:print("~s...done (closing)~n", [Txt]), + gen_tcp:close(CSock) + end 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). +slowly_send_response(CSock, Answer) -> + Response = lists:flatten(io_lib:format("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). -dummy_ssl_server_hang_loop(_) -> - %% Do not do ssl:ssl_accept as we - %% want to time out the underlying gen_tcp:connect +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. + +%% Time in milli seconds +now_ms() -> + {A,B,C} = erlang:now(), + A*1000000000+B*1000+(C div 1000). + +set_random_seed() -> + {_, _, Micros} = now(), + A = erlang:phash2([make_ref(), self(), Micros]), + random:seed(A, A, A). + + +otp_8739(doc) -> + ["OTP-8739"]; +otp_8739(suite) -> + []; +otp_8739(Config) when is_list(Config) -> + {_DummyServerPid, Port} = otp_8739_dummy_server(), + {ok,Host} = inet:gethostname(), + URL = ?URL_START ++ Host ++ ":" ++ 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(), + ct:print("Info: ~p", [Info]), + {value, {handlers, Handlers}} = + lists:keysearch(handlers, 1, Info), + case Handlers of + [] -> + ok; + _ -> + ct:fail({unexpected_handlers, Handlers}) + end; + Unexpected -> + ct:fail({unexpected, Unexpected}) + end. + +otp_8739_dummy_server() -> + Parent = self(), + Pid = spawn_link(fun() -> otp_8739_dummy_server_init(Parent) end), receive - stop -> - ok + {port, Port} -> + {Pid, Port} end. -hard_skip(Reason) -> - throw(skip(Reason)). +otp_8739_dummy_server_init(Parent) -> + Inet = inet_version(), + {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). -skip(Reason) -> - {skip, Reason}. +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} -> + ct:fail("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. diff --git a/lib/inets/test/httpc_cookie_SUITE.erl b/lib/inets/test/httpc_cookie_SUITE.erl index 93dbc270c5..80f43ec236 100644 --- a/lib/inets/test/httpc_cookie_SUITE.erl +++ b/lib/inets/test/httpc_cookie_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2011. All Rights Reserved. +%% Copyright Ericsson AB 2005-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -276,8 +276,6 @@ secure_cookie(Config) when is_list(Config) -> tsp("secure_cookie -> entry with" "~n Config: ~p", [Config]), - inets:enable_trace(max, io, httpc), - %% httpc:reset_cookies(), tsp("secure_cookie -> Cookies 1: ~p", [httpc:which_cookies()]), @@ -309,7 +307,6 @@ secure_cookie(Config) when is_list(Config) -> tsp("secure_cookie -> Cookies 4: ~p", [httpc:which_cookies()]), - inets:disable_trace(), tsp("secure_cookie -> done"), ok. diff --git a/lib/inets/test/httpc_proxy_SUITE.erl b/lib/inets/test/httpc_proxy_SUITE.erl new file mode 100644 index 0000000000..84db39e76b --- /dev/null +++ b/lib/inets/test/httpc_proxy_SUITE.erl @@ -0,0 +1,575 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2012. 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_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()}]. + +%% internal functions + +local_proxy_cases() -> + [http_head, + http_get, + http_options, + http_trace, + http_post, + http_put, + http_delete, + http_headers, + http_proxy_auth, + http_doesnotexist, + http_stream, + http_not_modified_otp_6821]. + +%%-------------------------------------------------------------------- + +init_per_suite(Config0) -> + case init_apps([crypto,public_key], 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() -> + [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 ?config(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_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. + +%%-------------------------------------------------------------------- +%% 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 ?config(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 = ?config(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 = ?config(protocol, Config), + {ServerName,ServerPort} = ?config(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 can not 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 = ?config(data_dir, Config), + PrivDir = ?config(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). diff --git a/lib/inets/test/httpc_proxy_SUITE_data/apache2/apache2.conf b/lib/inets/test/httpc_proxy_SUITE_data/apache2/apache2.conf new file mode 100644 index 0000000000..37af88c510 --- /dev/null +++ b/lib/inets/test/httpc_proxy_SUITE_data/apache2/apache2.conf @@ -0,0 +1,87 @@ +## Simple Apache 2 configuration file for daily test very local http server +## +## %CopyrightBegin% +## +## Copyright Ericsson AB 2012. 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% +## +## Author: Raimo Niskanen, Erlang/OTP +# +LockFile ${APACHE_LOCK_DIR}/accept.lock +PidFile ${APACHE_PID_FILE} + +Timeout 300 + +User ${APACHE_RUN_USER} +Group ${APACHE_RUN_GROUP} + +DefaultType text/plain +HostnameLookups Off +ErrorLog ${APACHE_LOG_DIR}/error.log +LogLevel warn + +Include ${APACHE_MODS_DIR}/*.load +Include ${APACHE_MODS_DIR}/*.conf + +Listen ${APACHE_HTTP_PORT} http + +<IfModule mod_ssl.c> + Listen ${APACHE_HTTPS_PORT} https + SSLMutex file:${APACHE_LOCK_DIR}/ssl_mutex +</IfModule> + +#<IfModule mod_gnutls.c> +# Listen 8443 +#</IfModule> + +#LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined +LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined +#LogFormat "%h %l %u %t \"%r\" %>s %O" common +#LogFormat "%{Referer}i -> %U" referer +#LogFormat "%{User-agent}i" agent + +CustomLog ${APACHE_LOG_DIR}/access.log combined + +<Directory /> + AllowOverride None + Order Deny,Allow + Deny from all +</Directory> + +ServerTokens Minimal +ServerSignature Off +KeepAlive On +KeepAliveTimeout 5 + +ServerName ${APACHE_SERVER_NAME} +ServerAdmin webmaster@${APACHE_SERVER_NAME} +DocumentRoot ${APACHE_DOCROOT} +<Directory ${APACHE_DOCROOT}> + Options Indexes FollowSymLinks MultiViews + AllowOverride None + Order allow,deny + Allow from all +</Directory> + +<VirtualHost *:${APACHE_HTTP_PORT}> +</VirtualHost> + +<IfModule mod_ssl.c> + <VirtualHost *:${APACHE_HTTPS_PORT}> + SSLCertificateFile ${APACHE_CERTS_DIR}/server-cert.pem + SSLCertificateKeyFile ${APACHE_CERTS_DIR}/server-key.pem + SSLEngine on + </VirtualHost> +</IfModule> diff --git a/lib/inets/test/httpc_proxy_SUITE_data/apache2/htdocs/index.html b/lib/inets/test/httpc_proxy_SUITE_data/apache2/htdocs/index.html new file mode 100644 index 0000000000..1c70d95348 --- /dev/null +++ b/lib/inets/test/httpc_proxy_SUITE_data/apache2/htdocs/index.html @@ -0,0 +1,4 @@ +<html><body><h1>It works!</h1> +<p>This is the default web page for this server.</p> +<p>The web server software is running but no content has been added, yet.</p> +</body></html> diff --git a/lib/inets/test/httpc_proxy_SUITE_data/server_proxy.sh b/lib/inets/test/httpc_proxy_SUITE_data/server_proxy.sh new file mode 100755 index 0000000000..4b05ea63ef --- /dev/null +++ b/lib/inets/test/httpc_proxy_SUITE_data/server_proxy.sh @@ -0,0 +1,198 @@ +#! /bin/sh +## +## Command file to handle external webserver and proxy +## apache2 and tinyproxy. +## +## %CopyrightBegin% +## +## Copyright Ericsson AB 2012. 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% +## +## Author: Raimo Niskanen, Erlang/OTP +# + +PATH=/usr/local/bin:/usr/local/sbin:/bin:/usr/bin:/sbin:/usr/sbin +SHELL=/bin/sh +unset CDPATH ENV BASH_ENV +IFS=' + ' + +APACHE_MODS_AVAILABLE_DIR="/etc/apache2/mods-available" +MODS="authz_host.load mime.conf mime.load ssl.conf ssl.load" + +APACHE_HTTP_PORT=8080 +APACHE_HTTPS_PORT=8443 +APACHE_SERVER_NAME=localhost +export APACHE_HTTP_PORT APACHE_HTTPS_PORT APACHE_SERVER_NAME + +PROXY_SERVER_NAME=localhost +PROXY_PORT=8000 +export PROXY_SERVER_NAME PROXY_PORT + +# All stdout goes to the calling erlang port, therefore +# these helpers push all side info to stderr. +status () { echo "$@"; } +info () { echo "$@" 1>&2; } +die () { REASON="$?"; status "$@"; exit "$REASON"; } +cmd () { "$@" 1>&2; } +silent () { "$@" 1>/dev/null 2>&1; } + +wait_for_pidfile () { + PIDFILE="${1:?Missing argument: PidFile}" + for t in 1 1 1 2 2 3 3 3 4; do + PID="`head -1 "$1" 2>/dev/null`" && [ :"$PID" != : ] && break + sleep $t + done + [ :"$PID" = : ] && die ":ERROR:No or empty PidFile: $1" + info "Started $PIDFILE[$PID]." +} + +kill_and_wait () { + PID_FILE="${1:?Missing argument: PidFile}" + if [ -f "$PID_FILE" ]; then + PID="`head -1 "$PID_FILE" 2>/dev/null`" + [ :"$PID" = : ] && \ + info "Empty Pid file: $1" + info "Stopping $1 [$PID]..." + shift + case :"${1:?Missing argument: kill command}" in + :kill) + [ :"$PID" = : ] || cmd kill "$PID";; + :*) + cmd "$@";; + esac + wait "$PID" + for t in 1 1 1 2; do + sleep $t + [ -e "$PID_FILE" ] || break + done + silent rm "$PID_FILE" + else + info "No pid file: $1" + fi +} + + +PRIV_DIR="`pwd`" +DATA_DIR="`dirname "$0"`" +DATA_DIR="`cd "$DATA_DIR" && pwd`" + +silent type apache2ctl || \ + die ":SKIP: Can not find apache2ctl." +silent type tinyproxy || \ + die ":SKIP: Can not find tinyproxy." + +[ -d "$APACHE_MODS_AVAILABLE_DIR" ] || \ + die ":SKIP:Can not locate modules dir $APACHE_MODS_AVAILABLE_DIR." + +silent mkdir apache2 tinyproxy +cd apache2 || \ + die ":ERROR:Can not cd to apache2" +CWD="`pwd`" +(cd ../tinyproxy) || \ + die ":ERROR:Can not cd to ../tinyproxy" + +unset APACHE_HTTPD APACHE_LYNX APACHE_STATUSURL + +## apache2ctl envvars variables +APACHE_CONFDIR="$DATA_DIR/apache2" +[ -f "$APACHE_CONFDIR"/apache2.conf ] || \ + die ":SKIP:No config file: $APACHE_CONFDIR/apache2.conf." +APACHE_RUN_USER=`id | sed 's/^uid=[0-9]\{1,\}(\([^)]*\)).*/\1/'` +APACHE_RUN_GROUP=`id | sed 's/.*[ ]gid=[0-9]\{1,\}(\([^)]*\)).*/\1/'` +APACHE_RUN_DIR="$CWD/run" +APACHE_PID_FILE="$APACHE_RUN_DIR/pid" +APACHE_LOCK_DIR="$CWD/lock" +APACHE_LOG_DIR="$CWD/log" +export APACHE_CONFDIR APACHE_RUN_USER APACHE_RUN_GROUP +export APACHE_RUN_DIR APACHE_PID_FILE +export APACHE_LOCK_DIR APACHE_LOG_DIR +silent cmd mkdir "$APACHE_CONFDIR" +silent cmd mkdir "$APACHE_RUN_DIR" "$APACHE_LOCK_DIR" "$APACHE_LOG_DIR" + +## Our apache2.conf additional variables +APACHE_MODS_DIR="$CWD/mods" +APACHE_DOCROOT="$APACHE_CONFDIR/htdocs" +APACHE_CERTS_DIR="$PRIV_DIR" +export APACHE_MODS_DIR APACHE_DOCROOT APACHE_CERTS_DIR +[ -d "$APACHE_MODS_DIR" ] || { + cmd mkdir "$APACHE_MODS_DIR" + for MOD in $MODS; do + cmd ln -s "$APACHE_MODS_AVAILABLE_DIR/$MOD" "$APACHE_MODS_DIR" || { + die ":ERROR:ln of apache 2 module $MOD failed" + } + done +} + +case :"${1:?}" in + + :start) + info "Starting apache2..." + cmd apache2ctl start + [ $? = 0 ] || \ + die ":ERROR: apache2 did not start." + wait_for_pidfile "$APACHE_PID_FILE" + + info "Starting tinyproxy..." + cmd cd ../tinyproxy || \ + die ":ERROR:Can not cd to `pwd`/../tinyproxy" + cat >tinyproxy.conf <<EOF +Port $PROXY_PORT + +Listen 127.0.0.1 +BindSame yes +Timeout 600 + +DefaultErrorFile "default.html" +Logfile "tinyproxy.log" +PidFile "tinyproxy.pid" + +MaxClients 100 +MinSpareServers 2 +MaxSpareServers 8 +StartServers 2 +MaxRequestsPerChild 0 + +ViaProxyName "tinyproxy" + +ConnectPort $APACHE_HTTPS_PORT +EOF + (tinyproxy -d -c tinyproxy.conf 1>/dev/null 2>&1 </dev/null &)& + wait_for_pidfile tinyproxy.pid + + status ":STARTED:$PROXY_SERVER_NAME:$PROXY_PORT|\ +$APACHE_SERVER_NAME:$APACHE_HTTP_PORT:$APACHE_HTTPS_PORT" + exit 0 + ;; + + :stop) + kill_and_wait ../tinyproxy/tinyproxy.pid kill + kill_and_wait "$APACHE_PID_FILE" apache2ctl stop + + status ":STOPPED:" + exit 0 + ;; + + :apache2ctl) + shift + cmd apache2ctl ${1+"$@"} + exit + ;; + + :*) + (exit 1); die ":ERROR: I do not know of command '$1'." + ;; + +esac diff --git a/lib/inets/test/httpd_SUITE.erl b/lib/inets/test/httpd_SUITE.erl index 41e4188e5f..1efa78a63e 100644 --- a/lib/inets/test/httpd_SUITE.erl +++ b/lib/inets/test/httpd_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2012. All Rights Reserved. +%% Copyright Ericsson AB 2005-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -67,97 +67,36 @@ ]). -export([ - pssl_mod_alias/1, essl_mod_alias/1, - - pssl_mod_actions/1, essl_mod_actions/1, - - pssl_mod_security/1, essl_mod_security/1, - - pssl_mod_auth/1, essl_mod_auth/1, - - pssl_mod_auth_api/1, essl_mod_auth_api/1, - - pssl_mod_auth_mnesia_api/1, essl_mod_auth_mnesia_api/1, - - pssl_mod_htaccess/1, essl_mod_htaccess/1, - - pssl_mod_cgi/1, essl_mod_cgi/1, - - pssl_mod_esi/1, essl_mod_esi/1, - - pssl_mod_get/1, essl_mod_get/1, - - pssl_mod_head/1, essl_mod_head/1, - - pssl_mod_all/1, essl_mod_all/1, - - pssl_load_light/1, essl_load_light/1, - - pssl_load_medium/1, essl_load_medium/1, - - pssl_load_heavy/1, essl_load_heavy/1, - - pssl_dos_hostname/1, essl_dos_hostname/1, - - pssl_time_test/1, essl_time_test/1, - - pssl_restart_no_block/1, essl_restart_no_block/1, - - pssl_restart_disturbing_block/1, essl_restart_disturbing_block/1, - - pssl_restart_non_disturbing_block/1, essl_restart_non_disturbing_block/1, - - pssl_block_disturbing_idle/1, essl_block_disturbing_idle/1, - - pssl_block_non_disturbing_idle/1, essl_block_non_disturbing_idle/1, - - pssl_block_503/1, essl_block_503/1, - - pssl_block_disturbing_active/1, essl_block_disturbing_active/1, - - pssl_block_non_disturbing_active/1, essl_block_non_disturbing_active/1, - - pssl_block_disturbing_active_timeout_not_released/1, essl_block_disturbing_active_timeout_not_released/1, - - pssl_block_disturbing_active_timeout_released/1, essl_block_disturbing_active_timeout_released/1, - - pssl_block_non_disturbing_active_timeout_not_released/1, essl_block_non_disturbing_active_timeout_not_released/1, - - pssl_block_non_disturbing_active_timeout_released/1, essl_block_non_disturbing_active_timeout_released/1, - - pssl_block_disturbing_blocker_dies/1, essl_block_disturbing_blocker_dies/1, - - pssl_block_non_disturbing_blocker_dies/1, essl_block_non_disturbing_blocker_dies/1 ]). @@ -242,26 +181,7 @@ groups() -> ip_block_non_disturbing_active_timeout_released, ip_block_disturbing_blocker_dies, ip_block_non_disturbing_blocker_dies]}, - {ssl, [], [{group, pssl}, {group, essl}]}, - {pssl, [], - [pssl_mod_alias, pssl_mod_actions, pssl_mod_security, - pssl_mod_auth, pssl_mod_auth_api, - pssl_mod_auth_mnesia_api, pssl_mod_htaccess, - pssl_mod_cgi, pssl_mod_esi, pssl_mod_get, pssl_mod_head, - pssl_mod_all, pssl_load_light, pssl_load_medium, - pssl_load_heavy, pssl_dos_hostname, pssl_time_test, - pssl_restart_no_block, pssl_restart_disturbing_block, - pssl_restart_non_disturbing_block, - pssl_block_disturbing_idle, - pssl_block_non_disturbing_idle, pssl_block_503, - pssl_block_disturbing_active, - pssl_block_non_disturbing_active, - pssl_block_disturbing_active_timeout_not_released, - pssl_block_disturbing_active_timeout_released, - pssl_block_non_disturbing_active_timeout_not_released, - pssl_block_non_disturbing_active_timeout_released, - pssl_block_disturbing_blocker_dies, - pssl_block_non_disturbing_blocker_dies]}, + {ssl, [], [{group, essl}]}, {essl, [], [essl_mod_alias, essl_mod_actions, essl_mod_security, essl_mod_auth, essl_mod_auth_api, @@ -375,8 +295,8 @@ init_per_testcase(Case, Config) -> init_per_testcase2(Case, Config) -> - tsp("init_per_testcase2 -> entry with" - "~n Config: ~p", [Config]), + %% tsp("init_per_testcase2 -> entry with" + %% "~n Config: ~p", [Config]), IpNormal = integer_to_list(?IP_PORT) ++ ".conf", IpHtaccess = integer_to_list(?IP_PORT) ++ "htaccess.conf", @@ -386,33 +306,33 @@ init_per_testcase2(Case, Config) -> DataDir = ?config(data_dir, Config), SuiteTopDir = ?config(suite_top_dir, Config), - tsp("init_per_testcase2 -> " - "~n SuiteDir: ~p" - "~n DataDir: ~p", [SuiteTopDir, DataDir]), + %% tsp("init_per_testcase2 -> " + %% "~n SuiteDir: ~p" + %% "~n DataDir: ~p", [SuiteTopDir, DataDir]), TcTopDir = filename:join(SuiteTopDir, Case), ?line ok = file:make_dir(TcTopDir), - tsp("init_per_testcase2 -> " - "~n TcTopDir: ~p", [TcTopDir]), + %% tsp("init_per_testcase2 -> " + %% "~n TcTopDir: ~p", [TcTopDir]), DataSrc = filename:join([DataDir, "server_root"]), ServerRoot = filename:join([TcTopDir, "server_root"]), - tsp("init_per_testcase2 -> " - "~n DataSrc: ~p" - "~n ServerRoot: ~p", [DataSrc, ServerRoot]), + %% tsp("init_per_testcase2 -> " + %% "~n DataSrc: ~p" + %% "~n ServerRoot: ~p", [DataSrc, ServerRoot]), ok = file:make_dir(ServerRoot), ok = file:make_dir(filename:join([TcTopDir, "logs"])), NewConfig = [{tc_top_dir, TcTopDir}, {server_root, ServerRoot} | Config], - tsp("init_per_testcase2 -> copy DataSrc to ServerRoot"), + %% tsp("init_per_testcase2 -> copy DataSrc to ServerRoot"), inets_test_lib:copy_dirs(DataSrc, ServerRoot), - tsp("init_per_testcase2 -> fix cgi"), + %% tsp("init_per_testcase2 -> fix cgi"), EnvCGI = filename:join([ServerRoot, "cgi-bin", "printenv.sh"]), {ok, FileInfo} = file:read_file_info(EnvCGI), ok = file:write_file_info(EnvCGI, @@ -432,14 +352,14 @@ init_per_testcase2(Case, Config) -> FileInfo1#file_info{mode = 8#00755}), %% To be used by IP test cases - tsp("init_per_testcase2 -> ip testcase setups"), + %% tsp("init_per_testcase2 -> ip testcase setups"), create_config([{port, ?IP_PORT}, {sock_type, ip_comm} | NewConfig], normal_access, IpNormal), create_config([{port, ?IP_PORT}, {sock_type, ip_comm} | NewConfig], mod_htaccess, IpHtaccess), %% To be used by SSL test cases - tsp("init_per_testcase2 -> ssl testcase setups"), + %% tsp("init_per_testcase2 -> ssl testcase setups"), SocketType = case atom_to_list(Case) of [X, $s, $s, $l | _] -> @@ -504,8 +424,8 @@ init_per_testcase2(Case, Config) -> NewConfig end, - tsp("init_per_testcase2 -> done when" - "~n NewConfig2: ~p", [NewConfig2]), + %% tsp("init_per_testcase2 -> done when" + %% "~n NewConfig2: ~p", [NewConfig2]), NewConfig2. @@ -530,27 +450,14 @@ init_per_testcase3(Case, Config) -> application:stop(inets), application:stop(ssl), cleanup_mnesia(), - - %% Set trace level - case lists:reverse(atom_to_list(Case)) of - "tset_emit" ++ _Rest -> % test-cases ending with time_test - tsp("init_per_testcase3(~w) -> disabling trace", [Case]), - inets:disable_trace(); - _ -> - tsp("init_per_testcase3(~w) -> enabling trace", [Case]), - %% TraceLevel = 70, - TraceLevel = max, - TraceDest = io, - inets:enable_trace(TraceLevel, TraceDest, httpd) - end, - + %% Start initialization tsp("init_per_testcase3(~w) -> start init", [Case]), - - + Dog = test_server:timetrap(inets_test_lib:minutes(10)), NewConfig = lists:keydelete(watchdog, 1, Config), TcTopDir = ?config(tc_top_dir, Config), + CaseRest = case atom_to_list(Case) of "ip_mod_htaccess" -> @@ -762,14 +669,9 @@ ip_mod_cgi(doc) -> ip_mod_cgi(suite) -> []; ip_mod_cgi(Config) when is_list(Config) -> - case test_server:os_type() of - vxworks -> - {skip, cgi_not_supported_on_vxwoks}; - _ -> - httpd_mod:cgi(ip_comm, ?IP_PORT, - ?config(host, Config), ?config(node, Config)), - ok - end. + httpd_mod:cgi(ip_comm, ?IP_PORT, + ?config(host, Config), ?config(node, Config)), + ok. %%------------------------------------------------------------------------- ip_mod_esi(doc) -> ["Module test: mod_esi"]; @@ -1088,13 +990,6 @@ ip_restart_non_disturbing_block(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -pssl_mod_alias(doc) -> - ["Module test: mod_alias - old SSL config"]; -pssl_mod_alias(suite) -> - []; -pssl_mod_alias(Config) when is_list(Config) -> - ssl_mod_alias(ssl, Config). - essl_mod_alias(doc) -> ["Module test: mod_alias - using new of configure new SSL"]; essl_mod_alias(suite) -> @@ -1111,13 +1006,6 @@ ssl_mod_alias(Tag, Config) -> %%------------------------------------------------------------------------- -pssl_mod_actions(doc) -> - ["Module test: mod_actions - old SSL config"]; -pssl_mod_actions(suite) -> - []; -pssl_mod_actions(Config) when is_list(Config) -> - ssl_mod_actions(ssl, Config). - essl_mod_actions(doc) -> ["Module test: mod_actions - using new of configure new SSL"]; essl_mod_actions(suite) -> @@ -1136,13 +1024,6 @@ ssl_mod_actions(Tag, Config) -> %%------------------------------------------------------------------------- -pssl_mod_security(doc) -> - ["Module test: mod_security - old SSL config"]; -pssl_mod_security(suite) -> - []; -pssl_mod_security(Config) when is_list(Config) -> - ssl_mod_security(ssl, Config). - essl_mod_security(doc) -> ["Module test: mod_security - using new of configure new SSL"]; essl_mod_security(suite) -> @@ -1162,13 +1043,6 @@ ssl_mod_security(Tag, Config) -> %%------------------------------------------------------------------------- -pssl_mod_auth(doc) -> - ["Module test: mod_auth - old SSL config"]; -pssl_mod_auth(suite) -> - []; -pssl_mod_auth(Config) when is_list(Config) -> - ssl_mod_auth(ssl, Config). - essl_mod_auth(doc) -> ["Module test: mod_auth - using new of configure new SSL"]; essl_mod_auth(suite) -> @@ -1186,12 +1060,6 @@ ssl_mod_auth(Tag, Config) -> %%------------------------------------------------------------------------- -pssl_mod_auth_api(doc) -> - ["Module test: mod_auth - old SSL config"]; -pssl_mod_auth_api(suite) -> - []; -pssl_mod_auth_api(Config) when is_list(Config) -> - ssl_mod_auth_api(ssl, Config). essl_mod_auth_api(doc) -> ["Module test: mod_auth - using new of configure new SSL"]; @@ -1212,12 +1080,6 @@ ssl_mod_auth_api(Tag, Config) -> %%------------------------------------------------------------------------- -pssl_mod_auth_mnesia_api(doc) -> - ["Module test: mod_auth_mnesia_api - old SSL config"]; -pssl_mod_auth_mnesia_api(suite) -> - []; -pssl_mod_auth_mnesia_api(Config) when is_list(Config) -> - ssl_mod_auth_mnesia_api(ssl, Config). essl_mod_auth_mnesia_api(doc) -> ["Module test: mod_auth_mnesia_api - using new of configure new SSL"]; @@ -1236,13 +1098,6 @@ ssl_mod_auth_mnesia_api(Tag, Config) -> %%------------------------------------------------------------------------- -pssl_mod_htaccess(doc) -> - ["Module test: mod_htaccess - old SSL config"]; -pssl_mod_htaccess(suite) -> - []; -pssl_mod_htaccess(Config) when is_list(Config) -> - ssl_mod_htaccess(ssl, Config). - essl_mod_htaccess(doc) -> ["Module test: mod_htaccess - using new of configure new SSL"]; essl_mod_htaccess(suite) -> @@ -1260,13 +1115,6 @@ ssl_mod_htaccess(Tag, Config) -> %%------------------------------------------------------------------------- -pssl_mod_cgi(doc) -> - ["Module test: mod_cgi - old SSL config"]; -pssl_mod_cgi(suite) -> - []; -pssl_mod_cgi(Config) when is_list(Config) -> - ssl_mod_cgi(ssl, Config). - essl_mod_cgi(doc) -> ["Module test: mod_cgi - using new of configure new SSL"]; essl_mod_cgi(suite) -> @@ -1275,27 +1123,15 @@ essl_mod_cgi(Config) when is_list(Config) -> ssl_mod_cgi(essl, Config). ssl_mod_cgi(Tag, Config) -> - case test_server:os_type() of - vxworks -> - {skip, cgi_not_supported_on_vxwoks}; - _ -> - httpd_mod:cgi(Tag, - ?SSL_PORT, - ?config(host, Config), - ?config(node, Config)), - ok - end. + httpd_mod:cgi(Tag, + ?SSL_PORT, + ?config(host, Config), + ?config(node, Config)), + ok. %%------------------------------------------------------------------------- -pssl_mod_esi(doc) -> - ["Module test: mod_esi - old SSL config"]; -pssl_mod_esi(suite) -> - []; -pssl_mod_esi(Config) when is_list(Config) -> - ssl_mod_esi(ssl, Config). - essl_mod_esi(doc) -> ["Module test: mod_esi - using new of configure new SSL"]; essl_mod_esi(suite) -> @@ -1313,13 +1149,6 @@ ssl_mod_esi(Tag, Config) -> %%------------------------------------------------------------------------- -pssl_mod_get(doc) -> - ["Module test: mod_get - old SSL config"]; -pssl_mod_get(suite) -> - []; -pssl_mod_get(Config) when is_list(Config) -> - ssl_mod_get(ssl, Config). - essl_mod_get(doc) -> ["Module test: mod_get - using new of configure new SSL"]; essl_mod_get(suite) -> @@ -1337,13 +1166,6 @@ ssl_mod_get(Tag, Config) -> %%------------------------------------------------------------------------- -pssl_mod_head(doc) -> - ["Module test: mod_head - old SSL config"]; -pssl_mod_head(suite) -> - []; -pssl_mod_head(Config) when is_list(Config) -> - ssl_mod_head(ssl, Config). - essl_mod_head(doc) -> ["Module test: mod_head - using new of configure new SSL"]; essl_mod_head(suite) -> @@ -1361,13 +1183,6 @@ ssl_mod_head(Tag, Config) -> %%------------------------------------------------------------------------- -pssl_mod_all(doc) -> - ["All modules test - old SSL config"]; -pssl_mod_all(suite) -> - []; -pssl_mod_all(Config) when is_list(Config) -> - ssl_mod_all(ssl, Config). - essl_mod_all(doc) -> ["All modules test - using new of configure new SSL"]; essl_mod_all(suite) -> @@ -1385,13 +1200,6 @@ ssl_mod_all(Tag, Config) -> %%------------------------------------------------------------------------- -pssl_load_light(doc) -> - ["Test light load - old SSL config"]; -pssl_load_light(suite) -> - []; -pssl_load_light(Config) when is_list(Config) -> - ssl_load_light(ssl, Config). - essl_load_light(doc) -> ["Test light load - using new of configure new SSL"]; essl_load_light(suite) -> @@ -1410,13 +1218,6 @@ ssl_load_light(Tag, Config) -> %%------------------------------------------------------------------------- -pssl_load_medium(doc) -> - ["Test medium load - old SSL config"]; -pssl_load_medium(suite) -> - []; -pssl_load_medium(Config) when is_list(Config) -> - ssl_load_medium(ssl, Config). - essl_load_medium(doc) -> ["Test medium load - using new of configure new SSL"]; essl_load_medium(suite) -> @@ -1441,13 +1242,6 @@ ssl_load_medium(Tag, Config) -> %%------------------------------------------------------------------------- -pssl_load_heavy(doc) -> - ["Test heavy load - old SSL config"]; -pssl_load_heavy(suite) -> - []; -pssl_load_heavy(Config) when is_list(Config) -> - ssl_load_heavy(ssl, Config). - essl_load_heavy(doc) -> ["Test heavy load - using new of configure new SSL"]; essl_load_heavy(suite) -> @@ -1472,12 +1266,6 @@ ssl_load_heavy(Tag, Config) -> %%------------------------------------------------------------------------- -pssl_dos_hostname(doc) -> - ["Denial Of Service (DOS) attack test case - old SSL config"]; -pssl_dos_hostname(suite) -> - []; -pssl_dos_hostname(Config) when is_list(Config) -> - ssl_dos_hostname(ssl, Config). essl_dos_hostname(doc) -> ["Denial Of Service (DOS) attack test case - using new of configure new SSL"]; @@ -1497,12 +1285,6 @@ ssl_dos_hostname(Tag, Config) -> %%------------------------------------------------------------------------- -pssl_time_test(doc) -> - ["old SSL config"]; -pssl_time_test(suite) -> - []; -pssl_time_test(Config) when is_list(Config) -> - ssl_time_test(ssl, Config). essl_time_test(doc) -> ["using new of configure new SSL"]; @@ -1535,13 +1317,6 @@ ssl_time_test(Tag, Config) when is_list(Config) -> %%------------------------------------------------------------------------- -pssl_block_503(doc) -> - ["Check that you will receive status code 503 when the server" - " is blocked and 200 when its not blocked - old SSL config."]; -pssl_block_503(suite) -> - []; -pssl_block_503(Config) when is_list(Config) -> - ssl_block_503(ssl, Config). essl_block_503(doc) -> ["Check that you will receive status code 503 when the server" @@ -1561,15 +1336,6 @@ ssl_block_503(Tag, Config) -> %%------------------------------------------------------------------------- -pssl_block_disturbing_idle(doc) -> - ["Check that you can block/unblock an idle server. The strategy " - "distribing does not really make a difference in this case." - "Old SSL config"]; -pssl_block_disturbing_idle(suite) -> - []; -pssl_block_disturbing_idle(Config) when is_list(Config) -> - ssl_block_disturbing_idle(ssl, Config). - essl_block_disturbing_idle(doc) -> ["Check that you can block/unblock an idle server. The strategy " "distribing does not really make a difference in this case." @@ -1589,15 +1355,6 @@ ssl_block_disturbing_idle(Tag, Config) -> %%------------------------------------------------------------------------- -pssl_block_non_disturbing_idle(doc) -> - ["Check that you can block/unblock an idle server. The strategy " - "non distribing does not really make a difference in this case." - "Old SSL config"]; -pssl_block_non_disturbing_idle(suite) -> - []; -pssl_block_non_disturbing_idle(Config) when is_list(Config) -> - ssl_block_non_disturbing_idle(ssl, Config). - essl_block_non_disturbing_idle(doc) -> ["Check that you can block/unblock an idle server. The strategy " "non distribing does not really make a difference in this case." @@ -1617,15 +1374,6 @@ ssl_block_non_disturbing_idle(Tag, Config) -> %%------------------------------------------------------------------------- -pssl_block_disturbing_active(doc) -> - ["Check that you can block/unblock an active server. The strategy " - "distribing means ongoing requests should be terminated." - "Old SSL config"]; -pssl_block_disturbing_active(suite) -> - []; -pssl_block_disturbing_active(Config) when is_list(Config) -> - ssl_block_disturbing_active(ssl, Config). - essl_block_disturbing_active(doc) -> ["Check that you can block/unblock an active server. The strategy " "distribing means ongoing requests should be terminated." @@ -1645,15 +1393,6 @@ ssl_block_disturbing_active(Tag, Config) -> %%------------------------------------------------------------------------- -pssl_block_non_disturbing_active(doc) -> - ["Check that you can block/unblock an idle server. The strategy " - "non distribing means the ongoing requests should be compleated." - "Old SSL config"]; -pssl_block_non_disturbing_active(suite) -> - []; -pssl_block_non_disturbing_active(Config) when is_list(Config) -> - ssl_block_non_disturbing_active(ssl, Config). - essl_block_non_disturbing_active(doc) -> ["Check that you can block/unblock an idle server. The strategy " "non distribing means the ongoing requests should be compleated." @@ -1673,17 +1412,6 @@ ssl_block_non_disturbing_active(Tag, Config) -> %%------------------------------------------------------------------------- -pssl_block_disturbing_active_timeout_not_released(doc) -> - ["Check that you can block an active server. The strategy " - "distribing means ongoing requests should be compleated" - "if the timeout does not occur." - "Old SSL config"]; -pssl_block_disturbing_active_timeout_not_released(suite) -> - []; -pssl_block_disturbing_active_timeout_not_released(Config) - when is_list(Config) -> - ssl_block_disturbing_active_timeout_not_released(ssl, Config). - essl_block_disturbing_active_timeout_not_released(doc) -> ["Check that you can block an active server. The strategy " "distribing means ongoing requests should be compleated" @@ -1706,17 +1434,6 @@ ssl_block_disturbing_active_timeout_not_released(Tag, Config) -> %%------------------------------------------------------------------------- -pssl_block_disturbing_active_timeout_released(doc) -> - ["Check that you can block an active server. The strategy " - "distribing means ongoing requests should be terminated when" - "the timeout occurs." - "Old SSL config"]; -pssl_block_disturbing_active_timeout_released(suite) -> - []; -pssl_block_disturbing_active_timeout_released(Config) - when is_list(Config) -> - ssl_block_disturbing_active_timeout_released(ssl, Config). - essl_block_disturbing_active_timeout_released(doc) -> ["Check that you can block an active server. The strategy " "distribing means ongoing requests should be terminated when" @@ -1741,16 +1458,6 @@ ssl_block_disturbing_active_timeout_released(Tag, Config) -> %%------------------------------------------------------------------------- -pssl_block_non_disturbing_active_timeout_not_released(doc) -> - ["Check that you can block an active server. The strategy " - "non non distribing means ongoing requests should be completed." - "Old SSL config"]; -pssl_block_non_disturbing_active_timeout_not_released(suite) -> - []; -pssl_block_non_disturbing_active_timeout_not_released(Config) - when is_list(Config) -> - ssl_block_non_disturbing_active_timeout_not_released(ssl, Config). - essl_block_non_disturbing_active_timeout_not_released(doc) -> ["Check that you can block an active server. The strategy " "non non distribing means ongoing requests should be completed." @@ -1774,16 +1481,6 @@ ssl_block_non_disturbing_active_timeout_not_released(Tag, Config) -> %%------------------------------------------------------------------------- -pssl_block_non_disturbing_active_timeout_released(doc) -> - ["Check that you can block an active server. The strategy " - "non distribing means ongoing requests should be completed. " - "When the timeout occurs the block operation sohould be canceled." - "Old SSL config"]; -pssl_block_non_disturbing_active_timeout_released(suite) -> - []; -pssl_block_non_disturbing_active_timeout_released(Config) - when is_list(Config) -> - ssl_block_non_disturbing_active_timeout_released(ssl, Config). essl_block_non_disturbing_active_timeout_released(doc) -> ["Check that you can block an active server. The strategy " @@ -1811,12 +1508,6 @@ ssl_block_non_disturbing_active_timeout_released(Tag, Config) %%------------------------------------------------------------------------- -pssl_block_disturbing_blocker_dies(doc) -> - ["old SSL config"]; -pssl_block_disturbing_blocker_dies(suite) -> - []; -pssl_block_disturbing_blocker_dies(Config) when is_list(Config) -> - ssl_block_disturbing_blocker_dies(ssl, Config). essl_block_disturbing_blocker_dies(doc) -> ["using new of configure new SSL"]; @@ -1835,13 +1526,6 @@ ssl_block_disturbing_blocker_dies(Tag, Config) -> %%------------------------------------------------------------------------- -pssl_block_non_disturbing_blocker_dies(doc) -> - ["old SSL config"]; -pssl_block_non_disturbing_blocker_dies(suite) -> - []; -pssl_block_non_disturbing_blocker_dies(Config) when is_list(Config) -> - ssl_block_non_disturbing_blocker_dies(ssl, Config). - essl_block_non_disturbing_blocker_dies(doc) -> ["using new of configure new SSL"]; essl_block_non_disturbing_blocker_dies(suite) -> @@ -1859,12 +1543,6 @@ ssl_block_non_disturbing_blocker_dies(Tag, Config) -> %%------------------------------------------------------------------------- -pssl_restart_no_block(doc) -> - ["old SSL config"]; -pssl_restart_no_block(suite) -> - []; -pssl_restart_no_block(Config) when is_list(Config) -> - ssl_restart_no_block(ssl, Config). essl_restart_no_block(doc) -> ["using new of configure new SSL"]; @@ -1883,12 +1561,6 @@ ssl_restart_no_block(Tag, Config) -> %%------------------------------------------------------------------------- -pssl_restart_disturbing_block(doc) -> - ["old SSL config"]; -pssl_restart_disturbing_block(suite) -> - []; -pssl_restart_disturbing_block(Config) when is_list(Config) -> - ssl_restart_disturbing_block(ssl, Config). essl_restart_disturbing_block(doc) -> ["using new of configure new SSL"]; @@ -1940,12 +1612,6 @@ ssl_restart_disturbing_block(Tag, Config) -> %%------------------------------------------------------------------------- -pssl_restart_non_disturbing_block(doc) -> - ["old SSL config"]; -pssl_restart_non_disturbing_block(suite) -> - []; -pssl_restart_non_disturbing_block(Config) when is_list(Config) -> - ssl_restart_non_disturbing_block(ssl, Config). essl_restart_non_disturbing_block(doc) -> ["using new of configure new SSL"]; @@ -2338,7 +2004,7 @@ create_config(Config, Access, FileName) -> "~n Type: ~p" "~n Port: ~p" "~n Host: ~p" - "~n", [ServerRoot, TcTopDir, Port, Type, Host]), + "~n", [ServerRoot, TcTopDir, Type, Port, Host]), SSL = if @@ -2698,11 +2364,6 @@ dos_hostname_request(Host) -> get_nof_clients(Mode, Load) -> get_nof_clients(test_server:os_type(), Mode, Load). -get_nof_clients(vxworks, _, light) -> 1; -get_nof_clients(vxworks, ip_comm, medium) -> 3; -get_nof_clients(vxworks, ssl, medium) -> 3; -get_nof_clients(vxworks, ip_comm, heavy) -> 5; -get_nof_clients(vxworks, ssl, heavy) -> 5; get_nof_clients(_, ip_comm, light) -> 5; get_nof_clients(_, ssl, light) -> 2; get_nof_clients(_, ip_comm, medium) -> 10; @@ -2781,3 +2442,4 @@ tsp(F, A) -> tsf(Reason) -> inets_test_lib:tsf(Reason). + diff --git a/lib/inets/test/httpd_mod.erl b/lib/inets/test/httpd_mod.erl index 387263ce58..df4ed6b179 100644 --- a/lib/inets/test/httpd_mod.erl +++ b/lib/inets/test/httpd_mod.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2012. All Rights Reserved. +%% Copyright Ericsson AB 2005-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -40,7 +40,6 @@ %%------------------------------------------------------------------------- alias(Type, Port, Host, Node) -> %% This is very crude, but... - tsp("alias -> Has IPv6 support: ~p", [inets_test_lib:has_ipv6_support()]), Opts = [], ok = httpd_test_lib:verify_request(Type, Host, Port, Opts, Node, "GET /pics/icon.sheet.gif " @@ -85,16 +84,7 @@ actions(Type, Port, Host, Node) -> %%------------------------------------------------------------------------- security(ServerRoot, Type, Port, Host, Node) -> - tsp("security -> " - "entry with" - "~n ServerRoot: ~p" - "~n Type: ~p" - "~n Port: ~p" - "~n Host: ~p" - "~n Node: ~p", [ServerRoot, Type, Port, Host, Node]), - - tsp("security -> " - "register - receive security events"), + global:register_name(mod_security_test, self()), % Receive events tsp("security -> " @@ -333,13 +323,7 @@ security(ServerRoot, Type, Port, Host, Node) -> %%------------------------------------------------------------------------- auth(Type, Port, Host, Node) -> - tsp("auth -> " - "entry with" - "~n Type: ~p" - "~n Port: ~p" - "~n Host: ~p" - "~n Node: ~p", [Type, Port, Host, Node]), - + %% Authentication required! ok = httpd_test_lib:verify_request(Type,Host,Port,Node, "GET /open/ HTTP/1.0\r\n\r\n", @@ -750,11 +734,6 @@ htaccess(Type, Port, Host, Node) -> {header, "WWW-Authenticate"}]). %%-------------------------------------------------------------------- cgi(Type, Port, Host, Node) -> -%% tsp("cgi -> entry with" -%% "~n Type: ~p" -%% "~n Port: ~p" -%% "~n Host: ~p" -%% "~n Node: ~p", []), {Script, Script2, Script3} = case test_server:os_type() of {win32, _} -> diff --git a/lib/inets/test/httpd_test_lib.erl b/lib/inets/test/httpd_test_lib.erl index 4b33350cf2..13584c50f6 100644 --- a/lib/inets/test/httpd_test_lib.erl +++ b/lib/inets/test/httpd_test_lib.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2001-2012. All Rights Reserved. +%% Copyright Ericsson AB 2001-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -91,7 +91,7 @@ verify_request(SocketType, Host, Port, Node, RequestStr, Options, TimeOut) when (is_integer(TimeOut) orelse (TimeOut =:= infinity)) -> verify_request(SocketType, Host, Port, [], Node, RequestStr, Options, TimeOut). -verify_request(SocketType, Host, Port, TranspOpts, Node, RequestStr, Options, TimeOut) -> +verify_request(SocketType, Host, Port, TranspOpts0, Node, RequestStr, Options, TimeOut) -> tsp("verify_request -> entry with" "~n SocketType: ~p" "~n Host: ~p" @@ -100,7 +100,17 @@ verify_request(SocketType, Host, Port, TranspOpts, Node, RequestStr, Options, Ti "~n Node: ~p" "~n Options: ~p" "~n TimeOut: ~p", - [SocketType, Host, Port, TranspOpts, Node, Options, TimeOut]), + [SocketType, Host, Port, TranspOpts0, Node, Options, TimeOut]), + + %% For now, until we modernize the httpd tests + TranspOpts = + case lists:member(inet6, TranspOpts0) of + true -> + TranspOpts0; + false -> + [inet | TranspOpts0] + end, + try inets_test_lib:connect_bin(SocketType, Host, Port, TranspOpts) of {ok, Socket} -> tsp("verify_request -> connected - now send message"), @@ -293,8 +303,7 @@ validate(RequestStr, #state{status_line = {Version, StatusCode, _}, list_to_integer(Headers#http_response_h.'content-length'), Body). - -%%-------------------------------------------------------------------- +%-------------------------------------------------------------------- %% Internal functions %%------------------------------------------------------------------ check_version(Version, Options) -> diff --git a/lib/inets/test/inets.spec.vxworks b/lib/inets/test/inets.spec.vxworks deleted file mode 100644 index 6886299226..0000000000 --- a/lib/inets/test/inets.spec.vxworks +++ /dev/null @@ -1,5 +0,0 @@ -{topcase, {dir, "../inets_test"}}. -{skip, {inets_SUITE, ip_mod_cgi, "Requires processes"}}. -{skip, {inets_SUITE, ip_mod_all_modules, "Requires processes"}}. -{skip, {inets_SUITE, ssl, "Requires SSL"}}. - diff --git a/lib/inets/test/inets_SUITE.erl b/lib/inets/test/inets_SUITE.erl index 6fa0f44d77..6510c70d08 100644 --- a/lib/inets/test/inets_SUITE.erl +++ b/lib/inets/test/inets_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1997-2011. All Rights Reserved. +%% Copyright Ericsson AB 1997-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -363,8 +363,6 @@ start_ftpc(suite) -> []; start_ftpc(Config) when is_list(Config) -> process_flag(trap_exit, true), - inets:disable_trace(), - inets:enable_trace(max, io, ftpc), ok = inets:start(), try begin @@ -393,16 +391,13 @@ start_ftpc(Config) when is_list(Config) -> tsf(stand_alone_not_shutdown) end, ok = inets:stop(), - inets:disable_trace(), ok; _ -> - inets:disable_trace(), {skip, "Unable to reach selected FTP server " ++ FtpdHost} end end catch throw:{error, not_found} -> - inets:disable_trace(), {skip, "No available FTP servers"} end. @@ -462,8 +457,6 @@ httpd_reload(Config) when is_list(Config) -> {document_root, PrivDir}, {bind_address, "localhost"}], - inets:enable_trace(max, io), - i("httpd_reload -> start inets"), ok = inets:start(), diff --git a/lib/inets/test/inets_appup_test.erl b/lib/inets/test/inets_appup_test.erl index 7ed237243e..d563b52ae7 100644 --- a/lib/inets/test/inets_appup_test.erl +++ b/lib/inets/test/inets_appup_test.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2002-2010. All Rights Reserved. +%% Copyright Ericsson AB 2002-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -257,6 +257,21 @@ check_instruction(_, Instr, _AllInstr, _Modules) -> check_version(V) when is_list(V) -> ok; +check_version(REBin) when is_binary(REBin) -> + try + begin + RE = binary_to_list(REBin), + case re:compile(RE) of + {ok, _} -> + ok; + {error, _} -> + error({bad_version, REBin}) + end + end + catch + _T:_E -> + error({bad_version, REBin}) + end; check_version(V) -> error({bad_version, V}). diff --git a/lib/inets/test/inets_sup_SUITE.erl b/lib/inets/test/inets_sup_SUITE.erl index 1d262a2739..0ac940fd3e 100644 --- a/lib/inets/test/inets_sup_SUITE.erl +++ b/lib/inets/test/inets_sup_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2011. All Rights Reserved. +%% Copyright Ericsson AB 2004-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -226,8 +226,6 @@ ftpc_worker(doc) -> ftpc_worker(suite) -> []; ftpc_worker(Config) when is_list(Config) -> - inets:disable_trace(), - inets:enable_trace(max, io, ftpc), [] = supervisor:which_children(ftp_sup), try begin @@ -239,20 +237,16 @@ ftpc_worker(Config) when is_list(Config) -> inets:stop(ftpc, Pid), test_server:sleep(5000), [] = supervisor:which_children(ftp_sup), - inets:disable_trace(), ok; Children -> - inets:disable_trace(), exit({unexpected_children, Children}) end; _ -> - inets:disable_trace(), {skip, "Unable to reach test FTP server"} end end catch throw:{error, not_found} -> - inets:disable_trace(), {skip, "No available FTP servers"} end. diff --git a/lib/inets/test/inets_test_lib.erl b/lib/inets/test/inets_test_lib.erl index 0f8671b682..6ccc7b0da1 100644 --- a/lib/inets/test/inets_test_lib.erl +++ b/lib/inets/test/inets_test_lib.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2001-2012. All Rights Reserved. +%% Copyright Ericsson AB 2001-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -22,26 +22,8 @@ -include("inets_test_lib.hrl"). -include_lib("inets/src/http_lib/http_internal.hrl"). -%% Various small utility functions --export([start_http_server/1, start_http_server/2]). --export([start_http_server_ssl/1, start_http_server_ssl/2]). --export([hostname/0]). --export([connect_bin/3, connect_bin/4, - connect_byte/3, connect_byte/4, - send/3, close/2]). --export([copy_file/3, copy_files/2, copy_dirs/2, del_dirs/1]). --export([info/4, log/4, debug/4, print/4]). --export([timestamp/0, formated_timestamp/0]). --export([tsp/1, tsp/2, tsf/1, tss/1]). --export([check_body/1]). --export([millis/0, millis_diff/2, hours/1, minutes/1, seconds/1, sleep/1]). --export([oscmd/1, has_ipv6_support/0, has_ipv6_support/1, print_system_info/1]). --export([run_on_os/2, run_on_windows/1]). --export([ensure_started/1]). --export([non_pc_tc_maybe_skip/4, os_based_skip/1, skip/3, fail/3]). --export([flush/0]). --export([start_node/1, stop_node/1]). - +%% Note: This directive should only be used in test suites. +-compile(export_all). %% -- Misc os command and stuff @@ -474,7 +456,7 @@ connect_bin(ssl, Host, Port, Opts0) -> Opts = [binary, {packet,0} | Opts0], connect(ssl, Host, Port, Opts); connect_bin(essl, Host, Port, Opts0) -> - Opts = [{ssl_imp, new}, binary, {packet,0}, {reuseaddr, true} | Opts0], + Opts = [{ssl_imp, new}, binary, {packet,0}| Opts0], connect(ssl, Host, Port, Opts); connect_bin(ip_comm, Host, Port, Opts0) -> Opts = [binary, {packet, 0} | Opts0], @@ -494,74 +476,10 @@ connect_byte(ip_comm, Host, Port, Opts0) -> Opts = [{packet,0} | Opts0], connect(ip_comm, Host, Port, Opts). - -%% This always falls back on IPV4, but tries IPV6 first. -connect(Proto, Host, Port, Opts0) -> - Opts = Opts0 -- [inet, inet6], - connect(Proto, Host, Port, Opts ++ [inet6], inet6). - -connect(ssl, Host, Port, Opts, Type) -> - tsp("connect(ssl) -> entry with" - "~n Host: ~p" - "~n Port: ~p" - "~n Opts: ~p" - "~n Type: ~p", [Host, Port, Opts, Type]), - ssl:start(), - %% We ignore this option for ssl... - %% ...maybe we should really treat this in the same way as ip_comm... - case ssl:connect(Host, Port, Opts) of - {ok, Socket} -> - {ok, Socket}; - {error, Reason} when Type =:= inet6 -> - tsp("connect(ssl) -> failed connecting with inet6: " - "~n Reason: ~p" - "~n trying inet", [Reason]), - connect(ssl, Host, Port, Opts -- [inet6], inet); - {error, Reason} -> - tsp("connect(ssl) -> failed connecting: " - "~n Reason: ~p", [Reason]), - {error, Reason}; - Error -> - Error - end; -connect(ip_comm, Host, Port, Opts, Type) -> - tsp("connect(ip_comm) -> entry with" - "~n Host: ~p" - "~n Port: ~p" - "~n Opts: ~p" - "~n Type: ~p", [Host, Port, Opts, Type]), - - case gen_tcp:connect(Host, Port, Opts, timer:seconds(10)) of - {ok, Socket} -> - tsp("connect success"), - {ok, Socket}; - - {error, Reason} when ((Type =:= inet6) andalso - ((Reason =:= timeout) orelse - (Reason =:= nxdomain) orelse - (Reason =:= eafnosupport) orelse - (Reason =:= econnreset) orelse - (Reason =:= enetunreach) orelse - (Reason =:= econnrefused) orelse - (Reason =:= ehostunreach))) -> - tsp("connect(ip_comm) -> Connect error: " - "~n Reason: ~p" - "~n Type: ~p" - "~n Opts: ~p", [Reason, Type, Opts]), - connect(ip_comm, Host, Port, Opts -- [inet6], inet); - - Error -> - tsp("connect(ip_comm) -> Fatal connect error: " - "~n Error: ~p" - "~nwhen" - "~n Host: ~p" - "~n Port: ~p" - "~n Opts: ~p" - "~n Type: ~p" - "~n", [Error, Host, Port, Opts, Type]), - Error - end. - +connect(ip_comm, Host, Port, Opts) -> + gen_tcp:connect(Host, Port, Opts); +connect(ssl, Host, Port, Opts) -> + ssl:connect(Host, Port, Opts). send(ssl, Socket, Data) -> ssl:send(Socket, Data); @@ -651,3 +569,13 @@ format_timestamp({_N1, _N2, N3} = Now) -> [YYYY,MM,DD,Hour,Min,Sec,round(N3/1000)]), lists:flatten(FormatDate). +start_apps(Apps) -> + lists:foreach(fun(App) -> + application:stop(App), + application:start(App) + end, Apps). +stop_apps(Apps) -> + lists:foreach(fun(App) -> + application:stop(App) + end, Apps). + diff --git a/lib/inets/test/rules.mk b/lib/inets/test/rules.mk index 047c03b267..c4a62a87ed 100644 --- a/lib/inets/test/rules.mk +++ b/lib/inets/test/rules.mk @@ -17,17 +17,12 @@ DEFAULT_TARGETS = opt debug instr release release_docs clean docs # Erlang language section # ---------------------------------------------------- EMULATOR = beam -ifeq ($(findstring vxworks,$(TARGET)),vxworks) -# VxWorks object files should be compressed. -# Other object files should have debug_info. -ERL_COMPILE_FLAGS += +compressed -else + ifdef BOOTSTRAP ERL_COMPILE_FLAGS += +slim else ERL_COMPILE_FLAGS += +debug_info endif -endif ERLC_WFLAGS = -W ERLC = erlc $(ERLC_WFLAGS) $(ERLC_FLAGS) ERL.beam = erl.beam -boot start_clean diff --git a/lib/inets/test/uri_SUITE.erl b/lib/inets/test/uri_SUITE.erl new file mode 100644 index 0000000000..9ba09e1474 --- /dev/null +++ b/lib/inets/test/uri_SUITE.erl @@ -0,0 +1,159 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2004-2013. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%% + +%% +%% ct:run("../inets_test", uri_SUITE). +%% + +-module(uri_SUITE). + +-include_lib("common_test/include/ct.hrl"). +-include("inets_test_lib.hrl"). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-define(GOOGLE, "www.google.com"). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- +suite() -> + [{ct_hooks,[ts_install_cth]}]. + +all() -> + [ + ipv4, + ipv6, + host, + userinfo, + scheme, + queries, + escaped, + hexed_query + ]. + +%%-------------------------------------------------------------------- + +init_per_suite(Config) -> + Config. +end_per_suite(_Config) -> + ok. + +%%-------------------------------------------------------------------- + +init_per_testcase(_Case, Config) -> + Config. +end_per_testcase(_Case, _Config) -> + ok. + +%%------------------------------------------------------------------------- +%% Test cases starts here. +%%------------------------------------------------------------------------- + +ipv4(Config) when is_list(Config) -> + {ok, {http,[],"127.0.0.1",80,"/foobar.html",[]}} = + http_uri:parse("http://127.0.0.1/foobar.html"). + +ipv6(Config) when is_list(Config) -> + {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"). + +host(Config) when is_list(Config) -> + {ok, {http,[],"localhost",8888,"/foobar.html",[]}} = + http_uri:parse("http://localhost:8888/foobar.html"). + +userinfo(Config) when is_list(Config) -> + {ok, {http,"nisse:foobar","localhost",8888,"/foobar.html",[]}} = + http_uri:parse("http://nisse:foobar@localhost:8888/foobar.html"). + +scheme(Config) when is_list(Config) -> + {error, no_scheme} = http_uri:parse("localhost/foobar.html"), + {error, {malformed_url, _, _}} = + http_uri:parse("localhost:8888/foobar.html"). + +queries(Config) when is_list(Config) -> + {ok, {http,[],"localhost",8888,"/foobar.html","?foo=bar&foobar=42"}} = + http_uri:parse("http://localhost:8888/foobar.html?foo=bar&foobar=42"). + +escaped(Config) when is_list(Config) -> + {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"). + +hexed_query(doc) -> + [{doc, "Solves OTP-6191"}]; +hexed_query(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). + + +%%-------------------------------------------------------------------- +%% Internal Functions ------------------------------------------------ +%%-------------------------------------------------------------------- + + +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. diff --git a/lib/inets/vsn.mk b/lib/inets/vsn.mk index 4aa5de8f1a..3f464c8684 100644 --- a/lib/inets/vsn.mk +++ b/lib/inets/vsn.mk @@ -2,7 +2,7 @@ # %CopyrightBegin% # -# Copyright Ericsson AB 2001-2012. All Rights Reserved. +# Copyright Ericsson AB 2001-2013. All Rights Reserved. # # The contents of this file are subject to the Erlang Public License, # Version 1.1, (the "License"); you may not use this file except in @@ -18,7 +18,7 @@ # %CopyrightEnd% APPLICATION = inets -INETS_VSN = 5.9.2.2 +INETS_VSN = 5.9.5 PRE_VSN = APP_VSN = "$(APPLICATION)-$(INETS_VSN)$(PRE_VSN)" |