aboutsummaryrefslogtreecommitdiff
path: root/src/ejabberd_http.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/ejabberd_http.erl')
-rw-r--r--src/ejabberd_http.erl283
1 files changed, 69 insertions, 214 deletions
diff --git a/src/ejabberd_http.erl b/src/ejabberd_http.erl
index 29d23e082..6f90cfcd7 100644
--- a/src/ejabberd_http.erl
+++ b/src/ejabberd_http.erl
@@ -25,17 +25,15 @@
-module(ejabberd_http).
-behaviour(ejabberd_listener).
--behaviour(ejabberd_config).
-author('alexey@process-one.net').
%% External exports
-export([start/3, start_link/3,
accept/1, receive_headers/1, recv_file/2,
- transform_listen_option/2, listen_opt_type/1,
- listen_options/0]).
+ listen_opt_type/1, listen_options/0]).
--export([init/3, opt_type/1]).
+-export([init/3]).
-include("logger.hrl").
-include("xmpp.hrl").
@@ -112,9 +110,10 @@ init(SockMod, Socket, Opts) ->
false -> [compression_none | TLSOpts1];
true -> TLSOpts1
end,
- TLSOpts3 = case get_certfile(Opts) of
- undefined -> TLSOpts2;
- CertFile -> [{certfile, CertFile}|TLSOpts2]
+ TLSOpts3 = case ejabberd_pkix:get_certfile(
+ ejabberd_config:get_myname()) of
+ error -> TLSOpts2;
+ {ok, CertFile} -> [{certfile, CertFile}|TLSOpts2]
end,
TLSOpts = [verify_none | TLSOpts3],
{SockMod1, Socket1} = if TLSEnabled ->
@@ -124,40 +123,16 @@ init(SockMod, Socket, Opts) ->
{fast_tls, TLSSocket};
true -> {SockMod, Socket}
end,
- Captcha = case proplists:get_bool(captcha, Opts) of
- true -> [{[<<"captcha">>], ejabberd_captcha}];
- false -> []
- end,
- Register = case proplists:get_bool(register, Opts) of
- true -> [{[<<"register">>], mod_register_web}];
- false -> []
- end,
- Admin = case proplists:get_bool(web_admin, Opts) of
- true -> [{[<<"admin">>], ejabberd_web_admin}];
- false -> []
- end,
- Bind = case proplists:get_bool(http_bind, Opts) of
- true -> [{[<<"http-bind">>], mod_bosh}];
- false -> []
- end,
- XMLRPC = case proplists:get_bool(xmlrpc, Opts) of
- true -> [{[], ejabberd_xmlrpc}];
- false -> []
- end,
SockPeer = proplists:get_value(sock_peer_name, Opts, none),
- DefinedHandlers = proplists:get_value(request_handlers, Opts, []),
- RequestHandlers = DefinedHandlers ++ Captcha ++ Register ++
- Admin ++ Bind ++ XMLRPC,
+ RequestHandlers = proplists:get_value(request_handlers, Opts, []),
?DEBUG("S: ~p~n", [RequestHandlers]),
- DefaultHost = proplists:get_value(default_host, Opts),
{ok, RE} = re:compile(<<"^(?:\\[(.*?)\\]|(.*?))(?::(\\d+))?$">>),
CustomHeaders = proplists:get_value(custom_headers, Opts, []),
State = #state{sockmod = SockMod1,
socket = Socket1,
- default_host = DefaultHost,
custom_headers = CustomHeaders,
options = Opts,
request_handlers = RequestHandlers,
@@ -287,19 +262,20 @@ process_header(State, Data) ->
{http_header, _, 'Accept-Language' = Name, _, Langs}} ->
State#state{request_lang = parse_lang(Langs),
request_headers = add_header(Name, Langs, State)};
- {ok, {http_header, _, 'Host' = Name, _, Host}} ->
+ {ok, {http_header, _, 'Host' = Name, _, Value}} ->
+ {Host, Port, TP} = get_transfer_protocol(State#state.addr_re, SockMod, Value),
State#state{request_host = Host,
- request_headers = add_header(Name, Host, State)};
+ request_port = Port,
+ request_tp = TP,
+ request_headers = add_header(Name, Value, State)};
{ok, {http_header, _, Name, _, Value}} when is_binary(Name) ->
State#state{request_headers =
add_header(normalize_header_name(Name), Value, State)};
{ok, {http_header, _, Name, _, Value}} ->
State#state{request_headers =
add_header(Name, Value, State)};
- {ok, http_eoh}
- when State#state.request_host == undefined ->
- ?DEBUG("An HTTP request without 'Host' HTTP "
- "header was received.", []),
+ {ok, http_eoh} when State#state.request_host == undefined;
+ State#state.request_host == error ->
{State1, Out} = process_request(State),
send_text(State1, Out),
process_header(State, {ok, {http_error, <<>>}});
@@ -307,32 +283,33 @@ process_header(State, Data) ->
?DEBUG("(~w) http query: ~w ~p~n",
[State#state.socket, State#state.request_method,
element(2, State#state.request_path)]),
- {HostProvided, Port, TP} =
- get_transfer_protocol(State#state.addr_re, SockMod,
- State#state.request_host),
- Host = get_host_really_served(State#state.default_host,
- HostProvided),
- State2 = State#state{request_host = Host,
- request_port = Port, request_tp = TP},
- {State3, Out} = process_request(State2),
- send_text(State3, Out),
- case State3#state.request_keepalive of
- true ->
- #state{sockmod = SockMod, socket = Socket,
- trail = State3#state.trail,
- options = State#state.options,
- default_host = State#state.default_host,
- custom_headers = State#state.custom_headers,
- request_handlers = State#state.request_handlers,
- addr_re = State#state.addr_re};
- _ ->
- #state{end_of_request = true,
- trail = State3#state.trail,
- options = State#state.options,
- default_host = State#state.default_host,
- custom_headers = State#state.custom_headers,
- request_handlers = State#state.request_handlers,
- addr_re = State#state.addr_re}
+ case ejabberd_router:is_my_route(State#state.request_host) of
+ true ->
+ {State3, Out} = process_request(State),
+ send_text(State3, Out),
+ case State3#state.request_keepalive of
+ true ->
+ #state{sockmod = SockMod, socket = Socket,
+ trail = State3#state.trail,
+ options = State#state.options,
+ default_host = State#state.default_host,
+ custom_headers = State#state.custom_headers,
+ request_handlers = State#state.request_handlers,
+ addr_re = State#state.addr_re};
+ _ ->
+ #state{end_of_request = true,
+ trail = State3#state.trail,
+ options = State#state.options,
+ default_host = State#state.default_host,
+ custom_headers = State#state.custom_headers,
+ request_handlers = State#state.request_handlers,
+ addr_re = State#state.addr_re}
+ end;
+ false ->
+ Out = make_text_output(State, 400, State#state.custom_headers,
+ <<"Host not served">>),
+ send_text(State, Out),
+ process_header(State, {ok, {http_error, <<>>}})
end;
_ ->
#state{end_of_request = true,
@@ -346,14 +323,6 @@ process_header(State, Data) ->
add_header(Name, Value, State)->
[{Name, Value} | State#state.request_headers].
-get_host_really_served(undefined, Provided) ->
- Provided;
-get_host_really_served(Default, Provided) ->
- case ejabberd_router:is_my_host(Provided) of
- true -> Provided;
- false -> Default
- end.
-
get_transfer_protocol(RE, SockMod, HostPort) ->
{Proto, DefPort} = case SockMod of
gen_tcp -> {http, 80};
@@ -361,15 +330,15 @@ get_transfer_protocol(RE, SockMod, HostPort) ->
end,
{Host, Port} = case re:run(HostPort, RE, [{capture,[1,2,3],binary}]) of
nomatch ->
- {<<"0.0.0.0">>, DefPort};
+ {error, DefPort};
{match, [<<>>, H, <<>>]} ->
- {H, DefPort};
+ {jid:nameprep(H), DefPort};
{match, [H, <<>>, <<>>]} ->
- {H, DefPort};
+ {jid:nameprep(H), DefPort};
{match, [<<>>, H, PortStr]} ->
- {H, binary_to_integer(PortStr)};
+ {jid:nameprep(H), binary_to_integer(PortStr)};
{match, [H, <<>>, PortStr]} ->
- {H, binary_to_integer(PortStr)}
+ {jid:nameprep(H), binary_to_integer(PortStr)}
end,
{Host, Port, Proto}.
@@ -461,6 +430,10 @@ process_request(#state{request_host = undefined,
custom_headers = CustomHeaders} = State) ->
{State, make_text_output(State, 400, CustomHeaders,
<<"Missing Host header">>)};
+process_request(#state{request_host = error,
+ custom_headers = CustomHeaders} = State) ->
+ {State, make_text_output(State, 400, CustomHeaders,
+ <<"Malformed Host header">>)};
process_request(#state{request_method = Method,
request_auth = Auth,
request_lang = Lang,
@@ -557,7 +530,7 @@ analyze_ip_xff(IP, [], _Host) -> IP;
analyze_ip_xff({IPLast, Port}, XFF, Host) ->
[ClientIP | ProxiesIPs] = str:tokens(XFF, <<", ">>) ++
[misc:ip_to_list(IPLast)],
- TrustedProxies = ejabberd_config:get_option({trusted_proxies, Host}, []),
+ TrustedProxies = ejabberd_option:trusted_proxies(Host),
IPClient = case is_ipchain_trusted(ProxiesIPs,
TrustedProxies)
of
@@ -581,7 +554,7 @@ is_ipchain_trusted(UserIPs, Masks) ->
{ok, IP2} ->
lists:any(
fun({Mask, MaskLen}) ->
- acl:ip_matches_mask(IP2, Mask, MaskLen)
+ misc:match_ip_mask(IP2, Mask, MaskLen)
end, Masks);
_ ->
false
@@ -803,7 +776,7 @@ rest_dir(N, Path, <<_H, T/binary>>) -> rest_dir(N, Path, T).
expand_custom_headers(Headers) ->
lists:map(fun({K, V}) ->
{K, misc:expand_keyword(<<"@VERSION@">>, V,
- ejabberd_config:get_version())}
+ ejabberd_option:version())}
end, Headers).
code_to_phrase(100) -> <<"Continue">>;
@@ -851,7 +824,7 @@ code_to_phrase(503) -> <<"Service Unavailable">>;
code_to_phrase(504) -> <<"Gateway Timeout">>;
code_to_phrase(505) -> <<"HTTP Version Not Supported">>.
--spec parse_auth(binary()) -> {binary(), binary()} | {oauth, binary(), []} | undefined.
+-spec parse_auth(binary()) -> {binary(), binary()} | {oauth, binary(), []} | invalid.
parse_auth(<<"Basic ", Auth64/binary>>) ->
try base64:decode(Auth64) of
Auth ->
@@ -927,150 +900,32 @@ normalize_path([_Parent, <<"..">>|Path], Norm) ->
normalize_path([Part | Path], Norm) ->
normalize_path(Path, [Part|Norm]).
--spec get_certfile([proplists:property()]) -> binary() | undefined.
-get_certfile(Opts) ->
- case lists:keyfind(certfile, 1, Opts) of
- {_, CertFile} ->
- CertFile;
- false ->
- case ejabberd_pkix:get_certfile(ejabberd_config:get_myname()) of
- {ok, CertFile} ->
- CertFile;
- error ->
- ejabberd_config:get_option({domain_certfile, ejabberd_config:get_myname()})
- end
- end.
-
-transform_listen_option(captcha, Opts) ->
- [{captcha, true}|Opts];
-transform_listen_option(register, Opts) ->
- [{register, true}|Opts];
-transform_listen_option(web_admin, Opts) ->
- [{web_admin, true}|Opts];
-transform_listen_option(http_bind, Opts) ->
- [{http_bind, true}|Opts];
-transform_listen_option(http_poll, Opts) ->
- Opts;
-transform_listen_option({request_handlers, Hs}, Opts) ->
- Hs1 = lists:map(
- fun({PList, Mod}) when is_list(PList) ->
- Path = iolist_to_binary([[$/, P] || P <- PList]),
- {Path, Mod};
- (Opt) ->
- Opt
- end, Hs),
- [{request_handlers, Hs1} | Opts];
-transform_listen_option(Opt, Opts) ->
- [Opt|Opts].
-
-prepare_request_module(mod_http_bind) ->
- mod_bosh;
-prepare_request_module(Mod) when is_atom(Mod) ->
- case code:ensure_loaded(Mod) of
- {module, Mod} ->
- Mod;
- Err ->
- ?ERROR_MSG(
- "Failed to load request handler ~s, "
- "did you mean ~s? Hint: "
- "make sure there is no typo and file ~s.beam "
- "exists inside either ~s or ~s directory",
- [Mod,
- misc:best_match(Mod, ejabberd_config:get_modules()),
- Mod,
- filename:dirname(code:which(?MODULE)),
- ext_mod:modules_dir()]),
- erlang:error(Err)
- end.
-
-emit_option_replacement(Option, Path, Handler) ->
- ?WARNING_MSG(
- "Listening option '~s' is deprecated, enable it via request handlers, e.g.:~n"
- "listen:~n"
- " ...~n"
- " -~n"
- " module: ~s~n"
- " request_handlers:~n"
- " ...~n"
- " \"~s\": ~s~n",
- [Option, ?MODULE, Path, Handler]).
-
--spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
-opt_type(trusted_proxies) ->
- fun (all) -> all;
- (TPs) -> lists:filtermap(
- fun(TP) ->
- case acl:parse_ip_netmask(iolist_to_binary(TP)) of
- {ok, Ip, Mask} -> {true, {Ip, Mask}};
- _ -> false
- end
- end, TPs)
- end;
-opt_type(_) -> [trusted_proxies].
-
-listen_opt_type(certfile = Opt) ->
- fun(S) ->
- ?WARNING_MSG("Listening option '~s' for ~s is deprecated, use "
- "'certfiles' global option instead", [Opt, ?MODULE]),
- {ok, File} = ejabberd_pkix:add_certfile(S),
- File
- end;
-listen_opt_type(captcha) ->
- fun(B) when is_boolean(B) ->
- emit_option_replacement(captcha, "/captcha", ejabberd_captcha),
- B
- end;
-listen_opt_type(register) ->
- fun(B) when is_boolean(B) ->
- emit_option_replacement(register, "/register", mod_register_web),
- B
- end;
-listen_opt_type(web_admin) ->
- fun(B) when is_boolean(B) ->
- emit_option_replacement(web_admin, "/admin", ejabberd_web_admin),
- B
- end;
-listen_opt_type(http_bind) ->
- fun(B) when is_boolean(B) ->
- emit_option_replacement(http_bind, "/bosh", mod_bosh),
- B
- end;
-listen_opt_type(xmlrpc) ->
- fun(B) when is_boolean(B) ->
- emit_option_replacement(xmlrpc, "/", ejabberd_xmlrpc),
- B
- end;
listen_opt_type(tag) ->
- fun(B) when is_binary(B) -> B end;
+ econf:binary();
listen_opt_type(request_handlers) ->
- fun(Hs) ->
- Hs1 = lists:map(fun
- ({Mod, Path}) when is_atom(Mod) -> {Path, Mod};
- ({Path, Mod}) -> {Path, Mod}
- end, Hs),
- Hs2 = [{str:tokens(
- iolist_to_binary(Path), <<"/">>),
- Mod} || {Path, Mod} <- Hs1],
- [{Path, prepare_request_module(Mod)} || {Path, Mod} <- Hs2]
- end;
+ econf:and_then(
+ econf:map(
+ econf:binary(),
+ econf:beam([[{socket_handoff, 3}, {process, 2}]])),
+ fun(L) ->
+ [{str:tokens(Path, <<"/">>), Mod} || {Path, Mod} <- L]
+ end);
listen_opt_type(default_host) ->
- fun iolist_to_binary/1;
+ econf:domain();
listen_opt_type(custom_headers) ->
- fun expand_custom_headers/1.
+ econf:and_then(
+ econf:map(
+ econf:binary(),
+ econf:binary()),
+ fun expand_custom_headers/1).
listen_options() ->
- [{certfile, undefined},
- {ciphers, undefined},
+ [{ciphers, undefined},
{dhfile, undefined},
{cafile, undefined},
{protocol_options, undefined},
{tls, false},
{tls_compression, false},
- {captcha, false},
- {register, false},
- {web_admin, false},
- {http_bind, false},
- {xmlrpc, false},
{request_handlers, []},
{tag, <<>>},
{default_host, undefined},