From 98fd9df4c4a04554fd2f707ca9ea2d674fad984d Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 15 Sep 2011 09:43:48 +0200 Subject: Updated http-server to make sure URLs in error-messages are URL-encoded. Added support in http-client to use URL-encoding. Also added the missing include directory for the inets application. OTP-8940 [httpd] Prevent XSS in error pages. Prevent user controlled input from being interpreted as HTML in error pages by encoding the reserved HTML characters. Michael Santos OTP-9124 --- lib/inets/doc/src/http_client.xml | 6 +- lib/inets/doc/src/httpd.xml | 4 +- lib/inets/doc/src/notes.xml | 31 ++- lib/inets/src/http_client/Makefile | 5 +- lib/inets/src/http_client/http_uri.erl | 116 ---------- lib/inets/src/http_client/httpc.erl | 29 ++- lib/inets/src/http_client/httpc_handler.erl | 42 ++-- lib/inets/src/http_client/httpc_internal.hrl | 8 +- lib/inets/src/http_lib/Makefile | 5 +- lib/inets/src/http_lib/http_transport.erl | 26 ++- lib/inets/src/http_lib/http_uri.erl | 141 ++++++++++++ lib/inets/src/http_lib/http_util.erl | 20 +- lib/inets/src/http_server/Makefile | 18 +- lib/inets/src/http_server/httpd.erl | 6 +- lib/inets/src/http_server/httpd_acceptor.erl | 3 +- lib/inets/src/http_server/httpd_conf.erl | 5 +- lib/inets/src/http_server/httpd_file.erl | 3 +- lib/inets/src/http_server/httpd_request.erl | 6 +- lib/inets/src/http_server/httpd_sup.erl | 3 +- lib/inets/src/http_server/httpd_util.erl | 82 ++++--- lib/inets/src/http_server/mod_alias.erl | 3 +- lib/inets/src/http_server/mod_auth.erl | 3 +- lib/inets/src/http_server/mod_auth_dets.erl | 3 +- lib/inets/src/http_server/mod_auth_plain.erl | 3 +- lib/inets/src/http_server/mod_auth_server.erl | 3 +- lib/inets/src/http_server/mod_dir.erl | 6 +- lib/inets/src/http_server/mod_esi.erl | 3 +- lib/inets/src/http_server/mod_include.erl | 6 +- lib/inets/src/http_server/mod_security.erl | 3 +- lib/inets/src/http_server/mod_security_server.erl | 3 +- lib/inets/src/inets_app/inets.appup.src | 62 ++---- lib/inets/test/httpc_SUITE.erl | 256 +++++++++++++++------- lib/inets/test/httpd_basic_SUITE.erl | 59 ++++- lib/inets/test/inets.spec | 2 +- lib/inets/test/inets_test_lib.erl | 61 +++++- lib/inets/vsn.mk | 6 +- 36 files changed, 680 insertions(+), 361 deletions(-) delete mode 100644 lib/inets/src/http_client/http_uri.erl create mode 100644 lib/inets/src/http_lib/http_uri.erl (limited to 'lib/inets') diff --git a/lib/inets/doc/src/http_client.xml b/lib/inets/doc/src/http_client.xml index ea8053cafa..49327ca80f 100644 --- a/lib/inets/doc/src/http_client.xml +++ b/lib/inets/doc/src/http_client.xml @@ -1,10 +1,10 @@ - +
- 20042010 + 20042011 Ericsson AB. All Rights Reserved. @@ -57,7 +57,7 @@ [{inets, [{services, [{httpc, PropertyList}]}]}]

For valid properties see - httpc(3).

+ httpc(3).

diff --git a/lib/inets/doc/src/httpd.xml b/lib/inets/doc/src/httpd.xml index 7dabeb33e9..f061488ac3 100644 --- a/lib/inets/doc/src/httpd.xml +++ b/lib/inets/doc/src/httpd.xml @@ -1,10 +1,10 @@ - +
- 19972010 + 19972011 Ericsson AB. All Rights Reserved. diff --git a/lib/inets/doc/src/notes.xml b/lib/inets/doc/src/notes.xml index 9ab35ff38b..ffbe4bd58f 100644 --- a/lib/inets/doc/src/notes.xml +++ b/lib/inets/doc/src/notes.xml @@ -1,10 +1,10 @@ - +
- 20022010 + 20022011 Ericsson AB. All Rights Reserved. @@ -32,6 +32,33 @@ notes.xml
+
Inets 5.3.5 + +
Fixed Bugs and Malfunctions + + +

Updated http-server to make sure URLs in error-messages + are URL-encoded. Added support in http-client to use + URL-encoding. Also added the missing include directory + for the inets application.

+

Own Id: OTP-8940

+

Aux Id: seq11735

+
+ + +

[httpd] Prevent XSS in error pages. + Prevent user controlled input from being interpreted + as HTML in error pages by encoding the reserved HTML + characters.

+

Michael Santos

+

Own Id: OTP-9124

