%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2013-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%
%%
%%
%%
%% ct:run("../inets_test", httpd_SUITE).
%%
-module(httpd_SUITE).
-include_lib("kernel/include/file.hrl").
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
-include("inets_test_lib.hrl").
%% Note: This directive should only be used in test suites.
-compile(export_all).
-record(httpd_user, {user_name, password, user_data}).
-record(httpd_group, {group_name, userlist}).
-define(MAX_HEADER_SIZE, 256).
%% Minutes before failed auths timeout.
-define(FAIL_EXPIRE_TIME,1).
%% Seconds before successful auths timeout.
-define(AUTH_TIMEOUT,5).
-define(URL_START, "http://").
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------
suite() ->
[{ct_hooks,[ts_install_cth]},
{timetrap,{seconds, 120}}
].
all() ->
[
{group, http_basic},
{group, https_basic},
{group, http_limit},
{group, https_limit},
{group, http_custom},
{group, https_custom},
{group, http_basic_auth},
{group, https_basic_auth},
{group, http_auth_api},
{group, https_auth_api},
{group, http_auth_api_dets},
{group, https_auth_api_dets},
{group, http_auth_api_mnesia},
{group, https_auth_api_mnesia},
{group, http_htaccess},
{group, https_htaccess},
{group, http_security},
{group, https_security},
{group, http_reload},
{group, https_reload},
{group, http_mime_types},
mime_types_format
].
groups() ->
[
{http_basic, [], basic_groups()},
{https_basic, [], basic_groups()},
{http_limit, [], [{group, limit}]},
{https_limit, [], [{group, limit}]},
{http_custom, [], [{group, custom}]},
{https_custom, [], [{group, custom}]},
{http_basic_auth, [], [{group, basic_auth}]},
{https_basic_auth, [], [{group, basic_auth}]},
{http_auth_api, [], [{group, auth_api}]},
{https_auth_api, [], [{group, auth_api}]},
{http_auth_api_dets, [], [{group, auth_api_dets}]},
{https_auth_api_dets, [], [{group, auth_api_dets}]},
{http_auth_api_mnesia, [], [{group, auth_api_mnesia}]},
{https_auth_api_mnesia, [], [{group, auth_api_mnesia}]},
{http_htaccess, [], [{group, htaccess}]},
{https_htaccess, [], [{group, htaccess}]},
{http_security, [], [{group, security}]},
{https_security, [], [{group, security}]},
{http_reload, [], [{group, reload}]},
{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, add_default]},
{reload, [], [non_disturbing_reconfiger_dies,
disturbing_reconfiger_dies,
non_disturbing_1_1,
non_disturbing_1_0,
non_disturbing_0_9,
disturbing_1_1,
disturbing_1_0,
disturbing_0_9
]},
{basic_auth, [], [basic_auth_1_1, basic_auth_1_0, basic_auth_0_9]},
{auth_api, [], [auth_api_1_1, auth_api_1_0, auth_api_0_9
]},
{auth_api_dets, [], [auth_api_1_1, auth_api_1_0, auth_api_0_9
]},
{auth_api_mnesia, [], [auth_api_1_1, auth_api_1_0, auth_api_0_9
]},
{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, 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()}
].
basic_groups ()->
[{group, http_1_1},
{group, http_1_0},
{group, http_0_9}
].
http_head() ->
[head].
http_get() ->
[alias,
get,
%%actions, Add configuration so that this test mod_action
esi,
content_length,
bad_hex,
missing_CR,
max_header,
max_content_length,
ipv6
].
load() ->
[light, medium
%%,heavy
].
init_per_suite(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
DataDir = proplists:get_value(data_dir, Config),
inets_test_lib:stop_apps([inets]),
ServerRoot = filename:join(PrivDir, "server_root"),
inets_test_lib:del_dirs(ServerRoot),
DocRoot = filename:join(ServerRoot, "htdocs"),
setup_server_dirs(ServerRoot, DocRoot, DataDir),
{ok, Hostname0} = inet:gethostname(),
Inet =
case (catch ct:get_config(ipv6_hosts)) of
undefined ->
inet;
Hosts when is_list(Hosts) ->
case lists:member(list_to_atom(Hostname0), Hosts) of
true ->
inet6;
false ->
inet
end;
_ ->
inet
end,
[{server_root, ServerRoot},
{doc_root, DocRoot},
{ipfamily, Inet},
{node, node()},
{host, inets_test_lib:hostname()},
{address, getaddr()} | Config].
end_per_suite(_Config) ->
ok.
%%--------------------------------------------------------------------
init_per_group(Group, Config0) when Group == https_basic;
Group == https_limit;
Group == https_custom;
Group == https_basic_auth;
Group == https_auth_api;
Group == https_auth_api_dets;
Group == https_auth_api_mnesia;
Group == https_security;
Group == https_reload
->
init_ssl(Group, Config0);
init_per_group(Group, Config0) when Group == http_basic;
Group == http_limit;
Group == http_custom;
Group == http_basic_auth;
Group == http_auth_api;
Group == http_auth_api_dets;
Group == http_auth_api_mnesia;
Group == http_security;
Group == http_reload;
Group == http_mime_types
->
ok = start_apps(Group),
init_httpd(Group, [{type, ip_comm} | Config0]);
init_per_group(http_1_1, Config) ->
[{http_version, "HTTP/1.1"} | Config];
init_per_group(http_1_0, Config) ->
[{http_version, "HTTP/1.0"} | Config];
init_per_group(http_0_9, Config) ->
case {os:type(), os:version()} of
{{win32, _}, {5,1,2600}} ->
{skip, "eaddrinuse XP problem"};
_ ->
[{http_version, "HTTP/0.9"} | Config]
end;
init_per_group(http_htaccess = Group, Config) ->
Path = proplists:get_value(doc_root, Config),
catch remove_htaccess(Path),
create_htaccess_data(Path, proplists:get_value(address, Config)),
ok = start_apps(Group),
init_httpd(Group, [{type, ip_comm} | Config]);
init_per_group(https_htaccess = Group, Config) ->
Path = proplists:get_value(doc_root, Config),
catch remove_htaccess(Path),
create_htaccess_data(Path, proplists:get_value(address, Config)),
init_ssl(Group, Config);
init_per_group(auth_api, Config) ->
[{auth_prefix, ""} | Config];
init_per_group(auth_api_dets, Config) ->
[{auth_prefix, "dets_"} | Config];
init_per_group(auth_api_mnesia, Config) ->
start_mnesia(proplists:get_value(node, Config)),
[{auth_prefix, "mnesia_"} | Config];
init_per_group(_, Config) ->
Config.
end_per_group(Group, _Config) when Group == http_basic;
Group == http_limit;
Group == http_basic_auth;
Group == http_auth_api;
Group == http_auth_api_dets;
Group == http_auth_api_mnesia;
Group == http_htaccess;
Group == http_security;
Group == http_reload;
Group == http_mime_types
->
inets:stop();
end_per_group(Group, _Config) when Group == https_basic;
Group == https_limit;
Group == https_basic_auth;
Group == https_auth_api;
Group == https_auth_api_dets;
Group == https_auth_api_mnesia;
Group == https_htaccess;
Group == https_security;
Group == https_reload
->
ssl:stop(),
inets:stop();
end_per_group(auth_api_mnesia, _) ->
cleanup_mnesia();
end_per_group(_, _) ->
ok.
%%--------------------------------------------------------------------
init_per_testcase(Case, Config) when Case == host; Case == trace ->
ct:timetrap({seconds, 20}),
Prop = proplists:get_value(tc_group_properties, Config),
Name = proplists:get_value(name, Prop),
Cb = case Name of
http_1_0 ->
httpd_1_0;
http_1_1 ->
httpd_1_1
end,
[{version_cb, Cb} | proplists:delete(version_cb, Config)];
init_per_testcase(range, Config) ->
ct:timetrap({seconds, 20}),
DocRoot = proplists:get_value(doc_root, Config),
create_range_data(DocRoot),
Config;
init_per_testcase(_, Config) ->
ct:timetrap({seconds, 20}),
Config.
end_per_testcase(_Case, _Config) ->
ok.
%%-------------------------------------------------------------------------
%% Test cases starts here.
%%-------------------------------------------------------------------------
head() ->
[{doc, "HTTP HEAD request for static page"}].
head(Config) when is_list(Config) ->
Version = proplists:get_value(http_version, Config),
Host = proplists:get_value(host, Config),
ok = httpd_test_lib:verify_request(proplists:get_value(type, Config), Host,
proplists:get_value(port, Config), proplists:get_value(node, Config),
http_request("HEAD /index.html ", Version, Host),
[{statuscode, head_status(Version)},
{version, Version}]).
get() ->
[{doc, "HTTP GET request for static page"}].
get(Config) when is_list(Config) ->
Version = proplists:get_value(http_version, Config),
Host = proplists:get_value(host, Config),
Type = proplists:get_value(type, Config),
ok = httpd_test_lib:verify_request(proplists:get_value(type, Config), Host,
proplists:get_value(port, Config),
transport_opts(Type, Config),
proplists:get_value(node, Config),
http_request("GET /index.html ", Version, Host),
[{statuscode, 200},
{header, "Content-Type", "text/html"},
{header, "Date"},
{header, "Server"},
{version, Version}]).
basic_auth_1_1(Config) when is_list(Config) ->
basic_auth([{http_version, "HTTP/1.1"} | Config]).
basic_auth_1_0(Config) when is_list(Config) ->
basic_auth([{http_version, "HTTP/1.0"} | Config]).
basic_auth_0_9(Config) when is_list(Config) ->
basic_auth([{http_version, "HTTP/0.9"} | Config]).
basic_auth() ->
[{doc, "Test Basic authentication with WWW-Authenticate header"}].
basic_auth(Config) ->
Version = proplists:get_value(http_version, Config),
Host = proplists:get_value(host, Config),
basic_auth_requiered(Config),
%% Authentication OK! ["one:OnePassword" user first in user list]
ok = auth_status(auth_request("/open/dummy.html", "one", "onePassword", Version, Host), Config,
[{statuscode, 200}]),
%% Authentication OK and a directory listing is supplied!
%% ["Aladdin:open sesame" user second in user list]
ok = auth_status(auth_request("/open/", "Aladdin", "AladdinPassword", Version, Host), Config,
[{statuscode, 200}]),
%% User correct but wrong password! ["one:one" user first in user list]
ok = auth_status(auth_request("/open/dummy.html", "one", "one", Version, Host), Config,
[{statuscode, 401},
{header, "WWW-Authenticate"}]),
%% Make sure Authenticate header is received even the second time
%% we try a incorrect password! Otherwise a browser client will hang!
ok = auth_status(auth_request("/open/dummy.html", "one", "one", Version, Host), Config,
[{statuscode, 401},
{header, "WWW-Authenticate"}]),
%% Neither user or password correct! ["dummy:dummy"]
ok = auth_status(auth_request("/open/dummy.html", "dummy", "dummy", Version, Host), Config,
[{statuscode, 401}]),
%% Nested secret/top_secret OK! ["Aladdin:open sesame"]
ok = http_status(auth_request("/secret/top_secret/", "Aladdin", "AladdinPassword", Version, Host),
Config, [{statuscode, 200}]),
%% Authentication still required!
basic_auth_requiered(Config).
auth_api_1_1(Config) when is_list(Config) ->
auth_api([{http_version, "HTTP/1.1"} | Config]).
auth_api_1_0(Config) when is_list(Config) ->
auth_api([{http_version, "HTTP/1.0"} | Config]).
auth_api_0_9(Config) when is_list(Config) ->
auth_api([{http_version, "HTTP/0.9"} | Config]).
auth_api() ->
[{doc, "Test mod_auth API"}].
auth_api(Config) when is_list(Config) ->
Prefix = proplists:get_value(auth_prefix, Config),
do_auth_api(Prefix, Config).
do_auth_api(AuthPrefix, Config) ->
Version = proplists:get_value(http_version, Config),
Host = proplists:get_value(host, Config),
Port = proplists:get_value(port, Config),
Node = proplists:get_value(node, Config),
ServerRoot = proplists:get_value(server_root, Config),
ok = http_status("GET / ", Config,
[{statuscode, 200}]),
ok = auth_status(auth_request("/", "one", "WrongPassword", Version, Host), Config,
[{statuscode, 200}]),
%% Make sure Authenticate header is received even the second time
%% we try a incorrect password! Otherwise a browser client will hang!
ok = auth_status(auth_request("/" ++ AuthPrefix ++ "open/",
"dummy", "WrongPassword", Version, Host), Config,
[{statuscode, 401},
{header, "WWW-Authenticate"}]),
ok = auth_status(auth_request("/" ++ AuthPrefix ++ "open/", "dummy", "WrongPassword",
Version, Host), Config, [{statuscode, 401},
{header, "WWW-Authenticate"}]),
%% Change the password to DummyPassword then try to add a user
%% Get an error and set it to NoPassword
ok = update_password(Node, ServerRoot, Host, Port, AuthPrefix,
"open", "NoPassword", "DummyPassword"),
{error,bad_password} =
add_user(Node, ServerRoot, Port, AuthPrefix, "open", "one",
"onePassword", []),
ok = update_password(Node, ServerRoot, Host, Port, AuthPrefix, "open",
"DummyPassword", "NoPassword"),
%% Test /*open, require user one Aladdin
remove_users(Node, ServerRoot, Host, Port, AuthPrefix, "open"),
ok = auth_status(auth_request("/" ++ AuthPrefix ++ "open/",
"one", "onePassword", Version, Host), Config,
[{statuscode, 401}]),
ok = auth_status(auth_request("/" ++ AuthPrefix ++ "open/",
"two", "twoPassword", Version, Host), Config,
[{statuscode, 401}]),
ok = auth_status(auth_request("/" ++ AuthPrefix ++ "open/",
"Aladdin", "onePassword", Version, Host),
Config, [{statuscode, 401}]),
true = add_user(Node, ServerRoot, Port, AuthPrefix, "open", "one",
"onePassword", []),
true = add_user(Node, ServerRoot, Port, AuthPrefix, "open", "two",
"twoPassword", []),
true = add_user(Node, ServerRoot, Port, AuthPrefix, "open", "Aladdin",
"AladdinPassword", []),
{ok, [_|_]} = list_users(Node, ServerRoot, Host, Port,
AuthPrefix, "open"),
ok = auth_status(auth_request("/" ++ AuthPrefix ++ "open/",
"one", "WrongPassword", Version, Host),
Config, [{statuscode, 401}]),
ok = auth_status(auth_request("/" ++ AuthPrefix ++ "open/",
"one", "onePassword", Version, Host),
Config, [{statuscode, 200}]),
ok = auth_status(auth_request("/" ++ AuthPrefix ++ "open/",
"two", "twoPassword", Version, Host),
Config,[{statuscode, 401}]),
ok = auth_status(auth_request("/" ++ AuthPrefix ++ "open/",
"Aladdin", "WrongPassword", Version, Host),
Config,[{statuscode, 401}]),
ok = auth_status(auth_request("/" ++ AuthPrefix ++ "open/",
"Aladdin", "AladdinPassword", Version, Host),
Config, [{statuscode, 200}]),
remove_users(Node, ServerRoot, Host, Port, AuthPrefix, "open"),
{ok, []} = list_users(Node, ServerRoot, Host, Port,
AuthPrefix, "open"),
%% Phase 2
remove_users(Node, ServerRoot, Host, Port, AuthPrefix, "secret"),
{ok, []} = list_users(Node, ServerRoot, Host, Port, AuthPrefix,
"secret"),
ok = auth_status(auth_request("/" ++ AuthPrefix ++ "secret/",
"one", "onePassword", Version, Host),
Config, [{statuscode, 401}]),
ok = auth_status(auth_request("/" ++ AuthPrefix ++ "secret/",
"two", "twoPassword", Version, Host),
Config, [{statuscode, 401}]),
ok = auth_status(auth_request("/" ++ AuthPrefix ++ "secret/",
"three", "threePassword", Version, Host),
Config, [{statuscode, 401}]),
add_user(Node, ServerRoot, Port, AuthPrefix, "secret", "one",
"onePassword",
[]),
add_user(Node, ServerRoot, Port, AuthPrefix, "secret",
"two", "twoPassword", []),
add_user(Node, ServerRoot, Port, AuthPrefix, "secret", "Aladdin",
"AladdinPassword",[]),
add_group_member(Node, ServerRoot, Port, AuthPrefix, "secret",
"one", "group1"),
add_group_member(Node, ServerRoot, Port, AuthPrefix, "secret",
"two", "group1"),
add_group_member(Node, ServerRoot, Port, AuthPrefix,
"secret", "Aladdin", "group2"),
ok = auth_status(auth_request("/" ++ AuthPrefix ++ "secret/",
"one", "onePassword", Version, Host),
Config, [{statuscode, 200}]),
ok = auth_status(auth_request("/" ++ AuthPrefix ++ "secret/",
"two", "twoPassword", Version, Host),
Config,[{statuscode, 200}]),
ok = auth_status(auth_request("/" ++ AuthPrefix ++ "secret/",
"Aladdin", "AladdinPassword", Version, Host),
Config, [{statuscode, 200}]),
ok = auth_status(auth_request("/" ++ AuthPrefix ++ "secret/",
"three", "threePassword", Version, Host),
Config, [{statuscode, 401}]),
remove_users(Node, ServerRoot, Host, Port, AuthPrefix, "secret"),
{ok, []} = list_users(Node, ServerRoot, Host, Port,
AuthPrefix, "secret"),
remove_groups(Node, ServerRoot, Host, Port, AuthPrefix, "secret"),
{ok, []} = list_groups(Node, ServerRoot, Host, Port, AuthPrefix, "secret"),
%% Phase 3
remove_users(Node, ServerRoot, Host, Port, AuthPrefix, "secret/top_secret"),
remove_groups(Node, ServerRoot, Host, Port, AuthPrefix, "secret/top_secret"),
ok = auth_status(auth_request("/" ++ AuthPrefix ++
"secret/top_secret/",
"three", "threePassword", Version, Host),
Config, [{statuscode, 401}]),
ok = auth_status(auth_request("/" ++ AuthPrefix ++
"secret/top_secret/", "two", "twoPassword", Version, Host),
Config, [{statuscode, 401}]),
add_user(Node, ServerRoot, Port, AuthPrefix,
"secret/top_secret","three",
"threePassword",[]),
add_user(Node, ServerRoot, Port, AuthPrefix, "secret/top_secret",
"two","twoPassword", []),
add_group_member(Node, ServerRoot, Port, AuthPrefix, "secret/top_secret", "three", "group3"),
ok = auth_status(auth_request("/" ++ AuthPrefix ++
"secret/top_secret/", "three", "threePassword",
Version, Host),
Config, [{statuscode, 200}]),
ok = auth_status(auth_request("/" ++ AuthPrefix ++
"secret/top_secret/", "two", "twoPassword", Version, Host),
Config, [{statuscode, 401}]),
add_group_member(Node, ServerRoot, Port, AuthPrefix, "secret/top_secret", "two", "group3"),
ok = auth_status(auth_request("/" ++ AuthPrefix ++
"secret/top_secret/",
"two", "twoPassword", Version, Host),
Config, [{statuscode, 200}]),
remove_users(Node, ServerRoot, Host, Port, AuthPrefix, "secret/top_secret"),
{ok, []} = list_users(Node, ServerRoot, Host, Port,
AuthPrefix, "secret/top_secret"),
remove_groups(Node, ServerRoot, Host, Port, AuthPrefix, "secret/top_secret"),
{ok, []} = list_groups(Node, ServerRoot, Host, Port, AuthPrefix, "secret/top_secret"),
ok = auth_status(auth_request("/" ++ AuthPrefix ++
"secret/top_secret/", "two", "twoPassword", Version, Host),
Config, [{statuscode, 401}]),
ok = auth_status(auth_request("/" ++ AuthPrefix ++
"secret/top_secret/","three", "threePassword", Version, Host),
Config, [{statuscde, 401}]).
%%-------------------------------------------------------------------------
ipv6() ->
[{require, ipv6_hosts},
{doc,"Test ipv6."}].
ipv6(Config) when is_list(Config) ->
{ok, Hostname0} = inet:gethostname(),
case lists:member(list_to_atom(Hostname0), ct:get_config(ipv6_hosts)) of
true ->
Version = proplists:get_value(http_version, Config),
Host = proplists:get_value(host, Config),
URI = http_request("GET / ", Version, Host),
httpd_test_lib:verify_request(proplists:get_value(type, Config), Host,
proplists:get_value(port, Config), [inet6],
proplists:get_value(code, Config),
URI,
[{statuscode, 200}, {version, Version}]);
false ->
{skip, "Host does not support IPv6"}
end.
%%-------------------------------------------------------------------------
htaccess_1_1(Config) when is_list(Config) ->
htaccess([{http_version, "HTTP/1.1"} | Config]).
htaccess_1_0(Config) when is_list(Config) ->
htaccess([{http_version, "HTTP/1.0"} | Config]).
htaccess_0_9(Config) when is_list(Config) ->
htaccess([{http_version, "HTTP/0.9"} | Config]).
htaccess() ->
[{doc, "Test mod_auth API"}].
htaccess(Config) when is_list(Config) ->
Version = proplists:get_value(http_version, Config),
Host = proplists:get_value(host, Config),
Type = proplists:get_value(type, Config),
Port = proplists:get_value(port, Config),
Node = proplists:get_value(node, Config),
%% Control that authentication required!
%% Control that the pages that shall be
%% authenticated really need authenticatin
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
http_request("GET /ht/open/ ", Version, Host),
[{statuscode, 401},
{version, Version},
{header, "WWW-Authenticate"}]),
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
http_request("GET /ht/secret/ ", Version, Host),
[{statuscode, 401},
{version, Version},
{header, "WWW-Authenticate"}]),
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
http_request("GET /ht/secret/top_secret/ ",
Version, Host),
[{statuscode, 401},
{version, Version},
{header, "WWW-Authenticate"}]),
%% Make sure Authenticate header is received even the second time
%% we try a incorrect password! Otherwise a browser client will hang!
ok = auth_status(auth_request("/ht/open/",
"dummy", "WrongPassword", Version, Host), Config,
[{statuscode, 401},
{header, "WWW-Authenticate"}]),
ok = auth_status(auth_request("/ht/open/",
"dummy", "WrongPassword", Version, Host), Config,
[{statuscode, 401},
{header, "WWW-Authenticate"}]),
%% Control that not just the first user in the list is valid
%% Control the first user
%% Authennticating ["one:OnePassword" user first in user list]
ok = auth_status(auth_request("/ht/open/dummy.html", "one", "OnePassword",
Version, Host), Config,
[{statuscode, 200}]),
%% Control the second user
%% Authentication OK and a directory listing is supplied!
%% ["Aladdin:open sesame" user second in user list]
ok = auth_status(auth_request("/ht/open/","Aladdin",
"AladdinPassword", Version, Host), Config,
[{statuscode, 200}]),
%% Contro that bad passwords and userids get a good denial
%% User correct but wrong password! ["one:one" user first in user list]
ok = auth_status(auth_request("/ht/open/", "one", "one", Version, Host), Config,
[{statuscode, 401}]),
%% Neither user or password correct! ["dummy:dummy"]
ok = auth_status(auth_request("/ht/open/", "dummy", "dummy", Version, Host), Config,
[{statuscode, 401}]),
%% Control that authetication still works, even if its a member in a group
%% Authentication OK! ["two:TwoPassword" user in first group]
ok = auth_status(auth_request("/ht/secret/dummy.html", "two",
"TwoPassword", Version, Host), Config,
[{statuscode, 200}]),
%% Authentication OK and a directory listing is supplied!
%% ["three:ThreePassword" user in second group]
ok = auth_status(auth_request("/ht/secret/", "three",
"ThreePassword", Version, Host), Config,
[{statuscode, 200}]),
%% Deny users with bad passwords even if the user is a group member
%% User correct but wrong password! ["two:two" user in first group]
ok = auth_status(auth_request("/ht/secret/", "two", "two", Version, Host), Config,
[{statuscode, 401}]),
%% Neither user or password correct! ["dummy:dummy"]
ok = auth_status(auth_request("/ht/secret/", "dummy", "dummy", Version, Host), Config,
[{statuscode, 401}]),
%% control that we deny the users that are in subnet above the allowed
ok = auth_status(auth_request("/ht/blocknet/dummy.html", "four",
"FourPassword", Version, Host), Config,
[{statuscode, 403}]),
%% Control that we only applies the rules to the right methods
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
http_request("HEAD /ht/blocknet/dummy.html ", Version, Host),
[{statuscode, head_status(Version)},
{version, Version}]),
%% Control that the rerquire directive can be overrideen
ok = auth_status(auth_request("/ht/secret/top_secret/ ", "Aladdin", "AladdinPassword",
Version, Host), Config,
[{statuscode, 401}]),
%% Authentication still required!
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
http_request("GET /ht/open/ ", Version, Host),
[{statuscode, 401},
{version, Version},
{header, "WWW-Authenticate"}]),
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
http_request("GET /ht/secret/ ", Version, Host),
[{statuscode, 401},
{version, Version},
{header, "WWW-Authenticate"}]),
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
http_request("GET /ht/secret/top_secret/ ", Version, Host),
[{statuscode, 401},
{version, Version},
{header, "WWW-Authenticate"}]).
%%-------------------------------------------------------------------------
host() ->
[{doc, "Test host header"}].
host(Config) when is_list(Config) ->
Cb = proplists:get_value(version_cb, Config),
Cb:host(proplists:get_value(type, Config), proplists:get_value(port, Config),
proplists:get_value(host, Config), proplists:get_value(node, Config)).
%%-------------------------------------------------------------------------
chunked() ->
[{doc, "Check that the server accepts chunked requests."}].
chunked(Config) when is_list(Config) ->
httpd_1_1:chunked(proplists:get_value(type, Config), proplists:get_value(port, Config),
proplists:get_value(host, Config), proplists:get_value(node, Config)).
%%-------------------------------------------------------------------------
expect() ->
["Check that the server handles request with the expect header "
"field appropiate"].
expect(Config) when is_list(Config) ->
httpd_1_1:expect(proplists:get_value(type, Config), proplists:get_value(port, Config),
proplists:get_value(host, Config), proplists:get_value(node, Config)).
%%-------------------------------------------------------------------------
max_clients_1_1() ->
[{doc, "Test max clients limit"}].
max_clients_1_1(Config) when is_list(Config) ->
do_max_clients([{http_version, "HTTP/1.1"} | Config]).
max_clients_1_0() ->
[{doc, "Test max clients limit"}].
max_clients_1_0(Config) when is_list(Config) ->
do_max_clients([{http_version, "HTTP/1.0"} | Config]).
max_clients_0_9() ->
[{doc, "Test max clients limit"}].
max_clients_0_9(Config) when is_list(Config) ->
do_max_clients([{http_version, "HTTP/0.9"} | Config]).
%%-------------------------------------------------------------------------
esi() ->
[{doc, "Test mod_esi"}].
esi(Config) when is_list(Config) ->
ok = http_status("GET /eval?httpd_example:print(\"Hi!\") ",
Config, [{statuscode, 200}]),
ok = http_status("GET /eval?not_allowed:print(\"Hi!\") ",
Config, [{statuscode, 403}]),
ok = http_status("GET /eval?httpd_example:undef(\"Hi!\") ",
Config, [{statuscode, 500}]),
ok = http_status("GET /cgi-bin/erl/httpd_example ",
Config, [{statuscode, 400}]),
ok = http_status("GET /cgi-bin/erl/httpd_example:get ",
Config, [{statuscode, 200}]),
ok = http_status("GET /cgi-bin/erl/httpd_example:"
"get?input=4711 ", Config,
[{statuscode, 200}]),
ok = http_status("GET /cgi-bin/erl/httpd_example:post ",
Config, [{statuscode, 200}]),
ok = http_status("GET /cgi-bin/erl/not_allowed:post ",
Config, [{statuscode, 403}]),
ok = http_status("GET /cgi-bin/erl/httpd_example:undef ",
Config, [{statuscode, 404}]),
ok = http_status("GET /cgi-bin/erl/httpd_example/yahoo ",
Config, [{statuscode, 302}]),
%% Check "ErlScriptNoCache" directive (default: false)
ok = http_status("GET /cgi-bin/erl/httpd_example:get ",
Config, [{statuscode, 200},
{no_header, "cache-control"}]),
ok = http_status("GET /cgi-bin/erl/httpd_example:peer ",
Config, [{statuscode, 200},
{header, "peer-cert-exist", peer(Config)}]).
%%-------------------------------------------------------------------------
mod_esi_chunk_timeout(Config) when is_list(Config) ->
ok = httpd_1_1:mod_esi_chunk_timeout(proplists:get_value(type, Config),
proplists:get_value(port, Config),
proplists:get_value(host, Config),
proplists:get_value(node, Config)).
%%-------------------------------------------------------------------------
cgi() ->
[{doc, "Test mod_cgi"}].
cgi(Config) when is_list(Config) ->
{Script, Script2, Script3} =
case test_server:os_type() of
{win32, _} ->
{"printenv.bat", "printenv.sh", "cgi_echo.exe"};
_ ->
{"printenv.sh", "printenv.bat", "cgi_echo"}
end,
%%The length (> 100) is intentional
ok = http_status("POST /cgi-bin/" ++ Script3 ++ " ",
{"Content-Length:100 \r\n",
"ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
"ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
"ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
"ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
"ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
"ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
"ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
"ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
"ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
"ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
"ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
"ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
"ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
"ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
"ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
"ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
"ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"},
Config,
[{statuscode, 200},
{header, "content-type", "text/plain"}]),
ok = http_status("GET /cgi-bin/"++ Script ++ " ", Config, [{statuscode, 200}]),
ok = http_status("GET /cgi-bin/not_there ", Config,
[{statuscode, 404}, {statuscode, 500}]),
ok = http_status("GET /cgi-bin/"++ Script ++ "?Nisse:kkk?sss/lll ",
Config,
[{statuscode, 200}]),
ok = http_status("POST /cgi-bin/"++ Script ++ " ", Config,
[{statuscode, 200}]),
ok = http_status("GET /htbin/"++ Script ++ " ", Config,
[{statuscode, 200}]),
ok = http_status("GET /htbin/not_there ", Config,
[{statuscode, 404},{statuscode, 500}]),
ok = http_status("GET /htbin/"++ Script ++ "?Nisse:kkk?sss/lll ", Config,
[{statuscode, 200}]),
ok = http_status("POST /htbin/"++ Script ++ " ", Config,
[{statuscode, 200}]),
ok = http_status("POST /htbin/"++ Script ++ " ", Config,
[{statuscode, 200}]),
%% Execute an existing, but bad CGI script..
ok = http_status("POST /htbin/"++ Script2 ++ " ", Config,
[{statuscode, 404}]),
ok = http_status("POST /cgi-bin/"++ Script2 ++ " ", Config,
[{statuscode, 404}]),
%% Check "ScriptNoCache" directive (default: false)
ok = http_status("GET /cgi-bin/" ++ Script ++ " ", Config,
[{statuscode, 200},
{no_header, "cache-control"}]).
%%-------------------------------------------------------------------------
cgi_chunked_encoding_test() ->
[{doc, "Test chunked encoding together with mod_cgi "}].
cgi_chunked_encoding_test(Config) when is_list(Config) ->
Host = proplists:get_value(host, Config),
Script =
case test_server:os_type() of
{win32, _} ->
"/cgi-bin/printenv.bat";
_ ->
"/cgi-bin/printenv.sh"
end,
Requests =
["GET " ++ Script ++ " HTTP/1.1\r\nHost:"++ Host ++"\r\n\r\n",
"GET /cgi-bin/erl/httpd_example/newformat HTTP/1.1\r\nHost:"
++ Host ++"\r\n\r\n"],
httpd_1_1:mod_cgi_chunked_encoding_test(proplists:get_value(type, Config), proplists:get_value(port, Config),
Host,
proplists:get_value(node, Config),
Requests).
%%-------------------------------------------------------------------------
alias_1_1() ->
[{doc, "Test mod_alias"}].
alias_1_1(Config) when is_list(Config) ->
alias([{http_version, "HTTP/1.1"} | Config]).
alias_1_0() ->
[{doc, "Test mod_alias"}].
alias_1_0(Config) when is_list(Config) ->
alias([{http_version, "HTTP/1.0"} | Config]).
alias_0_9() ->
[{doc, "Test mod_alias"}].
alias_0_9(Config) when is_list(Config) ->
alias([{http_version, "HTTP/0.9"} | Config]).
alias() ->
[{doc, "Test mod_alias"}].
alias(Config) when is_list(Config) ->
ok = http_status("GET /pics/icon.sheet.gif ", Config,
[{statuscode, 200},
{header, "Content-Type","image/gif"},
{header, "Server"},
{header, "Date"}]),
ok = http_status("GET / ", Config,
[{statuscode, 200},
{header, "Content-Type","text/html"},
{header, "Server"},
{header, "Date"}]),
ok = http_status("GET /misc/ ", Config,
[{statuscode, 200},
{header, "Content-Type","text/html"},
{header, "Server"},
{header, "Date"}]),
%% Check redirection if trailing slash is missing.
ok = http_status("GET /misc ", Config,
[{statuscode, 301},
{header, "Location"},
{header, "Content-Type","text/html"}]).
%%-------------------------------------------------------------------------
actions() ->
[{doc, "Test mod_actions"}].
actions(Config) when is_list(Config) ->
ok = http_status("GET /", Config, [{statuscode, 200}]).
%%-------------------------------------------------------------------------
range() ->
[{doc, "Test Range header"}].
range(Config) when is_list(Config) ->
httpd_1_1:range(proplists:get_value(type, Config), proplists:get_value(port, Config),
proplists:get_value(host, Config), proplists:get_value(node, Config)).
%%-------------------------------------------------------------------------
if_modified_since() ->
[{doc, "Test If-Modified-Since header"}].
if_modified_since(Config) when is_list(Config) ->
httpd_1_1:if_test(proplists:get_value(type, Config), proplists:get_value(port, Config),
proplists:get_value(host, Config), proplists:get_value(node, Config),
proplists:get_value(doc_root, Config)).
%%-------------------------------------------------------------------------
trace() ->
[{doc, "Test TRACE method"}].
trace(Config) when is_list(Config) ->
Cb = proplists:get_value(version_cb, Config),
Cb:trace(proplists:get_value(type, Config), proplists:get_value(port, Config),
proplists:get_value(host, Config), proplists:get_value(node, Config)).
%%-------------------------------------------------------------------------
light() ->
["Test light load"].
light(Config) when is_list(Config) ->
httpd_load:load_test(proplists:get_value(type, Config), proplists:get_value(port, Config), proplists:get_value(host, Config),
proplists:get_value(node, Config), 10).
%%-------------------------------------------------------------------------
medium() ->
["Test medium load"].
medium(Config) when is_list(Config) ->
httpd_load:load_test(proplists:get_value(type, Config), proplists:get_value(port, Config), proplists:get_value(host, Config),
proplists:get_value(node, Config), 100).
%%-------------------------------------------------------------------------
heavy() ->
["Test heavy load"].
heavy(Config) when is_list(Config) ->
httpd_load:load_test(proplists:get_value(type, Config), proplists:get_value(port, Config), proplists:get_value(host, Config),
proplists:get_value(node, Config),
1000).
%%-------------------------------------------------------------------------
content_length() ->
["Tests that content-length is correct OTP-5775"].
content_length(Config) ->
Version = proplists:get_value(http_version, Config),
Host = proplists:get_value(host, Config),
ok = httpd_test_lib:verify_request(proplists:get_value(type, Config), Host,
proplists:get_value(port, Config), proplists:get_value(node, Config),
http_request("GET /cgi-bin/erl/httpd_example:get_bin ",
Version, Host),
[{statuscode, 200},
{content_length, 274},
{version, Version}]).
%%-------------------------------------------------------------------------
bad_hex() ->
["Tests that a URI with a bad hexadecimal code is handled OTP-6003"].
bad_hex(Config) ->
Version = proplists:get_value(http_version, Config),
Host = proplists:get_value(host, Config),
ok = httpd_test_lib:verify_request(proplists:get_value(type, Config), Host,
proplists:get_value(port, Config), proplists:get_value(node, Config),
http_request("GET http://www.erlang.org/%skalle ",
Version, Host),
[{statuscode, 400},
{version, Version}]).
%%-------------------------------------------------------------------------
missing_CR() ->
["Tests missing CR in delimiter OTP-7304"].
missing_CR(Config) ->
Version = proplists:get_value(http_version, Config),
Host = proplists:get_value(host, Config),
ok = httpd_test_lib:verify_request(proplists:get_value(type, Config), Host,
proplists:get_value(port, Config), proplists:get_value(node, Config),
http_request_missing_CR("GET /index.html ", Version, Host),
[{statuscode, 200},
{version, Version}]).
%%-------------------------------------------------------------------------
customize() ->
[{doc, "Test filtering of headers with custom callback"}].
customize(Config) when is_list(Config) ->
Version = "HTTP/1.1",
Host = proplists:get_value(host, Config),
Type = proplists:get_value(type, Config),
ok = httpd_test_lib:verify_request(proplists:get_value(type, Config), Host,
proplists:get_value(port, Config),
transport_opts(Type, Config),
proplists:get_value(node, Config),
http_request("GET /index.html ", Version, Host),
[{statuscode, 200},
{header, "Content-Type", "text/html"},
{header, "Date"},
{no_header, "Server"},
{version, Version}]).
add_default() ->
[{doc, "Test adding default header with custom callback"}].
add_default(Config) when is_list(Config) ->
Version = "HTTP/1.1",
Host = proplists:get_value(host, Config),
Type = proplists:get_value(type, Config),
ok = httpd_test_lib:verify_request(proplists:get_value(type, Config), Host,
proplists:get_value(port, Config),
transport_opts(Type, Config),
proplists:get_value(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() ->
["Denial Of Service (DOS) attack, prevented by max_header"].
max_header(Config) when is_list(Config) ->
Version = proplists:get_value(http_version, Config),
Host = proplists:get_value(host, Config),
case Version of
"HTTP/0.9" ->
{skip, not_implemented};
_ ->
dos_hostname(proplists:get_value(type, Config), proplists:get_value(port, Config), Host,
proplists:get_value(node, Config), Version, ?MAX_HEADER_SIZE)
end.
%%-------------------------------------------------------------------------
max_content_length() ->
["Denial Of Service (DOS) attack, prevented by max_content_length"].
max_content_length(Config) when is_list(Config) ->
Version = proplists:get_value(http_version, Config),
Host = proplists:get_value(host, Config),
garbage_content_length(proplists:get_value(type, Config), proplists:get_value(port, Config), Host,
proplists:get_value(node, Config), Version).
%%-------------------------------------------------------------------------
security_1_1(Config) when is_list(Config) ->
security([{http_version, "HTTP/1.1"} | Config]).
security_1_0(Config) when is_list(Config) ->
security([{http_version, "HTTP/1.0"} | Config]).
security() ->
["Test mod_security"].
security(Config) ->
Version = proplists:get_value(http_version, Config),
Host = proplists:get_value(host, Config),
Port = proplists:get_value(port, Config),
Node = proplists:get_value(node, Config),
ServerRoot = proplists:get_value(server_root, Config),
global:register_name(mod_security_test, self()), % Receive events
ct:sleep(5000),
OpenDir = filename:join([ServerRoot, "htdocs", "open"]),
%% Test blocking / unblocking of users.
%% /open, require user one Aladdin
remove_users(Node, ServerRoot, Host, Port, "", "open"),
ok = auth_status(auth_request("/open/",
"one", "onePassword", Version, Host), Config,
[{statuscode, 401}]),
receive_security_event({event, auth_fail, Port, OpenDir,
[{user, "one"}, {password, "onePassword"}]},
Node, Port),
ok = auth_status(auth_request("/open/",
"two", "twoPassword", Version, Host), Config,
[{statuscode, 401}]),
receive_security_event({event, auth_fail, Port, OpenDir,
[{user, "two"}, {password, "twoPassword"}]},
Node, Port),
ok = auth_status(auth_request("/open/",
"Aladdin", "AladdinPassword", Version, Host),
Config, [{statuscode, 401}]),
receive_security_event({event, auth_fail, Port, OpenDir,
[{user, "Aladdin"},
{password, "AladdinPassword"}]},
Node, Port),
add_user(Node, ServerRoot, Port, "", "open", "one", "onePassword", []),
add_user(Node, ServerRoot, Port, "", "open", "two", "twoPassword", []),
ok = auth_status(auth_request("/open/", "one", "WrongPassword", Version, Host), Config,
[{statuscode, 401}]),
receive_security_event({event, auth_fail, Port, OpenDir,
[{user, "one"}, {password, "WrongPassword"}]},
Node, Port),
ok = auth_status(auth_request("/open/", "one", "WrongPassword", Version, Host), Config,
[{statuscode, 401}]),
receive_security_event({event, auth_fail, Port, OpenDir,
[{user, "one"}, {password, "WrongPassword"}]},
Node, Port),
receive_security_event({event, user_block, Port, OpenDir,
[{user, "one"}]}, Node, Port),
global:unregister_name(mod_security_test), % No more events.
ok = auth_status(auth_request("/open/", "one", "WrongPassword", Version, Host), Config,
[{statuscode, 401}]),
%% User "one" should be blocked now..
case list_blocked_users(Node, Port) of
[{"one",_, Port, OpenDir,_}] ->
ok;
Blocked ->
ct:fail({unexpected_blocked, Blocked})
end,
[{"one",_, Port, OpenDir,_}] = list_blocked_users(Node, Port, OpenDir),
true = unblock_user(Node, "one", Port, OpenDir),
%% User "one" should not be blocked any more.
[] = list_blocked_users(Node, Port),
ok = auth_status(auth_request("/open/", "one", "onePassword", Version, Host), Config,
[{statuscode, 200}]),
%% Test list_auth_users & auth_timeout
["one"] = list_auth_users(Node, Port),
ok = auth_status(auth_request("/open/", "two", "onePassword", Version, Host), Config,
[{statuscode, 401}]),
["one"] = list_auth_users(Node, Port),
["one"] = list_auth_users(Node, Port, OpenDir),
ok = auth_status(auth_request("/open/", "two", "twoPassword", Version, Host), Config,
[{statuscode, 401}]),
["one"] = list_auth_users(Node, Port),
["one"] = list_auth_users(Node, Port, OpenDir),
%% Wait for successful auth to timeout.
ct:sleep(?AUTH_TIMEOUT*1001),
[] = list_auth_users(Node, Port),
[] = list_auth_users(Node, Port, OpenDir),
%% "two" is blocked.
true = unblock_user(Node, "two", Port, OpenDir),
%% Test explicit blocking. Block user 'two'.
[] = list_blocked_users(Node,Port,OpenDir),
true = block_user(Node, "two", Port, OpenDir, 10),
ok = auth_status(auth_request("/open/", "two", "twoPassword", Version, Host), Config,
[{statuscode, 401}]),
true = unblock_user(Node, "two", Port, OpenDir).
%%-------------------------------------------------------------------------
non_disturbing_reconfiger_dies(Config) when is_list(Config) ->
do_reconfiger_dies([{http_version, "HTTP/1.1"} | Config], non_disturbing).
disturbing_reconfiger_dies(Config) when is_list(Config) ->
do_reconfiger_dies([{http_version, "HTTP/1.1"} | Config], disturbing).
do_reconfiger_dies(Config, DisturbingType) ->
Server = proplists:get_value(server_pid, Config),
Version = proplists:get_value(http_version, Config),
Host = proplists:get_value(host, Config),
Port = proplists:get_value(port, Config),
Type = proplists:get_value(type, Config),
HttpdConfig = httpd:info(Server),
BlockRequest = http_request("GET /eval?httpd_example:delay(2000) ", Version, Host),
{ok, Socket} = inets_test_lib:connect_bin(Type, Host, Port, transport_opts(Type, Config)),
inets_test_lib:send(Type, Socket, BlockRequest),
ct:sleep(100), %% Avoid possible timing issues
Pid = spawn(fun() -> httpd:reload_config([{server_name, "httpd_kill_" ++ Version},
{port, Port}|
proplists:delete(server_name, HttpdConfig)], DisturbingType)
end),
monitor(process, Pid),
exit(Pid, kill),
receive
{'DOWN', _, _, _, _} ->
ok
end,
inets_test_lib:close(Type, Socket),
[{server_name, "httpd_test"}] = httpd:info(Server, [server_name]).
%%-------------------------------------------------------------------------
disturbing_1_1(Config) when is_list(Config) ->
disturbing([{http_version, "HTTP/1.1"} | Config]).
disturbing_1_0(Config) when is_list(Config) ->
disturbing([{http_version, "HTTP/1.0"} | Config]).
disturbing_0_9(Config) when is_list(Config) ->
disturbing([{http_version, "HTTP/0.9"} | Config]).
disturbing(Config) when is_list(Config)->
Server = proplists:get_value(server_pid, Config),
Version = proplists:get_value(http_version, Config),
Host = proplists:get_value(host, Config),
Port = proplists:get_value(port, Config),
Type = proplists:get_value(type, Config),
HttpdConfig = httpd:info(Server),
BlockRequest = http_request("GET /eval?httpd_example:delay(2000) ", Version, Host),
{ok, Socket} = inets_test_lib:connect_bin(Type, Host, Port, transport_opts(Type, Config)),
inets_test_lib:send(Type, Socket, BlockRequest),
ct:sleep(100), %% Avoid possible timing issues
ok = httpd:reload_config([{server_name, "httpd_disturbing_" ++ Version}, {port, Port}|
proplists:delete(server_name, HttpdConfig)], disturbing),
Close = list_to_atom((typestr(Type)) ++ "_closed"),
receive
{Close, Socket} ->
ok;
Msg ->
ct:fail({{expected, {Close, Socket}}, {got, Msg}})
end,
inets_test_lib:close(Type, Socket),
[{server_name, "httpd_disturbing_" ++ Version}] = httpd:info(Server, [server_name]).
%%-------------------------------------------------------------------------
non_disturbing_1_1(Config) when is_list(Config) ->
non_disturbing([{http_version, "HTTP/1.1"} | Config]).
non_disturbing_1_0(Config) when is_list(Config) ->
non_disturbing([{http_version, "HTTP/1.0"} | Config]).
non_disturbing_0_9(Config) when is_list(Config) ->
non_disturbing([{http_version, "HTTP/0.9"} | Config]).
non_disturbing(Config) when is_list(Config)->
Server = proplists:get_value(server_pid, Config),
Version = proplists:get_value(http_version, Config),
Host = proplists:get_value(host, Config),
Port = proplists:get_value(port, Config),
Type = proplists:get_value(type, Config),
HttpdConfig = httpd:info(Server),
BlockRequest = http_request("GET /eval?httpd_example:delay(2000) ", Version, Host),
{ok, Socket} = inets_test_lib:connect_bin(Type, Host, Port, transport_opts(Type, Config)),
inets_test_lib:send(Type, Socket, BlockRequest),
ct:sleep(100), %% Avoid possible timing issues
ok = httpd:reload_config([{server_name, "httpd_non_disturbing_" ++ Version}, {port, Port}|
proplists:delete(server_name, HttpdConfig)], non_disturbing),
Transport = type(Type),
receive
{Transport, Socket, Msg} ->
ct:pal("Received message ~p~n", [Msg]),
ok
after 2000 ->
ct:fail(timeout)
end,
inets_test_lib:close(Type, Socket),
[{server_name, "httpd_non_disturbing_" ++ Version}] = httpd:info(Server, [server_name]).
%%-------------------------------------------------------------------------
mime_types_format(Config) when is_list(Config) ->
DataDir = proplists:get_value(data_dir, Config),
MimeTypes = filename:join(DataDir, "mime_types.txt"),
{ok,[{"wrl","x-world/x-vrml"},
{"vrml","x-world/x-vrml"},
{"ice","x-conference/x-cooltalk"},
{"movie","video/x-sgi-movie"},
{"avi","video/x-msvideo"},
{"qt","video/quicktime"},
{"mov","video/quicktime"},
{"mpeg","video/mpeg"},
{"mpg","video/mpeg"},
{"mpe","video/mpeg"},
{"sgml","text/x-sgml"},
{"sgm","text/x-sgml"},
{"etx","text/x-setext"},
{"tsv","text/tab-separated-values"},
{"rtx","text/richtext"},
{"txt","text/plain"},
{"html","text/html"},
{"htm","text/html"},
{"css","text/css"},
{"xwd","image/x-xwindowdump"},
{"xpm","image/x-xpixmap"},
{"xbm","image/x-xbitmap"},
{"rgb","image/x-rgb"},
{"ppm","image/x-portable-pixmap"},
{"pgm","image/x-portable-graymap"},
{"pbm","image/x-portable-bitmap"},
{"pnm","image/x-portable-anymap"},
{"ras","image/x-cmu-raster"},
{"tiff","image/tiff"},
{"tif","image/tiff"},
{"png","image/png"},
{"jpeg","image/jpeg"},
{"jpg","image/jpeg"},
{"jpe","image/jpeg"},
{"ief","image/ief"},
{"gif","image/gif"},
{"pdb","chemical/x-pdb"},
{"xyz","chemical/x-pdb"},
{"wav","audio/x-wav"},
{"ra","audio/x-realaudio"},
{"rpm","audio/x-pn-realaudio-plugin"},
{"ram","audio/x-pn-realaudio"},
{"aif","audio/x-aiff"},
{"aiff","audio/x-aiff"},
{"aifc","audio/x-aiff"},
{"mpga","audio/mpeg"},
{"mp2","audio/mpeg"},
{"au","audio/basic"},
{"snd","audio/basic"},
{"zip","application/zip"},
{"src","application/x-wais-source"},
{"ustar","application/x-ustar"},
{"ms","application/x-troff-ms"},
{"me","application/x-troff-me"},
{"man","application/x-troff-man"},
{"t","application/x-troff"},
{"tr","application/x-troff"},
{"roff","application/x-troff"},
{"texinfo","application/x-texinfo"},
{"texi","application/x-texinfo"},
{"tex","application/x-tex"},
{"tcl","application/x-tcl"},
{"tar","application/x-tar"},
{"sv4crc","application/x-sv4crc"},
{"sv4cpio","application/x-sv4cpio"},
{"sit","application/x-stuffit"},
{"shar","application/x-shar"},
{"sh","application/x-sh"},
{"nc","application/x-netcdf"},
{"cdf","application/x-netcdf"},
{"mif","application/x-mif"},
{"latex","application/x-latex"},
{"skp","application/x-koan"},
{"skd","application/x-koan"},
{"skt","application/x-koan"},
{"skm","application/x-koan"},
{"cgi","application/x-httpd-cgi"},
{"hdf","application/x-hdf"},
{"gz","application/x-gzip"},
{"gtar","application/x-gtar"},
{"dvi","application/x-dvi"},
{"dcr","application/x-director"},
{"dir","application/x-director"},
{"dxr","application/x-director"},
{"csh","application/x-csh"},
{"cpio","application/x-cpio"},
{"Z","application/x-compress"},
{"vcd","application/x-cdlink"},
{"bcpio","application/x-bcpio"},
{"rtf","application/rtf"},
{"ppt","application/powerpoint"},
{"ai","application/postscript"},
{"eps","application/postscript"},
{"ps","application/postscript"},
{"pdf","application/pdf"},
{"oda","application/oda"},
{"bin","application/octet-stream"},
{"dms","application/octet-stream"},
{"lha","application/octet-stream"},
{"lzh","application/octet-stream"},
{"exe","application/octet-stream"},
{"class","application/octet-stream"},
{"doc","application/msword"},
{"cpt","application/mac-compactpro"},
{"hqx","application/mac-binhex40"}]} = httpd_conf:load_mime_types(MimeTypes).
%%--------------------------------------------------------------------
%% Internal functions -----------------------------------
%%--------------------------------------------------------------------
url(http, End, Config) ->
Port = proplists:get_value(port, Config),
{ok,Host} = inet:gethostname(),
?URL_START ++ Host ++ ":" ++ integer_to_list(Port) ++ End.
do_max_clients(Config) ->
Version = proplists:get_value(http_version, Config),
Host = proplists:get_value(host, Config),
Port = proplists:get_value(port, Config),
Type = proplists:get_value(type, Config),
Request = http_request("GET /index.html ", Version, Host),
BlockRequest = http_request("GET /eval?httpd_example:delay(2000) ", Version, Host),
{ok, Socket} = inets_test_lib:connect_bin(Type, Host, Port, transport_opts(Type, Config)),
inets_test_lib:send(Type, Socket, BlockRequest),
ct:sleep(100), %% Avoid possible timing issues
ok = httpd_test_lib:verify_request(Type, Host,
Port,
transport_opts(Type, Config),
proplists:get_value(node, Config),
Request,
[{statuscode, 503},
{version, Version}]),
receive
{_, Socket, _Msg} ->
ok
end,
inets_test_lib:close(Type, Socket),
ct:sleep(100), %% Avoid possible timing issues
ok = httpd_test_lib:verify_request(Type, Host,
Port,
transport_opts(Type, Config),
proplists:get_value(node, Config),
Request,
[{statuscode, 200},
{version, Version}]).
setup_server_dirs(ServerRoot, DocRoot, DataDir) ->
CgiDir = filename:join(ServerRoot, "cgi-bin"),
AuthDir = filename:join(ServerRoot, "auth"),
PicsDir = filename:join(ServerRoot, "icons"),
ConfigDir = filename:join(ServerRoot, "config"),
ok = file:make_dir(ServerRoot),
ok = file:make_dir(DocRoot),
ok = file:make_dir(CgiDir),
ok = file:make_dir(AuthDir),
ok = file:make_dir(PicsDir),
ok = file:make_dir(ConfigDir),
DocSrc = filename:join(DataDir, "server_root/htdocs"),
AuthSrc = filename:join(DataDir, "server_root/auth"),
CgiSrc = filename:join(DataDir, "server_root/cgi-bin"),
PicsSrc = filename:join(DataDir, "server_root/icons"),
ConfigSrc = filename:join(DataDir, "server_root/config"),
inets_test_lib:copy_dirs(DocSrc, DocRoot),
inets_test_lib:copy_dirs(AuthSrc, AuthDir),
inets_test_lib:copy_dirs(CgiSrc, CgiDir),
inets_test_lib:copy_dirs(PicsSrc, PicsDir),
inets_test_lib:copy_dirs(ConfigSrc, ConfigDir),
Cgi = case test_server:os_type() of
{win32, _} ->
"cgi_echo.exe";
_ ->
"cgi_echo"
end,
inets_test_lib:copy_file(Cgi, DataDir, CgiDir),
AbsCgi = filename:join([CgiDir, Cgi]),
{ok, FileInfo} = file:read_file_info(AbsCgi),
ok = file:write_file_info(AbsCgi, FileInfo#file_info{mode = 8#00755}),
EnvCGI = filename:join([ServerRoot, "cgi-bin", "printenv.sh"]),
{ok, FileInfo1} = file:read_file_info(EnvCGI),
ok = file:write_file_info(EnvCGI,
FileInfo1#file_info{mode = 8#00755}).
start_apps(Group) when Group == https_basic;
Group == https_limit;
Group == https_custom;
Group == https_basic_auth;
Group == https_auth_api;
Group == https_auth_api_dets;
Group == https_auth_api_mnesia;
Group == https_htaccess;
Group == https_security;
Group == https_reload
->
inets_test_lib:start_apps([inets, asn1, crypto, public_key, ssl]);
start_apps(Group) when Group == http_basic;
Group == http_limit;
Group == http_custom;
Group == http_basic_auth;
Group == http_auth_api;
Group == http_auth_api_dets;
Group == http_auth_api_mnesia;
Group == http_htaccess;
Group == http_security;
Group == http_reload;
Group == http_mime_types->
inets_test_lib:start_apps([inets]).
server_start(_, HttpdConfig) ->
{ok, Pid} = inets:start(httpd, HttpdConfig),
Serv = inets:services_info(),
{value, {_, _, Info}} = lists:keysearch(Pid, 2, Serv),
{Pid, proplists:get_value(port, Info)}.
init_ssl(Group, Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
CaKey = {_Trusted,_} =
erl_make_certs:make_cert([{key, dsa},
{subject,
[{name, "Public Key"},
{?'id-at-name',
{printableString, "public_key"}},
{?'id-at-pseudonym',
{printableString, "pubkey"}},
{city, "Stockholm"},
{country, "SE"},
{org, "erlang"},
{org_unit, "testing dep"}
]}
]),
ok = erl_make_certs:write_pem(PrivDir, "public_key_cacert", CaKey),
CertK1 = {_Cert1, _} = erl_make_certs:make_cert([{issuer, CaKey}]),
CertK2 = {_Cert2,_} = erl_make_certs:make_cert([{issuer, CertK1},
{digest, md5},
{extensions, false}]),
ok = erl_make_certs:write_pem(PrivDir, "public_key_cert", CertK2),
case start_apps(Group) of
ok ->
init_httpd(Group, [{type, ssl} | Config]);
_ ->
{skip, "Could not start https apps"}
end.
server_config(http_basic, Config) ->
basic_conf() ++ server_config(http, Config);
server_config(https_basic, Config) ->
basic_conf() ++ server_config(https, Config);
server_config(http_reload, Config) ->
[{keep_alive_timeout, 2}] ++ server_config(http, Config);
server_config(https_reload, Config) ->
[{keep_alive_timeout, 2}] ++ server_config(https, Config);
server_config(http_limit, 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) ->
[{customize, ?MODULE}] ++ server_config(http, Config);
server_config(https_custom, 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) ->
ServerRoot = proplists:get_value(server_root, Config),
auth_conf(ServerRoot) ++ server_config(http, Config);
server_config(https_basic_auth, Config) ->
ServerRoot = proplists:get_value(server_root, Config),
auth_conf(ServerRoot) ++ server_config(https, Config);
server_config(http_auth_api, Config) ->
ServerRoot = proplists:get_value(server_root, Config),
auth_api_conf(ServerRoot, plain) ++ server_config(http, Config);
server_config(https_auth_api, Config) ->
ServerRoot = proplists:get_value(server_root, Config),
auth_api_conf(ServerRoot, plain) ++ server_config(https, Config);
server_config(http_auth_api_dets, Config) ->
ServerRoot = proplists:get_value(server_root, Config),
auth_api_conf(ServerRoot, dets) ++ server_config(http, Config);
server_config(https_auth_api_dets, Config) ->
ServerRoot = proplists:get_value(server_root, Config),
auth_api_conf(ServerRoot, dets) ++ server_config(https, Config);
server_config(http_auth_api_mnesia, Config) ->
ServerRoot = proplists:get_value(server_root, Config),
auth_api_conf(ServerRoot, mnesia) ++ server_config(http, Config);
server_config(https_auth_api_mnesia, Config) ->
ServerRoot = proplists:get_value(server_root, Config),
auth_api_conf(ServerRoot, mnesia) ++ server_config(https, Config);
server_config(http_htaccess, Config) ->
auth_access_conf() ++ server_config(http, Config);
server_config(https_htaccess, Config) ->
auth_access_conf() ++ server_config(https, Config);
server_config(http_security, Config) ->
ServerRoot = proplists:get_value(server_root, Config),
tl(auth_conf(ServerRoot)) ++ security_conf(ServerRoot) ++ server_config(http, Config);
server_config(https_security, Config) ->
ServerRoot = proplists:get_value(server_root, Config),
tl(auth_conf(ServerRoot)) ++ security_conf(ServerRoot) ++ server_config(https, Config);
server_config(http_mime_types, Config0) ->
Config1 = basic_conf() ++ server_config(http, Config0),
ServerRoot = proplists:get_value(server_root, Config0),
MimeTypesFile = filename:join([ServerRoot,"config", "mime.types"]),
[{mime_types, MimeTypesFile} | proplists:delete(mime_types, Config1)];
server_config(http, Config) ->
ServerRoot = proplists:get_value(server_root, Config),
[{port, 0},
{socket_type, {ip_comm, [{nodelay, true}]}},
{server_name,"httpd_test"},
{server_root, ServerRoot},
{document_root, proplists:get_value(doc_root, Config)},
{bind_address, any},
{ipfamily, proplists:get_value(ipfamily, Config)},
{max_header_size, 256},
{max_header_action, close},
{directory_index, ["index.html", "welcome.html"]},
{mime_types, [{"html","text/html"},{"htm","text/html"}, {"shtml","text/html"},
{"gif", "image/gif"}]},
{alias, {"/icons/", filename:join(ServerRoot,"icons") ++ "/"}},
{alias, {"/pics/", filename:join(ServerRoot,"icons") ++ "/"}},
{script_alias, {"/cgi-bin/", filename:join(ServerRoot, "cgi-bin") ++ "/"}},
{script_alias, {"/htbin/", filename:join(ServerRoot, "cgi-bin") ++ "/"}},
{erl_script_alias, {"/cgi-bin/erl", [httpd_example, io]}},
{eval_script_alias, {"/eval", [httpd_example, io]}}
];
server_config(https, Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
[{socket_type, {essl,
[{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),
Config = proplists:delete(server_pid, Config1),
{Pid, Port} = server_start(Group, server_config(Group, Config)),
[{server_pid, Pid}, {port, Port} | Config].
http_request(Request, "HTTP/1.1" = Version, Host, {Headers, Body}) ->
Request ++ Version ++ "\r\nhost:" ++ Host ++ "\r\n" ++ Headers ++ "\r\n" ++ Body;
http_request(Request, Version, _, {Headers, Body}) ->
Request ++ Version ++ "\r\n" ++ Headers ++ "\r\n" ++ Body.
http_request(Request, "HTTP/1.1" = Version, Host) ->
Request ++ Version ++ "\r\nhost:" ++ Host ++ "\r\n\r\n";
http_request(Request, Version, _) ->
Request ++ Version ++ "\r\n\r\n".
auth_request(Path, User, Passwd, "HTTP/1.1" = Version, Host) ->
"GET " ++ Path ++ " " ++ Version ++ "\r\nhost:" ++ Host ++
"\r\nAuthorization: Basic " ++
base64:encode_to_string(User++":"++Passwd) ++
"\r\n\r\n";
auth_request(Path, User, Passwd, Version, _Host) ->
"GET " ++ Path ++ " " ++ Version ++
"\r\nAuthorization: Basic " ++
base64:encode_to_string(User++":"++Passwd) ++
"\r\n\r\n".
http_request_missing_CR(Request, "HTTP/1.1" = Version, Host) ->
Request ++ Version ++ "\r\nhost:" ++ Host ++ "\r\n\r\n\n";
http_request_missing_CR(Request, Version, _) ->
Request ++ Version ++ "\r\n\n".
head_status("HTTP/0.9") ->
501; %% Not implemented in HTTP/0.9
head_status(_) ->
200.
basic_conf() ->
[{modules, [mod_alias, mod_range, mod_responsecontrol,
mod_trace, mod_esi, mod_cgi, mod_dir, mod_get, mod_head]}].
auth_access_conf() ->
[{modules, [mod_alias, mod_htaccess, mod_dir, mod_get, mod_head]},
{access_files, [".htaccess"]}].
auth_conf(Root) ->
[{modules, [mod_alias, mod_auth, mod_dir, mod_get, mod_head]},
{directory, {filename:join(Root, "htdocs/open"),
[{auth_type, plain},
{auth_name, "Open Area"},
{auth_user_file, filename:join(Root, "auth/passwd")},
{auth_group_file, filename:join(Root, "auth/group")},
{require_user, ["one", "Aladdin"]}]}},
{directory, {filename:join(Root, "htdocs/secret"),
[{auth_type, plain},
{auth_name, "Secret Area"},
{auth_user_file, filename:join(Root, "auth/passwd")},
{auth_group_file, filename:join(Root, "auth/group")},
{require_group, ["group1", "group2"]}]}},
{directory, {filename:join(Root, "htdocs/secret/top_secret"),
[{auth_type, plain},
{auth_name, "Top Secret Area"},
{auth_user_file, filename:join(Root, "auth/passwd")},
{auth_group_file, filename:join(Root, "auth/group")},
{require_group, ["group3"]}]}}].
auth_api_conf(Root, plain) ->
[{modules, [mod_alias, mod_auth, mod_dir, mod_get, mod_head]},
{directory, {filename:join(Root, "htdocs/open"),
[{auth_type, plain},
{auth_name, "Open Area"},
{auth_user_file, filename:join(Root, "auth/passwd")},
{auth_group_file, filename:join(Root, "auth/group")},
{require_user, ["one", "Aladdin"]}]}},
{directory, {filename:join(Root, "htdocs/secret"),
[{auth_type, plain},
{auth_name, "Secret Area"},
{auth_user_file, filename:join(Root, "auth/passwd")},
{auth_group_file, filename:join(Root, "auth/group")},
{require_group, ["group1", "group2"]}]}},
{directory, {filename:join(Root, "htdocs/secret/top_secret"),
[{auth_type, plain},
{auth_name, "Top Secret Area"},
{auth_user_file, filename:join(Root, "auth/passwd")},
{auth_group_file, filename:join(Root, "auth/group")},
{require_group, ["group3"]}]}}];
auth_api_conf(Root, dets) ->
[
{modules, [mod_alias, mod_auth, mod_dir, mod_get, mod_head]},
{directory, {filename:join(Root, "htdocs/dets_open"),
[{auth_type, dets},
{auth_name, "Dets Open Area"},
{auth_user_file, filename:join(Root, "passwd")},
{auth_group_file, filename:join(Root, "group")},
{require_user, ["one", "Aladdin"]}]}},
{directory, {filename:join(Root, "htdocs/dets_secret"),
[{auth_type, dets},
{auth_name, "Dests Secret Area"},
{auth_user_file, filename:join(Root, "passwd")},
{auth_group_file, filename:join(Root, "group")},
{require_group, ["group1", "group2"]}]}},
{directory, {filename:join(Root, "htdocs/dets_secret/top_secret"),
[{auth_type, dets},
{auth_name, "Dets Top Secret Area"},
{auth_user_file, filename:join(Root, "passwd")},
{auth_group_file, filename:join(Root, "group")},
{require_group, ["group3"]}]}}
];
auth_api_conf(Root, mnesia) ->
[{modules, [mod_alias, mod_auth, mod_dir, mod_get, mod_head]},
{directory, {filename:join(Root, "htdocs/mnesia_open"),
[{auth_type, mnesia},
{auth_name, "Mnesia Open Area"},
{require_user, ["one", "Aladdin"]}]}},
{directory, {filename:join(Root, "htdocs/mnesia_secret"),
[{auth_type, mnesia},
{auth_name, "Mnesia Secret Area"},
{require_group, ["group1", "group2"]}]}},
{directory, {filename:join(Root, "htdocs/mnesia_secret/top_secret"),
[{auth_type, mnesia},
{auth_name, "Mnesia Top Secret Area"},
{require_group, ["group3"]}]}}].
security_conf(Root) ->
SecFile = filename:join(Root, "security_data"),
Open = filename:join(Root, "htdocs/open"),
Secret = filename:join(Root, "htdocs/secret"),
TopSecret = filename:join(Root, "htdocs/secret/top_secret"),
[{modules, [mod_alias, mod_auth, mod_security, mod_dir, mod_get, mod_head]},
{security_directory, {Open,
[{auth_name, "Open Area"},
{auth_user_file, filename:join(Root, "auth/passwd")},
{auth_group_file, filename:join(Root, "auth/group")},
{require_user, ["one", "Aladdin"]} |
mod_security_conf(SecFile, Open)]}},
{security_directory, {Secret,
[{auth_name, "Secret Area"},
{auth_user_file, filename:join(Root, "auth/passwd")},
{auth_group_file, filename:join(Root, "auth/group")},
{require_group, ["group1", "group2"]} |
mod_security_conf(SecFile, Secret)]}},
{security_directory, {TopSecret,
[{auth_name, "Top Secret Area"},
{auth_user_file, filename:join(Root, "auth/passwd")},
{auth_group_file, filename:join(Root, "auth/group")},
{require_group, ["group3"]} |
mod_security_conf(SecFile, TopSecret)]}}].
mod_security_conf(SecFile, Dir) ->
[{data_file, SecFile},
{max_retries, 3},
{fail_expire_time, ?FAIL_EXPIRE_TIME},
{block_time, 1},
{auth_timeout, ?AUTH_TIMEOUT},
{callback_module, ?MODULE},
{path, Dir} %% This is should not be needed, but is atm, awful design!
].
http_status(Request, Config, Expected) ->
Version = proplists:get_value(http_version, Config),
Host = proplists:get_value(host, Config),
Type = proplists:get_value(type, Config),
httpd_test_lib:verify_request(proplists:get_value(type, Config), Host,
proplists:get_value(port, Config),
transport_opts(Type, Config),
proplists:get_value(node, Config),
http_request(Request, Version, Host),
Expected ++ [{version, Version}]).
http_status(Request, HeadersAndBody, Config, Expected) ->
Version = proplists:get_value(http_version, Config),
Host = proplists:get_value(host, Config),
Type = proplists:get_value(type, Config),
httpd_test_lib:verify_request(proplists:get_value(type, Config), Host,
proplists:get_value(port, Config),
transport_opts(Type, Config),
proplists:get_value(node, Config),
http_request(Request, Version, Host, HeadersAndBody),
Expected ++ [{version, Version}]).
auth_status(AuthRequest, Config, Expected) ->
Version = proplists:get_value(http_version, Config),
Host = proplists:get_value(host, Config),
Type = proplists:get_value(type, Config),
httpd_test_lib:verify_request(proplists:get_value(type, Config), Host,
proplists:get_value(port, Config),
transport_opts(Type, Config),
proplists:get_value(node, Config),
AuthRequest,
Expected ++ [{version, Version}]).
basic_auth_requiered(Config) ->
ok = http_status("GET /open/ ", Config, [{statuscode, 401},
{header, "WWW-Authenticate"}]),
ok = http_status("GET /secret/ ", Config, [{statuscode, 401},
{header, "WWW-Authenticate"}]),
ok = http_status("GET /secret/top_secret/ ", Config, [{statuscode, 401},
{header, "WWW-Authenticate"}]).
start_mnesia(Node) ->
case rpc:call(Node, ?MODULE, cleanup_mnesia, []) of
ok ->
ok;
Other ->
ct:fail({failed_to_cleanup_mnesia, Other})
end,
case rpc:call(Node, ?MODULE, setup_mnesia, []) of
{atomic, ok} ->
ok;
Other2 ->
ct:fail({failed_to_setup_mnesia, Other2})
end,
ok.
setup_mnesia() ->
setup_mnesia([node()]).
setup_mnesia(Nodes) ->
ok = mnesia:create_schema(Nodes),
ok = mnesia:start(),
{atomic, ok} = mnesia:create_table(httpd_user,
[{attributes,
record_info(fields, httpd_user)},
{disc_copies,Nodes}, {type, set}]),
{atomic, ok} = mnesia:create_table(httpd_group,
[{attributes,
record_info(fields,
httpd_group)},
{disc_copies,Nodes}, {type,bag}]).
cleanup_mnesia() ->
mnesia:start(),
mnesia:delete_table(httpd_user),
mnesia:delete_table(httpd_group),
stopped = mnesia:stop(),
mnesia:delete_schema([node()]),
ok.
transport_opts(ssl, Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
[proplists:get_value(ipfamily, Config),
{cacertfile, filename:join(PrivDir, "public_key_cacert.pem")}];
transport_opts(_, Config) ->
[proplists:get_value(ipfamily, Config)].
%%% mod_range
create_range_data(Path) ->
PathAndFileName=filename:join([Path,"range.txt"]),
case file:read_file(PathAndFileName) of
{error, enoent} ->
file:write_file(PathAndFileName,list_to_binary(["12345678901234567890",
"12345678901234567890",
"12345678901234567890",
"12345678901234567890",
"12345678901234567890"]));
_ ->
ok
end.
%%% mod_htaccess
create_htaccess_data(Path, IpAddress)->
create_htaccess_dirs(Path),
create_html_file(filename:join([Path,"ht/open/dummy.html"])),
create_html_file(filename:join([Path,"ht/blocknet/dummy.html"])),
create_html_file(filename:join([Path,"ht/secret/dummy.html"])),
create_html_file(filename:join([Path,"ht/secret/top_secret/dummy.html"])),
create_htaccess_file(filename:join([Path,"ht/open/.htaccess"]),
Path, "user one Aladdin"),
create_htaccess_file(filename:join([Path,"ht/secret/.htaccess"]),
Path, "group group1 group2"),
create_htaccess_file(filename:join([Path,
"ht/secret/top_secret/.htaccess"]),
Path, "user four"),
create_htaccess_file(filename:join([Path,"ht/blocknet/.htaccess"]),
Path, nouser, IpAddress),
create_user_group_file(filename:join([Path,"ht","users.file"]),
"one:OnePassword\ntwo:TwoPassword\nthree:"
"ThreePassword\nfour:FourPassword\nAladdin:"
"AladdinPassword"),
create_user_group_file(filename:join([Path,"ht","groups.file"]),
"group1: two one\ngroup2: two three").
create_html_file(PathAndFileName)->
file:write_file(PathAndFileName,list_to_binary(
"<html><head><title>test</title></head>
<body>testar</body></html>")).
create_htaccess_file(PathAndFileName, BaseDir, RequireData)->
file:write_file(PathAndFileName,
list_to_binary(
"AuthUserFile "++ BaseDir ++
"/ht/users.file\nAuthGroupFile "++ BaseDir
++ "/ht/groups.file\nAuthName Test\nAuthType"
" Basic\n<Limit>\nrequire " ++ RequireData ++
"\n</Limit>")).
create_htaccess_file(PathAndFileName, BaseDir, nouser, IpAddress)->
file:write_file(PathAndFileName,list_to_binary(
"AuthUserFile "++ BaseDir ++
"/ht/users.file\nAuthGroupFile " ++
BaseDir ++ "/ht/groups.file\nAuthName"
" Test\nAuthType"
" Basic\n<Limit GET>\n\tallow from " ++
format_ip(IpAddress,
string:rchr(IpAddress,$.)) ++
"\n</Limit>")).
create_user_group_file(PathAndFileName, Data)->
file:write_file(PathAndFileName, list_to_binary(Data)).
create_htaccess_dirs(Path)->
ok = file:make_dir(filename:join([Path,"ht"])),
ok = file:make_dir(filename:join([Path,"ht/open"])),
ok = file:make_dir(filename:join([Path,"ht/blocknet"])),
ok = file:make_dir(filename:join([Path,"ht/secret"])),
ok = file:make_dir(filename:join([Path,"ht/secret/top_secret"])).
remove_htaccess_dirs(Path)->
file:del_dir(filename:join([Path,"ht/secret/top_secret"])),
file:del_dir(filename:join([Path,"ht/secret"])),
file:del_dir(filename:join([Path,"ht/blocknet"])),
file:del_dir(filename:join([Path,"ht/open"])),
file:del_dir(filename:join([Path,"ht"])).
format_ip(IpAddress,Pos)when Pos > 0->
case lists:nth(Pos,IpAddress) of
$.->
case lists:nth(Pos-2,IpAddress) of
$.->
format_ip(IpAddress,Pos-3);
_->
lists:sublist(IpAddress,Pos-2) ++ "."
end;
_ ->
format_ip(IpAddress,Pos-1)
end;
format_ip(IpAddress, _Pos)->
"1" ++ IpAddress.
remove_htaccess(Path)->
file:delete(filename:join([Path,"ht/open/dummy.html"])),
file:delete(filename:join([Path,"ht/secret/dummy.html"])),
file:delete(filename:join([Path,"ht/secret/top_secret/dummy.html"])),
file:delete(filename:join([Path,"ht/blocknet/dummy.html"])),
file:delete(filename:join([Path,"ht/blocknet/.htaccess"])),
file:delete(filename:join([Path,"ht/open/.htaccess"])),
file:delete(filename:join([Path,"ht/secret/.htaccess"])),
file:delete(filename:join([Path,"ht/secret/top_secret/.htaccess"])),
file:delete(filename:join([Path,"ht","users.file"])),
file:delete(filename:join([Path,"ht","groups.file"])),
remove_htaccess_dirs(Path).
dos_hostname(Type, Port, Host, Node, Version, Max) ->
TooLongHeader = lists:append(lists:duplicate(Max + 1, "a")),
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
dos_hostname_request("", Version),
[{statuscode, 200},
{version, Version}]),
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
dos_hostname_request("dummy-host.ericsson.se", Version),
[{statuscode, 200},
{version, Version}]),
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
dos_hostname_request(TooLongHeader, Version),
[{statuscode, request_entity_too_large_code(Version)},
{version, Version}]).
dos_hostname_request(Host, Version) ->
dos_http_request("GET / ", Version, Host).
dos_http_request(Request, "HTTP/1.1" = Version, Host) ->
http_request(Request, Version, Host);
dos_http_request(Request, Version, Host) ->
Request ++ Version ++ "\r\nhost:" ++ Host ++ "\r\n\r\n".
request_entity_too_large_code("HTTP/1.0") ->
403; %% 413 not defined in HTTP/1.0
request_entity_too_large_code(_) ->
413.
length_required_code("HTTP/1.0") ->
403; %% 411 not defined in HTTP/1.0
length_required_code(_) ->
411.
garbage_content_length(Type, Port, Host, Node, Version) ->
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
garbage_content_length_request("GET / ", Version, Host, "aaaa"),
[{statuscode, length_required_code(Version)},
{version, Version}]),
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
garbage_content_length_request("GET / ", Version, Host,
lists:duplicate($a, 100)),
[{statuscode, request_entity_too_large_code(Version)},
{version, Version}]).
garbage_content_length_request(Request, Version, Host, Garbage) ->
http_request(Request, Version, Host,
{"content-length:" ++ Garbage, "Body with garbage content length indicator"}).
update_password(Node, ServerRoot, _Address, Port, AuthPrefix, Dir, Old, New)->
Directory = filename:join([ServerRoot, "htdocs", AuthPrefix ++ Dir]),
rpc:call(Node, mod_auth, update_password,
[undefined, Port, Directory, Old, New, New]).
add_user(Node, Root, Port, AuthPrefix, Dir, User, Password, UserData) ->
Addr = undefined,
Directory = filename:join([Root, "htdocs", AuthPrefix ++ Dir]),
rpc:call(Node, mod_auth, add_user,
[User, Password, UserData, Addr, Port, Directory]).
delete_user(Node, Root, _Host, Port, AuthPrefix, Dir, User) ->
Addr = undefined,
Directory = filename:join([Root, "htdocs", AuthPrefix ++ Dir]),
rpc:call(Node, mod_auth, delete_user, [User, Addr, Port, Directory]).
remove_users(Node, ServerRoot, Host, Port, AuthPrefix, Dir) ->
%% List users, delete them, and make sure they are gone.
case list_users(Node, ServerRoot, Host, Port, AuthPrefix, Dir) of
{ok, Users} ->
lists:foreach(fun(User) ->
delete_user(Node, ServerRoot, Host,
Port, AuthPrefix, Dir, User)
end,
Users),
{ok, []} = list_users(Node, ServerRoot, Host, Port, AuthPrefix, Dir);
_ ->
ok
end.
list_users(Node, Root, _Host, Port, AuthPrefix, Dir) ->
Addr = undefined,
Directory = filename:join([Root, "htdocs", AuthPrefix ++ Dir]),
rpc:call(Node, mod_auth, list_users, [Addr, Port, Directory]).
remove_groups(Node, ServerRoot, Host, Port, AuthPrefix, Dir) ->
{ok, Groups} = list_groups(Node, ServerRoot, Host, Port, AuthPrefix, Dir),
lists:foreach(fun(Group) ->
delete_group(Node, Group, Port, ServerRoot, AuthPrefix, Dir)
end,
Groups),
{ok, []} = list_groups(Node, ServerRoot, Host, Port, AuthPrefix, Dir).
delete_group(Node, Group, Port, Root, AuthPrefix, Dir) ->
Addr = undefined,
Directory = filename:join([Root, "htdocs", AuthPrefix ++ Dir]),
rpc:call(Node, mod_auth, delete_group, [Group, Addr, Port, Directory]).
list_groups(Node, Root, _, Port, AuthPrefix, Dir) ->
Addr = undefined,
Directory = filename:join([Root, "htdocs", AuthPrefix ++ Dir]),
rpc:call(Node, mod_auth, list_groups, [Addr, Port, Directory]).
add_group_member(Node, Root, Port, AuthPrefix, Dir, User, Group) ->
Addr = undefined,
Directory = filename:join([Root, "htdocs", AuthPrefix ++ Dir]),
rpc:call(Node, mod_auth, add_group_member, [Group, User, Addr, Port,
Directory]).
getaddr() ->
{ok,HostName} = inet:gethostname(),
{ok,{A1,A2,A3,A4}} = inet:getaddr(HostName,inet),
lists:flatten(io_lib:format("~p.~p.~p.~p",[A1,A2,A3,A4])).
receive_security_event(Event, Node, Port) ->
receive
Event ->
ok;
{'EXIT', _, _} ->
receive_security_event(Event, Node, Port)
after 5000 ->
%% Flush the message queue, to see if we got something...
inets_test_lib:flush()
end.
list_blocked_users(Node,Port) ->
Addr = undefined, % Assumed to be on the same host
rpc:call(Node, mod_security, list_blocked_users, [Addr,Port]).
list_blocked_users(Node,Port,Dir) ->
Addr = undefined, % Assumed to be on the same host
rpc:call(Node, mod_security, list_blocked_users, [Addr,Port,Dir]).
block_user(Node,User,Port,Dir,Sec) ->
Addr = undefined, % Assumed to be on the same host
rpc:call(Node, mod_security, block_user, [User, Addr, Port, Dir, Sec]).
unblock_user(Node,User,Port,Dir) ->
Addr = undefined, % Assumed to be on the same host
rpc:call(Node, mod_security, unblock_user, [User, Addr, Port, Dir]).
list_auth_users(Node,Port) ->
Addr = undefined, % Assumed to be on the same host
rpc:call(Node, mod_security, list_auth_users, [Addr,Port]).
list_auth_users(Node,Port,Dir) ->
Addr = undefined, % Assumed to be on the same host
rpc:call(Node, mod_security, list_auth_users, [Addr,Port,Dir]).
event(What, Port, Dir, Data) ->
Msg = {event, What, Port, Dir, Data},
case global:whereis_name(mod_security_test) of
undefined ->
ok;
_Pid ->
global:send(mod_security_test, Msg)
end.
type(ip_comm) ->
tcp;
type(_) ->
ssl.
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"}].
peer(Config) ->
case proplists:get_value(type, Config) of
ssl ->
"true";
_ ->
"false"
end.