diff options
Diffstat (limited to 'src/ejabberd_http.erl')
-rw-r--r-- | src/ejabberd_http.erl | 246 |
1 files changed, 131 insertions, 115 deletions
diff --git a/src/ejabberd_http.erl b/src/ejabberd_http.erl index 4e7f4b554..d8d1ddd44 100644 --- a/src/ejabberd_http.erl +++ b/src/ejabberd_http.erl @@ -5,7 +5,7 @@ %%% Created : 27 Feb 2004 by Alexey Shchepin <alexey@process-one.net> %%% %%% -%%% ejabberd, Copyright (C) 2002-2015 ProcessOne +%%% ejabberd, Copyright (C) 2002-2016 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -25,6 +25,8 @@ -module(ejabberd_http). +-behaviour(ejabberd_config). + -author('alexey@process-one.net'). %% External exports @@ -32,8 +34,7 @@ socket_type/0, receive_headers/1, url_encode/1, transform_listen_option/2]). -%% Callbacks --export([init/2]). +-export([init/2, opt_type/1]). -include("ejabberd.hrl"). -include("logger.hrl"). @@ -83,8 +84,9 @@ "">>). start(SockData, Opts) -> - supervisor:start_child(ejabberd_http_sup, - [SockData, Opts]). + {ok, + proc_lib:spawn(ejabberd_http, init, + [SockData, Opts])}. start_link(SockData, Opts) -> {ok, @@ -95,6 +97,7 @@ init({SockMod, Socket}, Opts) -> TLSEnabled = proplists:get_bool(tls, Opts), TLSOpts1 = lists:filter(fun ({certfile, _}) -> true; ({ciphers, _}) -> true; + ({dhfile, _}) -> true; (_) -> false end, Opts), @@ -114,9 +117,9 @@ init({SockMod, Socket}, Opts) -> TLSOpts = [verify_none | TLSOpts3], {SockMod1, Socket1} = if TLSEnabled -> inet:setopts(Socket, [{recbuf, 8192}]), - {ok, TLSSocket} = p1_tls:tcp_to_tls(Socket, + {ok, TLSSocket} = fast_tls:tcp_to_tls(Socket, TLSOpts), - {p1_tls, TLSSocket}; + {fast_tls, TLSSocket}; true -> {SockMod, Socket} end, Captcha = case proplists:get_bool(captcha, Opts) of @@ -158,9 +161,14 @@ init({SockMod, Socket}, Opts) -> default_host = DefaultHost, options = Opts, request_handlers = RequestHandlers}, - receive_headers(State). + try receive_headers(State) of + V -> V + catch + {error, _} -> State + end. -become_controller(_Pid) -> ok. +become_controller(_Pid) -> + ok. socket_type() -> raw. @@ -195,22 +203,20 @@ parse_headers(#state{request_method = Method, trail = Data} = State) -> PktType = case Method of - undefined -> http_bin; - _ -> httph_bin - end, + undefined -> http_bin; + _ -> httph_bin + end, case erlang:decode_packet(PktType, Data, []) of - {ok, Pkt, Rest} -> - NewState = process_header(State#state{trail = Rest}, {ok, Pkt}), + {ok, Pkt, Rest} -> + NewState = process_header(State#state{trail = Rest}, {ok, Pkt}), case NewState#state.end_of_request of - true -> - ok; - _ -> - parse_headers(NewState) + true -> ok; + _ -> parse_headers(NewState) end; - {more, _} -> - receive_headers(State#state{trail = Data}); - _ -> - ok + {more, _} -> + receive_headers(State#state{trail = Data}); + _ -> + ok end. process_header(State, Data) -> @@ -260,10 +266,8 @@ process_header(State, Data) -> State#state{request_host = Host, request_headers = add_header(Name, Host, State)}; {ok, {http_header, _, Name, _, Value}} when is_binary(Name) -> - State#state{request_headers = - add_header(normalize_header_name(Name), - Value, - State)}; + 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)}; @@ -284,16 +288,18 @@ process_header(State, Data) -> HostProvided), State2 = State#state{request_host = Host, request_port = Port, request_tp = TP}, - Out = process_request(State2), - send_text(State2, Out), - case State2#state.request_keepalive of + {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, request_handlers = State#state.request_handlers}; _ -> #state{end_of_request = true, + trail = State3#state.trail, options = State#state.options, default_host = State#state.default_host, request_handlers = State#state.request_handlers} @@ -316,22 +322,14 @@ get_host_really_served(Default, Provided) -> false -> Default end. -%% @spec (SockMod, HostPort) -> {Host::string(), Port::integer(), TP} -%% where -%% SockMod = gen_tcp | tls -%% HostPort = string() -%% TP = http | https -%% @doc Given a socket and hostport header, return data of transfer protocol. -%% Note that HostPort can be a string of a host like "example.org", -%% or a string of a host and port like "example.org:5280". get_transfer_protocol(SockMod, HostPort) -> [Host | PortList] = str:tokens(HostPort, <<":">>), case {SockMod, PortList} of {gen_tcp, []} -> {Host, 80, http}; {gen_tcp, [Port]} -> {Host, jlib:binary_to_integer(Port), http}; - {p1_tls, []} -> {Host, 443, https}; - {p1_tls, [Port]} -> + {fast_tls, []} -> {Host, 443, https}; + {fast_tls, [Port]} -> {Host, jlib:binary_to_integer(Port), https} end. @@ -371,20 +369,20 @@ process(Handlers, Request, Socket, SockMod, Trail) -> end. extract_path_query(#state{request_method = Method, - request_path = {abs_path, Path}}) + request_path = {abs_path, Path}} = State) when Method =:= 'GET' orelse Method =:= 'HEAD' orelse Method =:= 'DELETE' orelse Method =:= 'OPTIONS' -> case catch url_decode_q_split(Path) of - {'EXIT', _} -> 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, - {LPath, LQuery, <<"">>} + {'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, <<"">>}} end; extract_path_query(#state{request_method = Method, request_path = {abs_path, Path}, @@ -393,45 +391,49 @@ extract_path_query(#state{request_method = Method, socket = _Socket} = State) when (Method =:= 'POST' orelse Method =:= 'PUT') andalso is_integer(Len) -> - Data = recv_data(State, Len), + {NewState, Data} = recv_data(State, Len), ?DEBUG("client data: ~p~n", [Data]), case catch url_decode_q_split(Path) of - {'EXIT', _} -> false; - {NPath, _Query} -> - LPath = normalize_path([NPE - || NPE <- str:tokens(path_decode(NPath), <<"/">>)]), - LQuery = case catch parse_urlencoded(Data) of - {'EXIT', _Reason} -> []; - LQ -> LQ - end, - {LPath, LQuery, Data} + {'EXIT', _} -> {NewState, false}; + {NPath, _Query} -> + LPath = normalize_path([NPE + || NPE <- str:tokens(path_decode(NPath), <<"/">>)]), + LQuery = case catch parse_urlencoded(Data) of + {'EXIT', _Reason} -> []; + LQ -> LQ + end, + {NewState, {LPath, LQuery, Data}} end; -extract_path_query(_State) -> - false. +extract_path_query(State) -> + {State, false}. process_request(#state{request_method = Method, - request_auth = Auth, - request_lang = Lang, - sockmod = SockMod, - socket = Socket, - options = Options, - request_host = Host, - request_port = Port, - request_tp = TP, - request_headers = RequestHeaders, - request_handlers = RequestHandlers, - trail = Trail} = State) -> + request_auth = Auth, + request_lang = Lang, + sockmod = SockMod, + socket = Socket, + options = Options, + request_host = Host, + request_port = Port, + request_tp = TP, + request_headers = RequestHeaders, + request_handlers = RequestHandlers, + trail = Trail} = State) -> case extract_path_query(State) of - false -> - make_bad_request(State); - {LPath, LQuery, Data} -> - {ok, IPHere} = + {State2, false} -> + {State2, make_bad_request(State)}; + {State2, {LPath, LQuery, Data}} -> + PeerName = case SockMod of gen_tcp -> inet:peername(Socket); _ -> SockMod:peername(Socket) end, + IPHere = case PeerName of + {ok, V} -> V; + {error, _} = E -> throw(E) + end, XFF = proplists:get_value('X-Forwarded-For', RequestHeaders, []), IP = analyze_ip_xff(IPHere, XFF, Host), Request = #request{method = Method, @@ -446,27 +448,27 @@ process_request(#state{request_method = Method, opts = Options, headers = RequestHeaders, ip = IP}, - case process(RequestHandlers, Request, Socket, SockMod, Trail) of - El when is_record(El, xmlel) -> - make_xhtml_output(State, 200, [], El); - {Status, Headers, El} - when is_record(El, xmlel) -> - make_xhtml_output(State, Status, Headers, El); - Output when is_binary(Output) or is_list(Output) -> - make_text_output(State, 200, [], Output); - {Status, Headers, Output} - when is_binary(Output) or is_list(Output) -> - make_text_output(State, Status, Headers, Output); - {Status, Reason, Headers, Output} - when is_binary(Output) or is_list(Output) -> - make_text_output(State, Status, Reason, Headers, Output); - _ -> - none - end + Res = case process(RequestHandlers, Request, Socket, SockMod, Trail) of + El when is_record(El, xmlel) -> + make_xhtml_output(State, 200, [], El); + {Status, Headers, El} + when is_record(El, xmlel) -> + make_xhtml_output(State, Status, Headers, El); + Output when is_binary(Output) or is_list(Output) -> + make_text_output(State, 200, [], Output); + {Status, Headers, Output} + when is_binary(Output) or is_list(Output) -> + make_text_output(State, Status, Headers, Output); + {Status, Reason, Headers, Output} + when is_binary(Output) or is_list(Output) -> + make_text_output(State, Status, Reason, Headers, Output); + _ -> + none + end, + {State2, Res} end. make_bad_request(State) -> -%% Support for X-Forwarded-From make_xhtml_output(State, 400, [], ejabberd_web:make_xhtml([#xmlel{name = <<"h1">>, attrs = [], @@ -480,7 +482,8 @@ analyze_ip_xff({IPLast, Port}, XFF, Host) -> [jlib:ip_to_list(IPLast)], TrustedProxies = ejabberd_config:get_option( {trusted_proxies, Host}, - fun(TPs) -> + fun(all) -> all; + (TPs) -> [iolist_to_binary(TP) || TP <- TPs] end, []), IPClient = case is_ipchain_trusted(ProxiesIPs, @@ -503,32 +506,37 @@ is_ipchain_trusted(UserIPs, TrustedIPs) -> recv_data(State, Len) -> recv_data(State, Len, <<>>). -recv_data(_State, 0, Acc) -> (iolist_to_binary(Acc)); +recv_data(State, 0, Acc) -> {State, Acc}; +recv_data(#state{trail = Trail} = State, Len, <<>>) when byte_size(Trail) > Len -> + <<Data:Len/binary, Rest/binary>> = Trail, + {State#state{trail = Rest}, Data}; recv_data(State, Len, Acc) -> case State#state.trail of - <<>> -> - case (State#state.sockmod):recv(State#state.socket, Len, - 300000) - of - {ok, Data} -> - recv_data(State, Len - byte_size(Data), <<Acc/binary, Data/binary>>); - _ -> <<"">> - end; - _ -> - Trail = (State#state.trail), - recv_data(State#state{trail = <<>>}, - Len - byte_size(Trail), <<Acc/binary, Trail/binary>>) + <<>> -> + case (State#state.sockmod):recv(State#state.socket, + min(Len, 16#4000000), 300000) + of + {ok, Data} -> + recv_data(State, Len - byte_size(Data), <<Acc/binary, Data/binary>>); + Err -> + ?DEBUG("Cannot receive HTTP data: ~p", [Err]), + <<"">> + end; + _ -> + Trail = (State#state.trail), + recv_data(State#state{trail = <<>>}, + Len - byte_size(Trail), <<Acc/binary, Trail/binary>>) end. make_xhtml_output(State, Status, Headers, XHTML) -> Data = case lists:member(html, Headers) of - true -> - iolist_to_binary([?HTML_DOCTYPE, - xml:element_to_binary(XHTML)]); - _ -> - iolist_to_binary([?XHTML_DOCTYPE, - xml:element_to_binary(XHTML)]) - end, + true -> + iolist_to_binary([?HTML_DOCTYPE, + fxml:element_to_binary(XHTML)]); + _ -> + iolist_to_binary([?XHTML_DOCTYPE, + fxml:element_to_binary(XHTML)]) + end, Headers1 = case lists:keysearch(<<"Content-Type">>, 1, Headers) of @@ -756,6 +764,9 @@ parse_auth(<<"Basic ", Auth64/binary>>) -> {User, <<$:, Pass/binary>>} = erlang:split_binary(Auth, Pos-1), {User, Pass} end; +parse_auth(<<"Bearer ", SToken/binary>>) -> + Token = str:strip(SToken), + {oauth, Token, []}; parse_auth(<<_/binary>>) -> undefined. parse_urlencoded(S) -> @@ -870,3 +881,8 @@ transform_listen_option({request_handlers, Hs}, Opts) -> [{request_handlers, Hs1} | Opts]; transform_listen_option(Opt, Opts) -> [Opt|Opts]. + +opt_type(trusted_proxies) -> + fun (all) -> all; + (TPs) -> [iolist_to_binary(TP) || TP <- TPs] end; +opt_type(_) -> [trusted_proxies]. |