+
+
+
+ +
+ +
Inets 5.3.4
Improvements and New Features diff --git a/lib/inets/src/http_client/Makefile b/lib/inets/src/http_client/Makefile index 628c91421f..184f45f589 100644 --- a/lib/inets/src/http_client/Makefile +++ b/lib/inets/src/http_client/Makefile @@ -1,7 +1,7 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 2005-2010. All Rights Reserved. +# Copyright Ericsson AB 2005-2011. All Rights Reserved. # # The contents of this file are subject to the Erlang Public License, # Version 1.1, (the "License"); you may not use this file except in @@ -50,8 +50,7 @@ MODULES = \ httpc_handler_sup \ httpc_profile_sup \ httpc_response \ - httpc_request \ - http_uri \ + httpc_request HRL_FILES = httpc_internal.hrl diff --git a/lib/inets/src/http_client/http_uri.erl b/lib/inets/src/http_client/http_uri.erl deleted file mode 100644 index 615a0d8ec4..0000000000 --- a/lib/inets/src/http_client/http_uri.erl +++ /dev/null @@ -1,116 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2006-2009. All Rights Reserved. -%% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. -%% -%% %CopyrightEnd% -%% -%% - --module(http_uri). - --export([parse/1]). - -%%%========================================================================= -%%% API -%%%========================================================================= -parse(AbsURI) -> - case parse_scheme(AbsURI) of - {error, Reason} -> - {error, Reason}; - {Scheme, Rest} -> - case (catch parse_uri_rest(Scheme, Rest)) of - {UserInfo, Host, Port, Path, Query} -> - {Scheme, UserInfo, Host, Port, Path, Query}; - _ -> - {error, {malformed_url, AbsURI}} - end - end. - -%%%======================================================================== -%%% Internal functions -%%%======================================================================== -parse_scheme(AbsURI) -> - case split_uri(AbsURI, ":", {error, no_scheme}, 1, 1) of - {error, no_scheme} -> - {error, no_scheme}; - {StrScheme, Rest} -> - case list_to_atom(http_util:to_lower(StrScheme)) of - Scheme when Scheme == http; Scheme == https -> - {Scheme, Rest}; - Scheme -> - {error, {not_supported_scheme, Scheme}} - end - end. - -parse_uri_rest(Scheme, "//" ++ URIPart) -> - - {Authority, PathQuery} = - case split_uri(URIPart, "/", URIPart, 1, 0) of - Split = {_, _} -> - Split; - URIPart -> - case split_uri(URIPart, "\\?", URIPart, 1, 0) of - Split = {_, _} -> - Split; - URIPart -> - {URIPart,""} - end - end, - - {UserInfo, HostPort} = split_uri(Authority, "@", {"", Authority}, 1, 1), - {Host, Port} = parse_host_port(Scheme, HostPort), - {Path, Query} = parse_path_query(PathQuery), - {UserInfo, Host, Port, Path, Query}. - - -parse_path_query(PathQuery) -> - {Path, Query} = split_uri(PathQuery, "\\?", {PathQuery, ""}, 1, 0), - {path(Path), Query}. - - -parse_host_port(Scheme,"[" ++ HostPort) -> %ipv6 - DefaultPort = default_port(Scheme), - {Host, ColonPort} = split_uri(HostPort, "\\]", {HostPort, ""}, 1, 1), - {_, Port} = split_uri(ColonPort, ":", {"", DefaultPort}, 0, 1), - {Host, int_port(Port)}; - -parse_host_port(Scheme, HostPort) -> - DefaultPort = default_port(Scheme), - {Host, Port} = split_uri(HostPort, ":", {HostPort, DefaultPort}, 1, 1), - {Host, int_port(Port)}. - -split_uri(UriPart, SplitChar, NoMatchResult, SkipLeft, SkipRight) -> - case inets_regexp:first_match(UriPart, SplitChar) of - {match, Match, _} -> - {string:substr(UriPart, 1, Match - SkipLeft), - string:substr(UriPart, Match + SkipRight, length(UriPart))}; - nomatch -> - NoMatchResult - end. - -default_port(http) -> - 80; -default_port(https) -> - 443. - -int_port(Port) when is_integer(Port) -> - Port; -int_port(Port) when is_list(Port) -> - list_to_integer(Port). - -path("") -> - "/"; -path(Path) -> - Path. diff --git a/lib/inets/src/http_client/httpc.erl b/lib/inets/src/http_client/httpc.erl index 6deeab6948..ca186f46a7 100644 --- a/lib/inets/src/http_client/httpc.erl +++ b/lib/inets/src/http_client/httpc.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2009-2010. All Rights Reserved. +%% Copyright Ericsson AB 2009-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -434,18 +434,21 @@ handle_request(Method, Url, Stream = proplists:get_value(stream, Options), Host2 = header_host(Scheme, Host, Port), HeadersRecord = header_record(NewHeaders, Host2, HTTPOptions), - Receiver = proplists:get_value(receiver, Options), - SocketOpts = proplists:get_value(socket_opts, Options), + Receiver = proplists:get_value(receiver, Options), + SocketOpts = proplists:get_value(socket_opts, Options), + MaybeEscPath = maybe_url_encode(HTTPOptions, Path), + MaybeEscQuery = maybe_url_encode(HTTPOptions, Query), + AbsUri = maybe_url_encode(HTTPOptions, Url), Request = #request{from = Receiver, scheme = Scheme, address = {Host, Port}, - path = Path, - pquery = Query, + path = MaybeEscPath, + pquery = MaybeEscQuery, method = Method, headers = HeadersRecord, content = {ContentType, Body}, settings = HTTPOptions, - abs_uri = Url, + abs_uri = AbsUri, userinfo = UserInfo, stream = Stream, headers_as_is = headers_as_is(Headers, Options), @@ -465,6 +468,10 @@ handle_request(Method, Url, Error end. +maybe_url_encode(#http_options{url_encode = true}, URI) -> + http_uri:encode(URI); +maybe_url_encode(_, URI) -> + URI. handle_answer(RequestId, false, _) -> {ok, RequestId}; @@ -603,6 +610,13 @@ http_options_default() -> (_) -> error end, + + UrlDecodePost = fun(Value) when (Value =:= true) orelse + (Value =:= false) -> + {ok, Value}; + (_) -> + error + end, [ {version, {value, "HTTP/1.1"}, #http_options.version, VersionPost}, {timeout, {value, ?HTTP_REQUEST_TIMEOUT}, #http_options.timeout, TimeoutPost}, @@ -611,7 +625,8 @@ http_options_default() -> {proxy_auth, {value, undefined}, #http_options.proxy_auth, ProxyAuthPost}, {relaxed, {value, false}, #http_options.relaxed, RelaxedPost}, %% this field has to be *after* the timeout field (as that field is used for the default value) - {connect_timeout, {field, #http_options.timeout}, #http_options.connect_timeout, ConnTimeoutPost} + {connect_timeout, {field, #http_options.timeout}, #http_options.connect_timeout, ConnTimeoutPost}, + {url_encode, {value, false}, #http_options.url_encode, UrlDecodePost} ]. diff --git a/lib/inets/src/http_client/httpc_handler.erl b/lib/inets/src/http_client/httpc_handler.erl index db5ff3036a..5e5a9ce32e 100644 --- a/lib/inets/src/http_client/httpc_handler.erl +++ b/lib/inets/src/http_client/httpc_handler.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2002-2010. All Rights Reserved. +%% Copyright Ericsson AB 2002-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -794,37 +794,43 @@ deliver_answers([Request|Requests]) -> %% Purpose: Convert process state when code is changed %%-------------------------------------------------------------------- code_change(_, #state{request = Request, pipeline = Queue} = State, - [{from, '5.0.1'}, {to, '5.0.2'}]) -> - Settings = new_http_options(Request#request.settings), + from_pre_5_3_5) -> + Settings = new_http_options(Request#request.settings), NewRequest = Request#request{settings = Settings}, - NewQueue = new_queue(Queue, fun new_http_options/1), + NewQueue = new_queue(Queue, fun new_http_options/1), {ok, State#state{request = NewRequest, pipeline = NewQueue}}; code_change(_, #state{request = Request, pipeline = Queue} = State, - [{from, '5.0.2'}, {to, '5.0.1'}]) -> - Settings = old_http_options(Request#request.settings), + to_pre_5_3_5) -> + Settings = old_http_options(Request#request.settings), NewRequest = Request#request{settings = Settings}, - NewQueue = new_queue(Queue, fun old_http_options/1), + NewQueue = new_queue(Queue, fun old_http_options/1), {ok, State#state{request = NewRequest, pipeline = NewQueue}}; code_change(_, State, _) -> {ok, State}. -new_http_options({http_options, TimeOut, AutoRedirect, SslOpts, - Auth, Relaxed}) -> - {http_options, "HTTP/1.1", TimeOut, AutoRedirect, SslOpts, - Auth, Relaxed}. - -old_http_options({http_options, _, TimeOut, AutoRedirect, - SslOpts, Auth, Relaxed}) -> - {http_options, TimeOut, AutoRedirect, SslOpts, Auth, Relaxed}. - -new_queue(Queue, Fun) -> +new_http_options({http_options, + Version, Timeout, AutoRedirect, SslOpts, + ProxyAuth, Relaxed, ConnTimeout}) -> + UrlEncoding = false, + {http_options, + Version, Timeout, AutoRedirect, SslOpts, + ProxyAuth, Relaxed, ConnTimeout, UrlEncoding}. + +old_http_options({http_options, + Version, TimeOut, AutoRedirect, SslOpts, + ProxyAuth, Relaxed, ConnTimeout, _UrlEncode}) -> + {http_options, + Version, TimeOut, AutoRedirect, SslOpts, + ProxyAuth, Relaxed, ConnTimeout}. + +new_queue(Queue, TransformSettings) -> List = queue:to_list(Queue), NewList = lists:map(fun(Request) -> Settings = - Fun(Request#request.settings), + TransformSettings(Request#request.settings), Request#request{settings = Settings} end, List), queue:from_list(NewList). diff --git a/lib/inets/src/http_client/httpc_internal.hrl b/lib/inets/src/http_client/httpc_internal.hrl index 4d76c4beb3..7513aa2838 100644 --- a/lib/inets/src/http_client/httpc_internal.hrl +++ b/lib/inets/src/http_client/httpc_internal.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2010. All Rights Reserved. +%% Copyright Ericsson AB 2005-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -56,7 +56,11 @@ relaxed = false, %% integer() - ms before a connect times out - connect_timeout = ?HTTP_REQUEST_CTIMEOUT + connect_timeout = ?HTTP_REQUEST_CTIMEOUT, + + %% bool() - Use %-encoding rfc 2396 + url_encode + } ). diff --git a/lib/inets/src/http_lib/Makefile b/lib/inets/src/http_lib/Makefile index 7f4c92861c..a715e3c9af 100644 --- a/lib/inets/src/http_lib/Makefile +++ b/lib/inets/src/http_lib/Makefile @@ -1,7 +1,7 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 2005-2010. All Rights Reserved. +# Copyright Ericsson AB 2005-2011. All Rights Reserved. # # The contents of this file are subject to the Erlang Public License, # Version 1.1, (the "License"); you may not use this file except in @@ -45,7 +45,8 @@ MODULES = \ http_transport\ http_util \ http_request \ - http_response + http_response \ + http_uri HRL_FILES = http_internal.hrl diff --git a/lib/inets/src/http_lib/http_transport.erl b/lib/inets/src/http_lib/http_transport.erl index 7c2ac626e6..587d215051 100644 --- a/lib/inets/src/http_lib/http_transport.erl +++ b/lib/inets/src/http_lib/http_transport.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2010. All Rights Reserved. +%% Copyright Ericsson AB 2004-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -150,17 +150,22 @@ listen_ip_comm(Addr, Port) -> 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', _} -> + {'EXIT', Reason} -> Opts3 = [inet | Opts], + ?hlrt("ipv6 listen exit - try ipv4 instead", + [{reason, Reason}, {port, NewPort}, {opts, Opts3}]), gen_tcp:listen(NewPort, Opts3); Other -> @@ -168,6 +173,7 @@ listen_ip_comm(Addr, Port) -> end; _ -> Opts2 = [IpFamily | Opts], + ?hlrt("listen", [{port, NewPort}, {opts, Opts2}]), gen_tcp:listen(NewPort, Opts2) end. @@ -364,7 +370,21 @@ peername(ip_comm, Socket) -> http_util:integer_to_hexlist(G) ++":"++ http_util:integer_to_hexlist(H), {Port, PeerName}; - {error, _} -> + {error, Reason} -> + Report = io_lib:format("~p Failed getting PeerName for socket ~p: " + "~n Reason: ~p" + "~n Socket stat: ~p" + "~n IfList: ~p" + "~n Fd: ~p" + "~n", + [self(), + Socket, + Reason, + (catch inet:getstat(Socket)), + (catch inet:getiflist(Socket)), + (catch inet:getfd(Socket)) + ]), + (catch error_logger:error_report(Report)), {-1, "unknown"} end; diff --git a/lib/inets/src/http_lib/http_uri.erl b/lib/inets/src/http_lib/http_uri.erl new file mode 100644 index 0000000000..3804af60f4 --- /dev/null +++ b/lib/inets/src/http_lib/http_uri.erl @@ -0,0 +1,141 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2006-2011. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%% + +-module(http_uri). + +-export([parse/1]). +-export([parse/1, encode/1, decode/1]). + +%%%========================================================================= +%%% API +%%%========================================================================= +parse(AbsURI) -> + case parse_scheme(AbsURI) of + {error, Reason} -> + {error, Reason}; + {Scheme, Rest} -> + case (catch parse_uri_rest(Scheme, Rest)) of + {UserInfo, Host, Port, Path, Query} -> + {Scheme, UserInfo, Host, Port, Path, Query}; + _ -> + {error, {malformed_url, AbsURI}} + end + end. + +encode(URI) -> + Reserved = sets:from_list([$;, $:, $@, $&, $=, $+, $,, $/, $?, + $#, $[, $], $<, $>, $\", ${, $}, $|, + $\\, $', $^, $%, $ ]), + lists:append(lists:map(fun(Char) -> uri_encode(Char, Reserved) end, URI)). + +decode([$%,Hex1,Hex2|Rest]) -> + [hex2dec(Hex1)*16+hex2dec(Hex2)|decode(Rest)]; +decode([First|Rest]) -> + [First|decode(Rest)]; +decode([]) -> + []. + +%%%======================================================================== +%%% Internal functions +%%%======================================================================== +parse_scheme(AbsURI) -> + case split_uri(AbsURI, ":", {error, no_scheme}, 1, 1) of + {error, no_scheme} -> + {error, no_scheme}; + {StrScheme, Rest} -> + case list_to_atom(http_util:to_lower(StrScheme)) of + Scheme when Scheme == http; Scheme == https -> + {Scheme, Rest}; + Scheme -> + {error, {not_supported_scheme, Scheme}} + end + end. + +parse_uri_rest(Scheme, "//" ++ URIPart) -> + {Authority, PathQuery} = + case split_uri(URIPart, "/", URIPart, 1, 0) of + Split = {_, _} -> + Split; + URIPart -> + case split_uri(URIPart, "\\?", URIPart, 1, 0) of + Split = {_, _} -> + Split; + URIPart -> + {URIPart,""} + end + end, + + {UserInfo, HostPort} = split_uri(Authority, "@", {"", Authority}, 1, 1), + {Host, Port} = parse_host_port(Scheme, HostPort), + {Path, Query} = parse_path_query(PathQuery), + {UserInfo, Host, Port, Path, Query}. + + +parse_path_query(PathQuery) -> + {Path, Query} = split_uri(PathQuery, "\\?", {PathQuery, ""}, 1, 0), + {path(Path), Query}. + + +parse_host_port(Scheme,"[" ++ HostPort) -> %ipv6 + DefaultPort = default_port(Scheme), + {Host, ColonPort} = split_uri(HostPort, "\\]", {HostPort, ""}, 1, 1), + {_, Port} = split_uri(ColonPort, ":", {"", DefaultPort}, 0, 1), + {Host, int_port(Port)}; + +parse_host_port(Scheme, HostPort) -> + DefaultPort = default_port(Scheme), + {Host, Port} = split_uri(HostPort, ":", {HostPort, DefaultPort}, 1, 1), + {Host, int_port(Port)}. + +split_uri(UriPart, SplitChar, NoMatchResult, SkipLeft, SkipRight) -> + case inets_regexp:first_match(UriPart, SplitChar) of + {match, Match, _} -> + {string:substr(UriPart, 1, Match - SkipLeft), + string:substr(UriPart, Match + SkipRight, length(UriPart))}; + nomatch -> + NoMatchResult + end. + +default_port(http) -> + 80; +default_port(https) -> + 443. + +int_port(Port) when is_integer(Port) -> + Port; +int_port(Port) when is_list(Port) -> + list_to_integer(Port). + +path("") -> + "/"; +path(Path) -> + Path. + +uri_encode(Char, Reserved) -> + case sets:is_element(Char, Reserved) of + true -> + [ $% | http_util:integer_to_hexlist(Char)]; + false -> + [Char] + end. + +hex2dec(X) when (X>=$0) andalso (X=<$9) -> X-$0; +hex2dec(X) when (X>=$A) andalso (X=<$F) -> X-$A+10; +hex2dec(X) when (X>=$a) andalso (X=<$f) -> X-$a+10. diff --git a/lib/inets/src/http_lib/http_util.erl b/lib/inets/src/http_lib/http_util.erl index 4f1147176c..be0602ff6e 100644 --- a/lib/inets/src/http_lib/http_util.erl +++ b/lib/inets/src/http_lib/http_util.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2010. All Rights Reserved. +%% Copyright Ericsson AB 2005-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -24,7 +24,8 @@ convert_netscapecookie_date/1, hexlist_to_integer/1, integer_to_hexlist/1, convert_month/1, - is_hostname/1, + is_hostname/1, + html_encode/1, timestamp/0, timeout/2 ]). @@ -185,7 +186,14 @@ timeout(Timeout, Started) -> _ -> 0 end. + +html_encode(Chars) -> + Reserved = sets:from_list([$&, $<, $>, $\", $', $/]), + lists:append(lists:map(fun(Char) -> + char_to_html_entity(Char, Reserved) + end, Chars)). + %%%======================================================================== %%% Internal functions @@ -235,3 +243,11 @@ convert_to_ascii([Num | Reversed], 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 + true -> + "&#" ++ integer_to_list(Char) ++ ";"; + false -> + [Char] + end. diff --git a/lib/inets/src/http_server/Makefile b/lib/inets/src/http_server/Makefile index ce1405011e..3c36b384b8 100644 --- a/lib/inets/src/http_server/Makefile +++ b/lib/inets/src/http_server/Makefile @@ -1,7 +1,7 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 2005-2010. All Rights Reserved. +# Copyright Ericsson AB 2005-2011. All Rights Reserved. # # The contents of this file are subject to the Erlang Public License, # Version 1.1, (the "License"); you may not use this file except in @@ -98,12 +98,16 @@ INETS_FLAGS = -D'SERVER_SOFTWARE="$(APPLICATION)/$(VSN)"' # ---------------------------------------------------- # FLAGS # ---------------------------------------------------- -INETS_ERL_FLAGS += -I ../http_lib -I ../inets_app -pa ../../ebin - -ERL_COMPILE_FLAGS += $(INETS_ERL_FLAGS) \ - $(INETS_FLAGS) \ - +'{parse_transform,sys_pre_attributes}' \ - +'{attribute,insert,app_vsn,$(APP_VSN)}' +INETS_ERL_FLAGS += \ + -I ../http_lib \ + -I ../inets_app \ + -pa $(ERL_TOP)/lib/inets/ebin + +ERL_COMPILE_FLAGS += \ + $(INETS_ERL_FLAGS) \ + $(INETS_FLAGS) \ + +'{parse_transform,sys_pre_attributes}' \ + +'{attribute,insert,app_vsn,$(APP_VSN)}' # ---------------------------------------------------- diff --git a/lib/inets/src/http_server/httpd.erl b/lib/inets/src/http_server/httpd.erl index 8fe54ccef6..a88d002b03 100644 --- a/lib/inets/src/http_server/httpd.erl +++ b/lib/inets/src/http_server/httpd.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% Copyright Ericsson AB 1997-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -349,8 +349,8 @@ foreach([KeyValue|Rest]) -> {ok, Plus2Space, _} = inets_regexp:gsub(KeyValue,"[\+]"," "), case inets_regexp:split(Plus2Space,"=") of {ok,[Key|Value]} -> - [{httpd_util:decode_hex(Key), - httpd_util:decode_hex(lists:flatten(Value))}|foreach(Rest)]; + [{http_uri:decode(Key), http_uri:decode(lists:flatten(Value))}| + foreach(Rest)]; {ok,_} -> foreach(Rest) end. diff --git a/lib/inets/src/http_server/httpd_acceptor.erl b/lib/inets/src/http_server/httpd_acceptor.erl index 568fd3c610..fef3fe58c6 100644 --- a/lib/inets/src/http_server/httpd_acceptor.erl +++ b/lib/inets/src/http_server/httpd_acceptor.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2001-2009. All Rights Reserved. +%% Copyright Ericsson AB 2001-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -21,6 +21,7 @@ -include("httpd.hrl"). -include("httpd_internal.hrl"). +-include("inets_internal.hrl"). %% Internal application API -export([start_link/5, start_link/6]). diff --git a/lib/inets/src/http_server/httpd_conf.erl b/lib/inets/src/http_server/httpd_conf.erl index 5ca2e47eb5..2faf13e9b3 100644 --- a/lib/inets/src/http_server/httpd_conf.erl +++ b/lib/inets/src/http_server/httpd_conf.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% Copyright Ericsson AB 1997-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -30,8 +30,9 @@ validate_properties/1]). -define(VMODULE,"CONF"). --include("httpd.hrl"). -include("httpd_internal.hrl"). +-include("httpd.hrl"). +-include_lib("inets/src/http_lib/http_internal.hrl"). %%%========================================================================= diff --git a/lib/inets/src/http_server/httpd_file.erl b/lib/inets/src/http_server/httpd_file.erl index 5fd529100e..fbe713ecd1 100644 --- a/lib/inets/src/http_server/httpd_file.erl +++ b/lib/inets/src/http_server/httpd_file.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2006-2009. All Rights Reserved. +%% Copyright Ericsson AB 2006-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -22,6 +22,7 @@ -export([handle_error/4]). -include("httpd.hrl"). +-include("httpd_internal.hrl"). handle_error(eacces, Op, ModData, Path) -> handle_error(403, Op, ModData, Path,""); diff --git a/lib/inets/src/http_server/httpd_request.erl b/lib/inets/src/http_server/httpd_request.erl index 8eee08e766..75f03c4fc2 100644 --- a/lib/inets/src/http_server/httpd_request.erl +++ b/lib/inets/src/http_server/httpd_request.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2010. All Rights Reserved. +%% Copyright Ericsson AB 2005-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -256,9 +256,9 @@ validate_uri(RequestURI) -> UriNoQueryNoHex = case string:str(RequestURI, "?") of 0 -> - (catch httpd_util:decode_hex(RequestURI)); + (catch http_uri:decode(RequestURI)); Ndx -> - (catch httpd_util:decode_hex(string:left(RequestURI, Ndx))) + (catch http_uri:decode(string:left(RequestURI, Ndx))) end, case UriNoQueryNoHex of {'EXIT',_Reason} -> diff --git a/lib/inets/src/http_server/httpd_sup.erl b/lib/inets/src/http_server/httpd_sup.erl index 1507c6852a..90d74e3a14 100644 --- a/lib/inets/src/http_server/httpd_sup.erl +++ b/lib/inets/src/http_server/httpd_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2010. All Rights Reserved. +%% Copyright Ericsson AB 2004-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -37,6 +37,7 @@ -define(TIMEOUT, 15000). -include("httpd_internal.hrl"). +-include("inets_internal.hrl"). %%%========================================================================= diff --git a/lib/inets/src/http_server/httpd_util.erl b/lib/inets/src/http_server/httpd_util.erl index cfad79638f..7fe5d6d152 100644 --- a/lib/inets/src/http_server/httpd_util.erl +++ b/lib/inets/src/http_server/httpd_util.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% Copyright Ericsson AB 1997-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -21,7 +21,7 @@ -export([ip_address/2, lookup/2, lookup/3, multi_lookup/2, lookup_mime/2, lookup_mime/3, lookup_mime_default/2, lookup_mime_default/3, reason_phrase/1, message/3, rfc1123_date/0, - rfc1123_date/1, day/1, month/1, decode_hex/1, + rfc1123_date/1, day/1, month/1, flatlength/1, split_path/1, split_script_path/1, suffix/1, split/3, uniq/1, make_name/2,make_name/3,make_name/4,strip/1, @@ -32,7 +32,8 @@ dir_validate/2, file_validate/2, mime_type_validate/1, mime_types_validate/1, custom_date/0]). --export([encode_hex/1]). +-export([encode_hex/1, decode_hex/1]). + -include_lib("kernel/include/file.hrl"). ip_address({_,_,_,_} = Address, _IpFamily) -> @@ -175,14 +176,15 @@ reason_phrase(_) -> "Internal Server Error". %% message message(301,URL,_) -> - "The document has moved here."; + "The document has moved here."; message(304, _URL,_) -> "The document has not been changed."; -message(400,none,_) -> +message(400, none, _) -> "Your browser sent a query that this server could not understand."; -message(400,Msg,_) -> - "Your browser sent a query that this server could not understand. "++Msg; -message(401,none,_) -> +message(400, Msg, _) -> + "Your browser sent a query that this server could not understand. " ++ + http_util:html_encode(Msg); +message(401, none, _) -> "This server could not verify that you are authorized to access the document you requested. Either you supplied the wrong @@ -190,40 +192,57 @@ credentials (e.g., bad password), or your browser doesn't understand how to supply the credentials required."; message(403,RequestURI,_) -> - "You don't have permission to access "++RequestURI++" on this server."; + "You don't have permission to access " ++ + http_util:html_encode(RequestURI) ++ + " on this server."; message(404,RequestURI,_) -> - "The requested URL "++RequestURI++" was not found on this server."; + "The requested URL " ++ + http_util:html_encode(RequestURI) ++ + " was not found on this server."; message(408, Timeout, _) -> Timeout; message(412,none,_) -> "The requested preconditions where false"; message(413, Reason,_) -> - "Entity: " ++ Reason; + "Entity: " ++ http_util:html_encode(Reason); message(414,ReasonPhrase,_) -> - "Message "++ReasonPhrase++"."; + "Message " ++ http_util:html_encode(ReasonPhrase) ++ "."; message(416,ReasonPhrase,_) -> - ReasonPhrase; + http_util:html_encode(ReasonPhrase); message(500,_,ConfigDB) -> - ServerAdmin=lookup(ConfigDB,server_admin,"unknown@unknown"), + ServerAdmin = lookup(ConfigDB, server_admin, "unknown@unknown"), "The server encountered an internal error or " "misconfiguration and was unable to complete " "your request.

