diff options
Diffstat (limited to 'src/ejabberd_http.erl')
-rw-r--r-- | src/ejabberd_http.erl | 851 |
1 files changed, 851 insertions, 0 deletions
diff --git a/src/ejabberd_http.erl b/src/ejabberd_http.erl new file mode 100644 index 000000000..eab2332bb --- /dev/null +++ b/src/ejabberd_http.erl @@ -0,0 +1,851 @@ +%%%---------------------------------------------------------------------- +%%% File : ejabberd_http.erl +%%% Author : Alexey Shchepin <alexey@process-one.net> +%%% Purpose : +%%% Created : 27 Feb 2004 by Alexey Shchepin <alexey@process-one.net> +%%% +%%% +%%% ejabberd, Copyright (C) 2002-2013 ProcessOne +%%% +%%% 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. +%%% +%%% 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., 59 Temple Place, Suite 330, Boston, MA +%%% 02111-1307 USA +%%% +%%%---------------------------------------------------------------------- + +-module(ejabberd_http). + +-author('alexey@process-one.net'). + +%% External exports +-export([start/2, start_link/2, become_controller/1, + socket_type/0, receive_headers/1, url_encode/1]). + +%% Callbacks +-export([init/2]). + +-include("ejabberd.hrl"). +-include("logger.hrl"). + +-include("jlib.hrl"). + +-include("ejabberd_http.hrl"). + +-record(state, +{ + sockmod = gen_tcp :: gen_tcp | tls, + socket :: inet:socket() | tls:tls_socket(), + request_method :: method(), + request_version = {1, 1} :: {non_neg_integer(), non_neg_integer()}, + request_path :: {abs_path, binary()} | binary(), + request_auth :: {binary(), binary()}, + request_keepalive = false :: boolean(), + request_content_length = 0 :: non_neg_integer(), + request_lang = <<"en">> :: binary(), + request_handlers = [] :: [{[binary()], atom()}], + request_host :: binary(), + request_port = 5280 :: inet:port_number(), + request_tp = http :: protocol(), + request_headers = [] :: list(), + end_of_request = false :: boolean(), + default_host :: binary(), + trail = <<>> :: binary(), + websocket_handlers = [] :: [{[binary()], atom(), list()}] +}). + +-define(XHTML_DOCTYPE, + <<"<?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">>). + +-define(HTML_DOCTYPE, + <<"<!DOCTYPE html PUBLIC \"-//W3C//DTD " + "XHTML 1.0 Transitional//EN\" \"http://www.w3." + "org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n" + "">>). + +start(SockData, Opts) -> + supervisor:start_child(ejabberd_http_sup, + [SockData, Opts]). + +start_link(SockData, Opts) -> + {ok, + proc_lib:spawn_link(ejabberd_http, init, + [SockData, Opts])}. + +init({SockMod, Socket}, Opts) -> + TLSEnabled = lists:member(tls, Opts), + TLSOpts1 = lists:filter(fun ({certfile, _}) -> true; + (_) -> false + end, + Opts), + TLSOpts = [verify_none | TLSOpts1], + {SockMod1, Socket1} = if TLSEnabled -> + inet:setopts(Socket, [{recbuf, 8192}]), + {ok, TLSSocket} = tls:tcp_to_tls(Socket, + TLSOpts), + {tls, TLSSocket}; + true -> {SockMod, Socket} + end, + Captcha = case lists:member(captcha, Opts) of + true -> [{[<<"captcha">>], ejabberd_captcha}]; + false -> [] + end, + Register = case lists:member(register, Opts) of + true -> [{[<<"register">>], mod_register_web}]; + false -> [] + end, + Admin = case lists:member(web_admin, Opts) of + true -> [{[<<"admin">>], ejabberd_web_admin}]; + false -> [] + end, + Bind = case lists:member(http_bind, Opts) of + true -> [{[<<"http-bind">>], mod_http_bind}]; + false -> [] + end, + Poll = case lists:member(http_poll, Opts) of + true -> [{[<<"http-poll">>], ejabberd_http_poll}]; + false -> [] + end, + DefinedHandlers = case lists:keysearch(request_handlers, + 1, Opts) + of + {value, {request_handlers, H}} -> H; + false -> [] + end, + RequestHandlers = DefinedHandlers ++ Captcha ++ Register ++ + Admin ++ Bind ++ Poll, + ?DEBUG("S: ~p~n", [RequestHandlers]), + WebSocketHandlers = case + lists:keysearch(websocket_handlers, 1, Opts) + of + {value, {websocket_handlers, WH}} -> WH; + false -> [] + end, + ?DEBUG("WS: ~p~n", [WebSocketHandlers]), + DefaultHost = gen_mod:get_opt(default_host, Opts, + fun iolist_to_binary/1), + ?INFO_MSG("started: ~p", [{SockMod1, Socket1}]), + State = #state{sockmod = SockMod1, socket = Socket1, + default_host = DefaultHost, + request_handlers = RequestHandlers, + websocket_handlers = WebSocketHandlers}, + receive_headers(State). + +become_controller(_Pid) -> ok. + +socket_type() -> raw. + +send_text(_State, none) -> exit(normal); +send_text(State, Text) -> + case catch + (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. + +receive_headers(#state{trail = Trail, + request_method = Method} = State) -> + SockMod = State#state.sockmod, + Socket = State#state.socket, + Data = SockMod:recv(Socket, 0, 300000), + case Data of + {error, _} -> ok; + {ok, D} -> + NewTrail = <<Trail/binary, D/binary>>, + case {Method, NewTrail} of + {undefined, <<"<policy-file-request/>\000">>} -> + send_flash_policy(State); + _ -> + parse_headers(State#state{trail = NewTrail}) + end + end. + +parse_headers(#state{trail = <<>>} = State) -> + receive_headers(State); +parse_headers(#state{request_method = Method, + trail = Data} = + State) -> + PktType = case Method of + 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}), + case NewState#state.end_of_request of + true -> ok; + _ -> parse_headers(NewState) + end; + {more, _} -> receive_headers(State#state{trail = Data}); + _ -> ok + end. + +process_header(State, Data) -> + SockMod = State#state.sockmod, + Socket = State#state.socket, + case Data of + {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}} -> + KeepAlive1 = case jlib:tolower(Conn) of + <<"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}} -> + case catch jlib:binary_to_integer(SLen) of + 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)}; + {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 -> + ?WARNING_MSG("An HTTP request without 'Host' HTTP " + "header was received.", + []), + throw(http_request_no_host_header); + {ok, http_eoh} -> + ?DEBUG("(~w) http query: ~w ~s~n", + [State#state.socket, State#state.request_method, + element(2, State#state.request_path)]), + {HostProvided, Port, TP} = + get_transfer_protocol(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}, + Out = process_request(State2), + send_text(State2, Out), + case State2#state.request_keepalive of + true -> + #state{sockmod = SockMod, socket = Socket, + request_handlers = State#state.request_handlers, + websocket_handlers = State#state.websocket_handlers}; + _ -> + #state{end_of_request = true, + request_handlers = State#state.request_handlers, + websocket_handlers = State#state.websocket_handlers} + end; + {error, _Reason} -> + #state{end_of_request = true, + request_handlers = State#state.request_handlers, + websocket_handlers = State#state.websocket_handlers}; + _ -> + #state{end_of_request = true, + request_handlers = State#state.request_handlers, + websocket_handlers = State#state.websocket_handlers} + end. + +add_header(Name, Value, State)-> + [{Name, Value} | State#state.request_headers]. + +-define(GETOPT(Param, Opts), + case lists:keysearch(Param, 1, Opts) of + {value, {Param, V}} -> V; + false -> undefined + end). + +get_host_really_served(undefined, Provided) -> Provided; +get_host_really_served(Default, Provided) -> + case lists:member(Provided, ?MYHOSTS) of + true -> Provided; + false -> Default + end. + +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}; + {tls, []} -> {Host, 443, https}; + {tls, [Port]} -> + {Host, jlib:binary_to_integer(Port), https} + end. + +%% 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. + +process([], _, _, _, _) -> ejabberd_web:error(not_found); +process(Handlers, Request, Socket, SockMod, Trail) -> + {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), + code:ensure_loaded(HandlerModule), + R = case erlang:function_exported(HandlerModule, socket_handoff, 6) of + false -> + HandlerModule:process(LocalPath, Request); + _ -> + HandlerModule:socket_handoff(LocalPath, Request, Socket, SockMod, Trail, HandlerOpts) + end, + ejabberd_hooks:run(http_request_debug, [{LocalPath, Request}]), + R; + false -> + process(HandlersLeft, Request, Socket, SockMod, Trail) + end. + +extract_path_query(#state{request_method = Method, + request_path = {abs_path, Path}}) + 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 = [path_decode(NPE) + || NPE <- str:tokens(NPath, <<"/">>)], + LQuery = case catch parse_urlencoded(Query) of + {'EXIT', _Reason} -> []; + LQ -> LQ + end, + {LPath, LQuery, <<"">>} + end; +extract_path_query(#state{request_method = Method, + request_path = {abs_path, Path}, + request_content_length = Len, + sockmod = SockMod, + socket = Socket} = State) + when (Method =:= 'POST' orelse Method =:= 'PUT') andalso + is_integer(Len) -> + Data = recv_data(State, Len), + ?DEBUG("client data: ~p~n", [Data]), + case catch url_decode_q_split(Path) of + {'EXIT', _} -> false; + {NPath, _Query} -> + LPath = [path_decode(NPE) + || NPE <- str:tokens(NPath, <<"/">>)], + LQuery = case catch parse_urlencoded(Data) of + {'EXIT', _Reason} -> []; + LQ -> LQ + end, + {LPath, LQuery, Data} + end; +extract_path_query(_State) -> + false. + +process_request(#state{request_method = Method, + request_auth = Auth, + request_lang = Lang, + sockmod = SockMod, + socket = Socket, + request_host = Host, + request_port = Port, + request_tp = TP, + websocket_handlers = WebSocketHandlers, + 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} = + case SockMod of + gen_tcp -> + inet:peername(Socket); + _ -> + SockMod:peername(Socket) + end, + XFF = proplists:get_value('X-Forwarded-For', RequestHeaders, []), + IP = analyze_ip_xff(IPHere, XFF, Host), + Request = #request{method = Method, + path = LPath, + q = LQuery, + auth = Auth, + data = Data, + lang = Lang, + host = Host, + port = Port, + tp = TP, + headers = RequestHeaders, + ip = IP}, + case process(RequestHandlers ++ WebSocketHandlers, 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 + end. + +make_bad_request(State) -> + make_xhtml_output(State, 400, [], + ejabberd_web:make_xhtml([#xmlel{name = <<"h1">>, + attrs = [], + children = + [{xmlcdata, + <<"400 Bad Request">>}]}])). + +analyze_ip_xff(IP, [], _Host) -> IP; +analyze_ip_xff({IPLast, Port}, XFF, Host) -> + [ClientIP | ProxiesIPs] = str:tokens(XFF, <<", ">>) ++ + [jlib:ip_to_list(IPLast)], + TrustedProxies = ejabberd_config:get_local_option( + {trusted_proxies, Host}, + fun(TPs) -> + [iolist_to_binary(TP) || TP <- TPs] + end, []), + 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 + end, + {IPClient, Port}. + +is_ipchain_trusted(_UserIPs, all) -> true; +is_ipchain_trusted(UserIPs, TrustedIPs) -> + [] == UserIPs -- [<<"127.0.0.1">> | TrustedIPs]. + +recv_data(State, Len) -> recv_data(State, Len, <<>>). + +recv_data(_State, 0, Acc) -> (iolist_to_binary(Acc)); +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>>) + 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, + Headers1 = case lists:keysearch(<<"Content-Type">>, 1, + Headers) + of + {value, _} -> + [{<<"Content-Length">>, + iolist_to_binary(integer_to_list(byte_size(Data)))} + | Headers]; + _ -> + [{<<"Content-Type">>, <<"text/html; charset=utf-8">>}, + {<<"Content-Length">>, + iolist_to_binary(integer_to_list(byte_size(Data)))} + | Headers] + end, + HeadersOut = case {State#state.request_version, + State#state.request_keepalive} + of + {{1, 1}, true} -> Headers1; + {_, true} -> + [{<<"Connection">>, <<"keep-alive">>} | Headers1]; + {_, false} -> + [{<<"Connection">>, <<"close">>} | Headers1] + end, + Version = case State#state.request_version of + {1, 1} -> <<"HTTP/1.1 ">>; + _ -> <<"HTTP/1.0 ">> + end, + H = lists:map(fun ({Attr, Val}) -> + [Attr, <<": ">>, Val, <<"\r\n">>]; + (_) -> [] + end, + HeadersOut), + SL = [Version, + iolist_to_binary(integer_to_list(Status)), <<" ">>, + code_to_phrase(Status), <<"\r\n">>], + Data2 = case State#state.request_method of + 'HEAD' -> <<"">>; + _ -> Data + end, + [SL, H, <<"\r\n">>, Data2]. + +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), + Headers1 = case lists:keysearch(<<"Content-Type">>, 1, + Headers) + of + {value, _} -> + [{<<"Content-Length">>, + jlib:integer_to_binary(byte_size(Data))} + | Headers]; + _ -> + [{<<"Content-Type">>, <<"text/html; charset=utf-8">>}, + {<<"Content-Length">>, + jlib:integer_to_binary(byte_size(Data))} + | Headers] + end, + HeadersOut = case {State#state.request_version, + State#state.request_keepalive} + of + {{1, 1}, true} -> Headers1; + {_, true} -> + [{<<"Connection">>, <<"keep-alive">>} | Headers1]; + {_, false} -> + [{<<"Connection">>, <<"close">>} | Headers1] + end, + Version = case State#state.request_version of + {1, 1} -> <<"HTTP/1.1 ">>; + _ -> <<"HTTP/1.0 ">> + end, + H = lists:map(fun ({Attr, Val}) -> + [Attr, <<": ">>, Val, <<"\r\n">>] + end, + HeadersOut), + NewReason = case Reason of + <<"">> -> code_to_phrase(Status); + _ -> Reason + end, + SL = [Version, + jlib:integer_to_binary(Status), <<" ">>, + NewReason, <<"\r\n">>], + Data2 = case State#state.request_method of + 'HEAD' -> <<"">>; + _ -> Data + end, + [SL, H, <<"\r\n">>, Data2]. + +parse_lang(Langs) -> + case str:tokens(Langs, <<",; ">>) of + [First | _] -> First; + [] -> <<"en">> + end. + +% Code below is taken (with some modifications) from the yaws webserver, which +% is distributed under the folowing license: +% +% 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. + +url_decode_q_split(Path) -> + url_decode_q_split(Path, <<>>). + +url_decode_q_split(<<$?, T/binary>>, Acc) -> + %% Don't decode the query string here, that is parsed separately. + {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), <<>>}. + +path_decode(Path) -> path_decode(Path, <<>>). + +path_decode(<<$%, Hi, Lo, Tail/binary>>, Acc) -> + Hex = hex_to_integer([Hi, Lo]), + if Hex == 0 -> exit(badurl); + true -> ok + end, + 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). + +%% hex_to_integer + +hex_to_integer(Hex) -> + case catch list_to_integer(Hex, 16) of + {'EXIT', _} -> old_hex_to_integer(Hex); + X -> X + end. + +old_hex_to_integer(Hex) -> + DEHEX = fun (H) when H >= $a, H =< $f -> H - $a + 10; + (H) when H >= $A, H =< $F -> H - $A + 10; + (H) when H >= $0, H =< $9 -> H - $0 + end, + lists:foldl(fun (E, Acc) -> Acc * 16 + DEHEX(E) end, 0, + Hex). + +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">>. + +parse_auth(<<"Basic ", Auth64/binary>>) -> + Auth = jlib:decode_base64(Auth64), + %% 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), + {User, Pass} + end; +parse_auth(<<_/binary>>) -> undefined. + +parse_urlencoded(S) -> + parse_urlencoded(S, nokey, <<>>, key). + +parse_urlencoded(<<$%, Hi, Lo, Tail/binary>>, Last, Cur, + State) -> + Hex = hex_to_integer([Hi, Lo]), + 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, _, _, _) -> []. + + +url_encode(A) -> + url_encode(A, <<>>). + +url_encode(<<H:8, T/binary>>, Acc) when + (H >= $a andalso H =< $z) orelse + (H >= $A andalso H =< $Z) orelse + (H >= $0 andalso H =< $9) orelse + H == $_ orelse + H == $. orelse + H == $- orelse + H == $/ orelse + H == $: -> + url_encode(T, <<Acc/binary, H>>); +url_encode(<<H:8, T/binary>>, Acc) -> + case integer_to_hex(H) of + [X, Y] -> url_encode(T, <<Acc/binary, $%, X, Y>>); + [X] -> url_encode(T, <<Acc/binary, $%, $0, X>>) + end; +url_encode(<<>>, Acc) -> + Acc. + + +integer_to_hex(I) -> + case catch erlang:integer_to_list(I, 16) of + {'EXIT', _} -> old_integer_to_hex(I); + Int -> Int + end. + +old_integer_to_hex(I) when I < 10 -> integer_to_list(I); +old_integer_to_hex(I) when I < 16 -> [I - 10 + $A]; +old_integer_to_hex(I) when I >= 16 -> + N = trunc(I / 16), + old_integer_to_hex(N) ++ old_integer_to_hex(I rem 16). + +% 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). + +send_flash_policy(State) -> + Listen = ejabberd_config:get_local_option(listen, fun(V) -> V end), + Ports = lists:foldr(fun({{Port, _, _}, Handler, _Opts}, Acc) when + Handler == ejabberd_c2s orelse + Handler == ejabberd_http -> + case Acc of + [] -> + [integer_to_list(Port)]; + _ -> + [integer_to_list(Port), <<",">>, Acc] + end; + (_, Acc) -> + Acc + end, [], Listen), + PortsString = iolist_to_binary(Ports), + + send_text(State,<<"<?xml version=\"1.0\"?>\n" + "<!DOCTYPE cross-domain-policy SYSTEM " + "\"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd\">\n" + "<cross-domain-policy>\n" + " <allow-access-from domain=\"*\" to-ports=\"", PortsString/binary,"\"/>\n" + "</cross-domain-policy>\n\000">>), + + #state{end_of_request = true, + request_handlers = State#state.request_handlers, + websocket_handlers = State#state.websocket_handlers}. + +%% -*- tab-width:8 |