diff options
Diffstat (limited to 'lib/inets')
33 files changed, 1101 insertions, 852 deletions
diff --git a/lib/inets/doc/src/http_server.xml b/lib/inets/doc/src/http_server.xml index 4b6d64fc8f..aeda961714 100644 --- a/lib/inets/doc/src/http_server.xml +++ b/lib/inets/doc/src/http_server.xml @@ -4,7 +4,7 @@ <chapter> <header> <copyright> - <year>2004</year><year>2013</year> + <year>2004</year><year>2015</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -21,18 +21,8 @@ limitations under the License. </legalnotice> - <title>HTTP server</title> - <prepared>Ingela Anderton Andin</prepared> - <responsible></responsible> - <docno></docno> - <approved></approved> - <checked></checked> - <date></date> - <rev></rev> <file>http_server.xml</file> - - <marker id="intro"></marker> </header> <section> @@ -65,12 +55,9 @@ <p>As of <c>Inets</c> 5.0 the HTTP server is an easy to start/stop and customize web server providing the most basic - web server functionality. Depending on your needs, there - are also other Erlang-based web servers that can be of interest - such as <url href=" http://yaws.hyber.org ">Yaws</url>, which, - for example, has its own - markup support to generate HTML and supports certain buzzword - technologies, such as SOAP.</p> + web server functionality. Inets is designed for embedded systems + and if you want a full-fledged web server there are exists other + erlang open source alternatives.</p> <p>Almost all server functionality has been implemented using an especially crafted server API, which is described in the Erlang Web @@ -356,9 +343,9 @@ UserName:Password</pre> </taglist> <section> - <title>CGI Version 1.1, - <url href="http://www.ietf.org/rfc/rfc3875.txt">RFC 3875</url></title> - <p>The module <c>mod_cgi</c> enables execution of CGI scripts + <title>CGI Version 1.1, RFC 3875</title> + <p>The module <c>mod_cgi</c> enables execution of + <url href="http://www.ietf.org/rfc/rfc3875.txt">CGI scripts</url> on the server. A file matching the definition of a ScriptAlias config directive is treated as a CGI script. A CGI script is executed by the server and its output is returned to @@ -541,7 +528,7 @@ http://your.server.org/eval?httpd_example:print(atom_to_list(apply(erlang,halt,[ <title>mod_action - Filetype/Method-Based Script Execution</title> <p>This module runs CGI scripts whenever a file of a certain type or HTTP method (see - <url href="http://tools.ietf.org/html/rfc1945">RFC 1945</url>RFC 1945) + <url href="http://tools.ietf.org/html/rfc1945">RFC 1945</url>) is requested. </p> <p>Uses the following Erlang Web Server API interaction data: @@ -568,7 +555,7 @@ http://your.server.org/eval?httpd_example:print(atom_to_list(apply(erlang,halt,[ <taglist> <tag><c>{real_name, PathData}</c></tag> <item><c>PathData</c> is the argument used for API function - <seealso marker="mod_alias:path/3">mod_alias:path/3</seealso>.</item> + <seealso marker="mod_alias#path/3">mod_alias:path/3</seealso>.</item> </taglist> </section> diff --git a/lib/inets/doc/src/http_uri.xml b/lib/inets/doc/src/http_uri.xml index 47c40da96a..8e0301c520 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>2013</year> + <year>2012</year><year>2015</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -117,7 +117,8 @@ <v>Options = [Option]</v> <v>Option = {ipv6_host_with_brackets, boolean()} | {scheme_defaults, scheme_defaults()} | - {fragment, boolean()}]</v> + {fragment, boolean()} | + {schema_validation_fun, fun()}]</v> <v>Result = {Scheme, UserInfo, Host, Port, Path, Query} | {Scheme, UserInfo, Host, Port, Path, Query, Fragment}</v> <v>UserInfo = user_info()</v> @@ -141,6 +142,16 @@ <p>If the fragment option is <c>true</c>, the URI fragment is returned as part of the parsing result, otherwise it is ignored.</p> + <p>Scheme validation fun is to be defined as follows:</p> + + <code> +fun(SchemeStr :: string()) -> + valid | {error, Reason :: term()}. + </code> + + <p>It is called before scheme string gets converted into scheme atom and + thus possible atom leak could be prevented</p> + <marker id="encode"></marker> </desc> </func> diff --git a/lib/inets/doc/src/httpc.xml b/lib/inets/doc/src/httpc.xml index 31e44f405c..ca9b268a03 100644 --- a/lib/inets/doc/src/httpc.xml +++ b/lib/inets/doc/src/httpc.xml @@ -4,7 +4,7 @@ <erlref> <header> <copyright> - <year>2004</year><year>2013</year> + <year>2004</year><year>2015</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -554,8 +554,8 @@ <v>IpDesc = string()</v> <d>Example: "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> - <d><c>proxy</c> defaults to <c>{undefined, []}</c>, + "66.35.250.150" or "[2010:836B:4179::836B:4179]" (a complete IP address). + <c>proxy</c> defaults to <c>{undefined, []}</c>, that is, no proxy is configured and <c>https_proxy</c> defaults to the value of <c>proxy</c>.</d> <v>MaxSessions = integer()</v> @@ -597,8 +597,8 @@ for details.</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 + client. + These are the default values when a new request handler is started (for the initial connect). They are passed directly to the underlying transport (<c>gen_tcp</c> or <c>SSL</c>) <em>without</em> verification.</d> diff --git a/lib/inets/doc/src/httpd.xml b/lib/inets/doc/src/httpd.xml index 2a4aea41c2..62b92b8356 100644 --- a/lib/inets/doc/src/httpd.xml +++ b/lib/inets/doc/src/httpd.xml @@ -30,13 +30,14 @@ <file>httpd.sgml</file> </header> <module>httpd</module> - <modulesummary>An implementation of an HTTP - 1.1 compliant web server, as defined in <url href="http://www.ietf.org/rfc/rfc2616.txt">RFC 2616</url> + <modulesummary> + HTTP server API </modulesummary> <description> - <p>This module provides the HTTP server start options, some administrative - functions, and specifies the Erlang web server callback - API.</p> + <p>An implementation of an HTTP 1.1 compliant web server, as defined in + <url href="http://www.ietf.org/rfc/rfc2616.txt">RFC 2616</url>. + Provides web server start options, administrative functions, and + an Erlang callback API.</p> </description> <section> @@ -78,8 +79,7 @@ list.</p> <taglist> - <marker id="prop_proplist_file"></marker> - <tag>{proplist_file, path()}</tag> + <tag><marker id="prop_proplist_file"></marker>{proplist_file, path()}</tag> <item> <p>If this property is defined, <c>Inets</c> expects to find all other properties defined in this file. The @@ -87,8 +87,7 @@ properties.</p> </item> - <marker id="prop_file"></marker> - <tag>{file, path()}</tag> + <tag><marker id="prop_file"></marker>{file, path()}</tag> <item> <p>If this property is defined, <c>Inets</c> expects to find all other properties defined in this file, which uses Apache-like @@ -121,8 +120,7 @@ <marker id="props_mand"></marker> <p><em>Mandatory Properties</em></p> <taglist> - <marker id="prop_port"></marker> - <tag>{port, integer()} </tag> + <tag><marker id="prop_port"></marker>{port, integer()} </tag> <item> <p>The port that the HTTP server listen to. If zero is specified as port, an arbitrary available port @@ -130,22 +128,19 @@ determine which port was picked.</p> </item> - <marker id="prop_server_name"></marker> - <tag>{server_name, string()}</tag> + <tag><marker id="prop_server_name"></marker>{server_name, string()}</tag> <item> <p>The name of your server, normally a fully qualified domain name.</p> </item> - <marker id="prop_server_root"></marker> - <tag>{server_root, path()}</tag> + <tag><marker id="prop_server_root"></marker>{server_root, path()}</tag> <item> <p>Defines the home directory of the server, where log files, and so on, can be stored. Relative paths specified in other properties refer to this directory.</p> </item> - <marker id="prop_doc_root"></marker> - <tag>{document_root, path()}</tag> + <tag> <marker id="prop_doc_root"></marker>{document_root, path()}</tag> <item> <p>Defines the top directory for the documents that are available on the HTTP server.</p> @@ -155,15 +150,13 @@ <marker id="props_comm"></marker> <p><em>Communication Properties</em></p> <taglist> - <marker id="prop_bind_address"></marker> - <tag>{bind_address, ip_address() | hostname() | any}</tag> + <tag><marker id="prop_bind_address"></marker>{bind_address, ip_address() | hostname() | any}</tag> <item> <p>Default is <c>any</c>. <c>any</c> is denoted <em>*</em> in the Apache-like configuration file.</p> </item> - <marker id="profile"></marker> - <tag>{profile, atom()}</tag> + <tag><marker id="profile"></marker>{profile, atom()}</tag> <item> <p>Used together with <seealso marker="#prop_bind_address"><c>bind_address</c></seealso> and <seealso marker="#prop_port"><c>port</c></seealso> to uniquely identify @@ -176,25 +169,23 @@ </p> </item> - <marker id="prop_socket_type"></marker> - <tag>{socket_type, ip_comm | {essl, Config::proplist()}}</tag> + <tag><marker id="prop_socket_type"></marker>{socket_type, ip_comm | {ip_comm, Config::proplist()} | {essl, Config::proplist()}}</tag> <item> + <p>For <c>ip_comm</c> configuration options, see + <seealso marker="kernel:gen_tcp#listen-2">gen_tcp:listen/2</seealso>, some options + that are used internally by httpd can not be set.</p> <p>For <c>SSL</c> configuration options, see <seealso marker="ssl:ssl#listen-2">ssl:listen/2</seealso>.</p> <p>Default is <c>ip_comm</c>.</p> </item> - <marker id="prop_ipfamily"></marker> - <tag>{ipfamily, inet | inet6 | inet6fb4}</tag> + <tag><marker id="prop_ipfamily"></marker>{ipfamily, inet | inet6}</tag> <item> - <p>This option is only used when option - <c>socket_type</c> has value <c>ip_comm</c>.</p> - <p>Default is <c>inet6fb4</c>.</p> + <p>Default is <c>inet</c>, legacy option <c>inet6fb4</c> no longer makes sense and will be translated + to inet.</p> </item> - - <marker id="prop_minimum_bytes_per_second"></marker> - <tag>{minimum_bytes_per_second, integer()}</tag> + <tag><marker id="prop_minimum_bytes_per_second"></marker>{minimum_bytes_per_second, integer()}</tag> <item> <p>If given, sets a minimum of bytes per second value for connections.</p> <p>If the value is unreached, the socket closes for that connection.</p> @@ -205,8 +196,7 @@ <marker id="props_api_modules"></marker> <p><em>Erlang Web Server API Modules</em> </p> <taglist> - <marker id="prop_modules"></marker> - <tag>{modules, [atom()]} </tag> + <tag><marker id="prop_modules"></marker>{modules, [atom()]} </tag> <item> <p>Defines which modules the HTTP server uses when handling requests. Default is <c>[mod_alias, mod_auth, mod_esi, @@ -223,60 +213,52 @@ <p><em>Limit properties</em> </p> <taglist> - <marker id="prop_customize"></marker> - <tag>{customize, atom()}</tag> + <tag><marker id="prop_customize"></marker>{customize, atom()}</tag> <item> <p>A callback module to customize the inets HTTP servers behaviour see <seealso marker="httpd_custom_api"> httpd_custom_api</seealso> </p> </item> - <marker id="prop_disable_chunked_encoding"></marker> - <tag>{disable_chunked_transfer_encoding_send, boolean()}</tag> + <tag><marker id="prop_disable_chunked_encoding"></marker>{disable_chunked_transfer_encoding_send, boolean()}</tag> <item> <p>Allows you to disable chunked transfer-encoding when sending a response to an HTTP/1.1 client. Default is <c>false</c>.</p> </item> - <marker id="prop_keep_alive"></marker> - <tag>{keep_alive, boolean()}</tag> + <tag><marker id="prop_keep_alive"></marker>{keep_alive, boolean()}</tag> <item> <p>Instructs the server whether to use persistent connections when the client claims to be HTTP/1.1 compliant. Default is <c>true</c>.</p> </item> - <marker id="prop_keep_alive_timeout"></marker> - <tag>{keep_alive_timeout, integer()}</tag> + <tag><marker id="prop_keep_alive_timeout"></marker>{keep_alive_timeout, integer()}</tag> <item> <p>The number of seconds the server waits for a subsequent request from the client before closing the connection. Default is <c>150</c>.</p> </item> - <marker id="prop_max_body_size"></marker> - <tag>{max_body_size, integer()}</tag> + <tag><marker id="prop_max_body_size"></marker>{max_body_size, integer()}</tag> <item> <p>Limits the size of the message body of an HTTP request. Default is no limit.</p> </item> - <marker id="prop_max_clients"></marker> - <tag>{max_clients, integer()}</tag> + <tag><marker id="prop_max_clients"></marker>{max_clients, integer()}</tag> <item> <p>Limits the number of simultaneous requests that can be supported. Default is <c>150</c>.</p> </item> - <marker id="prop_max_header_size"></marker> - <tag>{max_header_size, integer()}</tag> + <tag><marker id="prop_max_header_size"></marker>{max_header_size, integer()}</tag> <item> <p>Limits the size of the message header of an HTTP request. Default is <c>10240</c>.</p> </item> - <marker id="prop_max_content_length"></marker> - <tag>{max_content_length, integer()}</tag> + <tag><marker id="prop_max_content_length"></marker>{max_content_length, integer()}</tag> <item> <p>Maximum content-length in an incoming request, in bytes. Requests with content larger than this are answered with status 413. @@ -284,15 +266,13 @@ </p> </item> - <marker id="prop_max_uri"></marker> - <tag>{max_uri_size, integer()}</tag> + <tag><marker id="prop_max_uri"></marker>{max_uri_size, integer()}</tag> <item> <p>Limits the size of the HTTP request URI. Default is no limit.</p> </item> - <marker id="prop_max_keep_alive_req"></marker> - <tag>{max_keep_alive_request, integer()}</tag> + <tag><marker id="prop_max_keep_alive_req"></marker>{max_keep_alive_request, integer()}</tag> <item> <p>The number of requests that a client can do on one connection. When the server has responded to the number of @@ -305,8 +285,7 @@ <marker id="props_admin"></marker> <p><em>Administrative Properties</em></p> <taglist> - <marker id="prop_mime_types"></marker> - <tag>{mime_types, [{MimeType, Extension}] | path()}</tag> + <tag><marker id="prop_mime_types"></marker>{mime_types, [{MimeType, Extension}] | path()}</tag> <item> <p><c>MimeType = string()</c> and <c>Extension = string()</c>. Files delivered to the client are MIME typed according to RFC @@ -322,24 +301,21 @@ text/plain asc txt</pre> <p>Default is [{"html","text/html"},{"htm","text/html"}].</p> </item> - <marker id="prop_mime_type"></marker> - <tag>{mime_type, string()}</tag> + <tag><marker id="prop_mime_type"></marker>{mime_type, string()}</tag> <item> <p>When the server is asked to provide a document type that cannot be determined by the MIME Type Settings, the server uses this default type.</p> </item> - <marker id="prop_server_admin"></marker> - <tag>{server_admin, string()}</tag> + <tag><marker id="prop_server_admin"></marker>{server_admin, string()}</tag> <item> <p>Defines the email-address of the server administrator to be included in any error messages returned by the server.</p> </item> - <marker id="prop_server_tokens"></marker> - <tag>{server_tokens, none|prod|major|minor|minimal|os|full|{private, string()}}</tag> + <tag><marker id="prop_server_tokens"></marker>{server_tokens, none|prod|major|minor|minimal|os|full|{private, string()}}</tag> <item> <p>Defines the look of the value of the server header.</p> <p>Example: Assuming the version of <c>Inets</c> is 5.8.1, @@ -366,8 +342,7 @@ text/plain asc txt</pre> <p>By default, the value is as before, that is, <c>minimal</c>.</p> </item> - <marker id="prop_log_format"></marker> - <tag>{log_format, common | combined}</tag> + <tag><marker id="prop_log_format"></marker>{log_format, common | combined}</tag> <item> <p>Defines if access logs are to be written according to the <c>common</c> log format or the extended common log format. @@ -410,8 +385,7 @@ text/plain asc txt</pre> </p> </item> - <marker id="prop_elog_format"></marker> - <tag>{error_log_format, pretty | compact}</tag> + <tag><marker id="prop_elog_format"></marker>{error_log_format, pretty | compact}</tag> <item> <p>Default is <c>pretty</c>. If the error log is meant to be read directly by a human, <c>pretty</c> is the best option.</p> @@ -433,60 +407,57 @@ text/plain asc txt</pre> <marker id="props_alias"></marker> <p><em>URL Aliasing Properties - Requires mod_alias</em></p> <taglist> - <marker id="prop_alias"></marker> - <tag>{alias, {Alias, RealName}}</tag> + <tag><marker id="prop_alias"></marker>{alias, {Alias, RealName}}</tag> <item> <p><c>Alias = string()</c> and <c>RealName = string()</c>. <c>alias</c> allows documents to be stored in the local file system instead of the <c>document_root</c> location. URLs with a path beginning with url-path is mapped to local files beginning with - directory-filename, for example: + directory-filename, for example:</p> <code>{alias, {"/image", "/ftp/pub/image"}}</code> - Access to http://your.server.org/image/foo.gif would refer to + <p>Access to http://your.server.org/image/foo.gif would refer to the file /ftp/pub/image/foo.gif.</p> </item> - <marker id="prop_re_write"></marker> - <tag>{re_write, {Re, Replacement}}</tag> + <tag><marker id="prop_re_write"></marker>{re_write, {Re, Replacement}}</tag> <item> <p><c>Re = string()</c> and <c>Replacement = string()</c>. <c>re_write</c> allows documents to be stored in the local file system instead of the <c>document_root</c> location. URLs are rewritten by <c>re:replace/3</c> to produce a path in the local file-system, - for example: + for example:</p> <code>{re_write, {"^/[~]([^/]+)(.*)$", "/home/\\1/public\\2"}}</code> - Access to http://your.server.org/~bob/foo.gif would refer to + <p>Access to http://your.server.org/~bob/foo.gif would refer to the file /home/bob/public/foo.gif. In an Apache-like configuration file, <c>Re</c> is separated from <c>Replacement</c> with one single space, and as expected backslashes do not need to be backslash escaped, the - same example would become: + same example would become:</p> <code>ReWrite ^/[~]([^/]+)(.*)$ /home/\1/public\2</code> - Beware of trailing space in <c>Replacement</c> to be used. + <p>Beware of trailing space in <c>Replacement</c> to be used. If you must have a space in <c>Re</c>, use, for example, the character encoding <c>\040</c>, see <seealso marker="stdlib:re">re(3)</seealso>.</p> </item> - <marker id="prop_dir_idx"></marker> - <tag>{directory_index, [string()]}</tag> + <tag><marker id="prop_dir_idx"></marker>{directory_index, [string()]}</tag> <item> <p><c>directory_index</c> specifies a list of resources to look for if a client requests a directory using a <c>/</c> at the end of the directory name. <c>file</c> depicts the name of a file in the directory. Several files can be given, in which case the server - returns the first it finds, for example: + returns the first it finds, for example:</p> <code>{directory_index, ["index.hml", "welcome.html"]}</code> - Access to http://your.server.org/docs/ would return + <p>Access to http://your.server.org/docs/ would return http://your.server.org/docs/index.html or http://your.server.org/docs/welcome.html if index.html does not exist.</p> @@ -496,38 +467,35 @@ text/plain asc txt</pre> <marker id="props_cgi"></marker> <p><em>CGI Properties - Requires mod_cgi</em></p> <taglist> - <marker id="prop_script_alias"></marker> - <tag>{script_alias, {Alias, RealName}}</tag> + <tag><marker id="prop_script_alias"></marker>{script_alias, {Alias, RealName}}</tag> <item> <p><c>Alias = string()</c> and <c>RealName = string()</c>. Have the same behavior as property <c>alias</c>, except that they also mark the target directory as containing CGI scripts. URLs with a path beginning with url-path are mapped to - scripts beginning with directory-filename, for example: + scripts beginning with directory-filename, for example:</p> <code>{script_alias, {"/cgi-bin/", "/web/cgi-bin/"}}</code> - Access to http://your.server.org/cgi-bin/foo would cause + <p>Access to http://your.server.org/cgi-bin/foo would cause the server to run the script /web/cgi-bin/foo.</p> </item> - <marker id="prop_script_re_write"></marker> - <tag>{script_re_write, {Re, Replacement}}</tag> + <tag><marker id="prop_script_re_write"></marker>{script_re_write, {Re, Replacement}}</tag> <item> <p><c>Re = string()</c> and <c>Replacement = string()</c>. Have the same behavior as property <c>re_write</c>, except that they also mark the target directory as containing CGI scripts. URLs with a path beginning with url-path are mapped to - scripts beginning with directory-filename, for example: + scripts beginning with directory-filename, for example:</p> <code>{script_re_write, {"^/cgi-bin/(\\d+)/", "/web/\\1/cgi-bin/"}}</code> - Access to http://your.server.org/cgi-bin/17/foo would cause + <p>Access to http://your.server.org/cgi-bin/17/foo would cause the server to run the script /web/17/cgi-bin/foo.</p> </item> - <marker id="prop_script_nocache"></marker> - <tag>{script_nocache, boolean()}</tag> + <tag><marker id="prop_script_nocache"></marker>{script_nocache, boolean()}</tag> <item> <p>If <c>script_nocache</c> is set to <c>true</c>, the HTTP server by default adds the header fields necessary to prevent proxies from @@ -535,8 +503,7 @@ text/plain asc txt</pre> Default to <c>false</c>.</p> </item> - <marker id="prop_script_timeout"></marker> - <tag>{script_timeout, integer()}</tag> + <tag><marker id="prop_script_timeout"></marker>{script_timeout, integer()}</tag> <item> <p>The time in seconds the web server waits between each chunk of data from the script. If the CGI script does not deliver @@ -544,8 +511,7 @@ text/plain asc txt</pre> closed. Default is <c>15</c>.</p> </item> - <marker id="prop_action"></marker> - <tag>{action, {MimeType, CgiScript}} - requires mod_action</tag> + <tag><marker id="prop_action"></marker>{action, {MimeType, CgiScript}} - requires mod_action</tag> <item> <p><c>MimeType = string()</c> and <c>CgiScript = string()</c>. <c>action</c> adds an action activating a CGI script @@ -558,8 +524,7 @@ text/plain asc txt</pre> <code>{action, {"text/plain", "/cgi-bin/log_and_deliver_text"}}</code> </item> - <marker id="prop_script"></marker> - <tag>{script, {Method, CgiScript}} - requires mod_action</tag> + <tag><marker id="prop_script"></marker>{script, {Method, CgiScript}} - requires mod_action</tag> <item> <p><c>Method = string()</c> and <c>CgiScript = string()</c>. <c>script</c> adds an action activating a CGI script @@ -578,17 +543,16 @@ text/plain asc txt</pre> <marker id="props_esi"></marker> <p><em>ESI Properties - Requires mod_esi</em></p> <taglist> - <marker id="prop_esi_alias"></marker> - <tag>{erl_script_alias, {URLPath, [AllowedModule]}}</tag> + <tag><marker id="prop_esi_alias"></marker>{erl_script_alias, {URLPath, [AllowedModule]}}</tag> <item> <p><c>URLPath = string()</c> and <c>AllowedModule = atom()</c>. <c>erl_script_alias</c> marks all URLs matching url-path as erl scheme scripts. A matching URL is mapped into a specific module - and function, for example: + and function, for example:</p> <code>{erl_script_alias, {"/cgi-bin/example", [httpd_example]}}</code> - A request to + <p>A request to http://your.server.org/cgi-bin/example/httpd_example:yahoo would refer to httpd_example:yahoo/3 or, if that does not exist, httpd_example:yahoo/2 and @@ -596,8 +560,7 @@ text/plain asc txt</pre> not be allowed to execute.</p> </item> - <marker id="prop_esi_nocache"></marker> - <tag>{erl_script_nocache, boolean()}</tag> + <tag><marker id="prop_esi_nocache"></marker>{erl_script_nocache, boolean()}</tag> <item> <p>If <c>erl_script_nocache</c> is set to <c>true</c>, the server adds HTTP header fields preventing proxies from caching the @@ -606,8 +569,7 @@ text/plain asc txt</pre> Default is <c>false</c>.</p> </item> - <marker id="prop_esi_timeout"></marker> - <tag>{erl_script_timeout, integer()}</tag> + <tag><marker id="prop_esi_timeout"></marker>{erl_script_timeout, integer()}</tag> <item> <p>If <c>erl_script_timeout</c> sets the time in seconds the server waits between each chunk of data to be delivered through @@ -615,8 +577,7 @@ text/plain asc txt</pre> for scripts that use the erl scheme.</p> </item> - <marker id="prop_esi_timeout"></marker> - <tag>{eval_script_alias, {URLPath, [AllowedModule]}}</tag> + <tag><marker id="prop_esi_timeout"></marker>{eval_script_alias, {URLPath, [AllowedModule]}}</tag> <item> <p><c>URLPath = string()</c> and <c>AllowedModule = atom()</c>. Same as <c>erl_script_alias</c> but for scripts @@ -628,24 +589,21 @@ text/plain asc txt</pre> <marker id="props_log"></marker> <p><em>Log Properties - Requires mod_log</em></p> <taglist> - <marker id="prop_elog"></marker> - <tag>{error_log, path()}</tag> + <tag><marker id="prop_elog"></marker>{error_log, path()}</tag> <item> <p>Defines the filename of the error log file to be used to log server errors. If the filename does not begin with a slash (/), it is assumed to be relative to the <c>server_root</c>.</p> </item> - <marker id="prop_slog"></marker> - <tag>{security_log, path()}</tag> + <tag><marker id="prop_slog"></marker>{security_log, path()}</tag> <item> <p>Defines the filename of the access log file to be used to log security events. If the filename does not begin with a slash (/), it is assumed to be relative to the <c>server_root</c>.</p> </item> - <marker id="prop_tlog"></marker> - <tag>{transfer_log, path()}</tag> + <tag><marker id="prop_tlog"></marker>{transfer_log, path()}</tag> <item> <p>Defines the filename of the access log file to be used to log incoming requests. If the filename does not begin with a @@ -656,8 +614,7 @@ text/plain asc txt</pre> <marker id="props_dlog"></marker> <p><em>Disk Log Properties - Requires mod_disk_log</em></p> <taglist> - <marker id="prop_dlog_format"></marker> - <tag>{disk_log_format, internal | external}</tag> + <tag><marker id="prop_dlog_format"></marker>{disk_log_format, internal | external}</tag> <item> <p>Defines the file format of the log files. See <c>disk_log</c> for details. If the internal file format is used, the @@ -667,16 +624,14 @@ text/plain asc txt</pre> <c>external</c>.</p> </item> - <marker id="prop_edlog"></marker> - <tag>{error_disk_log, path()}</tag> + <tag><marker id="prop_edlog"></marker>{error_disk_log, path()}</tag> <item> <p>Defines the filename of the (<c>disk_log(3)</c>) error log file to be used to log server errors. If the filename does not begin with a slash (/), it is assumed to be relative to the <c>server_root</c>.</p> </item> - <marker id="prop_edlog_size"></marker> - <tag>{error_disk_log_size, {MaxBytes, MaxFiles}}</tag> + <tag><marker id="prop_edlog_size"></marker>{error_disk_log_size, {MaxBytes, MaxFiles}}</tag> <item> <p><c>MaxBytes = integer()</c> and <c>MaxFiles = integer()</c>. Defines the properties of the (<c>disk_log(3)</c>) error log @@ -685,8 +640,7 @@ text/plain asc txt</pre> used before the first file is truncated and reused.</p> </item> - <marker id="prop_sdlog"></marker> - <tag>{security_disk_log, path()}</tag> + <tag><marker id="prop_sdlog"></marker>{security_disk_log, path()}</tag> <item> <p>Defines the filename of the (<c>disk_log(3)</c>) access log file logging incoming security events, that is, authenticated @@ -694,8 +648,7 @@ text/plain asc txt</pre> is assumed to be relative to the <c>server_root</c>.</p> </item> - <marker id="prop_sdlog_size"></marker> - <tag>{security_disk_log_size, {MaxBytes, MaxFiles}}</tag> + <tag><marker id="prop_sdlog_size"></marker>{security_disk_log_size, {MaxBytes, MaxFiles}}</tag> <item> <p><c>MaxBytes = integer()</c> and <c>MaxFiles = integer()</c>. Defines the properties of the <c>disk_log(3)</c> access log @@ -704,8 +657,7 @@ text/plain asc txt</pre> used before the first file is truncated and reused.</p> </item> - <marker id="prop_tdlog"></marker> - <tag>{transfer_disk_log, path()}</tag> + <tag><marker id="prop_tdlog"></marker>{transfer_disk_log, path()}</tag> <item> <p>Defines the filename of the (<c>disk_log(3)</c>) access log file logging incoming requests. If the filename does not begin @@ -713,8 +665,7 @@ text/plain asc txt</pre> <c>server_root</c>.</p> </item> - <marker id="prop_tdlog_size"></marker> - <tag>{transfer_disk_log_size, {MaxBytes, MaxFiles}}</tag> + <tag><marker id="prop_tdlog_size"></marker>{transfer_disk_log_size, {MaxBytes, MaxFiles}}</tag> <item> <p><c>MaxBytes = integer()</c> and <c>MaxFiles = integer()</c>. Defines the properties of the <c>disk_log(3)</c> access log @@ -734,32 +685,29 @@ text/plain asc txt</pre> <p>The properties for directories are as follows:</p> <taglist> - <marker id="prop_allow_from"></marker> - <tag>{allow_from, all | [RegxpHostString]}</tag> + <tag><marker id="prop_allow_from"></marker>{allow_from, all | [RegxpHostString]}</tag> <item> <p>Defines a set of hosts to be granted access to a - given directory, for example: + given directory, for example:</p> <code>{allow_from, ["123.34.56.11", "150.100.23"]}</code> - The host <c>123.34.56.11</c> and all machines on the <c>150.100.23</c> + <p>The host <c>123.34.56.11</c> and all machines on the <c>150.100.23</c> subnet are allowed access.</p> </item> - <marker id="prop_deny_from"></marker> - <tag>{deny_from, all | [RegxpHostString]}</tag> + <tag><marker id="prop_deny_from"></marker>{deny_from, all | [RegxpHostString]}</tag> <item> <p>Defines a set of hosts - to be denied access to a given directory, for example: + to be denied access to a given directory, for example:</p> <code>{deny_from, ["123.34.56.11", "150.100.23"]}</code> - The host <c>123.34.56.11</c> and all machines on the <c>150.100.23</c> + <p>The host <c>123.34.56.11</c> and all machines on the <c>150.100.23</c> subnet are not allowed access.</p> </item> - <marker id="prop_auth_type"></marker> - <tag>{auth_type, plain | dets | mnesia}</tag> + <tag><marker id="prop_auth_type"></marker>{auth_type, plain | dets | mnesia}</tag> <item> <p>Sets the type of authentication database that is used for the directory. The key difference between the different methods is @@ -769,8 +717,7 @@ text/plain asc txt</pre> configuration files.</p> </item> - <marker id="prop_auth_user_file"></marker> - <tag>{auth_user_file, path()}</tag> + <tag><marker id="prop_auth_user_file"></marker>{auth_user_file, path()}</tag> <item> <p>Sets the name of a file containing the list of users and passwords for user authentication. The filename can be either @@ -794,8 +741,7 @@ text/plain asc txt</pre> clients can download it.</p> </item> - <marker id="prop_auth_group_file"></marker> - <tag>{auth_group_file, path()}</tag> + <tag><marker id="prop_auth_group_file"></marker>{auth_group_file, path()}</tag> <item> <p>Sets the name of a file containing the list of user groups for user authentication. The filename can be either @@ -817,16 +763,14 @@ text/plain asc txt</pre> can download it.</p> </item> - <marker id="prop_auth_name"></marker> - <tag>{auth_name, string()}</tag> + <tag><marker id="prop_auth_name"></marker>{auth_name, string()}</tag> <item> <p>Sets the name of the authorization realm (auth-domain) for a directory. This string informs the client about which username and password to use.</p> </item> - <marker id="prop_auth_access_passwd"></marker> - <tag>{auth_access_password, string()}</tag> + <tag><marker id="prop_auth_access_passwd"></marker>{auth_access_password, string()}</tag> <item> <p>If set to other than "NoPassword", the password is required for all API calls. If the password is set to "DummyPassword", the @@ -836,15 +780,13 @@ text/plain asc txt</pre> text in the configuration file.</p> </item> - <marker id="prop_req_user"></marker> - <tag>{require_user, [string()]}</tag> + <tag><marker id="prop_req_user"></marker>{require_user, [string()]}</tag> <item> <p>Defines users to grant access to a given directory using a secret password.</p> </item> - <marker id="prop_req_grp"></marker> - <tag>{require_group, [string()]}</tag> + <tag><marker id="prop_req_grp"></marker>{require_group, [string()]}</tag> <item> <p>Defines users to grant access to a given directory using a secret password.</p> @@ -855,8 +797,7 @@ text/plain asc txt</pre> <marker id="props_htaccess"></marker> <p><em>Htaccess Authentication Properties - Requires mod_htaccess</em></p> <taglist> - <marker id="prop_access_files"></marker> - <tag>{access_files, [path()]}</tag> + <tag><marker id="prop_access_files"></marker>{access_files, [path()]}</tag> <item> <p>Specifies the filenames that are used for access files. When a request comes, every directory in the path @@ -876,16 +817,14 @@ text/plain asc txt</pre> <marker id="props_sdir"></marker> <p>The properties for the security directories are as follows:</p> <taglist> - <marker id="prop_data_file"></marker> - <tag>{data_file, path()}</tag> + <tag><marker id="prop_data_file"></marker>{data_file, path()}</tag> <item> <p>Name of the security data file. The filename can either be absolute or relative to the <c>server_root</c>. This file is used to store persistent data for module <c>mod_security</c>.</p> </item> - <marker id="prop_max_retries"></marker> - <tag>{max_retries, integer()}</tag> + <tag><marker id="prop_max_retries"></marker>{max_retries, integer()}</tag> <item> <p>Specifies the maximum number of attempts to authenticate a user before the user is blocked out. If a user @@ -897,16 +836,14 @@ text/plain asc txt</pre> Default is <c>3</c>. Can be set to infinity.</p> </item> - <marker id="prop_block_time"></marker> - <tag>{block_time, integer()}</tag> + <tag><marker id="prop_block_time"></marker>{block_time, integer()}</tag> <item> <p>Specifies the number of minutes a user is blocked. After this timehas passed, the user automatically regains access. Default is <c>60</c>.</p> </item> - <marker id="prop_fail_exp_time"></marker> - <tag>{fail_expire_time, integer()}</tag> + <tag><marker id="prop_fail_exp_time"></marker>{fail_expire_time, integer()}</tag> <item> <p>Specifies the number of minutes a failed user authentication is remembered. If a user authenticates after this @@ -915,8 +852,7 @@ text/plain asc txt</pre> Default is <c>30</c>.</p> </item> - <marker id="prop_auth_timeout"></marker> - <tag>{auth_timeout, integer()}</tag> + <tag><marker id="prop_auth_timeout"></marker>{auth_timeout, integer()}</tag> <item> Specifies the number of seconds a successful user authentication is remembered. After this time has passed, the diff --git a/lib/inets/doc/src/httpd_custom_api.xml b/lib/inets/doc/src/httpd_custom_api.xml index 23417900fa..d2e5441895 100644 --- a/lib/inets/doc/src/httpd_custom_api.xml +++ b/lib/inets/doc/src/httpd_custom_api.xml @@ -33,6 +33,20 @@ </description> <funcs> + <func> + <name>response_default_headers() -> [Header] </name> + <fsummary>Provide default headers for the HTTP servers responses.</fsummary> + <type> + <v>Header = {HeaderName :: string(), HeaderValue::string()}</v> + <d>string:to_lower/1 will be performed on the HeaderName</d> + </type> + <desc> + <p>Provide default headers for the HTTP servers responses. Note that this + option may override built-in defaults. + </p> + </desc> + </func> + <func> <name>response_header({HeaderName, HeaderValue}) -> {true, Header} | false </name> <fsummary>Filter and possible alter HTTP response headers.</fsummary> diff --git a/lib/inets/doc/src/notes.xml b/lib/inets/doc/src/notes.xml index eb1027b028..c98ec1a9dc 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>2014</year> + <year>2002</year><year>2015</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -33,7 +33,136 @@ <file>notes.xml</file> </header> - <section><title>Inets 6.0.1</title> + <section><title>Inets 6.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Replace obs-folds with spaces instead of failing</p> + <p> + Own Id: OTP-13069</p> + </item> + <item> + <p> + Add validation fun for URI scheme to http_uri API</p> + <p> + Own Id: OTP-13071</p> + </item> + <item> + <p> + Handle stream bodies as documented.</p> + <p> + Own Id: OTP-13093</p> + </item> + <item> + <p> + Correct error handling of mod_esi generated chunks. Send + warning headers in chunk trailers instead of generating + an unexpected additional 500 request response, when + problems, such as a timeout occurs.</p> + <p> + Own Id: OTP-13110</p> + </item> + <item> + <p> + HTTP client terminates gracefully when an invalid chunked + length header is encountered.</p> + <p> + Own Id: OTP-13117</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Add default for SNI (Server Name Indication) when running + https using the inets HTTP-client.</p> + <p> + Own Id: OTP-12985</p> + </item> + <item> + <p> + Be forgiving to chunked sizes that have trailing + whitespaces as prior implementation was. Also some legacy + embedded devices does actually have trailing whitespaces + even though this in not according to the spec.</p> + <p> + Own Id: OTP-13116</p> + </item> + </list> + </section> + +</section> + +<section><title>Inets 6.0.3</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Improved error handling and gracfully termination when an + invalid chunked length header is encountered.</p> + <p> + Own Id: OTP-13061</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Add possibility to set socket options, such as nodelay, + for httpd. Also phase out legacy option value inet6bf4 + for the ipfamily option. This value will be translated to + the value inet.</p> + <p> + *** POTENTIAL INCOMPATIBILITY ***</p> + <p> + Own Id: OTP-13062</p> + </item> + </list> + </section> + +</section> + +<section><title>Inets 6.0.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Avoid crash in mod_auth_server and mod_security_server + due to using an atom instead of a string when creating a + name.</p> + <p> + Own Id: OTP-13022</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Add function response_default_headers/0 to httpd + customize API, to allow user to specify default values + for HTTP response headers.</p> + <p> + Own Id: OTP-13013</p> + </item> + </list> + </section> + +</section> + +<section><title>Inets 6.0.1</title> <section><title>Fixed Bugs and Malfunctions</title> <list> @@ -1743,23 +1872,21 @@ <item> <p>[httpd] - Issues with ESI erl_script_timeout. </p> - <p> - <list type="bulleted"> - <item> - <p>The <c>erl_script_timeout</c> config option is ducumented - as a number of seconds. But when parsing the config, in the - new format (not a config file), it was handled as if in - number of milliseconds. </p> - </item> - <item> - <p>When the erl-script-timeout time was exceeded, the server - incorrectly marked the answer as sent, thereby leaving - client hanging (with an incomplete answer). - This has been changed, so that now the socket will be - closed. </p> - </item> - </list> - </p> + <list type="bulleted"> + <item> + <p>The <c>erl_script_timeout</c> config option is ducumented + as a number of seconds. But when parsing the config, in the + new format (not a config file), it was handled as if in + number of milliseconds. </p> + </item> + <item> + <p>When the erl-script-timeout time was exceeded, the server + incorrectly marked the answer as sent, thereby leaving + client hanging (with an incomplete answer). + This has been changed, so that now the socket will be + closed. </p> + </item> + </list> <p>Own Id: OTP-8509</p> </item> </list> @@ -1835,20 +1962,19 @@ <item> <p>[httpc] Several more or less critical fixes:</p> - <p> - <list type="bulleted"> - <item> - <p>Initial call between the httpc manager and request - handler was synchronous. </p> - <p>When the manager starts a new request handler, - this is no longer a synchronous operation. Previously, - the new request handler made the connection to the - server and issuing of the first request (the reason - for starting it) in the gen_server init function. - If the connection for some reason "took some time", - the manager hanged, leaving all other activities by - that manager also hanging. </p> - </item> + <list type="bulleted"> + <item> + <p>Initial call between the httpc manager and request + handler was synchronous. </p> + <p>When the manager starts a new request handler, + this is no longer a synchronous operation. Previously, + the new request handler made the connection to the + server and issuing of the first request (the reason + for starting it) in the gen_server init function. + If the connection for some reason "took some time", + the manager hanged, leaving all other activities by + that manager also hanging. </p> + </item> <!-- <item> <p>Copying of data between processes</p> @@ -1859,8 +1985,7 @@ <p>TBD</p> </item> --> - </list> - </p> + </list> <p>As a side-effect of these changes, some modules was also renamed, and a new api module, <seealso marker="httpc">httpc</seealso>, has been introduced diff --git a/lib/inets/doc/src/tftp.xml b/lib/inets/doc/src/tftp.xml index 00d9d53376..10398f5088 100644 --- a/lib/inets/doc/src/tftp.xml +++ b/lib/inets/doc/src/tftp.xml @@ -4,7 +4,7 @@ <erlref> <header> <copyright> - <year>2006</year><year>2013</year> + <year>2006</year><year>2015</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -216,12 +216,9 @@ five times when the time-out expires.</p> </item> </taglist> - - <marker id="start1"></marker> </section> <funcs> - <marker id="change_config_daemons"></marker> <func> <name>change_config(daemons, Options) -> [{Pid, Result}]</name> <fsummary>Changes configuration for all daemons. @@ -234,8 +231,6 @@ </type> <desc> <p>Changes configuration for all TFTP daemon processes. </p> - - <marker id="change_config_servers"></marker> </desc> </func> @@ -251,8 +246,6 @@ </type> <desc> <p>Changes configuration for all TFTP server processes.</p> - - <marker id="change_config_pid"></marker> </desc> </func> @@ -268,7 +261,6 @@ </type> <desc> <p>Changes configuration for a TFTP daemon, server, or client process.</p> - <marker id="info_daemons"></marker> </desc> </func> @@ -282,8 +274,6 @@ </type> <desc> <p>Returns information about all TFTP daemon processes.</p> - - <marker id="info_servers"></marker> </desc> </func> @@ -297,8 +287,6 @@ </type> <desc> <p>Returns information about all TFTP server processes. </p> - - <marker id="info_pid"></marker> </desc> </func> @@ -341,9 +329,7 @@ the regexps of these and the callback module corresponding to the first match is used, or an error tuple is returned if no matching regexp is found.</p> - </desc> - - <marker id="write_file"></marker> + </desc> </func> <func> @@ -359,8 +345,6 @@ port. When it receives a request for read or write, it spawns a temporary server process handling the actual transfer of the (virtual) file.</p> - - <marker id="read_file"></marker> </desc> </func> @@ -393,7 +377,6 @@ matching regexp is found.</p> </desc> </func> - </funcs> <section> diff --git a/lib/inets/src/http_client/httpc_handler.erl b/lib/inets/src/http_client/httpc_handler.erl index 6e6cc38c06..d1c52dcc78 100644 --- a/lib/inets/src/http_client/httpc_handler.erl +++ b/lib/inets/src/http_client/httpc_handler.erl @@ -26,6 +26,7 @@ -include_lib("inets/src/http_lib/http_internal.hrl"). -include("httpc_internal.hrl"). +-define(IS_STREAMED(Code), ((Code =:= 200) orelse (Code =:= 206))). %%-------------------------------------------------------------------- %% Internal Application API @@ -163,22 +164,22 @@ info(Pid) -> %% Request should not be streamed stream(BodyPart, #request{stream = none} = Request, _) -> ?hcrt("stream - none", []), - {BodyPart, Request}; + {false, BodyPart, Request}; %% Stream to caller stream(BodyPart, #request{stream = Self} = Request, Code) - when ((Code =:= 200) orelse (Code =:= 206)) andalso + when ?IS_STREAMED(Code) andalso ((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}; + {true, <<>>, Request}; %% Stream to file %% This has been moved to start_stream/3 %% We keep this for backward compatibillity... stream(BodyPart, #request{stream = Filename} = Request, Code) - when ((Code =:= 200) orelse (Code =:= 206)) andalso is_list(Filename) -> + when ?IS_STREAMED(Code) andalso is_list(Filename) -> ?hcrt("stream - filename", [{stream, Filename}, {code, Code}]), case file:open(Filename, [write, raw, append, delayed_write]) of {ok, Fd} -> @@ -190,18 +191,18 @@ stream(BodyPart, #request{stream = Filename} = Request, Code) %% Stream to file stream(BodyPart, #request{stream = Fd} = Request, Code) - when ((Code =:= 200) orelse (Code =:= 206)) -> + when ?IS_STREAMED(Code) -> ?hcrt("stream to file", [{stream, Fd}, {code, Code}]), case file:write(Fd, BodyPart) of ok -> - {<<>>, Request}; + {true, <<>>, Request}; {error, Reason} -> exit({stream_to_file_failed, Reason}) end; stream(BodyPart, Request,_) -> % only 200 and 206 responses can be streamed ?hcrt("stream - ignore", [{request, Request}]), - {BodyPart, Request}. + {false, BodyPart, Request}. %%==================================================================== @@ -474,18 +475,18 @@ handle_info({Proto, _Socket, Data}, {Module, whole_body, [Body, Length]} -> ?hcrd("data processed - whole body", [{length, Length}]), {_, Code, _} = StatusLine, - {NewBody, NewRequest} = stream(Body, Request, Code), + {Streamed, 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 -> + case Streamed of + false -> Length; - _ -> + true -> Length - size(Body) end, - NewState = next_body_chunk(State), + NewState = next_body_chunk(State, Code), NewMFA = {Module, whole_body, [NewBody, NewLength]}, {noreply, NewState#state{mfa = NewMFA, request = NewRequest}}; @@ -497,8 +498,8 @@ handle_info({Proto, _Socket, Data}, %% 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), + {_, NewBody, NewRequest} = stream(BodySoFar, Request, Code), + NewState = next_body_chunk(State, Code), NewMFA = {Module, decode_size, [TotalChunk, HexList, {MaxBodySize, NewBody, AccLength, MaxHeaderSize}]}, @@ -517,8 +518,8 @@ handle_info({Proto, _Socket, Data}, NewChunkSize = ChunkSize - ChunkSizeToSteal, {_, Code, _} = StatusLine, - {NewBody, NewRequest} = stream(StolenBody, Request, Code), - NewState = next_body_chunk(State), + {_, NewBody, NewRequest} = stream(StolenBody, Request, Code), + NewState = next_body_chunk(State, Code), NewMFA = {Module, decode_data, [NewChunkSize, NewTotalChunk, {MaxBodySize, NewBody, AccLength, MaxHeaderSize}]}, @@ -1071,13 +1072,13 @@ handle_http_msg({ChunkedHeaders, Body}, ?hcrt("handle_http_msg", [{chunked_headers, ChunkedHeaders}, {headers, Headers}]), NewHeaders = http_chunk:handle_headers(Headers, ChunkedHeaders), - {NewBody, NewRequest} = stream(Body, State#state.request, Code), + {_, 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), + {_, NewBody, NewRequest} = stream(Body, State#state.request, Code), handle_response(State#state{body = NewBody, request = NewRequest}). handle_http_body(_, #state{status = {ssl_tunnel, _}, @@ -1112,14 +1113,14 @@ handle_http_body(Body, #state{headers = Headers, case case_insensitive_header(TransferEnc) of "chunked" -> ?hcrt("handle_http_body - chunked", []), - case http_chunk:decode(Body, State#state.max_body_size, - State#state.max_header_size) of + try http_chunk:decode(Body, State#state.max_body_size, + State#state.max_header_size) of {Module, Function, Args} -> ?hcrt("handle_http_body - new mfa", [{module, Module}, {function, Function}, {args, Args}]), - NewState = next_body_chunk(State), + NewState = next_body_chunk(State, Code), {noreply, NewState#state{mfa = {Module, Function, Args}}}; {ok, {ChunkedHeaders, NewBody}} -> @@ -1133,11 +1134,18 @@ handle_http_body(Body, #state{headers = Headers, handle_response(State#state{headers = NewHeaders, body = NewBody}); _ -> - {NewBody2, _NewRequest} = + {_, NewBody2, _} = stream(NewBody, Request, Code), handle_response(State#state{headers = NewHeaders, body = NewBody2}) end + catch throw:{error, Reason} -> + NewState = + answer_request(Request, + httpc_response:error(Request, + Reason), + State), + {stop, normal, NewState} end; Enc when Enc =:= "identity"; Enc =:= undefined -> ?hcrt("handle_http_body - identity", []), @@ -1147,12 +1155,12 @@ handle_http_body(Body, #state{headers = Headers, true -> case httpc_response:whole_body(Body, Length) of {ok, Body} -> - {NewBody, NewRequest} = + {_, NewBody, NewRequest} = stream(Body, Request, Code), handle_response(State#state{body = NewBody, request = NewRequest}); MFA -> - NewState = next_body_chunk(State), + NewState = next_body_chunk(State, Code), {noreply, NewState#state{mfa = MFA}} end; false -> @@ -1646,21 +1654,21 @@ start_stream({_Version, _Code, _ReasonPhrase}, _Headers, {ok, Request}; start_stream({_Version, Code, _ReasonPhrase}, Headers, #request{stream = self} = Request) - when (Code =:= 200) orelse (Code =:= 206) -> + when ?IS_STREAMED(Code) -> ?hcrt("start stream - self", [{code, Code}]), Msg = httpc_response:stream_start(Headers, Request, ignore), httpc_response:send(Request#request.from, Msg), {ok, Request}; start_stream({_Version, Code, _ReasonPhrase}, Headers, #request{stream = {self, once}} = Request) - when (Code =:= 200) orelse (Code =:= 206) -> + when ?IS_STREAMED(Code) -> ?hcrt("start stream - self:once", [{code, Code}]), Msg = httpc_response:stream_start(Headers, Request, self()), httpc_response:send(Request#request.from, Msg), {ok, Request}; start_stream({_Version, Code, _ReasonPhrase}, _Headers, #request{stream = Filename} = Request) - when ((Code =:= 200) orelse (Code =:= 206)) andalso is_list(Filename) -> + when ?IS_STREAMED(Code) andalso is_list(Filename) -> ?hcrt("start stream", [{code, Code}, {filename, Filename}]), case file:open(Filename, [write, raw, append, delayed_write]) of {ok, Fd} -> @@ -1712,13 +1720,15 @@ end_stream(SL, R) -> next_body_chunk(#state{request = #request{stream = {self, once}}, once = once, - session = Session} = State) -> + session = Session} = State, + Code) when ?IS_STREAMED(Code) -> activate_once(Session), State#state{once = inactive}; next_body_chunk(#state{request = #request{stream = {self, once}}, - once = inactive} = State) -> + once = inactive} = State, + Code) when ?IS_STREAMED(Code) -> State; %% Wait for user to call stream_next -next_body_chunk(#state{session = Session} = State) -> +next_body_chunk(#state{session = Session} = State, _) -> activate_once(Session), State. @@ -1817,11 +1827,13 @@ host_header(_, URI) -> tls_upgrade(#state{status = {ssl_tunnel, #request{settings = - #http_options{ssl = {_, TLSOptions} = SocketType}, - address = Address} = Request}, + #http_options{ssl = {_, TLSOptions0} = SocketType}, + address = {Host, _} = Address} = Request}, session = #session{socket = TCPSocket} = Session0, options = Options} = State) -> + TLSOptions = maybe_add_sni(Host, TLSOptions0), + case ssl:connect(TCPSocket, TLSOptions) of {ok, TLSSocket} -> ClientClose = httpc_request:is_client_closing(Request#request.headers), @@ -1852,6 +1864,15 @@ tls_upgrade(#state{status = {stop, normal, State#state{request = Request}} end. +maybe_add_sni(Host, Options) -> + case http_util:is_hostname(Host) andalso + not lists:keymember(server_name_indication, 1, Options) of + true -> + [{server_name_indication, Host} | Options]; + false -> + Options + end. + %% --------------------------------------------------------------------- %% Session wrappers %% --------------------------------------------------------------------- diff --git a/lib/inets/src/http_lib/http_chunk.erl b/lib/inets/src/http_lib/http_chunk.erl index 9476ea9f5f..7325f24809 100644 --- a/lib/inets/src/http_lib/http_chunk.erl +++ b/lib/inets/src/http_lib/http_chunk.erl @@ -25,7 +25,7 @@ -include("http_internal.hrl"). %% API --export([decode/3, encode/1, encode_last/0, handle_headers/2]). +-export([decode/3, encode/1, encode_last/0, encode_last/1, 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]). @@ -57,7 +57,7 @@ %%------------------------------------------------------------------------- decode(ChunkedBody, MaxBodySize, MaxHeaderSize) -> %% Note decode_size will call decode_data. - decode_size([ChunkedBody, <<>>, [], + decode_size([ChunkedBody, <<>>, [], 0, {MaxBodySize, <<>>, 0, MaxHeaderSize}]). %%------------------------------------------------------------------------- @@ -85,6 +85,11 @@ encode(Chunk) when is_list(Chunk)-> encode_last() -> <<$0, ?CR, ?LF, ?CR, ?LF >>. +encode_last([]) -> + encode_last(); +encode_last(Trailers0) -> + Trailers = list_to_binary(encode_trailers(Trailers0)), + <<$0, ?CR, ?LF, Trailers/binary>>. %%------------------------------------------------------------------------- %% handle_headers(HeaderRecord, ChunkedHeaders) -> NewHeaderRecord @@ -120,65 +125,80 @@ handle_headers(ResponseHeaderRecord = #http_response_h{}, ChunkedHeaders) -> %% Functions that may be returned during the decoding process %% if the input data is incompleate. -decode_size([Bin, Rest, HexList, Info]) -> - decode_size(<<Rest/binary, Bin/binary>>, HexList, Info). +decode_size([Bin, Rest, HexList, AccSize, Info]) -> + decode_size(<<Rest/binary, Bin/binary>>, HexList, AccSize, Info). -ignore_extensions([Bin, Rest, NextFunction]) -> - ignore_extensions(<<Rest/binary, Bin/binary>>, NextFunction). +ignore_extensions([Bin, Rest, RemainingSize, TotalMaxHeaderSize, NextFunction]) -> + ignore_extensions(<<Rest/binary, Bin/binary>>, RemainingSize, TotalMaxHeaderSize, NextFunction). decode_data([Bin, ChunkSize, TotalChunk, Info]) -> decode_data(ChunkSize, <<TotalChunk/binary, Bin/binary>>, Info). -decode_trailer([Bin, Rest, Header, Headers, MaxHeaderSize, Body, - BodyLength]) -> +decode_trailer([Bin, Rest, Header, Headers, Body, + BodyLength, RemainingSize, TotalMaxHeaderSize]) -> decode_trailer(<<Rest/binary, Bin/binary>>, - Header, Headers, MaxHeaderSize, Body, BodyLength). + Header, Headers, Body, BodyLength, RemainingSize, TotalMaxHeaderSize). %%%======================================================================== %%% Internal functions %%%======================================================================== -decode_size(<<>>, HexList, Info) -> - {?MODULE, decode_size, [<<>>, HexList, Info]}; -decode_size(Data = <<?CR, ?LF, ChunkRest/binary>>, HexList, +decode_size(_, _, AccHeaderSize, {_,_,_, MaxHeaderSize}) when + AccHeaderSize > MaxHeaderSize -> + throw({error, {header_too_long, {max, MaxHeaderSize}}}); + +decode_size(<<>>, HexList, AccHeaderSize, Info) -> + {?MODULE, decode_size, [<<>>, HexList, AccHeaderSize, Info]}; +decode_size(Data = <<?CR, ?LF, ChunkRest/binary>>, HexList, AccHeaderSize, {MaxBodySize, Body, AccLength, MaxHeaderSize}) -> - ChunkSize = http_util:hexlist_to_integer(lists:reverse(HexList)), - case ChunkSize of + try http_util:hexlist_to_integer(lists:reverse(string:strip(HexList, left))) of 0 -> % Last chunk, there was no data - ignore_extensions(Data, {?MODULE, decode_trailer, - [<<>>, [],[], MaxHeaderSize, - Body, - integer_to_list(AccLength)]}); - _ -> + ignore_extensions(Data, remaing_size(MaxHeaderSize, AccHeaderSize), MaxHeaderSize, + {?MODULE, decode_trailer, + [<<>>, [],[], + Body, + integer_to_list(AccLength)]}); + ChunkSize -> %% Note decode_data may call decode_size again if there %% is more than one chunk, hence here is where the last parameter %% to this function comes in. decode_data(ChunkSize, ChunkRest, {MaxBodySize, Body, - ChunkSize + AccLength , + ChunkSize + AccLength, MaxHeaderSize}) + catch + _:_ -> + throw({error, {chunk_size, lists:reverse(HexList)}}) end; -decode_size(<<";", Rest/binary>>, HexList, Info) -> +decode_size(<<";", Rest/binary>>, HexList, AccHeaderSize, {_,_,_, MaxHeaderSize} = Info) -> %% Note ignore_extensions will call decode_size/1 again when %% it ignored all extensions. - ignore_extensions(Rest, {?MODULE, decode_size, [<<>>, HexList, Info]}); -decode_size(<<?CR>> = Data, HexList, Info) -> - {?MODULE, decode_size, [Data, HexList, Info]}; -decode_size(<<Octet, Rest/binary>>, HexList, Info) -> - decode_size(Rest, [Octet | HexList], Info). + ignore_extensions(Rest, remaing_size(MaxHeaderSize, AccHeaderSize), MaxHeaderSize, + {?MODULE, decode_size, [<<>>, HexList, AccHeaderSize, Info]}); +decode_size(<<?CR>> = Data, HexList, AccHeaderSize, Info) -> + {?MODULE, decode_size, [Data, HexList, AccHeaderSize, Info]}; +decode_size(<<Octet, Rest/binary>>, HexList, AccHeaderSize, Info) -> + decode_size(Rest, [Octet | HexList], AccHeaderSize + 1, Info). %% "All applications MUST ignore chunk-extension extensions they %% do not understand.", see RFC 2616 Section 3.6.1 We don't %% understand any extension... -ignore_extensions(<<>>, NextFunction) -> - {?MODULE, ignore_extensions, [<<>>, NextFunction]}; -ignore_extensions(Data = <<?CR, ?LF, _ChunkRest/binary>>, +ignore_extensions(_, 0, TotalMaxHeaderSize, _) -> + throw({error, {header_too_long, {max, TotalMaxHeaderSize}}}); +ignore_extensions(<<>>, RemainingSize, TotalMaxHeaderSize, NextFunction) -> + {?MODULE, ignore_extensions, [<<>>, RemainingSize, TotalMaxHeaderSize, NextFunction]}; +ignore_extensions(Data = <<?CR, ?LF, _ChunkRest/binary>>, RemainingSize, TotalMaxHeaderSize, {Module, Function, Args}) -> - Module:Function([Data | Args]); -ignore_extensions(<<?CR>> = Data, NextFunction) -> - {?MODULE, ignore_extensions, [Data, NextFunction]}; -ignore_extensions(<<_Octet, Rest/binary>>, NextFunction) -> - ignore_extensions(Rest, NextFunction). + case Function of + decode_trailer -> + Module:Function([Data | Args ++ [RemainingSize, TotalMaxHeaderSize]]); + _ -> + Module:Function([Data | Args]) + end; +ignore_extensions(<<?CR>> = Data, RemainingSize, TotalMaxHeaderSize, NextFunction) -> + {?MODULE, ignore_extensions, [Data, RemainingSize, TotalMaxHeaderSize, NextFunction]}; +ignore_extensions(<<_Octet, Rest/binary>>, RemainingSize, TotalMaxHeaderSize, NextFunction) -> + ignore_extensions(Rest, remaing_size(RemainingSize, 1), TotalMaxHeaderSize, NextFunction). decode_data(ChunkSize, TotalChunk, Info = {MaxBodySize, BodySoFar, AccLength, MaxHeaderSize}) @@ -190,83 +210,89 @@ decode_data(ChunkSize, TotalChunk, %% once it ignored all extensions. {?MODULE, ignore_extensions, [<<>>, - {?MODULE, decode_trailer, [<<>>, [],[], MaxHeaderSize, + {?MODULE, decode_trailer, [<<>>, [],[], <<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. - ignore_extensions(Rest, {?MODULE, decode_trailer, - [<<>>, [],[], MaxHeaderSize, + ignore_extensions(Rest, MaxHeaderSize, MaxHeaderSize, + {?MODULE, decode_trailer, + [<<>>, [],[], <<BodySoFar/binary, Data/binary>>, integer_to_list(AccLength)]}); <<Data:ChunkSize/binary, ?CR, ?LF, "0", ?CR, ?LF>> -> - {?MODULE, decode_trailer, [<<?CR, ?LF>>, [],[], MaxHeaderSize, + {?MODULE, decode_trailer, [<<?CR, ?LF>>, [],[], <<BodySoFar/binary, Data/binary>>, - integer_to_list(AccLength)]}; + integer_to_list(AccLength), MaxHeaderSize, MaxHeaderSize]}; <<Data:ChunkSize/binary, ?CR, ?LF, "0", ?CR, ?LF, Rest/binary>> -> - decode_trailer(<<?CR, ?LF, Rest/binary>>, [],[], MaxHeaderSize, + decode_trailer(<<?CR, ?LF, Rest/binary>>, [],[], <<BodySoFar/binary, Data/binary>>, - integer_to_list(AccLength)); - %% There are more chunks, so here we go agin... + integer_to_list(AccLength), MaxHeaderSize, MaxHeaderSize); + %% There are more chunks, so here we go again... <<Data:ChunkSize/binary, ?CR, ?LF>> -> NewBody = <<BodySoFar/binary, Data/binary>>, - {?MODULE, decode_size, [<<>>, [], {MaxBodySize, NewBody, AccLength, MaxHeaderSize}]}; + {?MODULE, decode_size, [<<>>, [], 0, {MaxBodySize, NewBody, AccLength, MaxHeaderSize}]}; <<Data:ChunkSize/binary, ?CR, ?LF, Rest/binary>> when (AccLength < MaxBodySize) or (MaxBodySize == nolimit) -> - decode_size(Rest, [], + decode_size(Rest, [], 0, {MaxBodySize, <<BodySoFar/binary, Data/binary>>, AccLength, MaxHeaderSize}); <<_:ChunkSize/binary, ?CR, ?LF, _/binary>> -> - throw({error, body_too_big}); + throw({error, {body_too_big, {max, MaxBodySize}}}); _ -> {?MODULE, decode_data, [ChunkSize, TotalChunk, Info]} end; decode_data(ChunkSize, TotalChunk, Info) -> {?MODULE, decode_data, [ChunkSize, TotalChunk, Info]}. -decode_trailer(<<>>, Header, Headers, MaxHeaderSize, Body, BodyLength) -> - {?MODULE, decode_trailer, [<<>>, Header, Headers, MaxHeaderSize, Body, - BodyLength]}; - +decode_trailer(_,_,_,_,_, 0, TotalMaxHeaderSize) -> + throw({error, {header_too_long, {max, TotalMaxHeaderSize}}}); +decode_trailer(<<>>, Header, Headers, Body, BodyLength, RemainingSize, TotalMaxHeaderSize) -> + {?MODULE, decode_trailer, [<<>>, Header, Headers, Body, + BodyLength, RemainingSize, TotalMaxHeaderSize]}; %% Note: If Bin is not empty it is part of a pipelined request/response. -decode_trailer(<<?CR,?LF,?CR,?LF, Bin/binary>>, [], [], _, Body, BodyLength) -> +decode_trailer(<<?CR,?LF,?CR,?LF, Bin/binary>>, [], [], Body, BodyLength, _, _) -> {ok, {["content-length:" ++ BodyLength], <<Body/binary, Bin/binary>>}}; decode_trailer(<<?CR,?LF,?CR,?LF, Bin/binary>>, - Header, Headers, MaxHeaderSize, Body, BodyLength) -> + Header, Headers, Body, BodyLength, _, _) -> NewHeaders = case Header of [] -> Headers; _ -> [lists:reverse(Header) | Headers] end, - Length = length(NewHeaders), - case Length > MaxHeaderSize of - true -> - throw({error, {header_too_long, MaxHeaderSize, - MaxHeaderSize-Length}}); - false -> - {ok, {["content-length:" ++ BodyLength | NewHeaders], - <<Body/binary, Bin/binary>>}} - end; -decode_trailer(<<?CR,?LF,?CR>> = Data, Header, Headers, MaxHeaderSize, - Body, BodyLength) -> - {?MODULE, decode_trailer, [Data, Header, Headers, MaxHeaderSize, Body, - BodyLength]}; -decode_trailer(<<?CR,?LF>> = Data, Header, Headers, MaxHeaderSize, - Body, BodyLength) -> - {?MODULE, decode_trailer, [Data, Header, Headers, MaxHeaderSize, Body, - BodyLength]}; -decode_trailer(<<?CR>> = Data, Header, Headers, MaxHeaderSize, - Body, BodyLength) -> - {?MODULE, decode_trailer, [Data, Header, Headers, MaxHeaderSize, Body, - BodyLength]}; -decode_trailer(<<?CR, ?LF, Rest/binary>>, Header, Headers, - MaxHeaderSize, Body, BodyLength) -> + {ok, {["content-length:" ++ BodyLength | NewHeaders], + <<Body/binary, Bin/binary>>}}; +decode_trailer(<<?CR,?LF,?CR>> = Data, Header, Headers, + Body, BodyLength, RemainingSize, TotalMaxHeaderSize) -> + {?MODULE, decode_trailer, [Data, Header, Headers, Body, + BodyLength, RemainingSize, TotalMaxHeaderSize]}; +decode_trailer(<<?CR,?LF>> = Data, Header, Headers, + Body, BodyLength, RemainingSize, TotalMaxHeaderSize) -> + {?MODULE, decode_trailer, [Data, Header, Headers, Body, + BodyLength, RemainingSize, TotalMaxHeaderSize]}; +decode_trailer(<<?CR>> = Data, Header, Headers, + Body, BodyLength, RemainingSize, TotalMaxHeaderSize) -> + {?MODULE, decode_trailer, [Data, Header, Headers, Body, + BodyLength, RemainingSize, TotalMaxHeaderSize]}; +decode_trailer(<<?CR, ?LF, Rest/binary>>, Header, Headers, Body, BodyLength, RemainingSize, TotalMaxHeaderSize) -> decode_trailer(Rest, [], [lists:reverse(Header) | Headers], - MaxHeaderSize, Body, BodyLength); + Body, BodyLength, RemainingSize, TotalMaxHeaderSize); +decode_trailer(<<Octet, Rest/binary>>, Header, Headers, Body, + BodyLength, RemainingSize, TotalMaxHeaderSize) -> + decode_trailer(Rest, [Octet | Header], Headers, + Body, BodyLength, remaing_size(RemainingSize, 1), TotalMaxHeaderSize). + +remaing_size(nolimit, _) -> + nolimit; +remaing_size(Total, Consumed) -> + Total - Consumed. -decode_trailer(<<Octet, Rest/binary>>, Header, Headers, MaxHeaderSize, Body, - BodyLength) -> - decode_trailer(Rest, [Octet | Header], Headers, MaxHeaderSize, - Body, BodyLength). +encode_trailers(Trailers) -> + encode_trailers(Trailers, ""). + +encode_trailers([], Acc) -> + Acc ++ ?CRLF ++ ?CRLF; +encode_trailers([{Header, Value} | Rest], Acc) -> + encode_trailers(Rest, Header ++ ":" ++ Value ++ ?CRLF ++ Acc). diff --git a/lib/inets/src/http_lib/http_response.erl b/lib/inets/src/http_lib/http_response.erl index 58b30c4e9e..42e5dd263d 100644 --- a/lib/inets/src/http_lib/http_response.erl +++ b/lib/inets/src/http_lib/http_response.erl @@ -31,16 +31,11 @@ %% Value - string() %% %% Description: Creates a http_response_h-record used internally to -%% handle http-headers. +%% handle http-headers, assumes reversed list of headers +%% to unfold multiline headers with obs-folds %%------------------------------------------------------------------------- -headers([], Headers) -> - Headers; - -headers([Header | Tail], Headers) -> - {Key, [$: | Value]} = - lists:splitwith(fun($:) -> false; (_) -> true end, Header), - headers(Tail, headers(http_util:to_lower(string:strip(Key)), - string:strip(Value), Headers)). +headers(RevLines, Headers) -> + fill_headers(RevLines, [], Headers). %%------------------------------------------------------------------------- %% headers(#http_response_h{}) -> HeaderList @@ -68,6 +63,25 @@ header_list(Headers) -> %%%======================================================================== %%% Internal functions %%%======================================================================== +fill_headers([], _, Headers) -> + Headers; +fill_headers([[]], _, Headers) -> + Headers; +fill_headers([[Ch|HeaderFold]|Tail], Folded, Headers) + when Ch == $\t; Ch == $\s -> + fill_headers(Tail, [HeaderFold|Folded], Headers); +fill_headers([Header | Tail], Folded, Headers) -> + Unfolded = unfold([Header|Folded]), + {Key, [$: | Value]} = + lists:splitwith(fun($:) -> false; (_) -> true end, Unfolded), + fill_headers(Tail, [], headers(http_util:to_lower(string:strip(Key)), + string:strip(Value), Headers)). + +unfold([L]) -> + L; +unfold(Folded) -> + string:join(Folded, " "). + headers("cache-control", Value, Headers) -> Headers#http_response_h{'cache-control'= Value}; headers("connection", Value, Headers) -> diff --git a/lib/inets/src/http_lib/http_transport.erl b/lib/inets/src/http_lib/http_transport.erl index 719dc4c425..ab6afe9c6c 100644 --- a/lib/inets/src/http_lib/http_transport.erl +++ b/lib/inets/src/http_lib/http_transport.erl @@ -40,12 +40,6 @@ -include_lib("inets/src/inets_app/inets_internal.hrl"). -include("http_internal.hrl"). --define(SERVICE, httpl). --define(hlri(Label, Content), ?report_important(Label, ?SERVICE, Content)). --define(hlrv(Label, Content), ?report_verbose(Label, ?SERVICE, Content)). --define(hlrd(Label, Content), ?report_debug(Label, ?SERVICE, Content)). --define(hlrt(Label, Content), ?report_trace(Label, ?SERVICE, Content)). - %%%========================================================================= %%% Internal application API @@ -55,38 +49,27 @@ %% start(SocketType) -> ok | {error, Reason} %% SocketType = ip_comm | {ssl, _} %% -%% Description: Makes sure inet_db or ssl is started. +%% Description: Makes sure ssl is started. %%------------------------------------------------------------------------- start(ip_comm) -> - do_start_ip_comm(); - -%% This is just for backward compatibillity + ok; +start({ip_comm, _}) -> + ok; start({ssl, _}) -> do_start_ssl(); start({essl, _}) -> do_start_ssl(). - -do_start_ip_comm() -> - case inet_db:start() of - {ok, _} -> - ok; - {error, {already_started, _}} -> - ok; - Error -> - Error - end. - do_start_ssl() -> - case ssl:start() of - ok -> - ok; - {error, {already_started,_}} -> - ok; - Error -> - Error + try lists:foreach(fun(App) -> + ok = application:ensure_started(App) + end, + [crypto, asn1, public_key, ssl]) + catch + _:Reason -> + {error, Reason} end. - + %%------------------------------------------------------------------------- %% connect(SocketType, Address, Options, Timeout) -> @@ -103,12 +86,8 @@ do_start_ssl() -> connect(SocketType, Address, Opts) -> connect(SocketType, Address, Opts, infinity). - -connect(ip_comm = _SocketType, {Host, Port}, Opts0, Timeout) - when is_list(Opts0) -> - Opts = [binary, {packet, 0}, {active, false}, {reuseaddr, true} | Opts0], - ?hlrt("connect using gen_tcp", - [{host, Host}, {port, Port}, {opts, Opts}, {timeout, Timeout}]), +connect(ip_comm, {Host, Port}, Opts0, Timeout) -> + Opts = [binary, {packet, 0}, {active, false}, {reuseaddr, true} | Opts0 ], try gen_tcp:connect(Host, Port, Opts, Timeout) of {ok, _} = OK -> OK; @@ -127,11 +106,6 @@ connect({ssl, SslConfig}, Address, Opts, Timeout) -> connect({essl, SslConfig}, {Host, Port}, Opts0, Timeout) -> Opts = [binary, {active, false}, {ssl_imp, new} | Opts0] ++ SslConfig, - ?hlrt("connect using essl", - [{host, Host}, - {port, Port}, - {ssl_config, SslConfig}, - {timeout, Timeout}]), case (catch ssl:connect(Host, Port, Opts, Timeout)) of {'EXIT', Reason} -> {error, {eoptions, Reason}}; @@ -156,29 +130,23 @@ connect({essl, SslConfig}, {Host, Port}, Opts0, Timeout) -> %% reason for this to enable a HTTP-server not running as root to use %% port 80. %%------------------------------------------------------------------------- -listen(ip_comm = _SocketType, Addr, Port, Fd, IpFamily) -> - listen_ip_comm(Addr, Port, Fd, IpFamily); - +listen(ip_comm, Addr, Port, Fd, IpFamily) -> + listen_ip_comm(Addr, Port, [], Fd, IpFamily); + +listen({ip_comm, SockOpts}, Addr, Port, Fd, IpFamily) -> + listen_ip_comm(Addr, Port, SockOpts, Fd, IpFamily); + listen({essl, SSLConfig}, Addr, Port, Fd, IpFamily) -> listen_ssl(Addr, Port, Fd, SSLConfig, IpFamily, []). -listen(ip_comm = _SocketType, Addr, Port, IpFamily) -> - listen_ip_comm(Addr, Port, undefined, IpFamily); +listen(ip_comm, Addr, Port, IpFamily) -> + listen_ip_comm(Addr, Port, [], undefined, IpFamily); %% Wrapper for backaward compatibillity listen({ssl, SSLConfig}, Addr, Port, IpFamily) -> - ?hlrt("listen (wrapper)", - [{addr, Addr}, - {port, Port}, - {ssl_config, SSLConfig}]), listen({?HTTP_DEFAULT_SSL_KIND, SSLConfig}, Addr, Port, IpFamily); - listen({essl, SSLConfig}, Addr, Port, IpFamily) -> - ?hlrt("listen (essl)", - [{addr, Addr}, - {port, Port}, - {ssl_config, SSLConfig}]), {SSLConfig2, ExtraOpts} = case proplists:get_value(log_alert, SSLConfig, undefined) of undefined -> {SSLConfig, []}; @@ -187,83 +155,30 @@ listen({essl, SSLConfig}, Addr, Port, IpFamily) -> end, listen_ssl(Addr, Port, undefined, SSLConfig2, IpFamily, ExtraOpts). -listen_ip_comm(Addr, Port, Fd, IpFamily) -> - case (catch do_listen_ip_comm(Addr, Port, Fd, IpFamily)) of +listen_ip_comm(Addr, Port, SockOpts, Fd, IpFamily) -> + case (catch do_listen_ip_comm(Addr, Port, SockOpts, Fd, IpFamily)) of {'EXIT', Reason} -> {error, {exit, Reason}}; Else -> Else end. -do_listen_ip_comm(Addr, Port, Fd, IpFamily) -> - {NewPort, Opts} = get_socket_info(Addr, Port, Fd), - case IpFamily of - inet6fb4 -> - Opts2 = [inet6 | Opts], - ?hlrt("try ipv6 listen", [{port, NewPort}, {opts, Opts2}]), - case (catch gen_tcp:listen(NewPort, Opts2)) of - {error, Reason} when ((Reason =:= nxdomain) orelse - (Reason =:= eafnosupport)) -> - Opts3 = [inet | Opts], - ?hlrt("ipv6 listen failed - try ipv4 instead", - [{reason, Reason}, {port, NewPort}, {opts, Opts3}]), - gen_tcp:listen(NewPort, Opts3); - - %% This is when a given hostname has resolved to a - %% IPv4-address. The inet6-option together with a - %% {ip, IPv4} option results in badarg - {'EXIT', Reason} -> - Opts3 = [inet | Opts], - ?hlrt("ipv6 listen exit - try ipv4 instead", - [{reason, Reason}, {port, NewPort}, {opts, Opts3}]), - gen_tcp:listen(NewPort, Opts3); - - Other -> - ?hlrt("ipv6 listen done", [{other, Other}]), - Other - end; - _ -> - Opts2 = [IpFamily | Opts], - ?hlrt("listen", [{port, NewPort}, {opts, Opts2}]), - gen_tcp:listen(NewPort, Opts2) - end. +do_listen_ip_comm(Addr, Port, SockOpts, Fd, IpFamily) -> + Backlog = proplists:get_value(backlog, SockOpts, 128), + {NewPort, Opts} = get_socket_info(Addr, Port, Fd, + [{backlog, Backlog}, {reuseaddr, true} | SockOpts]), + Opts2 = [IpFamily | Opts], + gen_tcp:listen(NewPort, Opts2). listen_ssl(Addr, Port, Fd, Opts0, IpFamily, ExtraOpts) -> - {NewPort, SockOpt} = get_socket_info(Addr, Port, Fd), + Backlog = proplists:get_value(backlog, Opts0, 128), + {NewPort, SockOpt} = get_socket_info(Addr, Port, Fd, + [{backlog, Backlog}, {reuseaddr, true}]), Opts = SockOpt ++ Opts0, - case IpFamily of - inet6fb4 -> - Opts2 = [inet6 | Opts] ++ ExtraOpts, - ?hlrt("try ipv6 listen", [{opts, Opts2}]), - case (catch ssl:listen(Port, Opts2)) of - {error, Reason} when ((Reason =:= nxdomain) orelse - (Reason =:= eafnosupport)) -> - Opts3 = [inet | Opts] ++ ExtraOpts, - ?hlrt("ipv6 listen failed - try ipv4 instead", - [{reason, Reason}, {opts, Opts3}]), - ssl:listen(NewPort, Opts3); - - {'EXIT', Reason} -> - Opts3 = [inet | Opts] ++ ExtraOpts, - ?hlrt("ipv6 listen exit - try ipv4 instead", - [{reason, Reason}, {opts, Opts3}]), - ssl:listen(NewPort, Opts3); - - Other -> - ?hlrt("ipv6 listen done", [{other, Other}]), - Other - end; - - _ -> - Opts2 = [IpFamily | Opts], - ?hlrt("listen", [{opts, Opts2}]), - ssl:listen(NewPort, Opts2 ++ ExtraOpts) - end. + Opts2 = [IpFamily | Opts], + ssl:listen(NewPort, Opts2 ++ ExtraOpts). - - -get_socket_info(Addr, Port, Fd) -> - BaseOpts = [{backlog, 128}, {reuseaddr, true}], +get_socket_info(Addr, Port, Fd, BaseOpts) -> %% The presence of a file descriptor takes precedence case Fd of undefined -> @@ -288,6 +203,8 @@ accept(SocketType, ListenSocket) -> accept(ip_comm, ListenSocket, Timeout) -> gen_tcp:accept(ListenSocket, Timeout); +accept({ip_comm, _}, ListenSocket, Timeout) -> + gen_tcp:accept(ListenSocket, Timeout); %% Wrapper for backaward compatibillity accept({ssl, SSLConfig}, ListenSocket, Timeout) -> @@ -307,6 +224,8 @@ accept({essl, _SSLConfig}, ListenSocket, Timeout) -> %%------------------------------------------------------------------------- controlling_process(ip_comm, Socket, NewOwner) -> gen_tcp:controlling_process(Socket, NewOwner); +controlling_process({ip_comm, _}, Socket, NewOwner) -> + gen_tcp:controlling_process(Socket, NewOwner); %% Wrapper for backaward compatibillity controlling_process({ssl, SSLConfig}, Socket, NewOwner) -> @@ -325,7 +244,8 @@ controlling_process({essl, _}, Socket, NewOwner) -> %% gen_tcp or ssl. %%------------------------------------------------------------------------- setopts(ip_comm, Socket, Options) -> - ?hlrt("ip_comm setopts", [{socket, Socket}, {options, Options}]), + inet:setopts(Socket, Options); +setopts({ip_comm, _}, Socket, Options) -> inet:setopts(Socket, Options); %% Wrapper for backaward compatibillity @@ -333,10 +253,7 @@ setopts({ssl, SSLConfig}, Socket, Options) -> setopts({?HTTP_DEFAULT_SSL_KIND, SSLConfig}, Socket, Options); setopts({essl, _}, Socket, Options) -> - ?hlrt("[e]ssl setopts", [{socket, Socket}, {options, Options}]), - Reason = (catch ssl:setopts(Socket, Options)), - ?hlrt("[e]ssl setopts result", [{reason, Reason}]), - Reason. + (catch ssl:setopts(Socket, Options)). %%------------------------------------------------------------------------- @@ -350,8 +267,10 @@ getopts(SocketType, Socket) -> Opts = [packet, packet_size, recbuf, sndbuf, priority, tos, send_timeout], getopts(SocketType, Socket, Opts). +getopts({ip_comm, _}, Socket, Options) -> + getopts(ip_comm, Socket, Options); + getopts(ip_comm, Socket, Options) -> - ?hlrt("ip_comm getopts", [{socket, Socket}, {options, Options}]), case inet:getopts(Socket, Options) of {ok, SocketOpts} -> SocketOpts; @@ -364,7 +283,6 @@ getopts({ssl, SSLConfig}, Socket, Options) -> getopts({?HTTP_DEFAULT_SSL_KIND, SSLConfig}, Socket, Options); getopts({essl, _}, Socket, Options) -> - ?hlrt("essl getopts", [{socket, Socket}, {options, Options}]), getopts_ssl(Socket, Options). getopts_ssl(Socket, Options) -> @@ -384,7 +302,6 @@ getopts_ssl(Socket, Options) -> %% Description: Gets the socket stats values for the socket %%------------------------------------------------------------------------- getstat(ip_comm = _SocketType, Socket) -> - ?hlrt("ip_comm getstat", [{socket, Socket}]), case inet:getstat(Socket) of {ok, Stats} -> Stats; @@ -409,6 +326,8 @@ getstat({essl, _} = _SocketType, _Socket) -> %%------------------------------------------------------------------------- send(ip_comm, Socket, Message) -> gen_tcp:send(Socket, Message); +send({ip_comm, _}, Socket, Message) -> + gen_tcp:send(Socket, Message); %% Wrapper for backaward compatibillity send({ssl, SSLConfig}, Socket, Message) -> @@ -417,7 +336,6 @@ send({ssl, SSLConfig}, Socket, Message) -> send({essl, _}, Socket, Message) -> ssl:send(Socket, Message). - %%------------------------------------------------------------------------- %% close(SocketType, Socket) -> ok | {error, Reason} %% SocketType = ip_comm | {ssl, _} @@ -427,6 +345,8 @@ send({essl, _}, Socket, Message) -> %%------------------------------------------------------------------------- close(ip_comm, Socket) -> gen_tcp:close(Socket); +close({ip_comm, []}, Socket) -> + gen_tcp:close(Socket); %% Wrapper for backaward compatibillity close({ssl, SSLConfig}, Socket) -> @@ -448,6 +368,8 @@ close({essl, _}, Socket) -> %%------------------------------------------------------------------------- peername(ip_comm, Socket) -> do_peername(inet:peername(Socket)); +peername({ip_comm, _}, Socket) -> + do_peername(inet:peername(Socket)); %% Wrapper for backaward compatibillity peername({ssl, SSLConfig}, Socket) -> @@ -480,7 +402,8 @@ do_peername({error, _}) -> %%------------------------------------------------------------------------- sockname(ip_comm, Socket) -> do_sockname(inet:sockname(Socket)); - +sockname({ip_comm, _}, Socket) -> + do_sockname(inet:sockname(Socket)); %% Wrapper for backaward compatibillity sockname({ssl, SSLConfig}, Socket) -> sockname({?HTTP_DEFAULT_SSL_KIND, SSLConfig}, Socket); @@ -555,28 +478,13 @@ sock_opts(Opts) -> %% -- negotiate -- negotiate(ip_comm,_,_) -> - ?hlrt("negotiate(ip_comm)", []), + ok; +negotiate({ip_comm, _},_,_) -> ok; negotiate({ssl, SSLConfig}, Socket, Timeout) -> - ?hlrt("negotiate(ssl)", []), negotiate({?HTTP_DEFAULT_SSL_KIND, SSLConfig}, Socket, Timeout); negotiate({essl, _}, Socket, Timeout) -> - ?hlrt("negotiate(essl)", []), negotiate_ssl(Socket, Timeout). negotiate_ssl(Socket, Timeout) -> - ?hlrt("negotiate_ssl", [{socket, Socket}, {timeout, Timeout}]), - case ssl:ssl_accept(Socket, Timeout) of - ok -> - ok; - {error, Reason} -> - ?hlrd("negotiate_ssl - accept failed", [{reason, Reason}]), - %% Look for "valid" error reasons - ValidReasons = [timeout, econnreset, esslaccept, esslerrssl], - case lists:member(Reason, ValidReasons) of - true -> - {error, normal}; - false -> - {error, Reason} - end - end. + ssl:ssl_accept(Socket, Timeout). diff --git a/lib/inets/src/http_lib/http_uri.erl b/lib/inets/src/http_lib/http_uri.erl index bd8ca5f489..9940136f5a 100644 --- a/lib/inets/src/http_lib/http_uri.erl +++ b/lib/inets/src/http_lib/http_uri.erl @@ -138,16 +138,33 @@ parse_scheme(AbsURI, Opts) -> {error, no_scheme} -> {error, no_scheme}; {SchemeStr, Rest} -> - Scheme = list_to_atom(http_util:to_lower(SchemeStr)), - SchemeDefaults = which_scheme_defaults(Opts), - case lists:keysearch(Scheme, 1, SchemeDefaults) of - {value, {Scheme, DefaultPort}} -> - {Scheme, DefaultPort, Rest}; - false -> - {Scheme, no_default_port, Rest} + case extract_scheme(SchemeStr, Opts) of + {error, Error} -> + {error, Error}; + {ok, Scheme} -> + SchemeDefaults = which_scheme_defaults(Opts), + case lists:keysearch(Scheme, 1, SchemeDefaults) of + {value, {Scheme, DefaultPort}} -> + {Scheme, DefaultPort, Rest}; + false -> + {Scheme, no_default_port, Rest} + end end end. +extract_scheme(Str, Opts) -> + case lists:keysearch(scheme_validation_fun, 1, Opts) of + {value, {scheme_validation_fun, Fun}} when is_function(Fun) -> + case Fun(Str) of + valid -> + {ok, list_to_atom(http_util:to_lower(Str))}; + {error, Error} -> + {error, Error} + end; + _ -> + {ok, list_to_atom(http_util:to_lower(Str))} + end. + parse_uri_rest(Scheme, DefaultPort, "//" ++ URIPart, Opts) -> {Authority, PathQueryFragment} = split_uri(URIPart, "[/?#]", {URIPart, ""}, 1, 0), diff --git a/lib/inets/src/http_lib/http_util.erl b/lib/inets/src/http_lib/http_util.erl index 0d07231302..aafa97afee 100644 --- a/lib/inets/src/http_lib/http_util.erl +++ b/lib/inets/src/http_lib/http_util.erl @@ -152,27 +152,11 @@ convert_netscapecookie_date([_D,_A,_Y, _SP, Sec=list_to_integer([S1,S2]), {{Year,Month,Day},{Hour,Min,Sec}}. -hexlist_to_integer([]) -> - empty; -%%When the string only contains one value its eaasy done. -%% 0-9 -hexlist_to_integer([Size]) when (Size >= 48) andalso (Size =< 57) -> - Size - 48; -%% A-F -hexlist_to_integer([Size]) when (Size >= 65) andalso (Size =< 70) -> - Size - 55; -%% a-f -hexlist_to_integer([Size]) when (Size >= 97) andalso (Size =< 102) -> - Size - 87; -hexlist_to_integer([_Size]) -> - not_a_num; +hexlist_to_integer(List) -> + list_to_integer(List, 16). -hexlist_to_integer(Size) -> - Len = string:span(Size, "1234567890abcdefABCDEF"), - hexlist_to_integer2(Size, 16 bsl (4 *(Len-2)),0). - -integer_to_hexlist(Num)-> - integer_to_hexlist(Num, get_size(Num), []). +integer_to_hexlist(Int) -> + integer_to_list(Int, 16). convert_month("Jan") -> 1; convert_month("Feb") -> 2; @@ -213,51 +197,6 @@ html_encode(Chars) -> %%%======================================================================== %%% Internal functions %%%======================================================================== -hexlist_to_integer2([],_Pos,Sum)-> - Sum; -hexlist_to_integer2([HexVal | HexString], Pos, Sum) - when HexVal >= 48, HexVal =< 57 -> - hexlist_to_integer2(HexString, Pos bsr 4, Sum + ((HexVal-48) * Pos)); - -hexlist_to_integer2([HexVal | HexString], Pos, Sum) - when HexVal >= 65, HexVal =<70 -> - hexlist_to_integer2(HexString, Pos bsr 4, Sum + ((HexVal-55) * Pos)); - -hexlist_to_integer2([HexVal | HexString], Pos, Sum) - when HexVal>=97, HexVal=<102 -> - hexlist_to_integer2(HexString, Pos bsr 4, Sum + ((HexVal-87) * Pos)); - -hexlist_to_integer2(_AfterHexString, _Pos, Sum)-> - Sum. - -integer_to_hexlist(Num, Pot, Res) when Pot < 0 -> - convert_to_ascii([Num | Res]); - -integer_to_hexlist(Num,Pot,Res) -> - Position = (16 bsl (Pot*4)), - PosVal = Num div Position, - integer_to_hexlist(Num - (PosVal*Position), Pot-1, [PosVal | Res]). - -get_size(Num)-> - get_size(Num, 0). - -get_size(Num, Pot) when Num < (16 bsl(Pot *4)) -> - Pot-1; - -get_size(Num, Pot) -> - get_size(Num, Pot+1). - -convert_to_ascii(RevesedNum) -> - convert_to_ascii(RevesedNum, []). - -convert_to_ascii([], Num)-> - Num; -convert_to_ascii([Num | Reversed], Number) - when (Num > -1) andalso (Num < 10) -> - convert_to_ascii(Reversed, [Num + 48 | Number]); -convert_to_ascii([Num | Reversed], Number) - when (Num > 9) andalso (Num < 16) -> - convert_to_ascii(Reversed, [Num + 55 | Number]). char_to_html_entity(Char, Reserved) -> case sets:is_element(Char, Reserved) of diff --git a/lib/inets/src/http_server/Makefile b/lib/inets/src/http_server/Makefile index b09877550d..1c05d454a5 100644 --- a/lib/inets/src/http_server/Makefile +++ b/lib/inets/src/http_server/Makefile @@ -40,6 +40,10 @@ RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN) # ---------------------------------------------------- # Target Specs # ---------------------------------------------------- + +BEHAVIOUR_MODULES= \ + httpd_custom_api + MODULES = \ httpd \ httpd_acceptor \ @@ -86,10 +90,13 @@ MODULES = \ HRL_FILES = httpd.hrl httpd_internal.hrl mod_auth.hrl -ERL_FILES = $(MODULES:%=%.erl) +ERL_FILES = $(MODULES:%=%.erl)\ + $(BEHAVIOUR_MODULES:%=%.erl) TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR)) +BEHAVIOUR_TARGET_FILES= $(BEHAVIOUR_MODULES:%=$(EBIN)/%.$(EMULATOR)) + INETS_FLAGS = -D'SERVER_SOFTWARE="$(APPLICATION)/$(VSN)"' @@ -109,11 +116,12 @@ ERL_COMPILE_FLAGS += \ # ---------------------------------------------------- # Targets # ---------------------------------------------------- +$(TARGET_FILES): $(BEHAVIOUR_TARGET_FILES) debug opt: $(TARGET_FILES) clean: - rm -f $(TARGET_FILES) + rm -f $(TARGET_FILES) $(BEHAVIOUR_TARGET_FILES) rm -f core docs: @@ -129,7 +137,7 @@ release_spec: opt $(INSTALL_DIR) "$(RELSYSDIR)/src/http_server" $(INSTALL_DATA) $(HRL_FILES) $(ERL_FILES) "$(RELSYSDIR)/src/http_server" $(INSTALL_DIR) "$(RELSYSDIR)/ebin" - $(INSTALL_DATA) $(TARGET_FILES) "$(RELSYSDIR)/ebin" + $(INSTALL_DATA) $(TARGET_FILES) $(BEHAVIOUR_TARGET_FILES) "$(RELSYSDIR)/ebin" release_docs_spec: diff --git a/lib/inets/src/http_server/httpd_conf.erl b/lib/inets/src/http_server/httpd_conf.erl index d592984669..a7783bc1e9 100644 --- a/lib/inets/src/http_server/httpd_conf.erl +++ b/lib/inets/src/http_server/httpd_conf.erl @@ -156,7 +156,7 @@ load("BindAddress " ++ Address0, []) -> case string:tokens(Address0, [$|]) of [Address1] -> ?hdrv("load BindAddress", [{address1, Address1}]), - {clean_address(Address1), inet6fb4}; + {clean_address(Address1), inet}; [Address1, IpFamilyStr] -> ?hdrv("load BindAddress", [{address1, Address1}, @@ -353,14 +353,21 @@ clean_address(Addr) -> make_ipfamily(IpFamilyStr) -> - IpFamily = list_to_atom(IpFamilyStr), - case lists:member(IpFamily, [inet, inet6, inet6fb4]) of - true -> - IpFamily; - false -> - throw({error, {bad_ipfamily, IpFamilyStr}}) - end. - + validate_ipfamily(list_to_atom(IpFamilyStr)). + +validate_ipfamily(inet) -> + inet; +validate_ipfamily(inet6) -> + inet6; +%% Backwards compatibility wrapper, +%% fallback to the default, IPV4, +%% as it will most proably work. +%% IPv6 standard moved away from +%% beeing able to fallback to ipv4 +validate_ipfamily(inet6fb4) -> + inet; +validate_ipfamily(IpFamilyStr) -> + throw({error, {bad_ipfamily, IpFamilyStr}}). %% %% load_mime_types/1 -> {ok, MimeTypes} | {error, Reason} @@ -393,20 +400,16 @@ validate_properties2(Properties) -> undefined -> case proplists:get_value(sock_type, Properties, ip_comm) of ip_comm -> - case proplists:get_value(ipfamily, Properties) of - undefined -> - [{bind_address, any}, - {ipfamily, inet6fb4} | Properties]; - _ -> - [{bind_address, any} | Properties] - end; + add_inet_defaults(Properties); + {ip_comm, _} -> + add_inet_defaults(Properties); _ -> [{bind_address, any} | Properties] end; any -> Properties; Address0 -> - IpFamily = proplists:get_value(ipfamily, Properties, inet6fb4), + IpFamily = proplists:get_value(ipfamily, Properties, inet), case httpd_util:ip_address(Address0, IpFamily) of {ok, Address} -> Properties1 = proplists:delete(bind_address, Properties), @@ -418,6 +421,16 @@ validate_properties2(Properties) -> throw(Error) end end. + +add_inet_defaults(Properties) -> + case proplists:get_value(ipfamily, Properties) of + undefined -> + [{bind_address, any}, + {ipfamily, inet} | Properties]; + _ -> + [{bind_address, any} | Properties] + end. + check_minimum_bytes_per_second(Properties) -> case proplists:get_value(minimum_bytes_per_second, Properties, false) of false -> @@ -487,12 +500,11 @@ validate_config_params([{server_tokens, Value} | _]) -> validate_config_params([{socket_type, ip_comm} | Rest]) -> validate_config_params(Rest); -validate_config_params([{socket_type, Value} | Rest]) - 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([{socket_type, {Value, Opts}} | Rest]) when Value == ip_comm; + Value == ssl; + Value == essl -> + %% Make sure not to set socket values used internaly + validate_config_params(Opts), validate_config_params(Rest); validate_config_params([{socket_type, Value} | _]) -> @@ -622,21 +634,32 @@ validate_config_params([{disable_chunked_transfer_encoding_send, Value} | validate_config_params([{disable_chunked_transfer_encoding_send, Value} | _ ]) -> throw({disable_chunked_transfer_encoding_send, Value}); +validate_config_params([{Name, _} = Opt | _]) when Name == packet; + Name == mode; + Name == active; + Name == reuseaddr -> + throw({internaly_handled_opt_can_not_be_set, Opt}); validate_config_params([_| Rest]) -> validate_config_params(Rest). -%% It is actually pointless to check bind_address in this way since -%% we need ipfamily to do it properly... is_bind_address(any) -> true; is_bind_address(Value) -> - case httpd_util:ip_address(Value, inet6fb4) of + case is_bind_address(Value, inet) of + false -> + is_bind_address(Value, inet6); + True -> + True + end. + +is_bind_address(Value, IpFamily) -> + case httpd_util:ip_address(Value, IpFamily) of {ok, _} -> true; _ -> false end. - + store(ConfigList0) -> ?hdrd("store", []), try validate_config_params(ConfigList0) of @@ -776,28 +799,6 @@ remove(ConfigDB) -> ets:delete(ConfigDB), ok. -%% config(ConfigDB) -> -%% case httpd_util:lookup(ConfigDB, socket_type, ip_comm) of -%% ssl -> -%% case ssl_certificate_file(ConfigDB) of -%% undefined -> -%% {error, -%% "Directive SSLCertificateFile " -%% "not found in the config file"}; -%% SSLCertificateFile -> -%% {ssl, -%% SSLCertificateFile++ -%% ssl_certificate_key_file(ConfigDB)++ -%% ssl_verify_client(ConfigDB)++ -%% ssl_ciphers(ConfigDB)++ -%% ssl_password(ConfigDB)++ -%% ssl_verify_depth(ConfigDB)++ -%% ssl_ca_certificate_file(ConfigDB)} -%% end; -%% ip_comm -> -%% ip_comm -%% end. - get_config(Address, Port, Profile) -> Tab = httpd_util:make_name("httpd_conf", Address, Port, Profile), @@ -836,6 +837,8 @@ lookup_socket_type(ConfigDB) -> case httpd_util:lookup(ConfigDB, socket_type, ip_comm) of ip_comm -> ip_comm; + {ip_comm, _} = Type -> + Type; {Tag, Conf} -> {Tag, Conf}; SSL when (SSL =:= ssl) orelse (SSL =:= essl) -> diff --git a/lib/inets/src/http_server/httpd_custom.erl b/lib/inets/src/http_server/httpd_custom.erl index a1fe058bd1..2b9701ef75 100644 --- a/lib/inets/src/http_server/httpd_custom.erl +++ b/lib/inets/src/http_server/httpd_custom.erl @@ -20,16 +20,27 @@ %% -module(httpd_custom). --export([response_header/1, request_header/1]). --export([customize_headers/3]). +-export([response_header/1, request_header/1, response_default_headers/0]). +-export([customize_headers/3, response_default_headers/1]). --include_lib("inets/src/inets_app/inets_internal.hrl"). +-include("../inets_app/inets_internal.hrl"). + +-behaviour(httpd_custom_api). + +%%-------------------------------------------------------------------- +%% Behavior API ----------------------------------- +%%-------------------------------------------------------------------- response_header(Header) -> {true, httpify(Header)}. request_header(Header) -> {true, Header}. +response_default_headers() -> + []. +%%-------------------------------------------------------------------- +%% Internal API ----------------------------------- +%%-------------------------------------------------------------------- customize_headers(?MODULE, Function, Arg) -> ?MODULE:Function(Arg); customize_headers(Module, Function, Arg) -> @@ -43,6 +54,20 @@ customize_headers(Module, Function, Arg) -> ?MODULE:Function(Arg) end. +response_default_headers(?MODULE) -> + response_default_headers(); +response_default_headers(Module) -> + try Module:response_default_headers() of + Defaults -> + [{http_util:to_lower(Key), Value} || {Key, Value} <- Defaults, + is_list(Key), is_list(Value)] + catch + _:_ -> + ?MODULE:response_default_headers() + end. +%%-------------------------------------------------------------------- +%% Internal functions ----------------------------------- +%%-------------------------------------------------------------------- httpify({Key0, Value}) -> %% make sure first letter is capital (defacto standard) Words1 = string:tokens(Key0, "-"), diff --git a/lib/inets/src/http_server/httpd_custom_api.erl b/lib/inets/src/http_server/httpd_custom_api.erl new file mode 100644 index 0000000000..d5a6fa8715 --- /dev/null +++ b/lib/inets/src/http_server/httpd_custom_api.erl @@ -0,0 +1,32 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2015-2015. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +%% +-module(httpd_custom_api). + +-callback response_default_headers() -> + [{Key::string(), Value::string()}]. +-callback response_header({Key::string(), Value::string()}) -> + {true, {Key::string(), Value::string()}} | false | + {true, string()}. %% Used internally to avoid traversing headers twice +-callback request_header({Key::string(), Value::string()}) -> + {true, {Key::string(), Value::string()}} | false. + +-optional_callbacks([response_default_headers/0, response_header/1, + request_header/1]). diff --git a/lib/inets/src/http_server/httpd_example.erl b/lib/inets/src/http_server/httpd_example.erl index d729affd6d..0222487a4b 100644 --- a/lib/inets/src/http_server/httpd_example.erl +++ b/lib/inets/src/http_server/httpd_example.erl @@ -24,7 +24,7 @@ -export([newformat/3]). %% These are used by the inets test-suite --export([delay/1]). +-export([delay/1, chunk_timeout/3]). print(String) -> @@ -142,3 +142,11 @@ i(F) -> i(F,[]). i(F,A) -> io:format(F ++ "~n",A). sleep(T) -> receive after T -> ok end. + +%% ------------------------------------------------------ + +chunk_timeout(SessionID, _, StrInt) -> + mod_esi:deliver(SessionID, "Tranfer-Encoding:chunked/html\r\n\r\n"), + mod_esi:deliver(SessionID, top("Test chunk encoding timeout")), + timer:sleep(20000), + mod_esi:deliver(SessionID, footer()). diff --git a/lib/inets/src/http_server/httpd_request_handler.erl b/lib/inets/src/http_server/httpd_request_handler.erl index e5d006c1fd..134576059d 100644 --- a/lib/inets/src/http_server/httpd_request_handler.erl +++ b/lib/inets/src/http_server/httpd_request_handler.erl @@ -443,7 +443,7 @@ handle_body(#state{headers = Headers, body = Body, mod = ModData} = State, MaxHeaderSize, MaxBodySize) -> case Headers#http_request_h.'transfer-encoding' of "chunked" -> - case http_chunk:decode(Body, MaxBodySize, MaxHeaderSize) of + try http_chunk:decode(Body, MaxBodySize, MaxHeaderSize) of {Module, Function, Args} -> http_transport:setopts(ModData#mod.socket_type, ModData#mod.socket, @@ -455,6 +455,14 @@ handle_body(#state{headers = Headers, body = Body, mod = ModData} = State, http_chunk:handle_headers(Headers, ChunkedHeaders), handle_response(State#state{headers = NewHeaders, body = NewBody}) + catch + throw:Error -> + httpd_response:send_status(ModData, 400, + "Bad input"), + Reason = io_lib:format("Chunk decoding failed: ~p~n", + [Error]), + error_log(Reason, ModData), + {stop, normal, State#state{response_sent = true}} end; Encoding when is_list(Encoding) -> httpd_response:send_status(ModData, 501, @@ -622,21 +630,10 @@ decrease(N) when is_integer(N) -> decrease(N) -> N. -error_log(ReasonString, Info) -> +error_log(ReasonString, #mod{config_db = ConfigDB}) -> Error = lists:flatten( io_lib:format("Error reading request: ~s", [ReasonString])), - error_log(mod_log, Info, Error), - error_log(mod_disk_log, Info, Error). - -error_log(Mod, #mod{config_db = ConfigDB} = Info, String) -> - Modules = httpd_util:lookup(ConfigDB, modules, - [mod_get, mod_head, mod_log]), - case lists:member(Mod, Modules) of - true -> - Mod:error_log(Info, String); - _ -> - ok - end. + httpd_util:error_log(ConfigDB, Error). %%-------------------------------------------------------------------- diff --git a/lib/inets/src/http_server/httpd_response.erl b/lib/inets/src/http_server/httpd_response.erl index 7e73da7060..c0b5f09faf 100644 --- a/lib/inets/src/http_server/httpd_response.erl +++ b/lib/inets/src/http_server/httpd_response.erl @@ -20,8 +20,8 @@ %% -module(httpd_response). -export([generate_and_send_response/1, send_status/3, send_header/3, - send_body/3, send_chunk/3, send_final_chunk/2, split_header/2, - is_disable_chunked_send/1, cache_headers/2]). + send_body/3, send_chunk/3, send_final_chunk/2, send_final_chunk/3, + split_header/2, is_disable_chunked_send/1, cache_headers/2]). -export([map_status_code/2]). -include_lib("inets/src/inets_app/inets_internal.hrl"). @@ -89,8 +89,7 @@ traverse_modules(ModData,[Module|Rest]) -> "~n Error: ~p" "~n Stack trace: ~p", [Module, T, E, ?STACK()])), - report_error(mod_log, ModData#mod.config_db, String), - report_error(mod_disk_log, ModData#mod.config_db, String), + httpd_util:error_log(ModData#mod.config_db, String), send_status(ModData, 500, none), done end. @@ -245,7 +244,6 @@ send_chunk(_, <<>>, _) -> ok; send_chunk(_, [], _) -> ok; - send_chunk(#mod{http_version = "HTTP/1.1", socket_type = Type, socket = Sock}, Response0, false) -> Response = http_chunk:encode(Response0), @@ -254,10 +252,13 @@ send_chunk(#mod{http_version = "HTTP/1.1", send_chunk(#mod{socket_type = Type, socket = Sock} = _ModData, Response, _) -> httpd_socket:deliver(Type, Sock, Response). +send_final_chunk(Mod, IsDisableChunkedSend) -> + send_final_chunk(Mod, [], IsDisableChunkedSend). + send_final_chunk(#mod{http_version = "HTTP/1.1", - socket_type = Type, socket = Sock}, false) -> - httpd_socket:deliver(Type, Sock, http_chunk:encode_last()); -send_final_chunk(#mod{socket_type = Type, socket = Sock}, _) -> + socket_type = Type, socket = Sock}, Trailers, false) -> + httpd_socket:deliver(Type, Sock, http_chunk:encode_last(Trailers)); +send_final_chunk(#mod{socket_type = Type, socket = Sock}, _, _) -> httpd_socket:close(Type, Sock). is_disable_chunked_send(Db) -> @@ -287,14 +288,21 @@ create_header(ConfigDb, KeyValueTupleHeaders) -> Date = httpd_util:rfc1123_date(), ContentType = "text/html", Server = server(ConfigDb), - Headers0 = add_default_headers([{"date", Date}, - {"content-type", ContentType} - | if Server=="" -> []; - true -> [{"server", Server}] - end - ], - KeyValueTupleHeaders), CustomizeCB = httpd_util:lookup(ConfigDb, customize, httpd_custom), + + CustomDefaults = httpd_custom:response_default_headers(CustomizeCB), + SystemDefaultes = ([{"date", Date}, + {"content-type", ContentType} + | if Server=="" -> []; + true -> [{"server", Server}] + end + ]), + + %% System defaults not present in custom defaults will be added + %% to defaults + Defaults = add_default_headers(SystemDefaultes, CustomDefaults), + + Headers0 = add_default_headers(Defaults, KeyValueTupleHeaders), lists:filtermap(fun(H) -> httpd_custom:customize_headers(CustomizeCB, response_header, H) end, @@ -390,16 +398,6 @@ send_response_old(#mod{socket_type = Type, content_length(Body)-> integer_to_list(httpd_util:flatlength(Body)). -report_error(Mod, ConfigDB, Error) -> - Modules = httpd_util:lookup(ConfigDB, modules, - [mod_get, mod_head, mod_log]), - case lists:member(Mod, Modules) of - true -> - Mod:report_error(ConfigDB, Error); - _ -> - ok - end. - handle_headers([], NewHeaders) -> {ok, NewHeaders}; diff --git a/lib/inets/src/http_server/httpd_sup.erl b/lib/inets/src/http_server/httpd_sup.erl index f0b1942e2f..bf40cedd5c 100644 --- a/lib/inets/src/http_server/httpd_sup.erl +++ b/lib/inets/src/http_server/httpd_sup.erl @@ -241,7 +241,7 @@ listen(Address, Port, Config) -> case http_transport:start(SocketType) of ok -> {ok, Fd} = get_fd(Port), - IpFamily = proplists:get_value(ipfamily, Config, inet6fb4), + IpFamily = proplists:get_value(ipfamily, Config, inet), case http_transport:listen(SocketType, Address, Port, Fd, IpFamily) of {ok, ListenSocket} -> NewConfig = proplists:delete(port, Config), @@ -286,6 +286,8 @@ socket_type(Config) -> socket_type(ip_comm = SocketType, _) -> SocketType; +socket_type({ip_comm, _} = SocketType, _) -> + SocketType; socket_type({essl, _} = SocketType, _) -> SocketType; socket_type(_, Config) -> diff --git a/lib/inets/src/http_server/httpd_util.erl b/lib/inets/src/http_server/httpd_util.erl index 9133309689..6dd6db6a0c 100644 --- a/lib/inets/src/http_server/httpd_util.erl +++ b/lib/inets/src/http_server/httpd_util.erl @@ -31,7 +31,7 @@ convert_netscapecookie_date/1, enable_debug/1, valid_options/3, modules_validate/1, module_validate/1, dir_validate/2, file_validate/2, mime_type_validate/1, - mime_types_validate/1, custom_date/0]). + mime_types_validate/1, custom_date/0, error_log/2]). -export([encode_hex/1, decode_hex/1]). -include_lib("kernel/include/file.hrl"). @@ -42,17 +42,7 @@ ip_address({_,_,_,_,_,_,_,_} = Address, _IpFamily) -> {ok, Address}; ip_address(Host, IpFamily) when ((IpFamily =:= inet) orelse (IpFamily =:= inet6)) -> - inet:getaddr(Host, IpFamily); -ip_address(Host, inet6fb4 = _IpFamily) -> - Inet = case gen_tcp:listen(0, [inet6]) of - {ok, Dummyport} -> - gen_tcp:close(Dummyport), - inet6; - _ -> - inet - end, - inet:getaddr(Host, Inet). - + inet:getaddr(Host, IpFamily). %% lookup @@ -769,3 +759,17 @@ do_enable_debug([{Level,Modules}|Rest]) ok end, do_enable_debug(Rest). + +error_log(ConfigDb, Error) -> + error_log(mod_log, ConfigDb, Error), + error_log(mod_disk_log, ConfigDb, Error). + +error_log(Mod, ConfigDB, Error) -> + Modules = httpd_util:lookup(ConfigDB, modules, + [mod_get, mod_head, mod_log]), + case lists:member(Mod, Modules) of + true -> + Mod:report_error(ConfigDB, Error); + _ -> + ok + end. diff --git a/lib/inets/src/http_server/mod_auth_server.erl b/lib/inets/src/http_server/mod_auth_server.erl index 3685c2e617..7d1e1a3431 100644 --- a/lib/inets/src/http_server/mod_auth_server.erl +++ b/lib/inets/src/http_server/mod_auth_server.erl @@ -316,7 +316,7 @@ lookup(Db, Key) -> make_name(Addr, Port, Profile) -> - httpd_util:make_name(?MODULE, Addr, Port, Profile). + httpd_util:make_name(?MODULE_STRING, Addr, Port, Profile). call(Name, Req) -> diff --git a/lib/inets/src/http_server/mod_esi.erl b/lib/inets/src/http_server/mod_esi.erl index f8baa90fd4..967bd6bbf3 100644 --- a/lib/inets/src/http_server/mod_esi.erl +++ b/lib/inets/src/http_server/mod_esi.erl @@ -377,7 +377,6 @@ erl_scheme_webpage_chunk(Mod, Func, Env, Input, ModData) -> end), Response = deliver_webpage_chunk(ModData, Pid), - process_flag(trap_exit,false), Response. @@ -419,7 +418,6 @@ deliver_webpage_chunk(#mod{config_db = Db} = ModData, Pid, Timeout) -> ?hdrv("deliver_webpage_chunk - timeout", []), send_headers(ModData, 504, [{"connection", "close"}]), httpd_socket:close(ModData#mod.socket_type, ModData#mod.socket), - process_flag(trap_exit,false), {proceed,[{response, {already_sent, 200, 0}} | ModData#mod.data]} end. @@ -447,7 +445,6 @@ send_headers(ModData, StatusCode, HTTPHeaders) -> ExtraHeaders ++ HTTPHeaders). handle_body(_, #mod{method = "HEAD"} = ModData, _, _, Size, _) -> - process_flag(trap_exit,false), {proceed, [{response, {already_sent, 200, Size}} | ModData#mod.data]}; handle_body(Pid, ModData, Body, Timeout, Size, IsDisableChunkedSend) -> @@ -455,34 +452,54 @@ handle_body(Pid, ModData, Body, Timeout, Size, IsDisableChunkedSend) -> httpd_response:send_chunk(ModData, Body, IsDisableChunkedSend), receive {esi_data, Data} when is_binary(Data) -> - ?hdrt("handle_body - received binary data (esi)", []), handle_body(Pid, ModData, Data, Timeout, Size + byte_size(Data), IsDisableChunkedSend); {esi_data, Data} -> - ?hdrt("handle_body - received data (esi)", []), handle_body(Pid, ModData, Data, Timeout, Size + length(Data), IsDisableChunkedSend); {ok, Data} -> - ?hdrt("handle_body - received data (ok)", []), handle_body(Pid, ModData, Data, Timeout, Size + length(Data), IsDisableChunkedSend); {'EXIT', Pid, normal} when is_pid(Pid) -> - ?hdrt("handle_body - exit:normal", []), httpd_response:send_final_chunk(ModData, IsDisableChunkedSend), {proceed, [{response, {already_sent, 200, Size}} | ModData#mod.data]}; {'EXIT', Pid, Reason} when is_pid(Pid) -> - ?hdrv("handle_body - exit", [{reason, Reason}]), - httpd_response:send_final_chunk(ModData, IsDisableChunkedSend), - exit({mod_esi_linked_process_died, Pid, Reason}) - + Error = lists:flatten(io_lib:format("mod_esi process failed with reason ~p", [Reason])), + httpd_util:error_log(ModData#mod.config_db, Error), + httpd_response:send_final_chunk(ModData, + [{"Warning", "199 inets server - body maybe incomplete, " + "internal server error"}], + IsDisableChunkedSend), + done after Timeout -> - ?hdrv("handle_body - timeout", []), - process_flag(trap_exit,false), - httpd_response:send_final_chunk(ModData, IsDisableChunkedSend), - exit({mod_esi_linked_process_timeout, Pid}) + kill_esi_delivery_process(Pid), + httpd_response:send_final_chunk(ModData, [{"Warning", "199 inets server - " + "body maybe incomplete, timed out"}], + IsDisableChunkedSend), + done end. +kill_esi_delivery_process(Pid) -> + exit(Pid, kill), + receive + {'EXIT', Pid, killed} -> + %% Clean message queue + receive + {esi_data, _} -> + ok + after 0 -> + ok + end, + receive + {ok, _} -> + ok + after 0 -> + ok + end + end. + + erl_script_timeout(Db) -> httpd_util:lookup(Db, erl_script_timeout, ?DEFAULT_ERL_TIMEOUT). diff --git a/lib/inets/src/http_server/mod_security_server.erl b/lib/inets/src/http_server/mod_security_server.erl index 81561493a0..f9281b0fdc 100644 --- a/lib/inets/src/http_server/mod_security_server.erl +++ b/lib/inets/src/http_server/mod_security_server.erl @@ -523,10 +523,10 @@ unblock_user(Info, User, Dir, Addr, Port, Profile, ETS, DETS, CBModule) -> ets:match_delete(ETS, {blocked_user, {User, Addr, Port, Profile, Dir, '_'}}). make_name(Addr,Port, Profile) -> - httpd_util:make_name(?MODULE,Addr,Port, Profile). + httpd_util:make_name(?MODULE_STRING, Addr, Port, Profile). make_name(Addr,Port, Profile, Num) -> - httpd_util:make_name(?MODULE,Addr,Port, + httpd_util:make_name(?MODULE_STRING, Addr,Port, atom_to_list(Profile) ++ "__" ++ integer_to_list(Num)). auth_fail_event(Mod,Addr,Port,Dir,User,Passwd) -> diff --git a/lib/inets/src/inets_app/inets.app.src b/lib/inets/src/inets_app/inets.app.src index e95f276bc4..2f213794a3 100644 --- a/lib/inets/src/inets_app/inets.app.src +++ b/lib/inets/src/inets_app/inets.app.src @@ -64,6 +64,7 @@ httpd_connection_sup, httpd_conf, httpd_custom, + httpd_custom_api, httpd_esi, httpd_example, httpd_file, diff --git a/lib/inets/test/http_format_SUITE.erl b/lib/inets/test/http_format_SUITE.erl index a97b51601f..e977bd1b9b 100644 --- a/lib/inets/test/http_format_SUITE.erl +++ b/lib/inets/test/http_format_SUITE.erl @@ -20,26 +20,12 @@ %% -module(http_format_SUITE). --author('[email protected]'). -include_lib("common_test/include/ct.hrl"). --include("test_server_line.hrl"). -include("http_internal.hrl"). -%% Test server specific exports --export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, init_per_group/2,end_per_group/2, init_per_testcase/2, end_per_testcase/2]). - -%% Test cases must be exported. --export([ chunk_decode/1, chunk_encode/1, - chunk_extensions_otp_6005/1, chunk_decode_otp_6264/1, - chunk_decode_empty_chunk_otp_6511/1, - 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, - check_content_length_encoding/1]). - -suite() -> [{ct_hooks,[ts_install_cth]}]. +%% Note: This directive should only be used in test suites. +-compile(export_all). all() -> [{group, chunk}, http_response, http_request, @@ -52,7 +38,8 @@ groups() -> [chunk_decode, chunk_encode, chunk_extensions_otp_6005, chunk_decode_otp_6264, chunk_decode_empty_chunk_otp_6511, - chunk_decode_trailer]}]. + chunk_whitespace_suffix, + chunk_decode_trailer, chunk_max_headersize, chunk_max_bodysize, chunk_not_hex]}]. init_per_suite(Config) -> Config. @@ -81,12 +68,8 @@ end_per_testcase(_, Config) -> %% Test cases starts here. %%------------------------------------------------------------------------- - -%%------------------------------------------------------------------------- -chunk_decode(doc) -> - ["Test http_chunk:decode/3"]; -chunk_decode(suite) -> - []; +chunk_decode() -> + [{doc, "Test http_chunk:decode/3"}]. chunk_decode(Config) when is_list(Config) -> ReqHeaders = #http_request_h{'transfer-encoding' = "chunked"}, ChunkedBody = "A" ++ ?CRLF ++ "1234567890" ++ ?CRLF ++ "4" ++ @@ -109,15 +92,11 @@ chunk_decode(Config) when is_list(Config) -> ?HTTP_MAX_BODY_SIZE, ?HTTP_MAX_HEADER_SIZE), {_, Body} = parse(Module, Function, Args, tl(NewChunkedBody)), - "1234567890HEJ!" = binary_to_list(Body), - - ok. + "1234567890HEJ!" = binary_to_list(Body). %%------------------------------------------------------------------------- -chunk_extensions_otp_6005(doc) -> - ["Make sure so called extensions are ignored"]; -chunk_extensions_otp_6005(suite) -> - []; +chunk_extensions_otp_6005() -> + [{doc, "Make sure so called extensions are ignored"}]. chunk_extensions_otp_6005(Config) when is_list(Config)-> ChunkedBody = "A;ignore this" ++ ?CRLF ++ "1234567890" ++ ?CRLF ++ "4" ++ ?CRLF ++ "HEJ!"++ ?CRLF ++ "0" ++ @@ -136,14 +115,11 @@ chunk_extensions_otp_6005(Config) when is_list(Config)-> ?HTTP_MAX_BODY_SIZE, ?HTTP_MAX_HEADER_SIZE), {_, NewBody} = parse(Module1, Function1, Args1, tl(ChunkedBody1)), - "1234567890HEJ!" = binary_to_list(NewBody), - ok. + "1234567890HEJ!" = binary_to_list(NewBody). %%------------------------------------------------------------------------- -chunk_decode_otp_6264(doc) -> - ["Check that 0 in the body does not count as the last chunk"]; -chunk_decode_otp_6264(suite) -> - []; +chunk_decode_otp_6264() -> + [{doc, "Check that 0 in the body does not count as the last chunk"}]. chunk_decode_otp_6264(Config) when is_list(Config)-> ChunkedBody = "A;ignore this" ++ ?CRLF ++ "1234567890" ++ ?CRLF ++ "4" ++ ?CRLF ++ "0123"++ ?CRLF ++ "0" ++ @@ -173,27 +149,33 @@ chunk_decode_otp_6264(Config) when is_list(Config)-> ?HTTP_MAX_BODY_SIZE, ?HTTP_MAX_HEADER_SIZE), {_, NewBody} = parse(Module1, Function1, Args1, tl(NewChunkedBody1)), - "12345678900" = binary_to_list(NewBody), - - ok. + "12345678900" = binary_to_list(NewBody). %%------------------------------------------------------------------------- -chunk_decode_empty_chunk_otp_6511(doc) -> - [""]; -chunk_decode_empty_chunk_otp_6511(suite) -> - []; chunk_decode_empty_chunk_otp_6511(Config) when is_list(Config) -> ChunkedBody = "0" ++ ?CRLF ++ ?CRLF, {ok,{["content-length:0"],<<>>}} = http_chunk:decode(list_to_binary(ChunkedBody), - ?HTTP_MAX_BODY_SIZE, ?HTTP_MAX_HEADER_SIZE), - ok. + ?HTTP_MAX_BODY_SIZE, ?HTTP_MAX_HEADER_SIZE). %%------------------------------------------------------------------------- -chunk_decode_trailer(doc) -> - ["Make sure trailers are handled correctly. Trailers should" - "become new headers"]; -chunk_decode_trailer(suite) -> - []; +chunk_whitespace_suffix() -> + [{doc, "Test whitespace after chunked length header"}]. +chunk_whitespace_suffix(Config) when is_list(Config) -> + ChunkedBody = "1a ; ignore-stuff-here" ++ ?CRLF ++ + "abcdefghijklmnopqrstuvwxyz" ++ ?CRLF ++ "10 " ++ ?CRLF + ++ "1234567890abcdef" ++ ?CRLF ++ "0 " ++ ?CRLF + ++ "some-footer:some-value" ++ ?CRLF + ++ "another-footer:another-value" ++ ?CRLF ++ ?CRLF, + {ok, {["content-length:42", "another-footer:another-value", + "some-footer:some-value", ""], + <<"abcdefghijklmnopqrstuvwxyz1234567890abcdef">>}} = + http_chunk:decode(list_to_binary(ChunkedBody), + ?HTTP_MAX_BODY_SIZE, ?HTTP_MAX_HEADER_SIZE). + +%%------------------------------------------------------------------------- +chunk_decode_trailer() -> + [{doc,"Make sure trailers are handled correctly. Trailers should" + "become new headers"}]. chunk_decode_trailer(Config) when is_list(Config)-> ChunkedBody = "1a; ignore-stuff-here" ++ ?CRLF ++ "abcdefghijklmnopqrstuvwxyz" ++ ?CRLF ++ "10" ++ ?CRLF @@ -249,30 +231,79 @@ chunk_decode_trailer(Config) when is_list(Config)-> ?HTTP_MAX_BODY_SIZE, ?HTTP_MAX_HEADER_SIZE), {_, NewBody} = parse(Module1, Function1, Args1, tl(ChunkedBody3)), - "abcdefghijklmnopqrstuvwxyz1234567890abcdef" = binary_to_list(NewBody), - - ok. + "abcdefghijklmnopqrstuvwxyz1234567890abcdef" = binary_to_list(NewBody). %%------------------------------------------------------------------------- -chunk_encode(doc) -> - ["Test http_chunk:encode/1 & http_chunk:encode_last/0"]; -chunk_encode(suite) -> - []; +chunk_encode() -> + [{doc, "Test http_chunk:encode/1 & http_chunk:encode_last/0"}]. chunk_encode(Config) when is_list(Config) -> <<54, ?CR, ?LF, 102,111,111,98,97,114, ?CR, ?LF>> = http_chunk:encode(list_to_binary("foobar")), ["6", ?CR, ?LF,"foobar", ?CR, ?LF] = http_chunk:encode("foobar"), - <<$0, ?CR, ?LF, ?CR, ?LF >> = http_chunk:encode_last(), - ok. - + <<$0, ?CR, ?LF, ?CR, ?LF >> = http_chunk:encode_last(). +%%------------------------------------------------------------------------- +chunk_max_headersize() -> + [{doc, "Test max header limit"}]. +chunk_max_headersize(Config) when is_list(Config) -> + ChunkedBody = "1a; ignore-stuff-here" ++ ?CRLF ++ + "abcdefghijklmnopqrstuvwxyz" ++ ?CRLF ++ "10" ++ ?CRLF + ++ "1234567890abcdef" ++ ?CRLF ++ "0" ++ ?CRLF + ++ "some-footer:some-value" ++ ?CRLF + ++ "another-footer:another-value" ++ ?CRLF ++ ?CRLF, + + {ok, {_, _}} = + http_chunk:decode(list_to_binary(ChunkedBody), + ?HTTP_MAX_BODY_SIZE, ?HTTP_MAX_HEADER_SIZE), + + %% Too long in length header + {error,{header_too_long, {max, 1}}} = + (catch http_chunk:decode(list_to_binary(ChunkedBody), + ?HTTP_MAX_BODY_SIZE, 1)), + + %% Too long in extension field + {error,{header_too_long, {max, 10}}} = + (catch http_chunk:decode(list_to_binary(ChunkedBody), + ?HTTP_MAX_BODY_SIZE, 10)), + + %% Too long in trailer + {error,{header_too_long, {max, 30}}} = + (catch http_chunk:decode(list_to_binary(ChunkedBody), + ?HTTP_MAX_BODY_SIZE, 30)). +%%------------------------------------------------------------------------- +chunk_not_hex() -> + [{doc, "Test bad chunked length header"}]. +chunk_not_hex(Config) when is_list(Config) -> + ChunkedBody = "åäö; ignore-stuff-here" ++ ?CRLF ++ + "abcdefghijklmnopqrstuvwxyz" ++ ?CRLF ++ "10" ++ ?CRLF + ++ "1234567890abcdef" ++ ?CRLF ++ "0" ++ ?CRLF + ++ "some-footer:some-value" ++ ?CRLF + ++ "another-footer:another-value" ++ ?CRLF ++ ?CRLF, + {error,{chunk_size, "åäö"}} = + (catch http_chunk:decode(list_to_binary(ChunkedBody), + ?HTTP_MAX_BODY_SIZE, ?HTTP_MAX_HEADER_SIZE)). +%%------------------------------------------------------------------------- +chunk_max_bodysize() -> + [{doc, "Test max body limit"}]. +chunk_max_bodysize(Config) when is_list(Config) -> + ChunkedBody = "1a; ignore-stuff-here" ++ ?CRLF ++ + "abcdefghijklmnopqrstuvwxyz" ++ ?CRLF ++ "10" ++ ?CRLF + ++ "1234567890abcdef" ++ ?CRLF ++ "0" ++ ?CRLF + ++ "some-footer:some-value" ++ ?CRLF + ++ "another-footer:another-value" ++ ?CRLF ++ ?CRLF, + {ok, {_, _}} = + http_chunk:decode(list_to_binary(ChunkedBody), + ?HTTP_MAX_BODY_SIZE, ?HTTP_MAX_HEADER_SIZE), + + %% Too long body + {error,{body_too_big, {max, 10}}} = + (catch http_chunk:decode(list_to_binary(ChunkedBody), + 10, ?HTTP_MAX_HEADER_SIZE)). %%------------------------------------------------------------------------- -http_response(doc) -> - ["Test httpc_response:parse*. This test case will simulate that the " +http_response() -> + [{doc, "Test httpc_response:parse*. This test case will simulate that the " "message will be recived a little at the time on a socket and the " - "package may be broken up into smaller parts at arbitrary point."]; -http_response(suite) -> - []; + "package may be broken up into smaller parts at arbitrary point."}]. http_response(Config) when is_list(Config) -> HttpHead1 = ["HTTP", "/1.1 ", "20", "0 ", "ok", [?CR, ?LF], @@ -340,12 +371,10 @@ http_response(Config) when is_list(Config) -> [<<>>,Length1], HttpBody1)), ok. %%------------------------------------------------------------------------- -http_request(doc) -> - ["Test httpd_request:parse* This test case will simulate that the " +http_request() -> + [{doc, "Test httpd_request:parse* This test case will simulate that the " "message will be recived a little at the time on a socket and the " - "package may be broken up into smaller parts at arbitrary point."]; -http_request(suite) -> - []; + "package may be broken up into smaller parts at arbitrary point."}]. http_request(Config) when is_list(Config) -> HttpHead = ["GE", "T ", "http://www.erlang", ".org ", "HTTP", @@ -407,15 +436,12 @@ http_request(Config) when is_list(Config) -> NewBody1 = binary_to_list(parse (httpd_request, whole_body, - [<<>>, Length1], HttpBody1)), - ok. + [<<>>, Length1], HttpBody1)). %%------------------------------------------------------------------------- -validate_request_line(doc) -> - ["Test httpd_request:validate/3. Makes sure you can not get past" +validate_request_line() -> + [{doc, "Test httpd_request:validate/3. Makes sure you can not get past" " the server_root and that the request is recognized by the server" - " and protcol version." ]; -validate_request_line(suite) -> - []; + " and protcol version."}]. validate_request_line(Config) when is_list(Config) -> %% HTTP/0.9 only has GET requests @@ -468,16 +494,12 @@ validate_request_line(Config) when is_list(Config) -> NewForbiddenUri1 = "http://127.0.0.1:8888/../home/ingela/test.html", {error, {bad_request, {forbidden, NewForbiddenUri1}}} = - httpd_request:validate("GET", NewForbiddenUri1, "HTTP/1.1"), - - ok. + httpd_request:validate("GET", NewForbiddenUri1, "HTTP/1.1"). %%------------------------------------------------------------------------- -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() -> + [{doc, "Test http_request:headers/2. Check that the content-length is" + " encoded even when it is zero."}]. check_content_length_encoding(Config) when is_list(Config) -> %% Check that the content-length is preserved. @@ -486,16 +508,12 @@ check_content_length_encoding(Config) when is_list(Config) -> 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. + true = (string:str(Header2, "content-length: 0\r\n") > 0). %%------------------------------------------------------------------------- -esi_parse_headers(doc) -> - ["Test httpd_esi:*. All header values are received in the same" - " erlang message."]; -esi_parse_headers(suite) -> - []; +esi_parse_headers() -> + [{doc, "Test httpd_esi:*. All header values are received in the same" + " erlang message."}]. esi_parse_headers(Config) when is_list(Config) -> ESIResult = "content-type:text/html\r\ndate:Thu, 28 Oct 2004 07:57:43 " @@ -522,16 +540,14 @@ esi_parse_headers(Config) when is_list(Config) -> httpd_esi:handle_headers(Headers2), {proceed,"/foo/bar.html"} = - httpd_esi:handle_headers("location:/foo/bar.html\r\n"), - ok. + httpd_esi:handle_headers("location:/foo/bar.html\r\n"). %%-------------------------------------------------------------------- -cgi_parse_headers(doc) -> - ["Test httpd_cgi:*. This test case will simulate that the " +cgi_parse_headers() -> + [{doc, "Test httpd_cgi:*. This test case will simulate that the " "message will be recived a little at the time on a socket and the " - "package may be broken up into smaller parts at arbitrary point."]; -cgi_parse_headers(suite) -> - []; + "package may be broken up into smaller parts at arbitrary point."}]. + cgi_parse_headers(Config) when is_list(Config) -> CGIResult = ["content-type:text", "/html\ndate:Thu, 28 Oct 2004 07:57:43 " @@ -567,26 +583,18 @@ cgi_parse_headers(Config) when is_list(Config) -> {ok,[{"content-type","text/html"}, {"connection","close"}, {"content-language","en"}, - {"age","4711"}], {200,"ok"}} = httpd_cgi:handle_headers(Headers3), - - ok. - + {"age","4711"}], {200,"ok"}} = httpd_cgi:handle_headers(Headers3). %%------------------------------------------------------------------------- -is_absolut_uri(doc) -> - ["Test http_request:is_absolut_uri/1."]; -is_absolut_uri(suite) -> - []; +is_absolut_uri() -> + [{doc, "Test http_request:is_absolut_uri/1."}]. is_absolut_uri(Config) when is_list(Config) -> true = http_request:is_absolut_uri("http://www.erlang.org"), true = http_request:is_absolut_uri("https://www.erlang.org"), false = http_request:is_absolut_uri("index.html"). - %%------------------------------------------------------------------------- -convert_netscapecookie_date(doc) -> - ["Test http_util:convert_netscapecookie_date/1."]; -convert_netscapecookie_date(suite) -> - []; +convert_netscapecookie_date() -> + [{doc, "Test http_util:convert_netscapecookie_date/1."}]. convert_netscapecookie_date(Config) when is_list(Config) -> {{2006,1,6},{8,59,38}} = http_util:convert_netscapecookie_date("Mon, 06-Jan-2006 08:59:38 GMT"), @@ -619,9 +627,7 @@ convert_netscapecookie_date(Config) when is_list(Config) -> {{2006,12,12},{8,59,38}} = http_util:convert_netscapecookie_date("Sun 12-Dec-06 08:59:38 GMT"), {{2036,1,1},{8,0,1}} = - http_util:convert_netscapecookie_date("Tue Jan 01 08:00:01 2036 GMT"), - ok. - + http_util:convert_netscapecookie_date("Tue Jan 01 08:00:01 2036 GMT"). %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- diff --git a/lib/inets/test/httpc_SUITE.erl b/lib/inets/test/httpc_SUITE.erl index 5b40d08859..c6c59ab1af 100644 --- a/lib/inets/test/httpc_SUITE.erl +++ b/lib/inets/test/httpc_SUITE.erl @@ -98,13 +98,17 @@ only_simulated() -> stream_once, stream_single_chunk, stream_no_length, + not_streamed_once, + stream_large_not_200_or_206, no_content_204, tolerate_missing_CR, userinfo, bad_response, internal_server_error, invalid_http, + invalid_chunk_size, headers_dummy, + headers_with_obs_fold, empty_response_header, remote_socket_close, remote_socket_close_async, @@ -407,6 +411,21 @@ stream_no_length(Config) when is_list(Config) -> stream_test(Request1, {stream, self}), Request2 = {url(group_name(Config), "/http_1_0_no_length_multiple.html", Config), []}, stream_test(Request2, {stream, self}). +%%------------------------------------------------------------------------- +stream_large_not_200_or_206() -> + [{doc, "Test the option stream for large responses with status codes " + "other than 200 or 206" }]. +stream_large_not_200_or_206(Config) when is_list(Config) -> + Request = {url(group_name(Config), "/large_404_response.html", Config), []}, + {404, _} = not_streamed_test(Request, {stream, self}). +%%------------------------------------------------------------------------- +not_streamed_once() -> + [{doc, "Test not streamed responses with once streaming"}]. +not_streamed_once(Config) when is_list(Config) -> + Request0 = {url(group_name(Config), "/404.html", Config), []}, + {404, _} = not_streamed_test(Request0, {stream, {self, once}}), + Request1 = {url(group_name(Config), "/404_chunked.html", Config), []}, + {404, _} = not_streamed_test(Request1, {stream, {self, once}}). %%------------------------------------------------------------------------- @@ -747,6 +766,22 @@ invalid_http(Config) when is_list(Config) -> ct:print("Parse error: ~p ~n", [Reason]). %%------------------------------------------------------------------------- + +invalid_chunk_size(doc) -> + ["Test parse error of HTTP chunk size"]; +invalid_chunk_size(suite) -> + []; +invalid_chunk_size(Config) when is_list(Config) -> + + URL = url(group_name(Config), "/invalid_chunk_size.html", Config), + + {error, {chunk_size, _} = Reason} = + httpc:request(get, {URL, []}, [], []), + + 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) -> @@ -893,6 +928,13 @@ headers_dummy(Config) when is_list(Config) -> %%------------------------------------------------------------------------- +headers_with_obs_fold(Config) when is_list(Config) -> + Request = {url(group_name(Config), "/obs_folded_headers.html", Config), []}, + {ok, {{_,200,_}, Headers, [_|_]}} = httpc:request(get, Request, [], []), + "a b" = proplists:get_value("folded", Headers). + +%%------------------------------------------------------------------------- + invalid_headers(Config) -> Request = {url(group_name(Config), "/dummy.html", Config), [{"cookie", undefined}]}, {error, _} = httpc:request(get, Request, [], []). @@ -1109,6 +1151,19 @@ stream_test(Request, To) -> Body = binary_to_list(StreamedBody). +not_streamed_test(Request, To) -> + {ok, {{_,Code,_}, [_ | _], Body}} = + httpc:request(get, Request, [], [{body_format, binary}]), + {ok, RequestId} = + httpc:request(get, Request, [], [{body_format, binary}, {sync, false}, To]), + + receive + {http, {RequestId, {{_, Code, _}, _Headers, Body}}} -> + {Code, binary_to_list(Body)}; + {http, Msg} -> + ct:fail(Msg) + end. + url(http, End, Config) -> Port = ?config(port, Config), {ok,Host} = inet:gethostname(), @@ -1640,6 +1695,11 @@ handle_uri(_,"/307.html",Port,_,Socket,_) -> "Content-Length:" ++ integer_to_list(length(Body)) ++ "\r\n\r\n" ++ Body; +handle_uri(_,"/404.html",_,_,_,_) -> + "HTTP/1.1 404 not found\r\n" ++ + "Content-Length:14\r\n\r\n" ++ + "Page not found"; + handle_uri(_,"/500.html",_,_,_,_) -> "HTTP/1.1 500 Internal Server Error\r\n" ++ "Content-Length:47\r\n\r\n" ++ @@ -1713,6 +1773,13 @@ handle_uri(_,"/dummy_headers.html",_,_,Socket,_) -> send(Socket, http_chunk:encode("obar</BODY></HTML>")), http_chunk:encode_last(); +handle_uri(_,"/obs_folded_headers.html",_,_,_,_) -> + "HTTP/1.1 200 ok\r\n" + "Content-Length:5\r\n" + "Folded: a\r\n" + " b\r\n\r\n" + "Hello"; + handle_uri(_,"/capital_transfer_encoding.html",_,_,Socket,_) -> Head = "HTTP/1.1 200 ok\r\n" ++ "Transfer-Encoding:Chunked\r\n\r\n", @@ -1768,6 +1835,15 @@ handle_uri(_,"/once_chunked.html",_,_,Socket,_) -> http_chunk:encode("obar</BODY></HTML>")), http_chunk:encode_last(); +handle_uri(_,"/404_chunked.html",_,_,Socket,_) -> + Head = "HTTP/1.1 404 not found\r\n" ++ + "Transfer-Encoding:Chunked\r\n\r\n", + send(Socket, Head), + send(Socket, http_chunk:encode("<HTML><BODY>Not ")), + send(Socket, + http_chunk:encode("found</BODY></HTML>")), + http_chunk:encode_last(); + handle_uri(_,"/single_chunk.html",_,_,Socket,_) -> Chunk = "HTTP/1.1 200 ok\r\n" ++ "Transfer-Encoding:Chunked\r\n\r\n" ++ @@ -1792,6 +1868,17 @@ handle_uri(_,"/http_1_0_no_length_multiple.html",_,_,Socket,_) -> send(Socket, string:copies("other multiple packets ", 200)), close(Socket); +handle_uri(_,"/large_404_response.html",_,_,Socket,_) -> + %% long body to make sure it will be sent in multiple tcp packets + Body = string:copies("other multiple packets ", 200), + Head = io_lib:format("HTTP/1.1 404 not found\r\n" + "Content-length: ~B\r\n" + "Content-type: text/plain\r\n\r\n", + [length(Body)]), + send(Socket, Head), + send(Socket, Body), + close(Socket); + handle_uri(_,"/once.html",_,_,Socket,_) -> Head = "HTTP/1.1 200 ok\r\n" ++ "Content-Length:32\r\n\r\n", @@ -1806,6 +1893,10 @@ 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(_,"/invalid_chunk_size.html",_,_,_,_) -> + "HTTP/1.1 200 ok\r\n" ++ + "Transfer-Encoding:chunked\r\n\r\nåäö\r\n"; + handle_uri(_,"/missing_reason_phrase.html",_,_,_,_) -> "HTTP/1.1 200\r\n" ++ "Content-Length: 32\r\n\r\n" diff --git a/lib/inets/test/httpd_1_1.erl b/lib/inets/test/httpd_1_1.erl index ae5fcba47b..d3a1e3672a 100644 --- a/lib/inets/test/httpd_1_1.erl +++ b/lib/inets/test/httpd_1_1.erl @@ -24,7 +24,7 @@ -include_lib("kernel/include/file.hrl"). -export([host/4, chunked/4, expect/4, range/4, if_test/5, trace/4, - head/4, mod_cgi_chunked_encoding_test/5]). + head/4, mod_cgi_chunked_encoding_test/5, mod_esi_chunk_timeout/4]). %% -define(all_keys_lower_case,true). -ifndef(all_keys_lower_case). @@ -274,6 +274,15 @@ mod_cgi_chunked_encoding_test(Type, Port, Host, Node, [Request| Rest])-> [{statuscode, 200}]), mod_cgi_chunked_encoding_test(Type, Port, Host, Node, Rest). + +mod_esi_chunk_timeout(Type, Port, Host, Node) -> + ok = httpd_test_lib:verify_request(Type, Host, Port, Node, + "GET /cgi-bin/erl/httpd_example/chunk_timeout?input=20000 HTTP/1.1\r\n" + "Host:"++ Host ++"\r\n" + "\r\n", + [{statuscode, 200}, + {header, "warning"}]). + %%-------------------------------------------------------------------- %% Internal functions %%-------------------------------------------------------------------- diff --git a/lib/inets/test/httpd_SUITE.erl b/lib/inets/test/httpd_SUITE.erl index a6236f828a..1d8a603981 100644 --- a/lib/inets/test/httpd_SUITE.erl +++ b/lib/inets/test/httpd_SUITE.erl @@ -97,7 +97,7 @@ groups() -> {https_reload, [], [{group, reload}]}, {http_mime_types, [], [alias_1_1, alias_1_0, alias_0_9]}, {limit, [], [max_clients_1_1, max_clients_1_0, max_clients_0_9]}, - {custom, [], [customize]}, + {custom, [], [customize, add_default]}, {reload, [], [non_disturbing_reconfiger_dies, disturbing_reconfiger_dies, non_disturbing_1_1, @@ -117,7 +117,7 @@ groups() -> {htaccess, [], [htaccess_1_1, htaccess_1_0, htaccess_0_9]}, {security, [], [security_1_1, security_1_0]}, %% Skip 0.9 as causes timing issus in test code {http_1_1, [], [host, chunked, expect, cgi, cgi_chunked_encoding_test, - trace, range, if_modified_since] ++ http_head() ++ http_get() ++ load()}, + trace, range, if_modified_since, mod_esi_chunk_timeout] ++ http_head() ++ http_get() ++ load()}, {http_1_0, [], [host, cgi, trace] ++ http_head() ++ http_get() ++ load()}, {http_0_9, [], http_head() ++ http_get() ++ load()} ]. @@ -757,6 +757,13 @@ esi(Config) when is_list(Config) -> Config, [{statuscode, 200}, {no_header, "cache-control"}]). %%------------------------------------------------------------------------- +mod_esi_chunk_timeout(Config) when is_list(Config) -> + ok = httpd_1_1:mod_esi_chunk_timeout(?config(type, Config), + ?config(port, Config), + ?config(host, Config), + ?config(node, Config)). + +%%------------------------------------------------------------------------- cgi() -> [{doc, "Test mod_cgi"}]. @@ -1003,10 +1010,23 @@ customize(Config) when is_list(Config) -> {no_header, "Server"}, {version, Version}]). -response_header({"server", _}) -> - false; -response_header(Header) -> - {true, Header}. +add_default() -> + [{doc, "Test adding default header with custom callback"}]. + +add_default(Config) when is_list(Config) -> + Version = "HTTP/1.1", + Host = ?config(host, Config), + Type = ?config(type, Config), + ok = httpd_test_lib:verify_request(?config(type, Config), Host, + ?config(port, Config), + transport_opts(Type, Config), + ?config(node, Config), + http_request("GET /index.html ", Version, Host), + [{statuscode, 200}, + {header, "Content-Type", "text/html"}, + {header, "Date", "Override-date"}, + {header, "X-Frame-Options"}, + {version, Version}]). %%------------------------------------------------------------------------- max_header() -> @@ -1421,13 +1441,15 @@ server_config(http_reload, Config) -> server_config(https_reload, Config) -> [{keep_alive_timeout, 2}] ++ server_config(https, Config); server_config(http_limit, Config) -> - [{max_clients, 1}, - %% Make sure option checking code is run - {max_content_length, 100000002}] ++ server_config(http, Config); + Conf = [{max_clients, 1}, + %% Make sure option checking code is run + {max_content_length, 100000002}] ++ server_config(http, Config), + ct:pal("Received message ~p~n", [Conf]), + Conf; server_config(http_custom, Config) -> - [{custom, ?MODULE}] ++ server_config(http, Config); + [{customize, ?MODULE}] ++ server_config(http, Config); server_config(https_custom, Config) -> - [{custom, ?MODULE}] ++ server_config(https, Config); + [{customize, ?MODULE}] ++ server_config(https, Config); server_config(https_limit, Config) -> [{max_clients, 1}] ++ server_config(https, Config); server_config(http_basic_auth, Config) -> @@ -1473,6 +1495,7 @@ server_config(http_mime_types, Config0) -> server_config(http, Config) -> ServerRoot = ?config(server_root, Config), [{port, 0}, + {socket_type, {ip_comm, [{nodelay, true}]}}, {server_name,"httpd_test"}, {server_root, ServerRoot}, {document_root, ?config(doc_root, Config)}, @@ -1494,13 +1517,14 @@ server_config(http, Config) -> server_config(https, Config) -> PrivDir = ?config(priv_dir, Config), [{socket_type, {essl, - [{cacertfile, - filename:join(PrivDir, "public_key_cacert.pem")}, - {certfile, - filename:join(PrivDir, "public_key_cert.pem")}, - {keyfile, - filename:join(PrivDir, "public_key_cert_key.pem")} - ]}}] ++ server_config(http, Config). + [{nodelay, true}, + {cacertfile, + filename:join(PrivDir, "public_key_cacert.pem")}, + {certfile, + filename:join(PrivDir, "public_key_cert.pem")}, + {keyfile, + filename:join(PrivDir, "public_key_cert_key.pem")} + ]}}] ++ proplists:delete(socket_type, server_config(http, Config)). init_httpd(Group, Config0) -> Config1 = proplists:delete(port, Config0), @@ -2030,3 +2054,14 @@ typestr(ip_comm) -> "tcp"; typestr(_) -> "ssl". + +response_header({"server", _}) -> + false; +response_header(Header) -> + {true, Header}. + +response_default_headers() -> + [%% Add new header + {"X-Frame-Options", "SAMEORIGIN"}, + %% Override built-in default + {"Date", "Override-date"}]. diff --git a/lib/inets/test/httpd_SUITE_data/Makefile.src b/lib/inets/test/httpd_SUITE_data/Makefile.src index b0fdb43d8d..cea40dd8cb 100644 --- a/lib/inets/test/httpd_SUITE_data/Makefile.src +++ b/lib/inets/test/httpd_SUITE_data/Makefile.src @@ -10,5 +10,10 @@ all: $(PROGS) cgi_echo@exe@: cgi_echo@obj@ $(LD) $(CROSSLDFLAGS) -o cgi_echo cgi_echo@obj@ @LIBS@ +@IFEQ@ (@CC@, cl -nologo) +cgi_echo@obj@: cgi_echo.c + $(CC) /c /Focgi_echo@obj@ $(CFLAGS) cgi_echo.c +@ELSE@ cgi_echo@obj@: cgi_echo.c $(CC) -c -o cgi_echo@obj@ $(CFLAGS) cgi_echo.c +@ENDIF@ diff --git a/lib/inets/test/httpd_test_lib.erl b/lib/inets/test/httpd_test_lib.erl index 8cc6f3bbd6..71e201f826 100644 --- a/lib/inets/test/httpd_test_lib.erl +++ b/lib/inets/test/httpd_test_lib.erl @@ -235,11 +235,17 @@ validate(RequestStr, #state{status_line = {Version, StatusCode, _}, _ -> ok end, - do_validate(http_response:header_list(Headers), Options, N, P), - check_body(RequestStr, StatusCode, - Headers#http_response_h.'content-type', - list_to_integer(Headers#http_response_h.'content-length'), - Body). + HList = http_response:header_list(Headers), + do_validate(HList, Options, N, P), + case lists:keysearch("warning", 1, HList) of + {value, _} -> + ok; + _ -> + check_body(RequestStr, StatusCode, + Headers#http_response_h.'content-type', + list_to_integer(Headers#http_response_h.'content-length'), + Body) + end. %-------------------------------------------------------------------- %% Internal functions @@ -294,9 +300,9 @@ do_validate(Header, [{header, HeaderField, Value}|Rest],N,P) -> {value, {LowerHeaderField, Value}} -> ok; false -> - ct:fail({wrong_header_field_value, LowerHeaderField, Header}); + ct:fail({wrong_header_field_value, LowerHeaderField, Header, Value}); _ -> - ct:fail({wrong_header_field_value, LowerHeaderField, Header}) + ct:fail({wrong_header_field_value, LowerHeaderField, Header, Value}) end, do_validate(Header, Rest, N, P); do_validate(Header,[{no_header, HeaderField}|Rest],N,P) -> diff --git a/lib/inets/test/uri_SUITE.erl b/lib/inets/test/uri_SUITE.erl index bfcd7bd339..2642b8fd4e 100644 --- a/lib/inets/test/uri_SUITE.erl +++ b/lib/inets/test/uri_SUITE.erl @@ -49,7 +49,8 @@ all() -> queries, fragments, escaped, - hexed_query + hexed_query, + scheme_validation ]. %%-------------------------------------------------------------------- @@ -175,6 +176,26 @@ hexed_query(Config) when is_list(Config) -> verify_uri(URI2, Verify2), verify_uri(URI3, Verify3). +scheme_validation(Config) when is_list(Config) -> + {ok, {http,[],"localhost",80,"/",""}} = + http_uri:parse("http://localhost#fragment"), + + ValidationFun = + fun("http") -> valid; + (_) -> {error, bad_scheme} + end, + + {ok, {http,[],"localhost",80,"/",""}} = + http_uri:parse("http://localhost#fragment", + [{scheme_validation_fun, ValidationFun}]), + {error, bad_scheme} = + http_uri:parse("https://localhost#fragment", + [{scheme_validation_fun, ValidationFun}]), + %% non-fun scheme_validation_fun works as no option passed + {ok, {https,[],"localhost",443,"/",""}} = + http_uri:parse("https://localhost#fragment", + [{scheme_validation_fun, none}]). + %%-------------------------------------------------------------------- %% Internal Functions ------------------------------------------------ |