Please contact the server administrator " - ++ ServerAdmin ++ ", and inform them of the time the error occurred " + ++ http_util:html_encode(ServerAdmin) ++ + ", and inform them of the time the error occurred " "and anything you might have done that may have caused the error."; message(501,{Method, RequestURI, HTTPVersion}, _ConfigDB) -> if is_atom(Method) -> atom_to_list(Method)++ - " to "++RequestURI++" ("++HTTPVersion++") not supported."; + " to " ++ + http_util:html_encode(RequestURI) ++ + " (" ++ HTTPVersion ++ ") not supported."; is_list(Method) -> Method++ - " to "++RequestURI++" ("++HTTPVersion++") not supported." + " to " ++ + http_util:html_encode(RequestURI) ++ + " (" ++ HTTPVersion ++ ") not supported." end; message(503, String, _ConfigDB) -> - "This service in unavailable due to: "++String. + "This service in unavailable due to: " ++ http_util:html_encode(String). + +maybe_encode(URI) -> + case lists:member($%, URI) of + true -> + URI; + false -> + http_uri:encode(URI) + end. %%convert_rfc_date(Date)->{{YYYY,MM,DD},{HH,MIN,SEC}} @@ -381,16 +400,11 @@ month(12) -> "Dec". %% decode_hex -decode_hex([$%,Hex1,Hex2|Rest]) -> - [hex2dec(Hex1)*16+hex2dec(Hex2)|decode_hex(Rest)]; -decode_hex([First|Rest]) -> - [First|decode_hex(Rest)]; -decode_hex([]) -> - []. +decode_hex(URI) -> + http_uri:decode(URI). -hex2dec(X) when (X>=$0) andalso (X=<$9) -> X-$0; -hex2dec(X) when (X>=$A) andalso (X=<$F) -> X-$A+10; -hex2dec(X) when (X>=$a) andalso (X=<$f) -> X-$a+10. +encode_hex(URI) -> + http_uri:encode(URI). %% flatlength flatlength(List) -> @@ -411,7 +425,7 @@ split_path(Path) -> case inets_regexp:match(Path,"[\?].*\$") of %% A QUERY_STRING exists! {match,Start,Length} -> - {httpd_util:decode_hex(string:substr(Path,1,Start-1)), + {http_uri:decode(string:substr(Path,1,Start-1)), string:substr(Path,Start,Length)}; %% A possible PATH_INFO exists! nomatch -> @@ -419,9 +433,9 @@ split_path(Path) -> end. split_path([],SoFar) -> - {httpd_util:decode_hex(lists:reverse(SoFar)),[]}; + {http_uri:decode(lists:reverse(SoFar)),[]}; split_path([$/|Rest],SoFar) -> - Path=httpd_util:decode_hex(lists:reverse(SoFar)), + Path = http_uri:decode(lists:reverse(SoFar)), case file:read_file_info(Path) of {ok,FileInfo} when FileInfo#file_info.type =:= regular -> {Path,[$/|Rest]}; @@ -454,7 +468,7 @@ pathinfo_querystring([C|Rest], SoFar) -> pathinfo_querystring(Rest, [C|SoFar]). split_script_path([$?|QueryString], SoFar) -> - Path = httpd_util:decode_hex(lists:reverse(SoFar)), + Path = http_uri:decode(lists:reverse(SoFar)), case file:read_file_info(Path) of {ok,FileInfo} when FileInfo#file_info.type =:= regular -> {Path, [$?|QueryString]}; @@ -464,7 +478,7 @@ split_script_path([$?|QueryString], SoFar) -> not_a_script end; split_script_path([], SoFar) -> - Path = httpd_util:decode_hex(lists:reverse(SoFar)), + Path = http_uri:decode(lists:reverse(SoFar)), case file:read_file_info(Path) of {ok,FileInfo} when FileInfo#file_info.type =:= regular -> {Path, []}; @@ -474,7 +488,7 @@ split_script_path([], SoFar) -> not_a_script end; split_script_path([$/|Rest], SoFar) -> - Path = httpd_util:decode_hex(lists:reverse(SoFar)), + Path = http_uri:decode(lists:reverse(SoFar)), case file:read_file_info(Path) of {ok, FileInfo} when FileInfo#file_info.type =:= regular -> {Path, [$/|Rest]}; @@ -608,8 +622,6 @@ hexlist_to_integer(List)-> %%---------------------------------------------------------------------- %%Converts an integer to an hexlist %%---------------------------------------------------------------------- -encode_hex(Num)-> - integer_to_hexlist(Num). integer_to_hexlist(Num) when is_integer(Num) -> http_util:integer_to_hexlist(Num). diff --git a/lib/inets/src/http_server/mod_alias.erl b/lib/inets/src/http_server/mod_alias.erl index ec0a12242f..41fcdb5e3a 100644 --- a/lib/inets/src/http_server/mod_alias.erl +++ b/lib/inets/src/http_server/mod_alias.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% Copyright Ericsson AB 1997-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -29,6 +29,7 @@ -include("httpd.hrl"). -include("httpd_internal.hrl"). +-include("inets_internal.hrl"). -define(VMODULE,"ALIAS"). diff --git a/lib/inets/src/http_server/mod_auth.erl b/lib/inets/src/http_server/mod_auth.erl index 07cafb4726..b1d8b03fe4 100644 --- a/lib/inets/src/http_server/mod_auth.erl +++ b/lib/inets/src/http_server/mod_auth.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1997-2009. All Rights Reserved. +%% Copyright Ericsson AB 1997-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -38,6 +38,7 @@ -include("httpd.hrl"). -include("mod_auth.hrl"). -include("httpd_internal.hrl"). +-include("inets_internal.hrl"). -define(VMODULE,"AUTH"). diff --git a/lib/inets/src/http_server/mod_auth_dets.erl b/lib/inets/src/http_server/mod_auth_dets.erl index bc6c2b70a0..2abf5c517a 100644 --- a/lib/inets/src/http_server/mod_auth_dets.erl +++ b/lib/inets/src/http_server/mod_auth_dets.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1998-2009. All Rights Reserved. +%% Copyright Ericsson AB 1998-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -35,6 +35,7 @@ -export([store_directory_data/3]). -include("httpd.hrl"). +-include("inets_internal.hrl"). -include("mod_auth.hrl"). store_directory_data(_Directory, DirData, Server_root) -> diff --git a/lib/inets/src/http_server/mod_auth_plain.erl b/lib/inets/src/http_server/mod_auth_plain.erl index d88859d28a..12342ba8da 100644 --- a/lib/inets/src/http_server/mod_auth_plain.erl +++ b/lib/inets/src/http_server/mod_auth_plain.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1998-2009. All Rights Reserved. +%% Copyright Ericsson AB 1998-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -22,6 +22,7 @@ -include("httpd.hrl"). -include("mod_auth.hrl"). -include("httpd_internal.hrl"). +-include("inets_internal.hrl"). -define(VMODULE,"AUTH_PLAIN"). diff --git a/lib/inets/src/http_server/mod_auth_server.erl b/lib/inets/src/http_server/mod_auth_server.erl index 5f9e59be9d..fc50356838 100644 --- a/lib/inets/src/http_server/mod_auth_server.erl +++ b/lib/inets/src/http_server/mod_auth_server.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2001-2009. All Rights Reserved. +%% Copyright Ericsson AB 2001-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -22,6 +22,7 @@ -include("httpd.hrl"). -include("httpd_internal.hrl"). +-include("inets_internal.hrl"). -behaviour(gen_server). diff --git a/lib/inets/src/http_server/mod_dir.erl b/lib/inets/src/http_server/mod_dir.erl index cdc7cc01e4..35e9de24e2 100644 --- a/lib/inets/src/http_server/mod_dir.erl +++ b/lib/inets/src/http_server/mod_dir.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1997-2009. All Rights Reserved. +%% Copyright Ericsson AB 1997-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -18,9 +18,11 @@ %% %% -module(mod_dir). + -export([do/1]). -include("httpd.hrl"). +-include("httpd_internal.hrl"). %% do @@ -57,7 +59,7 @@ do_dir(Info) -> case file:read_file_info(DefaultPath) of {ok,FileInfo} when FileInfo#file_info.type == directory -> DecodedRequestURI = - httpd_util:decode_hex(Info#mod.request_uri), + http_uri:decode(Info#mod.request_uri), ?DEBUG("do_dir -> ~n" " Path: ~p~n" " DefaultPath: ~p~n" diff --git a/lib/inets/src/http_server/mod_esi.erl b/lib/inets/src/http_server/mod_esi.erl index cb33544540..bbdb67bb0e 100644 --- a/lib/inets/src/http_server/mod_esi.erl +++ b/lib/inets/src/http_server/mod_esi.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% Copyright Ericsson AB 1997-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -29,6 +29,7 @@ -export([do/1, load/2, store/2]). -include("httpd.hrl"). +-include("inets_internal.hrl"). -define(VMODULE,"ESI"). -define(DEFAULT_ERL_TIMEOUT,15000). diff --git a/lib/inets/src/http_server/mod_include.erl b/lib/inets/src/http_server/mod_include.erl index 534eba8a36..790bf8f937 100644 --- a/lib/inets/src/http_server/mod_include.erl +++ b/lib/inets/src/http_server/mod_include.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1997-2009. All Rights Reserved. +%% Copyright Ericsson AB 1997-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -186,9 +186,9 @@ document_uri(ConfigDB, RequestURI) -> FileName = string:substr(Path,Start,Length), case inets_regexp:match(VirtualPath, FileName++"\$") of {match, _, _} -> - httpd_util:decode_hex(VirtualPath)++AfterPath; + http_uri:decode(VirtualPath)++AfterPath; nomatch -> - string:strip(httpd_util:decode_hex(VirtualPath),right,$/)++ + string:strip(http_uri:decode(VirtualPath),right,$/)++ "/"++FileName++AfterPath end. diff --git a/lib/inets/src/http_server/mod_security.erl b/lib/inets/src/http_server/mod_security.erl index 95793e1cfb..5cdbeb9792 100644 --- a/lib/inets/src/http_server/mod_security.erl +++ b/lib/inets/src/http_server/mod_security.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1998-2009. All Rights Reserved. +%% Copyright Ericsson AB 1998-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -32,6 +32,7 @@ -include("httpd.hrl"). -include("httpd_internal.hrl"). +-include("inets_internal.hrl"). -define(VMODULE,"SEC"). diff --git a/lib/inets/src/http_server/mod_security_server.erl b/lib/inets/src/http_server/mod_security_server.erl index 58060686b3..ca8bee0c8e 100644 --- a/lib/inets/src/http_server/mod_security_server.erl +++ b/lib/inets/src/http_server/mod_security_server.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2001-2009. All Rights Reserved. +%% Copyright Ericsson AB 2001-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -45,6 +45,7 @@ -include("httpd.hrl"). -include("httpd_internal.hrl"). +-include("inets_internal.hrl"). -behaviour(gen_server). diff --git a/lib/inets/src/inets_app/inets.appup.src b/lib/inets/src/inets_app/inets.appup.src index d86e998f01..c31b0deb30 100644 --- a/lib/inets/src/inets_app/inets.appup.src +++ b/lib/inets/src/inets_app/inets.appup.src @@ -1,7 +1,7 @@ %% This is an -*- erlang -*- file. %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1999-2010. All Rights Reserved. +%% Copyright Ericsson AB 1999-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -18,41 +18,29 @@ {"%VSN%", [ + {"5.3.4", + [ + {restart_application, inets} + ] + }, {"5.3.3", [ - {load_module, inets, soft_purge, soft_purge, []}, - {update, httpc_handler, soft, soft_purge, soft_purge, []}, - {update, httpc_manager, soft, soft_purge, soft_purge, []} + {restart_application, inets} ] }, {"5.3.2", [ - {load_module, inets, soft_purge, soft_purge, []}, - {load_module, http_util, soft_purge, soft_purge, []}, - {load_module, httpc_cookie, soft_purge, soft_purge, []}, - {update, httpc_handler, soft, soft_purge, soft_purge, []}, - {update, httpc_manager, soft, soft_purge, soft_purge, []} + {restart_application, inets} ] }, {"5.3.1", [ - {load_module, inets, soft_purge, soft_purge, []}, - {load_module, http_util, soft_purge, soft_purge, []}, - {load_module, httpc, soft_purge, soft_purge, []}, - {load_module, httpc_cookie, soft_purge, soft_purge, []}, - {update, httpc_handler, soft, soft_purge, soft_purge, [httpc_manager]}, - {update, httpc_manager, soft, soft_purge, soft_purge, []} + {restart_application, inets} ] }, {"5.3", [ - {load_module, inets, soft_purge, soft_purge, []}, - {load_module, http_util, soft_purge, soft_purge, []}, - {load_module, httpc, soft_purge, soft_purge, []}, - {load_module, httpc_cookie, soft_purge, soft_purge, []}, - {update, httpc_handler, soft, soft_purge, soft_purge, [httpc_manager]}, - {update, httpc_manager, soft, soft_purge, soft_purge, []}, - {load_module, mod_esi, soft_purge, soft_purge, []} + {restart_application, inets} ] }, {"5.2", @@ -72,41 +60,29 @@ } ], [ + {"5.3.4", + [ + {restart_application, inets} + ] + }, {"5.3.3", [ - {load_module, inets, soft_purge, soft_purge, []}, - {update, httpc_handler, soft, soft_purge, soft_purge, []}, - {update, httpc_manager, soft, soft_purge, soft_purge, []} + {restart_application, inets} ] }, {"5.3.2", [ - {load_module, inets, soft_purge, soft_purge, []}, - {load_module, http_util, soft_purge, soft_purge, []}, - {load_module, httpc_cookie, soft_purge, soft_purge, []}, - {update, httpc_handler, soft, soft_purge, soft_purge, []}, - {update, httpc_manager, soft, soft_purge, soft_purge, []} + {restart_application, inets} ] }, {"5.3.1", [ - {load_module, inets, soft_purge, soft_purge, []}, - {load_module, http_util, soft_purge, soft_purge, []}, - {load_module, httpc, soft_purge, soft_purge, []}, - {load_module, httpc_cookie, soft_purge, soft_purge, []}, - {update, httpc_handler, soft, soft_purge, soft_purge, [httpc_manager]}, - {update, httpc_manager, soft, soft_purge, soft_purge, []} + {restart_application, inets} ] }, {"5.3", [ - {load_module, inets, soft_purge, soft_purge, []}, - {load_module, http_util, soft_purge, soft_purge, []}, - {load_module, httpc, soft_purge, soft_purge, []}, - {load_module, httpc_cookie, soft_purge, soft_purge, []}, - {update, httpc_handler, soft, soft_purge, soft_purge, [httpc_manager]}, - {update, httpc_manager, soft, soft_purge, soft_purge, []}, - {load_module, mod_esi, soft_purge, soft_purge, []} + {restart_application, inets} ] }, {"5.2", diff --git a/lib/inets/test/httpc_SUITE.erl b/lib/inets/test/httpc_SUITE.erl index f2e8bebe07..71f017dae6 100644 --- a/lib/inets/test/httpc_SUITE.erl +++ b/lib/inets/test/httpc_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2010. All Rights Reserved. +%% Copyright Ericsson AB 2004-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -262,7 +262,9 @@ finish(Config) -> ok; _ -> test_server:timetrap_cancel(Dog) - end. + end, + (catch application:stop(inets)). + %%------------------------------------------------------------------------- %% Test cases starts here. @@ -1163,19 +1165,26 @@ proxy_options(suite) -> proxy_options(Config) when is_list(Config) -> case ?config(skip, Config) of undefined -> - case http:request(options, {?PROXY_URL, []}, [], []) of - {ok, {{_,200,_}, Headers, _}} -> - case lists:keysearch("allow", 1, Headers) of - {value, {"allow", _}} -> - ok; - _ -> - test_server:fail(http_options_request_failed) - end; - Unexpected -> - test_server:fail({unexpected_result, Unexpected}) - end; + Command = + fun() -> http:request(options, {?PROXY_URL, []}, [], []) end, + Verify = + fun({ok, {{_,200,_}, Headers, _}}) -> + case lists:keysearch("allow", 1, Headers) of + {value, {"allow", _}} -> + ok; + _ -> + tsf(http_options_request_failed) + end; + ({ok, {{_, 501, "Not Implemented"}, _Hdrs, _}}) -> + skip(options_not_implemented_in_server_501_ni); + ({ok, Unexpected}) -> + tsf({unexpected_success, Unexpected}); + (Unexpected) -> + tsf({unexpected_result, Unexpected}) + end, + expect(Command, Verify); Reason -> - {skip, Reason} + skip(Reason) end. @@ -1187,18 +1196,26 @@ proxy_head(suite) -> proxy_head(Config) when is_list(Config) -> case ?config(skip, Config) of undefined -> - case http:request(head, {?PROXY_URL, []}, [], []) of - {ok, {{_,200, _}, [_ | _], []}} -> - ok; - Unexpected -> - test_server:fail({unexpected_result, Unexpected}) - end; + Command = + fun() -> http:request(head, {?PROXY_URL, []}, [], []) end, + Verify = + fun({ok, {{_,200, _}, [_ | _], []}}) -> + ok; + ({ok, {{_,503,"Service Unavailable"}, [_ | _], []}}) -> + skip(head_not_implemented_in_server_503_su); + ({ok, Result}) -> + tsf({unexpected_success, Result}); + (Unexpected) -> + tsf({unexpected_result, Unexpected}) + end, + expect(Command, Verify); Reason -> - {skip, Reason} + skip(Reason) end. %%------------------------------------------------------------------------- + proxy_get(doc) -> ["Perform a GET request that goes through a proxy."]; proxy_get(suite) -> @@ -1216,7 +1233,9 @@ proxy_get(Config) when is_list(Config) -> {skip, Reason} end. + %%------------------------------------------------------------------------- + proxy_emulate_lower_versions(doc) -> ["Perform requests as 0.9 and 1.0 clients."]; proxy_emulate_lower_versions(suite) -> @@ -1260,7 +1279,9 @@ proxy_emulate_lower_versions(Config) when is_list(Config) -> pelv_get(Version) -> http:request(get, {?PROXY_URL, []}, [{version, Version}], []). + %%------------------------------------------------------------------------- + proxy_trace(doc) -> ["Perform a TRACE request that goes through a proxy."]; proxy_trace(suite) -> @@ -1268,11 +1289,12 @@ proxy_trace(suite) -> proxy_trace(Config) when is_list(Config) -> %%{ok, {{_,200,_}, [_ | _], "TRACE " ++ _}} = %% http:request(trace, {?PROXY_URL, []}, [], []), - {skip, "HTTP TRACE is no longer allowed on the ?PROXY_URL server due " - "to security reasons"}. + skip("HTTP TRACE is no longer allowed on the ?PROXY_URL server due " + "to security reasons"). %%------------------------------------------------------------------------- + proxy_post(doc) -> ["Perform a POST request that goes through a proxy. Note the server" " will reject the request this is a test of the sending of the" @@ -1280,21 +1302,34 @@ proxy_post(doc) -> proxy_post(suite) -> []; proxy_post(Config) when is_list(Config) -> - case ?config(skip, Config) of - undefined -> - case http:request(post, {?PROXY_URL, [], - "text/plain", "foobar"}, [],[]) of - {ok, {{_,405,_}, [_ | _], [_ | _]}} -> - ok; - Unexpected -> - test_server:fail({unexpected_result, Unexpected}) - end; - Reason -> - {skip, Reason} - end. + %% After the switch to the erlang.org app, + %% the post will succeed, so we skip this + %% until we can find a new way of testing + %% this. + %% case ?config(skip, Config) of + %% undefined -> + %% Command = + %% fun() -> + %% http:request(post, {?PROXY_URL, [], + %% "text/plain", "foobar"}, [],[]) + %% end, + %% Verify = + %% fun({ok, {{_,405,_}, [_ | _], [_ | _]}}) -> + %% ok; + %% ({ok, Result}) -> + %% tsf({unexpected_success, Result}); + %% (Unexpected) -> + %% tsf({unexpected_result, Unexpected}) + %% end, + %% expect(Command, Verify); + %% Reason -> + %% skip(Reason) + %% end. + skip("temporary skip"). %%------------------------------------------------------------------------- + proxy_put(doc) -> ["Perform a PUT request that goes through a proxy. Note the server" " will reject the request this is a test of the sending of the" @@ -1302,22 +1337,28 @@ proxy_put(doc) -> proxy_put(suite) -> []; proxy_put(Config) when is_list(Config) -> - case ?config(skip, Config) of - undefined -> - case http:request(put, {"http://www.erlang.org/foobar.html", [], - "html", "

