2004-03-02 22:16:55 +01:00
|
|
|
%%%----------------------------------------------------------------------
|
|
|
|
%%% File : ejabberd_http.erl
|
2007-12-24 14:57:53 +01:00
|
|
|
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
2007-05-07 17:47:33 +02:00
|
|
|
%%% Purpose :
|
2007-12-24 14:57:53 +01:00
|
|
|
%%% Created : 27 Feb 2004 by Alexey Shchepin <alexey@process-one.net>
|
|
|
|
%%%
|
|
|
|
%%%
|
2018-01-05 21:18:58 +01:00
|
|
|
%%% ejabberd, Copyright (C) 2002-2018 ProcessOne
|
2007-12-24 14:57:53 +01:00
|
|
|
%%%
|
|
|
|
%%% This program is free software; you can redistribute it and/or
|
|
|
|
%%% modify it under the terms of the GNU General Public License as
|
|
|
|
%%% published by the Free Software Foundation; either version 2 of the
|
|
|
|
%%% License, or (at your option) any later version.
|
|
|
|
%%%
|
|
|
|
%%% This program is distributed in the hope that it will be useful,
|
|
|
|
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
%%% General Public License for more details.
|
2009-01-12 15:44:42 +01:00
|
|
|
%%%
|
2014-02-22 11:27:40 +01:00
|
|
|
%%% You should have received a copy of the GNU General Public License along
|
|
|
|
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
|
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
2007-12-24 14:57:53 +01:00
|
|
|
%%%
|
2004-03-02 22:16:55 +01:00
|
|
|
%%%----------------------------------------------------------------------
|
|
|
|
|
|
|
|
-module(ejabberd_http).
|
2013-03-14 10:33:02 +01:00
|
|
|
|
2015-06-01 14:38:27 +02:00
|
|
|
-behaviour(ejabberd_config).
|
|
|
|
|
2007-12-24 14:57:53 +01:00
|
|
|
-author('alexey@process-one.net').
|
2004-03-02 22:16:55 +01:00
|
|
|
|
|
|
|
%% External exports
|
2013-03-14 10:33:02 +01:00
|
|
|
-export([start/2, start_link/2, become_controller/1,
|
2018-05-14 18:30:21 +02:00
|
|
|
socket_type/0, receive_headers/1, recv_file/2,
|
2017-04-30 18:01:47 +02:00
|
|
|
transform_listen_option/2, listen_opt_type/1]).
|
2004-03-02 22:16:55 +01:00
|
|
|
|
2015-06-03 15:05:17 +02:00
|
|
|
-export([init/2, opt_type/1]).
|
2009-12-07 18:33:02 +01:00
|
|
|
|
2013-04-08 11:12:54 +02:00
|
|
|
-include("logger.hrl").
|
2016-07-30 17:37:57 +02:00
|
|
|
-include("xmpp.hrl").
|
2004-03-04 21:56:32 +01:00
|
|
|
-include("ejabberd_http.hrl").
|
2018-05-14 18:30:21 +02:00
|
|
|
-include_lib("kernel/include/file.hrl").
|
2004-03-02 22:16:55 +01:00
|
|
|
|
|
|
|
-record(state, {sockmod,
|
|
|
|
socket,
|
|
|
|
request_method,
|
2004-07-06 23:34:50 +02:00
|
|
|
request_version,
|
2004-03-02 22:16:55 +01:00
|
|
|
request_path,
|
|
|
|
request_auth,
|
2004-09-30 23:54:39 +02:00
|
|
|
request_keepalive,
|
2018-05-14 18:30:21 +02:00
|
|
|
request_content_length = 0,
|
2014-04-06 00:39:51 +02:00
|
|
|
request_lang = <<"en">>,
|
2007-01-25 06:53:58 +01:00
|
|
|
%% XXX bard: request handlers are configured in
|
|
|
|
%% ejabberd.cfg under the HTTP service. For example,
|
|
|
|
%% to have the module test_web handle requests with
|
|
|
|
%% paths starting with "/test/module":
|
|
|
|
%%
|
2015-04-15 10:47:10 +02:00
|
|
|
%% {5280, ejabberd_http, [http_bind, web_admin,
|
2007-01-25 06:53:58 +01:00
|
|
|
%% {request_handlers, [{["test", "module"], mod_test_web}]}]}
|
|
|
|
%%
|
|
|
|
request_handlers = [],
|
2008-09-12 13:45:16 +02:00
|
|
|
request_host,
|
|
|
|
request_port,
|
|
|
|
request_tp,
|
|
|
|
request_headers = [],
|
2004-09-25 22:52:20 +02:00
|
|
|
end_of_request = false,
|
2014-10-03 16:53:55 +02:00
|
|
|
options = [],
|
2012-02-14 11:35:17 +01:00
|
|
|
default_host,
|
2017-03-27 23:19:11 +02:00
|
|
|
custom_headers,
|
2017-02-24 10:25:16 +01:00
|
|
|
trail = <<>>,
|
|
|
|
addr_re
|
2004-03-02 22:16:55 +01:00
|
|
|
}).
|
|
|
|
|
|
|
|
-define(XHTML_DOCTYPE,
|
2013-03-14 10:33:02 +01:00
|
|
|
<<"<?xml version='1.0'?>\n<!DOCTYPE html "
|
|
|
|
"PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//"
|
|
|
|
"EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1"
|
|
|
|
"-transitional.dtd\">\n">>).
|
2004-03-02 22:16:55 +01:00
|
|
|
|
2004-04-26 17:38:07 +02:00
|
|
|
-define(HTML_DOCTYPE,
|
2013-03-14 10:33:02 +01:00
|
|
|
<<"<!DOCTYPE html PUBLIC \"-//W3C//DTD "
|
|
|
|
"XHTML 1.0 Transitional//EN\" \"http://www.w3."
|
|
|
|
"org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
|
|
|
|
"">>).
|
2004-03-02 22:16:55 +01:00
|
|
|
|
2018-05-14 18:30:21 +02:00
|
|
|
-define(RECV_BUF, 65536).
|
|
|
|
-define(SEND_BUF, 65536).
|
|
|
|
-define(MAX_POST_SIZE, 20971520). %% 20Mb
|
|
|
|
|
2004-03-02 22:16:55 +01:00
|
|
|
start(SockData, Opts) ->
|
2015-12-09 15:40:20 +01:00
|
|
|
{ok,
|
|
|
|
proc_lib:spawn(ejabberd_http, init,
|
|
|
|
[SockData, Opts])}.
|
2004-03-02 22:16:55 +01:00
|
|
|
|
2009-12-07 18:33:02 +01:00
|
|
|
start_link(SockData, Opts) ->
|
2013-03-14 10:33:02 +01:00
|
|
|
{ok,
|
|
|
|
proc_lib:spawn_link(ejabberd_http, init,
|
|
|
|
[SockData, Opts])}.
|
2009-12-07 18:33:02 +01:00
|
|
|
|
|
|
|
init({SockMod, Socket}, Opts) ->
|
2013-08-12 14:25:05 +02:00
|
|
|
TLSEnabled = proplists:get_bool(tls, Opts),
|
2017-12-24 10:27:51 +01:00
|
|
|
TLSOpts1 = lists:filter(fun ({ciphers, _}) -> true;
|
2015-05-26 21:06:04 +02:00
|
|
|
({dhfile, _}) -> true;
|
2017-04-30 18:01:47 +02:00
|
|
|
({protocol_options, _}) -> true;
|
2013-03-14 10:33:02 +01:00
|
|
|
(_) -> false
|
|
|
|
end,
|
|
|
|
Opts),
|
2017-04-30 18:01:47 +02:00
|
|
|
TLSOpts2 = case proplists:get_bool(tls_compression, Opts) of
|
|
|
|
false -> [compression_none | TLSOpts1];
|
|
|
|
true -> TLSOpts1
|
2013-07-17 14:28:23 +02:00
|
|
|
end,
|
2017-12-24 10:27:51 +01:00
|
|
|
TLSOpts3 = case get_certfile(Opts) of
|
|
|
|
undefined -> TLSOpts2;
|
|
|
|
CertFile -> [{certfile, CertFile}|TLSOpts2]
|
|
|
|
end,
|
|
|
|
TLSOpts = [verify_none | TLSOpts3],
|
2013-03-14 10:33:02 +01:00
|
|
|
{SockMod1, Socket1} = if TLSEnabled ->
|
2018-05-14 18:30:21 +02:00
|
|
|
inet:setopts(Socket, [{recbuf, ?RECV_BUF}]),
|
2016-02-03 16:13:16 +01:00
|
|
|
{ok, TLSSocket} = fast_tls:tcp_to_tls(Socket,
|
2013-03-14 10:33:02 +01:00
|
|
|
TLSOpts),
|
2016-02-03 16:13:16 +01:00
|
|
|
{fast_tls, TLSSocket};
|
2013-03-14 10:33:02 +01:00
|
|
|
true -> {SockMod, Socket}
|
|
|
|
end,
|
2013-09-22 12:18:56 +02:00
|
|
|
Captcha = case proplists:get_bool(captcha, Opts) of
|
2013-03-14 10:33:02 +01:00
|
|
|
true -> [{[<<"captcha">>], ejabberd_captcha}];
|
|
|
|
false -> []
|
|
|
|
end,
|
2013-09-22 12:18:56 +02:00
|
|
|
Register = case proplists:get_bool(register, Opts) of
|
2013-03-14 10:33:02 +01:00
|
|
|
true -> [{[<<"register">>], mod_register_web}];
|
|
|
|
false -> []
|
|
|
|
end,
|
2013-09-22 12:18:56 +02:00
|
|
|
Admin = case proplists:get_bool(web_admin, Opts) of
|
2013-03-14 10:33:02 +01:00
|
|
|
true -> [{[<<"admin">>], ejabberd_web_admin}];
|
|
|
|
false -> []
|
|
|
|
end,
|
2013-09-22 12:18:56 +02:00
|
|
|
Bind = case proplists:get_bool(http_bind, Opts) of
|
2017-01-02 15:53:25 +01:00
|
|
|
true -> [{[<<"http-bind">>], mod_bosh}];
|
2013-03-14 10:33:02 +01:00
|
|
|
false -> []
|
|
|
|
end,
|
2014-10-03 16:53:55 +02:00
|
|
|
XMLRPC = case proplists:get_bool(xmlrpc, Opts) of
|
|
|
|
true -> [{[], ejabberd_xmlrpc}];
|
|
|
|
false -> []
|
|
|
|
end,
|
2017-05-08 13:34:35 +02:00
|
|
|
DefinedHandlers = proplists:get_value(request_handlers, Opts, []),
|
2013-03-14 10:33:02 +01:00
|
|
|
RequestHandlers = DefinedHandlers ++ Captcha ++ Register ++
|
2015-04-15 10:47:10 +02:00
|
|
|
Admin ++ Bind ++ XMLRPC,
|
2007-01-25 06:53:58 +01:00
|
|
|
?DEBUG("S: ~p~n", [RequestHandlers]),
|
|
|
|
|
2017-05-08 13:34:35 +02:00
|
|
|
DefaultHost = proplists:get_value(default_host, Opts),
|
2017-02-24 10:25:16 +01:00
|
|
|
{ok, RE} = re:compile(<<"^(?:\\[(.*?)\\]|(.*?))(?::(\\d+))?$">>),
|
2012-02-14 11:35:17 +01:00
|
|
|
|
2017-05-08 13:34:35 +02:00
|
|
|
CustomHeaders = proplists:get_value(custom_headers, Opts, []),
|
2017-03-27 23:19:11 +02:00
|
|
|
|
2009-12-08 18:32:46 +01:00
|
|
|
State = #state{sockmod = SockMod1,
|
|
|
|
socket = Socket1,
|
2012-02-14 11:35:17 +01:00
|
|
|
default_host = DefaultHost,
|
2017-03-27 23:19:11 +02:00
|
|
|
custom_headers = CustomHeaders,
|
2014-10-03 16:53:55 +02:00
|
|
|
options = Opts,
|
2017-02-24 10:25:16 +01:00
|
|
|
request_handlers = RequestHandlers,
|
|
|
|
addr_re = RE},
|
2015-08-17 15:50:02 +02:00
|
|
|
try receive_headers(State) of
|
|
|
|
V -> V
|
|
|
|
catch
|
|
|
|
{error, _} -> State
|
|
|
|
end.
|
2004-03-02 22:16:55 +01:00
|
|
|
|
2015-10-07 00:06:58 +02:00
|
|
|
become_controller(_Pid) ->
|
|
|
|
ok.
|
2006-01-13 02:55:20 +01:00
|
|
|
|
2006-09-25 05:51:11 +02:00
|
|
|
socket_type() ->
|
|
|
|
raw.
|
|
|
|
|
2018-05-14 18:30:21 +02:00
|
|
|
send_text(_State, none) ->
|
|
|
|
ok;
|
2004-03-02 22:16:55 +01:00
|
|
|
send_text(State, Text) ->
|
2018-05-14 18:30:21 +02:00
|
|
|
case (State#state.sockmod):send(State#state.socket, Text) of
|
|
|
|
ok -> ok;
|
|
|
|
{error, timeout} ->
|
|
|
|
?INFO_MSG("Timeout on ~p:send", [State#state.sockmod]),
|
|
|
|
exit(normal);
|
|
|
|
Error ->
|
|
|
|
?DEBUG("Error in ~p:send: ~p",
|
|
|
|
[State#state.sockmod, Error]),
|
|
|
|
exit(normal)
|
|
|
|
end.
|
|
|
|
|
|
|
|
send_file(State, Fd, Size, FileName) ->
|
|
|
|
try
|
|
|
|
case State#state.sockmod of
|
|
|
|
gen_tcp ->
|
|
|
|
case file:sendfile(Fd, State#state.socket, 0, Size, []) of
|
|
|
|
{ok, _} -> ok
|
|
|
|
end;
|
|
|
|
_ ->
|
|
|
|
case file:read(Fd, ?SEND_BUF) of
|
|
|
|
{ok, Data} ->
|
|
|
|
send_text(State, Data),
|
|
|
|
send_file(State, Fd, Size, FileName);
|
|
|
|
eof ->
|
|
|
|
ok
|
|
|
|
end
|
|
|
|
end
|
|
|
|
catch _:{case_clause, {error, Why}} ->
|
|
|
|
if Why /= closed ->
|
|
|
|
?INFO_MSG("Failed to read ~s: ~s",
|
|
|
|
[FileName, file_format_error(Why)]),
|
|
|
|
exit(normal);
|
|
|
|
true ->
|
|
|
|
ok
|
|
|
|
end
|
2009-10-19 11:36:23 +02:00
|
|
|
end.
|
2004-03-02 22:16:55 +01:00
|
|
|
|
2013-03-14 10:33:02 +01:00
|
|
|
receive_headers(#state{trail = Trail} = State) ->
|
2004-04-26 17:38:07 +02:00
|
|
|
SockMod = State#state.sockmod,
|
|
|
|
Socket = State#state.socket,
|
|
|
|
Data = SockMod:recv(Socket, 0, 300000),
|
2015-02-25 14:36:48 +01:00
|
|
|
case Data of
|
|
|
|
{error, _} -> ok;
|
|
|
|
{ok, D} ->
|
|
|
|
parse_headers(State#state{trail = <<Trail/binary, D/binary>>})
|
2012-04-06 16:22:08 +02:00
|
|
|
end.
|
|
|
|
|
|
|
|
parse_headers(#state{trail = <<>>} = State) ->
|
|
|
|
receive_headers(State);
|
2013-03-14 10:33:02 +01:00
|
|
|
parse_headers(#state{request_method = Method,
|
|
|
|
trail = Data} =
|
|
|
|
State) ->
|
2012-04-06 16:22:08 +02:00
|
|
|
PktType = case Method of
|
2015-10-07 00:06:58 +02:00
|
|
|
undefined -> http_bin;
|
|
|
|
_ -> httph_bin
|
|
|
|
end,
|
2013-06-24 12:04:56 +02:00
|
|
|
case erlang:decode_packet(PktType, Data, []) of
|
2015-10-07 00:06:58 +02:00
|
|
|
{ok, Pkt, Rest} ->
|
|
|
|
NewState = process_header(State#state{trail = Rest}, {ok, Pkt}),
|
2012-04-06 16:22:08 +02:00
|
|
|
case NewState#state.end_of_request of
|
2015-10-07 00:06:58 +02:00
|
|
|
true -> ok;
|
|
|
|
_ -> parse_headers(NewState)
|
2012-04-06 16:22:08 +02:00
|
|
|
end;
|
2015-10-07 00:06:58 +02:00
|
|
|
{more, _} ->
|
|
|
|
receive_headers(State#state{trail = Data});
|
|
|
|
_ ->
|
|
|
|
ok
|
2004-09-25 22:52:20 +02:00
|
|
|
end.
|
|
|
|
|
|
|
|
process_header(State, Data) ->
|
|
|
|
SockMod = State#state.sockmod,
|
|
|
|
Socket = State#state.socket,
|
2004-03-02 22:16:55 +01:00
|
|
|
case Data of
|
2013-03-14 10:33:02 +01:00
|
|
|
{ok, {http_request, Method, Uri, Version}} ->
|
|
|
|
KeepAlive = case Version of
|
|
|
|
{1, 1} -> true;
|
|
|
|
_ -> false
|
|
|
|
end,
|
|
|
|
Path = case Uri of
|
|
|
|
{absoluteURI, _Scheme, _Host, _Port, P} ->
|
|
|
|
{abs_path, P};
|
|
|
|
{abs_path, P} ->
|
|
|
|
{abs_path, P};
|
|
|
|
_ -> Uri
|
|
|
|
end,
|
|
|
|
State#state{request_method = Method,
|
|
|
|
request_version = Version, request_path = Path,
|
|
|
|
request_keepalive = KeepAlive};
|
|
|
|
{ok, {http_header, _, 'Connection' = Name, _, Conn}} ->
|
2017-04-11 12:13:58 +02:00
|
|
|
KeepAlive1 = case misc:tolower(Conn) of
|
2013-03-14 10:33:02 +01:00
|
|
|
<<"keep-alive">> -> true;
|
|
|
|
<<"close">> -> false;
|
|
|
|
_ -> State#state.request_keepalive
|
|
|
|
end,
|
|
|
|
State#state{request_keepalive = KeepAlive1,
|
|
|
|
request_headers = add_header(Name, Conn, State)};
|
|
|
|
{ok,
|
|
|
|
{http_header, _, 'Authorization' = Name, _, Auth}} ->
|
|
|
|
State#state{request_auth = parse_auth(Auth),
|
|
|
|
request_headers = add_header(Name, Auth, State)};
|
|
|
|
{ok,
|
|
|
|
{http_header, _, 'Content-Length' = Name, _, SLen}} ->
|
2016-09-24 22:34:28 +02:00
|
|
|
case catch binary_to_integer(SLen) of
|
2013-03-14 10:33:02 +01:00
|
|
|
Len when is_integer(Len) ->
|
|
|
|
State#state{request_content_length = Len,
|
|
|
|
request_headers = add_header(Name, SLen, State)};
|
|
|
|
_ -> State
|
|
|
|
end;
|
|
|
|
{ok,
|
|
|
|
{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}} ->
|
|
|
|
State#state{request_host = Host,
|
|
|
|
request_headers = add_header(Name, Host, State)};
|
2015-02-25 14:36:48 +01:00
|
|
|
{ok, {http_header, _, Name, _, Value}} when is_binary(Name) ->
|
2015-10-07 00:06:58 +02:00
|
|
|
State#state{request_headers =
|
|
|
|
add_header(normalize_header_name(Name), Value, State)};
|
2013-03-14 10:33:02 +01:00
|
|
|
{ok, {http_header, _, Name, _, Value}} ->
|
|
|
|
State#state{request_headers =
|
|
|
|
add_header(Name, Value, State)};
|
|
|
|
{ok, http_eoh}
|
|
|
|
when State#state.request_host == undefined ->
|
2017-10-11 17:53:53 +02:00
|
|
|
?DEBUG("An HTTP request without 'Host' HTTP "
|
|
|
|
"header was received.", []),
|
|
|
|
{State1, Out} = process_request(State),
|
|
|
|
send_text(State1, Out),
|
|
|
|
process_header(State, {ok, {http_error, <<>>}});
|
2013-03-14 10:33:02 +01:00
|
|
|
{ok, http_eoh} ->
|
2015-02-11 15:38:55 +01:00
|
|
|
?DEBUG("(~w) http query: ~w ~p~n",
|
2013-03-14 10:33:02 +01:00
|
|
|
[State#state.socket, State#state.request_method,
|
|
|
|
element(2, State#state.request_path)]),
|
|
|
|
{HostProvided, Port, TP} =
|
2017-02-24 10:25:16 +01:00
|
|
|
get_transfer_protocol(State#state.addr_re, SockMod,
|
2013-03-14 10:33:02 +01:00
|
|
|
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},
|
2015-11-12 17:23:39 +01:00
|
|
|
{State3, Out} = process_request(State2),
|
|
|
|
send_text(State3, Out),
|
|
|
|
case State3#state.request_keepalive of
|
2013-03-14 10:33:02 +01:00
|
|
|
true ->
|
|
|
|
#state{sockmod = SockMod, socket = Socket,
|
2015-11-12 17:23:39 +01:00
|
|
|
trail = State3#state.trail,
|
2015-01-15 17:39:12 +01:00
|
|
|
options = State#state.options,
|
2015-03-06 12:40:48 +01:00
|
|
|
default_host = State#state.default_host,
|
2017-03-27 23:19:11 +02:00
|
|
|
custom_headers = State#state.custom_headers,
|
2017-02-24 11:25:10 +01:00
|
|
|
request_handlers = State#state.request_handlers,
|
|
|
|
addr_re = State#state.addr_re};
|
2013-03-14 10:33:02 +01:00
|
|
|
_ ->
|
|
|
|
#state{end_of_request = true,
|
2015-11-12 17:23:39 +01:00
|
|
|
trail = State3#state.trail,
|
2015-01-15 17:39:12 +01:00
|
|
|
options = State#state.options,
|
2015-03-06 12:40:48 +01:00
|
|
|
default_host = State#state.default_host,
|
2017-03-27 23:19:11 +02:00
|
|
|
custom_headers = State#state.custom_headers,
|
2017-02-24 11:25:10 +01:00
|
|
|
request_handlers = State#state.request_handlers,
|
|
|
|
addr_re = State#state.addr_re}
|
2013-03-14 10:33:02 +01:00
|
|
|
end;
|
|
|
|
_ ->
|
|
|
|
#state{end_of_request = true,
|
2015-01-15 17:39:12 +01:00
|
|
|
options = State#state.options,
|
2015-03-06 12:40:48 +01:00
|
|
|
default_host = State#state.default_host,
|
2017-03-27 23:19:11 +02:00
|
|
|
custom_headers = State#state.custom_headers,
|
2017-02-24 11:25:10 +01:00
|
|
|
request_handlers = State#state.request_handlers,
|
|
|
|
addr_re = State#state.addr_re}
|
2007-01-25 06:53:58 +01:00
|
|
|
end.
|
|
|
|
|
2013-03-14 10:33:02 +01:00
|
|
|
add_header(Name, Value, State)->
|
2008-11-12 10:58:28 +01:00
|
|
|
[{Name, Value} | State#state.request_headers].
|
|
|
|
|
2012-02-14 11:35:17 +01:00
|
|
|
get_host_really_served(undefined, Provided) ->
|
|
|
|
Provided;
|
|
|
|
get_host_really_served(Default, Provided) ->
|
2016-12-29 22:00:36 +01:00
|
|
|
case ejabberd_router:is_my_host(Provided) of
|
2013-03-14 10:33:02 +01:00
|
|
|
true -> Provided;
|
|
|
|
false -> Default
|
2012-02-14 11:35:17 +01:00
|
|
|
end.
|
|
|
|
|
2017-02-24 10:25:16 +01:00
|
|
|
get_transfer_protocol(RE, SockMod, HostPort) ->
|
|
|
|
{Proto, DefPort} = case SockMod of
|
|
|
|
gen_tcp -> {http, 80};
|
|
|
|
fast_tls -> {https, 443}
|
|
|
|
end,
|
|
|
|
{Host, Port} = case re:run(HostPort, RE, [{capture,[1,2,3],binary}]) of
|
|
|
|
nomatch ->
|
|
|
|
{<<"0.0.0.0">>, DefPort};
|
|
|
|
{match, [<<>>, H, <<>>]} ->
|
|
|
|
{H, DefPort};
|
|
|
|
{match, [H, <<>>, <<>>]} ->
|
|
|
|
{H, DefPort};
|
|
|
|
{match, [<<>>, H, PortStr]} ->
|
|
|
|
{H, binary_to_integer(PortStr)};
|
|
|
|
{match, [H, <<>>, PortStr]} ->
|
|
|
|
{H, binary_to_integer(PortStr)}
|
|
|
|
end,
|
|
|
|
|
|
|
|
{Host, Port, Proto}.
|
2008-09-12 13:45:16 +02:00
|
|
|
|
2007-01-25 06:53:58 +01:00
|
|
|
%% XXX bard: search through request handlers looking for one that
|
|
|
|
%% matches the requested URL path, and pass control to it. If none is
|
|
|
|
%% found, answer with HTTP 404.
|
2015-02-25 10:42:59 +01:00
|
|
|
|
2018-05-14 18:30:21 +02:00
|
|
|
process([], _) -> ejabberd_web:error(not_found);
|
|
|
|
process(Handlers, Request) ->
|
2015-02-25 10:42:59 +01:00
|
|
|
{HandlerPathPrefix, HandlerModule, HandlerOpts, HandlersLeft} =
|
|
|
|
case Handlers of
|
|
|
|
[{Pfx, Mod} | Tail] ->
|
|
|
|
{Pfx, Mod, [], Tail};
|
|
|
|
[{Pfx, Mod, Opts} | Tail] ->
|
|
|
|
{Pfx, Mod, Opts, Tail}
|
|
|
|
end,
|
|
|
|
|
|
|
|
case (lists:prefix(HandlerPathPrefix, Request#request.path) or
|
|
|
|
(HandlerPathPrefix==Request#request.path)) of
|
|
|
|
true ->
|
|
|
|
?DEBUG("~p matches ~p", [Request#request.path, HandlerPathPrefix]),
|
|
|
|
%% LocalPath is the path "local to the handler", i.e. if
|
|
|
|
%% the handler was registered to handle "/test/" and the
|
|
|
|
%% requested path is "/test/foo/bar", the local path is
|
|
|
|
%% ["foo", "bar"]
|
|
|
|
LocalPath = lists:nthtail(length(HandlerPathPrefix), Request#request.path),
|
|
|
|
R = try
|
|
|
|
HandlerModule:socket_handoff(
|
2018-05-14 18:30:21 +02:00
|
|
|
LocalPath, Request, HandlerOpts)
|
2015-02-25 10:42:59 +01:00
|
|
|
catch error:undef ->
|
|
|
|
HandlerModule:process(LocalPath, Request)
|
|
|
|
end,
|
|
|
|
ejabberd_hooks:run(http_request_debug, [{LocalPath, Request}]),
|
|
|
|
R;
|
|
|
|
false ->
|
2018-05-14 18:30:21 +02:00
|
|
|
process(HandlersLeft, Request)
|
2004-03-02 22:16:55 +01:00
|
|
|
end.
|
|
|
|
|
2015-02-25 10:42:59 +01:00
|
|
|
extract_path_query(#state{request_method = Method,
|
2015-11-12 17:23:39 +01:00
|
|
|
request_path = {abs_path, Path}} = State)
|
2015-02-25 10:42:59 +01:00
|
|
|
when Method =:= 'GET' orelse
|
|
|
|
Method =:= 'HEAD' orelse
|
|
|
|
Method =:= 'DELETE' orelse Method =:= 'OPTIONS' ->
|
|
|
|
case catch url_decode_q_split(Path) of
|
2015-11-12 17:23:39 +01:00
|
|
|
{'EXIT', _} -> {State, false};
|
|
|
|
{NPath, Query} ->
|
|
|
|
LPath = normalize_path([NPE
|
|
|
|
|| NPE <- str:tokens(path_decode(NPath), <<"/">>)]),
|
|
|
|
LQuery = case catch parse_urlencoded(Query) of
|
|
|
|
{'EXIT', _Reason} -> [];
|
|
|
|
LQ -> LQ
|
|
|
|
end,
|
|
|
|
{State, {LPath, LQuery, <<"">>}}
|
2015-02-25 10:42:59 +01:00
|
|
|
end;
|
|
|
|
extract_path_query(#state{request_method = Method,
|
|
|
|
request_path = {abs_path, Path},
|
|
|
|
request_content_length = Len,
|
2018-05-14 18:30:21 +02:00
|
|
|
trail = Trail,
|
2015-02-25 10:42:59 +01:00
|
|
|
sockmod = _SockMod,
|
|
|
|
socket = _Socket} = State)
|
2018-05-14 18:30:21 +02:00
|
|
|
when (Method =:= 'POST' orelse Method =:= 'PUT') andalso Len>0 ->
|
2015-02-25 10:42:59 +01:00
|
|
|
case catch url_decode_q_split(Path) of
|
2018-05-14 18:30:21 +02:00
|
|
|
{'EXIT', _} -> {State, false};
|
2015-11-12 17:23:39 +01:00
|
|
|
{NPath, _Query} ->
|
2018-05-14 18:30:21 +02:00
|
|
|
LPath = normalize_path(
|
|
|
|
[NPE || NPE <- str:tokens(path_decode(NPath), <<"/">>)]),
|
|
|
|
case Method of
|
|
|
|
'PUT' ->
|
|
|
|
{State, {LPath, [], Trail}};
|
|
|
|
'POST' ->
|
|
|
|
case recv_data(State) of
|
|
|
|
{ok, Data} ->
|
|
|
|
LQuery = case catch parse_urlencoded(Data) of
|
|
|
|
{'EXIT', _Reason} -> [];
|
|
|
|
LQ -> LQ
|
|
|
|
end,
|
|
|
|
{State, {LPath, LQuery, Data}};
|
|
|
|
error ->
|
|
|
|
{State, false}
|
|
|
|
end
|
2016-09-27 23:22:30 +02:00
|
|
|
end
|
2015-02-25 10:42:59 +01:00
|
|
|
end;
|
2015-11-12 17:23:39 +01:00
|
|
|
extract_path_query(State) ->
|
|
|
|
{State, false}.
|
2015-02-25 10:42:59 +01:00
|
|
|
|
2017-10-11 17:53:53 +02:00
|
|
|
process_request(#state{request_host = undefined,
|
|
|
|
custom_headers = CustomHeaders} = State) ->
|
|
|
|
{State, make_text_output(State, 400, CustomHeaders,
|
|
|
|
<<"Missing Host header">>)};
|
2015-02-25 10:42:59 +01:00
|
|
|
process_request(#state{request_method = Method,
|
2015-10-07 00:06:58 +02:00
|
|
|
request_auth = Auth,
|
|
|
|
request_lang = Lang,
|
2018-05-23 14:52:47 +02:00
|
|
|
request_version = Version,
|
2015-10-07 00:06:58 +02:00
|
|
|
sockmod = SockMod,
|
|
|
|
socket = Socket,
|
|
|
|
options = Options,
|
|
|
|
request_host = Host,
|
|
|
|
request_port = Port,
|
|
|
|
request_tp = TP,
|
2018-05-14 18:30:21 +02:00
|
|
|
request_content_length = Length,
|
2015-10-07 00:06:58 +02:00
|
|
|
request_headers = RequestHeaders,
|
|
|
|
request_handlers = RequestHandlers,
|
2018-05-14 18:30:21 +02:00
|
|
|
custom_headers = CustomHeaders} = State) ->
|
2018-05-23 14:52:47 +02:00
|
|
|
case proplists:get_value(<<"Expect">>, RequestHeaders, <<>>) of
|
|
|
|
<<"100-", _/binary>> when Version == {1, 1} ->
|
|
|
|
send_text(State, <<"HTTP/1.1 100 Continue\r\n\r\n">>);
|
|
|
|
_ ->
|
|
|
|
ok
|
|
|
|
end,
|
2015-02-25 10:42:59 +01:00
|
|
|
case extract_path_query(State) of
|
2015-11-12 17:23:39 +01:00
|
|
|
{State2, false} ->
|
|
|
|
{State2, make_bad_request(State)};
|
|
|
|
{State2, {LPath, LQuery, Data}} ->
|
2015-08-17 15:50:02 +02:00
|
|
|
PeerName =
|
2007-05-07 17:47:33 +02:00
|
|
|
case SockMod of
|
|
|
|
gen_tcp ->
|
|
|
|
inet:peername(Socket);
|
|
|
|
_ ->
|
|
|
|
SockMod:peername(Socket)
|
|
|
|
end,
|
2015-08-17 15:50:02 +02:00
|
|
|
IPHere = case PeerName of
|
|
|
|
{ok, V} -> V;
|
|
|
|
{error, _} = E -> throw(E)
|
|
|
|
end,
|
2010-12-07 16:47:32 +01:00
|
|
|
XFF = proplists:get_value('X-Forwarded-For', RequestHeaders, []),
|
|
|
|
IP = analyze_ip_xff(IPHere, XFF, Host),
|
2015-02-25 10:42:59 +01:00
|
|
|
Request = #request{method = Method,
|
|
|
|
path = LPath,
|
|
|
|
q = LQuery,
|
|
|
|
auth = Auth,
|
2018-05-14 18:30:21 +02:00
|
|
|
length = Length,
|
|
|
|
sockmod = SockMod,
|
|
|
|
socket = Socket,
|
|
|
|
data = Data,
|
2015-02-25 10:42:59 +01:00
|
|
|
lang = Lang,
|
|
|
|
host = Host,
|
|
|
|
port = Port,
|
|
|
|
tp = TP,
|
2014-10-03 16:53:55 +02:00
|
|
|
opts = Options,
|
2015-02-25 10:42:59 +01:00
|
|
|
headers = RequestHeaders,
|
|
|
|
ip = IP},
|
2017-11-15 08:01:30 +01:00
|
|
|
RequestHandlers1 = ejabberd_hooks:run_fold(
|
|
|
|
http_request_handlers, RequestHandlers, [Host, Request]),
|
2018-05-14 18:30:21 +02:00
|
|
|
Res = case process(RequestHandlers1, Request) of
|
2015-11-12 17:23:39 +01:00
|
|
|
El when is_record(El, xmlel) ->
|
2017-03-27 23:19:11 +02:00
|
|
|
make_xhtml_output(State, 200, CustomHeaders, El);
|
2015-11-12 17:23:39 +01:00
|
|
|
{Status, Headers, El}
|
|
|
|
when is_record(El, xmlel) ->
|
2017-03-27 23:19:11 +02:00
|
|
|
make_xhtml_output(State, Status,
|
|
|
|
Headers ++ CustomHeaders, El);
|
2015-11-12 17:23:39 +01:00
|
|
|
Output when is_binary(Output) or is_list(Output) ->
|
2017-03-27 23:19:11 +02:00
|
|
|
make_text_output(State, 200, CustomHeaders, Output);
|
2015-11-12 17:23:39 +01:00
|
|
|
{Status, Headers, Output}
|
|
|
|
when is_binary(Output) or is_list(Output) ->
|
2017-03-27 23:19:11 +02:00
|
|
|
make_text_output(State, Status,
|
|
|
|
Headers ++ CustomHeaders, Output);
|
2018-05-14 18:30:21 +02:00
|
|
|
{Status, Headers, {file, FileName}} ->
|
|
|
|
make_file_output(State, Status, Headers, FileName);
|
2015-11-12 17:23:39 +01:00
|
|
|
{Status, Reason, Headers, Output}
|
|
|
|
when is_binary(Output) or is_list(Output) ->
|
2017-03-27 23:19:11 +02:00
|
|
|
make_text_output(State, Status, Reason,
|
|
|
|
Headers ++ CustomHeaders, Output);
|
2015-11-12 17:23:39 +01:00
|
|
|
_ ->
|
|
|
|
none
|
|
|
|
end,
|
2018-05-15 14:25:19 +02:00
|
|
|
{State2#state{trail = <<>>}, Res}
|
2015-02-25 10:42:59 +01:00
|
|
|
end.
|
2011-09-05 08:31:58 +02:00
|
|
|
|
|
|
|
make_bad_request(State) ->
|
2017-03-27 23:19:11 +02:00
|
|
|
make_xhtml_output(State, 400, State#state.custom_headers,
|
2013-03-14 10:33:02 +01:00
|
|
|
ejabberd_web:make_xhtml([#xmlel{name = <<"h1">>,
|
|
|
|
attrs = [],
|
|
|
|
children =
|
|
|
|
[{xmlcdata,
|
|
|
|
<<"400 Bad Request">>}]}])).
|
|
|
|
|
|
|
|
analyze_ip_xff(IP, [], _Host) -> IP;
|
2010-12-07 16:47:32 +01:00
|
|
|
analyze_ip_xff({IPLast, Port}, XFF, Host) ->
|
2013-03-14 10:33:02 +01:00
|
|
|
[ClientIP | ProxiesIPs] = str:tokens(XFF, <<", ">>) ++
|
2017-04-11 12:13:58 +02:00
|
|
|
[misc:ip_to_list(IPLast)],
|
2017-04-29 10:39:40 +02:00
|
|
|
TrustedProxies = ejabberd_config:get_option({trusted_proxies, Host}, []),
|
2013-03-14 10:33:02 +01:00
|
|
|
IPClient = case is_ipchain_trusted(ProxiesIPs,
|
|
|
|
TrustedProxies)
|
|
|
|
of
|
|
|
|
true ->
|
|
|
|
{ok, IPFirst} = inet_parse:address(
|
|
|
|
binary_to_list(ClientIP)),
|
|
|
|
?DEBUG("The IP ~w was replaced with ~w due to "
|
|
|
|
"header X-Forwarded-For: ~s",
|
|
|
|
[IPLast, IPFirst, XFF]),
|
|
|
|
IPFirst;
|
|
|
|
false -> IPLast
|
2010-12-07 16:47:32 +01:00
|
|
|
end,
|
|
|
|
{IPClient, Port}.
|
2013-03-14 10:33:02 +01:00
|
|
|
|
2018-05-04 09:53:07 +02:00
|
|
|
is_ipchain_trusted([], _) -> false;
|
2013-03-14 10:33:02 +01:00
|
|
|
is_ipchain_trusted(_UserIPs, all) -> true;
|
2018-05-04 09:53:07 +02:00
|
|
|
is_ipchain_trusted(UserIPs, Masks) ->
|
|
|
|
lists:all(
|
|
|
|
fun(IP) ->
|
|
|
|
case inet:parse_address(binary_to_list(IP)) of
|
|
|
|
{ok, IP2} ->
|
|
|
|
lists:any(
|
|
|
|
fun({Mask, MaskLen}) ->
|
|
|
|
acl:ip_matches_mask(IP2, Mask, MaskLen)
|
|
|
|
end, [{{127,0,0,1}, 8} | Masks]);
|
|
|
|
_ ->
|
|
|
|
false
|
|
|
|
end
|
|
|
|
end, UserIPs).
|
2004-03-02 22:16:55 +01:00
|
|
|
|
2018-05-14 18:30:21 +02:00
|
|
|
recv_data(#state{request_content_length = Len}) when Len >= ?MAX_POST_SIZE ->
|
|
|
|
error;
|
|
|
|
recv_data(#state{request_content_length = Len, trail = Trail,
|
|
|
|
sockmod = SockMod, socket = Socket}) ->
|
|
|
|
NewLen = Len - byte_size(Trail),
|
|
|
|
if NewLen > 0 ->
|
|
|
|
case SockMod:recv(Socket, NewLen, 60000) of
|
|
|
|
{ok, Data} -> {ok, <<Trail/binary, Data/binary>>};
|
|
|
|
{error, _} -> error
|
2015-11-12 17:23:39 +01:00
|
|
|
end;
|
2018-05-14 18:30:21 +02:00
|
|
|
true ->
|
|
|
|
{ok, Trail}
|
2004-03-02 22:16:55 +01:00
|
|
|
end.
|
|
|
|
|
2018-05-14 18:30:21 +02:00
|
|
|
recv_file(#request{length = Len, data = Trail,
|
|
|
|
sockmod = SockMod, socket = Socket}, Path) ->
|
|
|
|
case file:open(Path, [write, exclusive, raw]) of
|
|
|
|
{ok, Fd} ->
|
|
|
|
case file:write(Fd, Trail) of
|
|
|
|
ok ->
|
|
|
|
NewLen = max(0, Len - byte_size(Trail)),
|
|
|
|
case do_recv_file(NewLen, SockMod, Socket, Fd) of
|
|
|
|
ok ->
|
|
|
|
ok;
|
|
|
|
{error, _} = Err ->
|
|
|
|
file:delete(Path),
|
|
|
|
Err
|
|
|
|
end;
|
|
|
|
{error, _} = Err ->
|
|
|
|
file:delete(Path),
|
|
|
|
Err
|
|
|
|
end;
|
|
|
|
{error, _} = Err ->
|
|
|
|
Err
|
|
|
|
end.
|
2013-03-14 10:33:02 +01:00
|
|
|
|
2018-05-14 18:30:21 +02:00
|
|
|
do_recv_file(0, _SockMod, _Socket, Fd) ->
|
|
|
|
file:close(Fd);
|
|
|
|
do_recv_file(Len, SockMod, Socket, Fd) ->
|
|
|
|
ChunkLen = min(Len, ?RECV_BUF),
|
|
|
|
try
|
|
|
|
{ok, Data} = SockMod:recv(Socket, ChunkLen, timer:seconds(30)),
|
|
|
|
ok = file:write(Fd, Data),
|
2018-05-19 17:29:33 +02:00
|
|
|
do_recv_file(Len-size(Data), SockMod, Socket, Fd)
|
2018-05-14 18:30:21 +02:00
|
|
|
catch _:{badmatch, {error, _} = Err} ->
|
|
|
|
file:close(Fd),
|
|
|
|
Err
|
|
|
|
end.
|
2013-03-14 10:33:02 +01:00
|
|
|
|
2018-05-14 18:30:21 +02:00
|
|
|
make_headers(State, Status, Reason, Headers, Data) ->
|
|
|
|
Len = if is_integer(Data) -> Data;
|
|
|
|
true -> iolist_size(Data)
|
|
|
|
end,
|
|
|
|
Headers1 = [{<<"Content-Length">>, integer_to_binary(Len)} | Headers],
|
|
|
|
Headers2 = case lists:keyfind(<<"Content-Type">>, 1, Headers) of
|
|
|
|
{_, _} ->
|
|
|
|
Headers1;
|
|
|
|
false ->
|
|
|
|
[{<<"Content-Type">>, <<"text/html; charset=utf-8">>}
|
|
|
|
| Headers1]
|
2004-03-13 21:01:37 +01:00
|
|
|
end,
|
2005-05-23 01:29:54 +02:00
|
|
|
HeadersOut = case {State#state.request_version,
|
2018-05-14 18:30:21 +02:00
|
|
|
State#state.request_keepalive} of
|
|
|
|
{{1, 1}, true} -> Headers2;
|
|
|
|
{_, true} ->
|
|
|
|
[{<<"Connection">>, <<"keep-alive">>} | Headers2];
|
|
|
|
{_, false} ->
|
|
|
|
[{<<"Connection">>, <<"close">>} | Headers2]
|
2005-05-23 01:29:54 +02:00
|
|
|
end,
|
|
|
|
Version = case State#state.request_version of
|
2018-05-14 18:30:21 +02:00
|
|
|
{1, 1} -> <<"HTTP/1.1 ">>;
|
|
|
|
_ -> <<"HTTP/1.0 ">>
|
2005-05-23 01:29:54 +02:00
|
|
|
end,
|
2018-05-14 18:30:21 +02:00
|
|
|
H = [[Attr, <<": ">>, Val, <<"\r\n">>] || {Attr, Val} <- HeadersOut],
|
2013-03-14 10:33:02 +01:00
|
|
|
NewReason = case Reason of
|
|
|
|
<<"">> -> code_to_phrase(Status);
|
|
|
|
_ -> Reason
|
|
|
|
end,
|
|
|
|
SL = [Version,
|
2016-09-24 22:34:28 +02:00
|
|
|
integer_to_binary(Status), <<" ">>,
|
2013-03-14 10:33:02 +01:00
|
|
|
NewReason, <<"\r\n">>],
|
2018-05-14 18:30:21 +02:00
|
|
|
[SL, H, <<"\r\n">>].
|
|
|
|
|
|
|
|
make_xhtml_output(State, Status, Headers, XHTML) ->
|
|
|
|
Data = case State#state.request_method of
|
|
|
|
'HEAD' -> <<"">>;
|
|
|
|
_ ->
|
|
|
|
DocType = case lists:member(html, Headers) of
|
|
|
|
true -> ?HTML_DOCTYPE;
|
|
|
|
false -> ?XHTML_DOCTYPE
|
|
|
|
end,
|
|
|
|
iolist_to_binary([DocType, fxml:element_to_binary(XHTML)])
|
|
|
|
end,
|
|
|
|
EncodedHdrs = make_headers(State, Status, <<"">>, Headers, Data),
|
|
|
|
[EncodedHdrs, Data].
|
|
|
|
|
|
|
|
make_text_output(State, Status, Headers, Text) ->
|
|
|
|
make_text_output(State, Status, <<"">>, Headers, Text).
|
|
|
|
|
|
|
|
make_text_output(State, Status, Reason, Headers, Text) ->
|
|
|
|
Data = iolist_to_binary(Text),
|
2011-04-12 23:31:08 +02:00
|
|
|
Data2 = case State#state.request_method of
|
2018-05-14 18:30:21 +02:00
|
|
|
'HEAD' -> <<"">>;
|
|
|
|
_ -> Data
|
2013-03-14 10:33:02 +01:00
|
|
|
end,
|
2018-05-14 18:30:21 +02:00
|
|
|
EncodedHdrs = make_headers(State, Status, Reason, Headers, Data2),
|
|
|
|
[EncodedHdrs, Data2].
|
|
|
|
|
|
|
|
make_file_output(State, Status, Headers, FileName) ->
|
|
|
|
case file:read_file_info(FileName) of
|
|
|
|
{ok, #file_info{size = Size}} when State#state.request_method == 'HEAD' ->
|
|
|
|
make_headers(State, Status, <<"">>, Headers, Size);
|
|
|
|
{ok, #file_info{size = Size}} ->
|
|
|
|
case file:open(FileName, [raw, read]) of
|
|
|
|
{ok, Fd} ->
|
|
|
|
EncodedHdrs = make_headers(State, Status, <<"">>, Headers, Size),
|
|
|
|
send_text(State, EncodedHdrs),
|
|
|
|
send_file(State, Fd, Size, FileName),
|
|
|
|
file:close(Fd),
|
|
|
|
none;
|
|
|
|
{error, Why} ->
|
|
|
|
Reason = file_format_error(Why),
|
|
|
|
?ERROR_MSG("Failed to open ~s: ~s", [FileName, Reason]),
|
|
|
|
make_text_output(State, 404, Reason, [], <<>>)
|
|
|
|
end;
|
|
|
|
{error, Why} ->
|
|
|
|
Reason = file_format_error(Why),
|
|
|
|
?ERROR_MSG("Failed to read info of ~s: ~s", [FileName, Reason]),
|
|
|
|
make_text_output(State, 404, Reason, [], <<>>)
|
|
|
|
end.
|
2004-03-02 22:16:55 +01:00
|
|
|
|
2004-05-01 22:10:25 +02:00
|
|
|
parse_lang(Langs) ->
|
2013-03-14 10:33:02 +01:00
|
|
|
case str:tokens(Langs, <<",; ">>) of
|
|
|
|
[First | _] -> First;
|
|
|
|
[] -> <<"en">>
|
2004-05-01 22:10:25 +02:00
|
|
|
end.
|
|
|
|
|
2018-05-14 18:30:21 +02:00
|
|
|
file_format_error(Reason) ->
|
|
|
|
case file:format_error(Reason) of
|
|
|
|
"unknown POSIX error" -> atom_to_list(Reason);
|
|
|
|
Text -> Text
|
|
|
|
end.
|
|
|
|
|
2004-03-02 22:16:55 +01:00
|
|
|
% Code below is taken (with some modifications) from the yaws webserver, which
|
2018-01-27 17:35:38 +01:00
|
|
|
% is distributed under the following license:
|
2004-03-02 22:16:55 +01:00
|
|
|
%
|
|
|
|
% This software (the yaws webserver) is free software.
|
|
|
|
% Parts of this software is Copyright (c) Claes Wikstrom <klacke@hyber.org>
|
|
|
|
% Any use or misuse of the source code is hereby freely allowed.
|
|
|
|
%
|
|
|
|
% 1. Redistributions of source code must retain the above copyright
|
|
|
|
% notice as well as this list of conditions.
|
|
|
|
%
|
|
|
|
% 2. Redistributions in binary form must reproduce the above copyright
|
|
|
|
% notice as well as this list of conditions.
|
|
|
|
|
2008-11-12 11:03:27 +01:00
|
|
|
%% @doc Split the URL and return {Path, QueryPart}
|
2004-03-02 22:16:55 +01:00
|
|
|
url_decode_q_split(Path) ->
|
2013-03-14 10:33:02 +01:00
|
|
|
url_decode_q_split(Path, <<>>).
|
|
|
|
|
|
|
|
url_decode_q_split(<<$?, T/binary>>, Acc) ->
|
2004-03-02 22:16:55 +01:00
|
|
|
%% Don't decode the query string here, that is parsed separately.
|
2013-03-14 10:33:02 +01:00
|
|
|
{path_norm_reverse(Acc), T};
|
|
|
|
url_decode_q_split(<<H, T/binary>>, Acc) when H /= 0 ->
|
|
|
|
url_decode_q_split(T, <<H, Acc/binary>>);
|
|
|
|
url_decode_q_split(<<>>, Ack) ->
|
|
|
|
{path_norm_reverse(Ack), <<>>}.
|
2004-03-02 22:16:55 +01:00
|
|
|
|
2008-11-12 11:03:27 +01:00
|
|
|
%% @doc Decode a part of the URL and return string()
|
2013-03-14 10:33:02 +01:00
|
|
|
path_decode(Path) -> path_decode(Path, <<>>).
|
|
|
|
|
|
|
|
path_decode(<<$%, Hi, Lo, Tail/binary>>, Acc) ->
|
2018-04-03 00:21:33 +02:00
|
|
|
Hex = list_to_integer([Hi, Lo], 16),
|
2013-03-14 10:33:02 +01:00
|
|
|
if Hex == 0 -> exit(badurl);
|
2008-11-12 11:03:27 +01:00
|
|
|
true -> ok
|
|
|
|
end,
|
2013-03-14 10:33:02 +01:00
|
|
|
path_decode(Tail, <<Acc/binary, Hex>>);
|
|
|
|
path_decode(<<H, T/binary>>, Acc) when H /= 0 ->
|
|
|
|
path_decode(T, <<Acc/binary, H>>);
|
|
|
|
path_decode(<<>>, Acc) -> Acc.
|
|
|
|
|
|
|
|
path_norm_reverse(<<"/", T/binary>>) -> start_dir(0, <<"/">>, T);
|
|
|
|
path_norm_reverse(T) -> start_dir(0, <<"">>, T).
|
|
|
|
|
|
|
|
start_dir(N, Path, <<"..">>) -> rest_dir(N, Path, <<"">>);
|
|
|
|
start_dir(N, Path, <<"/", T/binary>>) -> start_dir(N, Path, T);
|
|
|
|
start_dir(N, Path, <<"./", T/binary>>) -> start_dir(N, Path, T);
|
|
|
|
start_dir(N, Path, <<"../", T/binary>>) ->
|
|
|
|
start_dir(N + 1, Path, T);
|
|
|
|
start_dir(N, Path, T) -> rest_dir(N, Path, T).
|
|
|
|
|
|
|
|
rest_dir(_N, Path, <<>>) ->
|
|
|
|
case Path of
|
|
|
|
<<>> -> <<"/">>;
|
|
|
|
_ -> Path
|
|
|
|
end;
|
|
|
|
rest_dir(0, Path, <<$/, T/binary>>) ->
|
|
|
|
start_dir(0, <<$/, Path/binary>>, T);
|
|
|
|
rest_dir(N, Path, <<$/, T/binary>>) ->
|
|
|
|
start_dir(N - 1, Path, T);
|
|
|
|
rest_dir(0, Path, <<H, T/binary>>) ->
|
|
|
|
rest_dir(0, <<H, Path/binary>>, T);
|
|
|
|
rest_dir(N, Path, <<_H, T/binary>>) -> rest_dir(N, Path, T).
|
2004-03-02 22:16:55 +01:00
|
|
|
|
2017-03-27 23:52:49 +02:00
|
|
|
expand_custom_headers(Headers) ->
|
|
|
|
lists:map(fun({K, V}) ->
|
2018-06-14 13:00:47 +02:00
|
|
|
{K, misc:expand_keyword(<<"@VERSION@">>, V,
|
|
|
|
ejabberd_config:get_version())}
|
2017-03-27 23:52:49 +02:00
|
|
|
end, Headers).
|
|
|
|
|
2013-03-14 10:33:02 +01:00
|
|
|
code_to_phrase(100) -> <<"Continue">>;
|
|
|
|
code_to_phrase(101) -> <<"Switching Protocols ">>;
|
|
|
|
code_to_phrase(200) -> <<"OK">>;
|
|
|
|
code_to_phrase(201) -> <<"Created">>;
|
|
|
|
code_to_phrase(202) -> <<"Accepted">>;
|
|
|
|
code_to_phrase(203) ->
|
|
|
|
<<"Non-Authoritative Information">>;
|
|
|
|
code_to_phrase(204) -> <<"No Content">>;
|
|
|
|
code_to_phrase(205) -> <<"Reset Content">>;
|
|
|
|
code_to_phrase(206) -> <<"Partial Content">>;
|
|
|
|
code_to_phrase(300) -> <<"Multiple Choices">>;
|
|
|
|
code_to_phrase(301) -> <<"Moved Permanently">>;
|
|
|
|
code_to_phrase(302) -> <<"Found">>;
|
|
|
|
code_to_phrase(303) -> <<"See Other">>;
|
|
|
|
code_to_phrase(304) -> <<"Not Modified">>;
|
|
|
|
code_to_phrase(305) -> <<"Use Proxy">>;
|
|
|
|
code_to_phrase(306) -> <<"(Unused)">>;
|
|
|
|
code_to_phrase(307) -> <<"Temporary Redirect">>;
|
|
|
|
code_to_phrase(400) -> <<"Bad Request">>;
|
|
|
|
code_to_phrase(401) -> <<"Unauthorized">>;
|
|
|
|
code_to_phrase(402) -> <<"Payment Required">>;
|
|
|
|
code_to_phrase(403) -> <<"Forbidden">>;
|
|
|
|
code_to_phrase(404) -> <<"Not Found">>;
|
|
|
|
code_to_phrase(405) -> <<"Method Not Allowed">>;
|
|
|
|
code_to_phrase(406) -> <<"Not Acceptable">>;
|
|
|
|
code_to_phrase(407) ->
|
|
|
|
<<"Proxy Authentication Required">>;
|
|
|
|
code_to_phrase(408) -> <<"Request Timeout">>;
|
|
|
|
code_to_phrase(409) -> <<"Conflict">>;
|
|
|
|
code_to_phrase(410) -> <<"Gone">>;
|
|
|
|
code_to_phrase(411) -> <<"Length Required">>;
|
|
|
|
code_to_phrase(412) -> <<"Precondition Failed">>;
|
|
|
|
code_to_phrase(413) -> <<"Request Entity Too Large">>;
|
|
|
|
code_to_phrase(414) -> <<"Request-URI Too Long">>;
|
|
|
|
code_to_phrase(415) -> <<"Unsupported Media Type">>;
|
|
|
|
code_to_phrase(416) ->
|
|
|
|
<<"Requested Range Not Satisfiable">>;
|
|
|
|
code_to_phrase(417) -> <<"Expectation Failed">>;
|
|
|
|
code_to_phrase(500) -> <<"Internal Server Error">>;
|
|
|
|
code_to_phrase(501) -> <<"Not Implemented">>;
|
|
|
|
code_to_phrase(502) -> <<"Bad Gateway">>;
|
|
|
|
code_to_phrase(503) -> <<"Service Unavailable">>;
|
|
|
|
code_to_phrase(504) -> <<"Gateway Timeout">>;
|
|
|
|
code_to_phrase(505) -> <<"HTTP Version Not Supported">>.
|
|
|
|
|
2016-03-30 16:47:40 +02:00
|
|
|
-spec parse_auth(binary()) -> {binary(), binary()} | {oauth, binary(), []} | undefined.
|
2013-03-14 10:33:02 +01:00
|
|
|
parse_auth(<<"Basic ", Auth64/binary>>) ->
|
2017-05-23 09:43:26 +02:00
|
|
|
Auth = try base64:decode(Auth64)
|
|
|
|
catch _:badarg -> <<>>
|
|
|
|
end,
|
2013-03-14 10:33:02 +01:00
|
|
|
%% Auth should be a string with the format: user@server:password
|
|
|
|
%% Note that password can contain additional characters '@' and ':'
|
|
|
|
case str:chr(Auth, $:) of
|
|
|
|
0 ->
|
|
|
|
undefined;
|
|
|
|
Pos ->
|
|
|
|
{User, <<$:, Pass/binary>>} = erlang:split_binary(Auth, Pos-1),
|
2016-07-22 16:51:48 +02:00
|
|
|
PassUtf8 = unicode:characters_to_binary(binary_to_list(Pass), utf8),
|
|
|
|
{User, PassUtf8}
|
2004-03-02 22:16:55 +01:00
|
|
|
end;
|
2015-09-25 14:53:25 +02:00
|
|
|
parse_auth(<<"Bearer ", SToken/binary>>) ->
|
|
|
|
Token = str:strip(SToken),
|
|
|
|
{oauth, Token, []};
|
2013-03-14 10:33:02 +01:00
|
|
|
parse_auth(<<_/binary>>) -> undefined.
|
2004-03-02 22:16:55 +01:00
|
|
|
|
|
|
|
parse_urlencoded(S) ->
|
2013-03-14 10:33:02 +01:00
|
|
|
parse_urlencoded(S, nokey, <<>>, key).
|
2004-03-02 22:16:55 +01:00
|
|
|
|
2013-03-14 10:33:02 +01:00
|
|
|
parse_urlencoded(<<$%, Hi, Lo, Tail/binary>>, Last, Cur,
|
|
|
|
State) ->
|
2018-04-03 00:21:33 +02:00
|
|
|
Hex = list_to_integer([Hi, Lo], 16),
|
2013-03-14 10:33:02 +01:00
|
|
|
parse_urlencoded(Tail, Last, <<Cur/binary, Hex>>, State);
|
|
|
|
parse_urlencoded(<<$&, Tail/binary>>, _Last, Cur, key) ->
|
|
|
|
[{Cur, <<"">>} | parse_urlencoded(Tail,
|
|
|
|
nokey, <<>>,
|
|
|
|
key)]; %% cont keymode
|
|
|
|
parse_urlencoded(<<$&, Tail/binary>>, Last, Cur, value) ->
|
|
|
|
V = {Last, Cur},
|
|
|
|
[V | parse_urlencoded(Tail, nokey, <<>>, key)];
|
|
|
|
parse_urlencoded(<<$+, Tail/binary>>, Last, Cur, State) ->
|
|
|
|
parse_urlencoded(Tail, Last, <<Cur/binary, $\s>>, State);
|
|
|
|
parse_urlencoded(<<$=, Tail/binary>>, _Last, Cur, key) ->
|
|
|
|
parse_urlencoded(Tail, Cur, <<>>,
|
|
|
|
value); %% change mode
|
|
|
|
parse_urlencoded(<<H, Tail/binary>>, Last, Cur, State) ->
|
|
|
|
parse_urlencoded(Tail, Last, <<Cur/binary, H>>, State);
|
|
|
|
parse_urlencoded(<<>>, Last, Cur, _State) ->
|
|
|
|
[{Last, Cur}];
|
|
|
|
parse_urlencoded(undefined, _, _, _) -> [].
|
|
|
|
|
2015-02-25 14:36:48 +01:00
|
|
|
% The following code is mostly taken from yaws_ssl.erl
|
|
|
|
|
|
|
|
toupper(C) when C >= $a andalso C =< $z -> C - 32;
|
|
|
|
toupper(C) -> C.
|
|
|
|
|
|
|
|
tolower(C) when C >= $A andalso C =< $Z -> C + 32;
|
|
|
|
tolower(C) -> C.
|
|
|
|
|
|
|
|
normalize_header_name(Name) ->
|
|
|
|
normalize_header_name(Name, [], true).
|
|
|
|
|
|
|
|
normalize_header_name(<<"">>, Acc, _) ->
|
|
|
|
iolist_to_binary(Acc);
|
|
|
|
normalize_header_name(<<"-", Rest/binary>>, Acc, _) ->
|
|
|
|
normalize_header_name(Rest, [Acc, "-"], true);
|
|
|
|
normalize_header_name(<<C:8, Rest/binary>>, Acc, true) ->
|
|
|
|
normalize_header_name(Rest, [Acc, toupper(C)], false);
|
|
|
|
normalize_header_name(<<C:8, Rest/binary>>, Acc, false) ->
|
|
|
|
normalize_header_name(Rest, [Acc, tolower(C)], false).
|
|
|
|
|
2013-06-25 11:26:44 +02:00
|
|
|
normalize_path(Path) ->
|
|
|
|
normalize_path(Path, []).
|
|
|
|
|
|
|
|
normalize_path([], Norm) -> lists:reverse(Norm);
|
2013-06-25 13:46:21 +02:00
|
|
|
normalize_path([<<"..">>|Path], Norm) ->
|
2013-06-25 11:26:44 +02:00
|
|
|
normalize_path(Path, Norm);
|
2013-06-25 13:46:21 +02:00
|
|
|
normalize_path([_Parent, <<"..">>|Path], Norm) ->
|
2013-06-25 11:26:44 +02:00
|
|
|
normalize_path(Path, Norm);
|
|
|
|
normalize_path([Part | Path], Norm) ->
|
|
|
|
normalize_path(Path, [Part|Norm]).
|
2013-08-12 14:25:05 +02:00
|
|
|
|
2017-12-24 10:27:51 +01:00
|
|
|
-spec get_certfile([proplists:property()]) -> binary() | undefined.
|
|
|
|
get_certfile(Opts) ->
|
|
|
|
case lists:keyfind(certfile, 1, Opts) of
|
2017-12-25 06:41:51 +01:00
|
|
|
{_, CertFile} ->
|
|
|
|
CertFile;
|
2017-12-24 10:27:51 +01:00
|
|
|
false ->
|
2018-06-14 13:00:47 +02:00
|
|
|
case ejabberd_pkix:get_certfile(ejabberd_config:get_myname()) of
|
2017-12-24 10:27:51 +01:00
|
|
|
{ok, CertFile} ->
|
|
|
|
CertFile;
|
|
|
|
error ->
|
2018-06-14 13:00:47 +02:00
|
|
|
ejabberd_config:get_option({domain_certfile, ejabberd_config:get_myname()})
|
2017-12-24 10:27:51 +01:00
|
|
|
end
|
|
|
|
end.
|
|
|
|
|
2013-08-12 14:25:05 +02:00
|
|
|
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) ->
|
2015-04-15 10:47:10 +02:00
|
|
|
Opts;
|
2013-08-12 14:25:05 +02:00
|
|
|
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].
|
2015-06-01 14:38:27 +02:00
|
|
|
|
2017-05-08 11:59:28 +02:00
|
|
|
-spec opt_type(trusted_proxies) -> fun((all | [binary()]) -> all | [binary()]);
|
|
|
|
(atom()) -> [atom()].
|
2015-06-01 14:38:27 +02:00
|
|
|
opt_type(trusted_proxies) ->
|
2015-12-04 13:14:39 +01:00
|
|
|
fun (all) -> all;
|
2018-05-04 09:53:07 +02:00
|
|
|
(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;
|
2015-06-01 14:38:27 +02:00
|
|
|
opt_type(_) -> [trusted_proxies].
|
2017-04-30 18:01:47 +02:00
|
|
|
|
2017-05-08 11:59:28 +02:00
|
|
|
-spec listen_opt_type(tls) -> fun((boolean()) -> boolean());
|
|
|
|
(certfile) -> fun((binary()) -> binary());
|
|
|
|
(ciphers) -> fun((binary()) -> binary());
|
|
|
|
(dhfile) -> fun((binary()) -> binary());
|
|
|
|
(protocol_options) -> fun(([binary()]) -> binary());
|
|
|
|
(tls_compression) -> fun((boolean()) -> boolean());
|
|
|
|
(captcha) -> fun((boolean()) -> boolean());
|
|
|
|
(register) -> fun((boolean()) -> boolean());
|
|
|
|
(web_admin) -> fun((boolean()) -> boolean());
|
|
|
|
(http_bind) -> fun((boolean()) -> boolean());
|
|
|
|
(xmlrpc) -> fun((boolean()) -> boolean());
|
|
|
|
(request_handlers) -> fun(([{binary(), atom()}]) ->
|
|
|
|
[{binary(), atom()}]);
|
|
|
|
(default_host) -> fun((binary()) -> binary());
|
|
|
|
(custom_headers) -> fun(([{binary(), binary()}]) ->
|
|
|
|
[{binary(), binary()}]);
|
|
|
|
(atom()) -> [atom()].
|
2017-04-30 18:01:47 +02:00
|
|
|
listen_opt_type(tls) ->
|
|
|
|
fun(B) when is_boolean(B) -> B end;
|
2017-12-24 10:27:51 +01:00
|
|
|
listen_opt_type(certfile = Opt) ->
|
2017-05-12 15:27:09 +02:00
|
|
|
fun(S) ->
|
2017-12-24 10:27:51 +01:00
|
|
|
?WARNING_MSG("Listening option '~s' for ~s is deprecated, use "
|
|
|
|
"'certfiles' global option instead", [Opt, ?MODULE]),
|
2017-05-12 15:27:09 +02:00
|
|
|
ejabberd_pkix:add_certfile(S),
|
|
|
|
iolist_to_binary(S)
|
|
|
|
end;
|
2017-04-30 18:01:47 +02:00
|
|
|
listen_opt_type(ciphers) ->
|
2017-05-17 14:42:18 +02:00
|
|
|
fun iolist_to_binary/1;
|
2017-04-30 18:01:47 +02:00
|
|
|
listen_opt_type(dhfile) ->
|
2017-05-12 08:34:57 +02:00
|
|
|
fun misc:try_read_file/1;
|
2017-04-30 18:01:47 +02:00
|
|
|
listen_opt_type(protocol_options) ->
|
|
|
|
fun(Options) -> str:join(Options, <<"|">>) end;
|
|
|
|
listen_opt_type(tls_compression) ->
|
|
|
|
fun(B) when is_boolean(B) -> B end;
|
|
|
|
listen_opt_type(captcha) ->
|
|
|
|
fun(B) when is_boolean(B) -> B end;
|
|
|
|
listen_opt_type(register) ->
|
|
|
|
fun(B) when is_boolean(B) -> B end;
|
|
|
|
listen_opt_type(web_admin) ->
|
|
|
|
fun(B) when is_boolean(B) -> B end;
|
|
|
|
listen_opt_type(http_bind) ->
|
|
|
|
fun(B) when is_boolean(B) -> B end;
|
|
|
|
listen_opt_type(xmlrpc) ->
|
|
|
|
fun(B) when is_boolean(B) -> B end;
|
|
|
|
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,
|
|
|
|
case Mod of
|
|
|
|
mod_http_bind -> mod_bosh;
|
|
|
|
_ -> Mod
|
|
|
|
end} || {Path, Mod} <- Hs2]
|
|
|
|
end;
|
|
|
|
listen_opt_type(default_host) ->
|
|
|
|
fun(A) -> A end;
|
|
|
|
listen_opt_type(custom_headers) ->
|
|
|
|
fun expand_custom_headers/1;
|
Validate additional listen opts
The options "inet", "inet6" and "backlog" are valid listen options, but are
currently logged as errors (even though they do work):
2018-02-28 16:08:44.141 [error] <0.338.0>@ejabberd_listener:validate_module_option:630 unknown listen option 'backlog' for 'ejabberd_c2s' will be likely ignored, available options are: access, shaper, certfile, ciphers, dhfile, cafile, client_cafile, protocol_options, tls, tls_compression, starttls, starttls_required, tls_verify, zlib, max_fsm_queue
This adds the necessary validators so they are correctly recognized.
2018-02-28 17:14:35 +01:00
|
|
|
listen_opt_type(inet) -> fun(B) when is_boolean(B) -> B end;
|
|
|
|
listen_opt_type(inet6) -> fun(B) when is_boolean(B) -> B end;
|
|
|
|
listen_opt_type(backlog) ->
|
|
|
|
fun(I) when is_integer(I), I>0 -> I end;
|
2018-04-30 10:52:00 +02:00
|
|
|
listen_opt_type(accept_interval) ->
|
|
|
|
fun(I) when is_integer(I), I>=0 -> I end;
|
2017-04-30 18:01:47 +02:00
|
|
|
listen_opt_type(_) ->
|
|
|
|
%% TODO
|
|
|
|
fun(A) -> A end.
|