foo

" - "

bar

"}, [], []) of - {ok, {{_,405,_}, [_ | _], [_ | _]}} -> - ok; - Unexpected -> - test_server:fail({unexpected_result, Unexpected}) - end; - Reason -> - {skip, Reason} - end. + %% After the switch to the erlang.org app, + %% the post will succeed, so we skip this + %% until we can find a new way of testing + %% this. + %% case ?config(skip, Config) of + %% undefined -> + %% case http:request(put, {"http://www.erlang.org/foobar.html", [], + %% "html", "

foo

" + %% "

bar

"}, [], []) of + %% {ok, {{_,405,_}, [_ | _], [_ | _]}} -> + %% ok; + %% Unexpected -> + %% test_server:fail({unexpected_result, Unexpected}) + %% end; + %% Reason -> + %% {skip, Reason} + %% end. + skip("temporary skip"). %%------------------------------------------------------------------------- + proxy_delete(doc) -> ["Perform a DELETE request that goes through a proxy. Note the server" " will reject the request this is a test of the sending of the" @@ -1326,21 +1367,32 @@ proxy_delete(doc) -> proxy_delete(suite) -> []; proxy_delete(Config) when is_list(Config) -> - case ?config(skip, Config) of - undefined -> - URL = ?PROXY_URL ++ "/foobar.html", - case http:request(delete, {URL, []}, [], []) of - {ok, {{_,404,_}, [_ | _], [_ | _]}} -> - ok; - Unexpected -> - test_server:fail({unexpected_result, Unexpected}) - end; - Reason -> - {skip, Reason} - end. + %% After the switch to the erlang.org app, + %% the post will succeed, so we skip this + %% until we can find a new way of testing + %% this. + %% case ?config(skip, Config) of + %% undefined -> + %% URL = ?PROXY_URL ++ "/foobar.html", + %% Command = fun() -> http:request(delete, {URL, []}, [], []) end, + %% Verify = + %% fun({ok, {{_,404,_}, [_ | _], [_ | _]}}) -> + %% ok; + %% ({ok, Result}) -> + %% tsf({unexpected_success, Result}); + %% (Unexpected) -> + %% tsf({unexpected_result, Unexpected}) + %% end, + %% expect(Command, Verify); + + %% Reason -> + %% skip(Reason) + %% end. + skip("temporary skip"). %%------------------------------------------------------------------------- + proxy_headers(doc) -> ["Use as many request headers as possible"]; proxy_headers(suite) -> @@ -2489,7 +2541,7 @@ otp_8739_dummy_server_init(Parent) -> Parent ! {port, Port}, otp_8739_dummy_server_main(Parent, ListenSocket). -otp_8739_dummy_server_main(Parent, ListenSocket) -> +otp_8739_dummy_server_main(_Parent, ListenSocket) -> case gen_tcp:accept(ListenSocket) of {ok, Sock} -> %% Ignore the request, and simply wait for the socket to close @@ -2990,29 +3042,46 @@ content_length([_Head | Tail]) -> content_length(Tail). provocate_not_modified_bug(Url) -> + tsp("provocate_not_modified_bug -> entry with" + "~n Url: ~p", [Url]), + Timeout = 15000, %% 15s should be plenty - {ok, {{_, 200, _}, ReplyHeaders, _Body}} = - http:request(get, {Url, []}, [{timeout, Timeout}], []), - Etag = pick_header(ReplyHeaders, "ETag"), - Last = pick_header(ReplyHeaders, "last-modified"), - - case http:request(get, {Url, [{"If-None-Match", Etag}, - {"If-Modified-Since", Last}]}, - [{timeout, 15000}], - []) of - {ok, {{_, 304, _}, _, _}} -> %% The expected reply - page_unchanged; - {ok, {{_, 200, _}, _, _}} -> - %% If the page has changed since the - %% last request we retry to - %% trigger the bug - provocate_not_modified_bug(Url); - {error, timeout} -> - %% Not what we expected. Tcpdump can be used to - %% verify that we receive the complete http-reply - %% but still time out. - incorrect_result + case http:request(get, {Url, []}, [{timeout, Timeout}], []) of + {ok, {{_, 200, _}, ReplyHeaders, _Body}} -> + tsp("provocate_not_modified_bug -> received 200 reply" + "~n ReplyHeaders: ~p", [ReplyHeaders]), + Etag = pick_header(ReplyHeaders, "ETag"), + Last = pick_header(ReplyHeaders, "last-modified"), + case http:request(get, {Url, [{"If-None-Match", Etag}, + {"If-Modified-Since", Last}]}, + [{timeout, 15000}], + []) of + {ok, {{_, 304, _}, _, _}} -> %% The expected reply + tsp("provocate_not_modified_bug -> unchanged", []), + page_unchanged; + {ok, {{_, 200, _}, _, _}} -> + %% If the page has changed since the + %% last request we retry to + %% trigger the bug + tsp("provocate_not_modified_bug -> changed", []), + provocate_not_modified_bug(Url); + {error, timeout} -> + %% Not what we expected. Tcpdump can be used to + %% verify that we receive the complete http-reply + %% but still time out. + tsp("provocate_not_modified_bug -> timeout", []), + incorrect_result + end; + {ok, UnexpectedReply, ReplyHeaders, _} -> + tsp("provocate_not_modified_bug -> received unexpected reply" + "~n UnexpectedReply: ~p" + "~n ReplyHeaders: ~p", [UnexpectedReply, ReplyHeaders]), + unexpected_reply; + {error, Reason} -> + tsp("provocate_not_modified_bug -> unexpected failure" + "~n Reason: ~p", [Reason]), + unexpected_error end. pick_header(Headers, Name) -> @@ -3029,6 +3098,10 @@ not_implemented_yet() -> exit(not_implemented_yet). +expect(Command, Verify) -> + Result = (catch Command()), + Verify(Result). + p(F) -> p(F, []). @@ -3038,7 +3111,24 @@ p(F, A) -> tsp(F) -> tsp(F, []). tsp(F, A) -> - test_server:format("~p ~p:" ++ F ++ "~n", [self(), ?MODULE | A]). + Timestamp = formated_timestamp(), + test_server:format("** ~s ** ~p ~p:" ++ F ++ "~n", [Timestamp, self(), ?MODULE | A]). + +formated_timestamp() -> + format_timestamp( os:timestamp() ). + +format_timestamp({_N1, _N2, N3} = Now) -> + {Date, Time} = calendar:now_to_datetime(Now), + {YYYY,MM,DD} = Date, + {Hour,Min,Sec} = Time, + FormatDate = + io_lib:format("~.4w:~.2.0w:~.2.0w ~.2.0w:~.2.0w:~.2.0w 4~w", + [YYYY,MM,DD,Hour,Min,Sec,round(N3/1000)]), + lists:flatten(FormatDate). tsf(Reason) -> test_server:fail(Reason). + +skip(Reason) -> + {skip, Reason}. + diff --git a/lib/inets/test/httpd_basic_SUITE.erl b/lib/inets/test/httpd_basic_SUITE.erl index f86c1fcb49..ed0fe942cf 100644 --- a/lib/inets/test/httpd_basic_SUITE.erl +++ b/lib/inets/test/httpd_basic_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2009. All Rights Reserved. +%% Copyright Ericsson AB 2007-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -25,13 +25,16 @@ %% Note: This directive should only be used in test suites. -compile(export_all). +-define(URL_START, "http://localhost:"). + all(doc) -> ["Basic test of httpd."]; all(suite) -> [ uri_too_long_414, - header_too_long_413 + header_too_long_413, + escaped_url_in_error_body ]. %%-------------------------------------------------------------------- @@ -132,5 +135,57 @@ header_too_long_413(Config) when is_list(Config) -> inets:stop(httpd, Pid). +escaped_url_in_error_body(doc) -> + ["Test Url-encoding see OTP-8940"]; +escaped_url_in_error_body(suite) -> + []; +escaped_url_in_error_body(Config) when is_list(Config) -> + HttpdConf = ?config(httpd_conf, Config), + {ok, Pid} = inets:start(httpd, [{port, 0} | HttpdConf]), + Info = httpd:info(Pid), + Port = proplists:get_value(port, Info), + _Address = proplists:get_value(bind_address, Info), + Path = "/this_is_bold", + URL = ?URL_START ++ integer_to_list(Port) ++ Path, + EscapedPath = http_uri:encode(Path), + case httpc:request(get, {URL, []}, + [{url_encode, true}, + {version, "HTTP/1.0"}], + [{full_result, false}]) of + {ok, {404, Body1}} -> + case find_URL_path(string:tokens(Body1, " ")) of + EscapedPath -> + ok; + BadPath1 -> + tsf({unexpected_path_1, EscapedPath, BadPath1}) + end; + {ok, UnexpectedOK1} -> + tsf({unexpected_ok_1, UnexpectedOK1}) + end, + + case httpc:request(get, {URL, []}, + [{version, "HTTP/1.0"}], + [{full_result, false}]) of + {ok, {404, Body2}} -> + HTMLEncodedPath = http_util:html_encode(Path), + case find_URL_path(string:tokens(Body2, " ")) of + HTMLEncodedPath -> + ok; + BadPath2 -> + tsf({unexpected_path_2, EscapedPath, BadPath2}) + end; + {ok, UnexpectedOK2} -> + tsf({unexpected_ok_2, UnexpectedOK2}) + end, + inets:stop(httpd, Pid). + +find_URL_path([]) -> + ""; +find_URL_path(["URL", URL | _]) -> + URL; +find_URL_path([_ | Rest]) -> + find_URL_path(Rest). +tsf(Reason) -> + test_server:fail(Reason). diff --git a/lib/inets/test/inets.spec b/lib/inets/test/inets.spec index a9b4524295..ba525f62c1 100644 --- a/lib/inets/test/inets.spec +++ b/lib/inets/test/inets.spec @@ -1,2 +1,2 @@ {topcase, {dir, "../inets_test"}}. -{hosts, ["tuor"]}. +{hosts, ["fobi"]}. diff --git a/lib/inets/test/inets_test_lib.erl b/lib/inets/test/inets_test_lib.erl index 6af2ad32f7..609bc89e15 100644 --- a/lib/inets/test/inets_test_lib.erl +++ b/lib/inets/test/inets_test_lib.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2001-2009. All Rights Reserved. +%% Copyright Ericsson AB 2001-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -32,14 +32,64 @@ -export([non_pc_tc_maybe_skip/4, os_based_skip/1]). start_http_server(Conf) -> - application:load(inets), - ok = application:set_env(inets, services, [{httpd, Conf}]), - ok = application:start(inets). + ?DEBUG("start_http_server -> entry with" + "~n Conf: ~p", [Conf]), + inets_ensure_loaded(), + inets_set_env(services, [{httpd, Conf}]), + inets_ensure_started(), + ok. + start_http_server_ssl(FileName) -> application:start(ssl), catch start_http_server(FileName). +inets_ensure_loaded() -> + ensure_loaded(inets). + +ensure_loaded(App) -> + case application:load(App) of + ok -> + ok; + {error, {already_loaded, _}} -> + ok; + LoadRes -> + ?LOG("start_http_server -> failed loading ~p: ~p", [App, LoadRes]), + tsf({failed_loading, LoadRes}) + end. + + +inets_set_env(Service, Config) -> + app_set_env(inets, Service, Config). + +app_set_env(App, Param, Value) -> + case application:set_env(App, Param, Value) of + ok -> + ?DEBUG("start_http_server -> env set", []), + ok; + SetEnvRes -> + ?LOG("start_http_server -> failed set env for ~p: ~p", + [App, SetEnvRes]), + exit({failed_set_env, App, SetEnvRes}) + end. + +inets_ensure_started() -> + ensure_app_started(inets). + +ensure_app_started(App) -> + case application:start(App) of + ok -> + ?DEBUG("start_http_server -> ~p started", [App]), + ok; + {error, {already_started, _}} -> + ok; + StartRes -> + ?LOG("start_http_server -> failed starting ~p: ~p", + [App, StartRes]), + exit({failed_starting, App, StartRes}) + end. + + %% ---------------------------------------------------------------------- %% print functions %% @@ -300,3 +350,6 @@ sleep(MSecs) -> skip(Reason, File, Line) -> exit({skipped, {Reason, File, Line}}). + +tsf(Reason) -> + test_server:fail(Reason). diff --git a/lib/inets/vsn.mk b/lib/inets/vsn.mk index 408f49c19f..feb29107bf 100644 --- a/lib/inets/vsn.mk +++ b/lib/inets/vsn.mk @@ -1,9 +1,11 @@ APPLICATION = inets -INETS_VSN = 5.3.4 +INETS_VSN = 5.3.5 PRE_VSN = APP_VSN = "$(APPLICATION)-$(INETS_VSN)$(PRE_VSN)" -TICKETS = OTP-8739 OTP-8741 OTP-8742 +TICKETS = OTP-8940 + +TICKETS_5_3_4 = OTP-8739 OTP-8741 OTP-8742 TICKETS_5_3_3 = \ OTP-8609 \ -- cgit v1.2.3