aboutsummaryrefslogtreecommitdiff
path: root/src/web
diff options
context:
space:
mode:
Diffstat (limited to 'src/web')
-rw-r--r--src/web/Makefile.in2
-rw-r--r--src/web/bosh.hrl36
-rw-r--r--src/web/ejabberd_bosh.erl1489
-rw-r--r--src/web/ejabberd_http.erl1723
-rw-r--r--src/web/ejabberd_http.hrl65
-rw-r--r--src/web/ejabberd_http_bind.erl2127
-rw-r--r--src/web/ejabberd_http_bindjson.erl2034
-rw-r--r--src/web/ejabberd_http_poll.erl569
-rw-r--r--src/web/ejabberd_http_ws.erl210
-rw-r--r--src/web/ejabberd_http_wsjson.erl235
-rw-r--r--src/web/ejabberd_web.erl86
-rw-r--r--src/web/ejabberd_web_admin.erl4684
-rw-r--r--src/web/ejabberd_web_admin.hrl106
-rw-r--r--src/web/ejabberd_websocket.erl722
-rw-r--r--src/web/ejabberd_ws.erl44
-rw-r--r--src/web/http_bind.hrl33
-rw-r--r--src/web/mochijson2.erl782
-rw-r--r--src/web/mod_bosh.erl254
-rw-r--r--src/web/mod_http_bind.erl122
-rw-r--r--src/web/mod_http_bindjson.erl153
-rw-r--r--src/web/mod_http_fileserver.erl417
-rw-r--r--src/web/mod_http_fileserver_log.erl199
-rw-r--r--src/web/mod_register_web.erl732
-rw-r--r--src/web/pshb_http.erl792
-rw-r--r--src/web/simple_ws_check.erl27
-rw-r--r--src/web/websocket_test.erl23
-rw-r--r--src/web/xmpp_json.erl691
27 files changed, 8655 insertions, 9702 deletions
diff --git a/src/web/Makefile.in b/src/web/Makefile.in
index 77f801410..5ac2fe5e6 100644
--- a/src/web/Makefile.in
+++ b/src/web/Makefile.in
@@ -15,7 +15,7 @@ EFLAGS += -pz ..
# make debug=true to compile Erlang module with debug informations.
ifdef debug
- EFLAGS+=+debug_info +export_all
+ EFLAGS+=+debug_info
endif
SOURCES = $(wildcard *.erl)
diff --git a/src/web/bosh.hrl b/src/web/bosh.hrl
index d47bd7c94..70fbe8723 100644
--- a/src/web/bosh.hrl
+++ b/src/web/bosh.hrl
@@ -19,16 +19,34 @@
%%%
%%%----------------------------------------------------------------------
--define(CT_XML, {"Content-Type", "text/xml; charset=utf-8"}).
--define(CT_PLAIN, {"Content-Type", "text/plain"}).
+-define(CT_XML,
+ {<<"Content-Type">>, <<"text/xml; charset=utf-8">>}).
--define(AC_ALLOW_ORIGIN, {"Access-Control-Allow-Origin", "*"}).
--define(AC_ALLOW_METHODS, {"Access-Control-Allow-Methods", "GET, POST, OPTIONS"}).
--define(AC_ALLOW_HEADERS, {"Access-Control-Allow-Headers", "Content-Type"}).
--define(AC_MAX_AGE, {"Access-Control-Max-Age", "86400"}).
+-define(CT_PLAIN,
+ {<<"Content-Type">>, <<"text/plain">>}).
--define(OPTIONS_HEADER, [?CT_PLAIN, ?AC_ALLOW_ORIGIN, ?AC_ALLOW_METHODS,
- ?AC_ALLOW_HEADERS, ?AC_MAX_AGE]).
--define(HEADER, [?CT_XML, ?AC_ALLOW_ORIGIN, ?AC_ALLOW_HEADERS]).
+-define(CT_JSON,
+ {<<"Content-Type">>, <<"application/json">>}).
+
+-define(AC_ALLOW_ORIGIN,
+ {<<"Access-Control-Allow-Origin">>, <<"*">>}).
+
+-define(AC_ALLOW_METHODS,
+ {<<"Access-Control-Allow-Methods">>,
+ <<"GET, POST, OPTIONS">>}).
+
+-define(AC_ALLOW_HEADERS,
+ {<<"Access-Control-Allow-Headers">>,
+ <<"Content-Type">>}).
+
+-define(AC_MAX_AGE,
+ {<<"Access-Control-Max-Age">>, <<"86400">>}).
+
+-define(OPTIONS_HEADER,
+ [?CT_PLAIN, ?AC_ALLOW_ORIGIN, ?AC_ALLOW_METHODS,
+ ?AC_ALLOW_HEADERS, ?AC_MAX_AGE]).
+
+-define(HEADER(CType),
+ [CType, ?AC_ALLOW_ORIGIN, ?AC_ALLOW_HEADERS]).
-define(PROCNAME, ejabberd_mod_bosh).
diff --git a/src/web/ejabberd_bosh.erl b/src/web/ejabberd_bosh.erl
index 36f73abfd..1d0380e64 100644
--- a/src/web/ejabberd_bosh.erl
+++ b/src/web/ejabberd_bosh.erl
@@ -31,249 +31,278 @@
%% API
-export([start/2, start/3, start_link/3]).
--export([send_xml/2, setopts/2, controlling_process/2, migrate/3,
- custom_receiver/1, become_controller/2, reset_stream/1,
- change_shaper/2, monitor/1, close/1, sockname/1,
- peername/1, process_request/2, send/2, change_controller/2]).
+
+-export([send_xml/2, setopts/2, controlling_process/2,
+ migrate/3, custom_receiver/1, become_controller/2,
+ reset_stream/1, change_shaper/2, monitor/1, close/1,
+ sockname/1, peername/1, process_request/3, send/2,
+ change_controller/2]).
%% gen_fsm callbacks
-export([init/1, wait_for_session/2, wait_for_session/3,
- active/2, active/3, handle_event/3, print_state/1,
- handle_sync_event/4, handle_info/3, terminate/3, code_change/4]).
+ active/2, active/3, handle_event/3, print_state/1,
+ handle_sync_event/4, handle_info/3, terminate/3,
+ code_change/4]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
+
-include("ejabberd_http.hrl").
+
-include("bosh.hrl").
%%-define(DBGFSM, true).
-ifdef(DBGFSM).
+
-define(FSMOPTS, [{debug, [trace]}]).
+
-else.
+
-define(FSMOPTS, []).
+
-endif.
--define(BOSH_VERSION, "1.10").
--define(NS_CLIENT, "jabber:client").
--define(NS_BOSH, "urn:xmpp:xbosh").
--define(NS_HTTP_BIND, "http://jabber.org/protocol/httpbind").
+-define(BOSH_VERSION, <<"1.10">>).
+
+-define(NS_CLIENT, <<"jabber:client">>).
+
+-define(NS_BOSH, <<"urn:xmpp:xbosh">>).
+
+-define(NS_HTTP_BIND,
+ <<"http://jabber.org/protocol/httpbind">>).
+
+-define(DEFAULT_MAXPAUSE, 120).
+
+-define(DEFAULT_WAIT, 300).
+
+-define(DEFAULT_HOLD, 1).
+
+-define(DEFAULT_POLLING, 2).
--define(DEFAULT_MAXPAUSE, 120). %% secs
--define(DEFAULT_WAIT, 300). %% secs
--define(DEFAULT_HOLD, 1). %% num
--define(DEFAULT_POLLING, 2). %% secs
--define(DEFAULT_INACTIVITY, 30). %% secs
+-define(DEFAULT_INACTIVITY, 30).
-define(MAX_SHAPED_REQUESTS_QUEUE_LEN, 1000).
--define(SEND_TIMEOUT, 15000). %% millisecs
-
--record(state, {host,
- sid,
- el_ibuf,
- el_obuf,
- shaper_state,
- c2s_pid,
- xmpp_ver,
- inactivity_timer,
- wait_timer,
- wait_timeout = ?DEFAULT_WAIT,
- inactivity_timeout,
- prev_rid,
- prev_key,
- prev_poll,
- max_concat = unlimited,
- responses = gb_trees:empty(),
- receivers = gb_trees:empty(),
- shaped_receivers = queue:new(),
- ip,
- max_requests}).
-
--record(body, {http_reason = "", %% Using HTTP reason phrase is
- %% a hack, but we need a clue why
- %% a connection gets terminated:
- %% 'condition' attribute is not enough
- attrs = [],
- els = [],
- size = 0}).
-
-%%%===================================================================
-%%% API
-%%%===================================================================
-%% TODO: If compile with no supervisor option, start the session without
-%% supervisor
+
+-define(SEND_TIMEOUT, 15000).
+
+-type bosh_socket() :: {http_bind, pid(),
+ {inet:ip_address(),
+ inet:port_number()}}.
+
+-export_type([bosh_socket/0]).
+
+-record(state,
+ {host = <<"">> :: binary(),
+ sid = <<"">> :: binary(),
+ el_ibuf = buf_new() :: queue(),
+ el_obuf = buf_new() :: queue(),
+ shaper_state = none :: shaper:shaper(),
+ c2s_pid :: pid(),
+ xmpp_ver = <<"">> :: binary(),
+ inactivity_timer :: reference(),
+ wait_timer :: reference(),
+ wait_timeout = ?DEFAULT_WAIT :: timeout(),
+ inactivity_timeout = ?DEFAULT_INACTIVITY :: timeout(),
+ prev_rid = 0 :: non_neg_integer(),
+ prev_key = <<"">> :: binary(),
+ prev_poll :: erlang:timestamp(),
+ max_concat = unlimited :: unlimited | non_neg_integer(),
+ responses = gb_trees:empty() :: gb_tree(),
+ receivers = gb_trees:empty() :: gb_tree(),
+ shaped_receivers = queue:new() :: queue(),
+ ip :: inet:ip_address(),
+ max_requests = 1 :: non_neg_integer()}).
+
+-record(body,
+ {http_reason = <<"">> :: binary(),
+ attrs = [] :: [{any(), any()}],
+ els = [] :: [xml_stream:xml_stream_el()],
+ size = 0 :: non_neg_integer()}).
+
start(#body{attrs = Attrs} = Body, IP, SID) ->
- XMPPDomain = get_attr('to', Attrs),
+ XMPPDomain = get_attr(to, Attrs),
Node = ejabberd_cluster:get_node(SID),
- SupervisorProc = {gen_mod:get_module_proc(XMPPDomain, ?PROCNAME), Node},
- case catch supervisor:start_child(SupervisorProc, [Body, IP, SID]) of
- {ok, Pid} ->
- {ok, Pid};
- {'EXIT', {noproc, _}} ->
- check_bosh_module(XMPPDomain),
- {error, module_not_loaded};
- Err ->
- ?ERROR_MSG("Failed to start BOSH session: ~p", [Err]),
- {error, Err}
+ SupervisorProc = {gen_mod:get_module_proc(XMPPDomain,
+ ?PROCNAME),
+ Node},
+ case catch supervisor:start_child(SupervisorProc,
+ [Body, IP, SID])
+ of
+ {ok, Pid} -> {ok, Pid};
+ {'EXIT', {noproc, _}} ->
+ check_bosh_module(XMPPDomain),
+ {error, module_not_loaded};
+ Err ->
+ ?ERROR_MSG("Failed to start BOSH session: ~p", [Err]),
+ {error, Err}
end.
start(StateName, State) ->
- ?GEN_FSM:start_link(?MODULE, [StateName, State], ?FSMOPTS).
+ (?GEN_FSM):start_link(?MODULE, [StateName, State],
+ ?FSMOPTS).
start_link(Body, IP, SID) ->
- ?GEN_FSM:start_link(?MODULE, [Body, IP, SID], ?FSMOPTS).
+ (?GEN_FSM):start_link(?MODULE, [Body, IP, SID],
+ ?FSMOPTS).
-send({http_bind, _FsmRef, _IP}, _Packet) ->
- {error, badarg}.
+send({http_bind, FsmRef, IP}, Packet) ->
+ send_xml({http_bind, FsmRef, IP}, Packet).
send_xml({http_bind, FsmRef, _IP}, Packet) ->
- case catch ?GEN_FSM:sync_send_all_state_event(
- FsmRef, {send_xml, Packet}, ?SEND_TIMEOUT) of
- {'EXIT', {timeout, _}} ->
- {error, timeout};
- {'EXIT', _} ->
- {error, einval};
- Res ->
- Res
+ case catch (?GEN_FSM):sync_send_all_state_event(FsmRef,
+ {send_xml, Packet},
+ ?SEND_TIMEOUT)
+ of
+ {'EXIT', {timeout, _}} -> {error, timeout};
+ {'EXIT', _} -> {error, einval};
+ Res -> Res
end.
setopts({http_bind, FsmRef, _IP}, Opts) ->
case lists:member({active, once}, Opts) of
- true ->
- ?GEN_FSM:send_all_state_event(FsmRef, {activate, self()});
- _ ->
- case lists:member({active, false}, Opts) of
- true ->
- case catch ?GEN_FSM:sync_send_all_state_event(
- FsmRef, deactivate_socket) of
- {'EXIT', _} ->
- {error, einval};
- Res ->
- Res
- end;
- _ ->
- ok
- end
+ true ->
+ (?GEN_FSM):send_all_state_event(FsmRef,
+ {activate, self()});
+ _ ->
+ case lists:member({active, false}, Opts) of
+ true ->
+ case catch (?GEN_FSM):sync_send_all_state_event(FsmRef,
+ deactivate_socket)
+ of
+ {'EXIT', _} -> {error, einval};
+ Res -> Res
+ end;
+ _ -> ok
+ end
end.
-controlling_process(_Socket, _Pid) ->
- ok.
+controlling_process(_Socket, _Pid) -> ok.
custom_receiver({http_bind, FsmRef, _IP}) ->
{receiver, ?MODULE, FsmRef}.
become_controller(FsmRef, C2SPid) ->
- ?GEN_FSM:send_all_state_event(FsmRef, {become_controller, C2SPid}).
+ (?GEN_FSM):send_all_state_event(FsmRef,
+ {become_controller, C2SPid}).
change_controller({http_bind, FsmRef, _IP}, C2SPid) ->
become_controller(FsmRef, C2SPid).
-reset_stream({http_bind, _FsmRef, _IP}) ->
- ok.
+reset_stream({http_bind, _FsmRef, _IP}) -> ok.
change_shaper({http_bind, FsmRef, _IP}, Shaper) ->
- ?GEN_FSM:send_all_state_event(FsmRef, {change_shaper, Shaper}).
+ (?GEN_FSM):send_all_state_event(FsmRef,
+ {change_shaper, Shaper}).
monitor({http_bind, FsmRef, _IP}) ->
erlang:monitor(process, FsmRef).
close({http_bind, FsmRef, _IP}) ->
- catch ?GEN_FSM:sync_send_all_state_event(FsmRef, close).
+ catch (?GEN_FSM):sync_send_all_state_event(FsmRef,
+ close).
-sockname(_Socket) ->
- {ok, {{0,0,0,0}, 0}}.
+sockname(_Socket) -> {ok, {{0, 0, 0, 0}, 0}}.
-peername({http_bind, _FsmRef, IP}) ->
- {ok, IP}.
+peername({http_bind, _FsmRef, IP}) -> {ok, IP}.
migrate(FsmRef, Node, After) ->
erlang:send_after(After, FsmRef, {migrate, Node}).
-process_request(Data, IP) ->
+process_request(Data, IP, Type) ->
Opts1 = ejabberd_c2s_config:get_c2s_limits(),
- Opts = [{xml_socket, true} | Opts1],
- MaxStanzaSize =
- case lists:keysearch(max_stanza_size, 1, Opts) of
- {value, {_, Size}} -> Size;
- _ -> infinity
- end,
+ Opts = case Type of
+ xml ->
+ [{xml_socket, true} | Opts1];
+ json ->
+ Opts1
+ end,
+ MaxStanzaSize = case lists:keysearch(max_stanza_size, 1,
+ Opts)
+ of
+ {value, {_, Size}} -> Size;
+ _ -> infinity
+ end,
PayloadSize = iolist_size(Data),
if PayloadSize > MaxStanzaSize ->
- http_error(403, "Request Too Large");
+ http_error(403, <<"Request Too Large">>, Type);
true ->
- case decode_body(Data, PayloadSize) of
- {ok, #body{attrs = Attrs} = Body} ->
- SID = get_attr('sid', Attrs),
- To = get_attr('to', Attrs),
- if SID == "", To == "" ->
- %% Initial request which lacks "to" attribute
- bosh_response(
- #body{http_reason = "Missing 'to' attribute",
- attrs = [{type, "terminate"},
- {condition, "improper-addressing"}]});
- SID == "" ->
- %% Initial request
- case start(Body, IP, make_sid()) of
- {ok, Pid} ->
- process_request(Pid, Body, IP);
- _Err ->
- bosh_response(
- #body{http_reason =
- "Failed to start BOSH session",
- attrs = [{type, "terminate"},
- {condition,
- "internal-server-error"}]})
- end;
- true ->
- case mod_bosh:find_session(SID) of
- {ok, Pid} ->
- process_request(Pid, Body, IP);
- error ->
- bosh_response(
- #body{http_reason = "Session ID mismatch",
- attrs = [{type, "terminate"},
- {condition,
- "item-not-found"}]})
- end
- end;
- {error, Reason} ->
- http_error(400, Reason)
- end
+ case decode_body(Data, PayloadSize, Type) of
+ {ok, #body{attrs = Attrs} = Body} ->
+ SID = get_attr(sid, Attrs),
+ To = get_attr(to, Attrs),
+ if SID == <<"">>, To == <<"">> ->
+ bosh_response(#body{http_reason =
+ <<"Missing 'to' attribute">>,
+ attrs =
+ [{type, <<"terminate">>},
+ {condition,
+ <<"improper-addressing">>}]},
+ Type);
+ SID == <<"">> ->
+ case start(Body, IP, make_sid()) of
+ {ok, Pid} -> process_request(Pid, Body, IP, Type);
+ _Err ->
+ bosh_response(#body{http_reason =
+ <<"Failed to start BOSH session">>,
+ attrs =
+ [{type, <<"terminate">>},
+ {condition,
+ <<"internal-server-error">>}]},
+ Type)
+ end;
+ true ->
+ case mod_bosh:find_session(SID) of
+ {ok, Pid} -> process_request(Pid, Body, IP, Type);
+ error ->
+ bosh_response(#body{http_reason =
+ <<"Session ID mismatch">>,
+ attrs =
+ [{type, <<"terminate">>},
+ {condition,
+ <<"item-not-found">>}]},
+ Type)
+ end
+ end;
+ {error, Reason} -> http_error(400, Reason, Type)
+ end
end.
-process_request(Pid, Req, _IP) ->
- case catch ?GEN_FSM:sync_send_event(Pid, Req, infinity) of
- #body{} = Resp ->
- bosh_response(Resp);
- {'EXIT', {Reason, _}} when Reason == noproc; Reason == normal ->
- bosh_response(#body{http_reason = "BOSH session not found",
- attrs = [{type, "terminate"},
- {condition,
- "item-not-found"}]});
- {'EXIT', _} ->
- bosh_response(#body{http_reason = "Unexpected error",
- attrs = [{type, "terminate"},
- {condition, "internal-server-error"}]})
+process_request(Pid, Req, _IP, Type) ->
+ case catch (?GEN_FSM):sync_send_event(Pid, Req,
+ infinity)
+ of
+ #body{} = Resp -> bosh_response(Resp, Type);
+ {'EXIT', {Reason, _}}
+ when Reason == noproc; Reason == normal ->
+ bosh_response(#body{http_reason =
+ <<"BOSH session not found">>,
+ attrs =
+ [{type, <<"terminate">>},
+ {condition, <<"item-not-found">>}]},
+ Type);
+ {'EXIT', _} ->
+ bosh_response(#body{http_reason =
+ <<"Unexpected error">>,
+ attrs =
+ [{type, <<"terminate">>},
+ {condition, <<"internal-server-error">>}]},
+ Type)
end.
-%%%===================================================================
-%%% gen_fsm callbacks
-%%%===================================================================
init([#body{attrs = Attrs}, IP, SID]) ->
- %% Read c2s options from the first ejabberd_c2s configuration in
- %% the config file listen section
- %% TODO: We should have different access and shaper values for
- %% each connector. The default behaviour should be however to use
- %% the default c2s restrictions if not defined for the current
- %% connector.
Opts1 = ejabberd_c2s_config:get_c2s_limits(),
Opts2 = [{xml_socket, true} | Opts1],
Shaper = none,
ShaperState = shaper:new(Shaper),
Socket = make_socket(self(), IP),
XMPPVer = get_attr('xmpp:version', Attrs),
- XMPPDomain = get_attr('to', Attrs),
- {InBuf, Opts} = case gen_mod:get_module_opt(XMPPDomain, mod_bosh,
- prebind, false) of
+ XMPPDomain = get_attr(to, Attrs),
+ {InBuf, Opts} = case gen_mod:get_module_opt(
+ XMPPDomain,
+ mod_bosh, prebind,
+ fun(B) when is_boolean(B) -> B end,
+ false) of
true ->
JID = make_random_jid(XMPPDomain),
{buf_new(), [{jid, JID} | Opts2]};
@@ -281,314 +310,333 @@ init([#body{attrs = Attrs}, IP, SID]) ->
{buf_in([make_xmlstreamstart(XMPPDomain, XMPPVer)],
buf_new()),
Opts2}
- end,
- ejabberd_socket:start(ejabberd_c2s, ?MODULE, Socket, Opts),
- Inactivity = gen_mod:get_module_opt(XMPPDomain, mod_bosh,
- max_inactivity, ?DEFAULT_INACTIVITY),
- MaxConcat = gen_mod:get_module_opt(XMPPDomain, mod_bosh,
- max_concat, unlimited),
- State = #state{host = XMPPDomain,
- sid = SID,
- ip = IP,
- xmpp_ver = XMPPVer,
- el_ibuf = InBuf,
- max_concat = MaxConcat,
- el_obuf = buf_new(),
- inactivity_timeout = Inactivity,
- shaper_state = ShaperState},
+ end,
+ ejabberd_socket:start(ejabberd_c2s, ?MODULE, Socket,
+ Opts),
+ Inactivity = gen_mod:get_module_opt(XMPPDomain,
+ mod_bosh, max_inactivity,
+ fun(I) when is_integer(I), I>0 -> I end,
+ ?DEFAULT_INACTIVITY),
+ MaxConcat = gen_mod:get_module_opt(XMPPDomain, mod_bosh, max_concat,
+ fun(unlimited) -> unlimited;
+ (N) when is_integer(N), N>0 -> N
+ end, unlimited),
+ State = #state{host = XMPPDomain, sid = SID, ip = IP,
+ xmpp_ver = XMPPVer, el_ibuf = InBuf,
+ max_concat = MaxConcat, el_obuf = buf_new(),
+ inactivity_timeout = Inactivity,
+ shaper_state = ShaperState},
NewState = restart_inactivity_timer(State),
mod_bosh:open_session(SID, self()),
{ok, wait_for_session, NewState};
init([StateName, State]) ->
mod_bosh:open_session(State#state.sid, self()),
case State#state.c2s_pid of
- C2SPid when is_pid(C2SPid) ->
- NewSocket = make_socket(self(), State#state.ip),
- C2SPid ! {change_socket, NewSocket},
- NewState = restart_inactivity_timer(State),
- {ok, StateName, NewState};
- _ ->
- %% TODO: it seems like we're losing the connection :-/
- {stop, normal}
+ C2SPid when is_pid(C2SPid) ->
+ NewSocket = make_socket(self(), State#state.ip),
+ C2SPid ! {change_socket, NewSocket},
+ NewState = restart_inactivity_timer(State),
+ {ok, StateName, NewState};
+ _ -> {stop, normal}
end.
wait_for_session(_Event, State) ->
- ?ERROR_MSG("unexpected event in 'wait_for_session': ~p", [_Event]),
+ ?ERROR_MSG("unexpected event in 'wait_for_session': ~p",
+ [_Event]),
{next_state, wait_for_session, State}.
-wait_for_session(#body{attrs = Attrs} = Req, From, State) ->
- RID = get_attr('rid', Attrs),
- ?DEBUG("got request:~n"
- "** RequestID: ~p~n"
- "** Request: ~p~n"
- "** From: ~p~n"
- "** State: ~p",
- [RID, Req, From, State]),
- Wait = min(get_attr('wait', Attrs, undefined), ?DEFAULT_WAIT),
- Hold = min(get_attr('hold', Attrs, undefined), ?DEFAULT_HOLD),
- NewKey = get_attr('newkey', Attrs),
- Type = get_attr('type', Attrs),
+wait_for_session(#body{attrs = Attrs} = Req, From,
+ State) ->
+ RID = get_attr(rid, Attrs),
+ ?DEBUG("got request:~n** RequestID: ~p~n** Request: "
+ "~p~n** From: ~p~n** State: ~p",
+ [RID, Req, From, State]),
+ Wait = min(get_attr(wait, Attrs, undefined),
+ ?DEFAULT_WAIT),
+ Hold = min(get_attr(hold, Attrs, undefined),
+ ?DEFAULT_HOLD),
+ NewKey = get_attr(newkey, Attrs),
+ Type = get_attr(type, Attrs),
Requests = Hold + 1,
{PollTime, Polling} = if Wait == 0, Hold == 0 ->
- {now(), [{polling, ?DEFAULT_POLLING}]};
- true ->
- {undefined, []}
- end,
- MaxPause = gen_mod:get_module_opt(State#state.host, mod_bosh,
- max_pause, ?DEFAULT_MAXPAUSE),
- Resp = #body{attrs = [{sid, State#state.sid},
- {wait, Wait},
- {ver, ?BOSH_VERSION},
- {polling, ?DEFAULT_POLLING},
- {inactivity, State#state.inactivity_timeout},
- {hold, Hold},
- {'xmpp:restartlogic', true},
- {requests, Requests},
- {secure, true},
- {maxpause, MaxPause},
- {'xmlns:xmpp', ?NS_BOSH},
- {'xmlns:stream', ?NS_STREAM},
- {from, State#state.host}|Polling]},
- {ShaperState, _} = shaper:update(State#state.shaper_state, Req#body.size),
+ {now(), [{polling, ?DEFAULT_POLLING}]};
+ true -> {undefined, []}
+ end,
+ MaxPause = gen_mod:get_module_opt(State#state.host,
+ mod_bosh, max_pause,
+ fun(I) when is_integer(I), I>0 -> I end,
+ ?DEFAULT_MAXPAUSE),
+ Resp = #body{attrs =
+ [{sid, State#state.sid}, {wait, Wait},
+ {ver, ?BOSH_VERSION}, {polling, ?DEFAULT_POLLING},
+ {inactivity, State#state.inactivity_timeout},
+ {hold, Hold}, {'xmpp:restartlogic', true},
+ {requests, Requests}, {secure, true},
+ {maxpause, MaxPause}, {'xmlns:xmpp', ?NS_BOSH},
+ {'xmlns:stream', ?NS_STREAM}, {from, State#state.host}
+ | Polling]},
+ {ShaperState, _} =
+ shaper:update(State#state.shaper_state, Req#body.size),
State1 = State#state{wait_timeout = Wait,
- prev_rid = RID,
- prev_key = NewKey,
- prev_poll = PollTime,
- shaper_state = ShaperState,
- max_requests = Requests},
+ prev_rid = RID, prev_key = NewKey,
+ prev_poll = PollTime, shaper_state = ShaperState,
+ max_requests = Requests},
Els = maybe_add_xmlstreamend(Req#body.els, Type),
State2 = route_els(State1, Els),
{State3, RespEls} = get_response_els(State2),
State4 = stop_inactivity_timer(State3),
case RespEls of
- [] ->
- State5 = restart_wait_timer(State4),
- Receivers = gb_trees:insert(RID, {From, Resp},
- State5#state.receivers),
- {next_state, active, State5#state{receivers = Receivers}};
- _ ->
- reply_next_state(State4, Resp#body{els = RespEls}, RID, From)
+ [] ->
+ State5 = restart_wait_timer(State4),
+ Receivers = gb_trees:insert(RID, {From, Resp},
+ State5#state.receivers),
+ {next_state, active,
+ State5#state{receivers = Receivers}};
+ _ ->
+ reply_next_state(State4, Resp#body{els = RespEls}, RID,
+ From)
end;
wait_for_session(_Event, _From, State) ->
- ?ERROR_MSG("unexpected sync event in 'wait_for_session': ~p", [_Event]),
+ ?ERROR_MSG("unexpected sync event in 'wait_for_session': ~p",
+ [_Event]),
{reply, {error, badarg}, wait_for_session, State}.
active({#body{} = Body, From}, State) ->
active1(Body, From, State);
active(_Event, State) ->
- ?ERROR_MSG("unexpected event in 'active': ~p", [_Event]),
+ ?ERROR_MSG("unexpected event in 'active': ~p",
+ [_Event]),
{next_state, active, State}.
-active(#body{attrs = Attrs, size = Size} = Req, From, State) ->
- ?DEBUG("got request:~n"
- "** Request: ~p~n"
- "** From: ~p~n"
- "** State: ~p",
- [Req, From, State]),
- {ShaperState, Pause} = shaper:update(State#state.shaper_state, Size),
+active(#body{attrs = Attrs, size = Size} = Req, From,
+ State) ->
+ ?DEBUG("got request:~n** Request: ~p~n** From: "
+ "~p~n** State: ~p",
+ [Req, From, State]),
+ {ShaperState, Pause} =
+ shaper:update(State#state.shaper_state, Size),
State1 = State#state{shaper_state = ShaperState},
if Pause > 0 ->
- QLen = queue:len(State1#state.shaped_receivers),
- if QLen < ?MAX_SHAPED_REQUESTS_QUEUE_LEN ->
- TRef = start_shaper_timer(Pause),
- Q = queue:in({TRef, From, Req}, State1#state.shaped_receivers),
- State2 = stop_inactivity_timer(State1),
- {next_state, active, State2#state{shaped_receivers = Q}};
- true ->
- RID = get_attr('rid', Attrs),
- reply_stop(State1,
- #body{http_reason = "Too many requests",
- attrs = [{"type", "terminate"},
- {"condition", "policy-violation"}]},
- From, RID)
- end;
- true ->
- active1(Req, From, State1)
+ QLen = queue:len(State1#state.shaped_receivers),
+ if QLen < (?MAX_SHAPED_REQUESTS_QUEUE_LEN) ->
+ TRef = start_shaper_timer(Pause),
+ Q = queue:in({TRef, From, Req},
+ State1#state.shaped_receivers),
+ State2 = stop_inactivity_timer(State1),
+ {next_state, active,
+ State2#state{shaped_receivers = Q}};
+ true ->
+ RID = get_attr(rid, Attrs),
+ reply_stop(State1,
+ #body{http_reason = <<"Too many requests">>,
+ attrs =
+ [{<<"type">>, <<"terminate">>},
+ {<<"condition">>,
+ <<"policy-violation">>}]},
+ From, RID)
+ end;
+ true -> active1(Req, From, State1)
end;
active(_Event, _From, State) ->
- ?ERROR_MSG("unexpected sync event in 'active': ~p", [_Event]),
+ ?ERROR_MSG("unexpected sync event in 'active': ~p",
+ [_Event]),
{reply, {error, badarg}, active, State}.
active1(#body{attrs = Attrs} = Req, From, State) ->
- RID = get_attr('rid', Attrs),
- Key = get_attr('key', Attrs),
+ RID = get_attr(rid, Attrs),
+ Key = get_attr(key, Attrs),
IsValidKey = is_valid_key(State#state.prev_key, Key),
IsOveractivity = is_overactivity(State#state.prev_poll),
- Type = get_attr('type', Attrs),
- if RID > State#state.prev_rid + State#state.max_requests ->
- reply_stop(State,
- #body{http_reason = "Request ID is out of range",
- attrs = [{"type", "terminate"},
- {"condition", "item-not-found"}]},
- From, RID);
+ Type = get_attr(type, Attrs),
+ if RID >
+ State#state.prev_rid + State#state.max_requests ->
+ reply_stop(State,
+ #body{http_reason = <<"Request ID is out of range">>,
+ attrs =
+ [{<<"type">>, <<"terminate">>},
+ {<<"condition">>, <<"item-not-found">>}]},
+ From, RID);
RID > State#state.prev_rid + 1 ->
- State1 = restart_inactivity_timer(State),
- %% TODO: gb_trees:insert/3 may raise an exception
- Receivers = gb_trees:insert(RID, {From, Req},
- State1#state.receivers),
- {next_state, active, State1#state{receivers = Receivers}};
+ State1 = restart_inactivity_timer(State),
+ Receivers = gb_trees:insert(RID, {From, Req},
+ State1#state.receivers),
+ {next_state, active,
+ State1#state{receivers = Receivers}};
RID =< State#state.prev_rid ->
- %% TODO: do we need to check 'key' here? It seems so...
- case gb_trees:lookup(RID, State#state.responses) of
- {value, PrevBody} ->
- {next_state, active, do_reply(State, From, PrevBody, RID)};
- none ->
- reply_stop(State,
- #body{http_reason = "Request ID is out of range",
- attrs = [{"type", "terminate"},
- {"condition", "item-not-found"}]},
- From, RID)
- end;
+ case gb_trees:lookup(RID, State#state.responses) of
+ {value, PrevBody} ->
+ {next_state, active,
+ do_reply(State, From, PrevBody, RID)};
+ none ->
+ reply_stop(State,
+ #body{http_reason =
+ <<"Request ID is out of range">>,
+ attrs =
+ [{<<"type">>, <<"terminate">>},
+ {<<"condition">>,
+ <<"item-not-found">>}]},
+ From, RID)
+ end;
not IsValidKey ->
- reply_stop(State,
- #body{http_reason = "Session key mismatch",
- attrs = [{"type", "terminate"},
- {"condition", "item-not-found"}]},
- From, RID);
+ reply_stop(State,
+ #body{http_reason = <<"Session key mismatch">>,
+ attrs =
+ [{<<"type">>, <<"terminate">>},
+ {<<"condition">>, <<"item-not-found">>}]},
+ From, RID);
IsOveractivity ->
- reply_stop(State,
- #body{http_reason = "Too many requests",
- attrs = [{"type", "terminate"},
- {"condition", "policy-violation"}]},
- From, RID);
+ reply_stop(State,
+ #body{http_reason = <<"Too many requests">>,
+ attrs =
+ [{<<"type">>, <<"terminate">>},
+ {<<"condition">>, <<"policy-violation">>}]},
+ From, RID);
true ->
- State1 = stop_inactivity_timer(State),
- State2 = stop_wait_timer(State1),
- Els = case get_attr('xmpp:restart', Attrs, false) of
- true ->
- XMPPDomain = get_attr('to', Attrs,
- State#state.host),
- XMPPVer = get_attr('xmpp:version', Attrs,
- State#state.xmpp_ver),
- [make_xmlstreamstart(XMPPDomain, XMPPVer)];
- false ->
- Req#body.els
- end,
- State3 = route_els(State2, maybe_add_xmlstreamend(Els, Type)),
- {State4, RespEls} = get_response_els(State3),
- NewKey = get_attr('newkey', Attrs, Key),
- Pause = get_attr('pause', Attrs, undefined),
- NewPoll = case State#state.prev_poll of
- undefined -> undefined;
- _ -> now()
- end,
- State5 = State4#state{prev_poll = NewPoll,
- prev_key = NewKey},
- if Type == "terminate" ->
- reply_stop(State5, #body{http_reason = "Session close",
- attrs = [{"type", "terminate"}],
- els = RespEls}, From, RID);
- Pause /= undefined ->
- State6 = drop_holding_receiver(State5),
- State7 = restart_inactivity_timer(State6, Pause),
- InBuf = buf_in(RespEls, State7#state.el_ibuf),
- {next_state, active,
- State7#state{prev_rid = RID,
- el_ibuf = InBuf}};
- RespEls == [] ->
- State6 = drop_holding_receiver(State5),
- State7 = restart_wait_timer(State6),
- %% TODO: gb_trees:insert/3 may raise an exception
- Receivers = gb_trees:insert(RID, {From, #body{}},
- State7#state.receivers),
- {next_state, active, State7#state{prev_rid = RID,
- receivers = Receivers}};
- true ->
- State6 = drop_holding_receiver(State5),
- reply_next_state(State6#state{prev_rid = RID},
- #body{els = RespEls}, RID, From)
- end
+ State1 = stop_inactivity_timer(State),
+ State2 = stop_wait_timer(State1),
+ Els = case get_attr('xmpp:restart', Attrs, false) of
+ true ->
+ XMPPDomain = get_attr(to, Attrs, State#state.host),
+ XMPPVer = get_attr('xmpp:version', Attrs,
+ State#state.xmpp_ver),
+ [make_xmlstreamstart(XMPPDomain, XMPPVer)];
+ false -> Req#body.els
+ end,
+ State3 = route_els(State2,
+ maybe_add_xmlstreamend(Els, Type)),
+ {State4, RespEls} = get_response_els(State3),
+ NewKey = get_attr(newkey, Attrs, Key),
+ Pause = get_attr(pause, Attrs, undefined),
+ NewPoll = case State#state.prev_poll of
+ undefined -> undefined;
+ _ -> now()
+ end,
+ State5 = State4#state{prev_poll = NewPoll,
+ prev_key = NewKey},
+ if Type == <<"terminate">> ->
+ reply_stop(State5,
+ #body{http_reason = <<"Session close">>,
+ attrs = [{<<"type">>, <<"terminate">>}],
+ els = RespEls},
+ From, RID);
+ Pause /= undefined ->
+ State6 = drop_holding_receiver(State5),
+ State7 = restart_inactivity_timer(State6, Pause),
+ InBuf = buf_in(RespEls, State7#state.el_ibuf),
+ {next_state, active,
+ State7#state{prev_rid = RID, el_ibuf = InBuf}};
+ RespEls == [] ->
+ State6 = drop_holding_receiver(State5),
+ State7 = restart_wait_timer(State6),
+ Receivers = gb_trees:insert(RID, {From, #body{}},
+ State7#state.receivers),
+ {next_state, active,
+ State7#state{prev_rid = RID, receivers = Receivers}};
+ true ->
+ State6 = drop_holding_receiver(State5),
+ reply_next_state(State6#state{prev_rid = RID},
+ #body{els = RespEls}, RID, From)
+ end
end.
-handle_event({become_controller, C2SPid}, StateName, State) ->
+handle_event({become_controller, C2SPid}, StateName,
+ State) ->
State1 = route_els(State#state{c2s_pid = C2SPid}),
{next_state, StateName, State1};
-handle_event({change_shaper, Shaper}, StateName, State) ->
+handle_event({change_shaper, Shaper}, StateName,
+ State) ->
NewShaperState = shaper:new(Shaper),
- {next_state, StateName, State#state{shaper_state = NewShaperState}};
+ {next_state, StateName,
+ State#state{shaper_state = NewShaperState}};
handle_event(_Event, StateName, State) ->
- ?ERROR_MSG("unexpected event in '~s': ~p", [StateName, _Event]),
+ ?ERROR_MSG("unexpected event in '~s': ~p",
+ [StateName, _Event]),
{next_state, StateName, State}.
-handle_sync_event({send_xml, {xmlstreamstart, _, _} = El}, _From,
- StateName, State) when State#state.xmpp_ver >= "1.0" ->
- %% Avoid sending empty <body/> element
+handle_sync_event({send_xml,
+ {xmlstreamstart, _, _} = El},
+ _From, StateName, State)
+ when State#state.xmpp_ver >= <<"1.0">> ->
OutBuf = buf_in([El], State#state.el_obuf),
{reply, ok, StateName, State#state{el_obuf = OutBuf}};
-handle_sync_event({send_xml, El}, _From, StateName, State) ->
+handle_sync_event({send_xml, El}, _From, StateName,
+ State) ->
OutBuf = buf_in([El], State#state.el_obuf),
State1 = State#state{el_obuf = OutBuf},
- case gb_trees:lookup(State1#state.prev_rid, State1#state.receivers) of
- {value, {From, Body}} ->
- {State2, Els} = get_response_els(State1),
- {reply, ok, StateName, reply(State2, Body#body{els = Els},
- State2#state.prev_rid, From)};
- none ->
- State2 = case queue:out(State1#state.shaped_receivers) of
- {{value, {TRef, From, Body}}, Q} ->
- cancel_timer(TRef),
- ?GEN_FSM:send_event(self(), {Body, From}),
- State1#state{shaped_receivers = Q};
- _ ->
- State1
- end,
- {reply, ok, StateName, State2}
+ case gb_trees:lookup(State1#state.prev_rid,
+ State1#state.receivers)
+ of
+ {value, {From, Body}} ->
+ {State2, Els} = get_response_els(State1),
+ {reply, ok, StateName,
+ reply(State2, Body#body{els = Els},
+ State2#state.prev_rid, From)};
+ none ->
+ State2 = case queue:out(State1#state.shaped_receivers)
+ of
+ {{value, {TRef, From, Body}}, Q} ->
+ cancel_timer(TRef),
+ (?GEN_FSM):send_event(self(), {Body, From}),
+ State1#state{shaped_receivers = Q};
+ _ -> State1
+ end,
+ {reply, ok, StateName, State2}
end;
handle_sync_event(close, _From, _StateName, State) ->
{stop, normal, State};
-handle_sync_event(deactivate_socket, _From, StateName, StateData) ->
- {reply, ok, StateName, StateData#state{c2s_pid = undefined}};
+handle_sync_event(deactivate_socket, _From, StateName,
+ StateData) ->
+ {reply, ok, StateName,
+ StateData#state{c2s_pid = undefined}};
handle_sync_event(_Event, _From, StateName, State) ->
- ?ERROR_MSG("unexpected sync event in '~s': ~p", [StateName, _Event]),
+ ?ERROR_MSG("unexpected sync event in '~s': ~p",
+ [StateName, _Event]),
{reply, {error, badarg}, StateName, State}.
handle_info({timeout, TRef, wait_timeout}, StateName,
- #state{wait_timer = TRef} = State) ->
+ #state{wait_timer = TRef} = State) ->
{next_state, StateName, drop_holding_receiver(State)};
handle_info({timeout, TRef, inactive}, _StateName,
- #state{inactivity_timer = TRef} = State) ->
+ #state{inactivity_timer = TRef} = State) ->
{stop, normal, State};
-handle_info({timeout, TRef, shaper_timeout}, StateName, State) ->
+handle_info({timeout, TRef, shaper_timeout}, StateName,
+ State) ->
case queue:out(State#state.shaped_receivers) of
- {{value, {TRef, From, Req}}, Q} ->
- ?GEN_FSM:send_event(self(), {Req, From}),
- {next_state, StateName, State#state{shaped_receivers = Q}};
- {{value, _}, _} ->
- ?ERROR_MSG("shaper_timeout mismatch:~n"
- "** TRef: ~p~n"
- "** State: ~p",
- [TRef, State]),
- {stop, normal, State};
- _ ->
- {next_state, StateName, State}
+ {{value, {TRef, From, Req}}, Q} ->
+ (?GEN_FSM):send_event(self(), {Req, From}),
+ {next_state, StateName,
+ State#state{shaped_receivers = Q}};
+ {{value, _}, _} ->
+ ?ERROR_MSG("shaper_timeout mismatch:~n** TRef: ~p~n** "
+ "State: ~p",
+ [TRef, State]),
+ {stop, normal, State};
+ _ -> {next_state, StateName, State}
end;
handle_info({migrate, Node}, StateName, State) ->
if Node /= node() ->
- NewState = bounce_receivers(State, migrated),
- {migrate, NewState,
- {Node, ?MODULE, start, [StateName, NewState]}, 0};
- true ->
- {next_state, StateName, State}
+ NewState = bounce_receivers(State, migrated),
+ {migrate, NewState,
+ {Node, ?MODULE, start, [StateName, NewState]}, 0};
+ true -> {next_state, StateName, State}
end;
handle_info(_Info, StateName, State) ->
- ?ERROR_MSG("unexpected info:~n"
- "** Msg: ~p~n"
- "** StateName: ~p",
- [_Info, StateName]),
+ ?ERROR_MSG("unexpected info:~n** Msg: ~p~n** StateName: ~p",
+ [_Info, StateName]),
{next_state, StateName, State}.
terminate({migrated, ClonePid}, _StateName, State) ->
- ?INFO_MSG("Migrating session \"~s\" (c2s_pid = ~p) to ~p on node ~p",
- [State#state.sid, State#state.c2s_pid,
- ClonePid, node(ClonePid)]),
+ ?INFO_MSG("Migrating session \"~s\" (c2s_pid = "
+ "~p) to ~p on node ~p",
+ [State#state.sid, State#state.c2s_pid, ClonePid,
+ node(ClonePid)]),
mod_bosh:close_session(State#state.sid);
terminate(_Reason, _StateName, State) ->
mod_bosh:close_session(State#state.sid),
case State#state.c2s_pid of
- C2SPid when is_pid(C2SPid) ->
- ?GEN_FSM:send_event(C2SPid, closed);
- _ ->
- ok
+ C2SPid when is_pid(C2SPid) ->
+ (?GEN_FSM):send_event(C2SPid, closed);
+ _ -> ok
end,
bounce_receivers(State, closed),
bounce_els_from_obuf(State).
@@ -596,53 +644,57 @@ terminate(_Reason, _StateName, State) ->
code_change(_OldVsn, StateName, State, _Extra) ->
{ok, StateName, State}.
-print_state(State) ->
- State.
+print_state(State) -> State.
-%%%===================================================================
-%%% Internal functions
-%%%===================================================================
route_els(#state{el_ibuf = Buf} = State) ->
- route_els(State#state{el_ibuf = buf_new()}, buf_to_list(Buf)).
+ route_els(State#state{el_ibuf = buf_new()},
+ buf_to_list(Buf)).
route_els(State, Els) ->
case State#state.c2s_pid of
- C2SPid when is_pid(C2SPid) ->
- lists:foreach(
- fun(El) ->
- ?GEN_FSM:send_event(C2SPid, El)
- end, Els),
- State;
- _ ->
- InBuf = buf_in(Els, State#state.el_ibuf),
- State#state{el_ibuf = InBuf}
+ C2SPid when is_pid(C2SPid) ->
+ lists:foreach(fun (El) ->
+ (?GEN_FSM):send_event(C2SPid, El)
+ end,
+ Els),
+ State;
+ _ ->
+ InBuf = buf_in(Els, State#state.el_ibuf),
+ State#state{el_ibuf = InBuf}
end.
-get_response_els(#state{el_obuf = OutBuf, max_concat = MaxConcat} = State) ->
+get_response_els(#state{el_obuf = OutBuf,
+ max_concat = MaxConcat} =
+ State) ->
{Els, NewOutBuf} = buf_out(OutBuf, MaxConcat),
{State#state{el_obuf = NewOutBuf}, Els}.
reply(State, Body, RID, From) ->
State1 = restart_inactivity_timer(State),
- Receivers = gb_trees:delete_any(RID, State1#state.receivers),
+ Receivers = gb_trees:delete_any(RID,
+ State1#state.receivers),
State2 = do_reply(State1, From, Body, RID),
case catch gb_trees:take_smallest(Receivers) of
- {NextRID, {From1, Req}, Receivers1} when NextRID == RID + 1 ->
- ?GEN_FSM:send_event(self(), {Req, From1}),
- State2#state{receivers = Receivers1};
- _ ->
- State2#state{receivers = Receivers}
+ {NextRID, {From1, Req}, Receivers1}
+ when NextRID == RID + 1 ->
+ (?GEN_FSM):send_event(self(), {Req, From1}),
+ State2#state{receivers = Receivers1};
+ _ -> State2#state{receivers = Receivers}
end.
reply_next_state(State, Body, RID, From) ->
State1 = restart_inactivity_timer(State),
- Receivers = gb_trees:delete_any(RID, State1#state.receivers),
+ Receivers = gb_trees:delete_any(RID,
+ State1#state.receivers),
State2 = do_reply(State1, From, Body, RID),
case catch gb_trees:take_smallest(Receivers) of
- {NextRID, {From1, Req}, Receivers1} when NextRID == RID + 1 ->
- active(Req, From1, State2#state{receivers = Receivers1});
- _ ->
- {next_state, active, State2#state{receivers = Receivers}}
+ {NextRID, {From1, Req}, Receivers1}
+ when NextRID == RID + 1 ->
+ active(Req, From1,
+ State2#state{receivers = Receivers1});
+ _ ->
+ {next_state, active,
+ State2#state{receivers = Receivers}}
end.
reply_stop(State, Body, From, RID) ->
@@ -651,344 +703,407 @@ reply_stop(State, Body, From, RID) ->
drop_holding_receiver(State) ->
RID = State#state.prev_rid,
case gb_trees:lookup(RID, State#state.receivers) of
- {value, {From, Body}} ->
- State1 = restart_inactivity_timer(State),
- Receivers = gb_trees:delete_any(RID, State1#state.receivers),
- State2 = State1#state{receivers = Receivers},
- do_reply(State2, From, Body, RID);
- none ->
- State
+ {value, {From, Body}} ->
+ State1 = restart_inactivity_timer(State),
+ Receivers = gb_trees:delete_any(RID,
+ State1#state.receivers),
+ State2 = State1#state{receivers = Receivers},
+ do_reply(State2, From, Body, RID);
+ none -> State
end.
do_reply(State, From, Body, RID) ->
- ?DEBUG("send reply:~n"
- "** RequestID: ~p~n"
- "** Reply: ~p~n"
- "** To: ~p~n"
- "** State: ~p",
- [RID, Body, From, State]),
- ?GEN_FSM:reply(From, Body),
- Responses = gb_trees:delete_any(RID, State#state.responses),
+ ?DEBUG("send reply:~n** RequestID: ~p~n** Reply: "
+ "~p~n** To: ~p~n** State: ~p",
+ [RID, Body, From, State]),
+ (?GEN_FSM):reply(From, Body),
+ Responses = gb_trees:delete_any(RID,
+ State#state.responses),
Responses1 = case gb_trees:size(Responses) of
- N when N < State#state.max_requests; N == 0 ->
- Responses;
- _ ->
- element(3, gb_trees:take_smallest(Responses))
- end,
+ N when N < State#state.max_requests; N == 0 ->
+ Responses;
+ _ -> element(3, gb_trees:take_smallest(Responses))
+ end,
Responses2 = gb_trees:insert(RID, Body, Responses1),
State#state{responses = Responses2}.
bounce_receivers(State, Reason) ->
Receivers = gb_trees:to_list(State#state.receivers),
- ShapedReceivers = lists:map(
- fun({_, From, #body{attrs = Attrs} = Body}) ->
- RID = get_attr('rid', Attrs),
- {RID, {From, Body}}
- end, queue:to_list(State#state.shaped_receivers)),
- lists:foldl(
- fun({RID, {From, Body}}, AccState) ->
- NewBody = if Reason == closed ->
- #body{http_reason = "Session closed",
- attrs = [{type, "terminate"},
- {condition, "other-request"}]};
- Reason == migrated ->
- Body#body{http_reason = "Session migrated"}
- end,
- do_reply(AccState, From, NewBody, RID)
- end, State, Receivers ++ ShapedReceivers).
+ ShapedReceivers = lists:map(fun ({_, From,
+ #body{attrs = Attrs} = Body}) ->
+ RID = get_attr(rid, Attrs),
+ {RID, {From, Body}}
+ end,
+ queue:to_list(State#state.shaped_receivers)),
+ lists:foldl(fun ({RID, {From, Body}}, AccState) ->
+ NewBody = if Reason == closed ->
+ #body{http_reason =
+ <<"Session closed">>,
+ attrs =
+ [{type, <<"terminate">>},
+ {condition,
+ <<"other-request">>}]};
+ Reason == migrated ->
+ Body#body{http_reason =
+ <<"Session migrated">>}
+ end,
+ do_reply(AccState, From, NewBody, RID)
+ end,
+ State, Receivers ++ ShapedReceivers).
bounce_els_from_obuf(State) ->
- lists:foreach(
- fun({xmlstreamelement, El}) ->
- case El of
- {xmlelement, Name, Attrs, _}
- when Name == "presence";
- Name == "message";
- Name == "iq" ->
- FromS = xml:get_attr_s("from", Attrs),
- ToS = xml:get_attr_s("to", Attrs),
- case {jlib:string_to_jid(FromS),
- jlib:string_to_jid(ToS)} of
- {#jid{} = From, #jid{} = To} ->
- ejabberd_router:route(From, To, El);
- _ ->
- ok
- end;
- _ ->
- ok
- end;
- (_) ->
- ok
- end, buf_to_list(State#state.el_obuf)).
-
-is_valid_key("", "") ->
- true;
-is_valid_key([_|_] = PrevKey, [_|_] = Key) ->
- sha:sha(Key) == PrevKey;
-is_valid_key(_, _) ->
- false.
-
-is_overactivity(undefined) ->
- false;
+ lists:foreach(fun ({xmlstreamelement, El}) ->
+ case El of
+ #xmlel{name = Name, attrs = Attrs}
+ when Name == <<"presence">>;
+ Name == <<"message">>;
+ Name == <<"iq">> ->
+ FromS = xml:get_attr_s(<<"from">>, Attrs),
+ ToS = xml:get_attr_s(<<"to">>, Attrs),
+ case {jlib:string_to_jid(FromS),
+ jlib:string_to_jid(ToS)}
+ of
+ {#jid{} = From, #jid{} = To} ->
+ ejabberd_router:route(From, To, El);
+ _ -> ok
+ end;
+ _ -> ok
+ end;
+ (_) -> ok
+ end,
+ buf_to_list(State#state.el_obuf)).
+
+is_valid_key(<<"">>, <<"">>) -> true;
+is_valid_key(PrevKey, Key) ->
+ sha:sha(Key) == PrevKey.
+
+is_overactivity(undefined) -> false;
is_overactivity(PrevPoll) ->
- PollPeriod = timer:now_diff(now(), PrevPoll) div 1000000,
- if PollPeriod < ?DEFAULT_POLLING ->
- true;
- true ->
- false
+ PollPeriod = timer:now_diff(now(), PrevPoll) div
+ 1000000,
+ if PollPeriod < (?DEFAULT_POLLING) -> true;
+ true -> false
end.
make_xmlstreamstart(XMPPDomain, Version) ->
VersionEl = case Version of
- "" -> [];
- _ -> [{"version", Version}]
- end,
- {xmlstreamstart, "stream:stream",
- [{"to", XMPPDomain},
- {"xmlns", ?NS_CLIENT},
- {"xmlns:xmpp", ?NS_BOSH},
- {"xmlns:stream", ?NS_STREAM}|VersionEl]}.
-
-maybe_add_xmlstreamend(Els, "terminate") ->
- Els ++ [{xmlstreamend, "stream:stream"}];
-maybe_add_xmlstreamend(Els, _) ->
- Els.
-
-encode_body(#body{attrs = Attrs, els = Els}) ->
- Attrs1 = lists:map(
- fun({K, V}) when is_atom(K) ->
- AmK = atom_to_list(K),
- case V of
- true -> {AmK, "true"};
- false -> {AmK, "false"};
- [_|_] -> {AmK, V};
- I when is_integer(I), I >= 0 ->
- {AmK, integer_to_list(I)}
- end;
- ({K, V}) ->
- {K, V}
- end, Attrs),
- Attrs2 = [{"xmlns", ?NS_HTTP_BIND}|Attrs1],
- {Attrs3, XMLs} =
- lists:foldr(
- fun({xmlstreamraw, XML}, {AttrsAcc, XMLBuf}) ->
- {AttrsAcc, [XML|XMLBuf]};
- ({xmlstreamelement, {xmlelement, "stream:error", _, _} = El},
- {AttrsAcc, XMLBuf}) ->
- {[{"type", "terminate"},
- {"condition", "remote-stream-error"},
- {"xmlns:stream", ?NS_STREAM}|AttrsAcc],
- [xml:element_to_binary(El)|XMLBuf]};
- ({xmlstreamelement, {xmlelement, "stream:features", _, _} = El}, {AttrsAcc, XMLBuf}) ->
- {lists:keystore("xmlns:stream", 1, AttrsAcc, {"xmlns:stream", ?NS_STREAM}),
- [xml:element_to_binary(El)|XMLBuf]};
- ({xmlstreamelement, El}, {AttrsAcc, XMLBuf}) ->
- {AttrsAcc, [xml:element_to_binary(El)|XMLBuf]};
- ({xmlstreamend, _}, {AttrsAcc, XMLBuf}) ->
- {[{"type", "terminate"},
- {"condition", "remote-stream-error"}|AttrsAcc], XMLBuf};
- ({xmlstreamstart, "stream:stream", SAttrs}, {AttrsAcc, XMLBuf}) ->
- StreamID = xml:get_attr_s("id", SAttrs),
- NewAttrs = case xml:get_attr_s("version", SAttrs) of
- "" ->
- [{"authid", StreamID}|AttrsAcc];
- V ->
- lists:keystore("xmlns:xmpp", 1, [{"xmpp:version", V},
- {"authid", StreamID} | AttrsAcc],
- {"xmlns:xmpp", ?NS_BOSH})
- end,
- {NewAttrs, XMLBuf};
- ({xmlstreamerror, _}, {AttrsAcc, XMLBuf}) ->
- {[{"type", "terminate"},
- {"condition", "remote-stream-error"}|AttrsAcc],
- XMLBuf};
- (_, Acc) ->
- Acc
- end, {Attrs2, []}, Els),
+ <<"">> -> [];
+ _ -> [{<<"version">>, Version}]
+ end,
+ {xmlstreamstart, <<"stream:stream">>,
+ [{<<"to">>, XMPPDomain}, {<<"xmlns">>, ?NS_CLIENT},
+ {<<"xmlns:xmpp">>, ?NS_BOSH},
+ {<<"xmlns:stream">>, ?NS_STREAM}
+ | VersionEl]}.
+
+maybe_add_xmlstreamend(Els, <<"terminate">>) ->
+ Els ++ [{xmlstreamend, <<"stream:stream">>}];
+maybe_add_xmlstreamend(Els, _) -> Els.
+
+encode_body(#body{attrs = Attrs, els = Els}, Type) ->
+ Attrs1 = lists:map(fun ({K, V}) when is_atom(K) ->
+ AmK = iolist_to_binary(atom_to_list(K)),
+ case V of
+ true -> {AmK, <<"true">>};
+ false -> {AmK, <<"false">>};
+ I when is_integer(I), I >= 0 ->
+ {AmK, iolist_to_binary(integer_to_list(I))};
+ _ -> {AmK, V}
+ end;
+ ({K, V}) -> {K, V}
+ end,
+ Attrs),
+ Attrs2 = [{<<"xmlns">>, ?NS_HTTP_BIND} | Attrs1],
+ {Attrs3, XMLs} = lists:foldr(fun ({xmlstreamraw, XML},
+ {AttrsAcc, XMLBuf}) ->
+ {AttrsAcc, [XML | XMLBuf]};
+ ({xmlstreamelement,
+ #xmlel{name = <<"stream:error">>} = El},
+ {AttrsAcc, XMLBuf}) ->
+ {[{<<"type">>, <<"terminate">>},
+ {<<"condition">>,
+ <<"remote-stream-error">>},
+ {<<"xmlns:stream">>, ?NS_STREAM}
+ | AttrsAcc],
+ [encode_element(El, Type) | XMLBuf]};
+ ({xmlstreamelement,
+ #xmlel{name = <<"stream:features">>} =
+ El},
+ {AttrsAcc, XMLBuf}) ->
+ {lists:keystore(<<"xmlns:stream">>, 1,
+ AttrsAcc,
+ {<<"xmlns:stream">>,
+ ?NS_STREAM}),
+ [encode_element(El, Type) | XMLBuf]};
+ ({xmlstreamelement,
+ #xmlel{name = Name, attrs = EAttrs} = El},
+ {AttrsAcc, XMLBuf})
+ when Name == <<"message">>;
+ Name == <<"presence">>;
+ Name == <<"iq">> ->
+ NewAttrs = lists:keystore(
+ <<"xmlns">>, 1, EAttrs,
+ {<<"xmlns">>, ?NS_CLIENT}),
+ NewEl = El#xmlel{attrs = NewAttrs},
+ {AttrsAcc,
+ [encode_element(NewEl, Type) | XMLBuf]};
+ ({xmlstreamelement, El},
+ {AttrsAcc, XMLBuf}) ->
+ {AttrsAcc,
+ [encode_element(El, Type) | XMLBuf]};
+ ({xmlstreamend, _}, {AttrsAcc, XMLBuf}) ->
+ {[{<<"type">>, <<"terminate">>},
+ {<<"condition">>,
+ <<"remote-stream-error">>}
+ | AttrsAcc],
+ XMLBuf};
+ ({xmlstreamstart, <<"stream:stream">>,
+ SAttrs},
+ {AttrsAcc, XMLBuf}) ->
+ StreamID = xml:get_attr_s(<<"id">>,
+ SAttrs),
+ NewAttrs = case
+ xml:get_attr_s(<<"version">>,
+ SAttrs)
+ of
+ <<"">> ->
+ [{<<"authid">>,
+ StreamID}
+ | AttrsAcc];
+ V ->
+ lists:keystore(<<"xmlns:xmpp">>,
+ 1,
+ [{<<"xmpp:version">>,
+ V},
+ {<<"authid">>,
+ StreamID}
+ | AttrsAcc],
+ {<<"xmlns:xmpp">>,
+ ?NS_BOSH})
+ end,
+ {NewAttrs, XMLBuf};
+ ({xmlstreamerror, _},
+ {AttrsAcc, XMLBuf}) ->
+ {[{<<"type">>, <<"terminate">>},
+ {<<"condition">>,
+ <<"remote-stream-error">>}
+ | AttrsAcc],
+ XMLBuf};
+ (_, Acc) -> Acc
+ end,
+ {Attrs2, []}, Els),
case XMLs of
- [] ->
- ["<body", attrs_to_list(Attrs3), "/>"];
- _ ->
- ["<body", attrs_to_list(Attrs3), $>, XMLs, "</body>"]
+ [] when Type == xml ->
+ [<<"<body">>, attrs_to_list(Attrs3), <<"/>">>];
+ _ when Type == xml ->
+ [<<"<body">>, attrs_to_list(Attrs3), $>, XMLs,
+ <<"</body>">>];
+ _ ->
+ jiffy:encode(
+ xmpp_json:to_json(
+ #xmlel{name = <<"body">>,
+ attrs = Attrs3,
+ children = XMLs}))
end.
-decode_body(BodyXML, Size) ->
- case xml_stream:parse_element(BodyXML) of
- {xmlelement, "body", Attrs, Els} ->
- case attrs_to_body_attrs(Attrs) of
- {error, _} = Err ->
- Err;
- BodyAttrs ->
- case get_attr(rid, BodyAttrs) of
- "" ->
- {error, "Missing \"rid\" attribute"};
- _ ->
- Els1 = lists:flatmap(
- fun({xmlelement, _, _, _} = El) ->
- [{xmlstreamelement, El}];
- (_) ->
- []
- end, Els),
- {ok, #body{attrs = BodyAttrs,
- size = Size,
- els = Els1}}
- end
- end;
- {xmlelement, _, _, _} ->
- {error, "Unexpected payload"};
- _ ->
- {error, "XML is not well-formed"}
+encode_element(El, xml) ->
+ xml:element_to_binary(El);
+encode_element(El, json) ->
+ El.
+
+decode_body(Data, Size, Type) ->
+ case decode(Data, Type) of
+ #xmlel{name = <<"body">>, attrs = Attrs,
+ children = Els} ->
+ case attrs_to_body_attrs(Attrs) of
+ {error, _} = Err -> Err;
+ BodyAttrs ->
+ case get_attr(rid, BodyAttrs) of
+ <<"">> -> {error, <<"Missing \"rid\" attribute">>};
+ _ ->
+ Els1 = lists:flatmap(fun (#xmlel{} = El) ->
+ [{xmlstreamelement, El}];
+ (_) -> []
+ end,
+ Els),
+ {ok, #body{attrs = BodyAttrs, size = Size, els = Els1}}
+ end
+ end;
+ #xmlel{} -> {error, <<"Unexpected payload">>};
+ _ when Type == xml ->
+ {error, <<"XML is not well-formed">>};
+ _ when Type == json ->
+ {error, <<"JSON is not well-formed">>}
+ end.
+
+decode(Data, xml) ->
+ xml_stream:parse_element(Data);
+decode(Data, json) ->
+ case catch jiffy:decode(Data) of
+ {'EXIT', _} ->
+ {error, bad_json};
+ JSON ->
+ case catch xmpp_json:from_json(JSON) of
+ {xmlstreamelement, NewEl} ->
+ NewEl;
+ XML ->
+ XML
+ end
end.
attrs_to_body_attrs(Attrs) ->
- lists:foldl(
- fun(_, {error, Reason}) ->
- {error, Reason};
- ({Attr, Val}, Acc) ->
- try
- case Attr of
- "ver" -> [{ver, Val}|Acc];
- "xmpp:version" -> [{'xmpp:version', Val}|Acc];
- "type" -> [{type, Val}|Acc];
- "key" -> [{key, Val}|Acc];
- "newkey" -> [{newkey, Val}|Acc];
- "xmlns" -> Val = ?NS_HTTP_BIND, Acc;
- "secure" -> [{secure, to_bool(Val)}|Acc];
- "xmpp:restart" -> [{'xmpp:restart', to_bool(Val)}|Acc];
- "to" -> [{to, [_|_] = jlib:nameprep(Val)}|Acc];
- "wait" -> [{wait, to_int(Val, 0)}|Acc];
- "ack" -> [{ack, to_int(Val, 0)}|Acc];
- "sid" -> [{sid, Val}|Acc];
- "hold" -> [{hold, to_int(Val, 0)}|Acc];
- "rid" -> [{rid, to_int(Val, 0)}|Acc];
- "pause" -> [{pause, to_int(Val, 0)}|Acc];
- _ -> [{Attr, Val}|Acc]
- end
- catch _:_ ->
- {error, "Invalid \"" ++ Attr ++ "\" attribute"}
- end
- end, [], Attrs).
+ lists:foldl(fun (_, {error, Reason}) -> {error, Reason};
+ ({Attr, Val}, Acc) ->
+ try case Attr of
+ <<"ver">> -> [{ver, Val} | Acc];
+ <<"xmpp:version">> ->
+ [{'xmpp:version', Val} | Acc];
+ <<"type">> -> [{type, Val} | Acc];
+ <<"key">> -> [{key, Val} | Acc];
+ <<"newkey">> -> [{newkey, Val} | Acc];
+ <<"xmlns">> -> Val = (?NS_HTTP_BIND), Acc;
+ <<"secure">> -> [{secure, to_bool(Val)} | Acc];
+ <<"xmpp:restart">> ->
+ [{'xmpp:restart', to_bool(Val)} | Acc];
+ <<"to">> ->
+ [{to, jlib:nameprep(Val)} | Acc];
+ <<"wait">> -> [{wait, to_int(Val, 0)} | Acc];
+ <<"ack">> -> [{ack, to_int(Val, 0)} | Acc];
+ <<"sid">> -> [{sid, Val} | Acc];
+ <<"hold">> -> [{hold, to_int(Val, 0)} | Acc];
+ <<"rid">> -> [{rid, to_int(Val, 0)} | Acc];
+ <<"pause">> -> [{pause, to_int(Val, 0)} | Acc];
+ _ -> [{Attr, Val} | Acc]
+ end
+ catch
+ _:_ ->
+ {error,
+ <<"Invalid \"", Attr/binary, "\" attribute">>}
+ end
+ end,
+ [], Attrs).
to_int(S, Min) ->
- case list_to_integer(S) of
- I when I >= Min ->
- I;
- _ ->
- erlang:error(badarg)
+ case jlib:binary_to_integer(S) of
+ I when I >= Min -> I;
+ _ -> erlang:error(badarg)
end.
-to_bool("true") -> true;
-to_bool("1") -> true;
-to_bool("false") -> false;
-to_bool("0") -> false.
+to_bool(<<"true">>) -> true;
+to_bool(<<"1">>) -> true;
+to_bool(<<"false">>) -> false;
+to_bool(<<"0">>) -> false.
-attrs_to_list(Attrs) ->
- [attr_to_list(A) || A <- Attrs].
+attrs_to_list(Attrs) -> [attr_to_list(A) || A <- Attrs].
attr_to_list({Name, Value}) ->
[$\s, Name, $=, $', xml:crypt(Value), $'].
-bosh_response(Body) ->
- {200, Body#body.http_reason, ?HEADER, encode_body(Body)}.
+bosh_response(Body, Type) ->
+ CType = case Type of
+ xml -> ?CT_XML;
+ json -> ?CT_JSON
+ end,
+ {200, Body#body.http_reason, ?HEADER(CType),
+ encode_body(Body, Type)}.
+
+http_error(Status, Reason, Type) ->
+ CType = case Type of
+ xml -> ?CT_XML;
+ json -> ?CT_JSON
+ end,
+ {Status, Reason, ?HEADER(CType), <<"">>}.
-http_error(Status, Reason) ->
- {Status, Reason, ?HEADER, ""}.
+make_sid() -> sha:sha(randoms:get_string()).
-make_sid() ->
- sha:sha(randoms:get_string()).
+-compile({no_auto_import, [{min, 2}]}).
--compile({no_auto_import,[min/2]}).
min(undefined, B) -> B;
min(A, B) -> erlang:min(A, B).
-%% Check that mod_bosh has been defined in config file.
-%% Print a warning in log file if this is not the case.
check_bosh_module(XmppDomain) ->
case gen_mod:is_loaded(XmppDomain, mod_bosh) of
- true -> ok;
- false -> ?ERROR_MSG("You are trying to use BOSH (HTTP Bind) in host ~p,"
- " but the module mod_bosh is not started in"
- " that host. Configure your BOSH client to connect"
- " to the correct host, or add your desired host to"
- " the configuration, or check your 'modules'"
- " section in your ejabberd configuration file.",
- [XmppDomain])
+ true -> ok;
+ false ->
+ ?ERROR_MSG("You are trying to use BOSH (HTTP Bind) "
+ "in host ~p, but the module mod_bosh "
+ "is not started in that host. Configure "
+ "your BOSH client to connect to the correct "
+ "host, or add your desired host to the "
+ "configuration, or check your 'modules' "
+ "section in your ejabberd configuration "
+ "file.",
+ [XmppDomain])
end.
-get_attr(Attr, Attrs) ->
- get_attr(Attr, Attrs, "").
+get_attr(Attr, Attrs) -> get_attr(Attr, Attrs, <<"">>).
get_attr(Attr, Attrs, Default) ->
case lists:keysearch(Attr, 1, Attrs) of
- {value, {_, Val}} ->
- Val;
- _ ->
- Default
+ {value, {_, Val}} -> Val;
+ _ -> Default
end.
-buf_new() ->
- queue:new().
+buf_new() -> queue:new().
buf_in(Xs, Buf) ->
- lists:foldl(
- fun(X, Acc) ->
- queue:in(X, Acc)
- end, Buf, Xs).
+ lists:foldl(fun (X, Acc) -> queue:in(X, Acc) end, Buf,
+ Xs).
buf_out(Buf, Num) when is_integer(Num), Num > 0 ->
buf_out(Buf, Num, []);
-buf_out(Buf, _) ->
- {queue:to_list(Buf), buf_new()}.
+buf_out(Buf, _) -> {queue:to_list(Buf), buf_new()}.
-buf_out(Buf, 0, Els) ->
- {lists:reverse(Els), Buf};
+buf_out(Buf, 0, Els) -> {lists:reverse(Els), Buf};
buf_out(Buf, I, Els) ->
case queue:out(Buf) of
- {{value, El}, NewBuf} ->
- buf_out(NewBuf, I-1, [El|Els]);
- {empty, _} ->
- buf_out(Buf, 0, Els)
+ {{value, El}, NewBuf} ->
+ buf_out(NewBuf, I - 1, [El | Els]);
+ {empty, _} -> buf_out(Buf, 0, Els)
end.
-buf_to_list(Buf) ->
- queue:to_list(Buf).
+buf_to_list(Buf) -> queue:to_list(Buf).
cancel_timer(TRef) when is_reference(TRef) ->
- ?GEN_FSM:cancel_timer(TRef);
-cancel_timer(_) ->
- false.
+ (?GEN_FSM):cancel_timer(TRef);
+cancel_timer(_) -> false.
restart_timer(TRef, Timeout, Msg) ->
cancel_timer(TRef),
erlang:start_timer(timer:seconds(Timeout), self(), Msg).
-restart_inactivity_timer(#state{inactivity_timeout = Timeout} = State) ->
+restart_inactivity_timer(#state{inactivity_timeout =
+ Timeout} =
+ State) ->
restart_inactivity_timer(State, Timeout).
-restart_inactivity_timer(#state{inactivity_timer = TRef} = State, Timeout) ->
+restart_inactivity_timer(#state{inactivity_timer =
+ TRef} =
+ State,
+ Timeout) ->
NewTRef = restart_timer(TRef, Timeout, inactive),
State#state{inactivity_timer = NewTRef}.
-stop_inactivity_timer(#state{inactivity_timer = TRef} = State) ->
+stop_inactivity_timer(#state{inactivity_timer = TRef} =
+ State) ->
cancel_timer(TRef),
State#state{inactivity_timer = undefined}.
restart_wait_timer(#state{wait_timer = TRef,
- wait_timeout = Timeout} = State) ->
+ wait_timeout = Timeout} =
+ State) ->
NewTRef = restart_timer(TRef, Timeout, wait_timeout),
State#state{wait_timer = NewTRef}.
stop_wait_timer(#state{wait_timer = TRef} = State) ->
- cancel_timer(TRef),
- State#state{wait_timer = undefined}.
+ cancel_timer(TRef), State#state{wait_timer = undefined}.
start_shaper_timer(Timeout) ->
erlang:start_timer(Timeout, self(), shaper_timeout).
make_random_jid(Host) ->
- %% Copied from cyrsasl_anonymous.erl
- User = lists:concat([randoms:get_string() | tuple_to_list(now())]),
+ User = iolist_to_binary([randoms:get_string()
+ | tuple_to_list(now())]),
jlib:make_jid(User, Host, randoms:get_string()).
-make_socket(Pid, IP) ->
- {http_bind, Pid, IP}.
+make_socket(Pid, IP) -> {http_bind, Pid, IP}.
diff --git a/src/web/ejabberd_http.erl b/src/web/ejabberd_http.erl
index ffc78449f..dc1c2dd09 100644
--- a/src/web/ejabberd_http.erl
+++ b/src/web/ejabberd_http.erl
@@ -25,735 +25,648 @@
%%%----------------------------------------------------------------------
-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]).
+-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("jlib.hrl").
--include("ejabberd_http.hrl").
--record(state, {sockmod,
- socket,
- request_method,
- request_version,
- request_path,
- request_auth,
- request_keepalive,
- request_content_length,
- request_lang = "en",
- %% 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":
- %%
- %% {5280, ejabberd_http, [http_poll, web_admin,
- %% {request_handlers, [{["test", "module"], mod_test_web}]}]}
- %%
- request_handlers = [],
- request_host,
- request_port,
- request_tp,
- request_headers = [],
- end_of_request = false,
- default_host,
- trail = <<>>,
- websocket_handlers = []
- }).
+-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").
+ <<"<?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").
-
+ <<"<!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]).
+ supervisor:start_child(ejabberd_http_sup,
+ [SockData, Opts]).
start_link(SockData, Opts) ->
- {ok, proc_lib:spawn_link(ejabberd_http, init, [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),
+ 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,
+ {SockMod1, Socket1} = if TLSEnabled ->
+ inet:setopts(Socket, [{recbuf, 8192}]),
+ {ok, TLSSocket} = tls:tcp_to_tls(Socket,
+ TLSOpts),
+ {tls, TLSSocket};
+ true -> {SockMod, Socket}
+ end,
case SockMod1 of
- gen_tcp ->
- inet:setopts(Socket1, [{packet, http}, {recbuf, 8192}]);
- _ ->
- ok
+ gen_tcp ->
+ inet:setopts(Socket1, [{packet, http_bin}, {recbuf, 8192}]);
+ _ -> ok
end,
-
- %% XXX bard: for backward compatibility, expand in Opts:
- %% web_admin -> {["admin"], ejabberd_web_admin}
- %% http_bind -> {["http-bind"], mod_http_bind}
- %% http_poll -> {["http-poll"], ejabberd_http_poll}
- %% register -> {["register"], mod_register_web}
-
- RequestHandlers =
- case lists:keysearch(request_handlers, 1, Opts) of
- {value, {request_handlers, H}} -> H;
- false -> []
- end ++
- case lists:member(captcha, Opts) of
- true -> [{["captcha"], ejabberd_captcha}];
- false -> []
- end ++
- case lists:member(register, Opts) of
- true -> [{["register"], mod_register_web}];
- false -> []
- end ++
- case lists:member(web_admin, Opts) of
- true -> [{["admin"], ejabberd_web_admin}];
- false -> []
- end ++
- case lists:member(http_bind, Opts) of
- true -> [{["http-bind"], mod_http_bind}];
- false -> []
- end ++
- case lists:member(http_poll, Opts) of
- true -> [{["http-poll"], ejabberd_http_poll}];
- false -> []
- 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,
+ 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, undefined),
+ 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},
+ State = #state{sockmod = SockMod1, socket = Socket1,
+ default_host = DefaultHost,
+ request_handlers = RequestHandlers,
+ websocket_handlers = WebSocketHandlers},
receive_headers(State).
+become_controller(_Pid) -> ok.
-become_controller(_Pid) ->
- ok.
-
-socket_type() ->
- raw.
-
+socket_type() -> raw.
-send_text(_State, none) ->
- exit(normal);
+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)
+ 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} = State) ->
+receive_headers(#state{trail = Trail} = State) ->
SockMod = State#state.sockmod,
Socket = State#state.socket,
Data = SockMod:recv(Socket, 0, 300000),
case State#state.sockmod of
- gen_tcp ->
- NewState = process_header(State, Data, true),
- case NewState#state.end_of_request of
- true ->
- ok;
- _ ->
- receive_headers(NewState)
- end;
- _ ->
- case Data of
- {ok, D} ->
- parse_headers(State#state{trail = <<Trail/binary, D/binary>>});
- {error, _} ->
- ok
- end
+ gen_tcp ->
+ NewState = process_header(State, Data),
+ case NewState#state.end_of_request of
+ true -> ok;
+ _ -> receive_headers(NewState)
+ end;
+ _ ->
+ case Data of
+ {ok, D} ->
+ parse_headers(State#state{trail =
+ <<Trail/binary, D/binary>>});
+ {error, _} -> ok
+ end
end.
parse_headers(#state{trail = <<>>} = State) ->
receive_headers(State);
-parse_headers(#state{request_method = Method, trail = Data} = State) ->
+parse_headers(#state{request_method = Method,
+ trail = Data} =
+ State) ->
PktType = case Method of
- undefined -> http;
- _ -> httph
- end,
- case decode_packet(PktType, Data) of
- {ok, Pkt, Rest} ->
- NewState = process_header(State#state{trail = Rest}, {ok, Pkt}, false),
- case NewState#state.end_of_request of
- true ->
- ok;
- _ ->
- parse_headers(NewState)
- end;
- {more, _} ->
- receive_headers(State#state{trail = Data});
- _ ->
- ok
+ 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, Normalize) ->
+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};
- _ -> 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 list_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_list(Name) andalso Normalize ->
- 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 ->
- case SockMod of
- gen_tcp ->
- inet:setopts(Socket, [{packet, http}]);
- _ ->
- ok
- end,
- #state{sockmod = SockMod,
- socket = Socket,
- request_handlers = State#state.request_handlers};
- _ ->
- #state{end_of_request = true,
- request_handlers = State#state.request_handlers}
- end;
- {error, _Reason} ->
- #state{end_of_request = true,
- request_handlers = State#state.request_handlers};
- _ ->
- #state{end_of_request = true,
- request_handlers = State#state.request_handlers}
+ {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 ->
+ case SockMod of
+ gen_tcp -> inet:setopts(Socket, [{packet, http_bin}]);
+ _ -> ok
+ end,
+ #state{sockmod = SockMod, socket = Socket,
+ request_handlers = State#state.request_handlers};
+ _ ->
+ #state{end_of_request = true,
+ request_handlers = State#state.request_handlers}
+ end;
+ {error, _Reason} ->
+ #state{end_of_request = true,
+ request_handlers = State#state.request_handlers};
+ _ ->
+ #state{end_of_request = true,
+ request_handlers = State#state.request_handlers}
end.
-add_header(Name, Value, State) ->
+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).
+-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(undefined, Provided) -> Provided;
get_host_really_served(Default, Provided) ->
case lists:member(Provided, ?MYHOSTS) of
- true -> Provided;
- false -> Default
+ true -> 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] = string:tokens(HostPort, ":"),
+ [Host | PortList] = str:tokens(HostPort, <<":">>),
case {SockMod, PortList} of
- {gen_tcp, []} ->
- {Host, 80, http};
- {gen_tcp, [Port]} ->
- {Host, list_to_integer(Port), http};
- {tls, []} ->
- {Host, 443, https};
- {tls, [Port]} ->
- {Host, list_to_integer(Port), https}
+ {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, #ws{} = Ws)->
- [{HandlerPathPrefix, HandlerModule, HandlerOpts} | HandlersLeft] = Handlers,
- case (lists:prefix(HandlerPathPrefix, Ws#ws.path) or
- (HandlerPathPrefix==Ws#ws.path)) of
- true ->
- LocalPath = lists:nthtail(length(HandlerPathPrefix), Ws#ws.path),
- ejabberd_hooks:run(ws_debug, [{LocalPath, Ws}]),
- Protocol = case lists:keysearch(protocol, 1, HandlerOpts) of
- {value, {protocol, P}} -> P;
- false -> undefined
- end,
- Origins = case lists:keysearch(origins, 1, HandlerOpts) of
- {value, {origins, O}} -> O;
- false -> []
- end,
- Auth = case lists:keysearch(auth, 1, HandlerOpts) of
- {value, {auth, A}} -> A;
- false -> undefined
- end,
- WS2 = Ws#ws{local_path = LocalPath,
- protocol=Protocol,
- acceptable_origins=Origins,
- auth_module=Auth},
- case ejabberd_websocket:is_acceptable(WS2) of
- true ->
- ejabberd_websocket:connect(WS2, HandlerModule);
- false ->
- process(HandlersLeft, Ws)
- end;
- false ->
- process(HandlersLeft, Ws)
+process([], _) -> ejabberd_web:error(not_found);
+process(Handlers, #ws{} = Ws) ->
+ [{HandlerPathPrefix, HandlerModule, HandlerOpts} | HandlersLeft] =
+ Handlers,
+ case lists:prefix(HandlerPathPrefix, Ws#ws.path) or
+ (HandlerPathPrefix == Ws#ws.path)
+ of
+ true ->
+ ?DEBUG("~p matches ~p",
+ [Ws#ws.path, HandlerPathPrefix]),
+ LocalPath = lists:nthtail(length(HandlerPathPrefix),
+ Ws#ws.path),
+ ejabberd_hooks:run(ws_debug, [{LocalPath, Ws}]),
+ Protocol = case lists:keysearch(protocol, 1,
+ HandlerOpts)
+ of
+ {value, {protocol, P}} -> P;
+ false -> undefined
+ end,
+ Origins = case lists:keysearch(origins, 1, HandlerOpts)
+ of
+ {value, {origins, O}} -> O;
+ false -> []
+ end,
+ Auth = case lists:keysearch(auth, 1, HandlerOpts) of
+ {value, {auth, A}} -> A;
+ false -> undefined
+ end,
+ WS2 = Ws#ws{local_path = LocalPath, protocol = Protocol,
+ acceptable_origins = Origins, auth_module = Auth},
+ case ejabberd_websocket:is_acceptable(WS2) of
+ true -> ejabberd_websocket:connect(WS2, HandlerModule);
+ false -> process(HandlersLeft, Ws)
+ end;
+ false -> process(HandlersLeft, Ws)
end;
process(Handlers, Request) ->
- [{HandlerPathPrefix, HandlerModule} | HandlersLeft] = Handlers,
-
- 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 = HandlerModule:process(LocalPath, Request),
- ejabberd_hooks:run(http_request_debug, [{LocalPath, Request}]),
- R;
- false ->
- process(HandlersLeft, Request)
+ %% Only the first element in the path prefix is checked
+ [{HandlerPathPrefix, HandlerModule} | HandlersLeft] =
+ Handlers,
+ case lists:prefix(HandlerPathPrefix,
+ Request#request.path)
+ or (HandlerPathPrefix == Request#request.path)
+ of
+ true ->
+ ?DEBUG("~p matches ~p",
+ [Request#request.path, HandlerPathPrefix]),
+ LocalPath = lists:nthtail(length(HandlerPathPrefix),
+ Request#request.path),
+ ?DEBUG("~p", [Request#request.headers]),
+ R = HandlerModule:process(LocalPath, Request),
+ ejabberd_hooks:run(http_request_debug,
+ [{LocalPath, Request}]),
+ R;
+ false -> process(HandlersLeft, Request)
end.
process_request(#state{request_method = Method,
- request_path = {abs_path, Path},
- request_auth = Auth,
- request_lang = Lang,
- request_handlers = RequestHandlers,
- request_host = Host,
- request_port = Port,
- request_tp = TP,
- request_headers = RequestHeaders,
+ request_path = {abs_path, Path}, request_auth = Auth,
+ request_lang = Lang, request_handlers = RequestHandlers,
+ request_host = Host, request_port = Port,
+ request_tp = TP, request_headers = RequestHeaders,
sockmod = SockMod,
websocket_handlers = WebSocketHandlers,
- socket = Socket} = State)
- when Method=:='GET' orelse Method=:='HEAD' orelse Method=:='DELETE' orelse Method=:='OPTIONS' ->
- case (catch url_decode_q_split(Path)) of
- {'EXIT', _} ->
- make_bad_request(State);
- {NPath, Query} ->
- LPath = [path_decode(NPE) || NPE <- string:tokens(NPath, "/")],
- LQuery = case (catch parse_urlencoded(Query)) of
- {'EXIT', _Reason} ->
- [];
- LQ ->
- LQ
- end,
- {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),
- %% XXX bard: This previously passed control to
- %% ejabberd_web:process_get, now passes it to a local
- %% procedure (process) that handles dispatching based on
- %% URL path prefix.
- case ejabberd_websocket:check(Path, RequestHeaders) of
- {true, VSN} ->
- {_, Origin} = case lists:keyfind("Sec-Websocket-Origin", 1, RequestHeaders) of
- false -> lists:keyfind("Origin", 1, RequestHeaders);
- Value -> Value
- end,
- Ws = #ws{socket = Socket,
- sockmod = SockMod,
- ws_autoexit = false,
- ip = IP,
- path = LPath,
- q = LQuery,
- vsn = VSN,
- host = Host,
- port = Port,
- origin = Origin,
- headers = RequestHeaders
- },
- process(WebSocketHandlers, Ws),
- none;
- false ->
- Request = #request{method = Method,
- path = LPath,
- q = LQuery,
- auth = Auth,
- lang = Lang,
- host = Host,
- port = Port,
- tp = TP,
- headers = RequestHeaders,
- ip = IP},
- case process(RequestHandlers, Request) of
- El when element(1, El) == xmlelement ->
- make_xhtml_output(State, 200, [], El);
- {Status, Headers, El} when
- element(1, El) == xmlelement ->
- make_xhtml_output(State, Status, Headers, El);
- Output when is_list(Output) or is_binary(Output) ->
- make_text_output(State, 200, [], Output);
- {Status, Headers, Output} when is_list(Output) or is_binary(Output) ->
- make_text_output(State, Status, Headers, Output);
- {Status, Reason, Headers, Output} when is_list(Output) or is_binary(Output) ->
- make_text_output(State, Status, Reason, Headers, Output)
- end
- end
+ socket = Socket} =
+ State)
+ when Method =:= 'GET' orelse
+ Method =:= 'HEAD' orelse
+ Method =:= 'DELETE' orelse Method =:= 'OPTIONS' ->
+ case catch url_decode_q_split(Path) of
+ {'EXIT', _} -> make_bad_request(State);
+ {NPath, Query} ->
+ LPath = [path_decode(NPE)
+ || NPE <- str:tokens(NPath, <<"/">>)],
+ LQuery = case catch parse_urlencoded(Query) of
+ {'EXIT', _Reason} -> [];
+ LQ -> LQ
+ end,
+ {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),
+ case ejabberd_websocket:check(Path, RequestHeaders) of
+ {true, VSN} ->
+ {_, Origin} = case
+ lists:keyfind(<<"Sec-Websocket-Origin">>, 1,
+ RequestHeaders)
+ of
+ false ->
+ lists:keyfind(<<"Origin">>, 1,
+ RequestHeaders);
+ Value -> Value
+ end,
+ Ws = #ws{socket = Socket, sockmod = SockMod,
+ ws_autoexit = false, ip = IP, path = LPath, q = LQuery,
+ vsn = VSN, host = Host, port = Port, origin = Origin,
+ headers = RequestHeaders},
+ process(WebSocketHandlers, Ws),
+ none;
+ false ->
+ Request = #request{method = Method, path = LPath,
+ q = LQuery, auth = Auth, lang = Lang,
+ host = Host, port = Port, tp = TP,
+ headers = RequestHeaders, ip = IP},
+ case process(RequestHandlers, Request) 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)
+ end
+ end
end;
-
process_request(#state{request_method = Method,
- request_path = {abs_path, Path},
- request_auth = Auth,
- request_content_length = Len,
- request_lang = Lang,
- sockmod = SockMod,
- socket = Socket,
- request_host = Host,
- request_port = Port,
- request_tp = TP,
+ request_path = {abs_path, Path}, request_auth = Auth,
+ request_content_length = Len, request_lang = Lang,
+ sockmod = SockMod, socket = Socket, request_host = Host,
+ request_port = Port, request_tp = TP,
request_headers = RequestHeaders,
- request_handlers = RequestHandlers} = State)
- when (Method=:='POST' orelse Method=:='PUT') andalso is_integer(Len) ->
- {ok, IPHere} =
- case SockMod of
- gen_tcp ->
- inet:peername(Socket);
- _ ->
- SockMod:peername(Socket)
- end,
- XFF = proplists:get_value('X-Forwarded-For', RequestHeaders, []),
+ request_handlers = RequestHandlers} =
+ State)
+ when (Method =:= 'POST' orelse Method =:= 'PUT') andalso
+ is_integer(Len) ->
+ {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),
case SockMod of
- gen_tcp ->
- inet:setopts(Socket, [{packet, 0}]);
- _ ->
- ok
+ gen_tcp -> inet:setopts(Socket, [{packet, 0}]);
+ _ -> ok
end,
Data = recv_data(State, Len),
?DEBUG("client data: ~p~n", [Data]),
- case (catch url_decode_q_split(Path)) of
- {'EXIT', _} ->
- make_bad_request(State);
- {NPath, _Query} ->
- LPath = [path_decode(NPE) || NPE <- string:tokens(NPath, "/")],
- LQuery = case (catch parse_urlencoded(Data)) of
- {'EXIT', _Reason} ->
- [];
- LQ ->
- LQ
- end,
- 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, Request) of
- El when element(1, El) == xmlelement ->
- make_xhtml_output(State, 200, [], El);
- {Status, Headers, El} when
- element(1, El) == xmlelement ->
- make_xhtml_output(State, Status, Headers, El);
- Output when is_list(Output) or is_binary(Output) ->
- make_text_output(State, 200, [], Output);
- {Status, Headers, Output} when is_list(Output) or is_binary(Output) ->
- make_text_output(State, Status, Headers, Output);
- {Status, Reason, Headers, Output} when is_list(Output) or is_binary(Output) ->
- make_text_output(State, Status, Reason, Headers, Output)
- end
+ case catch url_decode_q_split(Path) of
+ {'EXIT', _} -> make_bad_request(State);
+ {NPath, _Query} ->
+ LPath = [path_decode(NPE)
+ || NPE <- str:tokens(NPath, <<"/">>)],
+ LQuery = case catch parse_urlencoded(Data) of
+ {'EXIT', _Reason} -> [];
+ LQ -> LQ
+ end,
+ 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, Request) 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)
+ end
end;
-
-process_request(State) ->
- make_bad_request(State).
+process_request(State) -> make_bad_request(State).
make_bad_request(State) ->
- make_xhtml_output(State,
- 400,
- [],
- ejabberd_web:make_xhtml([{xmlelement, "h1", [],
- [{xmlcdata, "400 Bad Request"}]}])).
-
-%% Support for X-Forwarded-From
-analyze_ip_xff(IP, [], _Host) ->
- IP;
+ 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] = string:tokens(XFF, ", ")
- ++ [inet_parse:ntoa(IPLast)],
- TrustedProxies = case ejabberd_config:get_local_option(
- {trusted_proxies, Host}) of
- undefined -> [];
- TPs -> TPs
- end,
- IPClient = case is_ipchain_trusted(ProxiesIPs, TrustedProxies) of
- true ->
- {ok, IPFirst} = inet_parse:address(ClientIP),
- ?DEBUG("The IP ~w was replaced with ~w due to header "
- "X-Forwarded-For: ~s", [IPLast, IPFirst, XFF]),
- IPFirst;
- false ->
- IPLast
+ [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, all) -> true;
is_ipchain_trusted(UserIPs, TrustedIPs) ->
- [] == UserIPs -- ["127.0.0.1" | TrustedIPs].
+ [] == UserIPs -- [<<"127.0.0.1">> | TrustedIPs].
-recv_data(State, Len) ->
- recv_data(State, Len, []).
+recv_data(State, Len) -> recv_data(State, Len, <<>>).
-recv_data(_State, 0, Acc) ->
- binary_to_list(list_to_binary(Acc));
+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 - size(Data), [Acc | [Data]]);
- _ ->
- ""
- end;
- _ ->
- Trail = binary_to_list(State#state.trail),
- recv_data(State#state{trail = <<>>}, Len - length(Trail), [Acc | Trail])
+ <<>> ->
+ 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 ->
- list_to_binary([?HTML_DOCTYPE,
- element_to_string(XHTML)]);
- _ ->
- list_to_binary([?XHTML_DOCTYPE,
- element_to_string(XHTML)])
+ 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", integer_to_list(size(Data))} |
- Headers];
- _ ->
- [{"Content-Type", "text/html; charset=utf-8"},
- {"Content-Length", integer_to_list(size(Data))} |
- Headers]
+ 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} ->
- % not required for http versions < 1.1
- % but would make no harm
- [{"Connection", "close"} | Headers1]
+ 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 "
+ {1, 1} -> <<"HTTP/1.1 ">>;
+ _ -> <<"HTTP/1.0 ">>
end,
-
- H = lists:map(fun({Attr, Val}) ->
- [Attr, ": ", Val, "\r\n"];
- (_) ->
- []
- end, HeadersOut),
- SL = [Version, integer_to_list(Status), " ",
- code_to_phrase(Status), "\r\n"],
-
+ 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].
+ '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) when is_list(Text) ->
- make_text_output(State, Status, Reason, Headers, list_to_binary(Text));
-
-make_text_output(State, Status, Reason, Headers, Data) when is_binary(Data) ->
- Headers1 = case lists:keysearch("Content-Type", 1, Headers) of
- {value, _} ->
- [{"Content-Length", integer_to_list(size(Data))} |
- Headers];
- _ ->
- [{"Content-Type", "text/html; charset=utf-8"},
- {"Content-Length", integer_to_list(size(Data))} |
- Headers]
+ 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} ->
- % not required for http versions < 1.1
- % but would make no harm
- [{"Connection", "close"} | Headers1]
+ 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 "
+ {1, 1} -> <<"HTTP/1.1 ">>;
+ _ -> <<"HTTP/1.0 ">>
end,
-
- H = lists:map(fun({Attr, Val}) ->
- [Attr, ": ", Val, "\r\n"]
- end, HeadersOut),
+ H = lists:map(fun ({Attr, Val}) ->
+ [Attr, <<": ">>, Val, <<"\r\n">>]
+ end,
+ HeadersOut),
NewReason = case Reason of
- "" -> code_to_phrase(Status);
- _ -> Reason
- end,
- SL = [Version, integer_to_list(Status), " ",
- NewReason, "\r\n"],
-
+ <<"">> -> 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].
-
+ 'HEAD' -> <<"">>;
+ _ -> Data
+ end,
+ [SL, H, <<"\r\n">>, Data2].
parse_lang(Langs) ->
- case string:tokens(Langs, ",; ") of
- [First | _] ->
- First;
- [] ->
- "en"
+ case str:tokens(Langs, <<",; ">>) of
+ [First | _] -> First;
+ [] -> <<"en">>
end.
-element_to_string(El) ->
- case El of
- {xmlelement, Name, Attrs, Els} ->
- if
- Els /= [] ->
- [$<, Name, attrs_to_list(Attrs), $>,
- [element_to_string(E) || E <- Els],
- $<, $/, Name, $>];
- true ->
- [$<, Name, attrs_to_list(Attrs), $/, $>]
- end;
- {xmlcdata, CData} ->
- crypt(CData)
- end.
-
-attrs_to_list(Attrs) ->
- [attr_to_list(A) || A <- Attrs].
-
-attr_to_list({Name, Value}) ->
- [$\s, crypt(Name), $=, $", crypt(Value), $"].
-
-crypt(S) when is_list(S) ->
- [case C of
- $& -> "&amp;";
- $< -> "&lt;";
- $> -> "&gt;";
- $" -> "&quot;";
- $' -> "&#39;";
- _ -> C
- end || C <- S];
-crypt(S) when is_binary(S) ->
- crypt(binary_to_list(S)).
-
-
% Code below is taken (with some modifications) from the yaws webserver, which
% is distributed under the folowing license:
%
@@ -767,458 +680,204 @@ crypt(S) when is_binary(S) ->
% 2. Redistributions in binary form must reproduce the above copyright
% notice as well as this list of conditions.
-
-%% @doc Split the URL and return {Path, QueryPart}
url_decode_q_split(Path) ->
- url_decode_q_split(Path, []).
-url_decode_q_split([$?|T], Ack) ->
+ 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(Ack), T};
-url_decode_q_split([H|T], Ack) when H /= 0 ->
- url_decode_q_split(T, [H|Ack]);
-url_decode_q_split([], Ack) ->
- {path_norm_reverse(Ack), []}.
+ {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), <<>>}.
-%% @doc Decode a part of the URL and return string()
-path_decode(Path) ->
- path_decode(Path, []).
-path_decode([$%, Hi, Lo | Tail], 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);
+ if Hex == 0 -> exit(badurl);
true -> ok
end,
- path_decode(Tail, [Hex|Ack]);
-path_decode([H|T], Ack) when H /= 0 ->
- path_decode(T, [H|Ack]);
-path_decode([], Ack) ->
- lists:reverse(Ack).
-
-path_norm_reverse("/" ++ T) -> 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 ) -> start_dir(N , Path, T);
-start_dir(N, Path, "./" ++ T ) -> start_dir(N , Path, T);
-start_dir(N, Path, "../" ++ T ) -> 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 ] ) -> start_dir(0 , [ $/ | Path ], T);
-rest_dir (N, Path, [ $/ | T ] ) -> start_dir(N - 1, Path , T);
-rest_dir (0, Path, [ H | T ] ) -> rest_dir (0 , [ H | Path ], T);
-rest_dir (N, Path, [ _H | T ] ) -> rest_dir (N , Path , T).
+ 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 erlang:list_to_integer(Hex, 16) of
- {'EXIT', _} ->
- old_hex_to_integer(Hex);
- X ->
- X
+ 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(_Orig = "Basic " ++ Auth64) ->
- case decode_base64(Auth64) of
- {error, _Err} ->
- undefined;
- Auth ->
- %% Auth should be a string with the format: user@server:password
- %% Note that password can contain additional characters '@' and ':'
- case string:chr(Auth, $:) of
- 0 ->
- undefined;
- SplitIndex ->
- {User, [$: | Pass]} = lists:split(SplitIndex-1, Auth),
- {User, Pass}
- 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(_) ->
- undefined.
-
-
-
-decode_base64([]) ->
- [];
-decode_base64([Sextet1,Sextet2,$=,$=|Rest]) ->
- Bits2x6=
- (d(Sextet1) bsl 18) bor
- (d(Sextet2) bsl 12),
- Octet1=Bits2x6 bsr 16,
- [Octet1|decode_base64(Rest)];
-decode_base64([Sextet1,Sextet2,Sextet3,$=|Rest]) ->
- Bits3x6=
- (d(Sextet1) bsl 18) bor
- (d(Sextet2) bsl 12) bor
- (d(Sextet3) bsl 6),
- Octet1=Bits3x6 bsr 16,
- Octet2=(Bits3x6 bsr 8) band 16#ff,
- [Octet1,Octet2|decode_base64(Rest)];
-decode_base64([Sextet1,Sextet2,Sextet3,Sextet4|Rest]) ->
- Bits4x6=
- (d(Sextet1) bsl 18) bor
- (d(Sextet2) bsl 12) bor
- (d(Sextet3) bsl 6) bor
- d(Sextet4),
- Octet1=Bits4x6 bsr 16,
- Octet2=(Bits4x6 bsr 8) band 16#ff,
- Octet3=Bits4x6 band 16#ff,
- [Octet1,Octet2,Octet3|decode_base64(Rest)];
-decode_base64(_CatchAll) ->
- {error, bad_base64}.
-
-d(X) when X >= $A, X =<$Z ->
- X-65;
-d(X) when X >= $a, X =<$z ->
- X-71;
-d(X) when X >= $0, X =<$9 ->
- X+4;
-d($+) -> 62;
-d($/) -> 63;
-d(_) -> 63.
-
+parse_auth(<<_/binary>>) -> undefined.
parse_urlencoded(S) ->
- parse_urlencoded(S, nokey, [], key).
+ parse_urlencoded(S, nokey, <<>>, key).
-parse_urlencoded([$%, Hi, Lo | Tail], Last, Cur, State) ->
+parse_urlencoded(<<$%, Hi, Lo, Tail/binary>>, Last, Cur,
+ State) ->
Hex = hex_to_integer([Hi, Lo]),
- parse_urlencoded(Tail, Last, [Hex | Cur], State);
-
-parse_urlencoded([$& | Tail], _Last, Cur, key) ->
- [{lists:reverse(Cur), ""} |
- parse_urlencoded(Tail, nokey, [], key)]; %% cont keymode
-
-parse_urlencoded([$& | Tail], Last, Cur, value) ->
- V = {Last, lists:reverse(Cur)},
- [V | parse_urlencoded(Tail, nokey, [], key)];
-
-parse_urlencoded([$+ | Tail], Last, Cur, State) ->
- parse_urlencoded(Tail, Last, [$\s | Cur], State);
-
-parse_urlencoded([$= | Tail], _Last, Cur, key) ->
- parse_urlencoded(Tail, lists:reverse(Cur), [], value); %% change mode
-
-parse_urlencoded([H | Tail], Last, Cur, State) ->
- parse_urlencoded(Tail, Last, [H|Cur], State);
-
-parse_urlencoded([], Last, Cur, _State) ->
- [{Last, lists:reverse(Cur)}];
-
-parse_urlencoded(undefined, _, _, _) ->
- [].
-
-
-url_encode([H|T]) ->
- if
- H >= $a, $z >= H ->
- [H|url_encode(T)];
- H >= $A, $Z >= H ->
- [H|url_encode(T)];
- H >= $0, $9 >= H ->
- [H|url_encode(T)];
- H == $_; H == $.; H == $-; H == $/; H == $: -> % FIXME: more..
- [H|url_encode(T)];
- true ->
- case integer_to_hex(H) of
- [X, Y] ->
- [$%, X, Y | url_encode(T)];
- [X] ->
- [$%, $0, X | url_encode(T)]
- end
+ 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.
-url_encode([]) ->
- [].
integer_to_hex(I) ->
case catch erlang:integer_to_list(I, 16) of
- {'EXIT', _} ->
- old_integer_to_hex(I);
- Int ->
- Int
+ {'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(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
-extract_line(_, <<>>, _) ->
- none;
-extract_line(0, <<"\r", Rest/binary>>, Line) ->
- extract_line(1, Rest, Line);
-extract_line(0, <<A:8, Rest/binary>>, Line) ->
- extract_line(0, Rest, <<Line/binary, A>>);
-extract_line(1, <<"\n", Rest/binary>>, Line) ->
- {Line, Rest};
-extract_line(1, Data, Line) ->
- extract_line(0, Data, <<Line/binary, "\r">>).
-
-decode_packet(_, <<"\r\n", Rest/binary>>) ->
- {ok, http_eoh, Rest};
-decode_packet(Type, Data) ->
- case extract_line(0, Data, <<>>) of
- {LineB, Rest} ->
- Line = binary_to_list(LineB),
- Result = case Type of
- http ->
- parse_req(Line);
- httph ->
- parse_header_line(Line)
- end,
- case Result of
- {ok, H} ->
- {ok, H, Rest};
- Err ->
- {error, Err}
- end;
- _ ->
- {more, undefined}
- end.
-
-get_word(Line)->
- {Word, T} = lists:splitwith(fun(X)-> X /= $\ end, Line),
- {Word, lists:dropwhile(fun(X) -> X == $\ end, T)}.
+toupper(C) when C >= $a andalso C =< $z -> C - 32;
+toupper(C) -> C.
-
-parse_req(Line) ->
- {MethodStr, L1} = get_word(Line),
- ?DEBUG("Method: ~p~n", [MethodStr]),
- case L1 of
- [] ->
- bad_request;
- _ ->
- {URI, L2} = get_word(L1),
- {VersionStr, L3} = get_word(L2),
- ?DEBUG("URI: ~p~nVersion: ~p~nL3: ~p~n",
- [URI, VersionStr, L3]),
- case L3 of
- [] ->
- Method = case MethodStr of
- "GET" -> 'GET';
- "POST" -> 'POST';
- "HEAD" -> 'HEAD';
- "OPTIONS" -> 'OPTIONS';
- "TRACE" -> 'TRACE';
- "PUT" -> 'PUT';
- "DELETE" -> 'DELETE';
- S -> S
- end,
- Path = case URI of
- "*" ->
- % Is this correct?
- "*";
- _ ->
- case string:str(URI, "://") of
- 0 ->
- % Relative URI
- % ex: /index.html
- {abs_path, URI};
- N ->
- % Absolute URI
- % ex: http://localhost/index.html
-
- % Remove scheme
- % ex: URI2 = localhost/index.html
- URI2 = string:substr(URI, N + 3),
- % Look for the start of the path
- % (or the lack of a path thereof)
- case string:chr(URI2, $/) of
- 0 -> {abs_path, "/"};
- M -> {abs_path,
- string:substr(URI2, M + 1)}
- end
- end
- end,
- case VersionStr of
- [] ->
- {ok, {http_request, Method, Path, {0,9}}};
- "HTTP/1.0" ->
- {ok, {http_request, Method, Path, {1,0}}};
- "HTTP/1.1" ->
- {ok, {http_request, Method, Path, {1,1}}};
- _ ->
- bad_request
- end;
- _ ->
- bad_request
- end
- end.
-
-
-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.
+tolower(C) when C >= $A andalso C =< $Z -> C + 32;
+tolower(C) -> C.
normalize_header_name(Name) ->
- case parse_header_line(Name, "", true) of
- {ok, RName, _} ->
- lists:reverse(RName);
- {eol, RName} ->
- lists:reverse(RName)
- end.
-
-
-parse_header_line(Line) ->
- case parse_header_line(Line, "", true) of
- {ok, Name, Rest} ->
- encode_header(lists:reverse(Name), Rest);
- _ ->
- bad_request
- end.
-
-
-parse_header_line("", Name, _) ->
- {eol, Name};
-parse_header_line(":" ++ Rest, Name, _) ->
- {ok, Name, Rest};
-parse_header_line("-" ++ Rest, Name, _) ->
- parse_header_line(Rest, "-" ++ Name, true);
-parse_header_line([C | Rest], Name, true) ->
- parse_header_line(Rest, [toupper(C) | Name], false);
-parse_header_line([C | Rest], Name, false) ->
- parse_header_line(Rest, [tolower(C) | Name], false).
-
-
-encode_header("Connection", Con) ->
- {ok, {http_header, undefined, 'Connection', undefined, strip_spaces(Con)}};
-encode_header("Host", Con) ->
- {ok, {http_header, undefined, 'Host', undefined, strip_spaces(Con)}};
-encode_header("Accept", Con) ->
- {ok, {http_header, undefined, 'Accept', undefined, strip_spaces(Con)}};
-encode_header("If-Modified-Since", Con) ->
- {ok, {http_header, undefined, 'If-Modified-Since', undefined, strip_spaces(Con)}};
-encode_header("If-Match", Con) ->
- {ok, {http_header, undefined, 'If-Match', undefined, strip_spaces(Con)}};
-encode_header("If-None-Match", Con) ->
- {ok, {http_header, undefined, 'If-None-Match', undefined, strip_spaces(Con)}};
-encode_header("If-Range", Con) ->
- {ok, {http_header, undefined, 'If-Range', undefined, strip_spaces(Con)}};
-encode_header("If-Unmodified-Since", Con) ->
- {ok, {http_header, undefined, 'If-Unmodified-Since', undefined, strip_spaces(Con)}};
-encode_header("Range", Con) ->
- {ok, {http_header, undefined, 'Range', undefined, strip_spaces(Con)}};
-encode_header("User-Agent", Con) ->
- {ok, {http_header, undefined, 'User-Agent', undefined, strip_spaces(Con)}};
-encode_header("Accept-Ranges", Con) ->
- {ok, {http_header, undefined, 'Accept-Ranges', undefined, strip_spaces(Con)}};
-encode_header("Authorization", Con) ->
- {ok, {http_header, undefined, 'Authorization', undefined, strip_spaces(Con)}};
-encode_header("Keep-Alive", Con) ->
- {ok, {http_header, undefined, 'Keep-Alive', undefined, strip_spaces(Con)}};
-encode_header("Referer", Con) ->
- {ok, {http_header, undefined, 'Referer', undefined, strip_spaces(Con)}};
-encode_header("Content-Type", Con) ->
- {ok, {http_header, undefined, 'Content-Type', undefined, strip_spaces(Con)}};
-encode_header("Content-Length", Con) ->
- {ok, {http_header, undefined, 'Content-Length', undefined, strip_spaces(Con)}};
-encode_header("Cookie", Con) ->
- {ok, {http_header, undefined, 'Cookie', undefined, strip_spaces(Con)}};
-encode_header("Accept-Language", Con) ->
- {ok, {http_header, undefined, 'Accept-Language', undefined, strip_spaces(Con)}};
-encode_header("Accept-Encoding", Con) ->
- {ok, {http_header, undefined, 'Accept-Encoding', undefined, strip_spaces(Con)}};
-encode_header(Name, Val) ->
- {ok, {http_header, undefined, Name, undefined, strip_spaces(Val)}}.
-
-
-is_space($\s) ->
- true;
-is_space($\r) ->
- true;
-is_space($\n) ->
- true;
-is_space($\t) ->
- true;
-is_space(_) ->
- false.
-
-
-strip_spaces(String) ->
- strip_spaces(String, both).
-
-%% strip_spaces(String, left) ->
-%% drop_spaces(String);
-strip_spaces(String, right) ->
- lists:reverse(drop_spaces(lists:reverse(String)));
-strip_spaces(String, both) ->
- strip_spaces(drop_spaces(String), right).
-
-drop_spaces([]) ->
- [];
-drop_spaces(YS=[X|XS]) ->
- case is_space(X) of
- true ->
- drop_spaces(XS);
- false ->
- YS
- end.
+ 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).
+
+%% -*- tab-width:8
diff --git a/src/web/ejabberd_http.hrl b/src/web/ejabberd_http.hrl
index 5f1625ed5..51864f447 100644
--- a/src/web/ejabberd_http.hrl
+++ b/src/web/ejabberd_http.hrl
@@ -19,36 +19,39 @@
%%%
%%%----------------------------------------------------------------------
--record(request, {method,
- path,
- q = [],
- us,
- auth,
- lang = "",
- data = "",
- ip,
- host, % string()
- port, % integer()
- tp, % transfer protocol = http | https
- headers
- }).
+-record(request,
+ {method :: method(),
+ path = [] :: [binary()],
+ q = [] :: [{binary() | nokey, binary()}],
+ us = {<<>>, <<>>} :: {binary(), binary()},
+ auth :: {binary(), binary()} |
+ {auth_jid, {binary(), binary()}, jlib:jid()},
+ lang = <<"">> :: binary(),
+ data = <<"">> :: binary(),
+ ip :: {inet:ip_address(), inet:port_number()},
+ host = <<"">> :: binary(),
+ port = 5280 :: inet:port_number(),
+ tp = http :: protocol(),
+ headers = [] :: [{atom() | binary(), binary()}]}).
+-record(ws,
+ {socket :: inet:socket() | tls:tls_socket(),
+ sockmod = gen_tcp :: gen_tcp | tls,
+ ws_autoexit = false :: boolean(),
+ ip :: {inet:ip_address(), inet:port_number()},
+ vsn :: vsn(),
+ origin = <<"">> :: binary(),
+ host = <<"">> :: binary(),
+ port = 5280 :: inet:port_number(),
+ path = [] :: [binary()],
+ headers = [] :: [{atom() | binary(), binary()}],
+ local_path = [] :: [binary()],
+ q = [] :: [{binary() | nokey, binary()}],
+ protocol :: binary(),
+ acceptable_origins = [] :: [binary()],
+ auth_module :: atom()}).
-% Websocket Request
--record(ws, {
- socket, % the socket handling the request
- sockmod, % gen_tcp | tls
- ws_autoexit, % websocket process is automatically killed: true | false
- ip, % peer IP | undefined
- vsn, % {Maj,Min} | {'draft-hixie', Ver}
- origin, % the originator
- host, % the host
- port,
- path, % the websocket GET request path
- headers, % [{Tag, Val}]
- local_path,
- q,
- protocol,
- acceptable_origins = [],
- auth_module
- }).
+-type method() :: 'GET' | 'HEAD' | 'DELETE' | 'OPTIONS' | 'PUT' | 'POST' | 'TRACE'.
+-type protocol() :: http | https.
+-type http_request() :: #request{}.
+-type vsn() :: {'draft-hybi' | 'draft-hixie', non_neg_integer()}.
diff --git a/src/web/ejabberd_http_bind.erl b/src/web/ejabberd_http_bind.erl
index cbbe68c33..c8b688908 100644
--- a/src/web/ejabberd_http_bind.erl
+++ b/src/web/ejabberd_http_bind.erl
@@ -13,411 +13,373 @@
-behaviour(gen_fsm).
%% External exports
--export([start_link/4,
- init/1,
- handle_event/3,
- handle_sync_event/4,
- code_change/4,
- handle_info/3,
- terminate/3,
- send/2,
- send_xml/2,
- sockname/1,
- peername/1,
- setopts/2,
- controlling_process/2,
- become_controller/2,
- change_controller/2,
- custom_receiver/1,
- reset_stream/1,
- change_shaper/2,
- monitor/1,
- close/1,
- start/4,
- handle_session_start/8,
- handle_http_put/7,
- http_put/7,
- http_get/2,
- prepare_response/4,
- process_request/2]).
+-export([start_link/4, init/1, handle_event/3,
+ handle_sync_event/4, code_change/4, handle_info/3,
+ terminate/3, send/2, send_xml/2, sockname/1, peername/1,
+ setopts/2, controlling_process/2, become_controller/2,
+ change_controller/2, custom_receiver/1, reset_stream/1,
+ change_shaper/2, monitor/1, close/1, start/4,
+ handle_session_start/8, handle_http_put/7, http_put/7,
+ http_get/2, prepare_response/4, process_request/2]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
+
-include("ejabberd_http.hrl").
+
-include("http_bind.hrl").
--record(http_bind, {id, pid, to, hold, wait, process_delay, version}).
+-record(http_bind,
+ {id, pid, to, hold, wait, process_delay, version}).
-define(NULL_PEER, {{0, 0, 0, 0}, 0}).
-%% http binding request
--record(hbr, {rid,
- key,
- out}).
-
--record(state, {id,
- rid = none,
- key,
- socket,
- output = "",
- input = queue:new(),
- waiting_input = false,
- shaper_state,
- shaper_timer,
- last_receiver,
- last_poll,
- http_receiver,
- out_of_order_receiver = false,
- wait_timer,
- ctime = 0,
- timer,
- jid,
- pause=0,
- unprocessed_req_list = [], % list of request that have been delayed for proper reordering: {Request, PID}
- req_list = [], % list of requests (cache)
- max_inactivity,
- max_pause,
- ip = ?NULL_PEER
- }).
-
-%% Internal request format:
--record(http_put, {rid,
- attrs,
- payload,
- payload_size,
- hold,
- stream,
- ip}).
+-record(hbr, {rid, key, out}).
+
+-record(state,
+{
+ id,
+ rid = none,
+ key,
+ socket,
+ output = [],
+ input = queue:new(),
+ waiting_input = false,
+ shaper_state,
+ shaper_timer,
+ last_receiver,
+ last_poll,
+ http_receiver,
+ out_of_order_receiver = false,
+ wait_timer,
+ ctime = 0,
+ timer,
+ jid,
+ pause = 0,
+ unprocessed_req_list = [],
+ req_list = [],
+ max_inactivity,
+ max_pause,
+ ip = ?NULL_PEER
+}).
+
+-record(http_put,
+{
+ rid,
+ attrs,
+ payload,
+ payload_size,
+ hold, stream,
+ ip
+}).
%%-define(DBGFSM, true).
-ifdef(DBGFSM).
+
-define(FSMOPTS, [{debug, [trace]}]).
+
-else.
+
-define(FSMOPTS, []).
+
-endif.
--define(BOSH_VERSION, "1.8").
--define(NS_CLIENT, "jabber:client").
--define(NS_BOSH, "urn:xmpp:xbosh").
--define(NS_HTTP_BIND, "http://jabber.org/protocol/httpbind").
-
--define(MAX_REQUESTS, 2). % number of simultaneous requests
--define(MIN_POLLING, 2000000). % don't poll faster than that or we will
- % shoot you (time in microsec)
--define(MAX_WAIT, 3600). % max num of secs to keep a request on hold
--define(MAX_INACTIVITY, 30000). % msecs to wait before terminating
- % idle sessions
--define(MAX_PAUSE, 120). % may num of sec a client is allowed to pause
- % the session
-
-%% Wait 100ms before continue processing, to allow the client provide more related stanzas.
+-define(BOSH_VERSION, <<"1.8">>).
+
+-define(NS_CLIENT, <<"jabber:client">>).
+
+-define(NS_BOSH, <<"urn:xmpp:xbosh">>).
+
+-define(NS_HTTP_BIND,
+ <<"http://jabber.org/protocol/httpbind">>).
+
+-define(MAX_REQUESTS, 2).
+
+-define(MIN_POLLING, 2000000).
+
+-define(MAX_WAIT, 3600).
+
+-define(MAX_INACTIVITY, 30000).
+
+-define(MAX_PAUSE, 120).
+
-define(PROCESS_DELAY_DEFAULT, 100).
+
-define(PROCESS_DELAY_MIN, 0).
+
-define(PROCESS_DELAY_MAX, 1000).
-%% Line copied from mod_http_bind.erl
-define(PROCNAME_MHB, ejabberd_mod_http_bind).
-%%%----------------------------------------------------------------------
-%%% API
-%%%----------------------------------------------------------------------
-%% TODO: If compile with no supervisor option, start the session without
-%% supervisor
start(XMPPDomain, Sid, Key, IP) ->
?DEBUG("Starting session", []),
- SupervisorProc = gen_mod:get_module_proc(XMPPDomain, ?PROCNAME_MHB),
- case catch supervisor:start_child(SupervisorProc, [XMPPDomain, Sid, Key, IP]) of
- {ok, Pid} ->
- {ok, Pid};
- {error, _} = Err ->
- case check_bind_module(XMPPDomain) of
- false ->
- {error, "Cannot start HTTP bind session"};
- true ->
- ?ERROR_MSG("Cannot start HTTP bind session: ~p", [Err]),
- Err
- end;
- Exit ->
- ?ERROR_MSG("Cannot start HTTP bind session: ~p", [Exit]),
- {error, Exit}
+ SupervisorProc = gen_mod:get_module_proc(XMPPDomain,
+ ?PROCNAME_MHB),
+ case catch supervisor:start_child(SupervisorProc,
+ [XMPPDomain, Sid, Key, IP])
+ of
+ {ok, Pid} -> {ok, Pid};
+ {error, _} = Err ->
+ case check_bind_module(XMPPDomain) of
+ false -> {error, <<"Cannot start HTTP bind session">>};
+ true ->
+ ?ERROR_MSG("Cannot start HTTP bind session: ~p", [Err]),
+ Err
+ end;
+ Exit ->
+ ?ERROR_MSG("Cannot start HTTP bind session: ~p",
+ [Exit]),
+ {error, Exit}
end.
start_link(ServerHost, Sid, Key, IP) ->
- gen_fsm:start_link(?MODULE, [ServerHost, Sid, Key, IP], ?FSMOPTS).
+ gen_fsm:start_link(?MODULE, [ServerHost, Sid, Key, IP],
+ ?FSMOPTS).
send({http_bind, FsmRef, _IP}, Packet) ->
- gen_fsm:sync_send_all_state_event(FsmRef, {send, Packet}).
+ gen_fsm:sync_send_all_state_event(FsmRef,
+ {send, Packet}).
send_xml({http_bind, FsmRef, _IP}, Packet) ->
- gen_fsm:sync_send_all_state_event(FsmRef, {send_xml, Packet}).
+ gen_fsm:sync_send_all_state_event(FsmRef,
+ {send_xml, Packet}).
setopts({http_bind, FsmRef, _IP}, Opts) ->
case lists:member({active, once}, Opts) of
- true ->
- gen_fsm:send_all_state_event(FsmRef, {activate, self()});
- _ ->
- case lists:member({active, false}, Opts) of
- true ->
- case catch gen_fsm:sync_send_all_state_event(
- FsmRef, deactivate_socket) of
- {'EXIT', _} ->
- {error, einval};
- Res ->
- Res
- end;
- _ ->
- ok
- end
+ true ->
+ gen_fsm:send_all_state_event(FsmRef,
+ {activate, self()});
+ _ ->
+ case lists:member({active, false}, Opts) of
+ true ->
+ case catch gen_fsm:sync_send_all_state_event(FsmRef,
+ deactivate_socket)
+ of
+ {'EXIT', _} -> {error, einval};
+ Res -> Res
+ end;
+ _ -> ok
+ end
end.
-controlling_process(_Socket, _Pid) ->
- ok.
+controlling_process(_Socket, _Pid) -> ok.
custom_receiver({http_bind, FsmRef, _IP}) ->
{receiver, ?MODULE, FsmRef}.
become_controller(FsmRef, C2SPid) ->
- gen_fsm:send_all_state_event(FsmRef, {become_controller, C2SPid}).
+ gen_fsm:send_all_state_event(FsmRef,
+ {become_controller, C2SPid}).
change_controller({http_bind, FsmRef, _IP}, C2SPid) ->
become_controller(FsmRef, C2SPid).
-reset_stream({http_bind, _FsmRef, _IP}) ->
- ok.
+reset_stream({http_bind, _FsmRef, _IP}) -> ok.
change_shaper({http_bind, FsmRef, _IP}, Shaper) ->
- gen_fsm:send_all_state_event(FsmRef, {change_shaper, Shaper}).
+ gen_fsm:send_all_state_event(FsmRef,
+ {change_shaper, Shaper}).
monitor({http_bind, FsmRef, _IP}) ->
erlang:monitor(process, FsmRef).
close({http_bind, FsmRef, _IP}) ->
- catch gen_fsm:sync_send_all_state_event(FsmRef, {stop, close}).
+ catch gen_fsm:sync_send_all_state_event(FsmRef,
+ {stop, close}).
-sockname(_Socket) ->
- {ok, ?NULL_PEER}.
+sockname(_Socket) -> {ok, ?NULL_PEER}.
-peername({http_bind, _FsmRef, IP}) ->
- {ok, IP}.
+peername({http_bind, _FsmRef, IP}) -> {ok, IP}.
-%% Entry point for data coming from client through ejabberd HTTP server:
process_request(Data, IP) ->
Opts1 = ejabberd_c2s_config:get_c2s_limits(),
Opts = [{xml_socket, true} | Opts1],
- MaxStanzaSize =
- case lists:keysearch(max_stanza_size, 1, Opts) of
- {value, {_, Size}} -> Size;
- _ -> infinity
- end,
+ MaxStanzaSize = case lists:keysearch(max_stanza_size, 1,
+ Opts)
+ of
+ {value, {_, Size}} -> Size;
+ _ -> infinity
+ end,
PayloadSize = iolist_size(Data),
- case catch parse_request(Data, PayloadSize, MaxStanzaSize) of
- %% No existing session:
- {ok, {"", Rid, Attrs, Payload}} ->
- case xml:get_attr_s("to",Attrs) of
- "" ->
- ?DEBUG("Session not created (Improper addressing)", []),
- {200, ?HEADER, "<body type='terminate' "
- "condition='improper-addressing' "
- "xmlns='" ++ ?NS_HTTP_BIND ++ "'/>"};
- XmppDomain ->
- %% create new session
- Sid = make_sid(),
- case start(XmppDomain, Sid, "", IP) of
- {error, _} ->
- {500, ?HEADER, "<body type='terminate' "
- "condition='internal-server-error' "
- "xmlns='" ++ ?NS_HTTP_BIND ++ "'>Internal Server Error</body>"};
- {ok, Pid} ->
- handle_session_start(
- Pid, XmppDomain, Sid, Rid, Attrs,
- Payload, PayloadSize, IP)
- end
- end;
- %% Existing session
- {ok, {Sid, Rid, Attrs, Payload1}} ->
- StreamStart =
- case xml:get_attr_s("xmpp:restart",Attrs) of
- "true" ->
- true;
- _ ->
- false
- end,
- Payload2 = case xml:get_attr_s("type",Attrs) of
- "terminate" ->
- %% close stream
- Payload1 ++ [{xmlstreamend, "stream:stream"}];
- _ ->
- Payload1
- end,
- handle_http_put(Sid, Rid, Attrs, Payload2, PayloadSize,
- StreamStart, IP);
- {size_limit, Sid} ->
- case get_session(Sid) of
- {error, _} ->
- {404, ?HEADER, ""};
- {ok, #http_bind{pid = FsmRef}} ->
- gen_fsm:sync_send_all_state_event(FsmRef, {stop, close}),
- {200, ?HEADER, "<body type='terminate' "
- "condition='undefined-condition' "
- "xmlns='" ++ ?NS_HTTP_BIND ++ "'>Request Too Large</body>"}
- end;
- _ ->
- ?DEBUG("Received bad request: ~p", [Data]),
- {400, ?HEADER, ""}
+ case catch parse_request(Data, PayloadSize,
+ MaxStanzaSize)
+ of
+ %% No existing session:
+ {ok, {<<"">>, Rid, Attrs, Payload}} ->
+ case xml:get_attr_s(<<"to">>, Attrs) of
+ <<"">> ->
+ ?DEBUG("Session not created (Improper addressing)", []),
+ {200, ?HEADER,
+ <<"<body type='terminate' condition='improper-ad"
+ "dressing' xmlns='",
+ (?NS_HTTP_BIND)/binary, "'/>">>};
+ XmppDomain ->
+ Sid = make_sid(),
+ case start(XmppDomain, Sid, <<"">>, IP) of
+ {error, _} ->
+ {500, ?HEADER,
+ <<"<body type='terminate' condition='internal-se"
+ "rver-error' xmlns='",
+ (?NS_HTTP_BIND)/binary,
+ "'>Internal Server Error</body>">>};
+ {ok, Pid} ->
+ handle_session_start(Pid, XmppDomain, Sid, Rid, Attrs,
+ Payload, PayloadSize, IP)
+ end
+ end;
+ %% Existing session
+ {ok, {Sid, Rid, Attrs, Payload1}} ->
+ StreamStart = case xml:get_attr_s(<<"xmpp:restart">>,
+ Attrs)
+ of
+ <<"true">> -> true;
+ _ -> false
+ end,
+ Payload2 = case xml:get_attr_s(<<"type">>, Attrs) of
+ <<"terminate">> ->
+ Payload1 ++ [{xmlstreamend, <<"stream:stream">>}];
+ _ -> Payload1
+ end,
+ handle_http_put(Sid, Rid, Attrs, Payload2, PayloadSize,
+ StreamStart, IP);
+ {size_limit, Sid} ->
+ case get_session(Sid) of
+ {error, _} -> {404, ?HEADER, <<"">>};
+ {ok, #http_bind{pid = FsmRef}} ->
+ gen_fsm:sync_send_all_state_event(FsmRef,
+ {stop, close}),
+ {200, ?HEADER,
+ <<"<body type='terminate' condition='undefined-c"
+ "ondition' xmlns='",
+ (?NS_HTTP_BIND)/binary, "'>Request Too Large</body>">>}
+ end;
+ _ ->
+ ?DEBUG("Received bad request: ~p", [Data]),
+ {400, ?HEADER, <<"">>}
end.
handle_session_start(Pid, XmppDomain, Sid, Rid, Attrs,
Payload, PayloadSize, IP) ->
?DEBUG("got pid: ~p", [Pid]),
- Wait = case string:to_integer(xml:get_attr_s("wait",Attrs)) of
- {error, _} ->
- ?MAX_WAIT;
- {CWait, _} ->
- if
- (CWait > ?MAX_WAIT) ->
- ?MAX_WAIT;
- true ->
- CWait
- end
+ Wait = case str:to_integer(xml:get_attr_s(<<"wait">>,
+ Attrs))
+ of
+ {error, _} -> ?MAX_WAIT;
+ {CWait, _} ->
+ if CWait > (?MAX_WAIT) -> ?MAX_WAIT;
+ true -> CWait
+ end
end,
- Hold = case string:to_integer(xml:get_attr_s("hold",Attrs)) of
- {error, _} ->
- (?MAX_REQUESTS - 1);
- {CHold, _} ->
- if
- (CHold > (?MAX_REQUESTS - 1)) ->
- (?MAX_REQUESTS - 1);
- true ->
- CHold
- end
+ Hold = case str:to_integer(xml:get_attr_s(<<"hold">>,
+ Attrs))
+ of
+ {error, _} -> (?MAX_REQUESTS) - 1;
+ {CHold, _} ->
+ if CHold > (?MAX_REQUESTS) - 1 -> (?MAX_REQUESTS) - 1;
+ true -> CHold
+ end
end,
- Pdelay = case string:to_integer(xml:get_attr_s("process-delay",Attrs)) of
- {error, _} ->
- ?PROCESS_DELAY_DEFAULT;
- {CPdelay, _} when
- (?PROCESS_DELAY_MIN =< CPdelay) and
- (CPdelay =< ?PROCESS_DELAY_MAX) ->
- CPdelay;
- {CPdelay, _} ->
- lists:max([lists:min([CPdelay, ?PROCESS_DELAY_MAX]), ?PROCESS_DELAY_MIN])
+ Pdelay = case
+ str:to_integer(xml:get_attr_s(<<"process-delay">>,
+ Attrs))
+ of
+ {error, _} -> ?PROCESS_DELAY_DEFAULT;
+ {CPdelay, _}
+ when ((?PROCESS_DELAY_MIN) =< CPdelay) and
+ (CPdelay =< (?PROCESS_DELAY_MAX)) ->
+ CPdelay;
+ {CPdelay, _} ->
+ lists:max([lists:min([CPdelay, ?PROCESS_DELAY_MAX]),
+ ?PROCESS_DELAY_MIN])
end,
- Version =
- case catch list_to_float(
- xml:get_attr_s("ver", Attrs)) of
- {'EXIT', _} -> 0.0;
- V -> V
- end,
- XmppVersion = xml:get_attr_s("xmpp:version", Attrs),
+ Version = case catch
+ list_to_float(binary_to_list(xml:get_attr_s(<<"ver">>, Attrs)))
+ of
+ {'EXIT', _} -> 0.0;
+ V -> V
+ end,
+ XmppVersion = xml:get_attr_s(<<"xmpp:version">>, Attrs),
?DEBUG("Create session: ~p", [Sid]),
- mnesia:async_dirty(
- fun() ->
- mnesia:write(
- #http_bind{id = Sid,
- pid = Pid,
- to = {XmppDomain,
- XmppVersion},
- hold = Hold,
- wait = Wait,
- process_delay = Pdelay,
- version = Version
- })
- end),
- handle_http_put(Sid, Rid, Attrs, Payload, PayloadSize, true, IP).
+ mnesia:async_dirty(fun () ->
+ mnesia:write(#http_bind{id = Sid, pid = Pid,
+ to =
+ {XmppDomain,
+ XmppVersion},
+ hold = Hold, wait = Wait,
+ process_delay = Pdelay,
+ version = Version})
+ end),
+ handle_http_put(Sid, Rid, Attrs, Payload, PayloadSize,
+ true, IP).
%%%----------------------------------------------------------------------
%%% Callback functions from gen_fsm
%%%----------------------------------------------------------------------
-%%----------------------------------------------------------------------
-%% Func: init/1
-%% Returns: {ok, StateName, StateData} |
-%% {ok, StateName, StateData, Timeout} |
-%% ignore |
-%% {stop, StopReason}
-%%----------------------------------------------------------------------
init([ServerHost, Sid, Key, IP]) ->
?DEBUG("started: ~p", [{Sid, Key, IP}]),
-
- %% Read c2s options from the first ejabberd_c2s configuration in
- %% the config file listen section
- %% TODO: We should have different access and shaper values for
- %% each connector. The default behaviour should be however to use
- %% the default c2s restrictions if not defined for the current
- %% connector.
Opts1 = ejabberd_c2s_config:get_c2s_limits(),
Opts = [{xml_socket, true} | Opts1],
-
Shaper = none,
ShaperState = shaper:new(Shaper),
Socket = {http_bind, self(), IP},
Timer = erlang:start_timer(?MAX_INACTIVITY, self(), []),
- State = #state{id = Sid,
- key = Key,
- socket = Socket,
- shaper_state = ShaperState,
- max_inactivity = ?MAX_INACTIVITY,
- max_pause = ?MAX_PAUSE,
- timer = Timer},
- case gen_mod:get_module_opt(ServerHost, mod_http_bind,
- prebind, false) of
- true ->
- JID = make_random_jid(ServerHost),
- ejabberd_socket:start(ejabberd_c2s, ?MODULE, Socket,
- [{jid, JID} | Opts]),
- {ok, loop, State#state{jid = JID}};
- false ->
- ejabberd_socket:start(ejabberd_c2s, ?MODULE, Socket, Opts),
- {ok, loop, State}
+ State = #state{id = Sid, key = Key, socket = Socket,
+ shaper_state = ShaperState,
+ max_inactivity = ?MAX_INACTIVITY,
+ max_pause = ?MAX_PAUSE, timer = Timer},
+ case gen_mod:get_module_opt(ServerHost, mod_http_bind, prebind,
+ fun(B) when is_boolean(B) -> B end,
+ false)
+ of
+ true ->
+ JID = make_random_jid(ServerHost),
+ ejabberd_socket:start(ejabberd_c2s, ?MODULE, Socket,
+ [{jid, JID} | Opts]),
+ {ok, loop, State#state{jid = JID}};
+ false ->
+ ejabberd_socket:start(ejabberd_c2s, ?MODULE, Socket,
+ Opts),
+ {ok, loop, State}
end.
-%%----------------------------------------------------------------------
-%% Func: handle_event/3
-%% Returns: {next_state, NextStateName, NextStateData} |
-%% {next_state, NextStateName, NextStateData, Timeout} |
-%% {stop, Reason, NewStateData}
-%%----------------------------------------------------------------------
-handle_event({become_controller, C2SPid}, StateName, StateData) ->
+handle_event({become_controller, C2SPid}, StateName,
+ StateData) ->
erlang:monitor(process, C2SPid),
case StateData#state.input of
- cancel ->
- {next_state, StateName, StateData#state{
- waiting_input = C2SPid}};
- Input ->
- lists:foreach(
- fun(Event) ->
- C2SPid ! Event
- end, queue:to_list(Input)),
- {next_state, StateName, StateData#state{
- input = queue:new(),
- waiting_input = C2SPid}}
+ cancel ->
+ {next_state, StateName,
+ StateData#state{waiting_input = C2SPid}};
+ Input ->
+ lists:foreach(fun (Event) -> C2SPid ! Event end,
+ queue:to_list(Input)),
+ {next_state, StateName,
+ StateData#state{input = queue:new(),
+ waiting_input = C2SPid}}
end;
-
-handle_event({change_shaper, Shaper}, StateName, StateData) ->
+handle_event({change_shaper, Shaper}, StateName,
+ StateData) ->
NewShaperState = shaper:new(Shaper),
- {next_state, StateName, StateData#state{shaper_state = NewShaperState}};
+ {next_state, StateName,
+ StateData#state{shaper_state = NewShaperState}};
handle_event(_Event, StateName, StateData) ->
{next_state, StateName, StateData}.
-%%----------------------------------------------------------------------
-%% Func: handle_sync_event/4
-%% Returns: {next_state, NextStateName, NextStateData} |
-%% {next_state, NextStateName, NextStateData, Timeout} |
-%% {reply, Reply, NextStateName, NextStateData} |
-%% {reply, Reply, NextStateName, NextStateData, Timeout} |
-%% {stop, Reason, NewStateData} |
-%% {stop, Reason, Reply, NewStateData}
-%%----------------------------------------------------------------------
handle_sync_event({send_xml, Packet}, _From, StateName,
#state{http_receiver = undefined} = StateData) ->
Output = [Packet | StateData#state.output],
Reply = ok,
- {reply, Reply, StateName, StateData#state{output = Output}};
+ {reply, Reply, StateName,
+ StateData#state{output = Output}};
handle_sync_event({send_xml, Packet}, _From, StateName,
#state{out_of_order_receiver = true} = StateData) ->
Output = [Packet | StateData#state.output],
Reply = ok,
- {reply, Reply, StateName, StateData#state{output = Output}};
-handle_sync_event({send_xml, Packet}, _From, StateName, StateData) ->
+ {reply, Reply, StateName,
+ StateData#state{output = Output}};
+handle_sync_event({send_xml, Packet}, _From, StateName,
+ StateData) ->
Output = [Packet | StateData#state.output],
cancel_timer(StateData#state.timer),
Timer = set_inactivity_timer(StateData#state.pause,
@@ -426,196 +388,161 @@ handle_sync_event({send_xml, Packet}, _From, StateName, StateData) ->
gen_fsm:reply(StateData#state.http_receiver, HTTPReply),
cancel_timer(StateData#state.wait_timer),
Rid = StateData#state.rid,
- ReqList = [#hbr{rid = Rid,
- key = StateData#state.key,
- out = Output
- } |
- [El || El <- StateData#state.req_list,
- El#hbr.rid /= Rid ]
- ],
+ ReqList = [#hbr{rid = Rid, key = StateData#state.key,
+ out = Output}
+ | [El
+ || El <- StateData#state.req_list, El#hbr.rid /= Rid]],
Reply = ok,
{reply, Reply, StateName,
- StateData#state{output = [],
- http_receiver = undefined,
- req_list = ReqList,
- wait_timer = undefined,
+ StateData#state{output = [], http_receiver = undefined,
+ req_list = ReqList, wait_timer = undefined,
timer = Timer}};
-
-handle_sync_event({stop,close}, _From, _StateName, StateData) ->
- Reply = ok,
- {stop, normal, Reply, StateData};
-handle_sync_event({stop,stream_closed}, _From, _StateName, StateData) ->
- Reply = ok,
- {stop, normal, Reply, StateData};
-handle_sync_event(deactivate_socket, _From, StateName, StateData) ->
- %% Input = case StateData#state.input of
- %% cancel ->
- %% queue:new();
- %% Q ->
- %% Q
- %% end,
- {reply, ok, StateName, StateData#state{waiting_input = false}};
-handle_sync_event({stop,Reason}, _From, _StateName, StateData) ->
- ?DEBUG("Closing bind session ~p - Reason: ~p", [StateData#state.id, Reason]),
+handle_sync_event({stop, close}, _From, _StateName,
+ StateData) ->
+ Reply = ok, {stop, normal, Reply, StateData};
+handle_sync_event({stop, stream_closed}, _From,
+ _StateName, StateData) ->
+ Reply = ok, {stop, normal, Reply, StateData};
+handle_sync_event(deactivate_socket, _From, StateName,
+ StateData) ->
+ {reply, ok, StateName,
+ StateData#state{waiting_input = false}};
+handle_sync_event({stop, Reason}, _From, _StateName,
+ StateData) ->
+ ?DEBUG("Closing bind session ~p - Reason: ~p",
+ [StateData#state.id, Reason]),
Reply = ok,
{stop, normal, Reply, StateData};
-
%% HTTP PUT: Receive packets from the client
-handle_sync_event(#http_put{rid = Rid},
- _From, StateName, StateData)
- when StateData#state.shaper_timer /= undefined ->
- Pause =
- case erlang:read_timer(StateData#state.shaper_timer) of
- false ->
- 0;
- P -> P
- end,
+handle_sync_event(#http_put{rid = Rid}, _From,
+ StateName, StateData)
+ when StateData#state.shaper_timer /= undefined ->
+ Pause = case
+ erlang:read_timer(StateData#state.shaper_timer)
+ of
+ false -> 0;
+ P -> P
+ end,
Reply = {wait, Pause},
?DEBUG("Shaper timer for RID ~p: ~p", [Rid, Reply]),
{reply, Reply, StateName, StateData};
-
-handle_sync_event(#http_put{payload_size = PayloadSize} = Request,
+handle_sync_event(#http_put{payload_size =
+ PayloadSize} =
+ Request,
_From, StateName, StateData) ->
- ?DEBUG("New request: ~p",[Request]),
- %% Updating trafic shaper
+ ?DEBUG("New request: ~p", [Request]),
{NewShaperState, NewShaperTimer} =
- update_shaper(StateData#state.shaper_state, PayloadSize),
-
+ update_shaper(StateData#state.shaper_state,
+ PayloadSize),
handle_http_put_event(Request, StateName,
StateData#state{shaper_state = NewShaperState,
shaper_timer = NewShaperTimer});
-
%% HTTP GET: send packets to the client
handle_sync_event({http_get, _Rid, _Wait, _Hold}, _From,
StateName, #state{jid = JID} = StateData)
- when JID /= undefined ->
- %% This is a pre-bind state
- {reply, {ok, {prebind, JID}}, StateName, StateData#state{jid = undefined}};
-handle_sync_event({http_get, Rid, Wait, Hold}, From, StateName, StateData) ->
- %% setup timer
+ when JID /= undefined ->
+ {reply, {ok, {prebind, JID}}, StateName,
+ StateData#state{jid = undefined}};
+handle_sync_event({http_get, Rid, Wait, Hold}, From,
+ StateName, StateData) ->
TNow = tnow(),
- if
- (Hold > 0) and
- ((StateData#state.output == []) or (StateData#state.rid < Rid)) and
- ((TNow - StateData#state.ctime) < (Wait*1000*1000)) and
- (StateData#state.rid =< Rid) and
- (StateData#state.input /= cancel) and
- (StateData#state.pause == 0) ->
- send_receiver_reply(StateData#state.http_receiver, {ok, empty}),
- cancel_timer(StateData#state.wait_timer),
- WaitTimer = erlang:start_timer(Wait * 1000, self(), []),
- %% MR: Not sure we should cancel the state timer here.
- cancel_timer(StateData#state.timer),
- {next_state, StateName, StateData#state{
- http_receiver = From,
- out_of_order_receiver = StateData#state.rid < Rid,
- wait_timer = WaitTimer,
- timer = undefined}};
- true ->
- cancel_timer(StateData#state.timer),
- Reply = {ok, StateData#state.output},
- %% save request
- ReqList = [#hbr{rid = Rid,
- key = StateData#state.key,
- out = StateData#state.output
- } |
- [El || El <- StateData#state.req_list,
- El#hbr.rid /= Rid ]
- ],
- if
- (StateData#state.http_receiver /= undefined) and
- StateData#state.out_of_order_receiver ->
- {reply, Reply, StateName, StateData#state{
- output = [],
- timer = undefined,
- req_list = ReqList,
- out_of_order_receiver = false}};
- true ->
- send_receiver_reply(StateData#state.http_receiver, {ok, empty}),
- cancel_timer(StateData#state.wait_timer),
- Timer = set_inactivity_timer(StateData#state.pause,
- StateData#state.max_inactivity),
- {reply, Reply, StateName,
- StateData#state{output = [],
- http_receiver = undefined,
- wait_timer = undefined,
- timer = Timer,
- req_list = ReqList}}
- end
+ if (Hold > 0) and
+ ((StateData#state.output == []) or
+ (StateData#state.rid < Rid))
+ and (TNow - StateData#state.ctime < Wait * 1000 * 1000)
+ and (StateData#state.rid =< Rid)
+ and (StateData#state.input /= cancel)
+ and (StateData#state.pause == 0) ->
+ send_receiver_reply(StateData#state.http_receiver,
+ {ok, empty}),
+ cancel_timer(StateData#state.wait_timer),
+ WaitTimer = erlang:start_timer(Wait * 1000, self(), []),
+ cancel_timer(StateData#state.timer),
+ {next_state, StateName,
+ StateData#state{http_receiver = From,
+ out_of_order_receiver = StateData#state.rid < Rid,
+ wait_timer = WaitTimer, timer = undefined}};
+ true ->
+ cancel_timer(StateData#state.timer),
+ Reply = {ok, StateData#state.output},
+ ReqList = [#hbr{rid = Rid, key = StateData#state.key,
+ out = StateData#state.output}
+ | [El
+ || El <- StateData#state.req_list, El#hbr.rid /= Rid]],
+ if (StateData#state.http_receiver /= undefined) and
+ StateData#state.out_of_order_receiver ->
+ {reply, Reply, StateName,
+ StateData#state{output = [], timer = undefined,
+ req_list = ReqList,
+ out_of_order_receiver = false}};
+ true ->
+ send_receiver_reply(StateData#state.http_receiver,
+ {ok, empty}),
+ cancel_timer(StateData#state.wait_timer),
+ Timer = set_inactivity_timer(StateData#state.pause,
+ StateData#state.max_inactivity),
+ {reply, Reply, StateName,
+ StateData#state{output = [], http_receiver = undefined,
+ wait_timer = undefined, timer = Timer,
+ req_list = ReqList}}
+ end
end;
-
-handle_sync_event(peername, _From, StateName, StateData) ->
+handle_sync_event(peername, _From, StateName,
+ StateData) ->
Reply = {ok, StateData#state.ip},
{reply, Reply, StateName, StateData};
-
-handle_sync_event(_Event, _From, StateName, StateData) ->
- Reply = ok,
- {reply, Reply, StateName, StateData}.
+handle_sync_event(_Event, _From, StateName,
+ StateData) ->
+ Reply = ok, {reply, Reply, StateName, StateData}.
code_change(_OldVsn, StateName, StateData, _Extra) ->
{ok, StateName, StateData}.
-%%----------------------------------------------------------------------
-%% Func: handle_info/3
-%% Returns: {next_state, NextStateName, NextStateData} |
-%% {next_state, NextStateName, NextStateData, Timeout} |
-%% {stop, Reason, NewStateData}
-%%----------------------------------------------------------------------
-%% We reached the max_inactivity timeout:
handle_info({timeout, Timer, _}, _StateName,
- #state{id=SID, timer = Timer} = StateData) ->
- ?INFO_MSG("Session timeout. Closing the HTTP bind session: ~p", [SID]),
+ #state{id = SID, timer = Timer} = StateData) ->
+ ?INFO_MSG("Session timeout. Closing the HTTP bind "
+ "session: ~p",
+ [SID]),
{stop, normal, StateData};
-
handle_info({timeout, WaitTimer, _}, StateName,
#state{wait_timer = WaitTimer} = StateData) ->
- if
- StateData#state.http_receiver /= undefined ->
- cancel_timer(StateData#state.timer),
- Timer = set_inactivity_timer(StateData#state.pause,
- StateData#state.max_inactivity),
- gen_fsm:reply(StateData#state.http_receiver, {ok, empty}),
- Rid = StateData#state.rid,
- ReqList = [#hbr{rid = Rid,
- key = StateData#state.key,
- out = []
- } |
- [El || El <- StateData#state.req_list,
- El#hbr.rid /= Rid ]
- ],
- {next_state, StateName,
- StateData#state{http_receiver = undefined,
- req_list = ReqList,
- wait_timer = undefined,
- timer = Timer}};
- true ->
- {next_state, StateName, StateData}
+ if StateData#state.http_receiver /= undefined ->
+ cancel_timer(StateData#state.timer),
+ Timer = set_inactivity_timer(StateData#state.pause,
+ StateData#state.max_inactivity),
+ gen_fsm:reply(StateData#state.http_receiver,
+ {ok, empty}),
+ Rid = StateData#state.rid,
+ ReqList = [#hbr{rid = Rid, key = StateData#state.key,
+ out = []}
+ | [El
+ || El <- StateData#state.req_list, El#hbr.rid /= Rid]],
+ {next_state, StateName,
+ StateData#state{http_receiver = undefined,
+ req_list = ReqList, wait_timer = undefined,
+ timer = Timer}};
+ true -> {next_state, StateName, StateData}
end;
-
handle_info({timeout, ShaperTimer, _}, StateName,
#state{shaper_timer = ShaperTimer} = StateData) ->
- {next_state, StateName, StateData#state{shaper_timer = undefined}};
-
-handle_info({'DOWN', _MRef, process, C2SPid, _}, _StateName,
+ {next_state, StateName,
+ StateData#state{shaper_timer = undefined}};
+handle_info({'DOWN', _MRef, process, C2SPid, _},
+ _StateName,
#state{waiting_input = C2SPid} = StateData) ->
{stop, normal, StateData};
handle_info(_, StateName, StateData) ->
{next_state, StateName, StateData}.
-%%----------------------------------------------------------------------
-%% Func: terminate/3
-%% Purpose: Shutdown the fsm
-%% Returns: any
-%%----------------------------------------------------------------------
terminate(_Reason, _StateName, StateData) ->
- ?DEBUG("terminate: Deleting session ~s", [StateData#state.id]),
+ ?DEBUG("terminate: Deleting session ~s",
+ [StateData#state.id]),
mnesia:dirty_delete({http_bind, StateData#state.id}),
- send_receiver_reply(StateData#state.http_receiver, {ok, terminate}),
+ send_receiver_reply(StateData#state.http_receiver,
+ {ok, terminate}),
case StateData#state.waiting_input of
- false ->
- ok;
- C2SPid ->
- gen_fsm:send_event(C2SPid, closed)
+ false -> ok;
+ C2SPid -> gen_fsm:send_event(C2SPid, closed)
end,
ok.
@@ -623,738 +550,734 @@ terminate(_Reason, _StateName, StateData) ->
%%% Internal functions
%%%----------------------------------------------------------------------
-%% PUT / Get processing:
-handle_http_put_event(#http_put{rid = Rid, attrs = Attrs,
- hold = Hold} = Request,
+handle_http_put_event(#http_put{rid = Rid,
+ attrs = Attrs, hold = Hold} =
+ Request,
StateName, StateData) ->
- ?DEBUG("New request: ~p",[Request]),
- %% Check if Rid valid
- RidAllow = rid_allow(StateData#state.rid, Rid, Attrs, Hold,
- StateData#state.max_pause),
-
- %% Check if Rid is in sequence or out of sequence:
+ ?DEBUG("New request: ~p", [Request]),
+ RidAllow = rid_allow(StateData#state.rid, Rid, Attrs,
+ Hold, StateData#state.max_pause),
case RidAllow of
- buffer ->
- ?DEBUG("Buffered request: ~p", [Request]),
- %% Request is out of sequence:
- PendingRequests = StateData#state.unprocessed_req_list,
- %% In case an existing RID was already buffered:
- Requests = lists:keydelete(Rid, 2, PendingRequests),
- ReqList = [#hbr{rid = Rid,
- key = StateData#state.key,
- out = []
- } |
- [El || El <- StateData#state.req_list,
- El#hbr.rid > (Rid - 1 - Hold)]
- ],
- ?DEBUG("reqlist: ~p", [ReqList]),
- UnprocessedReqList = [Request | Requests],
- cancel_timer(StateData#state.timer),
- Timer = set_inactivity_timer(0, StateData#state.max_inactivity),
- {reply, ok, StateName,
- StateData#state{unprocessed_req_list = UnprocessedReqList,
- req_list = ReqList,
- timer = Timer}, hibernate};
-
- _ ->
- %% Request is in sequence:
- process_http_put(Request, StateName, StateData, RidAllow)
+ buffer ->
+ ?DEBUG("Buffered request: ~p", [Request]),
+ PendingRequests = StateData#state.unprocessed_req_list,
+ Requests = lists:keydelete(Rid, 2, PendingRequests),
+ ReqList = [#hbr{rid = Rid, key = StateData#state.key,
+ out = []}
+ | [El
+ || El <- StateData#state.req_list,
+ El#hbr.rid > Rid - 1 - Hold]],
+ ?DEBUG("reqlist: ~p", [ReqList]),
+ UnprocessedReqList = [Request | Requests],
+ cancel_timer(StateData#state.timer),
+ Timer = set_inactivity_timer(0,
+ StateData#state.max_inactivity),
+ {reply, ok, StateName,
+ StateData#state{unprocessed_req_list =
+ UnprocessedReqList,
+ req_list = ReqList, timer = Timer},
+ hibernate};
+ _ ->
+ process_http_put(Request, StateName, StateData,
+ RidAllow)
end.
-process_http_put(#http_put{rid = Rid, attrs = Attrs, payload = Payload,
- hold = Hold, stream = StreamTo,
- ip = IP} = Request,
+process_http_put(#http_put{rid = Rid, attrs = Attrs,
+ payload = Payload, hold = Hold, stream = StreamTo,
+ ip = IP} =
+ Request,
StateName, StateData, RidAllow) ->
?DEBUG("Actually processing request: ~p", [Request]),
- %% Check if key valid
- Key = xml:get_attr_s("key", Attrs),
- NewKey = xml:get_attr_s("newkey", Attrs),
- KeyAllow =
- case RidAllow of
- repeat ->
- true;
- false ->
- false;
- {true, _} ->
- case StateData#state.key of
- "" ->
- true;
- OldKey ->
- NextKey = sha:sha(Key),
- ?DEBUG("Key/OldKey/NextKey: ~s/~s/~s", [Key, OldKey, NextKey]),
- if
- OldKey == NextKey ->
- true;
- true ->
- ?DEBUG("wrong key: ~s",[Key]),
- false
- end
- end
- end,
+ Key = xml:get_attr_s(<<"key">>, Attrs),
+ NewKey = xml:get_attr_s(<<"newkey">>, Attrs),
+ KeyAllow = case RidAllow of
+ repeat -> true;
+ false -> false;
+ {true, _} ->
+ case StateData#state.key of
+ <<"">> -> true;
+ OldKey ->
+ NextKey = sha:sha(Key),
+ ?DEBUG("Key/OldKey/NextKey: ~s/~s/~s",
+ [Key, OldKey, NextKey]),
+ if OldKey == NextKey -> true;
+ true -> ?DEBUG("wrong key: ~s", [Key]), false
+ end
+ end
+ end,
TNow = tnow(),
- LastPoll = if
- Payload == [] ->
- TNow;
- true ->
- 0
+ LastPoll = if Payload == [] -> TNow;
+ true -> 0
end,
- if
- (Payload == []) and
- (Hold == 0) and
- (TNow - StateData#state.last_poll < ?MIN_POLLING) ->
- Reply = {error, polling_too_frequently},
- {reply, Reply, StateName, StateData};
- KeyAllow ->
- case RidAllow of
- false ->
- Reply = {error, not_exists},
- {reply, Reply, StateName, StateData};
- repeat ->
- ?DEBUG("REPEATING ~p", [Rid]),
- case [El#hbr.out ||
- El <- StateData#state.req_list,
- El#hbr.rid == Rid] of
- [] ->
- {error, not_exists};
- [Out | _XS] ->
- if (Rid == StateData#state.rid) and
- (StateData#state.http_receiver /= undefined) ->
- {reply, ok, StateName, StateData};
- true ->
- Reply = {repeat, lists:reverse(Out)},
- {reply, Reply, StateName, StateData#state{last_poll = LastPoll}}
- end
- end;
- {true, Pause} ->
- SaveKey = if
- NewKey == "" ->
- Key;
- true ->
- NewKey
- end,
- ?DEBUG(" -- SaveKey: ~s~n", [SaveKey]),
-
- %% save request
- ReqList1 =
- [El || El <- StateData#state.req_list,
- El#hbr.rid > (Rid - 1 - Hold)],
- ReqList =
- case lists:keymember(Rid, #hbr.rid, ReqList1) of
- true ->
- ReqList1;
- false ->
- [#hbr{rid = Rid,
- key = StateData#state.key,
- out = []
- } |
- ReqList1
- ]
- end,
- ?DEBUG("reqlist: ~p", [ReqList]),
-
- %% setup next timer
- cancel_timer(StateData#state.timer),
- Timer = set_inactivity_timer(Pause,
- StateData#state.max_inactivity),
- case StateData#state.waiting_input of
- false ->
- Input =
- lists:foldl(
- fun queue:in/2,
- StateData#state.input, Payload),
- Reply = ok,
- process_buffered_request(Reply, StateName,
- StateData#state{input = Input,
- rid = Rid,
- key = SaveKey,
- ctime = TNow,
- timer = Timer,
- pause = Pause,
- last_poll = LastPoll,
- req_list = ReqList,
- ip = IP
- });
- C2SPid ->
- JID = StateData#state.jid,
- case StreamTo of
- {To, ""} when JID == undefined ->
- gen_fsm:send_event(
- C2SPid,
- {xmlstreamstart, "stream:stream",
- [{"to", To},
- {"xmlns", ?NS_CLIENT},
- {"xmlns:stream", ?NS_STREAM}]});
- {To, Version} when JID == undefined ->
- gen_fsm:send_event(
- C2SPid,
- {xmlstreamstart, "stream:stream",
- [{"to", To},
- {"xmlns", ?NS_CLIENT},
- {"version", Version},
- {"xmlns:stream", ?NS_STREAM}]});
- _ ->
- ok
- end,
-
- MaxInactivity = get_max_inactivity(StreamTo, StateData#state.max_inactivity),
- MaxPause = get_max_inactivity(StreamTo, StateData#state.max_pause),
-
- ?DEBUG("really sending now: ~p", [Payload]),
- lists:foreach(
- fun({xmlstreamend, End}) ->
- gen_fsm:send_event(
- C2SPid, {xmlstreamend, End});
- (El) ->
- gen_fsm:send_event(
- C2SPid, {xmlstreamelement, El})
- end, Payload),
- Reply = ok,
- process_buffered_request(Reply, StateName,
- StateData#state{input = queue:new(),
- rid = Rid,
- key = SaveKey,
- ctime = TNow,
- timer = Timer,
- pause = Pause,
- last_poll = LastPoll,
- req_list = ReqList,
- max_inactivity = MaxInactivity,
- max_pause = MaxPause,
- ip = IP
- })
- end
- end;
- true ->
- Reply = {error, bad_key},
- {reply, Reply, StateName, StateData}
+ if (Payload == []) and (Hold == 0) and
+ (TNow - StateData#state.last_poll < (?MIN_POLLING)) ->
+ Reply = {error, polling_too_frequently},
+ {reply, Reply, StateName, StateData};
+ KeyAllow ->
+ case RidAllow of
+ false ->
+ Reply = {error, not_exists},
+ {reply, Reply, StateName, StateData};
+ repeat ->
+ ?DEBUG("REPEATING ~p", [Rid]),
+ case [El#hbr.out
+ || El <- StateData#state.req_list, El#hbr.rid == Rid]
+ of
+ [] -> {error, not_exists};
+ [Out | _XS] ->
+ if (Rid == StateData#state.rid) and
+ (StateData#state.http_receiver /= undefined) ->
+ {reply, ok, StateName, StateData};
+ true ->
+ Reply = {repeat, lists:reverse(Out)},
+ {reply, Reply, StateName,
+ StateData#state{last_poll = LastPoll}}
+ end
+ end;
+ {true, Pause} ->
+ SaveKey = if NewKey == <<"">> -> Key;
+ true -> NewKey
+ end,
+ ?DEBUG(" -- SaveKey: ~s~n", [SaveKey]),
+ ReqList1 = [El
+ || El <- StateData#state.req_list,
+ El#hbr.rid > Rid - 1 - Hold],
+ ReqList = case lists:keymember(Rid, #hbr.rid, ReqList1)
+ of
+ true -> ReqList1;
+ false ->
+ [#hbr{rid = Rid, key = StateData#state.key,
+ out = []}
+ | ReqList1]
+ end,
+ ?DEBUG("reqlist: ~p", [ReqList]),
+ cancel_timer(StateData#state.timer),
+ Timer = set_inactivity_timer(Pause,
+ StateData#state.max_inactivity),
+ case StateData#state.waiting_input of
+ false ->
+ Input = lists:foldl(fun queue:in/2,
+ StateData#state.input, Payload),
+ Reply = ok,
+ process_buffered_request(Reply, StateName,
+ StateData#state{input = Input,
+ rid = Rid,
+ key = SaveKey,
+ ctime = TNow,
+ timer = Timer,
+ pause = Pause,
+ last_poll =
+ LastPoll,
+ req_list =
+ ReqList,
+ ip = IP});
+ C2SPid ->
+ JID = StateData#state.jid,
+ case StreamTo of
+ {To, <<"">>} when JID == undefined ->
+ gen_fsm:send_event(C2SPid,
+ {xmlstreamstart,
+ <<"stream:stream">>,
+ [{<<"to">>, To},
+ {<<"xmlns">>, ?NS_CLIENT},
+ {<<"xmlns:stream">>,
+ ?NS_STREAM}]});
+ {To, Version} when JID == undefined ->
+ gen_fsm:send_event(C2SPid,
+ {xmlstreamstart,
+ <<"stream:stream">>,
+ [{<<"to">>, To},
+ {<<"xmlns">>, ?NS_CLIENT},
+ {<<"version">>, Version},
+ {<<"xmlns:stream">>,
+ ?NS_STREAM}]});
+ _ -> ok
+ end,
+ MaxInactivity = get_max_inactivity(StreamTo,
+ StateData#state.max_inactivity),
+ MaxPause = get_max_inactivity(StreamTo,
+ StateData#state.max_pause),
+ ?DEBUG("really sending now: ~p", [Payload]),
+ lists:foreach(fun ({xmlstreamend, End}) ->
+ gen_fsm:send_event(C2SPid,
+ {xmlstreamend,
+ End});
+ (El) ->
+ gen_fsm:send_event(C2SPid,
+ {xmlstreamelement,
+ El})
+ end,
+ Payload),
+ Reply = ok,
+ process_buffered_request(Reply, StateName,
+ StateData#state{input =
+ queue:new(),
+ rid = Rid,
+ key = SaveKey,
+ ctime = TNow,
+ timer = Timer,
+ pause = Pause,
+ last_poll =
+ LastPoll,
+ req_list =
+ ReqList,
+ max_inactivity =
+ MaxInactivity,
+ max_pause =
+ MaxPause,
+ ip = IP})
+ end
+ end;
+ true ->
+ Reply = {error, bad_key},
+ {reply, Reply, StateName, StateData}
end.
process_buffered_request(Reply, StateName, StateData) ->
Rid = StateData#state.rid,
Requests = StateData#state.unprocessed_req_list,
- case lists:keysearch(Rid+1, 2, Requests) of
- {value, Request} ->
- ?DEBUG("Processing buffered request: ~p", [Request]),
- NewRequests = lists:keydelete(Rid+1, 2, Requests),
- handle_http_put_event(
- Request, StateName,
- StateData#state{unprocessed_req_list = NewRequests});
- _ ->
- {reply, Reply, StateName, StateData, hibernate}
+ case lists:keysearch(Rid + 1, 2, Requests) of
+ {value, Request} ->
+ ?DEBUG("Processing buffered request: ~p", [Request]),
+ NewRequests = lists:keydelete(Rid + 1, 2, Requests),
+ handle_http_put_event(Request, StateName,
+ StateData#state{unprocessed_req_list =
+ NewRequests});
+ _ -> {reply, Reply, StateName, StateData, hibernate}
end.
-handle_http_put(Sid, Rid, Attrs, Payload, PayloadSize, StreamStart, IP) ->
- case http_put(Sid, Rid, Attrs, Payload, PayloadSize, StreamStart, IP) of
- {error, not_exists} ->
- ?DEBUG("no session associated with sid: ~p", [Sid]),
- {404, ?HEADER, ""};
- {{error, Reason}, Sess} ->
- ?DEBUG("Error on HTTP put. Reason: ~p", [Reason]),
- handle_http_put_error(Reason, Sess);
- {{repeat, OutPacket}, Sess} ->
- ?DEBUG("http_put said 'repeat!' ...~nOutPacket: ~p", [OutPacket]),
- send_outpacket(Sess, OutPacket);
- {{wait, Pause}, _Sess} ->
- ?DEBUG("Trafic Shaper: Delaying request ~p", [Rid]),
- timer:sleep(Pause),
- %{200, ?HEADER,
- % xml:element_to_binary(
- % {xmlelement, "body",
- % [{"xmlns", ?NS_HTTP_BIND},
- % {"type", "error"}], []})};
- handle_http_put(Sid, Rid, Attrs, Payload, PayloadSize,
- StreamStart, IP);
- {ok, Sess} ->
- prepare_response(Sess, Rid, [], StreamStart)
+handle_http_put(Sid, Rid, Attrs, Payload, PayloadSize,
+ StreamStart, IP) ->
+ case http_put(Sid, Rid, Attrs, Payload, PayloadSize,
+ StreamStart, IP)
+ of
+ {error, not_exists} ->
+ ?DEBUG("no session associated with sid: ~p", [Sid]),
+ {404, ?HEADER, <<"">>};
+ {{error, Reason}, Sess} ->
+ ?DEBUG("Error on HTTP put. Reason: ~p", [Reason]),
+ handle_http_put_error(Reason, Sess);
+ {{repeat, OutPacket}, Sess} ->
+ ?DEBUG("http_put said 'repeat!' ...~nOutPacket: ~p",
+ [OutPacket]),
+ send_outpacket(Sess, OutPacket);
+ {{wait, Pause}, _Sess} ->
+ ?DEBUG("Trafic Shaper: Delaying request ~p", [Rid]),
+ timer:sleep(Pause),
+ handle_http_put(Sid, Rid, Attrs, Payload, PayloadSize,
+ StreamStart, IP);
+ {ok, Sess} ->
+ prepare_response(Sess, Rid, [], StreamStart)
end.
-http_put(Sid, Rid, Attrs, Payload, PayloadSize, StreamStart, IP) ->
+http_put(Sid, Rid, Attrs, Payload, PayloadSize,
+ StreamStart, IP) ->
?DEBUG("Looking for session: ~p", [Sid]),
case get_session(Sid) of
- {error, _} ->
- {error, not_exists};
- {ok, #http_bind{pid = FsmRef, hold=Hold, to={To, StreamVersion}}=Sess}->
- NewStream =
- case StreamStart of
- true ->
- {To, StreamVersion};
- _ ->
- ""
- end,
- case catch {gen_fsm:sync_send_all_state_event(
- FsmRef,
- #http_put{rid = Rid, attrs = Attrs, payload = Payload,
- payload_size = PayloadSize, hold = Hold,
- stream = NewStream, ip = IP}, 30000), Sess} of
- {'EXIT', _} ->
- {error, not_exists};
- Res ->
- Res
- end
+ {error, _} -> {error, not_exists};
+ {ok,
+ #http_bind{pid = FsmRef, hold = Hold,
+ to = {To, StreamVersion}} =
+ Sess} ->
+ NewStream = case StreamStart of
+ true -> {To, StreamVersion};
+ _ -> <<"">>
+ end,
+ case catch {gen_fsm:sync_send_all_state_event(FsmRef,
+ #http_put{rid = Rid,
+ attrs = Attrs,
+ payload =
+ Payload,
+ payload_size =
+ PayloadSize,
+ hold = Hold,
+ stream =
+ NewStream,
+ ip = IP},
+ 30000),
+ Sess}
+ of
+ {'EXIT', _} -> {error, not_exists};
+ Res -> Res
+ end
end.
-handle_http_put_error(Reason, #http_bind{pid=FsmRef, version=Version})
- when Version >= 0 ->
- gen_fsm:sync_send_all_state_event(FsmRef, {stop, {put_error,Reason}}),
+handle_http_put_error(Reason,
+ #http_bind{pid = FsmRef, version = Version})
+ when Version >= 0 ->
+ gen_fsm:sync_send_all_state_event(FsmRef,
+ {stop, {put_error, Reason}}),
case Reason of
- not_exists ->
- {200, ?HEADER,
- xml:element_to_binary(
- {xmlelement, "body",
- [{"xmlns", ?NS_HTTP_BIND},
- {"type", "terminate"},
- {"condition", "item-not-found"}], []})};
- bad_key ->
- {200, ?HEADER,
- xml:element_to_binary(
- {xmlelement, "body",
- [{"xmlns", ?NS_HTTP_BIND},
- {"type", "terminate"},
- {"condition", "item-not-found"}], []})};
- polling_too_frequently ->
- {200, ?HEADER,
- xml:element_to_binary(
- {xmlelement, "body",
- [{"xmlns", ?NS_HTTP_BIND},
- {"type", "terminate"},
- {"condition", "policy-violation"}], []})}
+ not_exists ->
+ {200, ?HEADER,
+ xml:element_to_binary(#xmlel{name = <<"body">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_HTTP_BIND},
+ {<<"type">>, <<"terminate">>},
+ {<<"condition">>,
+ <<"item-not-found">>}],
+ children = []})};
+ bad_key ->
+ {200, ?HEADER,
+ xml:element_to_binary(#xmlel{name = <<"body">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_HTTP_BIND},
+ {<<"type">>, <<"terminate">>},
+ {<<"condition">>,
+ <<"item-not-found">>}],
+ children = []})};
+ polling_too_frequently ->
+ {200, ?HEADER,
+ xml:element_to_binary(#xmlel{name = <<"body">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_HTTP_BIND},
+ {<<"type">>, <<"terminate">>},
+ {<<"condition">>,
+ <<"policy-violation">>}],
+ children = []})}
end;
-handle_http_put_error(Reason, #http_bind{pid=FsmRef}) ->
- gen_fsm:sync_send_all_state_event(FsmRef,{stop, {put_error_no_version, Reason}}),
+handle_http_put_error(Reason,
+ #http_bind{pid = FsmRef}) ->
+ gen_fsm:sync_send_all_state_event(FsmRef,
+ {stop, {put_error_no_version, Reason}}),
case Reason of
- not_exists -> %% bad rid
- ?DEBUG("Closing HTTP bind session (Bad rid).", []),
- {404, ?HEADER, ""};
- bad_key ->
- ?DEBUG("Closing HTTP bind session (Bad key).", []),
- {404, ?HEADER, ""};
- polling_too_frequently ->
- ?DEBUG("Closing HTTP bind session (User polling too frequently).", []),
- {403, ?HEADER, ""}
+ not_exists -> %% bad rid
+ ?DEBUG("Closing HTTP bind session (Bad rid).", []),
+ {404, ?HEADER, <<"">>};
+ bad_key ->
+ ?DEBUG("Closing HTTP bind session (Bad key).", []),
+ {404, ?HEADER, <<"">>};
+ polling_too_frequently ->
+ ?DEBUG("Closing HTTP bind session (User polling "
+ "too frequently).",
+ []),
+ {403, ?HEADER, <<"">>}
end.
-%% Control RID ordering
rid_allow(none, _NewRid, _Attrs, _Hold, _MaxPause) ->
- %% First request - nothing saved so far
{true, 0};
rid_allow(OldRid, NewRid, Attrs, Hold, MaxPause) ->
- ?DEBUG("Previous rid / New rid: ~p/~p", [OldRid, NewRid]),
+ ?DEBUG("Previous rid / New rid: ~p/~p",
+ [OldRid, NewRid]),
if
- %% We did not miss any packet, we can process it immediately:
- NewRid == OldRid + 1 ->
- case catch list_to_integer(
- xml:get_attr_s("pause", Attrs)) of
- {'EXIT', _} ->
- {true, 0};
- Pause1 when Pause1 =< MaxPause ->
- ?DEBUG("got pause: ~p", [Pause1]),
- {true, Pause1};
- _ ->
- {true, 0}
- end;
- %% We have missed packets, we need to cached it to process it later on:
- (OldRid < NewRid) and
- (NewRid =< (OldRid + Hold + 1)) ->
- buffer;
- (NewRid =< OldRid) and
- (NewRid > OldRid - Hold - 1) ->
- repeat;
- true ->
- false
+ %% We did not miss any packet, we can process it immediately:
+ NewRid == OldRid + 1 ->
+ case catch
+ jlib:binary_to_integer(xml:get_attr_s(<<"pause">>,
+ Attrs))
+ of
+ {'EXIT', _} -> {true, 0};
+ Pause1 when Pause1 =< MaxPause ->
+ ?DEBUG("got pause: ~p", [Pause1]), {true, Pause1};
+ _ -> {true, 0}
+ end;
+ %% We have missed packets, we need to cached it to process it later on:
+ (OldRid < NewRid) and (NewRid =< OldRid + Hold + 1) ->
+ buffer;
+ (NewRid =< OldRid) and (NewRid > OldRid - Hold - 1) ->
+ repeat;
+ true -> false
end.
update_shaper(ShaperState, PayloadSize) ->
- {NewShaperState, Pause} = shaper:update(ShaperState, PayloadSize),
- if
- Pause > 0 ->
- ShaperTimer = erlang:start_timer(Pause, self(), activate), %% MR: Seems timer is not needed. Activate is not handled
- {NewShaperState, ShaperTimer};
- true ->
- {NewShaperState, undefined}
+ {NewShaperState, Pause} = shaper:update(ShaperState,
+ PayloadSize),
+ if Pause > 0 ->
+ ShaperTimer = erlang:start_timer(Pause, self(),
+ activate),
+ {NewShaperState, ShaperTimer};
+ true -> {NewShaperState, undefined}
end.
prepare_response(Sess, Rid, OutputEls, StreamStart) ->
- receive after Sess#http_bind.process_delay -> ok end,
+ receive after Sess#http_bind.process_delay -> ok end,
case catch http_get(Sess, Rid) of
- {ok, cancel} ->
- %% actually it would be better if we could completely
- %% cancel this request, but then we would have to hack
- %% ejabberd_http and I'm too lazy now
- {200, ?HEADER, "<body type='error' xmlns='"++?NS_HTTP_BIND++"'/>"};
- {ok, empty} ->
- {200, ?HEADER, "<body xmlns='"++?NS_HTTP_BIND++"'/>"};
- {ok, terminate} ->
- {200, ?HEADER, "<body type='terminate' xmlns='"++?NS_HTTP_BIND++"'/>"};
- {ok, {prebind, JID}} ->
- {200, ?HEADER,
- xml:element_to_string(
- {xmlelement, "body",
- [{"xmlns", ?NS_HTTP_BIND}, {"sid", Sess#http_bind.id},
- {"rid", integer_to_list(Rid + 1)}],
- [{xmlelement, "iq", [{"id", "pre_bind"}, {"type", "result"}],
- [{xmlelement, "bind", [{"xmlns", ?NS_BIND}],
- [{xmlelement, "jid", [],
- [{xmlcdata, jlib:jid_to_string(JID)}]}]}]}]})};
- {ok, ROutPacket} ->
- OutPacket = lists:reverse(ROutPacket),
- ?DEBUG("OutPacket: ~p", [OutputEls++OutPacket]),
- prepare_outpacket_response(Sess, Rid, OutputEls++OutPacket, StreamStart);
- {'EXIT', {shutdown, _}} ->
- {200, ?HEADER, "<body type='terminate' condition='system-shutdown' xmlns='"++?NS_HTTP_BIND++"'/>"};
- {'EXIT', _Reason} ->
- {200, ?HEADER, "<body type='terminate' xmlns='"++?NS_HTTP_BIND++"'/>"}
+ {ok, cancel} ->
+ {200, ?HEADER,
+ <<"<body type='error' xmlns='", (?NS_HTTP_BIND)/binary,
+ "'/>">>};
+ {ok, empty} ->
+ {200, ?HEADER,
+ <<"<body xmlns='", (?NS_HTTP_BIND)/binary, "'/>">>};
+ {ok, terminate} ->
+ {200, ?HEADER,
+ <<"<body type='terminate' xmlns='",
+ (?NS_HTTP_BIND)/binary, "'/>">>};
+ {ok, {prebind, JID}} ->
+ {200, ?HEADER,
+ xml:element_to_binary(#xmlel{name = <<"body">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_HTTP_BIND},
+ {<<"sid">>, Sess#http_bind.id},
+ {<<"rid">>,
+ iolist_to_binary(integer_to_list(Rid
+ +
+ 1))}],
+ children =
+ [#xmlel{name = <<"iq">>,
+ attrs =
+ [{<<"id">>,
+ <<"pre_bind">>},
+ {<<"type">>,
+ <<"result">>}],
+ children =
+ [#xmlel{name =
+ <<"bind">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_BIND}],
+ children =
+ [#xmlel{name
+ =
+ <<"jid">>,
+ attrs
+ =
+ [],
+ children
+ =
+ [{xmlcdata,
+ jlib:jid_to_string(JID)}]}]}]}]})};
+ {ok, ROutPacket} ->
+ OutPacket = lists:reverse(ROutPacket),
+ ?DEBUG("OutPacket: ~p", [OutputEls ++ OutPacket]),
+ prepare_outpacket_response(Sess, Rid,
+ OutputEls ++ OutPacket, StreamStart);
+ {'EXIT', {shutdown, _}} ->
+ {200, ?HEADER,
+ <<"<body type='terminate' condition='system-shut"
+ "down' xmlns='",
+ (?NS_HTTP_BIND)/binary, "'/>">>};
+ {'EXIT', _Reason} ->
+ {200, ?HEADER,
+ <<"<body type='terminate' xmlns='",
+ (?NS_HTTP_BIND)/binary, "'/>">>}
end.
-%% Send output payloads on establised sessions
-prepare_outpacket_response(Sess, _Rid, OutPacket, false) ->
+prepare_outpacket_response(Sess, _Rid, OutPacket,
+ false) ->
case catch send_outpacket(Sess, OutPacket) of
- {'EXIT', _Reason} ->
- ?DEBUG("Error in sending packet ~p ", [_Reason]),
- {200, ?HEADER,
- "<body type='terminate' xmlns='"++
- ?NS_HTTP_BIND++"'/>"};
- SendRes ->
- SendRes
+ {'EXIT', _Reason} ->
+ ?DEBUG("Error in sending packet ~p ", [_Reason]),
+ {200, ?HEADER,
+ <<"<body type='terminate' xmlns='",
+ (?NS_HTTP_BIND)/binary, "'/>">>};
+ SendRes -> SendRes
end;
%% Handle a new session along with its output payload
-prepare_outpacket_response(#http_bind{id=Sid, wait=Wait,
- hold=Hold, to=To}=_Sess,
- _Rid, OutPacket, true) ->
+prepare_outpacket_response(#http_bind{id = Sid,
+ wait = Wait, hold = Hold, to = To} =
+ _Sess,
+ _Rid, OutPacket, true) ->
case OutPacket of
- [{xmlstreamstart, _, OutAttrs} | Els] ->
- AuthID = xml:get_attr_s("id", OutAttrs),
- From = xml:get_attr_s("from", OutAttrs),
- Version = xml:get_attr_s("version", OutAttrs),
- OutEls =
- case Els of
- [] ->
- [];
- [{xmlstreamelement,
- {xmlelement, "stream:features",
- StreamAttribs, StreamEls}}
- | StreamTail] ->
- TypedTail =
- [check_default_xmlns(OEl) ||
- {xmlstreamelement, OEl} <-
- StreamTail],
- [{xmlelement, "stream:features",
- [{"xmlns:stream",
- ?NS_STREAM}] ++
- StreamAttribs, StreamEls}] ++
- TypedTail;
- StreamTail ->
- [check_default_xmlns(OEl) ||
- {xmlstreamelement, OEl} <-
- StreamTail]
- end,
- case OutEls of
- [{xmlelement,
- "stream:error",_,_}] ->
- {200, ?HEADER, "<body type='terminate' "
- "condition='host-unknown' "
- "xmlns='"++?NS_HTTP_BIND++"'/>"};
- _ ->
- BOSH_attribs =
- [{"authid", AuthID},
- {"xmlns:xmpp", ?NS_BOSH},
- {"xmlns:stream", ?NS_STREAM}] ++
- case OutEls of
- [] ->
- [];
- _ ->
- [{"xmpp:version", Version}]
- end,
- MaxInactivity = get_max_inactivity(To, ?MAX_INACTIVITY),
- MaxPause = get_max_pause(To),
- {200, ?HEADER,
- xml:element_to_binary(
- {xmlelement,"body",
- [{"xmlns",
- ?NS_HTTP_BIND},
- {"sid", Sid},
- {"wait", integer_to_list(Wait)},
- {"requests", integer_to_list(Hold+1)},
- {"inactivity",
- integer_to_list(
- trunc(MaxInactivity/1000))},
- {"maxpause",
- integer_to_list(MaxPause)},
- {"polling",
- integer_to_list(
- trunc(?MIN_POLLING/1000000))},
- {"ver", ?BOSH_VERSION},
- {"from", From},
- {"secure", "true"} %% we're always being secure
- ] ++ BOSH_attribs,OutEls})}
- end;
- _ ->
- {200, ?HEADER, "<body type='terminate' "
- "condition='internal-server-error' "
- "xmlns='"++?NS_HTTP_BIND++"'/>"}
+ [{xmlstreamstart, _, OutAttrs} | Els] ->
+ AuthID = xml:get_attr_s(<<"id">>, OutAttrs),
+ From = xml:get_attr_s(<<"from">>, OutAttrs),
+ Version = xml:get_attr_s(<<"version">>, OutAttrs),
+ OutEls = case Els of
+ [] -> [];
+ [{xmlstreamelement,
+ #xmlel{name = <<"stream:features">>,
+ attrs = StreamAttribs, children = StreamEls}}
+ | StreamTail] ->
+ TypedTail = [check_default_xmlns(OEl)
+ || {xmlstreamelement, OEl} <- StreamTail],
+ [#xmlel{name = <<"stream:features">>,
+ attrs =
+ [{<<"xmlns:stream">>, ?NS_STREAM}] ++
+ StreamAttribs,
+ children = StreamEls}]
+ ++ TypedTail;
+ StreamTail ->
+ [check_default_xmlns(OEl)
+ || {xmlstreamelement, OEl} <- StreamTail]
+ end,
+ case OutEls of
+ [#xmlel{name = <<"stream:error">>}] ->
+ {200, ?HEADER,
+ <<"<body type='terminate' condition='host-unknow"
+ "n' xmlns='",
+ (?NS_HTTP_BIND)/binary, "'/>">>};
+ _ ->
+ BOSH_attribs = [{<<"authid">>, AuthID},
+ {<<"xmlns:xmpp">>, ?NS_BOSH},
+ {<<"xmlns:stream">>, ?NS_STREAM}]
+ ++
+ case OutEls of
+ [] -> [];
+ _ -> [{<<"xmpp:version">>, Version}]
+ end,
+ MaxInactivity = get_max_inactivity(To, ?MAX_INACTIVITY),
+ MaxPause = get_max_pause(To),
+ {200, ?HEADER,
+ xml:element_to_binary(#xmlel{name = <<"body">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_HTTP_BIND},
+ {<<"sid">>, Sid},
+ {<<"wait">>,
+ iolist_to_binary(integer_to_list(Wait))},
+ {<<"requests">>,
+ iolist_to_binary(integer_to_list(Hold
+ +
+ 1))},
+ {<<"inactivity">>,
+ iolist_to_binary(integer_to_list(trunc(MaxInactivity
+ /
+ 1000)))},
+ {<<"maxpause">>,
+ iolist_to_binary(integer_to_list(MaxPause))},
+ {<<"polling">>,
+ iolist_to_binary(integer_to_list(trunc((?MIN_POLLING)
+ /
+ 1000000)))},
+ {<<"ver">>, ?BOSH_VERSION},
+ {<<"from">>, From},
+ {<<"secure">>, <<"true">>}]
+ ++ BOSH_attribs,
+ children = OutEls})}
+ end;
+ _ ->
+ {200, ?HEADER,
+ <<"<body type='terminate' condition='internal-se"
+ "rver-error' xmlns='",
+ (?NS_HTTP_BIND)/binary, "'/>">>}
end.
-
-http_get(#http_bind{pid = FsmRef, wait = Wait, hold = Hold}, Rid) ->
- gen_fsm:sync_send_all_state_event(
- FsmRef, {http_get, Rid, Wait, Hold}, 2 * ?MAX_WAIT * 1000).
+http_get(#http_bind{pid = FsmRef, wait = Wait,
+ hold = Hold},
+ Rid) ->
+ gen_fsm:sync_send_all_state_event(FsmRef,
+ {http_get, Rid, Wait, Hold},
+ 2 * (?MAX_WAIT) * 1000).
send_outpacket(#http_bind{pid = FsmRef}, OutPacket) ->
case OutPacket of
- [] ->
- {200, ?HEADER, "<body xmlns='"++?NS_HTTP_BIND++"'/>"};
- [{xmlstreamend, _}] ->
- gen_fsm:sync_send_all_state_event(FsmRef,{stop,stream_closed}),
- {200, ?HEADER, "<body xmlns='"++?NS_HTTP_BIND++"'/>"};
- _ ->
- %% TODO: We parse to add a default namespace to packet,
- %% The spec says adding the jabber:client namespace if
- %% mandatory, even if some implementation do not do that
- %% change on packets.
- %% I think this should be an option to avoid modifying
- %% packet in most case.
- AllElements =
- lists:all(fun({xmlstreamelement,
- {xmlelement, "stream:error", _, _}}) -> false;
- ({xmlstreamelement, _}) -> true;
- ({xmlstreamraw, _}) -> true;
- (_) -> false
- end, OutPacket),
- case AllElements of
- true ->
- TypedEls = lists:foldl(fun({xmlstreamelement, El}, Acc) ->
- Acc ++
- [xml:element_to_string(
- check_default_xmlns(El)
- )];
- ({xmlstreamraw, R}, Acc) ->
- Acc ++ [R]
- end,
- [],
- OutPacket),
-
- Body = "<body xmlns='"++?NS_HTTP_BIND++"'>"
- ++ TypedEls ++
- "</body>",
- ?DEBUG(" --- outgoing data --- ~n~s~n --- END --- ~n",
- [Body]),
- {200, ?HEADER, Body};
- false ->
- case OutPacket of
- [{xmlstreamstart, _, _} | SEls] ->
- OutEls =
- case SEls of
- [{xmlstreamelement,
- {xmlelement,
- "stream:features",
- StreamAttribs, StreamEls}} |
- StreamTail] ->
- TypedTail =
- [check_default_xmlns(OEl) ||
- {xmlstreamelement, OEl} <-
- StreamTail],
- [{xmlelement,
- "stream:features",
- [{"xmlns:stream",
- ?NS_STREAM}] ++
- StreamAttribs, StreamEls}] ++
- TypedTail;
- StreamTail ->
- [check_default_xmlns(OEl) ||
- {xmlstreamelement, OEl} <-
- StreamTail]
- end,
- {200, ?HEADER,
- xml:element_to_binary(
- {xmlelement,"body",
- [{"xmlns",
- ?NS_HTTP_BIND}],
- OutEls})};
+ [] ->
+ {200, ?HEADER,
+ <<"<body xmlns='", (?NS_HTTP_BIND)/binary, "'/>">>};
+ [{xmlstreamend, _}] ->
+ gen_fsm:sync_send_all_state_event(FsmRef,
+ {stop, stream_closed}),
+ {200, ?HEADER,
+ <<"<body xmlns='", (?NS_HTTP_BIND)/binary, "'/>">>};
+ _ ->
+ AllElements = lists:all(fun ({xmlstreamelement,
+ #xmlel{name = <<"stream:error">>}}) ->
+ false;
+ ({xmlstreamelement, _}) -> true;
+ ({xmlstreamraw, _}) -> true;
+ (_) -> false
+ end,
+ OutPacket),
+ case AllElements of
+ true ->
+ TypedEls = lists:foldl(fun ({xmlstreamelement, El},
+ Acc) ->
+ Acc ++
+ [xml:element_to_binary(check_default_xmlns(El))];
+ ({xmlstreamraw, R}, Acc) ->
+ Acc ++ [R]
+ end,
+ [], OutPacket),
+ Body = <<"<body xmlns='", (?NS_HTTP_BIND)/binary, "'>",
+ (iolist_to_binary(TypedEls))/binary, "</body>">>,
+ ?DEBUG(" --- outgoing data --- ~n~s~n --- END "
+ "--- ~n",
+ [Body]),
+ {200, ?HEADER, Body};
+ false ->
+ case OutPacket of
+ [{xmlstreamstart, _, _} | SEls] ->
+ OutEls = case SEls of
+ [{xmlstreamelement,
+ #xmlel{name = <<"stream:features">>,
+ attrs = StreamAttribs,
+ children = StreamEls}}
+ | StreamTail] ->
+ TypedTail = [check_default_xmlns(OEl)
+ || {xmlstreamelement, OEl}
+ <- StreamTail],
+ [#xmlel{name = <<"stream:features">>,
+ attrs =
+ [{<<"xmlns:stream">>,
+ ?NS_STREAM}]
+ ++ StreamAttribs,
+ children = StreamEls}]
+ ++ TypedTail;
+ StreamTail ->
+ [check_default_xmlns(OEl)
+ || {xmlstreamelement, OEl} <- StreamTail]
+ end,
+ {200, ?HEADER,
+ xml:element_to_binary(#xmlel{name = <<"body">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_HTTP_BIND}],
+ children = OutEls})};
+ _ ->
+ SErrCond = lists:filter(fun ({xmlstreamelement,
+ #xmlel{name =
+ <<"stream:error">>}}) ->
+ true;
+ (_) -> false
+ end,
+ OutPacket),
+ StreamErrCond = case SErrCond of
+ [] -> null;
+ [{xmlstreamelement,
+ #xmlel{} = StreamErrorTag}
+ | _] ->
+ [StreamErrorTag]
+ end,
+ gen_fsm:sync_send_all_state_event(FsmRef,
+ {stop,
+ {stream_error,
+ OutPacket}}),
+ case StreamErrCond of
+ null ->
+ {200, ?HEADER,
+ <<"<body type='terminate' condition='internal-se"
+ "rver-error' xmlns='",
+ (?NS_HTTP_BIND)/binary, "'/>">>};
_ ->
- SErrCond =
- lists:filter(
- fun({xmlstreamelement,
- {xmlelement, "stream:error",
- _, _}}) ->
- true;
- (_) -> false
- end, OutPacket),
- StreamErrCond =
- case SErrCond of
- [] ->
- null;
- [{xmlstreamelement,
- {xmlelement, _, _, _Cond} =
- StreamErrorTag} | _] ->
- [StreamErrorTag]
- end,
- gen_fsm:sync_send_all_state_event(FsmRef,
- {stop, {stream_error,OutPacket}}),
- case StreamErrCond of
- null ->
- {200, ?HEADER,
- "<body type='terminate' "
- "condition='internal-server-error' "
- "xmlns='"++?NS_HTTP_BIND++"'/>"};
- _ ->
- {200, ?HEADER,
- "<body type='terminate' "
- "condition='remote-stream-error' "
- "xmlns='"++?NS_HTTP_BIND++"' " ++
- "xmlns:stream='"++?NS_STREAM++"'>" ++
- elements_to_string(StreamErrCond) ++
- "</body>"}
- end
- end
- end
+ {200, ?HEADER,
+ <<"<body type='terminate' condition='remote-stre"
+ "am-error' xmlns='",
+ (?NS_HTTP_BIND)/binary, "' ", "xmlns:stream='",
+ (?NS_STREAM)/binary, "'>",
+ (elements_to_string(StreamErrCond))/binary,
+ "</body>">>}
+ end
+ end
+ end
end.
parse_request(Data, PayloadSize, MaxStanzaSize) ->
- ?DEBUG("--- incoming data --- ~n~s~n --- END --- ", [Data]),
- %% MR: I do not think it works if put put several elements in the
- %% same body:
+ ?DEBUG("--- incoming data --- ~n~s~n --- END "
+ "--- ",
+ [Data]),
case xml_stream:parse_element(Data) of
- {xmlelement, "body", Attrs, Els} ->
- Xmlns = xml:get_attr_s("xmlns",Attrs),
- if
- Xmlns /= ?NS_HTTP_BIND ->
- {error, bad_request};
- true ->
- case catch list_to_integer(xml:get_attr_s("rid", Attrs)) of
- {'EXIT', _} ->
- {error, bad_request};
- Rid ->
- %% I guess this is to remove XMLCDATA: Is it really needed ?
- FixedEls =
- lists:filter(
- fun(I) ->
- case I of
- {xmlelement, _, _, _} ->
- true;
- _ ->
- false
- end
- end, Els),
- Sid = xml:get_attr_s("sid",Attrs),
- if
- PayloadSize =< MaxStanzaSize ->
- {ok, {Sid, Rid, Attrs, FixedEls}};
- true ->
- {size_limit, Sid}
- end
- end
- end;
- {xmlelement, _Name, _Attrs, _Els} ->
- {error, bad_request};
- {error, _Reason} ->
- {error, bad_request}
+ #xmlel{name = <<"body">>, attrs = Attrs,
+ children = Els} ->
+ Xmlns = xml:get_attr_s(<<"xmlns">>, Attrs),
+ if Xmlns /= (?NS_HTTP_BIND) -> {error, bad_request};
+ true ->
+ case catch
+ jlib:binary_to_integer(xml:get_attr_s(<<"rid">>,
+ Attrs))
+ of
+ {'EXIT', _} -> {error, bad_request};
+ Rid ->
+ FixedEls = lists:filter(fun (I) ->
+ case I of
+ #xmlel{} -> true;
+ _ -> false
+ end
+ end,
+ Els),
+ Sid = xml:get_attr_s(<<"sid">>, Attrs),
+ if PayloadSize =< MaxStanzaSize ->
+ {ok, {Sid, Rid, Attrs, FixedEls}};
+ true -> {size_limit, Sid}
+ end
+ end
+ end;
+ #xmlel{} -> {error, bad_request};
+ {error, _Reason} -> {error, bad_request}
end.
-send_receiver_reply(undefined, _Reply) ->
- ok;
+send_receiver_reply(undefined, _Reply) -> ok;
send_receiver_reply(Receiver, Reply) ->
gen_fsm:reply(Receiver, Reply).
-
-%% Cancel timer and empty message queue.
-cancel_timer(undefined) ->
- ok;
+cancel_timer(undefined) -> ok;
cancel_timer(Timer) ->
erlang:cancel_timer(Timer),
- receive
- {timeout, Timer, _} ->
- ok
- after 0 ->
- ok
- end.
+ receive {timeout, Timer, _} -> ok after 0 -> ok end.
-%% If client asked for a pause (pause > 0), we apply the pause value
-%% as inactivity timer:
-set_inactivity_timer(Pause, _MaxInactivity) when Pause > 0 ->
- erlang:start_timer(Pause*1000, self(), []);
+set_inactivity_timer(Pause, _MaxInactivity)
+ when Pause > 0 ->
+ erlang:start_timer(Pause * 1000, self(), []);
%% Otherwise, we apply the max_inactivity value as inactivity timer:
set_inactivity_timer(_Pause, MaxInactivity) ->
erlang:start_timer(MaxInactivity, self(), []).
-
-%% TODO: Use tail recursion and list reverse ?
-elements_to_string([]) ->
- [];
+elements_to_string([]) -> [];
elements_to_string([El | Els]) ->
- [xml:element_to_binary(El)|elements_to_string(Els)].
+ [xml:element_to_binary(El) | elements_to_string(Els)].
-%% @spec (To, Default::integer()) -> integer()
-%% where To = [] | {Host::string(), Version::string()}
get_max_inactivity({Host, _}, Default) ->
- case gen_mod:get_module_opt(Host, mod_http_bind, max_inactivity, undefined) of
- Seconds when is_integer(Seconds) ->
- Seconds * 1000;
- undefined ->
- Default
+ case gen_mod:get_module_opt(Host, mod_http_bind, max_inactivity,
+ fun(I) when is_integer(I), I>0 -> I end,
+ undefined)
+ of
+ Seconds when is_integer(Seconds) -> Seconds * 1000;
+ undefined -> Default
end;
-get_max_inactivity(_, Default) ->
- Default.
+get_max_inactivity(_, Default) -> Default.
get_max_pause({Host, _}) ->
- gen_mod:get_module_opt(Host, mod_http_bind, max_pause, ?MAX_PAUSE);
-get_max_pause(_) ->
- ?MAX_PAUSE.
+ gen_mod:get_module_opt(Host, mod_http_bind, max_pause,
+ fun(I) when is_integer(I), I>0 -> I end,
+ ?MAX_PAUSE);
+get_max_pause(_) -> ?MAX_PAUSE.
-%% Current time as integer
tnow() ->
{TMegSec, TSec, TMSec} = now(),
(TMegSec * 1000000 + TSec) * 1000000 + TMSec.
-check_default_xmlns({xmlelement, Name, Attrs, Els} = El) ->
- case xml:get_tag_attr_s("xmlns", El) of
- "" -> {xmlelement, Name, [{"xmlns", ?NS_CLIENT} | Attrs], Els};
- _ -> El
+check_default_xmlns(#xmlel{name = Name, attrs = Attrs,
+ children = Els} =
+ El) ->
+ case xml:get_tag_attr_s(<<"xmlns">>, El) of
+ <<"">> ->
+ #xmlel{name = Name,
+ attrs = [{<<"xmlns">>, ?NS_CLIENT} | Attrs],
+ children = Els};
+ _ -> El
end;
-check_default_xmlns(El) ->
- El.
+check_default_xmlns(El) -> El.
-%% Check that mod_http_bind has been defined in config file.
-%% Print a warning in log file if this is not the case.
check_bind_module(XmppDomain) ->
case gen_mod:is_loaded(XmppDomain, mod_http_bind) of
- true -> ok;
- false -> ?ERROR_MSG("You are trying to use BOSH (HTTP Bind) in host ~p,"
- " but the module mod_http_bind is not started in"
- " that host. Configure your BOSH client to connect"
- " to the correct host, or add your desired host to"
- " the configuration, or check your 'modules'"
- " section in your ejabberd configuration file.",
- [XmppDomain])
+ true -> true;
+ false ->
+ ?ERROR_MSG("You are trying to use BOSH (HTTP Bind) "
+ "in host ~p, but the module mod_http_bind "
+ "is not started in that host. Configure "
+ "your BOSH client to connect to the correct "
+ "host, or add your desired host to the "
+ "configuration, or check your 'modules' "
+ "section in your ejabberd configuration "
+ "file.",
+ [XmppDomain]),
+ false
end.
make_sid() ->
- sha:sha(term_to_binary({now(), make_ref()}))
- ++ "-" ++ ejabberd_cluster:node_id().
+ <<(sha:sha(term_to_binary({now(), make_ref()})))/binary,
+ "-", (ejabberd_cluster:node_id())/binary>>.
get_session(SID) ->
- case string:tokens(SID, "-") of
- [_, NodeID] ->
- case ejabberd_cluster:get_node_by_id(NodeID) of
- Node when Node == node() ->
- case mnesia:dirty_read({http_bind, SID}) of
- [] ->
- {error, enoent};
- [Session] ->
- {ok, Session}
- end;
- Node ->
- case catch rpc:call(Node, mnesia, dirty_read,
- [{http_bind, SID}], 5000) of
- [Session] ->
- {ok, Session};
- _ ->
- {error, enoent}
- end
- end;
- _ ->
- {error, enoent}
+ case str:tokens(SID, <<"-">>) of
+ [_, NodeID] ->
+ case ejabberd_cluster:get_node_by_id(NodeID) of
+ Node when Node == node() ->
+ case mnesia:dirty_read({http_bind, SID}) of
+ [] -> {error, enoent};
+ [Session] -> {ok, Session}
+ end;
+ Node ->
+ case catch rpc:call(Node, mnesia, dirty_read,
+ [{http_bind, SID}], 5000)
+ of
+ [Session] -> {ok, Session};
+ _ -> {error, enoent}
+ end
+ end;
+ _ -> {error, enoent}
end.
make_random_jid(Host) ->
- %% Copied from cyrsasl_anonymous.erl
- User = lists:concat([randoms:get_string() | tuple_to_list(now())]),
+ User = iolist_to_binary([randoms:get_string()
+ | tuple_to_list(now())]),
jlib:make_jid(User, Host, randoms:get_string()).
diff --git a/src/web/ejabberd_http_bindjson.erl b/src/web/ejabberd_http_bindjson.erl
index 61f8779e8..245416142 100644
--- a/src/web/ejabberd_http_bindjson.erl
+++ b/src/web/ejabberd_http_bindjson.erl
@@ -7,393 +7,360 @@
%%% Id : $Id: ejabberd_http_bind.erl 953 2009-05-07 10:40:40Z alexey $
%%%----------------------------------------------------------------------
--module (ejabberd_http_bindjson).
+-module(ejabberd_http_bindjson).
-behaviour(gen_fsm).
%% External exports
--export([start_link/3,
- init/1,
- handle_event/3,
- handle_sync_event/4,
- code_change/4,
- handle_info/3,
- terminate/3,
- send/2,
- send_xml/2,
- sockname/1,
- peername/1,
- setopts/2,
- controlling_process/2,
- become_controller/2,
- change_controller/2,
- custom_receiver/1,
- reset_stream/1,
- change_shaper/2,
- monitor/1,
- close/1,
- start/4,
- handle_session_start/8,
- handle_http_put/7,
- http_put/7,
- http_get/2,
- prepare_response/4,
- process_request/2]).
+-export([start_link/3, init/1, handle_event/3,
+ handle_sync_event/4, code_change/4, handle_info/3,
+ terminate/3, send/2, send_xml/2, sockname/1, peername/1,
+ setopts/2, controlling_process/2, become_controller/2,
+ change_controller/2, custom_receiver/1, reset_stream/1,
+ change_shaper/2, monitor/1, close/1, start/4,
+ handle_session_start/8, handle_http_put/7, http_put/7,
+ http_get/2, prepare_response/4, process_request/2]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
+
-include("ejabberd_http.hrl").
+
-include("http_bind.hrl").
--record(http_bind, {id, pid, to, hold, wait, process_delay, version}).
+-record(http_bind,
+{
+ id,
+ pid,
+ to,
+ hold,
+ wait,
+ process_delay,
+ version
+}).
-define(NULL_PEER, {{0, 0, 0, 0}, 0}).
-%% http binding request
--record(hbr, {rid,
- key,
- out}).
-
--record(state, {id,
- rid = none,
- key,
- socket,
- output = "",
- input = queue:new(),
- waiting_input = false,
- shaper_state,
- shaper_timer,
- last_receiver,
- last_poll,
- http_receiver,
- wait_timer,
- ctime = 0,
- timer,
- pause=0,
- unprocessed_req_list = [], % list of request that have been delayed for proper reordering: {Request, PID}
- req_list = [], % list of requests (cache)
- max_inactivity,
- max_pause,
- ip = ?NULL_PEER
- }).
-
-%% Internal request format:
--record(http_put, {rid,
- attrs,
- payload,
- payload_size,
- hold,
- stream,
- ip}).
+-record(hbr, {rid, key, out}).
+
+-record(state,
+{
+ id,
+ rid = none,
+ key,
+ socket,
+ output = [],
+ input = queue:new(),
+ waiting_input = false,
+ shaper_state,
+ shaper_timer,
+ last_receiver,
+ last_poll,
+ http_receiver,
+ wait_timer,
+ ctime = 0,
+ timer,
+ pause = 0,
+ unprocessed_req_list = [],
+ req_list = [],
+ max_inactivity,
+ max_pause,
+ ip = ?NULL_PEER
+}).
+
+-record(http_put,
+{
+ rid,
+ attrs,
+ payload,
+ payload_size,
+ hold, stream,
+ ip
+}).
%%-define(DBGFSM, true).
-ifdef(DBGFSM).
+
-define(FSMOPTS, [{debug, [trace]}]).
+
-else.
+
-define(FSMOPTS, []).
+
-endif.
--define(BOSH_VERSION, "1.8").
--define(NS_CLIENT, "jabber:client").
--define(NS_BOSH, "urn:xmpp:xbosh").
--define(NS_HTTP_BIND, "http://jabber.org/protocol/httpbind").
-
--define(MAX_REQUESTS, 2). % number of simultaneous requests
--define(MIN_POLLING, 2000000). % don't poll faster than that or we will
- % shoot you (time in microsec)
--define(MAX_WAIT, 3600). % max num of secs to keep a request on hold
--define(MAX_INACTIVITY, 30000). % msecs to wait before terminating
- % idle sessions
--define(MAX_PAUSE, 120). % may num of sec a client is allowed to pause
- % the session
-
-%% Wait 100ms before continue processing, to allow the client provide more related stanzas.
+-define(BOSH_VERSION, <<"1.8">>).
+
+-define(NS_CLIENT, <<"jabber:client">>).
+
+-define(NS_BOSH, <<"urn:xmpp:xbosh">>).
+
+-define(NS_HTTP_BIND,
+ <<"http://jabber.org/protocol/httpbind">>).
+
+-define(MAX_REQUESTS, 2).
+
+-define(MIN_POLLING, 2000000).
+
+-define(MAX_WAIT, 3600).
+
+-define(MAX_INACTIVITY, 30000).
+
+-define(MAX_PAUSE, 120).
+
-define(PROCESS_DELAY_DEFAULT, 100).
+
-define(PROCESS_DELAY_MIN, 0).
--define(PROCESS_DELAY_MAX, 1000).
+-define(PROCESS_DELAY_MAX, 1000).
-%%%----------------------------------------------------------------------
-%%% API
-%%%----------------------------------------------------------------------
-%% TODO: If compile with no supervisor option, start the session without
-%% supervisor
start(XMPPDomain, Sid, Key, IP) ->
?DEBUG("Starting session", []),
- case catch supervisor:start_child(ejabberd_http_bind_sup, [Sid, Key, IP]) of
- {ok, Pid} ->
- {ok, Pid};
- {error, _} = Err ->
- case check_bind_module(XMPPDomain) of
- false ->
- {error, "Cannot start HTTP bind session"};
- true ->
- ?ERROR_MSG("Cannot start HTTP bind session: ~p", [Err]),
- Err
- end;
- Exit ->
- ?ERROR_MSG("Cannot start HTTP bind session: ~p", [Exit]),
- {error, Exit}
+ case catch
+ supervisor:start_child(ejabberd_http_bind_sup,
+ [Sid, Key, IP])
+ of
+ {ok, Pid} -> {ok, Pid};
+ {error, _} = Err ->
+ case check_bind_module(XMPPDomain) of
+ false -> {error, <<"Cannot start HTTP bind session">>};
+ true ->
+ ?ERROR_MSG("Cannot start HTTP bind session: ~p", [Err]),
+ Err
+ end;
+ Exit ->
+ ?ERROR_MSG("Cannot start HTTP bind session: ~p",
+ [Exit]),
+ {error, Exit}
end.
start_link(Sid, Key, IP) ->
gen_fsm:start_link(?MODULE, [Sid, Key, IP], ?FSMOPTS).
send({http_bind, FsmRef, _IP}, Packet) ->
- gen_fsm:sync_send_all_state_event(FsmRef, {send, Packet}).
+ gen_fsm:sync_send_all_state_event(FsmRef,
+ {send, Packet}).
send_xml({http_bind, FsmRef, _IP}, Packet) ->
- gen_fsm:sync_send_all_state_event(FsmRef, {send_xml, Packet}).
+ gen_fsm:sync_send_all_state_event(FsmRef,
+ {send_xml, Packet}).
setopts({http_bind, FsmRef, _IP}, Opts) ->
case lists:member({active, once}, Opts) of
- true ->
- gen_fsm:send_all_state_event(FsmRef, {activate, self()});
- _ ->
- case lists:member({active, false}, Opts) of
- true ->
- gen_fsm:sync_send_all_state_event(
- FsmRef, deactivate_socket);
- _ ->
- ok
- end
+ true ->
+ gen_fsm:send_all_state_event(FsmRef,
+ {activate, self()});
+ _ ->
+ case lists:member({active, false}, Opts) of
+ true ->
+ gen_fsm:sync_send_all_state_event(FsmRef,
+ deactivate_socket);
+ _ -> ok
+ end
end.
-controlling_process(_Socket, _Pid) ->
- ok.
+controlling_process(_Socket, _Pid) -> ok.
custom_receiver({http_bind, FsmRef, _IP}) ->
{receiver, ?MODULE, FsmRef}.
become_controller(FsmRef, C2SPid) ->
- gen_fsm:send_all_state_event(FsmRef, {become_controller, C2SPid}).
+ gen_fsm:send_all_state_event(FsmRef,
+ {become_controller, C2SPid}).
change_controller({http_bind, FsmRef, _IP}, C2SPid) ->
become_controller(FsmRef, C2SPid).
-reset_stream({http_bind, _FsmRef, _IP}) ->
- ok.
+reset_stream({http_bind, _FsmRef, _IP}) -> ok.
change_shaper({http_bind, FsmRef, _IP}, Shaper) ->
- gen_fsm:send_all_state_event(FsmRef, {change_shaper, Shaper}).
+ gen_fsm:send_all_state_event(FsmRef,
+ {change_shaper, Shaper}).
monitor({http_bind, FsmRef, _IP}) ->
erlang:monitor(process, FsmRef).
close({http_bind, FsmRef, _IP}) ->
- catch gen_fsm:sync_send_all_state_event(FsmRef, {stop, close}).
+ catch gen_fsm:sync_send_all_state_event(FsmRef,
+ {stop, close}).
-sockname(_Socket) ->
- {ok, ?NULL_PEER}.
+sockname(_Socket) -> {ok, ?NULL_PEER}.
-peername({http_bind, _FsmRef, IP}) ->
- {ok, IP}.
+peername({http_bind, _FsmRef, IP}) -> {ok, IP}.
-%% Entry point for data coming from client through ejabberd HTTP server:
process_request(Data, IP) ->
Opts1 = ejabberd_c2s_config:get_c2s_limits(),
Opts = [{xml_socket, true} | Opts1],
- MaxStanzaSize =
- case lists:keysearch(max_stanza_size, 1, Opts) of
- {value, {_, Size}} -> Size;
- _ -> infinity
- end,
+ MaxStanzaSize = case lists:keysearch(max_stanza_size, 1,
+ Opts)
+ of
+ {value, {_, Size}} -> Size;
+ _ -> infinity
+ end,
PayloadSize = iolist_size(Data),
- case catch parse_request(Data, PayloadSize, MaxStanzaSize) of
- %% No existing session:
- {ok, {"", Rid, Attrs, Payload}} ->
- case xml:get_attr_s("to",Attrs) of
- "" ->
- ?DEBUG("Session not created (Improper addressing)", []),
- {200, ?HEADER, "{\"body\":{\"type\":\"terminate\" "
- "\"condition\":\"improper-addressing\", "
- "\"xmlns\":\"" ++ ?NS_HTTP_BIND ++ "\"}}"};
- XmppDomain ->
- %% create new session
- Sid = make_sid(),
- case start(XmppDomain, Sid, "", IP) of
- {error, _} ->
- {500, ?HEADER,"{\"body\":{\"type\":\"terminate\" "
- "\"condition\":\"internal-server-error\", "
- "\"xmlns\":\"" ++ ?NS_HTTP_BIND ++ "\",\"$\":\"Internal Server Error\"}}"};
- {ok, Pid} ->
- handle_session_start(
- Pid, XmppDomain, Sid, Rid, Attrs,
- Payload, PayloadSize, IP)
- end
- end;
- %% Existing session
- {ok, {Sid, Rid, Attrs, Payload1}} ->
- StreamStart =
- case xml:get_attr_s("xmpp:restart",Attrs) of
- "true" ->
- true;
- _ ->
- false
- end,
- Payload2 = case xml:get_attr_s("type",Attrs) of
- "terminate" ->
- %% close stream
- Payload1 ++ [{xmlstreamend, "stream:stream"}];
- _ ->
- Payload1
- end,
- handle_http_put(Sid, Rid, Attrs, Payload2, PayloadSize,
- StreamStart, IP);
- {size_limit, Sid} ->
- case get_session(Sid) of
- {error, _} ->
- {404, ?HEADER, ""};
- {ok, #http_bind{pid = FsmRef}} ->
- gen_fsm:sync_send_all_state_event(FsmRef, {stop, close}),
- {200, ?HEADER, "{\"body\": {\"type\"=\"terminate\" "
- "\"condition\":\"undefined-condition\" "
- "\"xmlns\":\"" ++ ?NS_HTTP_BIND ++ "\", \"$\":\"Request Too Large\"}}"}
- end;
- _ ->
- ?DEBUG("Received bad request: ~p", [Data]),
- {400, ?HEADER, ""}
+ case catch parse_request(Data, PayloadSize,
+ MaxStanzaSize)
+ of
+ %% No existing session:
+ {ok, {<<"">>, Rid, Attrs, Payload}} ->
+ case xml:get_attr_s(<<"to">>, Attrs) of
+ <<"">> ->
+ ?DEBUG("Session not created (Improper addressing)", []),
+ {200, ?HEADER,
+ <<"{\"body\":{\"type\":\"terminate\" \"condition\""
+ ":\"improper-addressing\", \"xmlns\":\"",
+ (?NS_HTTP_BIND)/binary, "\"}}">>};
+ XmppDomain ->
+ Sid = make_sid(),
+ case start(XmppDomain, Sid, <<"">>, IP) of
+ {error, _} ->
+ {500, ?HEADER,
+ <<"{\"body\":{\"type\":\"terminate\" \"condition\""
+ ":\"internal-server-error\", \"xmlns\":\"",
+ (?NS_HTTP_BIND)/binary,
+ "\",\"$\":\"Internal Server Error\"}}">>};
+ {ok, Pid} ->
+ handle_session_start(Pid, XmppDomain, Sid, Rid, Attrs,
+ Payload, PayloadSize, IP)
+ end
+ end;
+ %% Existing session
+ {ok, {Sid, Rid, Attrs, Payload1}} ->
+ StreamStart = case xml:get_attr_s(<<"xmpp:restart">>,
+ Attrs)
+ of
+ <<"true">> -> true;
+ _ -> false
+ end,
+ Payload2 = case xml:get_attr_s(<<"type">>, Attrs) of
+ <<"terminate">> ->
+ Payload1 ++ [{xmlstreamend, <<"stream:stream">>}];
+ _ -> Payload1
+ end,
+ handle_http_put(Sid, Rid, Attrs, Payload2, PayloadSize,
+ StreamStart, IP);
+ {size_limit, Sid} ->
+ case get_session(Sid) of
+ {error, _} -> {404, ?HEADER, <<"">>};
+ {ok, #http_bind{pid = FsmRef}} ->
+ gen_fsm:sync_send_all_state_event(FsmRef,
+ {stop, close}),
+ {200, ?HEADER,
+ <<"{\"body\": {\"type\"=\"terminate\" \"conditio"
+ "n\":\"undefined-condition\" \"xmlns\":\"",
+ (?NS_HTTP_BIND)/binary,
+ "\", \"$\":\"Request Too Large\"}}">>}
+ end;
+ _ ->
+ ?DEBUG("Received bad request: ~p", [Data]),
+ {400, ?HEADER, <<"">>}
end.
handle_session_start(Pid, XmppDomain, Sid, Rid, Attrs,
Payload, PayloadSize, IP) ->
?DEBUG("got pid: ~p", [Pid]),
- Wait = case string:to_integer(xml:get_attr_s("wait",Attrs)) of
- {error, _} ->
- ?MAX_WAIT;
- {CWait, _} ->
- if
- (CWait > ?MAX_WAIT) ->
- ?MAX_WAIT;
- true ->
- CWait
- end
+ Wait = case str:to_integer(xml:get_attr_s(<<"wait">>,
+ Attrs))
+ of
+ {error, _} -> ?MAX_WAIT;
+ {CWait, _} ->
+ if CWait > (?MAX_WAIT) -> ?MAX_WAIT;
+ true -> CWait
+ end
end,
- Hold = case string:to_integer(xml:get_attr_s("hold",Attrs)) of
- {error, _} ->
- (?MAX_REQUESTS - 1);
- {CHold, _} ->
- if
- (CHold > (?MAX_REQUESTS - 1)) ->
- (?MAX_REQUESTS - 1);
- true ->
- CHold
- end
+ Hold = case str:to_integer(xml:get_attr_s(<<"hold">>,
+ Attrs))
+ of
+ {error, _} -> (?MAX_REQUESTS) - 1;
+ {CHold, _} ->
+ if CHold > (?MAX_REQUESTS) - 1 -> (?MAX_REQUESTS) - 1;
+ true -> CHold
+ end
end,
- Pdelay = case string:to_integer(xml:get_attr_s("process-delay",Attrs)) of
- {error, _} ->
- ?PROCESS_DELAY_DEFAULT;
- {CPdelay, _} when
- (?PROCESS_DELAY_MIN =< CPdelay) and
- (CPdelay =< ?PROCESS_DELAY_MAX) ->
- CPdelay;
- {CPdelay, _} ->
- erlang:max(
- erlang:min(CPdelay,?PROCESS_DELAY_MAX),
- ?PROCESS_DELAY_MIN)
+ Pdelay = case
+ str:to_integer(xml:get_attr_s(<<"process-delay">>,
+ Attrs))
+ of
+ {error, _} -> ?PROCESS_DELAY_DEFAULT;
+ {CPdelay, _}
+ when ((?PROCESS_DELAY_MIN) =< CPdelay) and
+ (CPdelay =< (?PROCESS_DELAY_MAX)) ->
+ CPdelay;
+ {CPdelay, _} ->
+ erlang:max(erlang:min(CPdelay, ?PROCESS_DELAY_MAX),
+ ?PROCESS_DELAY_MIN)
end,
- Version =
- case catch list_to_float(
- xml:get_attr_s("ver", Attrs)) of
- {'EXIT', _} -> 0.0;
- V -> V
- end,
- XmppVersion = xml:get_attr_s("xmpp:version", Attrs),
+ Version = case catch
+ list_to_float(binary_to_list(xml:get_attr_s(<<"ver">>, Attrs)))
+ of
+ {'EXIT', _} -> 0.0;
+ V -> V
+ end,
+ XmppVersion = xml:get_attr_s(<<"xmpp:version">>, Attrs),
?DEBUG("Create session: ~p", [Sid]),
- mnesia:async_dirty(
- fun() ->
- mnesia:write(
- #http_bind{id = Sid,
- pid = Pid,
- to = {XmppDomain,
- XmppVersion},
- hold = Hold,
- wait = Wait,
- process_delay = Pdelay,
- version = Version
- })
- end),
- handle_http_put(Sid, Rid, Attrs, Payload, PayloadSize, true, IP).
+ mnesia:async_dirty(fun () ->
+ mnesia:write(#http_bind{id = Sid, pid = Pid,
+ to =
+ {XmppDomain,
+ XmppVersion},
+ hold = Hold, wait = Wait,
+ process_delay = Pdelay,
+ version = Version})
+ end),
+ handle_http_put(Sid, Rid, Attrs, Payload, PayloadSize,
+ true, IP).
%%%----------------------------------------------------------------------
%%% Callback functions from gen_fsm
%%%----------------------------------------------------------------------
-%%----------------------------------------------------------------------
-%% Func: init/1
-%% Returns: {ok, StateName, StateData} |
-%% {ok, StateName, StateData, Timeout} |
-%% ignore |
-%% {stop, StopReason}
-%%----------------------------------------------------------------------
init([Sid, Key, IP]) ->
?DEBUG("started: ~p", [{Sid, Key, IP}]),
-
- %% Read c2s options from the first ejabberd_c2s configuration in
- %% the config file listen section
- %% TODO: We should have different access and shaper values for
- %% each connector. The default behaviour should be however to use
- %% the default c2s restrictions if not defined for the current
- %% connector.
Opts1 = ejabberd_c2s_config:get_c2s_limits(),
Opts = [{xml_socket, true} | Opts1],
-
Shaper = none,
ShaperState = shaper:new(Shaper),
Socket = {http_bind, self(), IP},
- ejabberd_socket:start(ejabberd_c2s, ?MODULE, Socket, Opts),
+ ejabberd_socket:start(ejabberd_c2s, ?MODULE, Socket,
+ Opts),
Timer = erlang:start_timer(?MAX_INACTIVITY, self(), []),
- {ok, loop, #state{id = Sid,
- key = Key,
- socket = Socket,
- shaper_state = ShaperState,
- max_inactivity = ?MAX_INACTIVITY,
- max_pause = ?MAX_PAUSE,
- timer = Timer}}.
-
-%%----------------------------------------------------------------------
-%% Func: handle_event/3
-%% Returns: {next_state, NextStateName, NextStateData} |
-%% {next_state, NextStateName, NextStateData, Timeout} |
-%% {stop, Reason, NewStateData}
-%%----------------------------------------------------------------------
-handle_event({become_controller, C2SPid}, StateName, StateData) ->
+ {ok, loop,
+ #state{id = Sid, key = Key, socket = Socket,
+ shaper_state = ShaperState,
+ max_inactivity = ?MAX_INACTIVITY,
+ max_pause = ?MAX_PAUSE, timer = Timer}}.
+
+handle_event({become_controller, C2SPid}, StateName,
+ StateData) ->
erlang:monitor(process, C2SPid),
case StateData#state.input of
- cancel ->
- {next_state, StateName, StateData#state{
- waiting_input = C2SPid}};
- Input ->
- lists:foreach(
- fun(Event) ->
- C2SPid ! Event
- end, queue:to_list(Input)),
- {next_state, StateName, StateData#state{
- input = queue:new(),
- waiting_input = C2SPid}}
+ cancel ->
+ {next_state, StateName,
+ StateData#state{waiting_input = C2SPid}};
+ Input ->
+ lists:foreach(fun (Event) -> C2SPid ! Event end,
+ queue:to_list(Input)),
+ {next_state, StateName,
+ StateData#state{input = queue:new(),
+ waiting_input = C2SPid}}
end;
-
-handle_event({change_shaper, Shaper}, StateName, StateData) ->
+handle_event({change_shaper, Shaper}, StateName,
+ StateData) ->
NewShaperState = shaper:new(Shaper),
- {next_state, StateName, StateData#state{shaper_state = NewShaperState}};
+ {next_state, StateName,
+ StateData#state{shaper_state = NewShaperState}};
handle_event(_Event, StateName, StateData) ->
{next_state, StateName, StateData}.
-%%----------------------------------------------------------------------
-%% Func: handle_sync_event/4
-%% Returns: {next_state, NextStateName, NextStateData} |
-%% {next_state, NextStateName, NextStateData, Timeout} |
-%% {reply, Reply, NextStateName, NextStateData} |
-%% {reply, Reply, NextStateName, NextStateData, Timeout} |
-%% {stop, Reason, NewStateData} |
-%% {stop, Reason, Reply, NewStateData}
-%%----------------------------------------------------------------------
handle_sync_event({send_xml, Packet}, _From, StateName,
#state{http_receiver = undefined} = StateData) ->
Output = [Packet | StateData#state.output],
Reply = ok,
- {reply, Reply, StateName, StateData#state{output = Output}};
-handle_sync_event({send_xml, Packet}, _From, StateName, StateData) ->
+ {reply, Reply, StateName,
+ StateData#state{output = Output}};
+handle_sync_event({send_xml, Packet}, _From, StateName,
+ StateData) ->
Output = [Packet | StateData#state.output],
cancel_timer(StateData#state.timer),
Timer = set_inactivity_timer(StateData#state.pause,
@@ -402,188 +369,151 @@ handle_sync_event({send_xml, Packet}, _From, StateName, StateData) ->
gen_fsm:reply(StateData#state.http_receiver, HTTPReply),
cancel_timer(StateData#state.wait_timer),
Rid = StateData#state.rid,
- ReqList = [#hbr{rid = Rid,
- key = StateData#state.key,
- out = Output
- } |
- [El || El <- StateData#state.req_list,
- El#hbr.rid /= Rid ]
- ],
+ ReqList = [#hbr{rid = Rid, key = StateData#state.key,
+ out = Output}
+ | [El
+ || El <- StateData#state.req_list, El#hbr.rid /= Rid]],
Reply = ok,
{reply, Reply, StateName,
- StateData#state{output = [],
- http_receiver = undefined,
- req_list = ReqList,
- wait_timer = undefined,
+ StateData#state{output = [], http_receiver = undefined,
+ req_list = ReqList, wait_timer = undefined,
timer = Timer}};
-
-handle_sync_event({stop,close}, _From, _StateName, StateData) ->
- Reply = ok,
- {stop, normal, Reply, StateData};
-handle_sync_event({stop,stream_closed}, _From, _StateName, StateData) ->
- Reply = ok,
- {stop, normal, Reply, StateData};
-handle_sync_event(deactivate_socket, _From, StateName, StateData) ->
- %% Input = case StateData#state.input of
- %% cancel ->
- %% queue:new();
- %% Q ->
- %% Q
- %% end,
- {reply, ok, StateName, StateData#state{waiting_input = false}};
-handle_sync_event({stop,Reason}, _From, _StateName, StateData) ->
- ?DEBUG("Closing bind session ~p - Reason: ~p", [StateData#state.id, Reason]),
+handle_sync_event({stop, close}, _From, _StateName,
+ StateData) ->
+ Reply = ok, {stop, normal, Reply, StateData};
+handle_sync_event({stop, stream_closed}, _From,
+ _StateName, StateData) ->
+ Reply = ok, {stop, normal, Reply, StateData};
+handle_sync_event(deactivate_socket, _From, StateName,
+ StateData) ->
+ {reply, ok, StateName,
+ StateData#state{waiting_input = false}};
+handle_sync_event({stop, Reason}, _From, _StateName,
+ StateData) ->
+ ?DEBUG("Closing bind session ~p - Reason: ~p",
+ [StateData#state.id, Reason]),
Reply = ok,
{stop, normal, Reply, StateData};
-
%% HTTP PUT: Receive packets from the client
-handle_sync_event(#http_put{rid = Rid},
- _From, StateName, StateData)
- when StateData#state.shaper_timer /= undefined ->
- Pause =
- case erlang:read_timer(StateData#state.shaper_timer) of
- false ->
- 0;
- P -> P
- end,
+handle_sync_event(#http_put{rid = Rid}, _From,
+ StateName, StateData)
+ when StateData#state.shaper_timer /= undefined ->
+ Pause = case
+ erlang:read_timer(StateData#state.shaper_timer)
+ of
+ false -> 0;
+ P -> P
+ end,
Reply = {wait, Pause},
?DEBUG("Shaper timer for RID ~p: ~p", [Rid, Reply]),
{reply, Reply, StateName, StateData};
-
-handle_sync_event(#http_put{payload_size = PayloadSize} = Request,
+handle_sync_event(#http_put{payload_size =
+ PayloadSize} =
+ Request,
_From, StateName, StateData) ->
- ?DEBUG("New request: ~p",[Request]),
- %% Updating trafic shaper
+ ?DEBUG("New request: ~p", [Request]),
{NewShaperState, NewShaperTimer} =
- update_shaper(StateData#state.shaper_state, PayloadSize),
-
+ update_shaper(StateData#state.shaper_state,
+ PayloadSize),
handle_http_put_event(Request, StateName,
StateData#state{shaper_state = NewShaperState,
shaper_timer = NewShaperTimer});
-
%% HTTP GET: send packets to the client
-handle_sync_event({http_get, Rid, Wait, Hold}, From, StateName, StateData) ->
- %% setup timer
- send_receiver_reply(StateData#state.http_receiver, {ok, empty}),
+handle_sync_event({http_get, Rid, Wait, Hold}, From,
+ StateName, StateData) ->
+ send_receiver_reply(StateData#state.http_receiver,
+ {ok, empty}),
cancel_timer(StateData#state.wait_timer),
TNow = tnow(),
- if
- (Hold > 0) and
- (StateData#state.output == []) and
- ((TNow - StateData#state.ctime) < (Wait*1000*1000)) and
- (StateData#state.rid == Rid) and
- (StateData#state.input /= cancel) and
- (StateData#state.pause == 0) ->
- WaitTimer = erlang:start_timer(Wait * 1000, self(), []),
- %% MR: Not sure we should cancel the state timer here.
- cancel_timer(StateData#state.timer),
- {next_state, StateName, StateData#state{
- http_receiver = From,
- wait_timer = WaitTimer,
- timer = undefined}};
- (StateData#state.input == cancel) ->
- cancel_timer(StateData#state.timer),
- Timer = set_inactivity_timer(StateData#state.pause,
- StateData#state.max_inactivity),
- Reply = {ok, cancel},
- {reply, Reply, StateName, StateData#state{
- input = queue:new(),
- http_receiver = undefined,
- wait_timer = undefined,
- timer = Timer}};
- true ->
- cancel_timer(StateData#state.timer),
- Timer = set_inactivity_timer(StateData#state.pause,
- StateData#state.max_inactivity),
- Reply = {ok, StateData#state.output},
- %% save request
- ReqList = [#hbr{rid = Rid,
- key = StateData#state.key,
- out = StateData#state.output
- } |
- [El || El <- StateData#state.req_list,
- El#hbr.rid /= Rid ]
- ],
- {reply, Reply, StateName, StateData#state{
- output = [],
- http_receiver = undefined,
- wait_timer = undefined,
- timer = Timer,
- req_list = ReqList}}
+ if (Hold > 0) and (StateData#state.output == []) and
+ (TNow - StateData#state.ctime < Wait * 1000 * 1000)
+ and (StateData#state.rid == Rid)
+ and (StateData#state.input /= cancel)
+ and (StateData#state.pause == 0) ->
+ WaitTimer = erlang:start_timer(Wait * 1000, self(), []),
+ cancel_timer(StateData#state.timer),
+ {next_state, StateName,
+ StateData#state{http_receiver = From,
+ wait_timer = WaitTimer, timer = undefined}};
+ StateData#state.input == cancel ->
+ cancel_timer(StateData#state.timer),
+ Timer = set_inactivity_timer(StateData#state.pause,
+ StateData#state.max_inactivity),
+ Reply = {ok, cancel},
+ {reply, Reply, StateName,
+ StateData#state{input = queue:new(),
+ http_receiver = undefined, wait_timer = undefined,
+ timer = Timer}};
+ true ->
+ cancel_timer(StateData#state.timer),
+ Timer = set_inactivity_timer(StateData#state.pause,
+ StateData#state.max_inactivity),
+ Reply = {ok, StateData#state.output},
+ ReqList = [#hbr{rid = Rid, key = StateData#state.key,
+ out = StateData#state.output}
+ | [El
+ || El <- StateData#state.req_list, El#hbr.rid /= Rid]],
+ {reply, Reply, StateName,
+ StateData#state{output = [], http_receiver = undefined,
+ wait_timer = undefined, timer = Timer,
+ req_list = ReqList}}
end;
-
-handle_sync_event(peername, _From, StateName, StateData) ->
+handle_sync_event(peername, _From, StateName,
+ StateData) ->
Reply = {ok, StateData#state.ip},
{reply, Reply, StateName, StateData};
-
-handle_sync_event(_Event, _From, StateName, StateData) ->
- Reply = ok,
- {reply, Reply, StateName, StateData}.
+handle_sync_event(_Event, _From, StateName,
+ StateData) ->
+ Reply = ok, {reply, Reply, StateName, StateData}.
code_change(_OldVsn, StateName, StateData, _Extra) ->
{ok, StateName, StateData}.
-%%----------------------------------------------------------------------
-%% Func: handle_info/3
-%% Returns: {next_state, NextStateName, NextStateData} |
-%% {next_state, NextStateName, NextStateData, Timeout} |
-%% {stop, Reason, NewStateData}
-%%----------------------------------------------------------------------
-%% We reached the max_inactivity timeout:
handle_info({timeout, Timer, _}, _StateName,
- #state{id=SID, timer = Timer} = StateData) ->
- ?INFO_MSG("Session timeout. Closing the HTTP bind session: ~p", [SID]),
+ #state{id = SID, timer = Timer} = StateData) ->
+ ?INFO_MSG("Session timeout. Closing the HTTP bind "
+ "session: ~p",
+ [SID]),
{stop, normal, StateData};
-
handle_info({timeout, WaitTimer, _}, StateName,
#state{wait_timer = WaitTimer} = StateData) ->
- if
- StateData#state.http_receiver /= undefined ->
- cancel_timer(StateData#state.timer),
- Timer = set_inactivity_timer(StateData#state.pause,
- StateData#state.max_inactivity),
- gen_fsm:reply(StateData#state.http_receiver, {ok, empty}),
- Rid = StateData#state.rid,
- ReqList = [#hbr{rid = Rid,
- key = StateData#state.key,
- out = []
- } |
- [El || El <- StateData#state.req_list,
- El#hbr.rid /= Rid ]
- ],
- {next_state, StateName,
- StateData#state{http_receiver = undefined,
- req_list = ReqList,
- wait_timer = undefined,
- timer = Timer}};
- true ->
- {next_state, StateName, StateData}
+ if StateData#state.http_receiver /= undefined ->
+ cancel_timer(StateData#state.timer),
+ Timer = set_inactivity_timer(StateData#state.pause,
+ StateData#state.max_inactivity),
+ gen_fsm:reply(StateData#state.http_receiver,
+ {ok, empty}),
+ Rid = StateData#state.rid,
+ ReqList = [#hbr{rid = Rid, key = StateData#state.key,
+ out = []}
+ | [El
+ || El <- StateData#state.req_list, El#hbr.rid /= Rid]],
+ {next_state, StateName,
+ StateData#state{http_receiver = undefined,
+ req_list = ReqList, wait_timer = undefined,
+ timer = Timer}};
+ true -> {next_state, StateName, StateData}
end;
-
handle_info({timeout, ShaperTimer, _}, StateName,
#state{shaper_timer = ShaperTimer} = StateData) ->
- {next_state, StateName, StateData#state{shaper_timer = undefined}};
-
-handle_info({'DOWN', _MRef, process, C2SPid, _}, _StateName,
+ {next_state, StateName,
+ StateData#state{shaper_timer = undefined}};
+handle_info({'DOWN', _MRef, process, C2SPid, _},
+ _StateName,
#state{waiting_input = C2SPid} = StateData) ->
{stop, normal, StateData};
handle_info(_, StateName, StateData) ->
{next_state, StateName, StateData}.
-%%----------------------------------------------------------------------
-%% Func: terminate/3
-%% Purpose: Shutdown the fsm
-%% Returns: any
-%%----------------------------------------------------------------------
terminate(_Reason, _StateName, StateData) ->
- ?DEBUG("terminate: Deleting session ~s", [StateData#state.id]),
+ ?DEBUG("terminate: Deleting session ~s",
+ [StateData#state.id]),
mnesia:dirty_delete({http_bind, StateData#state.id}),
- send_receiver_reply(StateData#state.http_receiver, {ok, terminate}),
+ send_receiver_reply(StateData#state.http_receiver,
+ {ok, terminate}),
case StateData#state.waiting_input of
- false ->
- ok;
- C2SPid ->
- gen_fsm:send_event(C2SPid, closed)
+ false -> ok;
+ C2SPid -> gen_fsm:send_event(C2SPid, closed)
end,
ok.
@@ -591,704 +521,708 @@ terminate(_Reason, _StateName, StateData) ->
%%% Internal functions
%%%----------------------------------------------------------------------
-%% PUT / Get processing:
-handle_http_put_event(#http_put{rid = Rid, attrs = Attrs,
- hold = Hold} = Request,
+handle_http_put_event(#http_put{rid = Rid,
+ attrs = Attrs, hold = Hold} =
+ Request,
StateName, StateData) ->
- ?DEBUG("New request: ~p",[Request]),
- %% Check if Rid valid
- RidAllow = rid_allow(StateData#state.rid, Rid, Attrs, Hold,
- StateData#state.max_pause),
-
- %% Check if Rid is in sequence or out of sequence:
+ ?DEBUG("New request: ~p", [Request]),
+ RidAllow = rid_allow(StateData#state.rid, Rid, Attrs,
+ Hold, StateData#state.max_pause),
case RidAllow of
- buffer ->
- ?DEBUG("Buffered request: ~p", [Request]),
- %% Request is out of sequence:
- PendingRequests = StateData#state.unprocessed_req_list,
- %% In case an existing RID was already buffered:
- Requests = lists:keydelete(Rid, 2, PendingRequests),
- ReqList = [#hbr{rid = Rid,
- key = StateData#state.key,
- out = []
- } |
- [El || El <- StateData#state.req_list,
- El#hbr.rid > (Rid - 1 - Hold)]
- ],
- ?DEBUG("reqlist: ~p", [ReqList]),
- UnprocessedReqList = [Request | Requests],
- cancel_timer(StateData#state.timer),
- Timer = set_inactivity_timer(0, StateData#state.max_inactivity),
- {reply, buffered, StateName,
- StateData#state{unprocessed_req_list = UnprocessedReqList,
- req_list = ReqList,
- timer = Timer}};
- _ ->
- %% Request is in sequence:
- process_http_put(Request, StateName, StateData, RidAllow)
+ buffer ->
+ ?DEBUG("Buffered request: ~p", [Request]),
+ PendingRequests = StateData#state.unprocessed_req_list,
+ Requests = lists:keydelete(Rid, 2, PendingRequests),
+ ReqList = [#hbr{rid = Rid, key = StateData#state.key,
+ out = []}
+ | [El
+ || El <- StateData#state.req_list,
+ El#hbr.rid > Rid - 1 - Hold]],
+ ?DEBUG("reqlist: ~p", [ReqList]),
+ UnprocessedReqList = [Request | Requests],
+ cancel_timer(StateData#state.timer),
+ Timer = set_inactivity_timer(0,
+ StateData#state.max_inactivity),
+ {reply, buffered, StateName,
+ StateData#state{unprocessed_req_list =
+ UnprocessedReqList,
+ req_list = ReqList, timer = Timer}};
+ _ ->
+ process_http_put(Request, StateName, StateData,
+ RidAllow)
end.
-process_http_put(#http_put{rid = Rid, attrs = Attrs, payload = Payload,
- hold = Hold, stream = StreamTo,
- ip = IP} = Request,
+process_http_put(#http_put{rid = Rid, attrs = Attrs,
+ payload = Payload, hold = Hold, stream = StreamTo,
+ ip = IP} =
+ Request,
StateName, StateData, RidAllow) ->
?DEBUG("Actually processing request: ~p", [Request]),
- %% Check if key valid
- Key = xml:get_attr_s("key", Attrs),
- NewKey = xml:get_attr_s("newkey", Attrs),
- KeyAllow =
- case RidAllow of
- repeat ->
- true;
- false ->
- false;
- {true, _} ->
- case StateData#state.key of
- "" ->
- true;
- OldKey ->
- NextKey = sha:sha(Key),
- ?DEBUG("Key/OldKey/NextKey: ~s/~s/~s", [Key, OldKey, NextKey]),
- if
- OldKey == NextKey ->
- true;
- true ->
- ?DEBUG("wrong key: ~s",[Key]),
- false
- end
- end
- end,
+ Key = xml:get_attr_s(<<"key">>, Attrs),
+ NewKey = xml:get_attr_s(<<"newkey">>, Attrs),
+ KeyAllow = case RidAllow of
+ repeat -> true;
+ false -> false;
+ {true, _} ->
+ case StateData#state.key of
+ <<"">> -> true;
+ OldKey ->
+ NextKey = sha:sha(Key),
+ ?DEBUG("Key/OldKey/NextKey: ~s/~s/~s",
+ [Key, OldKey, NextKey]),
+ if OldKey == NextKey -> true;
+ true -> ?DEBUG("wrong key: ~s", [Key]), false
+ end
+ end
+ end,
TNow = tnow(),
- LastPoll = if
- Payload == [] ->
- TNow;
- true ->
- 0
+ LastPoll = if Payload == [] -> TNow;
+ true -> 0
end,
- if
- (Payload == []) and
- (Hold == 0) and
- (TNow - StateData#state.last_poll < ?MIN_POLLING) ->
- Reply = {error, polling_too_frequently},
- {reply, Reply, StateName, StateData};
- KeyAllow ->
- case RidAllow of
- false ->
- Reply = {error, not_exists},
- {reply, Reply, StateName, StateData};
- repeat ->
- ?DEBUG("REPEATING ~p", [Rid]),
- Reply = case [El#hbr.out ||
- El <- StateData#state.req_list,
- El#hbr.rid == Rid] of
- [] ->
- {error, not_exists};
- [Out | _XS] ->
- {repeat, lists:reverse(Out)}
- end,
- {reply, Reply, StateName, StateData#state{input = cancel,
- last_poll = LastPoll}};
- {true, Pause} ->
- SaveKey = if
- NewKey == "" ->
- Key;
- true ->
- NewKey
- end,
- ?DEBUG(" -- SaveKey: ~s~n", [SaveKey]),
-
- %% save request
- ReqList1 =
- [El || El <- StateData#state.req_list,
- El#hbr.rid > (Rid - 1 - Hold)],
- ReqList =
- case lists:keymember(Rid, #hbr.rid, ReqList1) of
- true ->
- ReqList1;
- false ->
- [#hbr{rid = Rid,
- key = StateData#state.key,
- out = []
- } |
- ReqList1
- ]
- end,
- ?DEBUG("reqlist: ~p", [ReqList]),
-
- %% setup next timer
- cancel_timer(StateData#state.timer),
- Timer = set_inactivity_timer(Pause,
- StateData#state.max_inactivity),
- case StateData#state.waiting_input of
- false ->
- Input =
- lists:foldl(
- fun queue:in/2,
- StateData#state.input, Payload),
- Reply = ok,
- process_buffered_request(Reply, StateName,
- StateData#state{input = Input,
- rid = Rid,
- key = SaveKey,
- ctime = TNow,
- timer = Timer,
- pause = Pause,
- last_poll = LastPoll,
- req_list = ReqList,
- ip = IP
- });
- C2SPid ->
- case StreamTo of
- {To, ""} ->
- gen_fsm:send_event(
- C2SPid,
- {xmlstreamstart, "stream:stream",
- [{"to", To},
- {"xmlns", ?NS_CLIENT},
- {"xmlns:stream", ?NS_STREAM}]});
- {To, Version} ->
- gen_fsm:send_event(
- C2SPid,
- {xmlstreamstart, "stream:stream",
- [{"to", To},
- {"xmlns", ?NS_CLIENT},
- {"version", Version},
- {"xmlns:stream", ?NS_STREAM}]});
- _ ->
- ok
- end,
-
- MaxInactivity = get_max_inactivity(StreamTo, StateData#state.max_inactivity),
- MaxPause = get_max_inactivity(StreamTo, StateData#state.max_pause),
-
- ?DEBUG("really sending now: ~p", [Payload]),
- lists:foreach(
- fun({xmlstreamend, End}) ->
- gen_fsm:send_event(
- C2SPid, {xmlstreamend, End});
- (El) ->
- gen_fsm:send_event(
- C2SPid, {xmlstreamelement, El})
- end, Payload),
- Reply = ok,
- process_buffered_request(Reply, StateName,
- StateData#state{input = queue:new(),
- rid = Rid,
- key = SaveKey,
- ctime = TNow,
- timer = Timer,
- pause = Pause,
- last_poll = LastPoll,
- req_list = ReqList,
- max_inactivity = MaxInactivity,
- max_pause = MaxPause,
- ip = IP
- })
- end
- end;
- true ->
- Reply = {error, bad_key},
- {reply, Reply, StateName, StateData}
+ if (Payload == []) and (Hold == 0) and
+ (TNow - StateData#state.last_poll < (?MIN_POLLING)) ->
+ Reply = {error, polling_too_frequently},
+ {reply, Reply, StateName, StateData};
+ KeyAllow ->
+ case RidAllow of
+ false ->
+ Reply = {error, not_exists},
+ {reply, Reply, StateName, StateData};
+ repeat ->
+ ?DEBUG("REPEATING ~p", [Rid]),
+ Reply = case [El#hbr.out
+ || El <- StateData#state.req_list,
+ El#hbr.rid == Rid]
+ of
+ [] -> {error, not_exists};
+ [Out | _XS] -> {repeat, lists:reverse(Out)}
+ end,
+ {reply, Reply, StateName,
+ StateData#state{input = cancel, last_poll = LastPoll}};
+ {true, Pause} ->
+ SaveKey = if NewKey == <<"">> -> Key;
+ true -> NewKey
+ end,
+ ?DEBUG(" -- SaveKey: ~s~n", [SaveKey]),
+ ReqList1 = [El
+ || El <- StateData#state.req_list,
+ El#hbr.rid > Rid - 1 - Hold],
+ ReqList = case lists:keymember(Rid, #hbr.rid, ReqList1)
+ of
+ true -> ReqList1;
+ false ->
+ [#hbr{rid = Rid, key = StateData#state.key,
+ out = []}
+ | ReqList1]
+ end,
+ ?DEBUG("reqlist: ~p", [ReqList]),
+ cancel_timer(StateData#state.timer),
+ Timer = set_inactivity_timer(Pause,
+ StateData#state.max_inactivity),
+ case StateData#state.waiting_input of
+ false ->
+ Input = lists:foldl(fun queue:in/2,
+ StateData#state.input, Payload),
+ Reply = ok,
+ process_buffered_request(Reply, StateName,
+ StateData#state{input = Input,
+ rid = Rid,
+ key = SaveKey,
+ ctime = TNow,
+ timer = Timer,
+ pause = Pause,
+ last_poll =
+ LastPoll,
+ req_list =
+ ReqList,
+ ip = IP});
+ C2SPid ->
+ case StreamTo of
+ {To, <<"">>} ->
+ gen_fsm:send_event(C2SPid,
+ {xmlstreamstart,
+ <<"stream:stream">>,
+ [{<<"to">>, To},
+ {<<"xmlns">>, ?NS_CLIENT},
+ {<<"xmlns:stream">>,
+ ?NS_STREAM}]});
+ {To, Version} ->
+ gen_fsm:send_event(C2SPid,
+ {xmlstreamstart,
+ <<"stream:stream">>,
+ [{<<"to">>, To},
+ {<<"xmlns">>, ?NS_CLIENT},
+ {<<"version">>, Version},
+ {<<"xmlns:stream">>,
+ ?NS_STREAM}]});
+ _ -> ok
+ end,
+ MaxInactivity = get_max_inactivity(StreamTo,
+ StateData#state.max_inactivity),
+ MaxPause = get_max_inactivity(StreamTo,
+ StateData#state.max_pause),
+ ?DEBUG("really sending now: ~p", [Payload]),
+ lists:foreach(fun ({xmlstreamend, End}) ->
+ gen_fsm:send_event(C2SPid,
+ {xmlstreamend,
+ End});
+ (El) ->
+ gen_fsm:send_event(C2SPid,
+ {xmlstreamelement,
+ El})
+ end,
+ Payload),
+ Reply = ok,
+ process_buffered_request(Reply, StateName,
+ StateData#state{input =
+ queue:new(),
+ rid = Rid,
+ key = SaveKey,
+ ctime = TNow,
+ timer = Timer,
+ pause = Pause,
+ last_poll =
+ LastPoll,
+ req_list =
+ ReqList,
+ max_inactivity =
+ MaxInactivity,
+ max_pause =
+ MaxPause,
+ ip = IP})
+ end
+ end;
+ true ->
+ Reply = {error, bad_key},
+ {reply, Reply, StateName, StateData}
end.
process_buffered_request(Reply, StateName, StateData) ->
Rid = StateData#state.rid,
Requests = StateData#state.unprocessed_req_list,
- case lists:keysearch(Rid+1, 2, Requests) of
- {value, Request} ->
- ?DEBUG("Processing buffered request: ~p", [Request]),
- NewRequests = lists:keydelete(Rid+1, 2, Requests),
- handle_http_put_event(
- Request, StateName,
- StateData#state{unprocessed_req_list = NewRequests});
- _ ->
- {reply, Reply, StateName, StateData, hibernate}
+ case lists:keysearch(Rid + 1, 2, Requests) of
+ {value, Request} ->
+ ?DEBUG("Processing buffered request: ~p", [Request]),
+ NewRequests = lists:keydelete(Rid + 1, 2, Requests),
+ handle_http_put_event(Request, StateName,
+ StateData#state{unprocessed_req_list =
+ NewRequests});
+ _ -> {reply, Reply, StateName, StateData, hibernate}
end.
-handle_http_put(Sid, Rid, Attrs, Payload, PayloadSize, StreamStart, IP) ->
- case http_put(Sid, Rid, Attrs, Payload, PayloadSize, StreamStart, IP) of
- {error, not_exists} ->
- ?DEBUG("no session associated with sid: ~p", [Sid]),
- {404, ?HEADER, ""};
- {{error, Reason}, Sess} ->
- ?DEBUG("Error on HTTP put. Reason: ~p", [Reason]),
- handle_http_put_error(Reason, Sess);
- {{repeat, OutPacket}, Sess} ->
- ?DEBUG("http_put said \"repeat!\" ...~nOutPacket: ~p", [OutPacket]),
- send_outpacket(Sess, OutPacket);
- {{wait, Pause}, _Sess} ->
- ?DEBUG("Trafic Shaper: Delaying request ~p", [Rid]),
- timer:sleep(Pause),
- %{200, ?HEADER,
- % xmpp_json:to_json(
- % {xmlelement, "body",
- % [{"xmlns", ?NS_HTTP_BIND},
- % {"type", "error"}], []})};
- handle_http_put(Sid, Rid, Attrs, Payload, PayloadSize,
- StreamStart, IP);
- {buffered, _Sess} ->
- {200, ?HEADER, "{\"body\":{ \"xmlns\":\""++?NS_HTTP_BIND++"\"}}"};
- {ok, Sess} ->
- prepare_response(Sess, Rid, [], StreamStart)
+handle_http_put(Sid, Rid, Attrs, Payload, PayloadSize,
+ StreamStart, IP) ->
+ case http_put(Sid, Rid, Attrs, Payload, PayloadSize,
+ StreamStart, IP)
+ of
+ {error, not_exists} ->
+ ?DEBUG("no session associated with sid: ~p", [Sid]),
+ {404, ?HEADER, <<"">>};
+ {{error, Reason}, Sess} ->
+ ?DEBUG("Error on HTTP put. Reason: ~p", [Reason]),
+ handle_http_put_error(Reason, Sess);
+ {{repeat, OutPacket}, Sess} ->
+ ?DEBUG("http_put said \"repeat!\" ...~nOutPacket: ~p",
+ [OutPacket]),
+ send_outpacket(Sess, OutPacket);
+ {{wait, Pause}, _Sess} ->
+ ?DEBUG("Trafic Shaper: Delaying request ~p", [Rid]),
+ timer:sleep(Pause),
+ handle_http_put(Sid, Rid, Attrs, Payload, PayloadSize,
+ StreamStart, IP);
+ {buffered, _Sess} ->
+ {200, ?HEADER,
+ <<"{\"body\":{ \"xmlns\":\"", (?NS_HTTP_BIND)/binary,
+ "\"}}">>};
+ {ok, Sess} ->
+ prepare_response(Sess, Rid, [], StreamStart)
end.
-http_put(Sid, Rid, Attrs, Payload, PayloadSize, StreamStart, IP) ->
+http_put(Sid, Rid, Attrs, Payload, PayloadSize,
+ StreamStart, IP) ->
?DEBUG("Looking for session: ~p", [Sid]),
case get_session(Sid) of
- {error, _} ->
- {error, not_exists};
- {ok, #http_bind{pid = FsmRef, hold=Hold, to={To, StreamVersion}}=Sess}->
- NewStream =
- case StreamStart of
- true ->
- {To, StreamVersion};
- _ ->
- ""
- end,
- {gen_fsm:sync_send_all_state_event(
- FsmRef, #http_put{rid = Rid, attrs = Attrs, payload = Payload,
- payload_size = PayloadSize, hold = Hold,
- stream = NewStream, ip = IP}, 30000), Sess}
+ {error, _} -> {error, not_exists};
+ {ok,
+ #http_bind{pid = FsmRef, hold = Hold,
+ to = {To, StreamVersion}} =
+ Sess} ->
+ NewStream = case StreamStart of
+ true -> {To, StreamVersion};
+ _ -> <<"">>
+ end,
+ {gen_fsm:sync_send_all_state_event(FsmRef,
+ #http_put{rid = Rid, attrs = Attrs,
+ payload = Payload,
+ payload_size =
+ PayloadSize,
+ hold = Hold,
+ stream = NewStream,
+ ip = IP},
+ 30000),
+ Sess}
end.
-handle_http_put_error(Reason, #http_bind{pid=FsmRef, version=Version})
- when Version >= 0 ->
- gen_fsm:sync_send_all_state_event(FsmRef, {stop, {put_error,Reason}}),
+handle_http_put_error(Reason,
+ #http_bind{pid = FsmRef, version = Version})
+ when Version >= 0 ->
+ gen_fsm:sync_send_all_state_event(FsmRef,
+ {stop, {put_error, Reason}}),
case Reason of
- not_exists ->
- {200, ?HEADER,
- mochijson2:encode(
- xmpp_json:to_json(
- {xmlelement, "body",
- [{"xmlns", ?NS_HTTP_BIND},
- {"type", "terminate"},
- {"condition", "item-not-found"}], []}))};
- bad_key ->
- {200, ?HEADER,
- mochijson2:encode(
- xmpp_json:to_json(
- {xmlelement, "body",
- [{"xmlns", ?NS_HTTP_BIND},
- {"type", "terminate"},
- {"condition", "item-not-found"}], []}))};
- polling_too_frequently ->
- {200, ?HEADER,
- mochijson2:encode(
- xmpp_json:to_json(
- {xmlelement, "body",
- [{"xmlns", ?NS_HTTP_BIND},
- {"type", "terminate"},
- {"condition", "policy-violation"}], []}))}
+ not_exists ->
+ {200, ?HEADER,
+ jiffy:encode(xmpp_json:to_json(#xmlel{name =
+ <<"body">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_HTTP_BIND},
+ {<<"type">>,
+ <<"terminate">>},
+ {<<"condition">>,
+ <<"item-not-found">>}],
+ children = []}))};
+ bad_key ->
+ {200, ?HEADER,
+ jiffy:encode(xmpp_json:to_json(#xmlel{name =
+ <<"body">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_HTTP_BIND},
+ {<<"type">>,
+ <<"terminate">>},
+ {<<"condition">>,
+ <<"item-not-found">>}],
+ children = []}))};
+ polling_too_frequently ->
+ {200, ?HEADER,
+ jiffy:encode(xmpp_json:to_json(#xmlel{name =
+ <<"body">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_HTTP_BIND},
+ {<<"type">>,
+ <<"terminate">>},
+ {<<"condition">>,
+ <<"policy-violation">>}],
+ children = []}))}
end;
-handle_http_put_error(Reason, #http_bind{pid=FsmRef}) ->
- gen_fsm:sync_send_all_state_event(FsmRef,{stop, {put_error_no_version, Reason}}),
+handle_http_put_error(Reason,
+ #http_bind{pid = FsmRef}) ->
+ gen_fsm:sync_send_all_state_event(FsmRef,
+ {stop, {put_error_no_version, Reason}}),
case Reason of
- not_exists -> %% bad rid
- ?DEBUG("Closing HTTP bind session (Bad rid).", []),
- {404, ?HEADER, ""};
- bad_key ->
- ?DEBUG("Closing HTTP bind session (Bad key).", []),
- {404, ?HEADER, ""};
- polling_too_frequently ->
- ?DEBUG("Closing HTTP bind session (User polling too frequently).", []),
- {403, ?HEADER, ""}
+ not_exists -> %% bad rid
+ ?DEBUG("Closing HTTP bind session (Bad rid).", []),
+ {404, ?HEADER, <<"">>};
+ bad_key ->
+ ?DEBUG("Closing HTTP bind session (Bad key).", []),
+ {404, ?HEADER, <<"">>};
+ polling_too_frequently ->
+ ?DEBUG("Closing HTTP bind session (User polling "
+ "too frequently).",
+ []),
+ {403, ?HEADER, <<"">>}
end.
-%% Control RID ordering
rid_allow(none, _NewRid, _Attrs, _Hold, _MaxPause) ->
- %% First request - nothing saved so far
{true, 0};
rid_allow(OldRid, NewRid, Attrs, Hold, MaxPause) ->
- ?DEBUG("Previous rid / New rid: ~p/~p", [OldRid, NewRid]),
+ ?DEBUG("Previous rid / New rid: ~p/~p",
+ [OldRid, NewRid]),
if
- %% We did not miss any packet, we can process it immediately:
- NewRid == OldRid + 1 ->
- case catch list_to_integer(
- xml:get_attr_s("pause", Attrs)) of
- {'EXIT', _} ->
- {true, 0};
- Pause1 when Pause1 =< MaxPause ->
- ?DEBUG("got pause: ~p", [Pause1]),
- {true, Pause1};
- _ ->
- {true, 0}
- end;
- %% We have missed packets, we need to cached it to process it later on:
- (OldRid < NewRid) and
- (NewRid =< (OldRid + Hold + 1)) ->
- buffer;
- (NewRid =< OldRid) and
- (NewRid > OldRid - Hold - 1) ->
- repeat;
- true ->
- false
+ %% We did not miss any packet, we can process it immediately:
+ NewRid == OldRid + 1 ->
+ case catch
+ jlib:binary_to_integer(xml:get_attr_s(<<"pause">>,
+ Attrs))
+ of
+ {'EXIT', _} -> {true, 0};
+ Pause1 when Pause1 =< MaxPause ->
+ ?DEBUG("got pause: ~p", [Pause1]), {true, Pause1};
+ _ -> {true, 0}
+ end;
+ %% We have missed packets, we need to cached it to process it later on:
+ (OldRid < NewRid) and (NewRid =< OldRid + Hold + 1) ->
+ buffer;
+ (NewRid =< OldRid) and (NewRid > OldRid - Hold - 1) ->
+ repeat;
+ true -> false
end.
update_shaper(ShaperState, PayloadSize) ->
- {NewShaperState, Pause} = shaper:update(ShaperState, PayloadSize),
- if
- Pause > 0 ->
- ShaperTimer = erlang:start_timer(Pause, self(), activate), %% MR: Seems timer is not needed. Activate is not handled
- {NewShaperState, ShaperTimer};
- true ->
- {NewShaperState, undefined}
+ {NewShaperState, Pause} = shaper:update(ShaperState,
+ PayloadSize),
+ if Pause > 0 ->
+ ShaperTimer = erlang:start_timer(Pause, self(),
+ activate),
+ {NewShaperState, ShaperTimer};
+ true -> {NewShaperState, undefined}
end.
prepare_response(Sess, Rid, OutputEls, StreamStart) ->
- receive after Sess#http_bind.process_delay -> ok end,
+ receive after Sess#http_bind.process_delay -> ok end,
case catch http_get(Sess, Rid) of
- {ok, cancel} ->
- %% actually it would be better if we could completely
- %% cancel this request, but then we would have to hack
- %% ejabberd_http and I'm too lazy now
- {200, ?HEADER, "{\"body\": {\"type\":\"error\", \"xmlns\":\""++?NS_HTTP_BIND++"\"/>"};
- {ok, empty} ->
- {200, ?HEADER, "{\"body\":{ \"xmlns\":\""++?NS_HTTP_BIND++"\"}}"};
- {ok, terminate} ->
- {200, ?HEADER, "{\"body\": {\"type\":\"terminate\", \"xmlns\":\""++?NS_HTTP_BIND++"\"/>"};
- {ok, ROutPacket} ->
- OutPacket = lists:reverse(ROutPacket),
- ?DEBUG("OutPacket: ~p", [OutputEls++OutPacket]),
- prepare_outpacket_response(Sess, Rid, OutputEls++OutPacket, StreamStart);
- {'EXIT', {shutdown, _}} ->
- {200, ?HEADER, "{\"body\": {\"type\":\"terminate\",\"condition\":\"system-shutdown\", \"xmlns\":\""++ ?NS_HTTP_BIND++"\"}}"};
- {'EXIT', _Reason} ->
- {200, ?HEADER, "{\"body\": {\"type\":\"terminate, \"xmlns\":\""++ ?NS_HTTP_BIND++"\"}}"}
+ {ok, cancel} ->
+ {200, ?HEADER,
+ <<"{\"body\": {\"type\":\"error\", \"xmlns\":\"",
+ (?NS_HTTP_BIND)/binary, "\"/>">>};
+ {ok, empty} ->
+ {200, ?HEADER,
+ <<"{\"body\":{ \"xmlns\":\"", (?NS_HTTP_BIND)/binary,
+ "\"}}">>};
+ {ok, terminate} ->
+ {200, ?HEADER,
+ <<"{\"body\": {\"type\":\"terminate\", "
+ "\"xmlns\":\"",
+ (?NS_HTTP_BIND)/binary, "\"/>">>};
+ {ok, ROutPacket} ->
+ OutPacket = lists:reverse(ROutPacket),
+ ?DEBUG("OutPacket: ~p", [OutputEls ++ OutPacket]),
+ prepare_outpacket_response(Sess, Rid,
+ OutputEls ++ OutPacket, StreamStart);
+ {'EXIT', {shutdown, _}} ->
+ {200, ?HEADER,
+ <<"{\"body\": {\"type\":\"terminate\",\"conditio"
+ "n\":\"system-shutdown\", \"xmlns\":\"",
+ (?NS_HTTP_BIND)/binary, "\"}}">>};
+ {'EXIT', _Reason} ->
+ {200, ?HEADER,
+ <<"{\"body\": {\"type\":\"terminate, \"xmlns\":\"",
+ (?NS_HTTP_BIND)/binary, "\"}}">>}
end.
-%% Send output payloads on establised sessions
-prepare_outpacket_response(Sess, _Rid, OutPacket, false) ->
+prepare_outpacket_response(Sess, _Rid, OutPacket,
+ false) ->
case catch send_outpacket(Sess, OutPacket) of
- {'EXIT', _Reason} ->
- {200, ?HEADER,
- "{\"body\": {\"type\":\"terminate\", \"xmlns\":\""++ ?NS_HTTP_BIND++"\"}}"};
- SendRes ->
- SendRes
+ {'EXIT', _Reason} ->
+ {200, ?HEADER,
+ <<"{\"body\": {\"type\":\"terminate\", "
+ "\"xmlns\":\"",
+ (?NS_HTTP_BIND)/binary, "\"}}">>};
+ SendRes -> SendRes
end;
%% Handle a new session along with its output payload
-prepare_outpacket_response(#http_bind{id=Sid, wait=Wait,
- hold=Hold, to=To}=Sess,
- Rid, OutPacket, true) ->
+prepare_outpacket_response(#http_bind{id = Sid,
+ wait = Wait, hold = Hold, to = To} =
+ Sess,
+ Rid, OutPacket, true) ->
case OutPacket of
- [{xmlstreamstart, _, OutAttrs} | Els] ->
- AuthID = xml:get_attr_s("id", OutAttrs),
- From = xml:get_attr_s("from", OutAttrs),
- Version = xml:get_attr_s("version", OutAttrs),
- OutEls =
- case Els of
- [] ->
- [];
- [{xmlstreamelement,
- {xmlelement, "stream:features",
- StreamAttribs, StreamEls}}
- | StreamTail] ->
- TypedTail =
- [check_default_xmlns(OEl) ||
- {xmlstreamelement, OEl} <-
- StreamTail],
- [{xmlelement, "stream:features",
- [{"xmlns:stream",
- ?NS_STREAM}] ++
- StreamAttribs, StreamEls}] ++
- TypedTail;
- StreamTail ->
- [check_default_xmlns(OEl) ||
- {xmlstreamelement, OEl} <-
- StreamTail]
- end,
- case OutEls of
- [] ->
- prepare_response(Sess, Rid, OutPacket, true);
- [{xmlelement,
- "stream:error",_,_}] ->
- {200, ?HEADER, "{\"body\" : {\"type\":\"terminate\", "
- "\"condition\":\"host-unknown\", "
- "\"xmlns\"=\""++?NS_HTTP_BIND++"\"}}"};
- _ ->
- BOSH_attribs =
- [{"authid", AuthID},
- {"xmlns:xmpp", ?NS_BOSH},
- {"xmlns:stream", ?NS_STREAM}] ++
- case OutEls of
- [] ->
- [];
- _ ->
- [{"xmpp:version", Version}]
- end,
- MaxInactivity = get_max_inactivity(To, ?MAX_INACTIVITY),
- MaxPause = get_max_pause(To),
- {200, ?HEADER,
- mochijson2:encode(
- xmpp_json:to_json(
- {xmlelement,"body",
- [{"xmlns",
- ?NS_HTTP_BIND},
- {"sid", Sid},
- {"wait", integer_to_list(Wait)},
- {"requests", integer_to_list(Hold+1)},
- {"inactivity",
- integer_to_list(
- trunc(MaxInactivity/1000))},
- {"maxpause",
- integer_to_list(MaxPause)},
- {"polling",
- integer_to_list(
- trunc(?MIN_POLLING/1000000))},
- {"ver", ?BOSH_VERSION},
- {"from", From},
- {"secure", "true"} %% we're always being secure
- ] ++ BOSH_attribs,OutEls}))}
- end;
- _ ->
- {200, ?HEADER, "{\"body\" : {\"type\":\"terminate\", "
- "\"condition\":\"internal-server-error\", "
- "\"xmlns\"=\""++?NS_HTTP_BIND++"\"}}"}
+ [{xmlstreamstart, _, OutAttrs} | Els] ->
+ AuthID = xml:get_attr_s(<<"id">>, OutAttrs),
+ From = xml:get_attr_s(<<"from">>, OutAttrs),
+ Version = xml:get_attr_s(<<"version">>, OutAttrs),
+ OutEls = case Els of
+ [] -> [];
+ [{xmlstreamelement,
+ #xmlel{name = <<"stream:features">>,
+ attrs = StreamAttribs, children = StreamEls}}
+ | StreamTail] ->
+ TypedTail = [check_default_xmlns(OEl)
+ || {xmlstreamelement, OEl} <- StreamTail],
+ [#xmlel{name = <<"stream:features">>,
+ attrs =
+ [{<<"xmlns:stream">>, ?NS_STREAM}] ++
+ StreamAttribs,
+ children = StreamEls}]
+ ++ TypedTail;
+ StreamTail ->
+ [check_default_xmlns(OEl)
+ || {xmlstreamelement, OEl} <- StreamTail]
+ end,
+ case OutEls of
+ [] -> prepare_response(Sess, Rid, OutPacket, true);
+ [#xmlel{name = <<"stream:error">>}] ->
+ {200, ?HEADER,
+ <<"{\"body\" : {\"type\":\"terminate\", "
+ "\"condition\":\"host-unknown\", \"xmlns\"=\"",
+ (?NS_HTTP_BIND)/binary, "\"}}">>};
+ _ ->
+ BOSH_attribs = [{<<"authid">>, AuthID},
+ {<<"xmlns:xmpp">>, ?NS_BOSH},
+ {<<"xmlns:stream">>, ?NS_STREAM},
+ {<<"xmpp:version">>, Version}],
+% ++
+% case OutEls of
+% [] -> [];
+% _ -> [{<<"xmpp:version">>, Version}]
+% end,
+ MaxInactivity = get_max_inactivity(To, ?MAX_INACTIVITY),
+ MaxPause = get_max_pause(To),
+ {200, ?HEADER,
+ jiffy:encode(xmpp_json:to_json(#xmlel{name =
+ <<"body">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_HTTP_BIND},
+ {<<"sid">>,
+ Sid},
+ {<<"wait">>,
+ iolist_to_binary(integer_to_list(Wait))},
+ {<<"requests">>,
+ iolist_to_binary(integer_to_list(Hold
+ +
+ 1))},
+ {<<"inactivity">>,
+ iolist_to_binary(integer_to_list(trunc(MaxInactivity
+ /
+ 1000)))},
+ {<<"maxpause">>,
+ iolist_to_binary(integer_to_list(MaxPause))},
+ {<<"polling">>,
+ iolist_to_binary(integer_to_list(trunc((?MIN_POLLING)
+ /
+ 1000000)))},
+ {<<"ver">>,
+ ?BOSH_VERSION},
+ {<<"from">>,
+ From},
+ {<<"secure">>,
+ <<"true">>}]
+ ++
+ BOSH_attribs,
+ children =
+ OutEls}))}
+ end;
+ _ ->
+ {200, ?HEADER,
+ <<"{\"body\" : {\"type\":\"terminate\", "
+ "\"condition\":\"internal-server-error\", "
+ "\"xmlns\"=\"",
+ (?NS_HTTP_BIND)/binary, "\"}}">>}
end.
-
-http_get(#http_bind{pid = FsmRef, wait = Wait, hold = Hold}, Rid) ->
- gen_fsm:sync_send_all_state_event(
- FsmRef, {http_get, Rid, Wait, Hold}, 2 * ?MAX_WAIT * 1000).
+http_get(#http_bind{pid = FsmRef, wait = Wait,
+ hold = Hold},
+ Rid) ->
+ gen_fsm:sync_send_all_state_event(FsmRef,
+ {http_get, Rid, Wait, Hold},
+ 2 * (?MAX_WAIT) * 1000).
send_outpacket(#http_bind{pid = FsmRef}, OutPacket) ->
case OutPacket of
- [] ->
- {200, ?HEADER, "{\"body\": {\"xmlns\":\""++?NS_HTTP_BIND++"\"}}"};
- [{xmlstreamend, _}] ->
- gen_fsm:sync_send_all_state_event(FsmRef,{stop,stream_closed}),
- {200, ?HEADER, "{\"body\": {\"xmlns\":"++?NS_HTTP_BIND++"\"}}"};
- _ ->
- %% TODO: We parse to add a default namespace to packet,
- %% The spec says adding the jabber:client namespace if
- %% mandatory, even if some implementation do not do that
- %% change on packets.
- %% I think this should be an option to avoid modifying
- %% packet in most case.
- AllElements =
- lists:all(fun({xmlstreamelement,
- {xmlelement, "stream:error", _, _}}) -> false;
- ({xmlstreamelement, _}) -> true;
- (_) -> false
- end, OutPacket),
- case AllElements of
- true ->
- TypedEls = [check_default_xmlns(OEl) ||
- {xmlstreamelement, OEl} <- OutPacket],
- Body = mochijson2:encode(xmpp_json:to_json(
- {xmlelement,"body",
- [{"xmlns",
- ?NS_HTTP_BIND}],
- TypedEls})),
- ?DEBUG(" --- outgoing data --- ~n~s~n --- END --- ~n",
- [Body]),
- {200, ?HEADER, Body};
- false ->
- case OutPacket of
- [{xmlstreamstart, _, _} | SEls] ->
- OutEls =
- case SEls of
- [{xmlstreamelement,
- {xmlelement,
- "stream:features",
- StreamAttribs, StreamEls}} |
- StreamTail] ->
- TypedTail =
- [check_default_xmlns(OEl) ||
- {xmlstreamelement, OEl} <-
- StreamTail],
- [{xmlelement,
- "stream:features",
- [{"xmlns:stream",
- ?NS_STREAM}] ++
- StreamAttribs, StreamEls}] ++
- TypedTail;
- StreamTail ->
- [check_default_xmlns(OEl) ||
- {xmlstreamelement, OEl} <-
- StreamTail]
- end,
- {200, ?HEADER,
- mochijson2:encode(
- xmpp_json:to_json(
- {xmlelement,"body",
- [{"xmlns",
- ?NS_HTTP_BIND}],
- OutEls}))};
+ [] ->
+ {200, ?HEADER,
+ <<"{\"body\": {\"xmlns\":\"", (?NS_HTTP_BIND)/binary,
+ "\"}}">>};
+ [{xmlstreamend, _}] ->
+ gen_fsm:sync_send_all_state_event(FsmRef,
+ {stop, stream_closed}),
+ {200, ?HEADER,
+ <<"{\"body\": {\"xmlns\":", (?NS_HTTP_BIND)/binary,
+ "\"}}">>};
+ _ ->
+ AllElements = lists:all(fun ({xmlstreamelement,
+ #xmlel{name = <<"stream:error">>}}) ->
+ false;
+ ({xmlstreamelement, _}) -> true;
+ (_) -> false
+ end,
+ OutPacket),
+ case AllElements of
+ true ->
+ TypedEls = [check_default_xmlns(OEl)
+ || {xmlstreamelement, OEl} <- OutPacket],
+ Body = jiffy:encode(xmpp_json:to_json(#xmlel{name =
+ <<"body">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_HTTP_BIND}],
+ children =
+ TypedEls})),
+ ?DEBUG(" --- outgoing data --- ~n~s~n --- END "
+ "--- ~n",
+ [Body]),
+ {200, ?HEADER, Body};
+ false ->
+ case OutPacket of
+ [{xmlstreamstart, _, _} | SEls] ->
+ OutEls = case SEls of
+ [{xmlstreamelement,
+ #xmlel{name = <<"stream:features">>,
+ attrs = StreamAttribs,
+ children = StreamEls}}
+ | StreamTail] ->
+ TypedTail = [check_default_xmlns(OEl)
+ || {xmlstreamelement, OEl}
+ <- StreamTail],
+ [#xmlel{name = <<"stream:features">>,
+ attrs =
+ [{<<"xmlns:stream">>,
+ ?NS_STREAM}]
+ ++ StreamAttribs,
+ children = StreamEls}]
+ ++ TypedTail;
+ StreamTail ->
+ [check_default_xmlns(OEl)
+ || {xmlstreamelement, OEl} <- StreamTail]
+ end,
+ {200, ?HEADER,
+ jiffy:encode(xmpp_json:to_json(#xmlel{name =
+ <<"body">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_HTTP_BIND}],
+ children =
+ OutEls}))};
+ _ ->
+ SErrCond = lists:filter(fun ({xmlstreamelement,
+ #xmlel{name =
+ <<"stream:error">>}}) ->
+ true;
+ (_) -> false
+ end,
+ OutPacket),
+ StreamErrCond = case SErrCond of
+ [] -> null;
+ [{xmlstreamelement,
+ #xmlel{} = StreamErrorTag}
+ | _] ->
+ [StreamErrorTag]
+ end,
+ gen_fsm:sync_send_all_state_event(FsmRef,
+ {stop,
+ {stream_error,
+ OutPacket}}),
+ case StreamErrCond of
+ null ->
+ {200, ?HEADER,
+ <<"{\"body\" : {\"\"type\"\":\"terminate\", "
+ "\"condition\":\"internal-server-error\", "
+ "\"xmlns\"=\"",
+ (?NS_HTTP_BIND)/binary, "\"}}">>};
_ ->
- SErrCond =
- lists:filter(
- fun({xmlstreamelement,
- {xmlelement, "stream:error",
- _, _}}) ->
- true;
- (_) -> false
- end, OutPacket),
- StreamErrCond =
- case SErrCond of
- [] ->
- null;
- [{xmlstreamelement,
- {xmlelement, _, _, _Cond} =
- StreamErrorTag} | _] ->
- [StreamErrorTag]
- end,
- gen_fsm:sync_send_all_state_event(FsmRef,
- {stop, {stream_error,OutPacket}}),
- case StreamErrCond of
- null ->
- {200, ?HEADER,
- "{\"body\" : {\"\"type\"\":\"terminate\", "
- "\"condition\":\"internal-server-error\", "
- "\"xmlns\"=\""++?NS_HTTP_BIND++"\"}}"};
- _ ->
- {200, ?HEADER,
- "{\"body\" : {\"\"type\"\":\"terminate\", "
- "\"condition\":\"remote-stream-error\", "
- "\"xmlns\":\""++?NS_HTTP_BIND++"\", " ++
- "\"xmlns:stream\":\""++?NS_STREAM++"\" \"$\":" ++
- elements_to_string(StreamErrCond) ++
- "}}"}
- end
- end
- end
+ {200, ?HEADER,
+ <<"{\"body\" : {\"\"type\"\":\"terminate\", "
+ "\"condition\":\"remote-stream-error\", "
+ "\"xmlns\":\"",
+ (?NS_HTTP_BIND)/binary, "\", ",
+ "\"xmlns:stream\":\"", (?NS_STREAM)/binary,
+ "\" \"$\":",
+ (elements_to_string(StreamErrCond))/binary,
+ "}}">>}
+ end
+ end
+ end
end.
parse_request(Data, PayloadSize, MaxStanzaSize) ->
- ?DEBUG("--- incoming data --- ~n~p~n --- END --- ", [xmpp_json:from_json(mochijson2:decode(Data))]),
- %% MR: I do not think it works if put put several elements in the
- %% same body:
- case xmpp_json:from_json(mochijson2:decode(Data)) of
- {xmlstreamelement,{xmlelement, "body", Attrs, Els}} ->
- Xmlns = xml:get_attr_s("xmlns",Attrs),
- if
- Xmlns /= ?NS_HTTP_BIND ->
- {error, bad_request};
- true ->
- case catch list_to_integer(xml:get_attr_s("rid", Attrs)) of
- {'EXIT', _} ->
- {error, bad_request};
- Rid ->
- %% I guess this is to remove XMLCDATA: Is it really needed ?
- FixedEls =
- lists:filter(
- fun(I) ->
- case I of
- {xmlelement, _, _, _} ->
- true;
- _ ->
- false
- end
- end, Els),
- Sid = xml:get_attr_s("sid",Attrs),
- if
- PayloadSize =< MaxStanzaSize ->
- {ok, {Sid, Rid, Attrs, FixedEls}};
- true ->
- {size_limit, Sid}
- end
- end
- end;
- {xmlstreamelement,{xmlelement, _Name, _Attrs, _Els}} ->
- {error, bad_request};
- {error, _Reason} ->
- {error, bad_request}
+ ?DEBUG("--- incoming data --- ~n~p~n --- END "
+ "--- ",
+ [xmpp_json:from_json(jiffy:decode(Data))]),
+ case xmpp_json:from_json(jiffy:decode(Data)) of
+ {xmlstreamelement,
+ #xmlel{name = <<"body">>, attrs = Attrs,
+ children = Els}} ->
+ Xmlns = xml:get_attr_s(<<"xmlns">>, Attrs),
+ if Xmlns /= (?NS_HTTP_BIND) -> {error, bad_request};
+ true ->
+ case catch
+ jlib:binary_to_integer(xml:get_attr_s(<<"rid">>,
+ Attrs))
+ of
+ {'EXIT', _} -> {error, bad_request};
+ Rid ->
+ FixedEls = lists:filter(fun (I) ->
+ case I of
+ #xmlel{} -> true;
+ _ -> false
+ end
+ end,
+ Els),
+ Sid = xml:get_attr_s(<<"sid">>, Attrs),
+ if PayloadSize =< MaxStanzaSize ->
+ {ok, {Sid, Rid, Attrs, FixedEls}};
+ true -> {size_limit, Sid}
+ end
+ end
+ end;
+ _ -> {error, bad_request}
end.
-send_receiver_reply(undefined, _Reply) ->
- ok;
+send_receiver_reply(undefined, _Reply) -> ok;
send_receiver_reply(Receiver, Reply) ->
gen_fsm:reply(Receiver, Reply).
-
-%% Cancel timer and empty message queue.
-cancel_timer(undefined) ->
- ok;
+cancel_timer(undefined) -> ok;
cancel_timer(Timer) ->
erlang:cancel_timer(Timer),
- receive
- {timeout, Timer, _} ->
- ok
- after 0 ->
- ok
- end.
+ receive {timeout, Timer, _} -> ok after 0 -> ok end.
-%% If client asked for a pause (pause > 0), we apply the pause value
-%% as inactivity timer:
-set_inactivity_timer(Pause, _MaxInactivity) when Pause > 0 ->
- erlang:start_timer(Pause*1000, self(), []);
+set_inactivity_timer(Pause, _MaxInactivity)
+ when Pause > 0 ->
+ erlang:start_timer(Pause * 1000, self(), []);
%% Otherwise, we apply the max_inactivity value as inactivity timer:
set_inactivity_timer(_Pause, MaxInactivity) ->
erlang:start_timer(MaxInactivity, self(), []).
-
-%% TODO: Use tail recursion and list reverse ?
-elements_to_string([]) ->
- [];
+elements_to_string([]) -> [];
elements_to_string([El | Els]) ->
- [mochijson2:encode(xmpp_json:to_json(El))|elements_to_string(Els)].
+ [jiffy:encode(xmpp_json:to_json(El))
+ | elements_to_string(Els)].
-%% @spec (To, Default::integer()) -> integer()
-%% where To = [] | {Host::string(), Version::string()}
get_max_inactivity({Host, _}, Default) ->
- case gen_mod:get_module_opt(Host, mod_http_bind, max_inactivity, undefined) of
- Seconds when is_integer(Seconds) ->
- Seconds * 1000;
- undefined ->
- Default
+ case gen_mod:get_module_opt(Host, mod_http_bind, max_inactivity,
+ fun(I) when is_integer(I), I>0 -> I end,
+ undefined)
+ of
+ Seconds when is_integer(Seconds) -> Seconds * 1000;
+ undefined -> Default
end;
-get_max_inactivity(_, Default) ->
- Default.
+get_max_inactivity(_, Default) -> Default.
get_max_pause({Host, _}) ->
- gen_mod:get_module_opt(Host, mod_http_bind, max_pause, ?MAX_PAUSE);
-get_max_pause(_) ->
- ?MAX_PAUSE.
+ gen_mod:get_module_opt(Host, mod_http_bind, max_pause,
+ fun(I) when is_integer(I), I>0 -> I end,
+ ?MAX_PAUSE);
+get_max_pause(_) -> ?MAX_PAUSE.
-%% Current time as integer
tnow() ->
{TMegSec, TSec, TMSec} = now(),
(TMegSec * 1000000 + TSec) * 1000000 + TMSec.
-check_default_xmlns({xmlelement, Name, Attrs, Els} = El) ->
- case xml:get_tag_attr_s("xmlns", El) of
- "" -> {xmlelement, Name, [{"xmlns", ?NS_CLIENT} | Attrs], Els};
- _ -> El
+check_default_xmlns(#xmlel{name = Name, attrs = Attrs,
+ children = Els} =
+ El) ->
+ case xml:get_tag_attr_s(<<"xmlns">>, El) of
+ <<"">> ->
+ #xmlel{name = Name,
+ attrs = [{<<"xmlns">>, ?NS_CLIENT} | Attrs],
+ children = Els};
+ _ -> El
end.
-%% Check that mod_http_bind has been defined in config file.
-%% Print a warning in log file if this is not the case.
check_bind_module(XmppDomain) ->
case gen_mod:is_loaded(XmppDomain, mod_http_bind) of
- true -> true;
- false -> ?ERROR_MSG("You are trying to use BOSH (HTTP Bind), but the module mod_http_bind is not started.~n"
- "Check your 'modules' section in your ejabberd configuration file.",[]),
- false
+ true -> true;
+ false ->
+ ?ERROR_MSG("You are trying to use BOSH (HTTP Bind), "
+ "but the module mod_http_bind is not "
+ "started.~nCheck your 'modules' section "
+ "in your ejabberd configuration file.",
+ []),
+ false
end.
make_sid() ->
- sha:sha(term_to_binary({now(), make_ref()}))
- ++ "-" ++ ejabberd_cluster:node_id().
+ <<(sha:sha(term_to_binary({now(), make_ref()})))/binary,
+ "-", (ejabberd_cluster:node_id())/binary>>.
get_session(SID) ->
- case string:tokens(SID, "-") of
- [_, NodeID] ->
- case ejabberd_cluster:get_node_by_id(NodeID) of
- Node when Node == node() ->
- case mnesia:dirty_read({http_bind, SID}) of
- [] ->
- {error, enoent};
- [Session] ->
- {ok, Session}
- end;
- Node ->
- case catch rpc:call(Node, mnesia, dirty_read,
- [{http_bind, SID}], 5000) of
- [Session] ->
- {ok, Session};
- _ ->
- {error, enoent}
- end
- end;
- _ ->
- {error, enoent}
+ case str:tokens(SID, <<"-">>) of
+ [_, NodeID] ->
+ case ejabberd_cluster:get_node_by_id(NodeID) of
+ Node when Node == node() ->
+ case mnesia:dirty_read({http_bind, SID}) of
+ [] -> {error, enoent};
+ [Session] -> {ok, Session}
+ end;
+ Node ->
+ case catch rpc:call(Node, mnesia, dirty_read,
+ [{http_bind, SID}], 5000)
+ of
+ [Session] -> {ok, Session};
+ _ -> {error, enoent}
+ end
+ end;
+ _ -> {error, enoent}
end.
diff --git a/src/web/ejabberd_http_poll.erl b/src/web/ejabberd_http_poll.erl
index cde8124f9..937009014 100644
--- a/src/web/ejabberd_http_poll.erl
+++ b/src/web/ejabberd_http_poll.erl
@@ -25,210 +25,191 @@
%%%----------------------------------------------------------------------
-module(ejabberd_http_poll).
+
-author('alexey@process-one.net').
-behaviour(gen_fsm).
%% External exports
--export([start_link/3,
- init/1,
- handle_event/3,
- handle_sync_event/4,
- code_change/4,
- handle_info/3,
- terminate/3,
- send/2,
- setopts/2,
- sockname/1, peername/1,
- controlling_process/2,
- close/1,
- process/2]).
+-export([start_link/3, init/1, handle_event/3,
+ handle_sync_event/4, code_change/4, handle_info/3,
+ terminate/3, send/2, setopts/2, sockname/1, peername/1,
+ controlling_process/2, close/1, process/2]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
+
-include("ejabberd_http.hrl").
--record(http_poll, {id, pid}).
+-record(http_poll, {id :: pid() | binary(), pid :: pid()}).
+
+-type poll_socket() :: #http_poll{}.
+-export_type([poll_socket/0]).
--record(state, {id,
- key,
- socket,
- output = "",
- input = "",
- waiting_input = false, %% {ReceiverPid, Tag}
- last_receiver,
- http_poll_timeout,
- timer}).
+-record(state,
+ {id, key, socket, output = <<"">>, input = <<"">>,
+ waiting_input = false, last_receiver, http_poll_timeout,
+ timer}).
%-define(DBGFSM, true).
-ifdef(DBGFSM).
+
-define(FSMOPTS, [{debug, [trace]}]).
+
-else.
+
-define(FSMOPTS, []).
+
-endif.
--define(HTTP_POLL_TIMEOUT, 300000).
--define(CT, {"Content-Type", "text/xml; charset=utf-8"}).
--define(BAD_REQUEST, [?CT, {"Set-Cookie", "ID=-3:0; expires=-1"}]).
+-define(HTTP_POLL_TIMEOUT, 300).
+-define(CT,
+ {<<"Content-Type">>, <<"text/xml; charset=utf-8">>}).
+
+-define(BAD_REQUEST,
+ [?CT, {<<"Set-Cookie">>, <<"ID=-3:0; expires=-1">>}]).
-%%%----------------------------------------------------------------------
-%%% API
-%%%----------------------------------------------------------------------
start(ID, Key, IP) ->
update_tables(),
mnesia:create_table(http_poll,
- [{ram_copies, [node()]},
- {local_content, true},
- {attributes, record_info(fields, http_poll)}]),
+ [{ram_copies, [node()]}, {local_content, true},
+ {attributes, record_info(fields, http_poll)}]),
mnesia:add_table_copy(http_poll, node(), ram_copies),
- supervisor:start_child(ejabberd_http_poll_sup, [ID, Key, IP]).
+ supervisor:start_child(ejabberd_http_poll_sup,
+ [ID, Key, IP]).
start_link(ID, Key, IP) ->
gen_fsm:start_link(?MODULE, [ID, Key, IP], ?FSMOPTS).
send({http_poll, FsmRef, _IP}, Packet) ->
- gen_fsm:sync_send_all_state_event(FsmRef, {send, Packet}).
+ gen_fsm:sync_send_all_state_event(FsmRef,
+ {send, Packet}).
setopts({http_poll, FsmRef, _IP}, Opts) ->
case lists:member({active, once}, Opts) of
- true ->
- gen_fsm:send_all_state_event(FsmRef, {activate, self()});
- _ ->
- ok
+ true ->
+ gen_fsm:send_all_state_event(FsmRef,
+ {activate, self()});
+ _ -> ok
end.
-sockname(_Socket) ->
- {ok, {{0, 0, 0, 0}, 0}}.
+sockname(_Socket) -> {ok, {{0, 0, 0, 0}, 0}}.
-peername({http_poll, _FsmRef, IP}) ->
- {ok, IP}.
+peername({http_poll, _FsmRef, IP}) -> {ok, IP}.
-controlling_process(_Socket, _Pid) ->
- ok.
+controlling_process(_Socket, _Pid) -> ok.
close({http_poll, FsmRef, _IP}) ->
catch gen_fsm:sync_send_all_state_event(FsmRef, close).
-
-process([], #request{data = Data,
- ip = IP} = _Request) ->
+process([],
+ #request{data = Data, ip = IP} = _Request) ->
case catch parse_request(Data) of
- {ok, ID1, Key, NewKey, Packet} ->
- ID = if
- (ID1 == "0") or (ID1 == "mobile") ->
- NewID = make_sid(),
- {ok, Pid} = start(NewID, "", IP),
- mnesia:async_dirty(
- fun() ->
- mnesia:write(#http_poll{id = NewID,
- pid = Pid})
- end),
- NewID;
- true ->
- ID1
- end,
- case http_put(ID, Key, NewKey, Packet) of
- {error, not_exists} ->
- {200, ?BAD_REQUEST, ""};
- {error, bad_key} ->
- {200, ?BAD_REQUEST, ""};
- ok ->
- receive
- after 100 -> ok
- end,
- case http_get(ID) of
- {error, not_exists} ->
- {200, ?BAD_REQUEST, ""};
- {ok, OutPacket} ->
- if
- ID == ID1 ->
- Cookie = "ID=" ++ ID ++ "; expires=-1",
- {200, [?CT, {"Set-Cookie", Cookie}],
- OutPacket};
- ID1 == "mobile" ->
- {200, [?CT], [ID, $\n, OutPacket]};
- true ->
- Cookie = "ID=" ++ ID ++ "; expires=-1",
- {200, [?CT, {"Set-Cookie", Cookie}],
- OutPacket}
- end
- end
- end;
- _ ->
- HumanHTMLxmlel = get_human_html_xmlel(),
- {200, [?CT, {"Set-Cookie", "ID=-2:0; expires=-1"}], HumanHTMLxmlel}
+ {ok, ID1, Key, NewKey, Packet} ->
+ ID = if (ID1 == <<"0">>) or (ID1 == <<"mobile">>) ->
+ NewID = make_sid(),
+ {ok, Pid} = start(NewID, <<"">>, IP),
+ mnesia:async_dirty(fun () ->
+ mnesia:write(#http_poll{id =
+ NewID,
+ pid =
+ Pid})
+ end),
+ NewID;
+ true -> ID1
+ end,
+ case http_put(ID, Key, NewKey, Packet) of
+ {error, not_exists} -> {200, ?BAD_REQUEST, <<"">>};
+ {error, bad_key} -> {200, ?BAD_REQUEST, <<"">>};
+ ok ->
+ receive after 100 -> ok end,
+ case http_get(ID) of
+ {error, not_exists} -> {200, ?BAD_REQUEST, <<"">>};
+ {ok, OutPacket} ->
+ if ID == ID1 ->
+ Cookie = <<"ID=", ID/binary, "; expires=-1">>,
+ {200, [?CT, {<<"Set-Cookie">>, Cookie}],
+ OutPacket};
+ ID1 == <<"mobile">> ->
+ {200, [?CT], [ID, $\n, OutPacket]};
+ true ->
+ Cookie = <<"ID=", ID/binary, "; expires=-1">>,
+ {200, [?CT, {<<"Set-Cookie">>, Cookie}], OutPacket}
+ end
+ end
+ end;
+ _ ->
+ HumanHTMLxmlel = get_human_html_xmlel(),
+ {200,
+ [?CT, {<<"Set-Cookie">>, <<"ID=-2:0; expires=-1">>}],
+ HumanHTMLxmlel}
end;
process(_, _Request) ->
- {400, [], {xmlelement, "h1", [],
- [{xmlcdata, "400 Bad Request"}]}}.
+ {400, [],
+ #xmlel{name = <<"h1">>, attrs = [],
+ children = [{xmlcdata, <<"400 Bad Request">>}]}}.
-%% Code copied from mod_http_bind.erl and customized
get_human_html_xmlel() ->
- Heading = "ejabberd " ++ atom_to_list(?MODULE),
- {xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"}],
- [{xmlelement, "head", [],
- [{xmlelement, "title", [], [{xmlcdata, Heading}]}]},
- {xmlelement, "body", [],
- [{xmlelement, "h1", [], [{xmlcdata, Heading}]},
- {xmlelement, "p", [],
- [{xmlcdata, "An implementation of "},
- {xmlelement, "a",
- [{"href", "http://xmpp.org/extensions/xep-0025.html"}],
- [{xmlcdata, "Jabber HTTP Polling (XEP-0025)"}]}]},
- {xmlelement, "p", [],
- [{xmlcdata, "This web page is only informative. "
- "To use HTTP-Poll you need a Jabber/XMPP client that supports it."}
- ]}
- ]}]}.
+ Heading = <<"ejabberd ",
+ (iolist_to_binary(atom_to_list(?MODULE)))/binary>>,
+ #xmlel{name = <<"html">>,
+ attrs =
+ [{<<"xmlns">>, <<"http://www.w3.org/1999/xhtml">>}],
+ children =
+ [#xmlel{name = <<"head">>, attrs = [],
+ children =
+ [#xmlel{name = <<"title">>, attrs = [],
+ children = [{xmlcdata, Heading}]}]},
+ #xmlel{name = <<"body">>, attrs = [],
+ children =
+ [#xmlel{name = <<"h1">>, attrs = [],
+ children = [{xmlcdata, Heading}]},
+ #xmlel{name = <<"p">>, attrs = [],
+ children =
+ [{xmlcdata, <<"An implementation of ">>},
+ #xmlel{name = <<"a">>,
+ attrs =
+ [{<<"href">>,
+ <<"http://xmpp.org/extensions/xep-0025.html">>}],
+ children =
+ [{xmlcdata,
+ <<"Jabber HTTP Polling (XEP-0025)">>}]}]},
+ #xmlel{name = <<"p">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<"This web page is only informative. To "
+ "use HTTP-Poll you need a Jabber/XMPP "
+ "client that supports it.">>}]}]}]}.
%%%----------------------------------------------------------------------
%%% Callback functions from gen_fsm
%%%----------------------------------------------------------------------
-%%----------------------------------------------------------------------
-%% Func: init/1
-%% Returns: {ok, StateName, StateData} |
-%% {ok, StateName, StateData, Timeout} |
-%% ignore |
-%% {stop, StopReason}
-%%----------------------------------------------------------------------
init([ID, Key, IP]) ->
?INFO_MSG("started: ~p", [{ID, Key, IP}]),
-
- %% Read c2s options from the first ejabberd_c2s configuration in
- %% the config file listen section
- %% TODO: We should have different access and shaper values for
- %% each connector. The default behaviour should be however to use
- %% the default c2s restrictions if not defined for the current
- %% connector.
Opts = ejabberd_c2s_config:get_c2s_limits(),
-
- HTTPPollTimeout = case ejabberd_config:get_local_option({http_poll_timeout,
- ?MYNAME}) of
- %% convert seconds of option into milliseconds
- Int when is_integer(Int) -> Int*1000;
- undefined -> ?HTTP_POLL_TIMEOUT
- end,
-
+ HTTPPollTimeout = ejabberd_config:get_local_option(
+ {http_poll_timeout, ?MYNAME},
+ fun(I) when is_integer(I), I>0 -> I end,
+ ?HTTP_POLL_TIMEOUT) * 1000,
Socket = {http_poll, self(), IP},
- ejabberd_socket:start(ejabberd_c2s, ?MODULE, Socket, Opts),
+ ejabberd_socket:start(ejabberd_c2s, ?MODULE, Socket,
+ Opts),
Timer = erlang:start_timer(HTTPPollTimeout, self(), []),
- {ok, loop, #state{id = ID,
- key = Key,
- socket = Socket,
- http_poll_timeout = HTTPPollTimeout,
- timer = Timer}}.
+ {ok, loop,
+ #state{id = ID, key = Key, socket = Socket,
+ http_poll_timeout = HTTPPollTimeout, timer = Timer}}.
%%----------------------------------------------------------------------
%% Func: StateName/2
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
-%% {stop, Reason, NewStateData}
+%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
-
%%----------------------------------------------------------------------
%% Func: StateName/3
%% Returns: {next_state, NextStateName, NextStateData} |
@@ -236,145 +217,105 @@ init([ID, Key, IP]) ->
%% {reply, Reply, NextStateName, NextStateData} |
%% {reply, Reply, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData} |
-%% {stop, Reason, Reply, NewStateData}
+%% {stop, Reason, Reply, NewStateData}
%%----------------------------------------------------------------------
%state_name(Event, From, StateData) ->
% Reply = ok,
% {reply, Reply, state_name, StateData}.
-%%----------------------------------------------------------------------
-%% Func: handle_event/3
-%% Returns: {next_state, NextStateName, NextStateData} |
-%% {next_state, NextStateName, NextStateData, Timeout} |
-%% {stop, Reason, NewStateData}
-%%----------------------------------------------------------------------
handle_event({activate, From}, StateName, StateData) ->
case StateData#state.input of
- "" ->
- {next_state, StateName,
- StateData#state{waiting_input = {From, ok}}};
- Input ->
- Receiver = From,
- Receiver ! {tcp, StateData#state.socket, list_to_binary(Input)},
- {next_state, StateName, StateData#state{input = "",
- waiting_input = false,
- last_receiver = Receiver
- }}
+ <<"">> ->
+ {next_state, StateName,
+ StateData#state{waiting_input = {From, ok}}};
+ Input ->
+ Receiver = From,
+ Receiver !
+ {tcp, StateData#state.socket, iolist_to_binary(Input)},
+ {next_state, StateName,
+ StateData#state{input = <<"">>, waiting_input = false,
+ last_receiver = Receiver}}
end;
-
handle_event(_Event, StateName, StateData) ->
{next_state, StateName, StateData}.
-%%----------------------------------------------------------------------
-%% Func: handle_sync_event/4
-%% Returns: {next_state, NextStateName, NextStateData} |
-%% {next_state, NextStateName, NextStateData, Timeout} |
-%% {reply, Reply, NextStateName, NextStateData} |
-%% {reply, Reply, NextStateName, NextStateData, Timeout} |
-%% {stop, Reason, NewStateData} |
-%% {stop, Reason, Reply, NewStateData}
-%%----------------------------------------------------------------------
-handle_sync_event({send, Packet}, _From, StateName, StateData) ->
- Packet2 = if
- is_binary(Packet) ->
- binary_to_list(Packet);
- true ->
- Packet
+handle_sync_event({send, Packet}, _From, StateName,
+ StateData) ->
+ Packet2 = if is_binary(Packet) -> (Packet);
+ true -> Packet
end,
- Output = StateData#state.output ++ [lists:flatten(Packet2)],
+ Output = StateData#state.output ++
+ [lists:flatten(Packet2)],
Reply = ok,
- {reply, Reply, StateName, StateData#state{output = Output}};
-
+ {reply, Reply, StateName,
+ StateData#state{output = Output}};
handle_sync_event(stop, _From, _StateName, StateData) ->
- Reply = ok,
- {stop, normal, Reply, StateData};
-
+ Reply = ok, {stop, normal, Reply, StateData};
handle_sync_event({http_put, Key, NewKey, Packet},
_From, StateName, StateData) ->
Allow = case StateData#state.key of
- "" ->
- true;
- OldKey ->
- NextKey = jlib:encode_base64(
- binary_to_list(crypto:sha(Key))),
- if
- OldKey == NextKey ->
- true;
- true ->
- false
- end
+ <<"">> -> true;
+ OldKey ->
+ NextKey = jlib:encode_base64((crypto:sha(Key))),
+ if OldKey == NextKey -> true;
+ true -> false
+ end
end,
- if
- Allow ->
- case StateData#state.waiting_input of
- false ->
- Input = [StateData#state.input|Packet],
- Reply = ok,
- {reply, Reply, StateName, StateData#state{input = Input,
- key = NewKey}};
- {Receiver, _Tag} ->
- Receiver ! {tcp, StateData#state.socket,
- list_to_binary(Packet)},
- cancel_timer(StateData#state.timer),
- Timer = erlang:start_timer(StateData#state.http_poll_timeout, self(), []),
- Reply = ok,
- {reply, Reply, StateName,
- StateData#state{waiting_input = false,
- last_receiver = Receiver,
- key = NewKey,
- timer = Timer}}
- end;
- true ->
- Reply = {error, bad_key},
- {reply, Reply, StateName, StateData}
+ if Allow ->
+ case StateData#state.waiting_input of
+ false ->
+ Input = [StateData#state.input | Packet],
+ Reply = ok,
+ {reply, Reply, StateName,
+ StateData#state{input = Input, key = NewKey}};
+ {Receiver, _Tag} ->
+ Receiver !
+ {tcp, StateData#state.socket, iolist_to_binary(Packet)},
+ cancel_timer(StateData#state.timer),
+ Timer =
+ erlang:start_timer(StateData#state.http_poll_timeout,
+ self(), []),
+ Reply = ok,
+ {reply, Reply, StateName,
+ StateData#state{waiting_input = false,
+ last_receiver = Receiver, key = NewKey,
+ timer = Timer}}
+ end;
+ true ->
+ Reply = {error, bad_key},
+ {reply, Reply, StateName, StateData}
end;
-
-handle_sync_event(http_get, _From, StateName, StateData) ->
+handle_sync_event(http_get, _From, StateName,
+ StateData) ->
Reply = {ok, StateData#state.output},
- {reply, Reply, StateName, StateData#state{output = ""}};
-
-handle_sync_event(_Event, _From, StateName, StateData) ->
- Reply = ok,
- {reply, Reply, StateName, StateData}.
+ {reply, Reply, StateName,
+ StateData#state{output = <<"">>}};
+handle_sync_event(_Event, _From, StateName,
+ StateData) ->
+ Reply = ok, {reply, Reply, StateName, StateData}.
code_change(_OldVsn, StateName, StateData, _Extra) ->
{ok, StateName, StateData}.
-%%----------------------------------------------------------------------
-%% Func: handle_info/3
-%% Returns: {next_state, NextStateName, NextStateData} |
-%% {next_state, NextStateName, NextStateData, Timeout} |
-%% {stop, Reason, NewStateData}
-%%----------------------------------------------------------------------
handle_info({timeout, Timer, _}, _StateName,
#state{timer = Timer} = StateData) ->
{stop, normal, StateData};
-
handle_info(_, StateName, StateData) ->
{next_state, StateName, StateData}.
-%%----------------------------------------------------------------------
-%% Func: terminate/3
-%% Purpose: Shutdown the fsm
-%% Returns: any
-%%----------------------------------------------------------------------
terminate(_Reason, _StateName, StateData) ->
- mnesia:async_dirty(
- fun() ->
- mnesia:delete({http_poll, StateData#state.id})
- end),
+ mnesia:async_dirty(fun () ->
+ mnesia:delete({http_poll, StateData#state.id})
+ end),
case StateData#state.waiting_input of
- false ->
- %% We are testing this case due to "socket activation": If we pass
- %% here and the "socket" is not ready to receive, the tcp_closed
- %% will be lost.
- case StateData#state.last_receiver of
- undefined -> ok;
- Receiver ->
- Receiver ! {tcp_closed, StateData#state.socket}
- end;
- {Receiver, _Tag} ->
- Receiver ! {tcp_closed, StateData#state.socket}
+ false ->
+ case StateData#state.last_receiver of
+ undefined -> ok;
+ Receiver ->
+ Receiver ! {tcp_closed, StateData#state.socket}
+ end;
+ {Receiver, _Tag} ->
+ Receiver ! {tcp_closed, StateData#state.socket}
end,
catch resend_messages(StateData#state.output),
ok.
@@ -385,109 +326,85 @@ terminate(_Reason, _StateName, StateData) ->
http_put(ID, Key, NewKey, Packet) ->
case get_session(ID) of
- {error, _} ->
- {error, not_exists};
- {ok, #http_poll{pid = FsmRef}} ->
- gen_fsm:sync_send_all_state_event(
- FsmRef, {http_put, Key, NewKey, Packet})
+ {error, _} -> {error, not_exists};
+ {ok, #http_poll{pid = FsmRef}} ->
+ gen_fsm:sync_send_all_state_event(FsmRef,
+ {http_put, Key, NewKey, Packet})
end.
http_get(ID) ->
case get_session(ID) of
- {error, _} ->
- {error, not_exists};
- {ok, #http_poll{pid = FsmRef}} ->
- gen_fsm:sync_send_all_state_event(FsmRef, http_get)
+ {error, _} -> {error, not_exists};
+ {ok, #http_poll{pid = FsmRef}} ->
+ gen_fsm:sync_send_all_state_event(FsmRef, http_get)
end.
-
parse_request(Data) ->
- Comma = string:chr(Data, $,),
- Header = lists:sublist(Data, Comma - 1),
- Packet = lists:nthtail(Comma, Data),
- {ID, Key, NewKey} =
- case string:tokens(Header, ";") of
- [ID1] ->
- {ID1, "", ""};
- [ID1, Key1] ->
- {ID1, Key1, Key1};
- [ID1, Key1, NewKey1] ->
- {ID1, Key1, NewKey1}
- end,
+ Comma = str:chr(Data, $,),
+ Header = str:substr(Data, 1, Comma - 1),
+ Packet = str:substr(Data, Comma + 1, byte_size(Data)),
+ {ID, Key, NewKey} = case str:tokens(Header, <<";">>) of
+ [ID1] -> {ID1, <<"">>, <<"">>};
+ [ID1, Key1] -> {ID1, Key1, Key1};
+ [ID1, Key1, NewKey1] -> {ID1, Key1, NewKey1}
+ end,
{ok, ID, Key, NewKey, Packet}.
-
cancel_timer(Timer) ->
erlang:cancel_timer(Timer),
- receive
- {timeout, Timer, _} ->
- ok
- after 0 ->
- ok
- end.
+ receive {timeout, Timer, _} -> ok after 0 -> ok end.
-%% Resend the polled messages
resend_messages(Messages) ->
- lists:foreach(fun(Packet) ->
- resend_message(Packet)
- end, Messages).
-
-%% This function is used to resend messages that have been polled but not
-%% delivered.
+ lists:foreach(fun (Packet) -> resend_message(Packet)
+ end,
+ Messages).
+
resend_message(Packet) ->
- {xmlelement, Name, _, _} = ParsedPacket = xml_stream:parse_element(Packet),
- %% Avoid sending <stream:error>
- if Name == "iq"; Name == "message"; Name == "presence" ->
- From = get_jid("from", ParsedPacket),
- To = get_jid("to", ParsedPacket),
- ?DEBUG("Resend ~p ~p ~p~n",[From,To, ParsedPacket]),
- ejabberd_router:route(From, To, ParsedPacket);
- true ->
- ok
+ #xmlel{name = Name} = ParsedPacket =
+ xml_stream:parse_element(Packet),
+ if Name == <<"iq">>;
+ Name == <<"message">>;
+ Name == <<"presence">> ->
+ From = get_jid(<<"from">>, ParsedPacket),
+ To = get_jid(<<"to">>, ParsedPacket),
+ ?DEBUG("Resend ~p ~p ~p~n", [From, To, ParsedPacket]),
+ ejabberd_router:route(From, To, ParsedPacket);
+ true -> ok
end.
-%% Type can be "from" or "to"
-%% Parsed packet is a parsed Jabber packet.
get_jid(Type, ParsedPacket) ->
case xml:get_tag_attr(Type, ParsedPacket) of
- {value, StringJid} ->
- jlib:string_to_jid(StringJid);
- false ->
- jlib:make_jid("","","")
+ {value, StringJid} -> jlib:string_to_jid(StringJid);
+ false -> jlib:make_jid(<<"">>, <<"">>, <<"">>)
end.
update_tables() ->
- case catch mnesia:table_info(http_poll, local_content) of
- false ->
- mnesia:delete_table(http_poll);
- _ ->
- ok
+ case catch mnesia:table_info(http_poll, local_content)
+ of
+ false -> mnesia:delete_table(http_poll);
+ _ -> ok
end.
make_sid() ->
- sha:sha(term_to_binary({now(), make_ref()}))
- ++ "-" ++ ejabberd_cluster:node_id().
+ <<(sha:sha(term_to_binary({now(), make_ref()})))/binary,
+ "-", (ejabberd_cluster:node_id())/binary>>.
get_session(SID) ->
- case string:tokens(SID, "-") of
- [_, NodeID] ->
- case ejabberd_cluster:get_node_by_id(NodeID) of
- Node when Node == node() ->
- case mnesia:dirty_read({http_poll, SID}) of
- [] ->
- {error, enoent};
- [Session] ->
- {ok, Session}
- end;
- Node ->
- case catch rpc:call(Node, mnesia, dirty_read,
- [{http_poll, SID}], 5000) of
- [Session] ->
- {ok, Session};
- _ ->
- {error, enoent}
- end
- end;
- _ ->
- {error, enoent}
+ case str:tokens(SID, <<"-">>) of
+ [_, NodeID] ->
+ case ejabberd_cluster:get_node_by_id(NodeID) of
+ Node when Node == node() ->
+ case mnesia:dirty_read({http_poll, SID}) of
+ [] -> {error, enoent};
+ [Session] -> {ok, Session}
+ end;
+ Node ->
+ case catch rpc:call(Node, mnesia, dirty_read,
+ [{http_poll, SID}], 5000)
+ of
+ [Session] -> {ok, Session};
+ _ -> {error, enoent}
+ end
+ end;
+ _ -> {error, enoent}
end.
diff --git a/src/web/ejabberd_http_ws.erl b/src/web/ejabberd_http_ws.erl
index f79c41ce1..97545819b 100644
--- a/src/web/ejabberd_http_ws.erl
+++ b/src/web/ejabberd_http_ws.erl
@@ -23,192 +23,158 @@
%%% 02111-1307 USA
%%%
%%%----------------------------------------------------------------------
--module (ejabberd_http_ws).
+-module(ejabberd_http_ws).
-author('ecestari@process-one.net').
-behaviour(gen_fsm).
% External exports
--export([
- start/1,
- start_link/1,
- init/1,
- handle_event/3,
- handle_sync_event/4,
- code_change/4,
- handle_info/3,
- terminate/3,
- send/2,
- setopts/2,
- sockname/1, peername/1,
- controlling_process/2,
- become_controller/2,
- close/1]).
+-export([start/1, start_link/1, init/1, handle_event/3,
+ handle_sync_event/4, code_change/4, handle_info/3,
+ terminate/3, send/2, setopts/2, sockname/1, peername/1,
+ controlling_process/2, become_controller/2, close/1]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
+
-include("ejabberd_http.hrl").
--record(state, {
- socket,
- timeout,
- timer,
- input = "",
- waiting_input = false, %% {ReceiverPid, Tag}
- last_receiver,
- ws}).
+-define(WEBSOCKET_TIMEOUT, 300).
+
+-record(state,
+ {socket :: ws_socket(),
+ timeout = ?WEBSOCKET_TIMEOUT :: pos_integer(),
+ timer = make_ref() :: reference(),
+ input = <<"">> :: binary(),
+ waiting_input = false :: false | pid(),
+ last_receiver :: pid(),
+ ws :: atom()}).
%-define(DBGFSM, true).
-ifdef(DBGFSM).
+
-define(FSMOPTS, [{debug, [trace]}]).
+
-else.
+
-define(FSMOPTS, []).
+
-endif.
--define(WEBSOCKET_TIMEOUT, 300000).
-%
-%
-%%%%----------------------------------------------------------------------
-%%%% API
-%%%%----------------------------------------------------------------------
+-type ws_socket() :: {http_ws, pid(), {inet:ip_address(), inet:port_number()}}.
+-export_type([ws_socket/0]).
+
start(WS) ->
supervisor:start_child(ejabberd_wsloop_sup, [WS]).
start_link(WS) ->
- gen_fsm:start_link(?MODULE, [WS],?FSMOPTS).
+ gen_fsm:start_link(?MODULE, [WS], ?FSMOPTS).
send({http_ws, FsmRef, _IP}, Packet) ->
- gen_fsm:sync_send_all_state_event(FsmRef, {send, Packet}).
+ gen_fsm:sync_send_all_state_event(FsmRef,
+ {send, Packet}).
setopts({http_ws, FsmRef, _IP}, Opts) ->
case lists:member({active, once}, Opts) of
- true ->
- gen_fsm:send_all_state_event(FsmRef, {activate, self()});
- _ ->
- ok
+ true ->
+ gen_fsm:send_all_state_event(FsmRef,
+ {activate, self()});
+ _ -> ok
end.
-sockname(_Socket) ->
- {ok, {{0, 0, 0, 0}, 0}}.
+sockname(_Socket) -> {ok, {{0, 0, 0, 0}, 0}}.
-peername({http_ws, _FsmRef, IP}) ->
- {ok, IP}.
+peername({http_ws, _FsmRef, IP}) -> {ok, IP}.
-controlling_process(_Socket, _Pid) ->
- ok.
+controlling_process(_Socket, _Pid) -> ok.
become_controller(FsmRef, C2SPid) ->
- gen_fsm:send_all_state_event(FsmRef, {become_controller, C2SPid}).
+ gen_fsm:send_all_state_event(FsmRef,
+ {become_controller, C2SPid}).
close({http_ws, FsmRef, _IP}) ->
- catch gen_fsm:sync_send_all_state_event(FsmRef, close).
-
-%%% Internal
+ catch gen_fsm:sync_send_all_state_event(FsmRef, close).
+%%% Internal
init([WS]) ->
- %% Read c2s options from the first ejabberd_c2s configuration in
- %% the config file listen section
- %% TODO: We should have different access and shaper values for
- %% each connector. The default behaviour should be however to use
- %% the default c2s restrictions if not defined for the current
- %% connector.
Opts = ejabberd_c2s_config:get_c2s_limits(),
-
- WSTimeout = case ejabberd_config:get_local_option({websocket_timeout,
- ?MYNAME}) of
- %% convert seconds of option into milliseconds
- Int when is_integer(Int) -> Int*1000;
- undefined -> ?WEBSOCKET_TIMEOUT
- end,
-
+ WSTimeout = ejabberd_config:get_local_option(
+ {websocket_timeout, ?MYNAME},
+ fun(I) when is_integer(I), I>0 -> I end,
+ ?WEBSOCKET_TIMEOUT) * 1000,
Socket = {http_ws, self(), WS:get(ip)},
- ?DEBUG("Client connected through websocket ~p", [Socket]),
- ejabberd_socket:start(ejabberd_c2s, ?MODULE, Socket, Opts),
+ ?DEBUG("Client connected through websocket ~p",
+ [Socket]),
+ ejabberd_socket:start(ejabberd_c2s, ?MODULE, Socket,
+ Opts),
Timer = erlang:start_timer(WSTimeout, self(), []),
- {ok, loop, #state{
- socket = Socket,
- timeout = WSTimeout,
- timer = Timer,
- ws = WS}}.
+ {ok, loop,
+ #state{socket = Socket, timeout = WSTimeout,
+ timer = Timer, ws = WS}}.
handle_event({activate, From}, StateName, StateData) ->
case StateData#state.input of
- "" ->
- {next_state, StateName,
- StateData#state{waiting_input = {From, ok}}};
- Input ->
- Receiver = From,
- Receiver ! {tcp, StateData#state.socket, list_to_binary(Input)},
- {next_state, StateName, StateData#state{input = "",
- waiting_input = false,
- last_receiver = Receiver
- }}
+ <<"">> ->
+ {next_state, StateName,
+ StateData#state{waiting_input = From}};
+ Input ->
+ Receiver = From,
+ Receiver !
+ {tcp, StateData#state.socket, Input},
+ {next_state, StateName,
+ StateData#state{input = <<"">>, waiting_input = false,
+ last_receiver = Receiver}}
end.
-handle_sync_event({send, Packet}, _From, StateName, #state{ws = WS} = StateData) ->
- Packet2 = if
- is_binary(Packet) ->
- Packet;
- true ->
- list_to_binary(Packet)
- end,
- %?DEBUG("sending on websocket : ~p ", [Packet2]),
+handle_sync_event({send, Packet}, _From, StateName,
+ #state{ws = WS} = StateData) ->
+ Packet2 = if is_binary(Packet) -> Packet;
+ true -> iolist_to_binary(Packet)
+ end,
WS:send(Packet2),
{reply, ok, StateName, StateData};
-
-handle_sync_event(close, From, _StateName, StateData)->
+handle_sync_event(close, _From, _StateName, StateData) ->
{stop, normal, StateData}.
handle_info(closed, _StateName, StateData) ->
- {stop, normal, StateData};
-
-handle_info({browser, Packet}, StateName, StateData)->
- %?DEBUG("Received on websocket : ~p ", [Packet]),
- NPacket = unicode:characters_to_binary(Packet,latin1),
+ {stop, normal, StateData};
+handle_info({browser, Packet}, StateName, StateData) ->
+ NPacket = unicode:characters_to_binary(Packet, latin1),
NewState = case StateData#state.waiting_input of
- false ->
- Input = [StateData#state.input|NPacket],
- StateData#state{input = Input};
- {Receiver, _Tag} ->
- Receiver ! {tcp, StateData#state.socket,NPacket},
- cancel_timer(StateData#state.timer),
- Timer = erlang:start_timer(StateData#state.timeout, self(), []),
- StateData#state{waiting_input = false,
- last_receiver = Receiver,
- timer = Timer}
- end,
- {next_state, StateName, NewState};
-
-
+ false ->
+ Input = <<(StateData#state.input)/binary, NPacket/binary>>,
+ StateData#state{input = Input};
+ Receiver ->
+ Receiver ! {tcp, StateData#state.socket, NPacket},
+ cancel_timer(StateData#state.timer),
+ Timer = erlang:start_timer(StateData#state.timeout,
+ self(), []),
+ StateData#state{waiting_input = false,
+ last_receiver = Receiver, timer = Timer}
+ end,
+ {next_state, StateName, NewState};
handle_info({timeout, Timer, _}, _StateName,
#state{timer = Timer} = StateData) ->
{stop, normal, StateData};
-
handle_info(_, StateName, StateData) ->
{next_state, StateName, StateData}.
-
code_change(_OldVsn, StateName, StateData, _Extra) ->
- {ok, StateName, StateData}.
-
-terminate(_Reason, _StateName, StateData) ->
+ {ok, StateName, StateData}.
+
+terminate(_Reason, _StateName, StateData) ->
case StateData#state.waiting_input of
- false ->
- ok;
- {Receiver,_} ->
- ?DEBUG("C2S Pid : ~p", [Receiver]),
- Receiver ! {tcp_closed, StateData#state.socket }
+ false -> ok;
+ Receiver ->
+ ?DEBUG("C2S Pid : ~p", [Receiver]),
+ Receiver ! {tcp_closed, StateData#state.socket}
end,
ok.
cancel_timer(Timer) ->
erlang:cancel_timer(Timer),
- receive
- {timeout, Timer, _} ->
- ok
- after 0 ->
- ok
- end. \ No newline at end of file
+ receive {timeout, Timer, _} -> ok after 0 -> ok end.
diff --git a/src/web/ejabberd_http_wsjson.erl b/src/web/ejabberd_http_wsjson.erl
index 2abd12b3b..1a07252e8 100644
--- a/src/web/ejabberd_http_wsjson.erl
+++ b/src/web/ejabberd_http_wsjson.erl
@@ -24,196 +24,169 @@
%%%
%%%----------------------------------------------------------------------
--module (ejabberd_http_wsjson).
+-module(ejabberd_http_wsjson).
+
-author('ecestari@process-one.net').
-behaviour(gen_fsm).
% External exports
--export([
- start/1,
- start_link/1,
- init/1,
- handle_event/3,
- handle_sync_event/4,
- code_change/4,
- handle_info/3,
- terminate/3,
- send_xml/2,
- setopts/2,
- sockname/1, peername/1,
- controlling_process/2,
- become_controller/2,
+-export([start/1, start_link/1, init/1, handle_event/3,
+ handle_sync_event/4, code_change/4, handle_info/3,
+ terminate/3, send_xml/2, setopts/2, sockname/1,
+ peername/1, controlling_process/2, become_controller/2,
close/1]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
+
-include("ejabberd_http.hrl").
--record(state, {
- socket,
- timeout,
- timer,
- input = [],
- waiting_input = false, %% {ReceiverPid, Tag}
- last_receiver,
- ws}).
+-define(WEBSOCKET_TIMEOUT, 300).
+
+-record(state,
+ {socket :: ejabberd_http_ws:ws_socket(),
+ timeout = ?WEBSOCKET_TIMEOUT :: pos_integer(),
+ timer = make_ref() :: reference(),
+ input = [] :: list(),
+ waiting_input = false :: false | pid(),
+ last_receiver :: pid(),
+ ws :: atom()}).
%-define(DBGFSM, true).
-ifdef(DBGFSM).
+
-define(FSMOPTS, [{debug, [trace]}]).
+
-else.
+
-define(FSMOPTS, []).
+
-endif.
--define(WEBSOCKET_TIMEOUT, 300000).
-%
-%
-%%%%----------------------------------------------------------------------
-%%%% API
-%%%%----------------------------------------------------------------------
start(WS) ->
supervisor:start_child(ejabberd_wsloop_sup, [WS]).
start_link(WS) ->
- gen_fsm:start_link(?MODULE, [WS],?FSMOPTS).
+ gen_fsm:start_link(?MODULE, [WS], ?FSMOPTS).
send_xml({http_ws, FsmRef, _IP}, Packet) ->
- gen_fsm:sync_send_all_state_event(FsmRef, {send, Packet}).
+ gen_fsm:sync_send_all_state_event(FsmRef,
+ {send, Packet}).
setopts({http_ws, FsmRef, _IP}, Opts) ->
case lists:member({active, once}, Opts) of
- true ->
- gen_fsm:send_all_state_event(FsmRef, {activate, self()});
- _ ->
- ok
+ true ->
+ gen_fsm:send_all_state_event(FsmRef,
+ {activate, self()});
+ _ -> ok
end.
-sockname(_Socket) ->
- {ok, {{0, 0, 0, 0}, 0}}.
+sockname(_Socket) -> {ok, {{0, 0, 0, 0}, 0}}.
-peername({http_ws, _FsmRef, IP}) ->
- {ok, IP}.
+peername({http_ws, _FsmRef, IP}) -> {ok, IP}.
-controlling_process(_Socket, _Pid) ->
- ok.
+controlling_process(_Socket, _Pid) -> ok.
become_controller(FsmRef, C2SPid) ->
- gen_fsm:send_all_state_event(FsmRef, {become_controller, C2SPid}).
+ gen_fsm:send_all_state_event(FsmRef,
+ {become_controller, C2SPid}).
close({http_ws, FsmRef, _IP}) ->
- catch gen_fsm:sync_send_all_state_event(FsmRef, close).
-
-%%% Internal
+ catch gen_fsm:sync_send_all_state_event(FsmRef, close).
+%%% Internal
init([WS]) ->
- %% Read c2s options from the first ejabberd_c2s configuration in
- %% the config file listen section
- %% TODO: We should have different access and shaper values for
- %% each connector. The default behaviour should be however to use
- %% the default c2s restrictions if not defined for the current
- %% connector.
- Opts = [{xml_socket, true}|ejabberd_c2s_config:get_c2s_limits()],
-
- WSTimeout = case ejabberd_config:get_local_option({websocket_timeout,
- ?MYNAME}) of
- %% convert seconds of option into milliseconds
- Int when is_integer(Int) -> Int*1000;
- undefined -> ?WEBSOCKET_TIMEOUT
- end,
-
+ Opts = [{xml_socket, true}
+ | ejabberd_c2s_config:get_c2s_limits()],
+ WSTimeout = ejabberd_config:get_local_option(
+ {websocket_timeout, ?MYNAME},
+ fun(I) when is_integer(I), I>0 -> I end,
+ ?WEBSOCKET_TIMEOUT) * 1000,
Socket = {http_ws, self(), WS:get(ip)},
- ?DEBUG("Client connected through websocket ~p", [Socket]),
- ejabberd_socket:start(ejabberd_c2s, ?MODULE, Socket, Opts),
+ ?DEBUG("Client connected through websocket ~p",
+ [Socket]),
+ ejabberd_socket:start(ejabberd_c2s, ?MODULE, Socket,
+ Opts),
Timer = erlang:start_timer(WSTimeout, self(), []),
- {ok, loop, #state{
- socket = Socket,
- timeout = WSTimeout,
- timer = Timer,
- ws = WS}}.
+ {ok, loop,
+ #state{socket = Socket, timeout = WSTimeout,
+ timer = Timer, ws = WS}}.
handle_event({activate, From}, StateName, StateData) ->
case StateData#state.input of
- [] ->
- {next_state, StateName,
- StateData#state{waiting_input = {From, ok}}};
- Input ->
- Receiver = From,
- lists:reverse(lists:map(fun(Packet)->
- Receiver ! {tcp, StateData#state.socket, [Packet]}
- end, Input)),
- {next_state, StateName, StateData#state{input = "",
- waiting_input = false,
- last_receiver = Receiver
- }}
+ [] ->
+ {next_state, StateName,
+ StateData#state{waiting_input = From}};
+ Input ->
+ Receiver = From,
+ lists:reverse(lists:map(fun (Packet) ->
+ Receiver !
+ {tcp, StateData#state.socket,
+ [Packet]}
+ end,
+ Input)),
+ {next_state, StateName,
+ StateData#state{input = [], waiting_input = false,
+ last_receiver = Receiver}}
end.
-handle_sync_event({send, Packet}, _From, StateName, #state{ws = WS} = StateData) ->
+handle_sync_event({send, Packet}, _From, StateName,
+ #state{ws = WS} = StateData) ->
EJson = xmpp_json:to_json(Packet),
- Json = mochijson2:encode(EJson),
- WS:send(iolist_to_binary(Json)),
+ Json = jiffy:encode(EJson),
+ WS:send(Json),
{reply, ok, StateName, StateData};
-
-handle_sync_event(close, _From, _StateName, StateData) ->
- Reply = ok,
- {stop, normal, Reply, StateData}.
-
-handle_info({browser, <<"\n">>}, StateName, StateData)->
+handle_sync_event(close, _From, _StateName,
+ StateData) ->
+ Reply = ok, {stop, normal, Reply, StateData}.
+
+handle_info({browser, <<"\n">>}, StateName,
+ StateData) ->
NewState = case StateData#state.waiting_input of
- false ->
- ok;
- {Receiver, _Tag} ->
- Receiver ! {tcp, StateData#state.socket,<<"\n">>},
- cancel_timer(StateData#state.timer),
- Timer = erlang:start_timer(StateData#state.timeout, self(), []),
- StateData#state{waiting_input = false,
- last_receiver = Receiver,
- timer = Timer}
- end,
- {next_state, StateName, NewState};
-handle_info({browser, JsonPacket}, StateName, StateData)->
+ false -> ok;
+ Receiver ->
+ Receiver ! {tcp, StateData#state.socket, <<"\n">>},
+ cancel_timer(StateData#state.timer),
+ Timer = erlang:start_timer(StateData#state.timeout,
+ self(), []),
+ StateData#state{waiting_input = false,
+ last_receiver = Receiver, timer = Timer}
+ end,
+ {next_state, StateName, NewState};
+handle_info({browser, JsonPacket}, StateName,
+ StateData) ->
NewState = case StateData#state.waiting_input of
- false ->
- EJson = mochijson2:decode(JsonPacket),
- Packet = xmpp_json:from_json(EJson),
- Input = [Packet | StateData#state.input],
- StateData#state{input = Input};
- {Receiver, _Tag} ->
- %?DEBUG("Received from browser : ~p", [JsonPacket]),
- EJson = mochijson2:decode(JsonPacket),
- %?DEBUG("decoded : ~p", [EJson]),
- Packet = xmpp_json:from_json(EJson),
- %?DEBUG("sending to c2s : ~p", [Packet]),
- Receiver ! {tcp, StateData#state.socket,[Packet]},
- cancel_timer(StateData#state.timer),
- Timer = erlang:start_timer(StateData#state.timeout, self(), []),
- StateData#state{waiting_input = false,
- last_receiver = Receiver,
- timer = Timer}
- end,
- {next_state, StateName, NewState};
-
-
+ false ->
+ EJson = jiffy:decode(JsonPacket),
+ Packet = xmpp_json:from_json(EJson),
+ Input = [Packet | StateData#state.input],
+ StateData#state{input = Input};
+ Receiver ->
+ EJson = jiffy:decode(JsonPacket),
+ Packet = xmpp_json:from_json(EJson),
+ Receiver ! {tcp, StateData#state.socket, [Packet]},
+ cancel_timer(StateData#state.timer),
+ Timer = erlang:start_timer(StateData#state.timeout,
+ self(), []),
+ StateData#state{waiting_input = false,
+ last_receiver = Receiver, timer = Timer}
+ end,
+ {next_state, StateName, NewState};
handle_info({timeout, Timer, _}, _StateName,
#state{timer = Timer} = StateData) ->
{stop, normal, StateData};
-
handle_info(_, StateName, StateData) ->
{next_state, StateName, StateData}.
-
code_change(_OldVsn, StateName, StateData, _Extra) ->
- {ok, StateName, StateData}.
-
+ {ok, StateName, StateData}.
+
terminate(_Reason, _StateName, _StateData) -> ok.
cancel_timer(Timer) ->
erlang:cancel_timer(Timer),
- receive
- {timeout, Timer, _} ->
- ok
- after 0 ->
- ok
- end.
+ receive {timeout, Timer, _} -> ok after 0 -> ok end.
diff --git a/src/web/ejabberd_web.erl b/src/web/ejabberd_web.erl
index 932d829df..7a2ab3d84 100644
--- a/src/web/ejabberd_web.erl
+++ b/src/web/ejabberd_web.erl
@@ -1,7 +1,7 @@
%%%----------------------------------------------------------------------
%%% File : ejabberd_web.erl
%%% Author : Alexey Shchepin <alexey@process-one.net>
-%%% Purpose :
+%%% Purpose :
%%% Created : 28 Feb 2004 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
@@ -25,56 +25,80 @@
%%%----------------------------------------------------------------------
-module(ejabberd_web).
+
-author('alexey@process-one.net').
%% External exports
--export([make_xhtml/1, make_xhtml/2,
- error/1]).
+-export([make_xhtml/1, make_xhtml/2, error/1]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
--include("ejabberd_http.hrl").
+-include("ejabberd_http.hrl").
%% XXX bard: there are variants of make_xhtml in ejabberd_http and
%% ejabberd_web_admin. It might be a good idea to centralize it here
%% and also create an ejabberd_web.hrl file holding the macros, so
%% that third parties can use ejabberd_web as an "utility" library.
-make_xhtml(Els) ->
- make_xhtml([], Els).
+make_xhtml(Els) -> make_xhtml([], Els).
make_xhtml(HeadEls, Els) ->
- {xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"},
- {"xml:lang", "en"},
- {"lang", "en"}],
- [{xmlelement, "head", [],
- [{xmlelement, "meta", [{"http-equiv", "Content-Type"},
- {"content", "text/html; charset=utf-8"}], []}
- | HeadEls]},
- {xmlelement, "body", [], Els}
- ]}.
-
-
--define(X(Name), {xmlelement, Name, [], []}).
--define(XA(Name, Attrs), {xmlelement, Name, Attrs, []}).
--define(XE(Name, Els), {xmlelement, Name, [], Els}).
--define(XAE(Name, Attrs, Els), {xmlelement, Name, Attrs, Els}).
+ #xmlel{name = <<"html">>,
+ attrs =
+ [{<<"xmlns">>, <<"http://www.w3.org/1999/xhtml">>},
+ {<<"xml:lang">>, <<"en">>}, {<<"lang">>, <<"en">>}],
+ children =
+ [#xmlel{name = <<"head">>, attrs = [],
+ children =
+ [#xmlel{name = <<"meta">>,
+ attrs =
+ [{<<"http-equiv">>, <<"Content-Type">>},
+ {<<"content">>,
+ <<"text/html; charset=utf-8">>}],
+ children = []}
+ | HeadEls]},
+ #xmlel{name = <<"body">>, attrs = [], children = Els}]}.
+
+-define(X(Name),
+ #xmlel{name = Name, attrs = [], children = []}).
+
+-define(XA(Name, Attrs),
+ #xmlel{name = Name, attrs = Attrs, children = []}).
+
+-define(XE(Name, Els),
+ #xmlel{name = Name, attrs = [], children = Els}).
+
+-define(XAE(Name, Attrs, Els),
+ #xmlel{name = Name, attrs = Attrs, children = Els}).
+
-define(C(Text), {xmlcdata, Text}).
+
-define(XC(Name, Text), ?XE(Name, [?C(Text)])).
--define(XAC(Name, Attrs, Text), ?XAE(Name, Attrs, [?C(Text)])).
--define(LI(Els), ?XE("li", Els)).
--define(A(URL, Els), ?XAE("a", [{"href", URL}], Els)).
+-define(XAC(Name, Attrs, Text),
+ ?XAE(Name, Attrs, [?C(Text)])).
+
+-define(LI(Els), ?XE(<<"li">>, Els)).
+
+-define(A(URL, Els),
+ ?XAE(<<"a">>, [{<<"href">>, URL}], Els)).
+
-define(AC(URL, Text), ?A(URL, [?C(Text)])).
--define(P, ?X("p")).
--define(BR, ?X("br")).
+
+-define(P, ?X(<<"p">>)).
+
+-define(BR, ?X(<<"br">>)).
+
-define(INPUT(Type, Name, Value),
- ?XA("input", [{"type", Type},
- {"name", Name},
- {"value", Value}])).
+ ?XA(<<"input">>,
+ [{<<"type">>, Type}, {<<"name">>, Name},
+ {<<"value">>, Value}])).
error(not_found) ->
- {404, [], make_xhtml([?XC("h1", "404 Not Found")])};
+ {404, [],
+ make_xhtml([?XC(<<"h1">>, <<"404 Not Found">>)])};
error(not_allowed) ->
- {401, [], make_xhtml([?XC("h1", "401 Unauthorized")])}.
+ {401, [],
+ make_xhtml([?XC(<<"h1">>, <<"401 Unauthorized">>)])}.
diff --git a/src/web/ejabberd_web_admin.erl b/src/web/ejabberd_web_admin.erl
index 59e9b0313..25229aaad 100644
--- a/src/web/ejabberd_web_admin.erl
+++ b/src/web/ejabberd_web_admin.erl
@@ -27,25 +27,27 @@
%%%% definitions
-module(ejabberd_web_admin).
+
-author('alexey@process-one.net').
%% External exports
--export([process/2,
- list_users/4,
- list_users_in_diapason/4,
- pretty_print_xml/1,
+-export([process/2, list_users/4,
+ list_users_in_diapason/4, pretty_print_xml/1,
term_to_id/1]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
+
-include("ejabberd_http.hrl").
+
-include("ejabberd_web_admin.hrl").
-define(INPUTATTRS(Type, Name, Value, Attrs),
- ?XA("input", Attrs ++
- [{"type", Type},
- {"name", Name},
- {"value", Value}])).
+ ?XA(<<"input">>,
+ (Attrs ++
+ [{<<"type">>, Type}, {<<"name">>, Name},
+ {<<"value">>, Value}]))).
%%%==================================
%%%% get_acl_access
@@ -53,67 +55,67 @@
%% @spec (Path::[string()], Method) -> {HostOfRule, [AccessRule]}
%% where Method = 'GET' | 'POST'
-%% All accounts can access those URLs
-get_acl_rule([],_) -> {"localhost", [all]};
-get_acl_rule(["style.css"],_) -> {"localhost", [all]};
-get_acl_rule(["logo.png"],_) -> {"localhost", [all]};
-get_acl_rule(["logo-fill.png"],_) -> {"localhost", [all]};
-get_acl_rule(["favicon.ico"],_) -> {"localhost", [all]};
-get_acl_rule(["additions.js"],_) -> {"localhost", [all]};
+get_acl_rule([], _) -> {<<"localhost">>, [all]};
+get_acl_rule([<<"style.css">>], _) ->
+ {<<"localhost">>, [all]};
+get_acl_rule([<<"logo.png">>], _) ->
+ {<<"localhost">>, [all]};
+get_acl_rule([<<"logo-fill.png">>], _) ->
+ {<<"localhost">>, [all]};
+get_acl_rule([<<"favicon.ico">>], _) ->
+ {<<"localhost">>, [all]};
+get_acl_rule([<<"additions.js">>], _) ->
+ {<<"localhost">>, [all]};
%% This page only displays vhosts that the user is admin:
-get_acl_rule(["vhosts"],_) -> {"localhost", [all]};
-
+get_acl_rule([<<"vhosts">>], _) ->
+ {<<"localhost">>, [all]};
%% The pages of a vhost are only accesible if the user is admin of that vhost:
-get_acl_rule(["server", VHost | _RPath], Method)
- when Method=:='GET' orelse Method=:='HEAD' ->
+get_acl_rule([<<"server">>, VHost | _RPath], Method)
+ when Method =:= 'GET' orelse Method =:= 'HEAD' ->
{VHost, [configure, webadmin_view]};
-get_acl_rule(["server", VHost | _RPath], 'POST') -> {VHost, [configure]};
-
+get_acl_rule([<<"server">>, VHost | _RPath], 'POST') ->
+ {VHost, [configure]};
%% Default rule: only global admins can access any other random page
get_acl_rule(_RPath, Method)
- when Method=:='GET' orelse Method=:='HEAD' ->
+ when Method =:= 'GET' orelse Method =:= 'HEAD' ->
{global, [configure, webadmin_view]};
get_acl_rule(_RPath, 'POST') -> {global, [configure]}.
is_acl_match(Host, Rules, Jid) ->
- lists:any(
- fun(Rule) ->
- allow == acl:match_rule(Host, Rule, Jid)
- end,
- Rules).
+ lists:any(fun (Rule) ->
+ allow == acl:match_rule(Host, Rule, Jid)
+ end,
+ Rules).
%%%==================================
%%%% Menu Items Access
get_jid(Auth, HostHTTP, Method) ->
case get_auth_admin(Auth, HostHTTP, [], Method) of
- {ok, {User, Server}} ->
- jlib:make_jid(User, Server, "");
- {unauthorized, Error} ->
- ?ERROR_MSG("Unauthorized ~p: ~p", [Auth, Error]),
- throw({unauthorized, Auth})
+ {ok, {User, Server}} ->
+ jlib:make_jid(User, Server, <<"">>);
+ {unauthorized, Error} ->
+ ?ERROR_MSG("Unauthorized ~p: ~p", [Auth, Error]),
+ throw({unauthorized, Auth})
end.
get_menu_items(global, cluster, Lang, JID) ->
{Base, _, Items} = make_server_menu([], [], Lang, JID),
- lists:map(
- fun({URI, Name}) ->
- {Base++URI++"/", Name};
- ({URI, Name, _SubMenu}) ->
- {Base++URI++"/", Name}
- end,
- Items
- );
+ lists:map(fun ({URI, Name}) ->
+ {<<Base/binary, URI/binary, "/">>, Name};
+ ({URI, Name, _SubMenu}) ->
+ {<<Base/binary, URI/binary, "/">>, Name}
+ end,
+ Items);
get_menu_items(Host, cluster, Lang, JID) ->
{Base, _, Items} = make_host_menu(Host, [], Lang, JID),
- lists:map(
- fun({URI, Name}) ->
- {Base++URI++"/", Name};
- ({URI, Name, _SubMenu}) ->
- {Base++URI++"/", Name}
- end,
- Items
- ).
+ lists:map(fun ({URI, Name}) ->
+ {<<Base/binary, URI/binary, "/">>, Name};
+ ({URI, Name, _SubMenu}) ->
+ {<<Base/binary, URI/binary, "/">>, Name}
+ end,
+ Items).
+
%% get_menu_items(Host, Node, Lang, JID) ->
%% {Base, _, Items} = make_host_node_menu(Host, Node, Lang, JID),
%% lists:map(
@@ -130,7 +132,7 @@ is_allowed_path(BasePath, {Path, _}, JID) ->
is_allowed_path(BasePath, {Path, _, _}, JID) ->
is_allowed_path(BasePath ++ [Path], JID).
-is_allowed_path(["admin" | Path], JID) ->
+is_allowed_path([<<"admin">> | Path], JID) ->
is_allowed_path(Path, JID);
is_allowed_path(Path, JID) ->
{HostOfRule, AccessRule} = get_acl_rule(Path, 'GET'),
@@ -143,126 +145,132 @@ is_allowed_path(Path, JID) ->
%%path_to_url(Path) ->
%% "/" ++ string:join(Path, "/") ++ "/".
-%% @spec(URL) -> Path
-%% where Path = [string()]
-%% URL = string()
-%% Convert "admin/user/tom" -> ["admin", "user", "tom"]
-url_to_path(URL) ->
- string:tokens(URL, "/").
-
+url_to_path(URL) -> str:tokens(URL, <<"/">>).
+
%%%==================================
%%%% process/2
-process(["doc", LocalFile], _Request) ->
+process([<<"doc">>, LocalFile], _Request) ->
DocPath = case os:getenv("EJABBERD_DOC_PATH") of
- P when is_list(P) -> P;
- false -> "/share/doc/ejabberd/"
+ P when is_list(P) -> P;
+ false -> <<"/share/doc/ejabberd/">>
end,
- %% Code based in mod_http_fileserver
FileName = filename:join(DocPath, LocalFile),
case file:read_file(FileName) of
- {ok, FileContents} ->
- ?DEBUG("Delivering content.", []),
- {200,
- [{"Server", "ejabberd"}],
- FileContents};
- {error, Error} ->
- ?DEBUG("Delivering error: ~p", [Error]),
- Help = " " ++ FileName ++ " - Try to specify the path to ejabberd documentation "
- "with the environment variable EJABBERD_DOC_PATH. Check the ejabberd Guide for more information.",
- case Error of
- eacces -> {403, [], "Forbidden"++Help};
- enoent -> {404, [], "Not found"++Help};
- _Else -> {404, [], atom_to_list(Error)++Help}
- end
+ {ok, FileContents} ->
+ ?DEBUG("Delivering content.", []),
+ {200, [{<<"Server">>, <<"ejabberd">>}], FileContents};
+ {error, Error} ->
+ ?DEBUG("Delivering error: ~p", [Error]),
+ Help = <<" ", FileName/binary,
+ " - Try to specify the path to ejabberd "
+ "documentation with the environment variable "
+ "EJABBERD_DOC_PATH. Check the ejabberd "
+ "Guide for more information.">>,
+ case Error of
+ eacces -> {403, [], <<"Forbidden", Help/binary>>};
+ enoent -> {404, [], <<"Not found", Help/binary>>};
+ _Else ->
+ {404, [], <<(iolist_to_binary(atom_to_list(Error)))/binary, Help/binary>>}
+ end
end;
-
-process(["server", SHost | RPath] = Path, #request{auth = Auth, lang = Lang, host = HostHTTP, method = Method} = Request) ->
+process([<<"server">>, SHost | RPath] = Path,
+ #request{auth = Auth, lang = Lang, host = HostHTTP,
+ method = Method} =
+ Request) ->
Host = jlib:nameprep(SHost),
case lists:member(Host, ?MYHOSTS) of
- true ->
- case get_auth_admin(Auth, HostHTTP, Path, Method) of
- {ok, {User, Server}} ->
- AJID = get_jid(Auth, HostHTTP, Method),
- process_admin(Host, Request#request{path = RPath,
- auth = {auth_jid, Auth, AJID},
- us = {User, Server}});
- {unauthorized, "no-auth-provided"} ->
- {401,
- [{"WWW-Authenticate", "basic realm=\"ejabberd\""}],
- ejabberd_web:make_xhtml([?XCT("h1", "Unauthorized")])};
- {unauthorized, Error} ->
- {BadUser, _BadPass} = Auth,
- {IPT, _Port} = Request#request.ip,
- IPS = inet_parse:ntoa(IPT),
- ?WARNING_MSG("Access of ~p from ~p failed with error: ~p",
- [BadUser, IPS, Error]),
- {401,
- [{"WWW-Authenticate",
- "basic realm=\"auth error, retry login to ejabberd\""}],
- ejabberd_web:make_xhtml([?XCT("h1", "Unauthorized")])}
- end;
- false ->
- ejabberd_web:error(not_found)
+ true ->
+ case get_auth_admin(Auth, HostHTTP, Path, Method) of
+ {ok, {User, Server}} ->
+ AJID = get_jid(Auth, HostHTTP, Method),
+ process_admin(Host,
+ Request#request{path = RPath,
+ auth = {auth_jid, Auth, AJID},
+ us = {User, Server}});
+ {unauthorized, <<"no-auth-provided">>} ->
+ {401,
+ [{<<"WWW-Authenticate">>,
+ <<"basic realm=\"ejabberd\"">>}],
+ ejabberd_web:make_xhtml([?XCT(<<"h1">>,
+ <<"Unauthorized">>)])};
+ {unauthorized, Error} ->
+ {BadUser, _BadPass} = Auth,
+ {IPT, _Port} = Request#request.ip,
+ IPS = jlib:ip_to_list(IPT),
+ ?WARNING_MSG("Access of ~p from ~p failed with error: ~p",
+ [BadUser, IPS, Error]),
+ {401,
+ [{<<"WWW-Authenticate">>,
+ <<"basic realm=\"auth error, retry login "
+ "to ejabberd\"">>}],
+ ejabberd_web:make_xhtml([?XCT(<<"h1">>,
+ <<"Unauthorized">>)])}
+ end;
+ false -> ejabberd_web:error(not_found)
end;
-
-process(RPath, #request{auth = Auth, lang = Lang, host = HostHTTP, method = Method} = Request) ->
+process(RPath,
+ #request{auth = Auth, lang = Lang, host = HostHTTP,
+ method = Method} =
+ Request) ->
case get_auth_admin(Auth, HostHTTP, RPath, Method) of
- {ok, {User, Server}} ->
- AJID = get_jid(Auth, HostHTTP, Method),
- process_admin(global, Request#request{path = RPath,
- auth = {auth_jid, Auth, AJID},
- us = {User, Server}});
- {unauthorized, "no-auth-provided"} ->
- {401,
- [{"WWW-Authenticate", "basic realm=\"ejabberd\""}],
- ejabberd_web:make_xhtml([?XCT("h1", "Unauthorized")])};
- {unauthorized, Error} ->
- {BadUser, _BadPass} = Auth,
- {IPT, _Port} = Request#request.ip,
- IPS = inet_parse:ntoa(IPT),
- ?WARNING_MSG("Access of ~p from ~p failed with error: ~p",
- [BadUser, IPS, Error]),
- {401,
- [{"WWW-Authenticate",
- "basic realm=\"auth error, retry login to ejabberd\""}],
- ejabberd_web:make_xhtml([?XCT("h1", "Unauthorized")])}
+ {ok, {User, Server}} ->
+ AJID = get_jid(Auth, HostHTTP, Method),
+ process_admin(global,
+ Request#request{path = RPath,
+ auth = {auth_jid, Auth, AJID},
+ us = {User, Server}});
+ {unauthorized, <<"no-auth-provided">>} ->
+ {401,
+ [{<<"WWW-Authenticate">>,
+ <<"basic realm=\"ejabberd\"">>}],
+ ejabberd_web:make_xhtml([?XCT(<<"h1">>,
+ <<"Unauthorized">>)])};
+ {unauthorized, Error} ->
+ {BadUser, _BadPass} = Auth,
+ {IPT, _Port} = Request#request.ip,
+ IPS = jlib:ip_to_list(IPT),
+ ?WARNING_MSG("Access of ~p from ~p failed with error: ~p",
+ [BadUser, IPS, Error]),
+ {401,
+ [{<<"WWW-Authenticate">>,
+ <<"basic realm=\"auth error, retry login "
+ "to ejabberd\"">>}],
+ ejabberd_web:make_xhtml([?XCT(<<"h1">>,
+ <<"Unauthorized">>)])}
end.
get_auth_admin(Auth, HostHTTP, RPath, Method) ->
case Auth of
- {SJID, Pass} ->
- {HostOfRule, AccessRule} = get_acl_rule(RPath, Method),
- case jlib:string_to_jid(SJID) of
- error ->
- {unauthorized, "badformed-jid"};
- #jid{user = "", server = User} ->
- %% If the user only specified username, not username@server
- get_auth_account(HostOfRule, AccessRule, User, HostHTTP, Pass);
- #jid{user = User, server = Server} ->
- get_auth_account(HostOfRule, AccessRule, User, Server, Pass)
- end;
- undefined ->
- {unauthorized, "no-auth-provided"}
+ {SJID, Pass} ->
+ {HostOfRule, AccessRule} = get_acl_rule(RPath, Method),
+ case jlib:string_to_jid(SJID) of
+ error -> {unauthorized, <<"badformed-jid">>};
+ #jid{user = <<"">>, server = User} ->
+ get_auth_account(HostOfRule, AccessRule, User, HostHTTP,
+ Pass);
+ #jid{user = User, server = Server} ->
+ get_auth_account(HostOfRule, AccessRule, User, Server,
+ Pass)
+ end;
+ undefined -> {unauthorized, <<"no-auth-provided">>}
end.
-get_auth_account(HostOfRule, AccessRule, User, Server, Pass) ->
+get_auth_account(HostOfRule, AccessRule, User, Server,
+ Pass) ->
case ejabberd_auth:check_password(User, Server, Pass) of
- true ->
- case is_acl_match(HostOfRule, AccessRule,
- jlib:make_jid(User, Server, "")) of
- false ->
- {unauthorized, "unprivileged-account"};
- true ->
- {ok, {User, Server}}
- end;
- false ->
- case ejabberd_auth:is_user_exists(User, Server) of
- true ->
- {unauthorized, "bad-password"};
- false ->
- {unauthorized, "inexistent-account"}
- end
+ true ->
+ case is_acl_match(HostOfRule, AccessRule,
+ jlib:make_jid(User, Server, <<"">>))
+ of
+ false -> {unauthorized, <<"unprivileged-account">>};
+ true -> {ok, {User, Server}}
+ end;
+ false ->
+ case ejabberd_auth:is_user_exists(User, Server) of
+ true -> {unauthorized, <<"bad-password">>};
+ false -> {unauthorized, <<"inexistent-account">>}
+ end
end.
%%%==================================
@@ -271,1210 +279,1002 @@ get_auth_account(HostOfRule, AccessRule, User, Server, Pass) ->
make_xhtml(Els, Host, Lang, JID) ->
make_xhtml(Els, Host, cluster, Lang, JID).
-%% @spec (Els, Host, Node, Lang, JID) -> {200, [html], xmlelement()}
-%% where Host = global | string()
-%% Node = cluster | atom()
-%% JID = jid()
make_xhtml(Els, Host, Node, Lang, JID) ->
- Base = get_base_path(Host, cluster), %% Enforcing 'cluster' on purpose here
+ Base = get_base_path(Host, cluster),
MenuItems = make_navigation(Host, Node, Lang, JID),
{200, [html],
- {xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"},
- {"xml:lang", Lang},
- {"lang", Lang}],
- [{xmlelement, "head", [],
- [?XCT("title", "ejabberd Web Admin"),
- {xmlelement, "meta", [{"http-equiv", "Content-Type"},
- {"content", "text/html; charset=utf-8"}], []},
- {xmlelement, "script", [{"src", Base ++ "/additions.js"},
- {"type", "text/javascript"}], [?C(" ")]},
- {xmlelement, "link", [{"href", Base ++ "favicon.ico"},
- {"type", "image/x-icon"},
- {"rel", "shortcut icon"}], []},
- {xmlelement, "link", [{"href", Base ++ "style.css"},
- {"type", "text/css"},
- {"rel", "stylesheet"}], []}]},
- ?XE("body",
- [?XAE("div",
- [{"id", "container"}],
- [?XAE("div",
- [{"id", "header"}],
- [?XE("h1",
- [?ACT("/admin/", "ejabberd Web Admin")]
- )]),
- ?XAE("div",
- [{"id", "navigation"}],
- [?XE("ul",
- MenuItems
- )]),
- ?XAE("div",
- [{"id", "content"}],
- Els),
- ?XAE("div",
- [{"id", "clearcopyright"}],
- [{xmlcdata, ""}])]),
- ?XAE("div",
- [{"id", "copyrightouter"}],
- [?XAE("div",
- [{"id", "copyright"}],
- [?XC("p",
- "ejabberd (c) 2002-2012 ProcessOne")
- ])])])
- ]}}.
-
-get_base_path(global, cluster) -> "/admin/";
-get_base_path(Host, cluster) -> "/admin/server/" ++ Host ++ "/";
-get_base_path(global, Node) -> "/admin/node/" ++ atom_to_list(Node) ++ "/";
-get_base_path(Host, Node) -> "/admin/server/" ++ Host ++ "/node/" ++ atom_to_list(Node) ++ "/".
+ #xmlel{name = <<"html">>,
+ attrs =
+ [{<<"xmlns">>, <<"http://www.w3.org/1999/xhtml">>},
+ {<<"xml:lang">>, Lang}, {<<"lang">>, Lang}],
+ children =
+ [#xmlel{name = <<"head">>, attrs = [],
+ children =
+ [?XCT(<<"title">>, <<"ejabberd Web Admin">>),
+ #xmlel{name = <<"meta">>,
+ attrs =
+ [{<<"http-equiv">>, <<"Content-Type">>},
+ {<<"content">>,
+ <<"text/html; charset=utf-8">>}],
+ children = []},
+ #xmlel{name = <<"script">>,
+ attrs =
+ [{<<"src">>,
+ <<Base/binary, "/additions.js">>},
+ {<<"type">>, <<"text/javascript">>}],
+ children = [?C(<<" ">>)]},
+ #xmlel{name = <<"link">>,
+ attrs =
+ [{<<"href">>,
+ <<Base/binary, "favicon.ico">>},
+ {<<"type">>, <<"image/x-icon">>},
+ {<<"rel">>, <<"shortcut icon">>}],
+ children = []},
+ #xmlel{name = <<"link">>,
+ attrs =
+ [{<<"href">>,
+ <<Base/binary, "style.css">>},
+ {<<"type">>, <<"text/css">>},
+ {<<"rel">>, <<"stylesheet">>}],
+ children = []}]},
+ ?XE(<<"body">>,
+ [?XAE(<<"div">>, [{<<"id">>, <<"container">>}],
+ [?XAE(<<"div">>, [{<<"id">>, <<"header">>}],
+ [?XE(<<"h1">>,
+ [?ACT(<<"/admin/">>,
+ <<"ejabberd Web Admin">>)])]),
+ ?XAE(<<"div">>, [{<<"id">>, <<"navigation">>}],
+ [?XE(<<"ul">>, MenuItems)]),
+ ?XAE(<<"div">>, [{<<"id">>, <<"content">>}], Els),
+ ?XAE(<<"div">>, [{<<"id">>, <<"clearcopyright">>}],
+ [{xmlcdata, <<"">>}])]),
+ ?XAE(<<"div">>, [{<<"id">>, <<"copyrightouter">>}],
+ [?XAE(<<"div">>, [{<<"id">>, <<"copyright">>}],
+ [?XC(<<"p">>,
+ <<"ejabberd (c) 2002-2012 ProcessOne">>)])])])]}}.
+
+get_base_path(global, cluster) -> <<"/admin/">>;
+get_base_path(Host, cluster) ->
+ <<"/admin/server/", Host/binary, "/">>;
+get_base_path(global, Node) ->
+ <<"/admin/node/",
+ (iolist_to_binary(atom_to_list(Node)))/binary, "/">>;
+get_base_path(Host, Node) ->
+ <<"/admin/server/", Host/binary, "/node/",
+ (iolist_to_binary(atom_to_list(Node)))/binary, "/">>.
%%%==================================
%%%% css & images
additions_js() ->
-"
-function selectAll() {
- for(i=0;i<document.forms[0].elements.length;i++)
- { var e = document.forms[0].elements[i];
- if(e.type == 'checkbox')
- { e.checked = true; }
- }
-}
-function unSelectAll() {
- for(i=0;i<document.forms[0].elements.length;i++)
- { var e = document.forms[0].elements[i];
- if(e.type == 'checkbox')
- { e.checked = false; }
- }
-}
-".
+ <<"\nfunction selectAll() {\n for(i=0;i<documen"
+ "t.forms[0].elements.length;i++)\n { "
+ "var e = document.forms[0].elements[i];\n "
+ " if(e.type == 'checkbox')\n { e.checked "
+ "= true; }\n }\n}\nfunction unSelectAll() "
+ "{\n for(i=0;i<document.forms[0].elements.len"
+ "gth;i++)\n { var e = document.forms[0].eleme"
+ "nts[i];\n if(e.type == 'checkbox')\n "
+ " { e.checked = false; }\n }\n}\n">>.
css(Host) ->
Base = get_base_path(Host, cluster),
- "
-html,body {
- background: white;
- margin: 0;
- padding: 0;
- height: 100%;
-}
-
-#container {
- padding: 0;
- margin: 0;
- min-height: 100%;
- height: 100%;
- margin-bottom: -30px;
-}
-
-html>body #container {
- height: auto;
-}
-
-#header h1 {
- width: 100%;
- height: 55px;
- padding: 0;
- margin: 0;
- background: transparent url(\"" ++ Base ++ "logo-fill.png\");
-}
-
-#header h1 a {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 55px;
- padding: 0;
- margin: 0;
- background: transparent url(\"" ++ Base ++ "logo.png\") no-repeat;
- display: block;
- text-indent: -700em;
-}
-
-#clearcopyright {
- display: block;
- width: 100%;
- height: 30px;
-}
-
-#copyrightouter {
- display: table;
- width: 100%;
- height: 30px;
-}
-
-#copyright {
- display: table-cell;
- vertical-align: bottom;
- width: 100%;
- height: 30px;
-}
-
-#copyright p {
- margin-left: 0;
- margin-right: 0;
- margin-top: 5px;
- margin-bottom: 0;
- padding-left: 0;
- padding-right: 0;
- padding-top: 1px;
- padding-bottom: 1px;
- width: 100%;
- color: #ffffff;
- background-color: #fe8a00;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 7pt;
- font-weight: bold;
- text-align: center;
-}
-
-#navigation ul {
- position: absolute;
- top: 65px;
- left: 0;
- padding: 0 1px 1px 1px;
- margin: 0;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 8pt;
- font-weight: bold;
- border-top: 1px solid #d47911;
- width: 17em;
-}
-
-#navigation ul li {
- list-style: none;
- margin: 0;
- text-align: left;
- display: inline;
-}
-
-#navigation ul li a {
- margin: 0;
- display: block;
- padding: 3px 6px 3px 9px;
- border-left: 1em solid #ffc78c;
- border-right: 1px solid #d47911;
- border-bottom: 1px solid #d47911;
- background: #ffe3c9;
- text-decoration: none;
-}
-
-#navigation ul li a:link {
- color: #844;
-}
-
-#navigation ul li a:visited {
- color: #766;
-}
-
-#navigation ul li a:hover {
- border-color: #fc8800;
- color: #FFF;
- background: #332;
-}
-
-ul li #navhead a, ul li #navheadsub a, ul li #navheadsubsub a {
- text-align: center;
- border-top: 1px solid #d47911;
- border-bottom: 2px solid #d47911;
- background: #FED6A6;
-}
-
-#navheadsub, #navitemsub {
- border-left: 7px solid white;
- margin-left: 2px;
-}
-
-#navheadsubsub, #navitemsubsub {
- border-left: 14px solid white;
- margin-left: 4px;
-}
-
-#lastactivity li {
- font-weight: bold;
- border: 1px solid #d6760e;
- background-color: #fff2e8;
- padding: 2px;
- margin-bottom: -1px;
-}
-
-td.copy {
- color: #ffffff;
- background-color: #fe8a00;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 7pt;
- font-weight: bold;
- text-align: center;
-}
-
-input {
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 10pt;
- border: 1px solid #d6760e;
- color: #723202;
- background-color: #fff2e8;
- vertical-align: middle;
- margin-bottom: 0px;
- padding: 0.1em;
-}
-
-input[type=submit] {
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 8pt;
- font-weight: bold;
- color: #ffffff;
- background-color: #fe8a00;
- border: 1px solid #d6760e;
-}
-
-textarea {
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 10pt;
- border: 1px solid #d6760e;
- color: #723202;
- background-color: #fff2e8;
-}
-
-select {
- border: 1px solid #d6760e;
- color: #723202;
- background-color: #fff2e8;
- vertical-align: middle;
- margin-bottom: 0px;
- padding: 0.1em;
-}
-
-thead {
- color: #000000;
- background-color: #ffffff;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 10pt;
- font-weight: bold;
-}
-
-tr.head {
- color: #ffffff;
- background-color: #3b547a;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 9pt;
- font-weight: bold;
- text-align: center;
-}
-
-tr.oddraw {
- color: #412c75;
- background-color: #ccd4df;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 9pt;
- font-weight: normal;
- text-align: center;
-}
-
-tr.evenraw {
- color: #412c75;
- background-color: #dbe0e8;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 9pt;
- font-weight: normal;
- text-align: center;
-}
-
-td.leftheader {
- color: #412c75;
- background-color: #ccccc1;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 9pt;
- font-weight: bold;
- padding-left: 5px;
- padding-top: 2px;
- padding-bottom: 2px;
- margin-top: 0px;
- margin-bottom: 0px;
-}
-
-td.leftcontent {
- color: #000044;
- background-color: #e6e6df;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 7pt;
- font-weight: normal;
- padding-left: 5px;
- padding-right: 5px;
- padding-top: 2px;
- padding-bottom: 2px;
- margin-top: 0px;
- margin-bottom: 0px;
-}
-
-td.rightcontent {
- color: #000044;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 10pt;
- font-weight: normal;
- text-align: justify;
- padding-left: 10px;
- padding-right: 10px;
- padding-bottom: 5px;
-}
-
-
-h1 {
- color: #000044;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 14pt;
- font-weight: bold;
- text-align: center;
- padding-top: 2px;
- padding-bottom: 2px;
- margin-top: 0px;
- margin-bottom: 0px;
-}
-
-h2 {
- color: #000044;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 12pt;
- font-weight: bold;
- text-align: center;
- padding-top: 2px;
- padding-bottom: 2px;
- margin-top: 0px;
- margin-bottom: 0px;
-}
-
-h3 {
- color: #000044;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 10pt;
- font-weight: bold;
- text-align: left;
- padding-top: 20px;
- padding-bottom: 2px;
- margin-top: 0px;
- margin-bottom: 0px;
-}
-
-#content a:link {
- color: #990000;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 10pt;
- font-weight: bold;
- text-decoration: underline;
-}
-#content a:visited {
- color: #990000;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 10pt;
- font-weight: bold;
- text-decoration: underline;
-}
-#content a:hover {
- color: #cc6600;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 10pt;
- font-weight: bold;
- text-decoration: underline;
-}
-
-
-#content ul li {
- list-style-type: disc;
- font-size: 10pt;
- /*font-size: 7pt;*/
- padding-left: 10px;
-}
-
-#content ul.nolistyle>li {
- list-style-type: none;
-}
-
-#content li.big {
- font-size: 10pt;
-}
-
-#content {
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 10pt;
- padding-left: 17em;
- padding-top: 5px;
-}
-
-div.guidelink {
- text-align: right;
- padding-right: 1em;
-}
-
-table.withtextareas>tbody>tr>td {
- vertical-align: top;
-}
-
-p.result {
- border: 1px;
- border-style: dashed;
- border-color: #FE8A02;
- padding: 1em;
- margin-right: 1em;
- background: #FFE3C9;
-}
-
-*.alignright {
- font-size: 10pt;
- text-align: right;
-}
-
-".
+ <<"\nhtml,body {\n background: white;\n "
+ " margin: 0;\n padding: 0;\n height: "
+ "100%;\n}\n\n#container {\n padding: "
+ "0;\n margin: 0;\n min-height: 100%;\n "
+ " height: 100%;\n margin-bottom: -30px;\n}\n\n"
+ "html>body #container {\n height: auto;\n}\n\n"
+ "#header h1 {\n width: 100%;\n height: "
+ "55px;\n padding: 0;\n margin: 0;\n "
+ " background: transparent url(\"",
+ Base/binary,
+ "logo-fill.png\");\n}\n\n#header h1 a "
+ "{\n position: absolute;\n top: 0;\n "
+ " left: 0;\n width: 100%;\n height: "
+ "55px;\n padding: 0;\n margin: 0;\n "
+ " background: transparent url(\"",
+ Base/binary,
+ "logo.png\") no-repeat;\n display: block;\n "
+ " text-indent: -700em;\n}\n\n#clearcopyright "
+ "{\n display: block;\n width: 100%;\n "
+ " height: 30px;\n}\n\n#copyrightouter "
+ "{\n display: table;\n width: 100%;\n "
+ " height: 30px;\n}\n\n#copyright {\n "
+ " display: table-cell;\n vertical-align: "
+ "bottom;\n width: 100%;\n height: 30px;\n}\n\n"
+ "#copyright p {\n margin-left: 0;\n "
+ " margin-right: 0;\n margin-top: 5px;\n "
+ " margin-bottom: 0;\n padding-left: "
+ "0;\n padding-right: 0;\n padding-top: "
+ "1px;\n padding-bottom: 1px;\n width: "
+ "100%;\n color: #ffffff;\n background-color: "
+ "#fe8a00;\n font-family: Verdana, Arial, "
+ "Helvetica, sans-serif; \n font-size: "
+ "7pt;\n font-weight: bold;\n text-align: "
+ "center;\n}\n\n#navigation ul {\n position: "
+ "absolute;\n top: 65px;\n left: 0;\n "
+ " padding: 0 1px 1px 1px;\n margin: "
+ "0;\n font-family: Verdana, Arial, Helvetica, "
+ "sans-serif; \n font-size: 8pt;\n font-weigh"
+ "t: bold;\n border-top: 1px solid #d47911;\n "
+ " width: 17em;\n}\n\n#navigation ul li "
+ "{\n list-style: none;\n margin: 0;\n "
+ " text-align: left;\n display: inline;\n}\n\n"
+ "#navigation ul li a {\n margin: 0;\n "
+ " display: block;\n padding: 3px 6px "
+ "3px 9px;\n border-left: 1em solid #ffc78c;\n "
+ " border-right: 1px solid #d47911;\n "
+ " border-bottom: 1px solid #d47911;\n "
+ " background: #ffe3c9;\n text-decoration: "
+ "none;\n}\n\n#navigation ul li a:link "
+ "{\n color: #844;\n}\n\n#navigation "
+ "ul li a:visited {\n color: #766;\n}\n\n#navig"
+ "ation ul li a:hover {\n border-color: "
+ "#fc8800;\n color: #FFF;\n background: "
+ "#332;\n}\n\nul li #navhead a, ul li "
+ "#navheadsub a, ul li #navheadsubsub "
+ "a {\n text-align: center;\n border-top: "
+ "1px solid #d47911;\n border-bottom: "
+ "2px solid #d47911;\n background: #FED6A6;\n}\n\n"
+ "#navheadsub, #navitemsub {\n border-left: "
+ "7px solid white;\n margin-left: 2px;\n}\n\n#"
+ "navheadsubsub, #navitemsubsub {\n border-lef"
+ "t: 14px solid white;\n margin-left: "
+ "4px;\n}\n\n#lastactivity li {\n font-weight: "
+ "bold;\n border: 1px solid #d6760e;\n "
+ " background-color: #fff2e8;\n padding: "
+ "2px;\n margin-bottom: -1px;\n}\n\ntd.copy "
+ "{\n color: #ffffff;\n background-color: "
+ "#fe8a00;\n font-family: Verdana, Arial, "
+ "Helvetica, sans-serif; \n font-size: "
+ "7pt;\n font-weight: bold;\n text-align: "
+ "center;\n}\n\ninput {\n font-family: "
+ "Verdana, Arial, Helvetica, sans-serif; "
+ "\n font-size: 10pt;\n border: 1px "
+ "solid #d6760e;\n color: #723202;\n "
+ " background-color: #fff2e8;\n vertical-align"
+ ": middle;\n margin-bottom: 0px;\n "
+ "padding: 0.1em;\n}\n\ninput[type=submit] "
+ "{\n font-family: Verdana, Arial, Helvetica, "
+ "sans-serif; \n font-size: 8pt;\n font-weigh"
+ "t: bold;\n color: #ffffff;\n background-col"
+ "or: #fe8a00;\n border: 1px solid #d6760e;\n}\n\n"
+ "textarea {\n font-family: Verdana, "
+ "Arial, Helvetica, sans-serif; \n font-size: "
+ "10pt;\n border: 1px solid #d6760e;\n "
+ " color: #723202;\n background-color: "
+ "#fff2e8;\n}\n\nselect {\n border: 1px "
+ "solid #d6760e;\n color: #723202;\n "
+ " background-color: #fff2e8;\n vertical-align"
+ ": middle;\n margin-bottom: 0px; \n "
+ " padding: 0.1em;\n}\n\nthead {\n color: "
+ "#000000;\n background-color: #ffffff;\n "
+ " font-family: Verdana, Arial, Helvetica, "
+ "sans-serif; \n font-size: 10pt;\n "
+ "font-weight: bold;\n}\n\ntr.head {\n "
+ " color: #ffffff;\n background-color: "
+ "#3b547a;\n font-family: Verdana, Arial, "
+ "Helvetica, sans-serif; \n font-size: "
+ "9pt;\n font-weight: bold;\n text-align: "
+ "center;\n}\n\ntr.oddraw {\n color: "
+ "#412c75;\n background-color: #ccd4df;\n "
+ " font-family: Verdana, Arial, Helvetica, "
+ "sans-serif; \n font-size: 9pt;\n font-weigh"
+ "t: normal;\n text-align: center;\n}\n\ntr.ev"
+ "enraw {\n color: #412c75;\n background-colo"
+ "r: #dbe0e8;\n font-family: Verdana, "
+ "Arial, Helvetica, sans-serif; \n font-size: "
+ "9pt;\n font-weight: normal;\n text-align: "
+ "center;\n}\n\ntd.leftheader {\n color: "
+ "#412c75;\n background-color: #ccccc1;\n "
+ " font-family: Verdana, Arial, Helvetica, "
+ "sans-serif; \n font-size: 9pt;\n font-weigh"
+ "t: bold;\n padding-left: 5px;\n padding-top"
+ ": 2px;\n padding-bottom: 2px;\n margin-top: "
+ "0px;\n margin-bottom: 0px;\n}\n\ntd.leftcont"
+ "ent {\n color: #000044;\n background-color: "
+ "#e6e6df;\n font-family: Verdana, Arial, "
+ "Helvetica, sans-serif; \n font-size: "
+ "7pt;\n font-weight: normal;\n padding-left: "
+ "5px;\n padding-right: 5px;\n padding-top: "
+ "2px;\n padding-bottom: 2px;\n margin-top: "
+ "0px;\n margin-bottom: 0px;\n}\n\ntd.rightcon"
+ "tent {\n color: #000044;\n font-family: "
+ "Verdana, Arial, Helvetica, sans-serif; "
+ "\n font-size: 10pt;\n font-weight: "
+ "normal;\n text-align: justify;\n padding-le"
+ "ft: 10px;\n padding-right: 10px;\n "
+ " padding-bottom: 5px;\n}\n\n\nh1 {\n "
+ " color: #000044;\n font-family: Verdana, "
+ "Arial, Helvetica, sans-serif; \n font-size: "
+ "14pt;\n font-weight: bold;\n text-align: "
+ "center;\n padding-top: 2px;\n padding-botto"
+ "m: 2px;\n margin-top: 0px;\n margin-bottom: "
+ "0px;\n}\n\nh2 {\n color: #000044;\n "
+ " font-family: Verdana, Arial, Helvetica, "
+ "sans-serif; \n font-size: 12pt;\n "
+ "font-weight: bold;\n text-align: center;\n "
+ " padding-top: 2px;\n padding-bottom: "
+ "2px;\n margin-top: 0px;\n margin-bottom: "
+ "0px;\n}\n\nh3 {\n color: #000044;\n "
+ " font-family: Verdana, Arial, Helvetica, "
+ "sans-serif; \n font-size: 10pt;\n "
+ "font-weight: bold;\n text-align: left;\n "
+ " padding-top: 20px;\n padding-bottom: "
+ "2px;\n margin-top: 0px;\n margin-bottom: "
+ "0px;\n}\n\n#content a:link {\n color: "
+ "#990000; \n font-family: Verdana, Arial, "
+ "Helvetica, sans-serif; \n font-size: "
+ "10pt;\n font-weight: bold;\n text-decoratio"
+ "n: underline;\n}\n#content a:visited "
+ "{\n color: #990000; \n font-family: "
+ "Verdana, Arial, Helvetica, sans-serif; "
+ "\n font-size: 10pt;\n font-weight: "
+ "bold;\n text-decoration: underline;\n}\n#con"
+ "tent a:hover {\n color: #cc6600; \n "
+ " font-family: Verdana, Arial, Helvetica, "
+ "sans-serif; \n font-size: 10pt;\n "
+ "font-weight: bold;\n text-decoration: "
+ "underline;\n}\n\n\n#content ul li {\n "
+ " list-style-type: disc;\n font-size: "
+ "10pt;\n /*font-size: 7pt;*/\n padding-left: "
+ "10px;\n}\n\n#content ul.nolistyle>li "
+ "{\n list-style-type: none;\n}\n\n#content "
+ "li.big {\n font-size: 10pt;\n}\n\n#content "
+ "{\n font-family: Verdana, Arial, Helvetica, "
+ "sans-serif; \n font-size: 10pt;\n "
+ "padding-left: 17em;\n padding-top: "
+ "5px;\n}\n\ndiv.guidelink {\n text-align: "
+ "right;\n padding-right: 1em;\n}\n\ntable.wit"
+ "htextareas>tbody>tr>td {\n vertical-align: "
+ "top;\n}\n\np.result {\n border: 1px;\n "
+ " border-style: dashed;\n border-color: "
+ "#FE8A02;\n padding: 1em;\n margin-right: "
+ "1em;\n background: #FFE3C9;\n}\n\n*.alignrig"
+ "ht {\n font-size: 10pt;\n text-align: "
+ "right;\n}\n\n">>.
favicon() ->
- jlib:decode_base64(
- "AAABAAEAEBAQAAEABAAoAQAAFgAAACgAAAAQAAAAIAAAAAEABAAAAAAAAAA"
- "AAAAAAAAAAAAAAAAAAAAAAAAJf+cAAIPsAAGC8gAVhecAAIr8ACiR7wBBmO"
- "cAUKPsAFun8ABhqeoAgLryAJLB8ACz1PcAv9r7AMvi+gAAAAAAAgICARMhI"
- "CAkJCQkQkFCQgICN2d2cSMgJCRevdvVQkICAlqYh5MgICQkXrRCQkJCMgI7"
- "kiAjICAUFF2swkFBQRQUXazCQUFBAgI7kiAgICAkJF60QkJCQgICOpiHkyA"
- "gJCRevdvlQkICAjdndnMgICQkJCRCQkJCAgICARAgICAAAAAAAAAAAAAAAA"
- "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
- "AAAAAAAAAAA").
+ jlib:decode_base64(<<"AAABAAEAEBAQAAEABAAoAQAAFgAAACgAAAAQAAAAIAAAA"
+ "AEABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJf+cAAI"
+ "PsAAGC8gAVhecAAIr8ACiR7wBBmOcAUKPsAFun8ABhqeo"
+ "AgLryAJLB8ACz1PcAv9r7AMvi+gAAAAAAAgICARMhICAk"
+ "JCQkQkFCQgICN2d2cSMgJCRevdvVQkICAlqYh5MgICQkX"
+ "rRCQkJCMgI7kiAjICAUFF2swkFBQRQUXazCQUFBAgI7ki"
+ "AgICAkJF60QkJCQgICOpiHkyAgJCRevdvlQkICAjdndnM"
+ "gICQkJCRCQkJCAgICARAgICAAAAAAAAAAAAAAAAAAAAAA"
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+ "AAAAAAAAAAAAAAAAAAA">>).
logo() ->
- jlib:decode_base64(
- "iVBORw0KGgoAAAANSUhEUgAAAVcAAAA3CAMAAACPbPnEAAAAAXNSR0IArs4c"
- "6QAAAEtQTFRFcTIA1XcE/YsA/40E/pIH/JYc/5kg/54i/KIu/6U6/apE/61H"
- "/61P/bFX/7Vh/bda/rpq/L5s/8J2/cJ8/8qI/86Y/9aj/9mt/+bJ7EGiPwAA"
- "AZRJREFUeNrt28lug0AQhGHajrPv+/s/aVwpDlgE0gQ3tqO/DhxihMg33VJ7"
- "JmmCVKSJlVJ4bZQ93Jl/zjJv+8tzcMUVV1xxLXIlRfPAZptYrbf5YeW618PW"
- "yvG8w/g9ZwquuJ6Y6+bbdY0rrifhSmrmgUulVXbVDq3H39Zy6Cf9+8c7JNM/"
- "mXeY8+SMRmuIK6644oprkSupmQdulLhQdup1qJKmrmWmVpb5NN9LUyddu7nn"
- "LYkrrrjiimuVK6mZB+6VuFbiXJk8v/bnv0PVa+Yd5tdr/x7vCfqbgPsfV1xx"
- "xRXXKldSMw+8KPGgxJWyU7WZE538p0vOr/lOm/q7dPf+bOVKvVXiUcEVV1xx"
- "xbXMldTMA29KPCtxp7T6XpvxE6/9nm/l987mnG9l5u/8jO4Ot9uTEq8Krrji"
- "iiuuZa6kZh74UFpli3sO61btMfyHyWGv/RMs7wB67ne32/BdwRVXXHHFtcyV"
- "1MwDn0qrbHHvyPT/Dsarla/R/1GpQydYPhf0bqC/A7jz7YkrrrjiimuVK6nI"
- "F5dWoNvcLcs/AAAAAElFTkSuQmCC").
+ jlib:decode_base64(<<"iVBORw0KGgoAAAANSUhEUgAAAVcAAAA3CAMAAACPbPnEA"
+ "AAAAXNSR0IArs4c6QAAAEtQTFRFcTIA1XcE/YsA/40E/p"
+ "IH/JYc/5kg/54i/KIu/6U6/apE/61H/61P/bFX/7Vh/bd"
+ "a/rpq/L5s/8J2/cJ8/8qI/86Y/9aj/9mt/+bJ7EGiPwAA"
+ "AZRJREFUeNrt28lug0AQhGHajrPv+/s/aVwpDlgE0gQ3t"
+ "qO/DhxihMg33VJ7JmmCVKSJlVJ4bZQ93Jl/zjJv+8tzcM"
+ "UVV1xxLXIlRfPAZptYrbf5YeW618PWyvG8w/g9ZwquuJ6"
+ "Y6+bbdY0rrifhSmrmgUulVXbVDq3H39Zy6Cf9+8c7JNM/"
+ "mXeY8+SMRmuIK6644oprkSupmQdulLhQdup1qJKmrmWmV"
+ "pb5NN9LUyddu7nnLYkrrrjiimuVK6mZB+6VuFbiXJk8v/"
+ "bnv0PVa+Yd5tdr/x7vCfqbgPsfV1xxxRXXKldSMw+8KPG"
+ "gxJWyU7WZE538p0vOr/lOm/q7dPf+bOVKvVXiUcEVV1xx"
+ "xbXMldTMA29KPCtxp7T6XpvxE6/9nm/l987mnG9l5u/8j"
+ "O4Ot9uTEq8KrrjiiiuuZa6kZh74UFpli3sO61btMfyHyW"
+ "Gv/RMs7wB67ne32/BdwRVXXHHFtcyV1MwDn0qrbHHvyPT"
+ "/Dsarla/R/1GpQydYPhf0bqC/A7jz7YkrrrjiimuVK6nI"
+ "F5dWoNvcLcs/AAAAAElFTkSuQmCC">>).
logo_fill() ->
- jlib:decode_base64(
- "iVBORw0KGgoAAAANSUhEUgAAAAYAAAA3BAMAAADdxCZzAAAAAXNSR0IArs4c"
- "6QAAAB5QTFRF1nYO/ooC/o4O/pIS/p4q/q5K/rpq/sqM/tam/ubGzn/S/AAA"
- "AEFJREFUCNdlw0sRwCAQBUE+gSRHLGABC1jAAhbWAhZwC+88XdXOXb4UlFAr"
- "SmwN5ekdJY2BkudEec1QvrVQ/r3xOlK9HsTvertmAAAAAElFTkSuQmCC").
+ jlib:decode_base64(<<"iVBORw0KGgoAAAANSUhEUgAAAAYAAAA3BAMAAADdxCZzA"
+ "AAAAXNSR0IArs4c6QAAAB5QTFRF1nYO/ooC/o4O/pIS/p"
+ "4q/q5K/rpq/sqM/tam/ubGzn/S/AAAAEFJREFUCNdlw0s"
+ "RwCAQBUE+gSRHLGABC1jAAhbWAhZwC+88XdXOXb4UlFAr"
+ "SmwN5ekdJY2BkudEec1QvrVQ/r3xOlK9HsTvertmAAAAA"
+ "ElFTkSuQmCC">>).
%%%==================================
%%%% process_admin
process_admin(global,
- #request{path = [],
- auth = {_, _, AJID},
+ #request{path = [], auth = {_, _, AJID},
lang = Lang}) ->
- %%Base = get_base_path(global, cluster),
- make_xhtml(?H1GL(?T("Administration"), "toc", "Contents") ++
- [?XE("ul",
- [?LI([?ACT(MIU, MIN)]) || {MIU, MIN} <- get_menu_items(global, cluster, Lang, AJID)]
- )
- ], global, Lang, AJID);
-
+ make_xhtml((?H1GL((?T(<<"Administration">>)), <<"toc">>,
+ <<"Contents">>))
+ ++
+ [?XE(<<"ul">>,
+ [?LI([?ACT(MIU, MIN)])
+ || {MIU, MIN}
+ <- get_menu_items(global, cluster, Lang, AJID)])],
+ global, Lang, AJID);
process_admin(Host,
- #request{path = [],
- auth = {_, _Auth, AJID},
+ #request{path = [], auth = {_, _Auth, AJID},
lang = Lang}) ->
- %%Base = get_base_path(Host, cluster),
- make_xhtml([?XCT("h1", "Administration"),
- ?XE("ul",
- [?LI([?ACT(MIU, MIN)]) || {MIU, MIN} <- get_menu_items(Host, cluster, Lang, AJID)]
- )
- ], Host, Lang, AJID);
-
-process_admin(Host, #request{path = ["style.css"]}) ->
- {200, [{"Content-Type", "text/css"}, last_modified(), cache_control_public()], css(Host)};
-
-process_admin(_Host, #request{path = ["favicon.ico"]}) ->
- {200, [{"Content-Type", "image/x-icon"}, last_modified(), cache_control_public()], favicon()};
-
-process_admin(_Host, #request{path = ["logo.png"]}) ->
- {200, [{"Content-Type", "image/png"}, last_modified(), cache_control_public()], logo()};
-
-process_admin(_Host, #request{path = ["logo-fill.png"]}) ->
- {200, [{"Content-Type", "image/png"}, last_modified(), cache_control_public()], logo_fill()};
-
-process_admin(_Host, #request{path = ["additions.js"]}) ->
- {200, [{"Content-Type", "text/javascript"}, last_modified(), cache_control_public()], additions_js()};
-
+ make_xhtml([?XCT(<<"h1">>, <<"Administration">>),
+ ?XE(<<"ul">>,
+ [?LI([?ACT(MIU, MIN)])
+ || {MIU, MIN}
+ <- get_menu_items(Host, cluster, Lang, AJID)])],
+ Host, Lang, AJID);
process_admin(Host,
- #request{path = ["acls-raw"],
- q = Query,
- auth = {_, _Auth, AJID},
- lang = Lang}) ->
-
- Res = case lists:keysearch("acls", 1, Query) of
- {value, {_, String}} ->
- case erl_scan:string(String) of
- {ok, Tokens, _} ->
- case erl_parse:parse_term(Tokens) of
- {ok, NewACLs} ->
- case acl:add_list(Host, NewACLs, true) of
- ok ->
- ok;
- _ ->
- error
- end;
- _ ->
- error
- end;
- _ ->
- error
- end;
- _ ->
- nothing
+ #request{path = [<<"style.css">>]}) ->
+ {200,
+ [{<<"Content-Type">>, <<"text/css">>}, last_modified(),
+ cache_control_public()],
+ css(Host)};
+process_admin(_Host,
+ #request{path = [<<"favicon.ico">>]}) ->
+ {200,
+ [{<<"Content-Type">>, <<"image/x-icon">>},
+ last_modified(), cache_control_public()],
+ favicon()};
+process_admin(_Host,
+ #request{path = [<<"logo.png">>]}) ->
+ {200,
+ [{<<"Content-Type">>, <<"image/png">>}, last_modified(),
+ cache_control_public()],
+ logo()};
+process_admin(_Host,
+ #request{path = [<<"logo-fill.png">>]}) ->
+ {200,
+ [{<<"Content-Type">>, <<"image/png">>}, last_modified(),
+ cache_control_public()],
+ logo_fill()};
+process_admin(_Host,
+ #request{path = [<<"additions.js">>]}) ->
+ {200,
+ [{<<"Content-Type">>, <<"text/javascript">>},
+ last_modified(), cache_control_public()],
+ additions_js()};
+process_admin(Host,
+ #request{path = [<<"acls-raw">>], q = Query,
+ auth = {_, _Auth, AJID}, lang = Lang}) ->
+ Res = case lists:keysearch(<<"acls">>, 1, Query) of
+ {value, {_, String}} ->
+ case erl_scan:string(binary_to_list(String)) of
+ {ok, Tokens, _} ->
+ case erl_parse:parse_term(Tokens) of
+ {ok, NewACLs} ->
+ case acl:add_list(Host, NewACLs, true) of
+ ok -> ok;
+ _ -> error
+ end;
+ _ -> error
+ end;
+ _ -> error
+ end;
+ _ -> nothing
end,
- ACLs = lists:keysort(2, ets:select(acl, [{{acl, {'$1', Host}, '$2'},
- [], [{{acl, '$1', '$2'}}]}])),
+ ACLs = lists:keysort(2,
+ ets:select(acl,
+ [{{acl, {'$1', Host}, '$2'}, [],
+ [{{acl, '$1', '$2'}}]}])),
{NumLines, ACLsP} = term_to_paragraph(ACLs, 80),
- make_xhtml(?H1GL(?T("Access Control Lists"), "ACLDefinition", "ACL Definition") ++
- case Res of
- ok -> [?XREST("Submitted")];
- error -> [?XREST("Bad format")];
+ make_xhtml((?H1GL((?T(<<"Access Control Lists">>)),
+ <<"ACLDefinition">>, <<"ACL Definition">>))
+ ++
+ case Res of
+ ok -> [?XREST(<<"Submitted">>)];
+ error -> [?XREST(<<"Bad format">>)];
nothing -> []
- end ++
- [?XAE("form", [{"action", ""}, {"method", "post"}],
- [?TEXTAREA("acls", integer_to_list(lists:max([16, NumLines])), "80", ACLsP++"."),
- ?BR,
- ?INPUTT("submit", "submit", "Submit")
- ])
- ], Host, Lang, AJID);
-
+ end
+ ++
+ [?XAE(<<"form">>,
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ [?TEXTAREA(<<"acls">>,
+ (iolist_to_binary(integer_to_list(lists:max([16,
+ NumLines])))),
+ <<"80">>, <<(iolist_to_binary(ACLsP))/binary, ".">>),
+ ?BR,
+ ?INPUTT(<<"submit">>, <<"submit">>, <<"Submit">>)])],
+ Host, Lang, AJID);
process_admin(Host,
- #request{method = Method,
- path = ["acls"],
- auth = {_, _Auth, AJID},
- q = Query,
- lang = Lang}) ->
+ #request{method = Method, path = [<<"acls">>],
+ auth = {_, _Auth, AJID}, q = Query, lang = Lang}) ->
?DEBUG("query: ~p", [Query]),
Res = case Method of
- 'POST' ->
- case catch acl_parse_query(Host, Query) of
- {'EXIT', _} ->
- error;
- NewACLs ->
- ?INFO_MSG("NewACLs at ~s: ~p", [Host, NewACLs]),
- case acl:add_list(Host, NewACLs, true) of
- ok ->
- ?INFO_MSG("NewACLs: ok", []),
- ok;
- _ ->
- error
- end
- end;
- _ ->
- nothing
+ 'POST' ->
+ case catch acl_parse_query(Host, Query) of
+ {'EXIT', _} -> error;
+ NewACLs ->
+ ?INFO_MSG("NewACLs at ~s: ~p", [Host, NewACLs]),
+ case acl:add_list(Host, NewACLs, true) of
+ ok -> ?INFO_MSG("NewACLs: ok", []), ok;
+ _ -> error
+ end
+ end;
+ _ -> nothing
end,
- ACLs = lists:keysort(
- 2, ets:select(acl, [{{acl, {'$1', Host}, '$2'},
- [], [{{acl, '$1', '$2'}}]}])),
- make_xhtml(?H1GL(?T("Access Control Lists"), "ACLDefinition", "ACL Definition") ++
- case Res of
- ok -> [?XREST("Submitted")];
- error -> [?XREST("Bad format")];
+ ACLs = lists:keysort(2,
+ ets:select(acl,
+ [{{acl, {'$1', Host}, '$2'}, [],
+ [{{acl, '$1', '$2'}}]}])),
+ make_xhtml((?H1GL((?T(<<"Access Control Lists">>)),
+ <<"ACLDefinition">>, <<"ACL Definition">>))
+ ++
+ case Res of
+ ok -> [?XREST(<<"Submitted">>)];
+ error -> [?XREST(<<"Bad format">>)];
nothing -> []
- end ++
- [?XE("p", [?ACT("../acls-raw/", "Raw")])] ++
- [?XAE("form", [{"action", ""}, {"method", "post"}],
- [acls_to_xhtml(ACLs),
- ?BR,
- ?INPUTT("submit", "delete", "Delete Selected"),
- ?C(" "),
- ?INPUTT("submit", "submit", "Submit")
- ])
- ], Host, Lang, AJID);
-
+ end
+ ++
+ [?XE(<<"p">>, [?ACT(<<"../acls-raw/">>, <<"Raw">>)])] ++
+ [?XAE(<<"form">>,
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ [acls_to_xhtml(ACLs), ?BR,
+ ?INPUTT(<<"submit">>, <<"delete">>,
+ <<"Delete Selected">>),
+ ?C(<<" ">>),
+ ?INPUTT(<<"submit">>, <<"submit">>,
+ <<"Submit">>)])],
+ Host, Lang, AJID);
process_admin(Host,
- #request{path = ["access-raw"],
- auth = {_, _Auth, AJID},
- q = Query,
- lang = Lang}) ->
- SetAccess =
- fun(Rs) ->
- mnesia:transaction(
- fun() ->
- Os = mnesia:select(
- config,
- [{{config, {access, '$1', Host}, '$2'},
- [],
- ['$_']}]),
- lists:foreach(fun(O) ->
- mnesia:delete_object(O)
- end, Os),
- lists:foreach(
- fun({access, Name, Rules}) ->
- mnesia:write({config,
- {access, Name, Host},
- Rules})
- end, Rs)
- end)
- end,
- Res = case lists:keysearch("access", 1, Query) of
- {value, {_, String}} ->
- case erl_scan:string(String) of
- {ok, Tokens, _} ->
- case erl_parse:parse_term(Tokens) of
- {ok, Rs} ->
- case SetAccess(Rs) of
- {atomic, _} ->
- ok;
- _ ->
- error
- end;
- _ ->
- error
- end;
- _ ->
- error
- end;
- _ ->
- nothing
+ #request{path = [<<"access-raw">>],
+ auth = {_, _Auth, AJID}, q = Query, lang = Lang}) ->
+ SetAccess = fun (Rs) ->
+ mnesia:transaction(fun () ->
+ Os = mnesia:select(config,
+ [{{config,
+ {access,
+ '$1',
+ Host},
+ '$2'},
+ [],
+ ['$_']}]),
+ lists:foreach(fun (O) ->
+ mnesia:delete_object(O)
+ end,
+ Os),
+ lists:foreach(fun ({access,
+ Name,
+ Rules}) ->
+ mnesia:write({config,
+ {access,
+ Name,
+ Host},
+ Rules})
+ end,
+ Rs)
+ end)
+ end,
+ Res = case lists:keysearch(<<"access">>, 1, Query) of
+ {value, {_, String}} ->
+ case erl_scan:string(binary_to_list(String)) of
+ {ok, Tokens, _} ->
+ case erl_parse:parse_term(Tokens) of
+ {ok, Rs} ->
+ case SetAccess(Rs) of
+ {atomic, _} -> ok;
+ _ -> error
+ end;
+ _ -> error
+ end;
+ _ -> error
+ end;
+ _ -> nothing
end,
- Access =
- ets:select(config,
- [{{config, {access, '$1', Host}, '$2'},
- [],
- [{{access, '$1', '$2'}}]}]),
- {NumLines, AccessP} = term_to_paragraph(Access, 80),
- make_xhtml(?H1GL(?T("Access Rules"), "AccessRights", "Access Rights") ++
- case Res of
- ok -> [?XREST("Submitted")];
- error -> [?XREST("Bad format")];
+ Access = ets:select(config,
+ [{{config, {access, '$1', Host}, '$2'}, [],
+ [{{access, '$1', '$2'}}]}]),
+ {NumLines, AccessP} = term_to_paragraph(lists:keysort(2,Access), 80),
+ make_xhtml((?H1GL((?T(<<"Access Rules">>)),
+ <<"AccessRights">>, <<"Access Rights">>))
+ ++
+ case Res of
+ ok -> [?XREST(<<"Submitted">>)];
+ error -> [?XREST(<<"Bad format">>)];
nothing -> []
- end ++
- [?XAE("form", [{"action", ""}, {"method", "post"}],
- [?TEXTAREA("access", integer_to_list(lists:max([16, NumLines])), "80", AccessP++"."),
- ?BR,
- ?INPUTT("submit", "submit", "Submit")
- ])
- ], Host, Lang, AJID);
-
+ end
+ ++
+ [?XAE(<<"form">>,
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ [?TEXTAREA(<<"access">>,
+ (iolist_to_binary(integer_to_list(lists:max([16,
+ NumLines])))),
+ <<"80">>, <<(iolist_to_binary(AccessP))/binary, ".">>),
+ ?BR,
+ ?INPUTT(<<"submit">>, <<"submit">>, <<"Submit">>)])],
+ Host, Lang, AJID);
process_admin(Host,
- #request{method = Method,
- path = ["access"],
- q = Query,
- auth = {_, _Auth, AJID},
- lang = Lang}) ->
+ #request{method = Method, path = [<<"access">>],
+ q = Query, auth = {_, _Auth, AJID}, lang = Lang}) ->
?DEBUG("query: ~p", [Query]),
Res = case Method of
- 'POST' ->
- case catch access_parse_query(Host, Query) of
- {'EXIT', _} ->
- error;
- ok ->
- ok
- end;
- _ ->
- nothing
+ 'POST' ->
+ case catch access_parse_query(Host, Query) of
+ {'EXIT', _} -> error;
+ ok -> ok
+ end;
+ _ -> nothing
end,
- AccessRules =
- ets:select(config,
- [{{config, {access, '$1', Host}, '$2'},
- [],
- [{{access, '$1', '$2'}}]}]),
- make_xhtml(?H1GL(?T("Access Rules"), "AccessRights", "Access Rights") ++
- case Res of
- ok -> [?XREST("Submitted")];
- error -> [?XREST("Bad format")];
+ AccessRules = ets:select(config,
+ [{{config, {access, '$1', Host}, '$2'}, [],
+ [{{access, '$1', '$2'}}]}]),
+ make_xhtml((?H1GL((?T(<<"Access Rules">>)),
+ <<"AccessRights">>, <<"Access Rights">>))
+ ++
+ case Res of
+ ok -> [?XREST(<<"Submitted">>)];
+ error -> [?XREST(<<"Bad format">>)];
nothing -> []
- end ++
- [?XE("p", [?ACT("../access-raw/", "Raw")])] ++
- [?XAE("form", [{"action", ""}, {"method", "post"}],
- [access_rules_to_xhtml(AccessRules, Lang),
- ?BR,
- ?INPUTT("submit", "delete", "Delete Selected")
- ])
- ], Host, Lang, AJID);
-
+ end
+ ++
+ [?XE(<<"p">>, [?ACT(<<"../access-raw/">>, <<"Raw">>)])]
+ ++
+ [?XAE(<<"form">>,
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ [access_rules_to_xhtml(AccessRules, Lang), ?BR,
+ ?INPUTT(<<"submit">>, <<"delete">>,
+ <<"Delete Selected">>)])],
+ Host, Lang, AJID);
process_admin(Host,
- #request{path = ["access", SName],
- q = Query,
- auth = {_, _Auth, AJID},
- lang = Lang}) ->
+ #request{path = [<<"access">>, SName], q = Query,
+ auth = {_, _Auth, AJID}, lang = Lang}) ->
?DEBUG("query: ~p", [Query]),
- Name = list_to_atom(SName),
- Res = case lists:keysearch("rules", 1, Query) of
- {value, {_, String}} ->
- case parse_access_rule(String) of
- {ok, Rs} ->
- ejabberd_config:add_global_option(
- {access, Name, Host}, Rs),
- ok;
- _ ->
- error
- end;
- _ ->
- nothing
+ Name = jlib:binary_to_atom(SName),
+ Res = case lists:keysearch(<<"rules">>, 1, Query) of
+ {value, {_, String}} ->
+ case parse_access_rule(String) of
+ {ok, Rs} ->
+ ejabberd_config:add_global_option({access, Name, Host},
+ Rs),
+ ok;
+ _ -> error
+ end;
+ _ -> nothing
end,
- Rules = case ejabberd_config:get_global_option({access, Name, Host}) of
- undefined ->
- [];
- Rs1 ->
- Rs1
+ Rules = case ejabberd_config:get_global_option(
+ {access, Name, Host}, fun(V) -> V end)
+ of
+ undefined -> [];
+ Rs1 -> Rs1
end,
- make_xhtml([?XC("h1",
- io_lib:format(?T("~s access rule configuration"), [SName]))] ++
- case Res of
- ok -> [?XREST("Submitted")];
- error -> [?XREST("Bad format")];
+ make_xhtml([?XC(<<"h1">>,
+ list_to_binary(io_lib:format(
+ ?T(<<"~s access rule configuration">>),
+ [SName])))]
+ ++
+ case Res of
+ ok -> [?XREST(<<"Submitted">>)];
+ error -> [?XREST(<<"Bad format">>)];
nothing -> []
- end ++
- [?XAE("form", [{"action", ""}, {"method", "post"}],
- [access_rule_to_xhtml(Rules),
- ?BR,
- ?INPUTT("submit", "submit", "Submit")
- ])
- ], Host, Lang, AJID);
-
+ end
+ ++
+ [?XAE(<<"form">>,
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ [access_rule_to_xhtml(Rules), ?BR,
+ ?INPUTT(<<"submit">>, <<"submit">>, <<"Submit">>)])],
+ Host, Lang, AJID);
process_admin(global,
- #request{path = ["vhosts"],
- auth = {_, _Auth, AJID},
+ #request{path = [<<"vhosts">>], auth = {_, _Auth, AJID},
lang = Lang}) ->
Res = list_vhosts(Lang, AJID),
- make_xhtml(?H1GL(?T("Virtual Hosts"), "virtualhost", "Virtual Hosting") ++ Res, global, Lang, AJID);
-
+ make_xhtml((?H1GL((?T(<<"Virtual Hosts">>)),
+ <<"virtualhost">>, <<"Virtual Hosting">>))
+ ++ Res,
+ global, Lang, AJID);
process_admin(Host,
- #request{path = ["users"],
- q = Query,
- auth = {_, _Auth, AJID},
- lang = Lang}) when is_list(Host) ->
+ #request{path = [<<"users">>], q = Query,
+ auth = {_, _Auth, AJID}, lang = Lang})
+ when is_binary(Host) ->
Res = list_users(Host, Query, Lang, fun url_func/1),
- make_xhtml([?XCT("h1", "Users")] ++ Res, Host, Lang, AJID);
-
+ make_xhtml([?XCT(<<"h1">>, <<"Users">>)] ++ Res, Host,
+ Lang, AJID);
process_admin(Host,
- #request{path = ["users", Diap],
- auth = {_, _Auth, AJID},
- lang = Lang}) when is_list(Host) ->
- Res = list_users_in_diapason(Host, Diap, Lang, fun url_func/1),
- make_xhtml([?XCT("h1", "Users")] ++ Res, Host, Lang, AJID);
-
+ #request{path = [<<"users">>, Diap],
+ auth = {_, _Auth, AJID}, lang = Lang})
+ when is_binary(Host) ->
+ Res = list_users_in_diapason(Host, Diap, Lang,
+ fun url_func/1),
+ make_xhtml([?XCT(<<"h1">>, <<"Users">>)] ++ Res, Host,
+ Lang, AJID);
process_admin(Host,
- #request{
- path = ["online-users"],
- auth = {_, _Auth, AJID},
- lang = Lang}) when is_list(Host) ->
+ #request{path = [<<"online-users">>],
+ auth = {_, _Auth, AJID}, lang = Lang})
+ when is_binary(Host) ->
Res = list_online_users(Host, Lang),
- make_xhtml([?XCT("h1", "Online Users")] ++ Res, Host, Lang, AJID);
-
+ make_xhtml([?XCT(<<"h1">>, <<"Online Users">>)] ++ Res,
+ Host, Lang, AJID);
process_admin(Host,
- #request{path = ["last-activity"],
- auth = {_, _Auth, AJID},
- q = Query,
- lang = Lang}) when is_list(Host) ->
+ #request{path = [<<"last-activity">>],
+ auth = {_, _Auth, AJID}, q = Query, lang = Lang})
+ when is_binary(Host) ->
?DEBUG("query: ~p", [Query]),
- Month = case lists:keysearch("period", 1, Query) of
- {value, {_, Val}} ->
- Val;
- _ ->
- "month"
+ Month = case lists:keysearch(<<"period">>, 1, Query) of
+ {value, {_, Val}} -> Val;
+ _ -> <<"month">>
end,
- Res = case lists:keysearch("ordinary", 1, Query) of
- {value, {_, _}} ->
- list_last_activity(Host, Lang, false, Month);
- _ ->
- list_last_activity(Host, Lang, true, Month)
+ Res = case lists:keysearch(<<"ordinary">>, 1, Query) of
+ {value, {_, _}} ->
+ list_last_activity(Host, Lang, false, Month);
+ _ -> list_last_activity(Host, Lang, true, Month)
end,
- make_xhtml([?XCT("h1", "Users Last Activity")] ++
- [?XAE("form", [{"action", ""}, {"method", "post"}],
- [?CT("Period: "),
- ?XAE("select", [{"name", "period"}],
- lists:map(
- fun({O, V}) ->
- Sel = if
- O == Month -> [{"selected", "selected"}];
- true -> []
- end,
- ?XAC("option",
- Sel ++ [{"value", O}], V)
- end, [{"month", ?T("Last month")},
- {"year", ?T("Last year")},
- {"all", ?T("All activity")}])),
- ?C(" "),
- ?INPUTT("submit", "ordinary", "Show Ordinary Table"),
- ?C(" "),
- ?INPUTT("submit", "integral", "Show Integral Table")
- ])] ++
- Res, Host, Lang, AJID);
-
+ make_xhtml([?XCT(<<"h1">>, <<"Users Last Activity">>)]
+ ++
+ [?XAE(<<"form">>,
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ [?CT(<<"Period: ">>),
+ ?XAE(<<"select">>, [{<<"name">>, <<"period">>}],
+ (lists:map(fun ({O, V}) ->
+ Sel = if O == Month ->
+ [{<<"selected">>,
+ <<"selected">>}];
+ true -> []
+ end,
+ ?XAC(<<"option">>,
+ (Sel ++
+ [{<<"value">>, O}]),
+ V)
+ end,
+ [{<<"month">>, ?T(<<"Last month">>)},
+ {<<"year">>, ?T(<<"Last year">>)},
+ {<<"all">>,
+ ?T(<<"All activity">>)}]))),
+ ?C(<<" ">>),
+ ?INPUTT(<<"submit">>, <<"ordinary">>,
+ <<"Show Ordinary Table">>),
+ ?C(<<" ">>),
+ ?INPUTT(<<"submit">>, <<"integral">>,
+ <<"Show Integral Table">>)])]
+ ++ Res,
+ Host, Lang, AJID);
process_admin(Host,
- #request{path = ["stats"],
- auth = {_, _Auth, AJID},
+ #request{path = [<<"stats">>], auth = {_, _Auth, AJID},
lang = Lang}) ->
Res = get_stats(Host, Lang),
- make_xhtml([?XCT("h1", "Statistics")] ++ Res, Host, Lang, AJID);
-
+ make_xhtml([?XCT(<<"h1">>, <<"Statistics">>)] ++ Res,
+ Host, Lang, AJID);
process_admin(Host,
- #request{path = ["user", U],
- auth = {_, _Auth, AJID},
- q = Query,
- lang = Lang}) ->
+ #request{path = [<<"user">>, U],
+ auth = {_, _Auth, AJID}, q = Query, lang = Lang}) ->
case ejabberd_auth:is_user_exists(U, Host) of
- true ->
- Res = user_info(U, Host, Query, Lang),
- make_xhtml(Res, Host, Lang, AJID);
- false ->
- make_xhtml([?XCT("h1", "Not Found")], Host, Lang, AJID)
+ true ->
+ Res = user_info(U, Host, Query, Lang),
+ make_xhtml(Res, Host, Lang, AJID);
+ false ->
+ make_xhtml([?XCT(<<"h1">>, <<"Not Found">>)], Host,
+ Lang, AJID)
end;
-
process_admin(Host,
- #request{path = ["nodes"],
- auth = {_, _Auth, AJID},
+ #request{path = [<<"nodes">>], auth = {_, _Auth, AJID},
lang = Lang}) ->
Res = get_nodes(Lang),
make_xhtml(Res, Host, Lang, AJID);
-
process_admin(Host,
- #request{path = ["node", SNode | NPath],
- auth = {_, _Auth, AJID},
- q = Query,
- lang = Lang}) ->
+ #request{path = [<<"node">>, SNode | NPath],
+ auth = {_, _Auth, AJID}, q = Query, lang = Lang}) ->
case search_running_node(SNode) of
- false ->
- make_xhtml([?XCT("h1", "Node not found")], Host, Lang, AJID);
- Node ->
- Res = get_node(Host, Node, NPath, Query, Lang),
- make_xhtml(Res, Host, Node, Lang, AJID)
+ false ->
+ make_xhtml([?XCT(<<"h1">>, <<"Node not found">>)], Host,
+ Lang, AJID);
+ Node ->
+ Res = get_node(Host, Node, NPath, Query, Lang),
+ make_xhtml(Res, Host, Node, Lang, AJID)
end;
-
%%%==================================
%%%% process_admin default case
-
-process_admin(Host, #request{lang = Lang,
- auth = {_, _Auth, AJID}
- } = Request) ->
+process_admin(Host,
+ #request{lang = Lang, auth = {_, _Auth, AJID}} =
+ Request) ->
{Hook, Opts} = case Host of
- global -> {webadmin_page_main, [Request]};
- Host -> {webadmin_page_host, [Host, Request]}
+ global -> {webadmin_page_main, [Request]};
+ Host -> {webadmin_page_host, [Host, Request]}
end,
case ejabberd_hooks:run_fold(Hook, Host, [], Opts) of
- [] -> setelement(1, make_xhtml([?XC("h1", "Not Found")], Host, Lang, AJID), 404);
- Res -> make_xhtml(Res, Host, Lang, AJID)
+ [] ->
+ setelement(1,
+ make_xhtml([?XC(<<"h1">>, <<"Not Found">>)], Host, Lang,
+ AJID),
+ 404);
+ Res -> make_xhtml(Res, Host, Lang, AJID)
end.
%%%==================================
%%%% acl
acls_to_xhtml(ACLs) ->
- ?XAE("table", [],
- [?XE("tbody",
- lists:map(
- fun({acl, Name, Spec} = ACL) ->
- SName = atom_to_list(Name),
- ID = term_to_id(ACL),
- ?XE("tr",
- [?XE("td", [?INPUT("checkbox", "selected", ID)]),
- ?XC("td", SName)] ++
- acl_spec_to_xhtml(ID, Spec)
- )
- end, ACLs) ++
- [?XE("tr",
- [?X("td"),
- ?XE("td", [?INPUT("text", "namenew", "")])
- ] ++
- acl_spec_to_xhtml("new", {user, ""})
- )]
- )]).
-
-acl_spec_to_text({user, U}) ->
- {user, U};
-
-acl_spec_to_text({server, S}) ->
- {server, S};
-
+ ?XAE(<<"table">>, [],
+ [?XE(<<"tbody">>,
+ (lists:map(fun ({acl, Name, Spec} = ACL) ->
+ SName = iolist_to_binary(atom_to_list(Name)),
+ ID = term_to_id(ACL),
+ ?XE(<<"tr">>,
+ ([?XE(<<"td">>,
+ [?INPUT(<<"checkbox">>,
+ <<"selected">>, ID)]),
+ ?XC(<<"td">>, SName)]
+ ++ acl_spec_to_xhtml(ID, Spec)))
+ end,
+ ACLs)
+ ++
+ [?XE(<<"tr">>,
+ ([?X(<<"td">>),
+ ?XE(<<"td">>,
+ [?INPUT(<<"text">>, <<"namenew">>, <<"">>)])]
+ ++ acl_spec_to_xhtml(<<"new">>, {user, <<"">>})))]))]).
+
+acl_spec_to_text({user, U}) -> {user, U};
+acl_spec_to_text({server, S}) -> {server, S};
acl_spec_to_text({user, U, S}) ->
- {user, U ++ "@" ++ S};
-
+ {user, <<U/binary, "@", S/binary>>};
acl_spec_to_text({user_regexp, RU}) ->
{user_regexp, RU};
-
acl_spec_to_text({user_regexp, RU, S}) ->
- {user_regexp, RU ++ "@" ++ S};
-
+ {user_regexp, <<RU/binary, "@", S/binary>>};
acl_spec_to_text({server_regexp, RS}) ->
{server_regexp, RS};
-
acl_spec_to_text({node_regexp, RU, RS}) ->
- {node_regexp, RU ++ "@" ++ RS};
-
-acl_spec_to_text({user_glob, RU}) ->
- {user_glob, RU};
-
+ {node_regexp, <<RU/binary, "@", RS/binary>>};
+acl_spec_to_text({user_glob, RU}) -> {user_glob, RU};
acl_spec_to_text({user_glob, RU, S}) ->
- {user_glob, RU ++ "@" ++ S};
-
+ {user_glob, <<RU/binary, "@", S/binary>>};
acl_spec_to_text({server_glob, RS}) ->
{server_glob, RS};
-
acl_spec_to_text({node_glob, RU, RS}) ->
- {node_glob, RU ++ "@" ++ RS};
-
-acl_spec_to_text(all) ->
- {all, ""};
-
-acl_spec_to_text(Spec) ->
- {raw, term_to_string(Spec)}.
+ {node_glob, <<RU/binary, "@", RS/binary>>};
+acl_spec_to_text(all) -> {all, <<"">>};
+acl_spec_to_text(Spec) -> {raw, term_to_string(Spec)}.
acl_spec_to_xhtml(ID, Spec) ->
{Type, Str} = acl_spec_to_text(Spec),
[acl_spec_select(ID, Type), ?ACLINPUT(Str)].
acl_spec_select(ID, Opt) ->
- ?XE("td",
- [?XAE("select", [{"name", "type" ++ ID}],
- lists:map(
- fun(O) ->
- Sel = if
- O == Opt -> [{"selected", "selected"}];
- true -> []
- end,
- ?XAC("option",
- Sel ++ [{"value", atom_to_list(O)}],
- atom_to_list(O))
- end, [user, server, user_regexp, server_regexp,
- node_regexp, user_glob, server_glob, node_glob, all, raw]))]).
-
+ ?XE(<<"td">>,
+ [?XAE(<<"select">>,
+ [{<<"name">>, <<"type", ID/binary>>}],
+ (lists:map(fun (O) ->
+ Sel = if O == Opt ->
+ [{<<"selected">>,
+ <<"selected">>}];
+ true -> []
+ end,
+ ?XAC(<<"option">>,
+ (Sel ++
+ [{<<"value">>,
+ iolist_to_binary(atom_to_list(O))}]),
+ (iolist_to_binary(atom_to_list(O))))
+ end,
+ [user, server, user_regexp, server_regexp, node_regexp,
+ user_glob, server_glob, node_glob, all, raw])))]).
-%% @spec (T::any()) -> StringLine::string()
term_to_string(T) ->
- StringParagraph = lists:flatten(io_lib:format("~1000000p", [T])),
- %% Remove from the string all the carriage returns characters
- ejabberd_regexp:greplace(StringParagraph, "\\n ", "").
+ StringParagraph =
+ iolist_to_binary(io_lib:format("~1000000p", [T])),
+ ejabberd_regexp:greplace(StringParagraph, <<"\\n ">>,
+ <<"">>).
-%% @spec (T::any(), Cols::integer()) -> {NumLines::integer(), Paragraph::string()}
term_to_paragraph(T, Cols) ->
- Paragraph = erl_prettypr:format(erl_syntax:abstract(T), [{paper, Cols}]),
- FieldList = ejabberd_regexp:split(Paragraph, "\n"),
+ Paragraph = list_to_binary(erl_prettypr:format(erl_syntax:abstract(T),
+ [{paper, Cols}])),
+ FieldList = ejabberd_regexp:split(Paragraph, <<"\n">>),
NumLines = length(FieldList),
{NumLines, Paragraph}.
-term_to_id(T) ->
- jlib:encode_base64(binary_to_list(term_to_binary(T))).
-
+term_to_id(T) -> jlib:encode_base64((term_to_binary(T))).
acl_parse_query(Host, Query) ->
- ACLs = ets:select(acl, [{{acl, {'$1', Host}, '$2'},
- [], [{{acl, '$1', '$2'}}]}]),
- case lists:keysearch("submit", 1, Query) of
- {value, _} ->
- acl_parse_submit(ACLs, Query);
- _ ->
- case lists:keysearch("delete", 1, Query) of
- {value, _} ->
- acl_parse_delete(ACLs, Query)
- end
+ ACLs = ets:select(acl,
+ [{{acl, {'$1', Host}, '$2'}, [],
+ [{{acl, '$1', '$2'}}]}]),
+ case lists:keysearch(<<"submit">>, 1, Query) of
+ {value, _} -> acl_parse_submit(ACLs, Query);
+ _ ->
+ case lists:keysearch(<<"delete">>, 1, Query) of
+ {value, _} -> acl_parse_delete(ACLs, Query)
+ end
end.
acl_parse_submit(ACLs, Query) ->
- NewACLs =
- lists:map(
- fun({acl, Name, Spec} = ACL) ->
- %%SName = atom_to_list(Name),
- ID = term_to_id(ACL),
- case {lists:keysearch("type" ++ ID, 1, Query),
- lists:keysearch("value" ++ ID, 1, Query)} of
- {{value, {_, T}}, {value, {_, V}}} ->
- {Type, Str} = acl_spec_to_text(Spec),
- case {atom_to_list(Type), Str} of
- {T, V} ->
- ACL;
- _ ->
- NewSpec = string_to_spec(T, V),
- {acl, Name, NewSpec}
- end;
- _ ->
- ACL
- end
- end, ACLs),
- NewACL = case {lists:keysearch("namenew", 1, Query),
- lists:keysearch("typenew", 1, Query),
- lists:keysearch("valuenew", 1, Query)} of
- {{value, {_, ""}}, _, _} ->
- [];
- {{value, {_, N}}, {value, {_, T}}, {value, {_, V}}} ->
- NewName = list_to_atom(N),
- NewSpec = string_to_spec(T, V),
- [{acl, NewName, NewSpec}];
- _ ->
- []
+ NewACLs = lists:map(fun ({acl, Name, Spec} = ACL) ->
+ ID = term_to_id(ACL),
+ case {lists:keysearch(<<"type", ID/binary>>, 1,
+ Query),
+ lists:keysearch(<<"value", ID/binary>>, 1,
+ Query)}
+ of
+ {{value, {_, T}}, {value, {_, V}}} ->
+ {Type, Str} = acl_spec_to_text(Spec),
+ case
+ {iolist_to_binary(atom_to_list(Type)),
+ Str}
+ of
+ {T, V} -> ACL;
+ _ ->
+ NewSpec = string_to_spec(T, V),
+ {acl, Name, NewSpec}
+ end;
+ _ -> ACL
+ end
+ end,
+ ACLs),
+ NewACL = case {lists:keysearch(<<"namenew">>, 1, Query),
+ lists:keysearch(<<"typenew">>, 1, Query),
+ lists:keysearch(<<"valuenew">>, 1, Query)}
+ of
+ {{value, {_, <<"">>}}, _, _} -> [];
+ {{value, {_, N}}, {value, {_, T}}, {value, {_, V}}} ->
+ NewName = jlib:binary_to_atom(N),
+ NewSpec = string_to_spec(T, V),
+ [{acl, NewName, NewSpec}];
+ _ -> []
end,
NewACLs ++ NewACL.
-string_to_spec("user", Val) ->
+string_to_spec(<<"user">>, Val) ->
string_to_spec2(user, Val);
-string_to_spec("server", Val) ->
- {server, Val};
-string_to_spec("user_regexp", Val) ->
+string_to_spec(<<"server">>, Val) -> {server, Val};
+string_to_spec(<<"user_regexp">>, Val) ->
string_to_spec2(user_regexp, Val);
-string_to_spec("server_regexp", Val) ->
+string_to_spec(<<"server_regexp">>, Val) ->
{server_regexp, Val};
-string_to_spec("node_regexp", Val) ->
- #jid{luser = U, lserver = S, resource = ""} = jlib:string_to_jid(Val),
+string_to_spec(<<"node_regexp">>, Val) ->
+ #jid{luser = U, lserver = S, resource = <<"">>} =
+ jlib:string_to_jid(Val),
{node_regexp, U, S};
-string_to_spec("user_glob", Val) ->
+string_to_spec(<<"user_glob">>, Val) ->
string_to_spec2(user_glob, Val);
-string_to_spec("server_glob", Val) ->
+string_to_spec(<<"server_glob">>, Val) ->
{server_glob, Val};
-string_to_spec("node_glob", Val) ->
- #jid{luser = U, lserver = S, resource = ""} = jlib:string_to_jid(Val),
+string_to_spec(<<"node_glob">>, Val) ->
+ #jid{luser = U, lserver = S, resource = <<"">>} =
+ jlib:string_to_jid(Val),
{node_glob, U, S};
-string_to_spec("all", _) ->
- all;
-string_to_spec("raw", Val) ->
- {ok, Tokens, _} = erl_scan:string(Val ++ "."),
+string_to_spec(<<"all">>, _) -> all;
+string_to_spec(<<"raw">>, Val) ->
+ {ok, Tokens, _} = erl_scan:string(binary_to_list(<<Val/binary, ".">>)),
{ok, NewSpec} = erl_parse:parse_term(Tokens),
NewSpec.
string_to_spec2(ACLName, Val) ->
- #jid{luser = U, lserver = S, resource = ""} = jlib:string_to_jid(Val),
+ #jid{luser = U, lserver = S, resource = <<"">>} =
+ jlib:string_to_jid(Val),
case U of
- "" ->
- {ACLName, S};
- _ ->
- {ACLName, U, S}
+ <<"">> -> {ACLName, S};
+ _ -> {ACLName, U, S}
end.
-
acl_parse_delete(ACLs, Query) ->
- NewACLs =
- lists:filter(
- fun({acl, _Name, _Spec} = ACL) ->
- ID = term_to_id(ACL),
- not lists:member({"selected", ID}, Query)
- end, ACLs),
+ NewACLs = lists:filter(fun ({acl, _Name, _Spec} =
+ ACL) ->
+ ID = term_to_id(ACL),
+ not lists:member({"selected", ID}, Query)
+ end,
+ ACLs),
NewACLs.
-
access_rules_to_xhtml(AccessRules, Lang) ->
- ?XAE("table", [],
- [?XE("tbody",
- lists:map(
- fun({access, Name, Rules} = Access) ->
- SName = atom_to_list(Name),
- ID = term_to_id(Access),
- ?XE("tr",
- [?XE("td", [?INPUT("checkbox", "selected", ID)]),
- ?XE("td", [?AC(SName ++ "/", SName)]),
- ?XC("td", term_to_string(Rules))
- ]
- )
- end, AccessRules) ++
- [?XE("tr",
- [?X("td"),
- ?XE("td", [?INPUT("text", "namenew", "")]),
- ?XE("td", [?INPUTT("submit", "addnew", "Add New")])
- ]
- )]
- )]).
+ ?XAE(<<"table">>, [],
+ [?XE(<<"tbody">>,
+ (lists:map(fun ({access, Name, Rules} = Access) ->
+ SName = iolist_to_binary(atom_to_list(Name)),
+ ID = term_to_id(Access),
+ ?XE(<<"tr">>,
+ [?XE(<<"td">>,
+ [?INPUT(<<"checkbox">>,
+ <<"selected">>, ID)]),
+ ?XE(<<"td">>,
+ [?AC(<<SName/binary, "/">>, SName)]),
+ ?XC(<<"td">>, (term_to_string(Rules)))])
+ end,
+ lists:keysort(2,AccessRules))
+ ++
+ [?XE(<<"tr">>,
+ [?X(<<"td">>),
+ ?XE(<<"td">>,
+ [?INPUT(<<"text">>, <<"namenew">>, <<"">>)]),
+ ?XE(<<"td">>,
+ [?INPUTT(<<"submit">>, <<"addnew">>,
+ <<"Add New">>)])])]))]).
access_parse_query(Host, Query) ->
- AccessRules =
- ets:select(config,
- [{{config, {access, '$1', Host}, '$2'},
- [],
- [{{access, '$1', '$2'}}]}]),
- case lists:keysearch("addnew", 1, Query) of
- {value, _} ->
- access_parse_addnew(AccessRules, Host, Query);
- _ ->
- case lists:keysearch("delete", 1, Query) of
- {value, _} ->
- access_parse_delete(AccessRules, Host, Query)
- end
+ AccessRules = ets:select(config,
+ [{{config, {access, '$1', Host}, '$2'}, [],
+ [{{access, '$1', '$2'}}]}]),
+ case lists:keysearch(<<"addnew">>, 1, Query) of
+ {value, _} ->
+ access_parse_addnew(AccessRules, Host, Query);
+ _ ->
+ case lists:keysearch(<<"delete">>, 1, Query) of
+ {value, _} ->
+ access_parse_delete(AccessRules, Host, Query)
+ end
end.
access_parse_addnew(_AccessRules, Host, Query) ->
- case lists:keysearch("namenew", 1, Query) of
- {value, {_, String}} when String /= "" ->
- Name = list_to_atom(String),
- ejabberd_config:add_global_option({access, Name, Host}, []),
- ok
+ case lists:keysearch(<<"namenew">>, 1, Query) of
+ {value, {_, String}} when String /= <<"">> ->
+ Name = jlib:binary_to_atom(String),
+ ejabberd_config:add_global_option({access, Name, Host},
+ []),
+ ok
end.
access_parse_delete(AccessRules, Host, Query) ->
- lists:foreach(
- fun({access, Name, _Rules} = AccessRule) ->
- ID = term_to_id(AccessRule),
- case lists:member({"selected", ID}, Query) of
- true ->
- mnesia:transaction(
- fun() ->
- mnesia:delete({config, {access, Name, Host}})
- end);
- _ ->
- ok
- end
- end, AccessRules),
+ lists:foreach(fun ({access, Name, _Rules} =
+ AccessRule) ->
+ ID = term_to_id(AccessRule),
+ case lists:member({<<"selected">>, ID}, Query) of
+ true ->
+ mnesia:transaction(fun () ->
+ mnesia:delete({config,
+ {access,
+ Name,
+ Host}})
+ end);
+ _ -> ok
+ end
+ end,
+ AccessRules),
ok.
-
-
-
access_rule_to_xhtml(Rules) ->
- Text = lists:flatmap(
- fun({Access, ACL} = _Rule) ->
- SAccess = element_to_list(Access),
- SACL = atom_to_list(ACL),
- SAccess ++ "\s\t" ++ SACL ++ "\n"
- end, Rules),
- ?XAC("textarea", [{"name", "rules"},
- {"rows", "16"},
- {"cols", "80"}],
- Text).
+ Text = lists:flatmap(fun ({Access, ACL} = _Rule) ->
+ SAccess = element_to_list(Access),
+ SACL = atom_to_list(ACL),
+ [SAccess, " \t", SACL, "\n"]
+ end,
+ Rules),
+ ?XAC(<<"textarea">>,
+ [{<<"name">>, <<"rules">>}, {<<"rows">>, <<"16">>},
+ {<<"cols">>, <<"80">>}],
+ list_to_binary(Text)).
parse_access_rule(Text) ->
- Strings = string:tokens(Text, "\r\n"),
- case catch lists:flatmap(
- fun(String) ->
- case string:tokens(String, "\s\t") of
- [Access, ACL] ->
- [{list_to_element(Access), list_to_atom(ACL)}];
- [] ->
- []
- end
- end, Strings) of
- {'EXIT', _Reason} ->
- error;
- Rs ->
- {ok, Rs}
+ Strings = str:tokens(Text, <<"\r\n">>),
+ case catch lists:flatmap(fun (String) ->
+ case str:tokens(String, <<" \t">>) of
+ [Access, ACL] ->
+ [{list_to_element(Access),
+ jlib:binary_to_atom(ACL)}];
+ [] -> []
+ end
+ end,
+ Strings)
+ of
+ {'EXIT', _Reason} -> error;
+ Rs -> {ok, Rs}
end.
%%%==================================
%%%% list_vhosts
list_vhosts(Lang, JID) ->
- Hosts = ?MYHOSTS,
- HostsAllowed = lists:filter(
- fun(Host) ->
- is_acl_match(Host, [configure, webadmin_view], JID)
- end,
- Hosts
- ),
+ Hosts = (?MYHOSTS),
+ HostsAllowed = lists:filter(fun (Host) ->
+ is_acl_match(Host,
+ [configure, webadmin_view],
+ JID)
+ end,
+ Hosts),
list_vhosts2(Lang, HostsAllowed).
list_vhosts2(Lang, Hosts) ->
SHosts = lists:sort(Hosts),
- [?XE("table",
- [?XE("thead",
- [?XE("tr",
- [?XCT("td", "Host"),
- ?XCT("td", "Registered Users"),
- ?XCT("td", "Online Users")
- ])]),
- ?XE("tbody",
- lists:map(
- fun(Host) ->
- OnlineUsers =
- length(ejabberd_sm:get_vh_session_list(Host)),
- RegisteredUsers =
- ejabberd_auth:get_vh_registered_users_number(Host),
- ?XE("tr",
- [?XE("td", [?AC("../server/" ++ Host ++ "/", Host)]),
- ?XC("td", pretty_string_int(RegisteredUsers)),
- ?XC("td", pretty_string_int(OnlineUsers))
- ])
- end, SHosts)
- )])].
+ [?XE(<<"table">>,
+ [?XE(<<"thead">>,
+ [?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"Host">>),
+ ?XCT(<<"td">>, <<"Registered Users">>),
+ ?XCT(<<"td">>, <<"Online Users">>)])]),
+ ?XE(<<"tbody">>,
+ (lists:map(fun (Host) ->
+ OnlineUsers =
+ length(ejabberd_sm:get_vh_session_list(Host)),
+ RegisteredUsers =
+ ejabberd_auth:get_vh_registered_users_number(Host),
+ ?XE(<<"tr">>,
+ [?XE(<<"td">>,
+ [?AC(<<"../server/", Host/binary,
+ "/">>,
+ Host)]),
+ ?XC(<<"td">>,
+ (pretty_string_int(RegisteredUsers))),
+ ?XC(<<"td">>,
+ (pretty_string_int(OnlineUsers)))])
+ end,
+ SHosts)))])].
%%%==================================
%%%% list_users
@@ -1483,387 +1283,424 @@ list_users(Host, Query, Lang, URLFunc) ->
Res = list_users_parse_query(Query, Host),
Users = ejabberd_auth:get_vh_registered_users(Host),
SUsers = lists:sort([{S, U} || {U, S} <- Users]),
- FUsers =
- case length(SUsers) of
- N when N =< 100 ->
- [list_given_users(Host, SUsers, "../", Lang, URLFunc)];
- N ->
- NParts = trunc(math:sqrt(N * 0.618)) + 1,
- M = trunc(N / NParts) + 1,
- lists:flatmap(
- fun(K) ->
- L = K + M - 1,
- %%Node = integer_to_list(K) ++ "-" ++ integer_to_list(L),
- Last = if L < N -> su_to_list(lists:nth(L, SUsers));
- true -> su_to_list(lists:last(SUsers))
+ FUsers = case length(SUsers) of
+ N when N =< 100 ->
+ [list_given_users(Host, SUsers, <<"../">>, Lang,
+ URLFunc)];
+ N ->
+ NParts = trunc(math:sqrt(N * 6.17999999999999993783e-1))
+ + 1,
+ M = trunc(N / NParts) + 1,
+ lists:flatmap(fun (K) ->
+ L = K + M - 1,
+ Last = if L < N ->
+ su_to_list(lists:nth(L,
+ SUsers));
+ true ->
+ su_to_list(lists:last(SUsers))
+ end,
+ Name = <<(su_to_list(lists:nth(K,
+ SUsers)))/binary,
+ $\s, 226, 128, 148, $\s,
+ Last/binary>>,
+ [?AC((URLFunc({user_diapason, K, L})),
+ Name),
+ ?BR]
end,
- Name =
- su_to_list(lists:nth(K, SUsers)) ++
- [$\s, 226, 128, 148, $\s] ++
- Last,
- [?AC(URLFunc({user_diapason, K, L}), Name), ?BR]
- end, lists:seq(1, N, M))
- end,
+ lists:seq(1, N, M))
+ end,
case Res of
- ok -> [?XREST("Submitted")];
- error -> [?XREST("Bad format")];
- nothing -> []
- end ++
- [?XAE("form", [{"action", ""}, {"method", "post"}],
- [?XE("table",
- [?XE("tr",
- [?XC("td", ?T("User") ++ ":"),
- ?XE("td", [?INPUT("text", "newusername", "")]),
- ?XE("td", [?C([" @ ", Host])])
- ]),
- ?XE("tr",
- [?XC("td", ?T("Password") ++ ":"),
- ?XE("td", [?INPUT("password", "newuserpassword", "")]),
- ?X("td")
- ]),
- ?XE("tr",
- [?X("td"),
- ?XAE("td", [{"class", "alignright"}],
- [?INPUTT("submit", "addnewuser", "Add User")]),
- ?X("td")
- ])]),
- ?P] ++
- FUsers)].
-
-%% Parse user creation query and try register:
+ ok -> [?XREST(<<"Submitted">>)];
+ error -> [?XREST(<<"Bad format">>)];
+ nothing -> []
+ end
+ ++
+ [?XAE(<<"form">>,
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ ([?XE(<<"table">>,
+ [?XE(<<"tr">>,
+ [?XC(<<"td">>, <<(?T(<<"User">>))/binary, ":">>),
+ ?XE(<<"td">>,
+ [?INPUT(<<"text">>, <<"newusername">>, <<"">>)]),
+ ?XE(<<"td">>, [?C(<<" @ ", Host/binary>>)])]),
+ ?XE(<<"tr">>,
+ [?XC(<<"td">>, <<(?T(<<"Password">>))/binary, ":">>),
+ ?XE(<<"td">>,
+ [?INPUT(<<"password">>, <<"newuserpassword">>,
+ <<"">>)]),
+ ?X(<<"td">>)]),
+ ?XE(<<"tr">>,
+ [?X(<<"td">>),
+ ?XAE(<<"td">>, [{<<"class">>, <<"alignright">>}],
+ [?INPUTT(<<"submit">>, <<"addnewuser">>,
+ <<"Add User">>)]),
+ ?X(<<"td">>)])]),
+ ?P]
+ ++ FUsers))].
+
list_users_parse_query(Query, Host) ->
- case lists:keysearch("addnewuser", 1, Query) of
- {value, _} ->
- {value, {_, Username}} =
- lists:keysearch("newusername", 1, Query),
- {value, {_, Password}} =
- lists:keysearch("newuserpassword", 1, Query),
- case jlib:string_to_jid(Username++"@"++Host) of
- error ->
- error;
- #jid{user = User, server = Server} ->
- case ejabberd_auth:try_register(User, Server, Password) of
- {error, _Reason} ->
- error;
- _ ->
- ok
- end
- end;
- false ->
- nothing
+ case lists:keysearch(<<"addnewuser">>, 1, Query) of
+ {value, _} ->
+ {value, {_, Username}} =
+ lists:keysearch(<<"newusername">>, 1, Query),
+ {value, {_, Password}} =
+ lists:keysearch(<<"newuserpassword">>, 1, Query),
+ case jlib:string_to_jid(<<Username/binary, "@",
+ Host/binary>>)
+ of
+ error -> error;
+ #jid{user = User, server = Server} ->
+ case ejabberd_auth:try_register(User, Server, Password)
+ of
+ {error, _Reason} -> error;
+ _ -> ok
+ end
+ end;
+ false -> nothing
end.
-
list_users_in_diapason(Host, Diap, Lang, URLFunc) ->
Users = ejabberd_auth:get_vh_registered_users(Host),
SUsers = lists:sort([{S, U} || {U, S} <- Users]),
- [S1, S2] = ejabberd_regexp:split(Diap, "-"),
- N1 = list_to_integer(S1),
- N2 = list_to_integer(S2),
+ [S1, S2] = ejabberd_regexp:split(Diap, <<"-">>),
+ N1 = jlib:binary_to_integer(S1),
+ N2 = jlib:binary_to_integer(S2),
Sub = lists:sublist(SUsers, N1, N2 - N1 + 1),
- [list_given_users(Host, Sub, "../../", Lang, URLFunc)].
+ [list_given_users(Host, Sub, <<"../../">>, Lang,
+ URLFunc)].
list_given_users(Host, Users, Prefix, Lang, URLFunc) ->
ModOffline = get_offlinemsg_module(Host),
- ?XE("table",
- [?XE("thead",
- [?XE("tr",
- [?XCT("td", "User"),
- ?XCT("td", "Offline Messages"),
- ?XCT("td", "Last Activity")])]),
- ?XE("tbody",
- lists:map(
- fun(_SU = {Server, User}) ->
- US = {User, Server},
- QueueLenStr = get_offlinemsg_length(ModOffline, User, Server),
- FQueueLen = [?AC(URLFunc({users_queue, Prefix,
- User, Server}),
- QueueLenStr)],
- FLast =
- case ejabberd_sm:get_user_resources(User, Server) of
- [] ->
- case mod_last:get_last_info(User, Server) of
- not_found ->
- ?T("Never");
- {ok, Shift, _Status} ->
- TimeStamp = {Shift div 1000000,
- Shift rem 1000000,
- 0},
- {{Year, Month, Day}, {Hour, Minute, Second}} =
- calendar:now_to_local_time(TimeStamp),
- lists:flatten(
- io_lib:format(
- "~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
- [Year, Month, Day, Hour, Minute, Second]))
- end;
- _ ->
- ?T("Online")
- end,
- ?XE("tr",
- [?XE("td",
- [?AC(URLFunc({user, Prefix,
- ejabberd_http:url_encode(User),
- Server}),
- us_to_list(US))]),
- ?XE("td", FQueueLen),
- ?XC("td", FLast)])
- end, Users)
- )]).
+ ?XE(<<"table">>,
+ [?XE(<<"thead">>,
+ [?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"User">>),
+ ?XCT(<<"td">>, <<"Offline Messages">>),
+ ?XCT(<<"td">>, <<"Last Activity">>)])]),
+ ?XE(<<"tbody">>,
+ (lists:map(fun (_SU = {Server, User}) ->
+ US = {User, Server},
+ QueueLenStr = get_offlinemsg_length(ModOffline,
+ User,
+ Server),
+ FQueueLen = [?AC((URLFunc({users_queue, Prefix,
+ User, Server})),
+ QueueLenStr)],
+ FLast = case
+ ejabberd_sm:get_user_resources(User,
+ Server)
+ of
+ [] ->
+ case mod_last:get_last_info(User,
+ Server)
+ of
+ not_found -> ?T(<<"Never">>);
+ {ok, Shift, _Status} ->
+ TimeStamp = {Shift div
+ 1000000,
+ Shift rem
+ 1000000,
+ 0},
+ {{Year, Month, Day},
+ {Hour, Minute, Second}} =
+ calendar:now_to_local_time(TimeStamp),
+ iolist_to_binary(io_lib:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
+ [Year,
+ Month,
+ Day,
+ Hour,
+ Minute,
+ Second]))
+ end;
+ _ -> ?T(<<"Online">>)
+ end,
+ ?XE(<<"tr">>,
+ [?XE(<<"td">>,
+ [?AC((URLFunc({user, Prefix,
+ ejabberd_http:url_encode(User),
+ Server})),
+ (us_to_list(US)))]),
+ ?XE(<<"td">>, FQueueLen),
+ ?XC(<<"td">>, FLast)])
+ end,
+ Users)))]).
get_offlinemsg_length(ModOffline, User, Server) ->
case ModOffline of
- none -> "disabled";
- _ -> pretty_string_int(ModOffline:get_queue_length(User, Server))
+ none -> <<"disabled">>;
+ _ ->
+ pretty_string_int(ModOffline:get_queue_length(User,
+ Server))
end.
get_offlinemsg_module(Server) ->
case gen_mod:is_loaded(Server, mod_offline) of
- true ->
- mod_offline;
- false ->
- none
+ true -> mod_offline;
+ false -> none
end.
get_lastactivity_menuitem_list(Server) ->
case gen_mod:db_type(Server, mod_last) of
- mnesia -> [{"last-activity", "Last Activity"}];
- _ -> []
+ mnesia -> [{<<"last-activity">>, <<"Last Activity">>}];
+ _ -> []
end.
us_to_list({User, Server}) ->
- jlib:jid_to_string({User, Server, ""}).
+ jlib:jid_to_string({User, Server, <<"">>}).
su_to_list({Server, User}) ->
- jlib:jid_to_string({User, Server, ""}).
+ jlib:jid_to_string({User, Server, <<"">>}).
%%%==================================
%%%% get_stats
get_stats(global, Lang) ->
OnlineUsers = mnesia:table_info(session, size),
- RegisteredUsers = lists:foldl(
- fun(Host, Total) ->
- ejabberd_auth:get_vh_registered_users_number(Host) + Total
- end, 0, ejabberd_config:get_global_option(hosts)),
+ RegisteredUsers = lists:foldl(fun (Host, Total) ->
+ ejabberd_auth:get_vh_registered_users_number(Host)
+ + Total
+ end,
+ 0, ?MYHOSTS),
S2SConns = ejabberd_s2s:dirty_get_connections(),
S2SConnections = length(S2SConns),
- S2SServers = length(lists:usort([element(2, C) || C <- S2SConns])),
- [?XAE("table", [],
- [?XE("tbody",
- [?XE("tr", [?XCT("td", "Registered Users:"),
- ?XC("td", pretty_string_int(RegisteredUsers))]),
- ?XE("tr", [?XCT("td", "Online Users:"),
- ?XC("td", pretty_string_int(OnlineUsers))]),
- ?XE("tr", [?XCT("td", "Outgoing s2s Connections:"),
- ?XC("td", pretty_string_int(S2SConnections))]),
- ?XE("tr", [?XCT("td", "Outgoing s2s Servers:"),
- ?XC("td", pretty_string_int(S2SServers))])
- ])
- ])];
-
+ S2SServers = length(lists:usort([element(2, C)
+ || C <- S2SConns])),
+ [?XAE(<<"table">>, [],
+ [?XE(<<"tbody">>,
+ [?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"Registered Users:">>),
+ ?XC(<<"td">>, (pretty_string_int(RegisteredUsers)))]),
+ ?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"Online Users:">>),
+ ?XC(<<"td">>, (pretty_string_int(OnlineUsers)))]),
+ ?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"Outgoing s2s Connections:">>),
+ ?XC(<<"td">>, (pretty_string_int(S2SConnections)))]),
+ ?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"Outgoing s2s Servers:">>),
+ ?XC(<<"td">>, (pretty_string_int(S2SServers)))])])])];
get_stats(Host, Lang) ->
- OnlineUsers = length(ejabberd_sm:get_vh_session_list(Host)),
- RegisteredUsers = ejabberd_auth:get_vh_registered_users_number(Host),
- [?XAE("table", [],
- [?XE("tbody",
- [?XE("tr", [?XCT("td", "Registered Users:"),
- ?XC("td", pretty_string_int(RegisteredUsers))]),
- ?XE("tr", [?XCT("td", "Online Users:"),
- ?XC("td", pretty_string_int(OnlineUsers))])
- ])
- ])].
-
+ OnlineUsers =
+ length(ejabberd_sm:get_vh_session_list(Host)),
+ RegisteredUsers =
+ ejabberd_auth:get_vh_registered_users_number(Host),
+ [?XAE(<<"table">>, [],
+ [?XE(<<"tbody">>,
+ [?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"Registered Users:">>),
+ ?XC(<<"td">>, (pretty_string_int(RegisteredUsers)))]),
+ ?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"Online Users:">>),
+ ?XC(<<"td">>, (pretty_string_int(OnlineUsers)))])])])].
list_online_users(Host, _Lang) ->
- Users = [{S, U} || {U, S, _R} <- ejabberd_sm:get_vh_session_list(Host)],
+ Users = [{S, U}
+ || {U, S, _R} <- ejabberd_sm:get_vh_session_list(Host)],
SUsers = lists:usort(Users),
- lists:flatmap(
- fun({_S, U} = SU) ->
- [?AC("../user/" ++ ejabberd_http:url_encode(U) ++ "/",
- su_to_list(SU)),
- ?BR]
- end, SUsers).
+ lists:flatmap(fun ({_S, U} = SU) ->
+ [?AC(<<"../user/",
+ (ejabberd_http:url_encode(U))/binary, "/">>,
+ (su_to_list(SU))),
+ ?BR]
+ end,
+ SUsers).
user_info(User, Server, Query, Lang) ->
LServer = jlib:nameprep(Server),
US = {jlib:nodeprep(User), LServer},
Res = user_parse_query(User, Server, Query),
- Resources = ejabberd_sm:get_user_resources(User, Server),
+ Resources = ejabberd_sm:get_user_resources(User,
+ Server),
FResources =
- case Resources of
- [] ->
- [?CT("None")];
- _ ->
- [?XE("ul",
- lists:map(fun(R) ->
- FIP = case ejabberd_sm:get_user_info(
- User, Server, R) of
- offline ->
- "";
- Info when is_list(Info) ->
- Node = proplists:get_value(node, Info),
- Conn = proplists:get_value(conn, Info),
- {IP, Port} = proplists:get_value(ip, Info),
- ConnS = case Conn of
- c2s -> "plain";
- c2s_tls -> "tls";
- c2s_compressed -> "zlib";
- c2s_compressed_tls -> "tls+zlib";
- http_bind -> "http-bind";
- http_poll -> "http-poll"
- end,
- " (" ++
- ConnS ++ "://" ++
- inet_parse:ntoa(IP) ++
- ":" ++
- integer_to_list(Port)
- ++ "#" ++ atom_to_list(Node)
- ++ ")"
- end,
- ?LI([?C(R ++ FIP)])
- end, lists:sort(Resources)))]
- end,
+ case Resources of
+ [] -> [?CT(<<"None">>)];
+ _ ->
+ [?XE(<<"ul">>,
+ (lists:map(
+ fun (R) ->
+ FIP = case
+ ejabberd_sm:get_user_info(User,
+ Server,
+ R)
+ of
+ offline -> <<"">>;
+ Info
+ when
+ is_list(Info) ->
+ Node =
+ proplists:get_value(node,
+ Info),
+ Conn =
+ proplists:get_value(conn,
+ Info),
+ {IP, Port} =
+ proplists:get_value(ip,
+ Info),
+ ConnS = case Conn of
+ c2s ->
+ <<"plain">>;
+ c2s_tls ->
+ <<"tls">>;
+ c2s_compressed ->
+ <<"zlib">>;
+ c2s_compressed_tls ->
+ <<"tls+zlib">>;
+ http_bind ->
+ <<"http-bind">>;
+ http_poll ->
+ <<"http-poll">>
+ end,
+ <<" (", ConnS/binary,
+ "://",
+ (jlib:ip_to_list(IP))/binary,
+ ":",
+ (jlib:integer_to_binary(Port))/binary,
+ "#",
+ (jlib:atom_to_binary(Node))/binary,
+ ")">>
+ end,
+ ?LI([?C((<<R/binary, FIP/binary>>))])
+ end,
+ lists:sort(Resources))))]
+ end,
Password = ejabberd_auth:get_password_s(User, Server),
- FPassword = [?INPUT("password", "password", Password), ?C(" "),
- ?INPUTT("submit", "chpassword", "Change Password")],
- UserItems = ejabberd_hooks:run_fold(webadmin_user, LServer, [],
- [User, Server, Lang]),
- %% Code copied from list_given_users/5:
- LastActivity = case ejabberd_sm:get_user_resources(User, Server) of
- [] ->
- case mod_last:get_last_info(User, Server) of
- not_found ->
- ?T("Never");
- {ok, Shift, _Status} ->
- TimeStamp = {Shift div 1000000,
- Shift rem 1000000,
- 0},
- {{Year, Month, Day}, {Hour, Minute, Second}} =
- calendar:now_to_local_time(TimeStamp),
- lists:flatten(
- io_lib:format(
- "~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
- [Year, Month, Day, Hour, Minute, Second]))
- end;
- _ ->
- ?T("Online")
+ FPassword = [?INPUT(<<"password">>, <<"password">>,
+ Password),
+ ?C(<<" ">>),
+ ?INPUTT(<<"submit">>, <<"chpassword">>,
+ <<"Change Password">>)],
+ UserItems = ejabberd_hooks:run_fold(webadmin_user,
+ LServer, [], [User, Server, Lang]),
+ LastActivity = case ejabberd_sm:get_user_resources(User,
+ Server)
+ of
+ [] ->
+ case mod_last:get_last_info(User, Server) of
+ not_found -> ?T(<<"Never">>);
+ {ok, Shift, _Status} ->
+ TimeStamp = {Shift div 1000000,
+ Shift rem 1000000, 0},
+ {{Year, Month, Day}, {Hour, Minute, Second}} =
+ calendar:now_to_local_time(TimeStamp),
+ iolist_to_binary(io_lib:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
+ [Year, Month, Day,
+ Hour, Minute,
+ Second]))
+ end;
+ _ -> ?T(<<"Online">>)
end,
- [?XC("h1", ?T("User ") ++ us_to_list(US))] ++
- case Res of
- ok -> [?XREST("Submitted")];
- error -> [?XREST("Bad format")];
- nothing -> []
- end ++
- [?XAE("form", [{"action", ""}, {"method", "post"}],
- [?XCT("h3", "Connected Resources:")] ++ FResources ++
- [?XCT("h3", "Password:")] ++ FPassword ++
- [?XCT("h3", "Last Activity")] ++ [?C(LastActivity)] ++
- UserItems ++
- [?P, ?INPUTT("submit", "removeuser", "Remove User")])].
-
+ [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"User ~s">>),
+ [us_to_list(US)])))]
+ ++
+ case Res of
+ ok -> [?XREST(<<"Submitted">>)];
+ error -> [?XREST(<<"Bad format">>)];
+ nothing -> []
+ end
+ ++
+ [?XAE(<<"form">>,
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ ([?XCT(<<"h3">>, <<"Connected Resources:">>)] ++
+ FResources ++
+ [?XCT(<<"h3">>, <<"Password:">>)] ++
+ FPassword ++
+ [?XCT(<<"h3">>, <<"Last Activity">>)] ++
+ [?C(LastActivity)] ++
+ UserItems ++
+ [?P,
+ ?INPUTT(<<"submit">>, <<"removeuser">>,
+ <<"Remove User">>)]))].
user_parse_query(User, Server, Query) ->
- lists:foldl(fun({Action, _Value}, Acc) when Acc == nothing ->
- user_parse_query1(Action, User, Server, Query);
- ({_Action, _Value}, Acc) ->
- Acc
- end, nothing, Query).
-
-user_parse_query1("password", _User, _Server, _Query) ->
+ lists:foldl(fun ({Action, _Value}, Acc)
+ when Acc == nothing ->
+ user_parse_query1(Action, User, Server, Query);
+ ({_Action, _Value}, Acc) -> Acc
+ end,
+ nothing, Query).
+
+user_parse_query1(<<"password">>, _User, _Server,
+ _Query) ->
nothing;
-user_parse_query1("chpassword", User, Server, Query) ->
- case lists:keysearch("password", 1, Query) of
- {value, {_, undefined}} ->
- error;
- {value, {_, Password}} ->
- ejabberd_auth:set_password(User, Server, Password),
- ok;
- _ ->
- error
+user_parse_query1(<<"chpassword">>, User, Server,
+ Query) ->
+ case lists:keysearch(<<"password">>, 1, Query) of
+ {value, {_, Password}} ->
+ ejabberd_auth:set_password(User, Server, Password), ok;
+ _ -> error
end;
-user_parse_query1("removeuser", User, Server, _Query) ->
- ejabberd_auth:remove_user(User, Server),
- ok;
+user_parse_query1(<<"removeuser">>, User, Server,
+ _Query) ->
+ ejabberd_auth:remove_user(User, Server), ok;
user_parse_query1(Action, User, Server, Query) ->
- case ejabberd_hooks:run_fold(webadmin_user_parse_query, Server, [], [Action, User, Server, Query]) of
- [] -> nothing;
- Res -> Res
+ case ejabberd_hooks:run_fold(webadmin_user_parse_query,
+ Server, [], [Action, User, Server, Query])
+ of
+ [] -> nothing;
+ Res -> Res
end.
-
-
list_last_activity(Host, Lang, Integral, Period) ->
{MegaSecs, Secs, _MicroSecs} = now(),
TimeStamp = MegaSecs * 1000000 + Secs,
case Period of
- "all" ->
- TS = 0,
- Days = infinity;
- "year" ->
- TS = TimeStamp - 366 * 86400,
- Days = 366;
- _ ->
- TS = TimeStamp - 31 * 86400,
- Days = 31
+ <<"all">> -> TS = 0, Days = infinity;
+ <<"year">> -> TS = TimeStamp - 366 * 86400, Days = 366;
+ _ -> TS = TimeStamp - 31 * 86400, Days = 31
end,
- case catch mnesia:dirty_select(
- last_activity, [{{last_activity, {'_', Host}, '$1', '_'},
- [{'>', '$1', TS}],
- [{'trunc', {'/',
- {'-', TimeStamp, '$1'},
- 86400}}]}]) of
- {'EXIT', _Reason} ->
- [];
- Vals ->
- Hist = histogram(Vals, Integral),
- if
- Hist == [] ->
- [?CT("No Data")];
- true ->
- Left = if
- Days == infinity ->
- 0;
- true ->
- Days - length(Hist)
- end,
- Tail = if
- Integral ->
- lists:duplicate(Left, lists:last(Hist));
- true ->
- lists:duplicate(Left, 0)
- end,
- Max = lists:max(Hist),
- [?XAE("ol",
- [{"id", "lastactivity"}, {"start", "0"}],
- [?XAE("li",
- [{"style",
- "width:" ++ integer_to_list(
- trunc(90 * V / Max)) ++ "%;"}],
- [{xmlcdata, pretty_string_int(V)}])
- || V <- Hist ++ Tail])]
- end
+ case catch mnesia:dirty_select(last_activity,
+ [{{last_activity, {'_', Host}, '$1', '_'},
+ [{'>', '$1', TS}],
+ [{trunc,
+ {'/', {'-', TimeStamp, '$1'}, 86400}}]}])
+ of
+ {'EXIT', _Reason} -> [];
+ Vals ->
+ Hist = histogram(Vals, Integral),
+ if Hist == [] -> [?CT(<<"No Data">>)];
+ true ->
+ Left = if Days == infinity -> 0;
+ true -> Days - length(Hist)
+ end,
+ Tail = if Integral ->
+ lists:duplicate(Left, lists:last(Hist));
+ true -> lists:duplicate(Left, 0)
+ end,
+ Max = lists:max(Hist),
+ [?XAE(<<"ol">>,
+ [{<<"id">>, <<"lastactivity">>},
+ {<<"start">>, <<"0">>}],
+ [?XAE(<<"li">>,
+ [{<<"style">>,
+ <<"width:",
+ (iolist_to_binary(integer_to_list(trunc(90 * V
+ /
+ Max))))/binary,
+ "%;">>}],
+ [{xmlcdata, pretty_string_int(V)}])
+ || V <- Hist ++ Tail])]
+ end
end.
histogram(Values, Integral) ->
histogram(lists:sort(Values), Integral, 0, 0, []).
-histogram([H | T], Integral, Current, Count, Hist) when Current == H ->
+histogram([H | T], Integral, Current, Count, Hist)
+ when Current == H ->
histogram(T, Integral, Current, Count + 1, Hist);
-
-histogram([H | _] = Values, Integral, Current, Count, Hist) when Current < H ->
- if
- Integral ->
- histogram(Values, Integral, Current + 1, Count, [Count | Hist]);
- true ->
- histogram(Values, Integral, Current + 1, 0, [Count | Hist])
+histogram([H | _] = Values, Integral, Current, Count,
+ Hist)
+ when Current < H ->
+ if Integral ->
+ histogram(Values, Integral, Current + 1, Count,
+ [Count | Hist]);
+ true ->
+ histogram(Values, Integral, Current + 1, 0,
+ [Count | Hist])
end;
-
histogram([], _Integral, _Current, Count, Hist) ->
- if
- Count > 0 ->
- lists:reverse([Count | Hist]);
- true ->
- lists:reverse(Hist)
+ if Count > 0 -> lists:reverse([Count | Hist]);
+ true -> lists:reverse(Hist)
end.
%%%==================================
@@ -1871,989 +1708,1162 @@ histogram([], _Integral, _Current, Count, Hist) ->
get_nodes(Lang) ->
RunningNodes = mnesia:system_info(running_db_nodes),
- StoppedNodes = lists:usort(mnesia:system_info(db_nodes) ++
- mnesia:system_info(extra_db_nodes)) --
- RunningNodes,
- FRN = if
- RunningNodes == [] ->
- ?CT("None");
- true ->
- ?XE("ul",
- lists:map(
- fun(N) ->
- S = atom_to_list(N),
- ?LI([?AC("../node/" ++ S ++ "/", S)])
- end, lists:sort(RunningNodes)))
+ StoppedNodes = lists:usort(mnesia:system_info(db_nodes)
+ ++ mnesia:system_info(extra_db_nodes))
+ -- RunningNodes,
+ FRN = if RunningNodes == [] -> ?CT(<<"None">>);
+ true ->
+ ?XE(<<"ul">>,
+ (lists:map(fun (N) ->
+ S = iolist_to_binary(atom_to_list(N)),
+ ?LI([?AC(<<"../node/", S/binary, "/">>,
+ S)])
+ end,
+ lists:sort(RunningNodes))))
end,
- FSN = if
- StoppedNodes == [] ->
- ?CT("None");
- true ->
- ?XE("ul",
- lists:map(
- fun(N) ->
- S = atom_to_list(N),
- ?LI([?C(S)])
- end, lists:sort(StoppedNodes)))
+ FSN = if StoppedNodes == [] -> ?CT(<<"None">>);
+ true ->
+ ?XE(<<"ul">>,
+ (lists:map(fun (N) ->
+ S = iolist_to_binary(atom_to_list(N)),
+ ?LI([?C(S)])
+ end,
+ lists:sort(StoppedNodes))))
end,
- [?XCT("h1", "Nodes"),
- ?XCT("h3", "Running Nodes"),
- FRN,
- ?XCT("h3", "Stopped Nodes"),
- FSN].
+ [?XCT(<<"h1">>, <<"Nodes">>),
+ ?XCT(<<"h3">>, <<"Running Nodes">>), FRN,
+ ?XCT(<<"h3">>, <<"Stopped Nodes">>), FSN].
search_running_node(SNode) ->
- search_running_node(SNode, mnesia:system_info(running_db_nodes)).
+ search_running_node(SNode,
+ mnesia:system_info(running_db_nodes)).
-search_running_node(_, []) ->
- false;
+search_running_node(_, []) -> false;
search_running_node(SNode, [Node | Nodes]) ->
- case atom_to_list(Node) of
- SNode ->
- Node;
- _ ->
- search_running_node(SNode, Nodes)
+ case iolist_to_binary(atom_to_list(Node)) of
+ SNode -> Node;
+ _ -> search_running_node(SNode, Nodes)
end.
get_node(global, Node, [], Query, Lang) ->
Res = node_parse_query(Node, Query),
Base = get_base_path(global, Node),
MenuItems2 = make_menu_items(global, Node, Base, Lang),
- [?XC("h1", ?T("Node ") ++ atom_to_list(Node))] ++
- case Res of
- ok -> [?XREST("Submitted")];
- error -> [?XREST("Bad format")];
- nothing -> []
- end ++
- [?XE("ul",
- [?LI([?ACT(Base ++ "db/", "Database")]),
- ?LI([?ACT(Base ++ "backup/", "Backup")]),
- ?LI([?ACT(Base ++ "ports/", "Listened Ports")]),
- ?LI([?ACT(Base ++ "stats/", "Statistics")]),
- ?LI([?ACT(Base ++ "update/", "Update")])
- ] ++ MenuItems2),
- ?XAE("form", [{"action", ""}, {"method", "post"}],
- [?INPUTT("submit", "restart", "Restart"),
- ?C(" "),
- ?INPUTT("submit", "stop", "Stop")])
- ];
-
+ [?XC(<<"h1">>,
+ list_to_binary(io_lib:format(?T(<<"Node ~p">>), [Node])))]
+ ++
+ case Res of
+ ok -> [?XREST(<<"Submitted">>)];
+ error -> [?XREST(<<"Bad format">>)];
+ nothing -> []
+ end
+ ++
+ [?XE(<<"ul">>,
+ ([?LI([?ACT(<<Base/binary, "db/">>, <<"Database">>)]),
+ ?LI([?ACT(<<Base/binary, "backup/">>, <<"Backup">>)]),
+ ?LI([?ACT(<<Base/binary, "ports/">>,
+ <<"Listened Ports">>)]),
+ ?LI([?ACT(<<Base/binary, "stats/">>,
+ <<"Statistics">>)]),
+ ?LI([?ACT(<<Base/binary, "update/">>, <<"Update">>)])]
+ ++ MenuItems2)),
+ ?XAE(<<"form">>,
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ [?INPUTT(<<"submit">>, <<"restart">>, <<"Restart">>),
+ ?C(<<" ">>),
+ ?INPUTT(<<"submit">>, <<"stop">>, <<"Stop">>)])];
get_node(Host, Node, [], _Query, Lang) ->
Base = get_base_path(Host, Node),
MenuItems2 = make_menu_items(Host, Node, Base, Lang),
- [?XC("h1", ?T("Node ") ++ atom_to_list(Node)),
- ?XE("ul",
- [?LI([?ACT(Base ++ "modules/", "Modules")])] ++ MenuItems2)
- ];
-
-get_node(global, Node, ["db"], Query, Lang) ->
+ [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"Node ~p">>), [Node]))),
+ ?XE(<<"ul">>,
+ ([?LI([?ACT(<<Base/binary, "modules/">>,
+ <<"Modules">>)])]
+ ++ MenuItems2))];
+get_node(global, Node, [<<"db">>], Query, Lang) ->
case rpc:call(Node, mnesia, system_info, [tables]) of
- {badrpc, _Reason} ->
- [?XCT("h1", "RPC Call Error")];
- Tables ->
- ResS = case node_db_parse_query(Node, Tables, Query) of
- nothing -> [];
- ok -> [?XREST("Submitted")]
- end,
- STables = lists:sort(Tables),
- Rows = lists:map(
- fun(Table) ->
- STable = atom_to_list(Table),
- TInfo =
- case rpc:call(Node,
- mnesia,
- table_info,
- [Table, all]) of
- {badrpc, _} ->
- [];
- I ->
- I
- end,
- {Type, Size, Memory} =
- case {lists:keysearch(storage_type, 1, TInfo),
- lists:keysearch(size, 1, TInfo),
- lists:keysearch(memory, 1, TInfo)} of
- {{value, {storage_type, T}},
- {value, {size, S}},
- {value, {memory, M}}} ->
- {T, S, M};
- _ ->
- {unknown, 0, 0}
- end,
- ?XE("tr",
- [?XC("td", STable),
- ?XE("td", [db_storage_select(
- STable, Type, Lang)]),
- ?XAC("td", [{"class", "alignright"}],
- pretty_string_int(Size)),
- ?XAC("td", [{"class", "alignright"}],
- pretty_string_int(Memory))
- ])
- end, STables),
- [?XC("h1", ?T("Database Tables at ") ++ atom_to_list(Node))] ++
- ResS ++
- [?XAE("form", [{"action", ""}, {"method", "post"}],
- [?XAE("table", [],
- [?XE("thead",
- [?XE("tr",
- [?XCT("td", "Name"),
- ?XCT("td", "Storage Type"),
- ?XCT("td", "Elements"), %% Elements/items/records inserted in the table
- ?XCT("td", "Memory") %% Words or Bytes allocated to the table on this node
- ])]),
- ?XE("tbody",
- Rows ++
- [?XE("tr",
- [?XAE("td", [{"colspan", "4"},
- {"class", "alignright"}],
- [?INPUTT("submit", "submit",
- "Submit")])
- ])]
- )])])]
+ {badrpc, _Reason} ->
+ [?XCT(<<"h1">>, <<"RPC Call Error">>)];
+ Tables ->
+ ResS = case node_db_parse_query(Node, Tables, Query) of
+ nothing -> [];
+ ok -> [?XREST(<<"Submitted">>)]
+ end,
+ STables = lists:sort(Tables),
+ Rows = lists:map(fun (Table) ->
+ STable =
+ iolist_to_binary(atom_to_list(Table)),
+ TInfo = case rpc:call(Node, mnesia,
+ table_info,
+ [Table, all])
+ of
+ {badrpc, _} -> [];
+ I -> I
+ end,
+ {Type, Size, Memory} = case
+ {lists:keysearch(storage_type,
+ 1,
+ TInfo),
+ lists:keysearch(size,
+ 1,
+ TInfo),
+ lists:keysearch(memory,
+ 1,
+ TInfo)}
+ of
+ {{value,
+ {storage_type,
+ T}},
+ {value, {size, S}},
+ {value,
+ {memory, M}}} ->
+ {T, S, M};
+ _ -> {unknown, 0, 0}
+ end,
+ ?XE(<<"tr">>,
+ [?XC(<<"td">>, STable),
+ ?XE(<<"td">>,
+ [db_storage_select(STable, Type,
+ Lang)]),
+ ?XAC(<<"td">>,
+ [{<<"class">>, <<"alignright">>}],
+ (pretty_string_int(Size))),
+ ?XAC(<<"td">>,
+ [{<<"class">>, <<"alignright">>}],
+ (pretty_string_int(Memory)))])
+ end,
+ STables),
+ [?XC(<<"h1">>,
+ list_to_binary(io_lib:format(?T(<<"Database Tables at ~p">>),
+ [Node]))
+ )]
+ ++
+ ResS ++
+ [?XAE(<<"form">>,
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ [?XAE(<<"table">>, [],
+ [?XE(<<"thead">>,
+ [?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"Name">>),
+ ?XCT(<<"td">>, <<"Storage Type">>),
+ ?XCT(<<"td">>, <<"Elements">>),
+ ?XCT(<<"td">>, <<"Memory">>)])]),
+ ?XE(<<"tbody">>,
+ (Rows ++
+ [?XE(<<"tr">>,
+ [?XAE(<<"td">>,
+ [{<<"colspan">>, <<"4">>},
+ {<<"class">>, <<"alignright">>}],
+ [?INPUTT(<<"submit">>,
+ <<"submit">>,
+ <<"Submit">>)])])]))])])]
end;
-
-get_node(global, Node, ["backup"], Query, Lang) ->
+get_node(global, Node, [<<"backup">>], Query, Lang) ->
HomeDirRaw = case {os:getenv("HOME"), os:type()} of
- {EnvHome, _} when is_list(EnvHome) -> EnvHome;
- {false, {win32, _Osname}} -> "C:/";
- {false, _} -> "/tmp/"
- end,
+ {EnvHome, _} when is_list(EnvHome) -> list_to_binary(EnvHome);
+ {false, {win32, _Osname}} -> <<"C:/">>;
+ {false, _} -> <<"/tmp/">>
+ end,
HomeDir = filename:nativename(HomeDirRaw),
ResS = case node_backup_parse_query(Node, Query) of
- nothing -> [];
- ok -> [?XREST("Submitted")];
- {error, Error} -> [?XRES(?T("Error") ++": " ++ io_lib:format("~p", [Error]))]
+ nothing -> [];
+ ok -> [?XREST(<<"Submitted">>)];
+ {error, Error} ->
+ [?XRES(<<(?T(<<"Error">>))/binary, ": ",
+ (list_to_binary(io_lib:format("~p", [Error])))/binary>>)]
end,
- ?H1GL(?T("Backup of ") ++ atom_to_list(Node), "list-eja-commands", "List of ejabberd Commands") ++
- ResS ++
- [?XCT("p", "Please note that these options will only backup the builtin Mnesia database. If you are using the ODBC module, you also need to backup your SQL database separately."),
- ?XAE("form", [{"action", ""}, {"method", "post"}],
- [?XAE("table", [],
- [?XE("tbody",
- [?XE("tr",
- [?XCT("td", "Store binary backup:"),
- ?XE("td", [?INPUT("text", "storepath",
- filename:join(HomeDir, "ejabberd.backup"))]),
- ?XE("td", [?INPUTT("submit", "store",
- "OK")])
- ]),
- ?XE("tr",
- [?XCT("td", "Restore binary backup immediately:"),
- ?XE("td", [?INPUT("text", "restorepath",
- filename:join(HomeDir, "ejabberd.backup"))]),
- ?XE("td", [?INPUTT("submit", "restore",
- "OK")])
- ]),
- ?XE("tr",
- [?XCT("td",
- "Restore binary backup after next ejabberd restart (requires less memory):"),
- ?XE("td", [?INPUT("text", "fallbackpath",
- filename:join(HomeDir, "ejabberd.backup"))]),
- ?XE("td", [?INPUTT("submit", "fallback",
- "OK")])
- ]),
- ?XE("tr",
- [?XCT("td", "Store plain text backup:"),
- ?XE("td", [?INPUT("text", "dumppath",
- filename:join(HomeDir, "ejabberd.dump"))]),
- ?XE("td", [?INPUTT("submit", "dump",
- "OK")])
- ]),
- ?XE("tr",
- [?XCT("td", "Restore plain text backup immediately:"),
- ?XE("td", [?INPUT("text", "loadpath",
- filename:join(HomeDir, "ejabberd.dump"))]),
- ?XE("td", [?INPUTT("submit", "load",
- "OK")])
- ]),
- ?XE("tr",
- [?XCT("td", "Import users data from a PIEFXIS file (XEP-0227):"),
- ?XE("td", [?INPUT("text", "import_piefxis_filepath",
- filename:join(HomeDir, "users.xml"))]),
- ?XE("td", [?INPUTT("submit", "import_piefxis_file",
- "OK")])
- ]),
- ?XE("tr",
- [?XCT("td", "Export data of all users in the server to PIEFXIS files (XEP-0227):"),
- ?XE("td", [?INPUT("text", "export_piefxis_dirpath",
- HomeDir)]),
- ?XE("td", [?INPUTT("submit", "export_piefxis_dir",
- "OK")])
- ]),
- ?XE("tr",
- [?XE("td", [?CT("Export data of users in a host to PIEFXIS files (XEP-0227):"),
- ?C(" "),
- ?INPUT("text", "export_piefxis_host_dirhost", ?MYNAME)]),
- ?XE("td", [?INPUT("text", "export_piefxis_host_dirpath", HomeDir)]),
- ?XE("td", [?INPUTT("submit", "export_piefxis_host_dir",
- "OK")])
- ]),
- ?XE("tr",
- [?XCT("td", "Import user data from jabberd14 spool file:"),
- ?XE("td", [?INPUT("text", "import_filepath",
- filename:join(HomeDir, "user1.xml"))]),
- ?XE("td", [?INPUTT("submit", "import_file",
- "OK")])
- ]),
- ?XE("tr",
- [?XCT("td", "Import users data from jabberd14 spool directory:"),
- ?XE("td", [?INPUT("text", "import_dirpath",
- "/var/spool/jabber/")]),
- ?XE("td", [?INPUTT("submit", "import_dir",
- "OK")])
- ])
- ])
- ])])];
-
-get_node(global, Node, ["ports"], Query, Lang) ->
- Ports = rpc:call(Node, ejabberd_config, get_local_option, [listen]),
- Res = case catch node_ports_parse_query(Node, Ports, Query) of
- submitted ->
- ok;
- {'EXIT', _Reason} ->
- error;
- {is_added, ok} ->
- ok;
- {is_added, {error, Reason}} ->
- {error, io_lib:format("~p", [Reason])};
- _ ->
- nothing
+ (?H1GL(list_to_binary(io_lib:format(?T(<<"Backup of ~p">>), [Node])),
+ <<"list-eja-commands">>,
+ <<"List of ejabberd Commands">>))
+ ++
+ ResS ++
+ [?XCT(<<"p">>,
+ <<"Please note that these options will "
+ "only backup the builtin Mnesia database. "
+ "If you are using the ODBC module, you "
+ "also need to backup your SQL database "
+ "separately.">>),
+ ?XAE(<<"form">>,
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ [?XAE(<<"table">>, [],
+ [?XE(<<"tbody">>,
+ [?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"Store binary backup:">>),
+ ?XE(<<"td">>,
+ [?INPUT(<<"text">>, <<"storepath">>,
+ (filename:join(HomeDir,
+ "ejabberd.backup")))]),
+ ?XE(<<"td">>,
+ [?INPUTT(<<"submit">>, <<"store">>,
+ <<"OK">>)])]),
+ ?XE(<<"tr">>,
+ [?XCT(<<"td">>,
+ <<"Restore binary backup immediately:">>),
+ ?XE(<<"td">>,
+ [?INPUT(<<"text">>, <<"restorepath">>,
+ (filename:join(HomeDir,
+ "ejabberd.backup")))]),
+ ?XE(<<"td">>,
+ [?INPUTT(<<"submit">>, <<"restore">>,
+ <<"OK">>)])]),
+ ?XE(<<"tr">>,
+ [?XCT(<<"td">>,
+ <<"Restore binary backup after next ejabberd "
+ "restart (requires less memory):">>),
+ ?XE(<<"td">>,
+ [?INPUT(<<"text">>, <<"fallbackpath">>,
+ (filename:join(HomeDir,
+ "ejabberd.backup")))]),
+ ?XE(<<"td">>,
+ [?INPUTT(<<"submit">>, <<"fallback">>,
+ <<"OK">>)])]),
+ ?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"Store plain text backup:">>),
+ ?XE(<<"td">>,
+ [?INPUT(<<"text">>, <<"dumppath">>,
+ (filename:join(HomeDir,
+ "ejabberd.dump")))]),
+ ?XE(<<"td">>,
+ [?INPUTT(<<"submit">>, <<"dump">>,
+ <<"OK">>)])]),
+ ?XE(<<"tr">>,
+ [?XCT(<<"td">>,
+ <<"Restore plain text backup immediately:">>),
+ ?XE(<<"td">>,
+ [?INPUT(<<"text">>, <<"loadpath">>,
+ (filename:join(HomeDir,
+ "ejabberd.dump")))]),
+ ?XE(<<"td">>,
+ [?INPUTT(<<"submit">>, <<"load">>,
+ <<"OK">>)])]),
+ ?XE(<<"tr">>,
+ [?XCT(<<"td">>,
+ <<"Import users data from a PIEFXIS file "
+ "(XEP-0227):">>),
+ ?XE(<<"td">>,
+ [?INPUT(<<"text">>,
+ <<"import_piefxis_filepath">>,
+ (filename:join(HomeDir,
+ "users.xml")))]),
+ ?XE(<<"td">>,
+ [?INPUTT(<<"submit">>,
+ <<"import_piefxis_file">>,
+ <<"OK">>)])]),
+ ?XE(<<"tr">>,
+ [?XCT(<<"td">>,
+ <<"Export data of all users in the server "
+ "to PIEFXIS files (XEP-0227):">>),
+ ?XE(<<"td">>,
+ [?INPUT(<<"text">>,
+ <<"export_piefxis_dirpath">>,
+ HomeDir)]),
+ ?XE(<<"td">>,
+ [?INPUTT(<<"submit">>,
+ <<"export_piefxis_dir">>,
+ <<"OK">>)])]),
+ ?XE(<<"tr">>,
+ [?XE(<<"td">>,
+ [?CT(<<"Export data of users in a host to PIEFXIS "
+ "files (XEP-0227):">>),
+ ?C(<<" ">>),
+ ?INPUT(<<"text">>,
+ <<"export_piefxis_host_dirhost">>,
+ (?MYNAME))]),
+ ?XE(<<"td">>,
+ [?INPUT(<<"text">>,
+ <<"export_piefxis_host_dirpath">>,
+ HomeDir)]),
+ ?XE(<<"td">>,
+ [?INPUTT(<<"submit">>,
+ <<"export_piefxis_host_dir">>,
+ <<"OK">>)])]),
+ ?XE(<<"tr">>,
+ [?XE(<<"td">>,
+ [?CT(<<"Export all tables as SQL queries "
+ "to a file:">>),
+ ?C(<<" ">>),
+ ?INPUT(<<"text">>,
+ <<"export_sql_filehost">>,
+ (?MYNAME))]),
+ ?XE(<<"td">>,
+ [?INPUT(<<"text">>,
+ <<"export_sql_filepath">>,
+ (filename:join(HomeDir,
+ "db.sql")))]),
+ ?XE(<<"td">>,
+ [?INPUTT(<<"submit">>, <<"export_sql_file">>,
+ <<"OK">>)])]),
+ ?XE(<<"tr">>,
+ [?XCT(<<"td">>,
+ <<"Import user data from jabberd14 spool "
+ "file:">>),
+ ?XE(<<"td">>,
+ [?INPUT(<<"text">>, <<"import_filepath">>,
+ (filename:join(HomeDir,
+ "user1.xml")))]),
+ ?XE(<<"td">>,
+ [?INPUTT(<<"submit">>, <<"import_file">>,
+ <<"OK">>)])]),
+ ?XE(<<"tr">>,
+ [?XCT(<<"td">>,
+ <<"Import users data from jabberd14 spool "
+ "directory:">>),
+ ?XE(<<"td">>,
+ [?INPUT(<<"text">>, <<"import_dirpath">>,
+ <<"/var/spool/jabber/">>)]),
+ ?XE(<<"td">>,
+ [?INPUTT(<<"submit">>, <<"import_dir">>,
+ <<"OK">>)])])])])])];
+get_node(global, Node, [<<"ports">>], Query, Lang) ->
+ Ports = rpc:call(Node, ejabberd_config,
+ get_local_option, [listen,
+ {ejabberd_listener, validate_cfg},
+ []]),
+ Res = case catch node_ports_parse_query(Node, Ports,
+ Query)
+ of
+ submitted -> ok;
+ {'EXIT', _Reason} -> error;
+ {is_added, ok} -> ok;
+ {is_added, {error, Reason}} ->
+ {error, iolist_to_binary(io_lib:format("~p", [Reason]))};
+ _ -> nothing
end,
- %% TODO: This sorting does not work when [{{Port, IP}, Module, Opts}]
- NewPorts = lists:sort(
- rpc:call(Node, ejabberd_config, get_local_option, [listen])),
- H1String = ?T("Listened Ports at ") ++ atom_to_list(Node),
- ?H1GL(H1String, "listened", "Listening Ports") ++
- case Res of
- ok -> [?XREST("Submitted")];
- error -> [?XREST("Bad format")];
- {error, ReasonT} -> [?XRES(?T("Error") ++ ": " ++ ReasonT)];
- nothing -> []
- end ++
- [?XAE("form", [{"action", ""}, {"method", "post"}],
- [node_ports_to_xhtml(NewPorts, Lang)])
- ];
-
-get_node(Host, Node, ["modules"], Query, Lang) when is_list(Host) ->
- Modules = rpc:call(Node, gen_mod, loaded_modules_with_opts, [Host]),
- Res = case catch node_modules_parse_query(Host, Node, Modules, Query) of
- submitted ->
- ok;
- {'EXIT', Reason} ->
- ?INFO_MSG("~p~n", [Reason]),
- error;
- _ ->
- nothing
+ NewPorts = lists:sort(rpc:call(Node, ejabberd_config,
+ get_local_option,
+ [listen,
+ {ejabberd_listener, validate_cfg},
+ []])),
+ H1String = <<(?T(<<"Listened Ports at ">>))/binary,
+ (iolist_to_binary(atom_to_list(Node)))/binary>>,
+ (?H1GL(H1String, <<"listened">>, <<"Listening Ports">>))
+ ++
+ case Res of
+ ok -> [?XREST(<<"Submitted">>)];
+ error -> [?XREST(<<"Bad format">>)];
+ {error, ReasonT} ->
+ [?XRES(<<(?T(<<"Error">>))/binary, ": ",
+ ReasonT/binary>>)];
+ nothing -> []
+ end
+ ++
+ [?XAE(<<"form">>,
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ [node_ports_to_xhtml(NewPorts, Lang)])];
+get_node(Host, Node, [<<"modules">>], Query, Lang)
+ when is_binary(Host) ->
+ Modules = rpc:call(Node, gen_mod,
+ loaded_modules_with_opts, [Host]),
+ Res = case catch node_modules_parse_query(Host, Node,
+ Modules, Query)
+ of
+ submitted -> ok;
+ {'EXIT', Reason} -> ?INFO_MSG("~p~n", [Reason]), error;
+ _ -> nothing
end,
- NewModules = lists:sort(
- rpc:call(Node, gen_mod, loaded_modules_with_opts, [Host])),
- H1String = ?T("Modules at ") ++ atom_to_list(Node),
- ?H1GL(H1String, "modoverview", "Modules Overview") ++
- case Res of
- ok -> [?XREST("Submitted")];
- error -> [?XREST("Bad format")];
- nothing -> []
- end ++
- [?XAE("form", [{"action", ""}, {"method", "post"}],
- [node_modules_to_xhtml(NewModules, Lang)])
- ];
-
-get_node(global, Node, ["stats"], _Query, Lang) ->
- UpTime = rpc:call(Node, erlang, statistics, [wall_clock]),
- UpTimeS = io_lib:format("~.3f", [element(1, UpTime)/1000]),
+ NewModules = lists:sort(rpc:call(Node, gen_mod,
+ loaded_modules_with_opts, [Host])),
+ H1String = list_to_binary(io_lib:format(?T(<<"Modules at ~p">>), [Node])),
+ (?H1GL(H1String, <<"modoverview">>,
+ <<"Modules Overview">>))
+ ++
+ case Res of
+ ok -> [?XREST(<<"Submitted">>)];
+ error -> [?XREST(<<"Bad format">>)];
+ nothing -> []
+ end
+ ++
+ [?XAE(<<"form">>,
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ [node_modules_to_xhtml(NewModules, Lang)])];
+get_node(global, Node, [<<"stats">>], _Query, Lang) ->
+ UpTime = rpc:call(Node, erlang, statistics,
+ [wall_clock]),
+ UpTimeS = list_to_binary(io_lib:format("~.3f",
+ [element(1, UpTime) / 1000])),
CPUTime = rpc:call(Node, erlang, statistics, [runtime]),
- CPUTimeS = io_lib:format("~.3f", [element(1, CPUTime)/1000]),
+ CPUTimeS = list_to_binary(io_lib:format("~.3f",
+ [element(1, CPUTime) / 1000])),
OnlineUsers = mnesia:table_info(session, size),
- TransactionsCommitted =
- rpc:call(Node, mnesia, system_info, [transaction_commits]),
- TransactionsAborted =
- rpc:call(Node, mnesia, system_info, [transaction_failures]),
- TransactionsRestarted =
- rpc:call(Node, mnesia, system_info, [transaction_restarts]),
- TransactionsLogged =
- rpc:call(Node, mnesia, system_info, [transaction_log_writes]),
-
- [?XC("h1", io_lib:format(?T("Statistics of ~p"), [Node])),
- ?XAE("table", [],
- [?XE("tbody",
- [?XE("tr", [?XCT("td", "Uptime:"),
- ?XAC("td", [{"class", "alignright"}],
- UpTimeS)]),
- ?XE("tr", [?XCT("td", "CPU Time:"),
- ?XAC("td", [{"class", "alignright"}],
- CPUTimeS)]),
- ?XE("tr", [?XCT("td", "Online Users:"),
- ?XAC("td", [{"class", "alignright"}],
- pretty_string_int(OnlineUsers))]),
- ?XE("tr", [?XCT("td", "Transactions Committed:"),
- ?XAC("td", [{"class", "alignright"}],
- pretty_string_int(TransactionsCommitted))]),
- ?XE("tr", [?XCT("td", "Transactions Aborted:"),
- ?XAC("td", [{"class", "alignright"}],
- pretty_string_int(TransactionsAborted))]),
- ?XE("tr", [?XCT("td", "Transactions Restarted:"),
- ?XAC("td", [{"class", "alignright"}],
- pretty_string_int(TransactionsRestarted))]),
- ?XE("tr", [?XCT("td", "Transactions Logged:"),
- ?XAC("td", [{"class", "alignright"}],
- pretty_string_int(TransactionsLogged))])
- ])
- ])];
-
-get_node(global, Node, ["update"], Query, Lang) ->
+ TransactionsCommitted = rpc:call(Node, mnesia,
+ system_info, [transaction_commits]),
+ TransactionsAborted = rpc:call(Node, mnesia,
+ system_info, [transaction_failures]),
+ TransactionsRestarted = rpc:call(Node, mnesia,
+ system_info, [transaction_restarts]),
+ TransactionsLogged = rpc:call(Node, mnesia, system_info,
+ [transaction_log_writes]),
+ [?XC(<<"h1">>,
+ list_to_binary(io_lib:format(?T(<<"Statistics of ~p">>), [Node]))),
+ ?XAE(<<"table">>, [],
+ [?XE(<<"tbody">>,
+ [?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"Uptime:">>),
+ ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}],
+ UpTimeS)]),
+ ?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"CPU Time:">>),
+ ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}],
+ CPUTimeS)]),
+ ?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"Online Users:">>),
+ ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}],
+ (pretty_string_int(OnlineUsers)))]),
+ ?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"Transactions Committed:">>),
+ ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}],
+ (pretty_string_int(TransactionsCommitted)))]),
+ ?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"Transactions Aborted:">>),
+ ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}],
+ (pretty_string_int(TransactionsAborted)))]),
+ ?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"Transactions Restarted:">>),
+ ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}],
+ (pretty_string_int(TransactionsRestarted)))]),
+ ?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"Transactions Logged:">>),
+ ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}],
+ (pretty_string_int(TransactionsLogged)))])])])];
+get_node(global, Node, [<<"update">>], Query, Lang) ->
rpc:call(Node, code, purge, [ejabberd_update]),
Res = node_update_parse_query(Node, Query),
rpc:call(Node, code, load_file, [ejabberd_update]),
- {ok, _Dir, UpdatedBeams, Script, LowLevelScript, Check} =
+ {ok, _Dir, UpdatedBeams, Script, LowLevelScript,
+ Check} =
rpc:call(Node, ejabberd_update, update_info, []),
- Mods =
- case UpdatedBeams of
- [] ->
- ?CT("None");
- _ ->
- BeamsLis =
- lists:map(
- fun(Beam) ->
- BeamString = atom_to_list(Beam),
- ?LI([
- ?INPUT("checkbox", "selected", BeamString),
- %%?XA("input", [{"checked", ""}, %% Selected by default
- %% {"type", "checkbox"},
- %% {"name", "selected"},
- %% {"value", BeamString}]),
- ?C(BeamString)])
- end,
- UpdatedBeams),
- SelectButtons =
- [?BR,
- ?INPUTATTRS("button", "selectall", "Select All",
- [{"onClick", "selectAll()"}]),
- ?C(" "),
- ?INPUTATTRS("button", "unselectall", "Unselect All",
- [{"onClick", "unSelectAll()"}])],
- %%?XE("ul", BeamsLis)
- ?XAE("ul", [{"class", "nolistyle"}], BeamsLis ++ SelectButtons)
- end,
- FmtScript = ?XC("pre", io_lib:format("~p", [Script])),
- FmtLowLevelScript = ?XC("pre", io_lib:format("~p", [LowLevelScript])),
- [?XC("h1", ?T("Update ") ++ atom_to_list(Node))] ++
- case Res of
- ok -> [?XREST("Submitted")];
- {error, ErrorText} -> [?XREST("Error: " ++ ErrorText)];
- nothing -> []
- end ++
- [?XAE("form", [{"action", ""}, {"method", "post"}],
- [
- ?XCT("h2", "Update plan"),
- ?XCT("h3", "Modified modules"), Mods,
- ?XCT("h3", "Update script"), FmtScript,
- ?XCT("h3", "Low level update script"), FmtLowLevelScript,
- ?XCT("h3", "Script check"), ?XC("pre", atom_to_list(Check)),
+ Mods = case UpdatedBeams of
+ [] -> ?CT(<<"None">>);
+ _ ->
+ BeamsLis = lists:map(fun (Beam) ->
+ BeamString =
+ iolist_to_binary(atom_to_list(Beam)),
+ ?LI([?INPUT(<<"checkbox">>,
+ <<"selected">>,
+ BeamString),
+ ?C(BeamString)])
+ end,
+ UpdatedBeams),
+ SelectButtons = [?BR,
+ ?INPUTATTRS(<<"button">>, <<"selectall">>,
+ <<"Select All">>,
+ [{<<"onClick">>,
+ <<"selectAll()">>}]),
+ ?C(<<" ">>),
+ ?INPUTATTRS(<<"button">>, <<"unselectall">>,
+ <<"Unselect All">>,
+ [{<<"onClick">>,
+ <<"unSelectAll()">>}])],
+ ?XAE(<<"ul">>, [{<<"class">>, <<"nolistyle">>}],
+ (BeamsLis ++ SelectButtons))
+ end,
+ FmtScript = (?XC(<<"pre">>,
+ list_to_binary(io_lib:format("~p", [Script])))),
+ FmtLowLevelScript = (?XC(<<"pre">>,
+ list_to_binary(io_lib:format("~p", [LowLevelScript])))),
+ [?XC(<<"h1">>,
+ list_to_binary(io_lib:format(?T(<<"Update ~p">>), [Node])))]
+ ++
+ case Res of
+ ok -> [?XREST(<<"Submitted">>)];
+ {error, ErrorText} ->
+ [?XREST(<<"Error: ", ErrorText/binary>>)];
+ nothing -> []
+ end
+ ++
+ [?XAE(<<"form">>,
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ [?XCT(<<"h2">>, <<"Update plan">>),
+ ?XCT(<<"h3">>, <<"Modified modules">>), Mods,
+ ?XCT(<<"h3">>, <<"Update script">>), FmtScript,
+ ?XCT(<<"h3">>, <<"Low level update script">>),
+ FmtLowLevelScript, ?XCT(<<"h3">>, <<"Script check">>),
+ ?XC(<<"pre">>, (iolist_to_binary(Check))),
?BR,
- ?INPUTT("submit", "update", "Update")
- ])
- ];
-
+ ?INPUTT(<<"submit">>, <<"update">>, <<"Update">>)])];
get_node(Host, Node, NPath, Query, Lang) ->
{Hook, Opts} = case Host of
- global -> {webadmin_page_node, [Node, NPath, Query, Lang]};
- Host -> {webadmin_page_hostnode, [Host, Node, NPath, Query, Lang]}
+ global ->
+ {webadmin_page_node, [Node, NPath, Query, Lang]};
+ Host ->
+ {webadmin_page_hostnode,
+ [Host, Node, NPath, Query, Lang]}
end,
case ejabberd_hooks:run_fold(Hook, Host, [], Opts) of
- [] -> [?XC("h1", "Not Found")];
- Res -> Res
+ [] -> [?XC(<<"h1">>, <<"Not Found">>)];
+ Res -> Res
end.
%%%==================================
%%%% node parse
node_parse_query(Node, Query) ->
- case lists:keysearch("restart", 1, Query) of
- {value, _} ->
- case rpc:call(Node, init, restart, []) of
- {badrpc, _Reason} ->
- error;
- _ ->
- ok
- end;
- _ ->
- case lists:keysearch("stop", 1, Query) of
- {value, _} ->
- case rpc:call(Node, init, stop, []) of
- {badrpc, _Reason} ->
- error;
- _ ->
- ok
- end;
- _ ->
- nothing
- end
+ case lists:keysearch(<<"restart">>, 1, Query) of
+ {value, _} ->
+ case rpc:call(Node, init, restart, []) of
+ {badrpc, _Reason} -> error;
+ _ -> ok
+ end;
+ _ ->
+ case lists:keysearch(<<"stop">>, 1, Query) of
+ {value, _} ->
+ case rpc:call(Node, init, stop, []) of
+ {badrpc, _Reason} -> error;
+ _ -> ok
+ end;
+ _ -> nothing
+ end
end.
-
db_storage_select(ID, Opt, Lang) ->
- ?XAE("select", [{"name", "table" ++ ID}],
- lists:map(
- fun({O, Desc}) ->
- Sel = if
- O == Opt -> [{"selected", "selected"}];
- true -> []
- end,
- ?XACT("option",
- Sel ++ [{"value", atom_to_list(O)}],
- Desc)
- end, [{ram_copies, "RAM copy"},
- {disc_copies, "RAM and disc copy"},
- {disc_only_copies, "Disc only copy"},
- {unknown, "Remote copy"},
- {delete_content, "Delete content"},
- {delete_table, "Delete table"}])).
-
-node_db_parse_query(_Node, _Tables, [{nokey,[]}]) ->
+ ?XAE(<<"select">>,
+ [{<<"name">>, <<"table", ID/binary>>}],
+ (lists:map(fun ({O, Desc}) ->
+ Sel = if O == Opt ->
+ [{<<"selected">>, <<"selected">>}];
+ true -> []
+ end,
+ ?XACT(<<"option">>,
+ (Sel ++
+ [{<<"value">>,
+ iolist_to_binary(atom_to_list(O))}]),
+ Desc)
+ end,
+ [{ram_copies, <<"RAM copy">>},
+ {disc_copies, <<"RAM and disc copy">>},
+ {disc_only_copies, <<"Disc only copy">>},
+ {unknown, <<"Remote copy">>},
+ {delete_content, <<"Delete content">>},
+ {delete_table, <<"Delete table">>}]))).
+
+node_db_parse_query(_Node, _Tables, [{nokey, <<>>}]) ->
nothing;
node_db_parse_query(Node, Tables, Query) ->
- lists:foreach(
- fun(Table) ->
- STable = atom_to_list(Table),
- case lists:keysearch("table" ++ STable, 1, Query) of
- {value, {_, SType}} ->
- Type = case SType of
- "unknown" -> unknown;
- "ram_copies" -> ram_copies;
- "disc_copies" -> disc_copies;
- "disc_only_copies" -> disc_only_copies;
- "delete_content" -> delete_content;
- "delete_table" -> delete_table;
- _ -> false
- end,
- if
- Type == false ->
- ok;
- Type == delete_content ->
- mnesia:clear_table(Table);
- Type == delete_table ->
- mnesia:delete_table(Table);
- Type == unknown ->
- mnesia:del_table_copy(Table, Node);
- true ->
- case mnesia:add_table_copy(Table, Node, Type) of
- {aborted, _} ->
- mnesia:change_table_copy_type(
- Table, Node, Type);
- _ ->
- ok
- end
- end;
- _ ->
- ok
- end
- end, Tables),
+ lists:foreach(fun (Table) ->
+ STable = iolist_to_binary(atom_to_list(Table)),
+ case lists:keysearch(<<"table", STable/binary>>, 1,
+ Query)
+ of
+ {value, {_, SType}} ->
+ Type = case SType of
+ <<"unknown">> -> unknown;
+ <<"ram_copies">> -> ram_copies;
+ <<"disc_copies">> -> disc_copies;
+ <<"disc_only_copies">> ->
+ disc_only_copies;
+ <<"delete_content">> -> delete_content;
+ <<"delete_table">> -> delete_table;
+ _ -> false
+ end,
+ if Type == false -> ok;
+ Type == delete_content ->
+ mnesia:clear_table(Table);
+ Type == delete_table ->
+ mnesia:delete_table(Table);
+ Type == unknown ->
+ mnesia:del_table_copy(Table, Node);
+ true ->
+ case mnesia:add_table_copy(Table, Node,
+ Type)
+ of
+ {aborted, _} ->
+ mnesia:change_table_copy_type(Table,
+ Node,
+ Type);
+ _ -> ok
+ end
+ end;
+ _ -> ok
+ end
+ end,
+ Tables),
ok.
-node_backup_parse_query(_Node, [{nokey,[]}]) ->
+node_backup_parse_query(_Node, [{nokey, <<>>}]) ->
nothing;
node_backup_parse_query(Node, Query) ->
- lists:foldl(
- fun(Action, nothing) ->
- case lists:keysearch(Action, 1, Query) of
- {value, _} ->
- case lists:keysearch(Action ++ "path", 1, Query) of
- {value, {_, Path}} ->
- Res =
- case Action of
- "store" ->
- rpc:call(Node, mnesia,
- backup, [Path]);
- "restore" ->
- rpc:call(Node, ejabberd_admin,
- restore, [Path]);
- "fallback" ->
- rpc:call(Node, mnesia,
- install_fallback, [Path]);
- "dump" ->
- rpc:call(Node, ejabberd_admin,
- dump_to_textfile, [Path]);
- "load" ->
- rpc:call(Node, mnesia,
- load_textfile, [Path]);
- "import_piefxis_file" ->
- rpc:call(Node, ejabberd_piefxis,
- import_file, [Path]);
- "export_piefxis_dir" ->
- rpc:call(Node, ejabberd_piefxis,
- export_server, [Path]);
- "export_piefxis_host_dir" ->
- {value, {_, Host}} = lists:keysearch(Action ++ "host", 1, Query),
- rpc:call(Node, ejabberd_piefxis,
- export_host, [Path, Host]);
- "import_file" ->
- rpc:call(Node, ejabberd_admin,
- import_file, [Path]);
- "import_dir" ->
- rpc:call(Node, ejabberd_admin,
- import_dir, [Path])
- end,
- case Res of
- {error, Reason} ->
- {error, Reason};
- {badrpc, Reason} ->
- {badrpc, Reason};
- _ ->
- ok
+ lists:foldl(fun (Action, nothing) ->
+ case lists:keysearch(Action, 1, Query) of
+ {value, _} ->
+ case lists:keysearch(<<Action/binary, "path">>, 1,
+ Query)
+ of
+ {value, {_, Path}} ->
+ Res = case Action of
+ <<"store">> ->
+ rpc:call(Node, mnesia, backup,
+ [binary_to_list(Path)]);
+ <<"restore">> ->
+ rpc:call(Node, ejabberd_admin,
+ restore, [Path]);
+ <<"fallback">> ->
+ rpc:call(Node, mnesia,
+ install_fallback,
+ [binary_to_list(Path)]);
+ <<"dump">> ->
+ rpc:call(Node, ejabberd_admin,
+ dump_to_textfile,
+ [Path]);
+ <<"load">> ->
+ rpc:call(Node, mnesia,
+ load_textfile,
+ [binary_to_list(Path)]);
+ <<"import_piefxis_file">> ->
+ rpc:call(Node, ejabberd_piefxis,
+ import_file, [Path]);
+ <<"export_piefxis_dir">> ->
+ rpc:call(Node, ejabberd_piefxis,
+ export_server, [Path]);
+ <<"export_piefxis_host_dir">> ->
+ {value, {_, Host}} =
+ lists:keysearch(<<Action/binary,
+ "host">>,
+ 1, Query),
+ rpc:call(Node, ejabberd_piefxis,
+ export_host,
+ [Path, Host]);
+ <<"export_sql_file">> ->
+ {value, {_, Host}} =
+ lists:keysearch(<<Action/binary,
+ "host">>,
+ 1, Query),
+ rpc:call(Node, ejd2odbc,
+ export, [Host, Path]);
+ <<"import_file">> ->
+ rpc:call(Node, ejabberd_admin,
+ import_file, [Path]);
+ <<"import_dir">> ->
+ rpc:call(Node, ejabberd_admin,
+ import_dir, [Path])
+ end,
+ case Res of
+ {error, Reason} -> {error, Reason};
+ {badrpc, Reason} -> {badrpc, Reason};
+ _ -> ok
+ end;
+ OtherError -> {error, OtherError}
end;
- OtherError ->
- {error, OtherError}
- end;
- _ ->
- nothing
- end;
- (_Action, Res) ->
- Res
- end, nothing, ["store", "restore", "fallback", "dump", "load", "import_file", "import_dir",
- "import_piefxis_file", "export_piefxis_dir", "export_piefxis_host_dir"]).
+ _ -> nothing
+ end;
+ (_Action, Res) -> Res
+ end,
+ nothing,
+ [<<"store">>, <<"restore">>, <<"fallback">>, <<"dump">>,
+ <<"load">>, <<"import_file">>, <<"import_dir">>,
+ <<"import_piefxis_file">>, <<"export_piefxis_dir">>,
+ <<"export_piefxis_host_dir">>, <<"export_sql_file">>]).
node_ports_to_xhtml(Ports, Lang) ->
- ?XAE("table", [{"class", "withtextareas"}],
- [?XE("thead",
- [?XE("tr",
- [?XCT("td", "Port"),
- ?XCT("td", "IP"),
- ?XCT("td", "Protocol"),
- ?XCT("td", "Module"),
- ?XCT("td", "Options")
- ])]),
- ?XE("tbody",
- lists:map(
- fun({PortIP, Module, Opts} = _E) ->
- {_Port, SPort, _TIP, SIP, SSPort, NetProt, OptsClean} =
- get_port_data(PortIP, Opts),
- SModule = atom_to_list(Module),
- {NumLines, SOptsClean} = term_to_paragraph(OptsClean, 40),
- %%ID = term_to_id(E),
- ?XE("tr",
- [?XAE("td", [{"size", "6"}], [?C(SPort)]),
- ?XAE("td", [{"size", "15"}], [?C(SIP)]),
- ?XAE("td", [{"size", "4"}], [?C(atom_to_list(NetProt))]),
- ?XE("td", [?INPUTS("text", "module" ++ SSPort,
- SModule, "15")]),
- ?XE("td", [?TEXTAREA("opts" ++ SSPort, integer_to_list(NumLines), "35", SOptsClean)]),
- ?XE("td", [?INPUTT("submit", "add" ++ SSPort,
- "Update")]),
- ?XE("td", [?INPUTT("submit", "delete" ++ SSPort,
- "Delete")])
- ]
- )
- end, Ports) ++
- [?XE("tr",
- [?XE("td", [?INPUTS("text", "portnew", "", "6")]),
- ?XE("td", [?INPUTS("text", "ipnew", "0.0.0.0", "15")]),
- ?XE("td", [make_netprot_html("tcp")]),
- ?XE("td", [?INPUTS("text", "modulenew", "", "15")]),
- ?XE("td", [?TEXTAREA("optsnew", "2", "35", "[]")]),
- ?XAE("td", [{"colspan", "2"}],
- [?INPUTT("submit", "addnew", "Add New")])
- ]
- )]
- )]).
+ ?XAE(<<"table">>, [{<<"class">>, <<"withtextareas">>}],
+ [?XE(<<"thead">>,
+ [?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"Port">>), ?XCT(<<"td">>, <<"IP">>),
+ ?XCT(<<"td">>, <<"Protocol">>),
+ ?XCT(<<"td">>, <<"Module">>),
+ ?XCT(<<"td">>, <<"Options">>)])]),
+ ?XE(<<"tbody">>,
+ (lists:map(fun ({PortIP, Module, Opts} = _E) ->
+ {_Port, SPort, _TIP, SIP, SSPort, NetProt,
+ OptsClean} =
+ get_port_data(PortIP, Opts),
+ SModule =
+ iolist_to_binary(atom_to_list(Module)),
+ {NumLines, SOptsClean} =
+ term_to_paragraph(OptsClean, 40),
+ ?XE(<<"tr">>,
+ [?XAE(<<"td">>, [{<<"size">>, <<"6">>}],
+ [?C(SPort)]),
+ ?XAE(<<"td">>, [{<<"size">>, <<"15">>}],
+ [?C(SIP)]),
+ ?XAE(<<"td">>, [{<<"size">>, <<"4">>}],
+ [?C((iolist_to_binary(atom_to_list(NetProt))))]),
+ ?XE(<<"td">>,
+ [?INPUTS(<<"text">>,
+ <<"module", SSPort/binary>>,
+ SModule, <<"15">>)]),
+ ?XE(<<"td">>,
+ [?TEXTAREA(<<"opts", SSPort/binary>>,
+ (iolist_to_binary(integer_to_list(NumLines))),
+ <<"35">>, SOptsClean)]),
+ ?XE(<<"td">>,
+ [?INPUTT(<<"submit">>,
+ <<"add", SSPort/binary>>,
+ <<"Update">>)]),
+ ?XE(<<"td">>,
+ [?INPUTT(<<"submit">>,
+ <<"delete", SSPort/binary>>,
+ <<"Delete">>)])])
+ end,
+ Ports)
+ ++
+ [?XE(<<"tr">>,
+ [?XE(<<"td">>,
+ [?INPUTS(<<"text">>, <<"portnew">>, <<"">>,
+ <<"6">>)]),
+ ?XE(<<"td">>,
+ [?INPUTS(<<"text">>, <<"ipnew">>, <<"0.0.0.0">>,
+ <<"15">>)]),
+ ?XE(<<"td">>, [make_netprot_html(<<"tcp">>)]),
+ ?XE(<<"td">>,
+ [?INPUTS(<<"text">>, <<"modulenew">>, <<"">>,
+ <<"15">>)]),
+ ?XE(<<"td">>,
+ [?TEXTAREA(<<"optsnew">>, <<"2">>, <<"35">>,
+ <<"[]">>)]),
+ ?XAE(<<"td">>, [{<<"colspan">>, <<"2">>}],
+ [?INPUTT(<<"submit">>, <<"addnew">>,
+ <<"Add New">>)])])]))]).
make_netprot_html(NetProt) ->
- ?XAE("select", [{"name", "netprotnew"}],
- lists:map(
- fun(O) ->
- Sel = if
- O == NetProt -> [{"selected", "selected"}];
- true -> []
- end,
- ?XAC("option",
- Sel ++ [{"value", O}],
- O)
- end, ["tcp", "udp"])).
+ ?XAE(<<"select">>, [{<<"name">>, <<"netprotnew">>}],
+ (lists:map(fun (O) ->
+ Sel = if O == NetProt ->
+ [{<<"selected">>, <<"selected">>}];
+ true -> []
+ end,
+ ?XAC(<<"option">>, (Sel ++ [{<<"value">>, O}]), O)
+ end,
+ [<<"tcp">>, <<"udp">>]))).
get_port_data(PortIP, Opts) ->
- {Port, IPT, IPS, _IPV, NetProt, OptsClean} = ejabberd_listener:parse_listener_portip(PortIP, Opts),
- SPort = io_lib:format("~p", [Port]),
-
- SSPort = lists:flatten(
- lists:map(
- fun(N) -> io_lib:format("~.16b", [N]) end,
- binary_to_list(crypto:md5(SPort++IPS++atom_to_list(NetProt))))),
+ {Port, IPT, IPS, _IPV, NetProt, OptsClean} =
+ ejabberd_listener:parse_listener_portip(PortIP, Opts),
+ SPort = jlib:integer_to_binary(Port),
+ SSPort = list_to_binary(
+ lists:map(fun (N) ->
+ io_lib:format("~.16b", [N])
+ end,
+ binary_to_list(
+ crypto:md5(
+ [SPort, IPS, atom_to_list(NetProt)])))),
{Port, SPort, IPT, IPS, SSPort, NetProt, OptsClean}.
-
node_ports_parse_query(Node, Ports, Query) ->
- lists:foreach(
- fun({PortIpNetp, Module1, Opts1}) ->
- {Port, _SPort, TIP, _SIP, SSPort, NetProt, _OptsClean} =
- get_port_data(PortIpNetp, Opts1),
- case lists:keysearch("add" ++ SSPort, 1, Query) of
- {value, _} ->
- PortIpNetp2 = {Port, TIP, NetProt},
- {{value, {_, SModule}}, {value, {_, SOpts}}} =
- {lists:keysearch("module" ++ SSPort, 1, Query),
- lists:keysearch("opts" ++ SSPort, 1, Query)},
- Module = list_to_atom(SModule),
- {ok, Tokens, _} = erl_scan:string(SOpts ++ "."),
- {ok, Opts} = erl_parse:parse_term(Tokens),
- rpc:call(Node, ejabberd_listener, delete_listener,
- [PortIpNetp2, Module1]),
- R=rpc:call(Node, ejabberd_listener, add_listener,
- [PortIpNetp2, Module, Opts]),
- throw({is_added, R});
- _ ->
- case lists:keysearch("delete" ++ SSPort, 1, Query) of
- {value, _} ->
- rpc:call(Node, ejabberd_listener, delete_listener,
- [PortIpNetp, Module1]),
- throw(submitted);
- _ ->
- ok
- end
- end
- end, Ports),
- case lists:keysearch("addnew", 1, Query) of
- {value, _} ->
- {{value, {_, SPort}},
- {value, {_, STIP}}, %% It is a string that may represent a tuple
- {value, {_, SNetProt}},
- {value, {_, SModule}},
- {value, {_, SOpts}}} =
- {lists:keysearch("portnew", 1, Query),
- lists:keysearch("ipnew", 1, Query),
- lists:keysearch("netprotnew", 1, Query),
- lists:keysearch("modulenew", 1, Query),
- lists:keysearch("optsnew", 1, Query)},
- {ok, Toks, _} = erl_scan:string(SPort ++ "."),
- {ok, Port2} = erl_parse:parse_term(Toks),
- {ok, ToksIP, _} = erl_scan:string(STIP ++ "."),
- STIP2 = case erl_parse:parse_term(ToksIP) of
- {ok, IPTParsed} -> IPTParsed;
- {error, _} -> STIP
- end,
- Module = list_to_atom(SModule),
- NetProt2 = list_to_atom(SNetProt),
- {ok, Tokens, _} = erl_scan:string(SOpts ++ "."),
- {ok, Opts} = erl_parse:parse_term(Tokens),
- {Port2, _SPort, IP2, _SIP, _SSPort, NetProt2, OptsClean} =
- get_port_data({Port2, STIP2, NetProt2}, Opts),
- R=rpc:call(Node, ejabberd_listener, add_listener,
+ lists:foreach(fun ({PortIpNetp, Module1, Opts1}) ->
+ {Port, _SPort, TIP, _SIP, SSPort, NetProt,
+ _OptsClean} =
+ get_port_data(PortIpNetp, Opts1),
+ case lists:keysearch(<<"add", SSPort/binary>>, 1,
+ Query)
+ of
+ {value, _} ->
+ PortIpNetp2 = {Port, TIP, NetProt},
+ {{value, {_, SModule}}, {value, {_, SOpts}}} =
+ {lists:keysearch(<<"module",
+ SSPort/binary>>,
+ 1, Query),
+ lists:keysearch(<<"opts", SSPort/binary>>,
+ 1, Query)},
+ Module = jlib:binary_to_atom(SModule),
+ {ok, Tokens, _} =
+ erl_scan:string(binary_to_list(SOpts) ++ "."),
+ {ok, Opts} = erl_parse:parse_term(Tokens),
+ rpc:call(Node, ejabberd_listener,
+ delete_listener,
+ [PortIpNetp2, Module1]),
+ R = rpc:call(Node, ejabberd_listener,
+ add_listener,
+ [PortIpNetp2, Module, Opts]),
+ throw({is_added, R});
+ _ ->
+ case lists:keysearch(<<"delete",
+ SSPort/binary>>,
+ 1, Query)
+ of
+ {value, _} ->
+ rpc:call(Node, ejabberd_listener,
+ delete_listener,
+ [PortIpNetp, Module1]),
+ throw(submitted);
+ _ -> ok
+ end
+ end
+ end,
+ Ports),
+ case lists:keysearch(<<"addnew">>, 1, Query) of
+ {value, _} ->
+ {{value, {_, SPort}}, {value, {_, STIP}},
+ {value, {_, SNetProt}}, {value, {_, SModule}},
+ {value, {_, SOpts}}} =
+ {lists:keysearch(<<"portnew">>, 1, Query),
+ lists:keysearch(<<"ipnew">>, 1, Query),
+ lists:keysearch(<<"netprotnew">>, 1, Query),
+ lists:keysearch(<<"modulenew">>, 1, Query),
+ lists:keysearch(<<"optsnew">>, 1, Query)},
+ {ok, Toks, _} = erl_scan:string(binary_to_list(<<SPort/binary, ".">>)),
+ {ok, Port2} = erl_parse:parse_term(Toks),
+ {ok, ToksIP, _} = erl_scan:string(binary_to_list(<<STIP/binary, ".">>)),
+ STIP2 = case erl_parse:parse_term(ToksIP) of
+ {ok, IPTParsed} -> IPTParsed;
+ {error, _} -> STIP
+ end,
+ Module = jlib:binary_to_atom(SModule),
+ NetProt2 = jlib:binary_to_atom(SNetProt),
+ {ok, Tokens, _} = erl_scan:string(binary_to_list(<<SOpts/binary, ".">>)),
+ {ok, Opts} = erl_parse:parse_term(Tokens),
+ {Port2, _SPort, IP2, _SIP, _SSPort, NetProt2,
+ OptsClean} =
+ get_port_data({Port2, STIP2, NetProt2}, Opts),
+ R = rpc:call(Node, ejabberd_listener, add_listener,
[{Port2, IP2, NetProt2}, Module, OptsClean]),
- throw({is_added, R});
- _ ->
- ok
+ throw({is_added, R});
+ _ -> ok
end.
node_modules_to_xhtml(Modules, Lang) ->
- ?XAE("table", [{"class", "withtextareas"}],
- [?XE("thead",
- [?XE("tr",
- [?XCT("td", "Module"),
- ?XCT("td", "Options")
- ])]),
- ?XE("tbody",
- lists:map(
- fun({Module, Opts} = _E) ->
- SModule = atom_to_list(Module),
- {NumLines, SOpts} = term_to_paragraph(Opts, 40),
- %%ID = term_to_id(E),
- ?XE("tr",
- [?XC("td", SModule),
- ?XE("td", [?TEXTAREA("opts" ++ SModule, integer_to_list(NumLines), "40", SOpts)]),
- ?XE("td", [?INPUTT("submit", "restart" ++ SModule,
- "Restart")]),
- ?XE("td", [?INPUTT("submit", "stop" ++ SModule,
- "Stop")])
- ]
- )
- end, Modules) ++
- [?XE("tr",
- [?XE("td", [?INPUT("text", "modulenew", "")]),
- ?XE("td", [?TEXTAREA("optsnew", "2", "40", "[]")]),
- ?XAE("td", [{"colspan", "2"}],
- [?INPUTT("submit", "start", "Start")])
- ]
- )]
- )]).
+ ?XAE(<<"table">>, [{<<"class">>, <<"withtextareas">>}],
+ [?XE(<<"thead">>,
+ [?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"Module">>),
+ ?XCT(<<"td">>, <<"Options">>)])]),
+ ?XE(<<"tbody">>,
+ (lists:map(fun ({Module, Opts} = _E) ->
+ SModule =
+ iolist_to_binary(atom_to_list(Module)),
+ {NumLines, SOpts} = term_to_paragraph(Opts,
+ 40),
+ ?XE(<<"tr">>,
+ [?XC(<<"td">>, SModule),
+ ?XE(<<"td">>,
+ [?TEXTAREA(<<"opts", SModule/binary>>,
+ (iolist_to_binary(integer_to_list(NumLines))),
+ <<"40">>, SOpts)]),
+ ?XE(<<"td">>,
+ [?INPUTT(<<"submit">>,
+ <<"restart",
+ SModule/binary>>,
+ <<"Restart">>)]),
+ ?XE(<<"td">>,
+ [?INPUTT(<<"submit">>,
+ <<"stop", SModule/binary>>,
+ <<"Stop">>)])])
+ end,
+ Modules)
+ ++
+ [?XE(<<"tr">>,
+ [?XE(<<"td">>,
+ [?INPUT(<<"text">>, <<"modulenew">>, <<"">>)]),
+ ?XE(<<"td">>,
+ [?TEXTAREA(<<"optsnew">>, <<"2">>, <<"40">>,
+ <<"[]">>)]),
+ ?XAE(<<"td">>, [{<<"colspan">>, <<"2">>}],
+ [?INPUTT(<<"submit">>, <<"start">>,
+ <<"Start">>)])])]))]).
node_modules_parse_query(Host, Node, Modules, Query) ->
- lists:foreach(
- fun({Module, _Opts1}) ->
- SModule = atom_to_list(Module),
- case lists:keysearch("restart" ++ SModule, 1, Query) of
- {value, _} ->
- {value, {_, SOpts}} =
- lists:keysearch("opts" ++ SModule, 1, Query),
- {ok, Tokens, _} = erl_scan:string(SOpts ++ "."),
- {ok, Opts} = erl_parse:parse_term(Tokens),
- rpc:call(Node, gen_mod, stop_module, [Host, Module]),
- rpc:call(Node, gen_mod, start_module, [Host, Module, Opts]),
- throw(submitted);
- _ ->
- case lists:keysearch("stop" ++ SModule, 1, Query) of
- {value, _} ->
- rpc:call(Node, gen_mod, stop_module, [Host, Module]),
- throw(submitted);
- _ ->
- ok
- end
- end
- end, Modules),
- case lists:keysearch("start", 1, Query) of
- {value, _} ->
- {{value, {_, SModule}},
- {value, {_, SOpts}}} =
- {lists:keysearch("modulenew", 1, Query),
- lists:keysearch("optsnew", 1, Query)},
- Module = list_to_atom(SModule),
- {ok, Tokens, _} = erl_scan:string(SOpts ++ "."),
- {ok, Opts} = erl_parse:parse_term(Tokens),
- rpc:call(Node, gen_mod, start_module, [Host, Module, Opts]),
- throw(submitted);
- _ ->
- ok
+ lists:foreach(fun ({Module, _Opts1}) ->
+ SModule = iolist_to_binary(atom_to_list(Module)),
+ case lists:keysearch(<<"restart", SModule/binary>>, 1,
+ Query)
+ of
+ {value, _} ->
+ {value, {_, SOpts}} = lists:keysearch(<<"opts",
+ SModule/binary>>,
+ 1, Query),
+ {ok, Tokens, _} =
+ erl_scan:string(binary_to_list(<<SOpts/binary, ".">>)),
+ {ok, Opts} = erl_parse:parse_term(Tokens),
+ rpc:call(Node, gen_mod, stop_module,
+ [Host, Module]),
+ rpc:call(Node, gen_mod, start_module,
+ [Host, Module, Opts]),
+ throw(submitted);
+ _ ->
+ case lists:keysearch(<<"stop", SModule/binary>>,
+ 1, Query)
+ of
+ {value, _} ->
+ rpc:call(Node, gen_mod, stop_module,
+ [Host, Module]),
+ throw(submitted);
+ _ -> ok
+ end
+ end
+ end,
+ Modules),
+ case lists:keysearch(<<"start">>, 1, Query) of
+ {value, _} ->
+ {{value, {_, SModule}}, {value, {_, SOpts}}} =
+ {lists:keysearch(<<"modulenew">>, 1, Query),
+ lists:keysearch(<<"optsnew">>, 1, Query)},
+ Module = jlib:binary_to_atom(SModule),
+ {ok, Tokens, _} = erl_scan:string(binary_to_list(<<SOpts/binary, ".">>)),
+ {ok, Opts} = erl_parse:parse_term(Tokens),
+ rpc:call(Node, gen_mod, start_module,
+ [Host, Module, Opts]),
+ throw(submitted);
+ _ -> ok
end.
-
node_update_parse_query(Node, Query) ->
- case lists:keysearch("update", 1, Query) of
- {value, _} ->
- ModulesToUpdateStrings = proplists:get_all_values("selected",Query),
- ModulesToUpdate = [list_to_atom(M) || M <- ModulesToUpdateStrings],
- case rpc:call(Node, ejabberd_update, update, [ModulesToUpdate]) of
- {ok, _} ->
- ok;
- {error, Error} ->
- ?ERROR_MSG("~p~n", [Error]),
- {error, io_lib:format("~p", [Error])};
- {badrpc, Error} ->
- ?ERROR_MSG("Bad RPC: ~p~n", [Error]),
- {error, "Bad RPC: " ++ io_lib:format("~p", [Error])}
- end;
- _ ->
- nothing
+ case lists:keysearch(<<"update">>, 1, Query) of
+ {value, _} ->
+ ModulesToUpdateStrings =
+ proplists:get_all_values(<<"selected">>, Query),
+ ModulesToUpdate = [jlib:binary_to_atom(M)
+ || M <- ModulesToUpdateStrings],
+ case rpc:call(Node, ejabberd_update, update,
+ [ModulesToUpdate])
+ of
+ {ok, _} -> ok;
+ {error, Error} ->
+ ?ERROR_MSG("~p~n", [Error]),
+ {error, iolist_to_binary(io_lib:format("~p", [Error]))};
+ {badrpc, Error} ->
+ ?ERROR_MSG("Bad RPC: ~p~n", [Error]),
+ {error,
+ <<"Bad RPC: ", (iolist_to_binary(io_lib:format("~p", [Error])))/binary>>}
+ end;
+ _ -> nothing
end.
-
pretty_print_xml(El) ->
- lists:flatten(pretty_print_xml(El, "")).
+ list_to_binary(pretty_print_xml(El, <<"">>)).
pretty_print_xml({xmlcdata, CData}, Prefix) ->
- [Prefix, CData, $\n];
-pretty_print_xml({xmlelement, Name, Attrs, Els}, Prefix) ->
+ IsBlankCData = lists:all(
+ fun($\f) -> true;
+ ($\r) -> true;
+ ($\n) -> true;
+ ($\t) -> true;
+ ($\v) -> true;
+ ($ ) -> true;
+ (_) -> false
+ end, binary_to_list(CData)),
+ if IsBlankCData ->
+ [];
+ true ->
+ [Prefix, CData, $\n]
+ end;
+pretty_print_xml(#xmlel{name = Name, attrs = Attrs,
+ children = Els},
+ Prefix) ->
[Prefix, $<, Name,
case Attrs of
- [] ->
- [];
- [{Attr, Val} | RestAttrs] ->
- AttrPrefix = [Prefix,
- string:copies(" ", length(Name) + 2)],
- [$\s, Attr, $=, $', xml:crypt(Val), $' |
- lists:map(fun({Attr1, Val1}) ->
- [$\n, AttrPrefix,
- Attr1, $=, $', xml:crypt(Val1), $']
- end, RestAttrs)]
+ [] -> [];
+ [{Attr, Val} | RestAttrs] ->
+ AttrPrefix = [Prefix,
+ str:copies(<<" ">>, byte_size(Name) + 2)],
+ [$\s, Attr, $=, $', xml:crypt(Val) | [$',
+ lists:map(fun ({Attr1,
+ Val1}) ->
+ [$\n,
+ AttrPrefix,
+ Attr1, $=,
+ $',
+ xml:crypt(Val1),
+ $']
+ end,
+ RestAttrs)]]
end,
- if
- Els == [] ->
- "/>\n";
- true ->
- OnlyCData = lists:all(fun({xmlcdata, _}) -> true;
- ({xmlelement, _, _, _}) -> false
- end, Els),
- if
- OnlyCData ->
- [$>,
- xml:get_cdata(Els),
- $<, $/, Name, $>, $\n
- ];
- true ->
- [$>, $\n,
- lists:map(fun(E) ->
- pretty_print_xml(E, [Prefix, " "])
- end, Els),
- Prefix, $<, $/, Name, $>, $\n
- ]
- end
+ if Els == [] -> <<"/>\n">>;
+ true ->
+ OnlyCData = lists:all(fun ({xmlcdata, _}) -> true;
+ (#xmlel{}) -> false
+ end,
+ Els),
+ if OnlyCData ->
+ [$>, xml:get_cdata(Els), $<, $/, Name, $>, $\n];
+ true ->
+ [$>, $\n,
+ lists:map(fun (E) ->
+ pretty_print_xml(E, [Prefix, <<" ">>])
+ end,
+ Els),
+ Prefix, $<, $/, Name, $>, $\n]
+ end
end].
-element_to_list(X) when is_atom(X) -> atom_to_list(X);
-element_to_list(X) when is_integer(X) -> integer_to_list(X).
+element_to_list(X) when is_atom(X) ->
+ iolist_to_binary(atom_to_list(X));
+element_to_list(X) when is_integer(X) ->
+ iolist_to_binary(integer_to_list(X)).
-list_to_element(List) ->
- {ok, Tokens, _} = erl_scan:string(List),
+list_to_element(Bin) ->
+ {ok, Tokens, _} = erl_scan:string(binary_to_list(Bin)),
[{_, _, Element}] = Tokens,
Element.
url_func({user_diapason, From, To}) ->
- integer_to_list(From) ++ "-" ++ integer_to_list(To) ++ "/";
+ <<(iolist_to_binary(integer_to_list(From)))/binary, "-",
+ (iolist_to_binary(integer_to_list(To)))/binary, "/">>;
url_func({users_queue, Prefix, User, _Server}) ->
- Prefix ++ "user/" ++ User ++ "/queue/";
+ <<Prefix/binary, "user/", User/binary, "/queue/">>;
url_func({user, Prefix, User, _Server}) ->
- Prefix ++ "user/" ++ User ++ "/".
+ <<Prefix/binary, "user/", User/binary, "/">>.
last_modified() ->
- {"Last-Modified", "Mon, 25 Feb 2008 13:23:30 GMT"}.
+ {<<"Last-Modified">>,
+ <<"Mon, 25 Feb 2008 13:23:30 GMT">>}.
+
cache_control_public() ->
- {"Cache-Control", "public"}.
+ {<<"Cache-Control">>, <<"public">>}.
%% Transform 1234567890 into "1,234,567,890"
pretty_string_int(Integer) when is_integer(Integer) ->
- pretty_string_int(integer_to_list(Integer));
-pretty_string_int(String) when is_list(String) ->
- {_, Result} = lists:foldl(
- fun(NewNumber, {3, Result}) ->
- {1, [NewNumber, $, | Result]};
- (NewNumber, {CountAcc, Result}) ->
- {CountAcc+1, [NewNumber | Result]}
- end,
- {0, ""},
- lists:reverse(String)),
+ pretty_string_int(iolist_to_binary(integer_to_list(Integer)));
+pretty_string_int(String) when is_binary(String) ->
+ {_, Result} = lists:foldl(fun (NewNumber, {3, Result}) ->
+ {1, <<NewNumber, $,, Result/binary>>};
+ (NewNumber, {CountAcc, Result}) ->
+ {CountAcc + 1, <<NewNumber, Result/binary>>}
+ end,
+ {0, <<"">>}, lists:reverse(binary_to_list(String))),
Result.
%%%==================================
%%%% navigation menu
-%% @spec (Host, Node, Lang, JID::jid()) -> [LI]
make_navigation(Host, Node, Lang, JID) ->
Menu = make_navigation_menu(Host, Node, Lang, JID),
make_menu_items(Lang, Menu).
-%% @spec (Host, Node, Lang, JID::jid()) -> Menu
-%% where Host = global | string()
-%% Node = cluster | string()
-%% Lang = string()
-%% Menu = {URL, Title} | {URL, Title, [Menu]}
-%% URL = string()
-%% Title = string()
make_navigation_menu(Host, Node, Lang, JID) ->
- HostNodeMenu = make_host_node_menu(Host, Node, Lang, JID),
- HostMenu = make_host_menu(Host, HostNodeMenu, Lang, JID),
+ HostNodeMenu = make_host_node_menu(Host, Node, Lang,
+ JID),
+ HostMenu = make_host_menu(Host, HostNodeMenu, Lang,
+ JID),
NodeMenu = make_node_menu(Host, Node, Lang),
make_server_menu(HostMenu, NodeMenu, Lang, JID).
-%% @spec (Host, Node, Base, Lang) -> [LI]
make_menu_items(global, cluster, Base, Lang) ->
HookItems = get_menu_items_hook(server, Lang),
- make_menu_items(Lang, {Base, "", HookItems});
-
+ make_menu_items(Lang, {Base, <<"">>, HookItems});
make_menu_items(global, Node, Base, Lang) ->
HookItems = get_menu_items_hook({node, Node}, Lang),
- make_menu_items(Lang, {Base, "", HookItems});
-
+ make_menu_items(Lang, {Base, <<"">>, HookItems});
make_menu_items(Host, cluster, Base, Lang) ->
HookItems = get_menu_items_hook({host, Host}, Lang),
- make_menu_items(Lang, {Base, "", HookItems});
-
+ make_menu_items(Lang, {Base, <<"">>, HookItems});
make_menu_items(Host, Node, Base, Lang) ->
- HookItems = get_menu_items_hook({hostnode, Host, Node}, Lang),
- make_menu_items(Lang, {Base, "", HookItems}).
-
+ HookItems = get_menu_items_hook({hostnode, Host, Node},
+ Lang),
+ make_menu_items(Lang, {Base, <<"">>, HookItems}).
make_host_node_menu(global, _, _Lang, _JID) ->
- {"", "", []};
+ {<<"">>, <<"">>, []};
make_host_node_menu(_, cluster, _Lang, _JID) ->
- {"", "", []};
+ {<<"">>, <<"">>, []};
make_host_node_menu(Host, Node, Lang, JID) ->
HostNodeBase = get_base_path(Host, Node),
- HostNodeFixed = [{"modules/", "Modules"}]
- ++ get_menu_items_hook({hostnode, Host, Node}, Lang),
+ HostNodeFixed = [{<<"modules/">>, <<"Modules">>}] ++
+ get_menu_items_hook({hostnode, Host, Node}, Lang),
HostNodeBasePath = url_to_path(HostNodeBase),
- HostNodeFixed2 = [Tuple || Tuple <- HostNodeFixed, is_allowed_path(HostNodeBasePath, Tuple, JID)],
- {HostNodeBase, atom_to_list(Node), HostNodeFixed2}.
+ HostNodeFixed2 = [Tuple
+ || Tuple <- HostNodeFixed,
+ is_allowed_path(HostNodeBasePath, Tuple, JID)],
+ {HostNodeBase, iolist_to_binary(atom_to_list(Node)),
+ HostNodeFixed2}.
make_host_menu(global, _HostNodeMenu, _Lang, _JID) ->
- {"", "", []};
+ {<<"">>, <<"">>, []};
make_host_menu(Host, HostNodeMenu, Lang, JID) ->
HostBase = get_base_path(Host, cluster),
- HostFixed = [{"acls", "Access Control Lists"},
- {"access", "Access Rules"},
- {"users", "Users"},
- {"online-users", "Online Users"}]
- ++ get_lastactivity_menuitem_list(Host) ++
- [{"nodes", "Nodes", HostNodeMenu},
- {"stats", "Statistics"}]
- ++ get_menu_items_hook({host, Host}, Lang),
+ HostFixed = [{<<"acls">>, <<"Access Control Lists">>},
+ {<<"access">>, <<"Access Rules">>},
+ {<<"users">>, <<"Users">>},
+ {<<"online-users">>, <<"Online Users">>}]
+ ++
+ get_lastactivity_menuitem_list(Host) ++
+ [{<<"nodes">>, <<"Nodes">>, HostNodeMenu},
+ {<<"stats">>, <<"Statistics">>}]
+ ++ get_menu_items_hook({host, Host}, Lang),
HostBasePath = url_to_path(HostBase),
- HostFixed2 = [Tuple || Tuple <- HostFixed, is_allowed_path(HostBasePath, Tuple, JID)],
+ HostFixed2 = [Tuple
+ || Tuple <- HostFixed,
+ is_allowed_path(HostBasePath, Tuple, JID)],
{HostBase, Host, HostFixed2}.
make_node_menu(_Host, cluster, _Lang) ->
- {"", "", []};
+ {<<"">>, <<"">>, []};
make_node_menu(global, Node, Lang) ->
NodeBase = get_base_path(global, Node),
- NodeFixed = [{"db/", "Database"},
- {"backup/", "Backup"},
- {"ports/", "Listened Ports"},
- {"stats/", "Statistics"},
- {"update/", "Update"}]
- ++ get_menu_items_hook({node, Node}, Lang),
- {NodeBase, atom_to_list(Node), NodeFixed};
+ NodeFixed = [{<<"db/">>, <<"Database">>},
+ {<<"backup/">>, <<"Backup">>},
+ {<<"ports/">>, <<"Listened Ports">>},
+ {<<"stats/">>, <<"Statistics">>},
+ {<<"update/">>, <<"Update">>}]
+ ++ get_menu_items_hook({node, Node}, Lang),
+ {NodeBase, iolist_to_binary(atom_to_list(Node)),
+ NodeFixed};
make_node_menu(_Host, _Node, _Lang) ->
- {"", "", []}.
+ {<<"">>, <<"">>, []}.
make_server_menu(HostMenu, NodeMenu, Lang, JID) ->
Base = get_base_path(global, cluster),
- Fixed = [{"acls", "Access Control Lists"},
- {"access", "Access Rules"},
- {"vhosts", "Virtual Hosts", HostMenu},
- {"nodes", "Nodes", NodeMenu},
- {"stats", "Statistics"}]
- ++ get_menu_items_hook(server, Lang),
+ Fixed = [{<<"acls">>, <<"Access Control Lists">>},
+ {<<"access">>, <<"Access Rules">>},
+ {<<"vhosts">>, <<"Virtual Hosts">>, HostMenu},
+ {<<"nodes">>, <<"Nodes">>, NodeMenu},
+ {<<"stats">>, <<"Statistics">>}]
+ ++ get_menu_items_hook(server, Lang),
BasePath = url_to_path(Base),
- Fixed2 = [Tuple || Tuple <- Fixed, is_allowed_path(BasePath, Tuple, JID)],
- {Base, "ejabberd", Fixed2}.
-
+ Fixed2 = [Tuple
+ || Tuple <- Fixed,
+ is_allowed_path(BasePath, Tuple, JID)],
+ {Base, <<"ejabberd">>, Fixed2}.
get_menu_items_hook({hostnode, Host, Node}, Lang) ->
- ejabberd_hooks:run_fold(webadmin_menu_hostnode, Host, [], [Host, Node, Lang]);
+ ejabberd_hooks:run_fold(webadmin_menu_hostnode, Host,
+ [], [Host, Node, Lang]);
get_menu_items_hook({host, Host}, Lang) ->
- ejabberd_hooks:run_fold(webadmin_menu_host, Host, [], [Host, Lang]);
+ ejabberd_hooks:run_fold(webadmin_menu_host, Host, [],
+ [Host, Lang]);
get_menu_items_hook({node, Node}, Lang) ->
- ejabberd_hooks:run_fold(webadmin_menu_node, [], [Node, Lang]);
+ ejabberd_hooks:run_fold(webadmin_menu_node, [],
+ [Node, Lang]);
get_menu_items_hook(server, Lang) ->
ejabberd_hooks:run_fold(webadmin_menu_main, [], [Lang]).
-
-%% @spec (Lang::string(), Menu) -> [LI]
-%% where Menu = {MURI::string(), MName::string(), Items::[Item]}
-%% Item = {IURI::string(), IName::string()} | {IURI::string(), IName::string(), Menu}
make_menu_items(Lang, Menu) ->
lists:reverse(make_menu_items2(Lang, 1, Menu)).
make_menu_items2(Lang, Deep, {MURI, MName, _} = Menu) ->
Res = case MName of
- "" -> [];
- _ -> [make_menu_item(header, Deep, MURI, MName, Lang) ]
+ <<"">> -> [];
+ _ -> [make_menu_item(header, Deep, MURI, MName, Lang)]
end,
make_menu_items2(Lang, Deep, Menu, Res).
-make_menu_items2(_, _Deep, {_, _, []}, Res) ->
- Res;
-
-make_menu_items2(Lang, Deep, {MURI, MName, [Item | Items]}, Res) ->
+make_menu_items2(_, _Deep, {_, _, []}, Res) -> Res;
+make_menu_items2(Lang, Deep,
+ {MURI, MName, [Item | Items]}, Res) ->
Res2 = case Item of
- {IURI, IName} ->
- [make_menu_item(item, Deep, MURI++IURI++"/", IName, Lang) | Res];
- {IURI, IName, SubMenu} ->
- %%ResTemp = [?LI([?ACT(MURI ++ IURI ++ "/", IName)]) | Res],
- ResTemp = [make_menu_item(item, Deep, MURI++IURI++"/", IName, Lang) | Res],
- ResSubMenu = make_menu_items2(Lang, Deep+1, SubMenu),
- ResSubMenu ++ ResTemp
+ {IURI, IName} ->
+ [make_menu_item(item, Deep,
+ <<MURI/binary, IURI/binary, "/">>, IName, Lang)
+ | Res];
+ {IURI, IName, SubMenu} ->
+ ResTemp = [make_menu_item(item, Deep,
+ <<MURI/binary, IURI/binary, "/">>,
+ IName, Lang)
+ | Res],
+ ResSubMenu = make_menu_items2(Lang, Deep + 1, SubMenu),
+ ResSubMenu ++ ResTemp
end,
- make_menu_items2(Lang, Deep, {MURI, MName, Items}, Res2).
+ make_menu_items2(Lang, Deep, {MURI, MName, Items},
+ Res2).
make_menu_item(header, 1, URI, Name, _Lang) ->
- ?LI([?XAE("div", [{"id", "navhead"}], [?AC(URI, Name)] )]);
+ ?LI([?XAE(<<"div">>, [{<<"id">>, <<"navhead">>}],
+ [?AC(URI, Name)])]);
make_menu_item(header, 2, URI, Name, _Lang) ->
- ?LI([?XAE("div", [{"id", "navheadsub"}], [?AC(URI, Name)] )]);
+ ?LI([?XAE(<<"div">>, [{<<"id">>, <<"navheadsub">>}],
+ [?AC(URI, Name)])]);
make_menu_item(header, 3, URI, Name, _Lang) ->
- ?LI([?XAE("div", [{"id", "navheadsubsub"}], [?AC(URI, Name)] )]);
+ ?LI([?XAE(<<"div">>, [{<<"id">>, <<"navheadsubsub">>}],
+ [?AC(URI, Name)])]);
make_menu_item(item, 1, URI, Name, Lang) ->
- ?LI([?XAE("div", [{"id", "navitem"}], [?ACT(URI, Name)] )]);
+ ?LI([?XAE(<<"div">>, [{<<"id">>, <<"navitem">>}],
+ [?ACT(URI, Name)])]);
make_menu_item(item, 2, URI, Name, Lang) ->
- ?LI([?XAE("div", [{"id", "navitemsub"}], [?ACT(URI, Name)] )]);
+ ?LI([?XAE(<<"div">>, [{<<"id">>, <<"navitemsub">>}],
+ [?ACT(URI, Name)])]);
make_menu_item(item, 3, URI, Name, Lang) ->
- ?LI([?XAE("div", [{"id", "navitemsubsub"}], [?ACT(URI, Name)] )]).
+ ?LI([?XAE(<<"div">>, [{<<"id">>, <<"navitemsubsub">>}],
+ [?ACT(URI, Name)])]).
%%%==================================
%%% vim: set foldmethod=marker foldmarker=%%%%,%%%=:
+
diff --git a/src/web/ejabberd_web_admin.hrl b/src/web/ejabberd_web_admin.hrl
index 73f792e2d..46f002fbf 100644
--- a/src/web/ejabberd_web_admin.hrl
+++ b/src/web/ejabberd_web_admin.hrl
@@ -19,58 +19,84 @@
%%%
%%%----------------------------------------------------------------------
--define(X(Name), {xmlelement, Name, [], []}).
--define(XA(Name, Attrs), {xmlelement, Name, Attrs, []}).
--define(XE(Name, Els), {xmlelement, Name, [], Els}).
--define(XAE(Name, Attrs, Els), {xmlelement, Name, Attrs, Els}).
+-define(X(Name),
+ #xmlel{name = Name, attrs = [], children = []}).
+
+-define(XA(Name, Attrs),
+ #xmlel{name = Name, attrs = Attrs, children = []}).
+
+-define(XE(Name, Els),
+ #xmlel{name = Name, attrs = [], children = Els}).
+
+-define(XAE(Name, Attrs, Els),
+ #xmlel{name = Name, attrs = Attrs, children = Els}).
+
-define(C(Text), {xmlcdata, Text}).
+
-define(XC(Name, Text), ?XE(Name, [?C(Text)])).
--define(XAC(Name, Attrs, Text), ?XAE(Name, Attrs, [?C(Text)])).
+
+-define(XAC(Name, Attrs, Text),
+ ?XAE(Name, Attrs, [?C(Text)])).
-define(T(Text), translate:translate(Lang, Text)).
--define(CT(Text), ?C(?T(Text))).
--define(XCT(Name, Text), ?XC(Name, ?T(Text))).
--define(XACT(Name, Attrs, Text), ?XAC(Name, Attrs, ?T(Text))).
--define(LI(Els), ?XE("li", Els)).
--define(A(URL, Els), ?XAE("a", [{"href", URL}], Els)).
+-define(CT(Text), ?C((?T(Text)))).
+
+-define(XCT(Name, Text), ?XC(Name, (?T(Text)))).
+
+-define(XACT(Name, Attrs, Text),
+ ?XAC(Name, Attrs, (?T(Text)))).
+
+-define(LI(Els), ?XE(<<"li">>, Els)).
+
+-define(A(URL, Els),
+ ?XAE(<<"a">>, [{<<"href">>, URL}], Els)).
+
-define(AC(URL, Text), ?A(URL, [?C(Text)])).
--define(ACT(URL, Text), ?AC(URL, ?T(Text))).
--define(P, ?X("p")).
--define(BR, ?X("br")).
+
+-define(ACT(URL, Text), ?AC(URL, (?T(Text)))).
+
+-define(P, ?X(<<"p">>)).
+
+-define(BR, ?X(<<"br">>)).
+
-define(INPUT(Type, Name, Value),
- ?XA("input", [{"type", Type},
- {"name", Name},
- {"value", Value}])).
--define(INPUTT(Type, Name, Value), ?INPUT(Type, Name, ?T(Value))).
+ ?XA(<<"input">>,
+ [{<<"type">>, Type}, {<<"name">>, Name},
+ {<<"value">>, Value}])).
+
+-define(INPUTT(Type, Name, Value),
+ ?INPUT(Type, Name, (?T(Value)))).
+
-define(INPUTS(Type, Name, Value, Size),
- ?XA("input", [{"type", Type},
- {"name", Name},
- {"value", Value},
- {"size", Size}])).
--define(INPUTST(Type, Name, Value, Size), ?INPUT(Type, Name, ?T(Value), Size)).
--define(ACLINPUT(Text), ?XE("td", [?INPUT("text", "value" ++ ID, Text)])).
+ ?XA(<<"input">>,
+ [{<<"type">>, Type}, {<<"name">>, Name},
+ {<<"value">>, Value}, {<<"size">>, Size}])).
+
+-define(INPUTST(Type, Name, Value, Size),
+ ?INPUT(Type, Name, (?T(Value)), Size)).
+
+-define(ACLINPUT(Text),
+ ?XE(<<"td">>,
+ [?INPUT(<<"text">>, <<"value", ID/binary>>, Text)])).
-define(TEXTAREA(Name, Rows, Cols, Value),
- ?XAC("textarea", [{"name", Name},
- {"rows", Rows},
- {"cols", Cols}],
+ ?XAC(<<"textarea">>,
+ [{<<"name">>, Name}, {<<"rows">>, Rows},
+ {<<"cols">>, Cols}],
Value)).
-%% Build an xmlelement for result
--define(XRES(Text), ?XAC("p", [{"class", "result"}], Text)).
--define(XREST(Text), ?XRES(?T(Text))).
+-define(XRES(Text),
+ ?XAC(<<"p">>, [{<<"class">>, <<"result">>}], Text)).
-%% Guide Link
--define(GL(Ref, Title),
- ?XAE("div",
- [{"class", "guidelink"}],
- [?XAE("a",
- [{"href", "/admin/doc/guide.html#"++ Ref},
- {"target", "_blank"}],
- [?C("[Guide: " ++ Title ++ "]")])
- ])).
+-define(XREST(Text), ?XRES((?T(Text)))).
+-define(GL(Ref, Title),
+ ?XAE(<<"div">>, [{<<"class">>, <<"guidelink">>}],
+ [?XAE(<<"a">>,
+ [{<<"href">>, <<"/admin/doc/guide.html#", Ref/binary>>},
+ {<<"target">>, <<"_blank">>}],
+ [?C(<<"[Guide: ", Title/binary, "]">>)])])).
-%% h1 with a Guide Link
--define(H1GL(Name, Ref, Title), [?XC("h1", Name), ?GL(Ref, Title)]).
+-define(H1GL(Name, Ref, Title),
+ [?XC(<<"h1">>, Name), ?GL(Ref, Title)]).
diff --git a/src/web/ejabberd_websocket.erl b/src/web/ejabberd_websocket.erl
index 8030842a0..fde8baff6 100644
--- a/src/web/ejabberd_websocket.erl
+++ b/src/web/ejabberd_websocket.erl
@@ -8,12 +8,12 @@
%%% (http://github.com/ostinelli/misultin/blob/master/src/misultin_websocket.erl)
%%% Copyright (C) 2010, Roberto Ostinelli <roberto@ostinelli.net>, Joe Armstrong.
%%% All rights reserved.
-%%%
+%%%
%%% Code portions from Joe Armstrong have been originally taken under MIT license at the address:
%%% <http://armstrongonsoftware.blogspot.com/2009/12/comet-is-dead-long-live-websockets.html>
%%%
%%% BSD License
-%%%
+%%%
%%% Redistribution and use in source and binary forms, with or without modification, are permitted provided
%%% that the following conditions are met:
%%%
@@ -36,400 +36,446 @@
%%% ejabberd, Copyright (C) 2002-2010 ProcessOne
%%%----------------------------------------------------------------------
--module (ejabberd_websocket).
+-module(ejabberd_websocket).
+
-author('ecestari@process-one.net').
+
-export([connect/2, check/2, is_acceptable/1]).
+
-include("ejabberd.hrl").
+
-include("jlib.hrl").
+
-include("ejabberd_http.hrl").
-check(_Path, Headers)->
- % set supported websocket protocols, order does matter
- VsnSupported = [{'draft-hybi', 8}, {'draft-hybi', 13}, {'draft-hixie', 0}, {'draft-hixie', 68}],
- % checks
- check_websockets(VsnSupported, Headers).
+check(_Path, Headers) ->
+ VsnSupported = [{'draft-hybi', 8}, {'draft-hybi', 13},
+ {'draft-hixie', 0}, {'draft-hixie', 68}],
+ check_websockets(VsnSupported, Headers).
-% Checks if websocket can be access by client
-% If origins are set in configuration, check if it belongs
-% If origins not set, access is open.
-is_acceptable(#ws{origin=Origin, protocol=Protocol,
- headers = Headers, acceptable_origins = Origins, auth_module=undefined})->
- ClientProtocol = lists:keyfind("Sec-Websocket-Protocol",1, Headers),
- case {(Origins == []) or lists:member(Origin, Origins), ClientProtocol, Protocol } of
- {false, _, _} ->
- ?INFO_MSG("client does not come from authorized origin", []),
- false;
- {_, false, _} ->
- true;
- {_, {_, P}, P} ->
- true;
- _ = E->
- ?INFO_MSG("Wrong protocol requested : ~p", [E]),
- false
- end;
-is_acceptable(#ws{local_path=LocalPath, origin=Origin, ip=IP, q=Q, protocol=Protocol, headers = Headers,auth_module=Module})->
- Module:is_acceptable(LocalPath, Q, Origin, Protocol, IP, Headers).
+is_acceptable(#ws{origin = Origin, protocol = Protocol,
+ headers = Headers, acceptable_origins = Origins,
+ auth_module = undefined}) ->
+ ClientProtocol =
+ lists:keyfind(<<"Sec-Websocket-Protocol">>, 1, Headers),
+ case {(Origins == []) or lists:member(Origin, Origins),
+ ClientProtocol, Protocol}
+ of
+ {false, _, _} ->
+ ?INFO_MSG("client does not come from authorized "
+ "origin",
+ []),
+ false;
+ {_, false, _} -> true;
+ {_, {_, P}, P} -> true;
+ _ = E ->
+ ?INFO_MSG("Wrong protocol requested : ~p", [E]), false
+ end;
+is_acceptable(#ws{local_path = LocalPath,
+ origin = Origin, ip = IP, q = Q, protocol = Protocol,
+ headers = Headers, auth_module = Module}) ->
+ Module:is_acceptable(LocalPath, Q, Origin, Protocol, IP,
+ Headers).
-% Connect and handshake with Websocket.
-connect(#ws{vsn = Vsn, socket = Socket, q=Q,origin=Origin, host=Host, port=Port, sockmod = SockMod, path = Path, headers = Headers, ws_autoexit = WsAutoExit} = Ws, WsLoop) ->
- % build handshake
- HandshakeServer = handshake(Vsn, Socket,SockMod, Headers, {Path, Q, Origin, Host, Port}),
- % send handshake back
- SockMod:send(Socket, HandshakeServer),
- ?DEBUG("Sent handshake response : ~p", [HandshakeServer]),
- Ws0 = ejabberd_ws:new(Ws#ws{origin = Origin, host = Host}, self()),
- %?DEBUG("Ws0 : ~p",[Ws0]),
- % add data to ws record and spawn controlling process
- {ok, WsHandleLoopPid} = WsLoop:start_link(Ws0),
- erlang:monitor(process, WsHandleLoopPid),
- % set opts
- case SockMod of
- gen_tcp ->
- inet:setopts(Socket, [{packet, 0}, {active, true}]);
- _ ->
- SockMod:setopts(Socket, [{packet, 0}, {active, true}])
- end,
- % start listening for incoming data
- ws_loop(Vsn, none, Socket, WsHandleLoopPid, SockMod, WsAutoExit).
+connect(#ws{vsn = Vsn, socket = Socket, q = Q,
+ origin = Origin, host = Host, port = Port,
+ sockmod = SockMod, path = Path, headers = Headers,
+ ws_autoexit = WsAutoExit} =
+ Ws,
+ WsLoop) ->
+ HandshakeServer = handshake(Vsn, Socket, SockMod,
+ Headers, {Path, Q, Origin, Host, Port}),
+ SockMod:send(Socket, HandshakeServer),
+ ?DEBUG("Sent handshake response : ~p",
+ [HandshakeServer]),
+ Ws0 = ejabberd_ws:new(Ws#ws{origin = Origin,
+ host = Host},
+ self()),
+ {ok, WsHandleLoopPid} = WsLoop:start_link(Ws0),
+ erlang:monitor(process, WsHandleLoopPid),
+ case SockMod of
+ gen_tcp ->
+ inet:setopts(Socket, [{packet, 0}, {active, true}]);
+ _ ->
+ SockMod:setopts(Socket, [{packet, 0}, {active, true}])
+ end,
+ ws_loop(Vsn, none, Socket, WsHandleLoopPid, SockMod,
+ WsAutoExit).
-
check_websockets([], _Headers) -> false;
-check_websockets([Vsn|T], Headers) ->
- case check_websocket(Vsn, Headers) of
- false -> check_websockets(T, Headers);
- Value -> Value
- end.
+check_websockets([Vsn | T], Headers) ->
+ case check_websocket(Vsn, Headers) of
+ false -> check_websockets(T, Headers);
+ Value -> Value
+ end.
-% Function: {true, Vsn} | false
-% Description: Check if the incoming request is a websocket request.
check_websocket({'draft-hixie', 0} = Vsn, Headers) ->
- %?DEBUG("testing for websocket protocol ~p", [Vsn]),
- % set required headers
- RequiredHeaders = [
- {'Upgrade', "WebSocket"}, {'Connection', "Upgrade"}, {'Host', ignore}, {"Origin", ignore},
- {"Sec-Websocket-Key1", ignore}, {"Sec-Websocket-Key2", ignore}
- ],
- % check for headers existance
- case check_headers(Headers, RequiredHeaders) of
- true ->
- % return
- {true, Vsn};
- _RemainingHeaders ->
- %?DEBUG("not protocol ~p, remaining headers: ~p", [Vsn, _RemainingHeaders]),
- false
- end;
+ RequiredHeaders = [{'Upgrade', <<"WebSocket">>},
+ {'Connection', <<"Upgrade">>}, {'Host', ignore},
+ {<<"Origin">>, ignore},
+ {<<"Sec-Websocket-Key1">>, ignore},
+ {<<"Sec-Websocket-Key2">>, ignore}],
+ case check_headers(Headers, RequiredHeaders) of
+ true -> {true, Vsn};
+ _RemainingHeaders ->
+ %?DEBUG("not protocol ~p, remaining headers: ~p", [Vsn, _RemainingHeaders]),
+ false
+ end;
check_websocket({'draft-hixie', 68} = Vsn, Headers) ->
- %?DEBUG("testing for websocket protocol ~p", [Vsn]),
- % set required headers
- RequiredHeaders = [
- {'Upgrade', "WebSocket"}, {'Connection', "Upgrade"}, {'Host', ignore}, {"Origin", ignore}
- ],
- % check for headers existance
- case check_headers(Headers, RequiredHeaders) of
- true -> {true, Vsn};
- _RemainingHeaders ->
- %?DEBUG("not protocol ~p, remaining headers: ~p", [Vsn, _RemainingHeaders]),
- false
- end;
+ RequiredHeaders = [{'Upgrade', <<"WebSocket">>},
+ {'Connection', <<"Upgrade">>}, {'Host', ignore},
+ {<<"Origin">>, ignore}],
+ case check_headers(Headers, RequiredHeaders) of
+ true -> {true, Vsn};
+ _RemainingHeaders ->
+ %?DEBUG("not protocol ~p, remaining headers: ~p", [Vsn, _RemainingHeaders]),
+ false
+ end;
check_websocket({'draft-hybi', 8} = Vsn, Headers) ->
- %?DEBUG("testing for websocket protocol ~p", [Vsn]),
- % set required headers
- RequiredHeaders = [
- {'Upgrade', "websocket"}, {'Connection', ignore}, {'Host', ignore},
- {"Sec-Websocket-Key", ignore}, {"Sec-Websocket-Version", "8"}
- ],
- % check for headers existance
- case check_headers(Headers, RequiredHeaders) of
- true -> {true, Vsn};
- RemainingHeaders ->
- %%?INFO_MSG("not protocol ~p, remaining headers: ~p", [Vsn, RemainingHeaders]),
- false
- end;
+ RequiredHeaders = [{'Upgrade', <<"websocket">>},
+ {'Connection', ignore}, {'Host', ignore},
+ {<<"Sec-Websocket-Key">>, ignore},
+ {<<"Sec-Websocket-Version">>, <<"8">>}],
+ case check_headers(Headers, RequiredHeaders) of
+ true -> {true, Vsn};
+ _RemainingHeaders ->
+ %%?INFO_MSG("not protocol ~p, remaining headers: ~p", [Vsn, RemainingHeaders]),
+ false
+ end;
check_websocket({'draft-hybi', 13} = Vsn, Headers) ->
- %?DEBUG("testing for websocket protocol ~p", [Vsn]),
- % set required headers
- RequiredHeaders = [
- {'Upgrade', "websocket"}, {'Connection', ignore}, {'Host', ignore},
- {"Sec-Websocket-Key", ignore}, {"Sec-Websocket-Version", "13"}
- ],
- % check for headers existance
- case check_headers(Headers, RequiredHeaders) of
- true -> {true, Vsn};
- RemainingHeaders ->
- %%?INFO_MSG("not protocol ~p, remaining headers: ~p", [Vsn, RemainingHeaders]),
- false
- end;
-check_websocket(_Vsn, _Headers) -> false. % not implemented
+ RequiredHeaders = [{'Upgrade', <<"websocket">>},
+ {'Connection', ignore}, {'Host', ignore},
+ {<<"Sec-Websocket-Key">>, ignore},
+ {<<"Sec-Websocket-Version">>, <<"13">>}],
+ case check_headers(Headers, RequiredHeaders) of
+ true -> {true, Vsn};
+ _RemainingHeaders ->
+ %%?INFO_MSG("not protocol ~p, remaining headers: ~p", [Vsn, RemainingHeaders]),
+ false
+ end.
-% Function: true | [{RequiredTag, RequiredVal}, ..]
-% Description: Check if headers correspond to headers requirements.
check_headers(Headers, RequiredHeaders) ->
- F = fun({Tag, Val}) ->
- % see if the required Tag is in the Headers
- case lists:keyfind(Tag, 1, Headers) of
- false -> true; % header not found, keep in list
- {_, HVal} ->
- %?DEBUG("check: ~p", [{Tag, HVal,Val }]),
- case Val of
- ignore -> false; % ignore value -> ok, remove from list
- HVal -> false; % expected val -> ok, remove from list
- _ -> true % val is different, keep in list
- end
+ F = fun ({Tag, Val}) ->
+ case lists:keyfind(Tag, 1, Headers) of
+ false -> true; % header not found, keep in list
+ {_, HVal} ->
+ case Val of
+ ignore -> false; % ignore value -> ok, remove from list
+ HVal -> false; % expected val -> ok, remove from list
+ _ ->
+ true % val is different, keep in list
+ end
end
end,
- case lists:filter(F, RequiredHeaders) of
- [] -> true;
- MissingHeaders -> MissingHeaders
- end.
+ case lists:filter(F, RequiredHeaders) of
+ [] -> true;
+ MissingHeaders -> MissingHeaders
+ end.
-% Function: List
-% Description: Builds the server handshake response.
-handshake({'draft-hixie', 0}, Sock,SocketMod, Headers, {Path, Q,Origin, Host, Port}) ->
- % build data
- {_, Key1} = lists:keyfind("Sec-Websocket-Key1",1, Headers),
- {_, Key2} = lists:keyfind("Sec-Websocket-Key2",1, Headers),
- HostPort = case lists:keyfind('Host', 1, Headers) of
- {_, Value} -> Value;
- _ -> string:join([Host, integer_to_list(Port)],":")
- end,
- % handshake needs body of the request, still need to read it [TODO: default recv timeout hard set, will be exported when WS protocol is final]
- case SocketMod of
- gen_tcp ->
- inet:setopts(Sock, [{packet, raw}, {active, false}]);
- _ ->
- SocketMod:setopts(Sock, [{packet, raw}, {active, false}])
- end,
- Body = case SocketMod:recv(Sock, 8, 30*1000) of
- {ok, Bin} -> Bin;
- {error, timeout} ->
- ?WARNING_MSG("timeout in reading websocket body", []),
- <<>>;
- _Other ->
- ?ERROR_MSG("tcp error treating data: ~p", [_Other]),
- <<>>
- end,
- QParams = lists:map(
- fun({nokey,[]})->
- none;
- ({K, V})->
- K ++ "=" ++ V
- end, Q),
- QString = case QParams of
- [none]-> "";
- QParams-> "?" ++ string:join(QParams, "&")
- end,
- %?DEBUG("got content in body of websocket request: ~p, ~p", [Body,string:join([Host, Path],"/")]),
- % prepare handhsake response
- ["HTTP/1.1 101 WebSocket Protocol Handshake\r\n",
- "Upgrade: WebSocket\r\n",
- "Connection: Upgrade\r\n",
- "Sec-WebSocket-Origin: ", Origin, "\r\n",
- "Sec-WebSocket-Location: ws://", HostPort, "/", string:join(Path,"/"),
- QString, "\r\n\r\n",
- build_challenge({'draft-hixie', 0}, {Key1, Key2, Body})
- ];
-handshake({'draft-hixie', 68}, _Sock,_SocketMod, Headers, {Path, Origin, Host, Port}) ->
- HostPort = case lists:keyfind('Host', 1, Headers) of
- {_, Value} -> Value;
- _ -> string:join([Host, integer_to_list(Port)],":")
- end,
- ["HTTP/1.1 101 Web Socket Protocol Handshake\r\n",
- "Upgrade: WebSocket\r\n",
- "Connection: Upgrade\r\n",
- "WebSocket-Origin: ", Origin , "\r\n",
- "WebSocket-Location: ws://", HostPort, "/", string:join(Path,"/"),"\r\n\r\n"
- ];
-handshake({'draft-hybi', _}, Sock,SocketMod, Headers, {Path, Q,Origin, Host, Port}) ->
- {_, Key} = lists:keyfind("Sec-Websocket-Key",1, Headers),
- Hash = jlib:encode_base64(binary_to_list(sha:sha1(Key++"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))),
- ["HTTP/1.1 101 Switching Protocols\r\n",
- "Upgrade: websocket\r\n",
- "Connection: Upgrade\r\n",
- "Sec-WebSocket-Accept: ", Hash, "\r\n\r\n"
- ].
+handshake({'draft-hixie', 0}, Sock, SocketMod, Headers,
+ {Path, Q, Origin, Host, Port}) ->
+ {_, Key1} = lists:keyfind(<<"Sec-Websocket-Key1">>, 1,
+ Headers),
+ {_, Key2} = lists:keyfind(<<"Sec-Websocket-Key2">>, 1,
+ Headers),
+ HostPort = case lists:keyfind('Host', 1, Headers) of
+ {_, Value} -> Value;
+ _ ->
+ str:join([Host,
+ jlib:integer_to_binary(Port)],
+ <<":">>)
+ end,
+ case SocketMod of
+ gen_tcp ->
+ inet:setopts(Sock, [{packet, raw}, {active, false}]);
+ _ ->
+ SocketMod:setopts(Sock,
+ [{packet, raw}, {active, false}])
+ end,
+ Body = case SocketMod:recv(Sock, 8, 30 * 1000) of
+ {ok, Bin} -> Bin;
+ {error, timeout} ->
+ ?WARNING_MSG("timeout in reading websocket body", []),
+ <<>>;
+ _Other ->
+ ?ERROR_MSG("tcp error treating data: ~p", [_Other]),
+ <<>>
+ end,
+ QParams = lists:map(fun ({nokey, <<>>}) -> none;
+ ({K, V}) -> <<K/binary, "=", V/binary>>
+ end,
+ Q),
+ QString = case QParams of
+ [none] -> <<"">>;
+ QParams -> <<"?", (str:join(QParams, <<"&">>))/binary>>
+ end,
+ [<<"HTTP/1.1 101 WebSocket Protocol Handshake\r\n">>,
+ <<"Upgrade: WebSocket\r\n">>,
+ <<"Connection: Upgrade\r\n">>,
+ <<"Sec-WebSocket-Origin: ">>, Origin, <<"\r\n">>,
+ <<"Sec-WebSocket-Location: ws://">>, HostPort, <<"/">>,
+ str:join(Path, <<"/">>), QString, <<"\r\n\r\n">>,
+ build_challenge({'draft-hixie', 0},
+ {Key1, Key2, Body})];
+handshake({'draft-hixie', 68}, _Sock, _SocketMod,
+ Headers, {Path, _Q, Origin, Host, Port}) ->
+ HostPort = case lists:keyfind('Host', 1, Headers) of
+ {_, Value} -> Value;
+ _ ->
+ str:join([Host,
+ iolist_to_binary(integer_to_list(Port))],
+ <<":">>)
+ end,
+ [<<"HTTP/1.1 101 Web Socket Protocol Handshake\r\n">>,
+ <<"Upgrade: WebSocket\r\n">>,
+ <<"Connection: Upgrade\r\n">>, <<"WebSocket-Origin: ">>,
+ Origin, <<"\r\n">>, <<"WebSocket-Location: ws://">>,
+ HostPort, <<"/">>, str:join(Path, <<"/">>),
+ <<"\r\n\r\n">>];
+handshake({'draft-hybi', _}, _Sock, _SocketMod, Headers,
+ {_Path, _Q, _Origin, _Host, _Port}) ->
+ {_, Key} = lists:keyfind(<<"Sec-Websocket-Key">>, 1,
+ Headers),
+ Hash = jlib:encode_base64(
+ sha:sha1(<<Key/binary, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11">>)),
+ [<<"HTTP/1.1 101 Switching Protocols\r\n">>,
+ <<"Upgrade: websocket\r\n">>,
+ <<"Connection: Upgrade\r\n">>,
+ <<"Sec-WebSocket-Accept: ">>, Hash, <<"\r\n\r\n">>].
-% Function: List
-% Description: Builds the challenge for a handshake response.
-% Code portions from Sergio Veiga <http://sergioveiga.com/index.php/2010/06/17/websocket-handshake-76-in-erlang/>
-build_challenge({'draft-hixie', 0}, {Key1, Key2, Key3}) ->
- Ikey1 = [D || D <- Key1, $0 =< D, D =< $9],
- Ikey2 = [D || D <- Key2, $0 =< D, D =< $9],
- Blank1 = length([D || D <- Key1, D =:= 32]),
- Blank2 = length([D || D <- Key2, D =:= 32]),
- Part1 = list_to_integer(Ikey1) div Blank1,
- Part2 = list_to_integer(Ikey2) div Blank2,
- Ckey = <<Part1:4/big-unsigned-integer-unit:8, Part2:4/big-unsigned-integer-unit:8, Key3/binary>>,
- erlang:md5(Ckey).
-
-
-ws_loop(Vsn, HandlerState, Socket, WsHandleLoopPid, SocketMode, WsAutoExit) ->
- receive
- {tcp, Socket, Data} ->
- %?ERROR_MSG("[WS recv] ~p~n[Buffer state] ~p", [Data, Buffer]),
- {NewHandlerState, ToSend} = handle_data(Vsn, HandlerState, Data, Socket, WsHandleLoopPid, SocketMode, WsAutoExit),
- lists:foreach(fun(Pkt) ->
- SocketMode:send(Socket, Pkt)
- end, ToSend),
- ws_loop(Vsn, NewHandlerState, Socket, WsHandleLoopPid, SocketMode, WsAutoExit);
- {tcp_closed, Socket} ->
- ?DEBUG("tcp connection was closed, exit", []),
- % close websocket and custom controlling loop
- websocket_close(Socket, WsHandleLoopPid, SocketMode, WsAutoExit);
- {'DOWN', Ref, process, WsHandleLoopPid, Reason} ->
- case Reason of
- normal ->
- %?DEBUG("linked websocket controlling loop stopped.", []);
- ok;
- _ ->
- ?ERROR_MSG("linked websocket controlling loop crashed with reason: ~p", [Reason])
+build_challenge({'draft-hixie', 0},
+ {Key1, Key2, Key3}) ->
+ Ikey1 = << <<D>> || <<D>> <= Key1, $0 =< D, D =< $9>>,
+ Ikey2 = << <<D>> || <<D>> <= Key2, $0 =< D, D =< $9>>,
+ Blank1 = byte_size(<< <<D>> || <<D>> <= Key1, D =:= 32>>),
+ Blank2 = byte_size(<< <<D>> || <<D>> <= Key2, D =:= 32>>),
+ Part1 = jlib:binary_to_integer(Ikey1) div Blank1,
+ Part2 = jlib:binary_to_integer(Ikey2) div Blank2,
+ Ckey = <<Part1:4/big-unsigned-integer-unit:8,
+ Part2:4/big-unsigned-integer-unit:8, Key3/binary>>,
+ erlang:md5(Ckey).
+
+ws_loop(Vsn, HandlerState, Socket, WsHandleLoopPid,
+ SocketMode, WsAutoExit) ->
+ receive
+ {tcp, Socket, Data} ->
+ {NewHandlerState, ToSend} = handle_data(Vsn,
+ HandlerState, Data, Socket,
+ WsHandleLoopPid, SocketMode,
+ WsAutoExit),
+ lists:foreach(fun (Pkt) -> SocketMode:send(Socket, Pkt)
end,
- % demonitor
- erlang:demonitor(Ref),
- % close websocket and custom controlling loop
- websocket_close(Socket, WsHandleLoopPid, SocketMode, WsAutoExit);
- {send, Data} ->
- %?DEBUG("sending data to websocket: ~p", [Data]),
- SocketMode:send(Socket, encode_frame(Vsn, Data)),
- ws_loop(Vsn, HandlerState, Socket, WsHandleLoopPid, SocketMode, WsAutoExit);
- shutdown ->
- ?DEBUG("shutdown request received, closing websocket with pid ~p", [self()]),
- % close websocket and custom controlling loop
- websocket_close(Socket, WsHandleLoopPid, SocketMode, WsAutoExit);
- _Ignored ->
- ?WARNING_MSG("received unexpected message, ignoring: ~p", [_Ignored]),
- ws_loop(Vsn, HandlerState, Socket, WsHandleLoopPid, SocketMode, WsAutoExit)
- end.
+ ToSend),
+ ws_loop(Vsn, NewHandlerState, Socket, WsHandleLoopPid,
+ SocketMode, WsAutoExit);
+ {tcp_closed, Socket} ->
+ ?DEBUG("tcp connection was closed, exit", []),
+ websocket_close(Socket, WsHandleLoopPid, SocketMode,
+ WsAutoExit);
+ {'DOWN', Ref, process, WsHandleLoopPid, Reason} ->
+ case Reason of
+ normal ->
+ %?DEBUG("linked websocket controlling loop stopped.", []);
+ ok;
+ _ ->
+ ?ERROR_MSG("linked websocket controlling loop crashed "
+ "with reason: ~p",
+ [Reason])
+ end,
+ erlang:demonitor(Ref),
+ websocket_close(Socket, WsHandleLoopPid, SocketMode,
+ WsAutoExit);
+ {send, Data} ->
+ SocketMode:send(Socket, encode_frame(Vsn, Data)),
+ ws_loop(Vsn, HandlerState, Socket, WsHandleLoopPid,
+ SocketMode, WsAutoExit);
+ shutdown ->
+ ?DEBUG("shutdown request received, closing websocket "
+ "with pid ~p",
+ [self()]),
+ websocket_close(Socket, WsHandleLoopPid, SocketMode,
+ WsAutoExit);
+ _Ignored ->
+ ?WARNING_MSG("received unexpected message, ignoring: ~p",
+ [_Ignored]),
+ ws_loop(Vsn, HandlerState, Socket, WsHandleLoopPid,
+ SocketMode, WsAutoExit)
+ end.
encode_frame({'draft-hybi', _}, Data, Opcode) ->
case byte_size(Data) of
- S1 when S1 < 126 ->
- <<1:1, 0:3, Opcode:4, 0:1, S1:7, Data/binary>>;
- S2 when S2 < 65536 ->
- <<1:1, 0:3, Opcode:4, 0:1, 126:7, S2:16, Data/binary>>;
- S3 ->
- <<1:1, 0:3, Opcode:4, 0:1, 127:7, S3:64, Data/binary>>
+ S1 when S1 < 126 ->
+ <<1:1, 0:3, Opcode:4, 0:1, S1:7, Data/binary>>;
+ S2 when S2 < 65536 ->
+ <<1:1, 0:3, Opcode:4, 0:1, 126:7, S2:16, Data/binary>>;
+ S3 ->
+ <<1:1, 0:3, Opcode:4, 0:1, 127:7, S3:64, Data/binary>>
end.
-encode_frame({'draft-hybi', _}=Vsn, Data) ->
+encode_frame({'draft-hybi', _} = Vsn, Data) ->
encode_frame(Vsn, Data, 1);
-encode_frame(_, Data) ->
- <<0, Data/binary, 255>>.
+encode_frame(_, Data) -> <<0, Data/binary, 255>>.
process_hixie_68(none, Data) ->
process_hixie_68({false, <<>>}, Data);
-process_hixie_68({false, <<>>}, <<0,T/binary>>) ->
+process_hixie_68({false, <<>>}, <<0, T/binary>>) ->
process_hixie_68({true, <<>>}, T);
-process_hixie_68(L, <<>>) ->
- {L, [], []};
-process_hixie_68({_, L}, <<255,T/binary>>) ->
+process_hixie_68(L, <<>>) -> {L, [], []};
+process_hixie_68({_, L}, <<255, T/binary>>) ->
{L2, Recv, Send} = process_hixie_68({false, <<>>}, T),
- {L2, [L|Recv], Send};
+ {L2, [L | Recv], Send};
process_hixie_68({true, L}, <<H/utf8, T/binary>>) ->
process_hixie_68({true, <<L/binary, H>>}, T).
--record(hybi_8_state, {mask=none, offset=0, left, final_frame=true, opcode, unprocessed = <<>>, unmasked = <<>>, unmasked_msg = <<>>}).
+-record(hybi_8_state,
+ {mask = none, offset = 0, left, final_frame = true,
+ opcode, unprocessed = <<>>, unmasked = <<>>,
+ unmasked_msg = <<>>}).
-decode_hybi_8_header(<<Final:1, _:3, Opcode:4, 0:1, Len:7, Data/binary>>) when Len < 126 ->
+decode_hybi_8_header(<<Final:1, _:3, Opcode:4, 0:1,
+ Len:7, Data/binary>>)
+ when Len < 126 ->
{Len, Final, Opcode, none, Data};
-decode_hybi_8_header(<<Final:1, _:3, Opcode:4, 0:1, 126:7, Len:16/integer, Data/binary>>) ->
+decode_hybi_8_header(<<Final:1, _:3, Opcode:4, 0:1,
+ 126:7, Len:16/integer, Data/binary>>) ->
{Len, Final, Opcode, none, Data};
-decode_hybi_8_header(<<Final:1, _:3, Opcode:4, 0:1, 127:7, Len:64/integer, Data/binary>>) ->
+decode_hybi_8_header(<<Final:1, _:3, Opcode:4, 0:1,
+ 127:7, Len:64/integer, Data/binary>>) ->
{Len, Final, Opcode, none, Data};
-decode_hybi_8_header(<<Final:1, _:3, Opcode:4, 1:1, Len:7, Mask:4/binary, Data/binary>>) when Len < 126 ->
+decode_hybi_8_header(<<Final:1, _:3, Opcode:4, 1:1,
+ Len:7, Mask:4/binary, Data/binary>>)
+ when Len < 126 ->
{Len, Final, Opcode, Mask, Data};
-decode_hybi_8_header(<<Final:1, _:3, Opcode:4, 1:1, 126:7, Len:16/integer, Mask:4/binary, Data/binary>>) ->
+decode_hybi_8_header(<<Final:1, _:3, Opcode:4, 1:1,
+ 126:7, Len:16/integer, Mask:4/binary, Data/binary>>) ->
{Len, Final, Opcode, Mask, Data};
-decode_hybi_8_header(<<Final:1, _:3, Opcode:4, 1:1, 127:7, Len:64/integer, Mask:4/binary, Data/binary>>) ->
+decode_hybi_8_header(<<Final:1, _:3, Opcode:4, 1:1,
+ 127:7, Len:64/integer, Mask:4/binary, Data/binary>>) ->
{Len, Final, Opcode, Mask, Data};
-decode_hybi_8_header(_) ->
- none.
+decode_hybi_8_header(_) -> none.
unmask_hybi_8_int(Offset, _, <<>>, Acc) ->
{Acc, Offset};
-unmask_hybi_8_int(0, <<M:32>>=Mask, <<N:32, Rest/binary>>, Acc) ->
- unmask_hybi_8_int(0, Mask, Rest, <<Acc/binary, (M bxor N):32>>);
-unmask_hybi_8_int(0, <<M:8, _/binary>>=Mask, <<N:8, Rest/binary>>, Acc) ->
- unmask_hybi_8_int(1, Mask, Rest, <<Acc/binary, (M bxor N):8>>);
-unmask_hybi_8_int(1, <<_:8, M:8, _/binary>>=Mask, <<N:8, Rest/binary>>, Acc) ->
- unmask_hybi_8_int(2, Mask, Rest, <<Acc/binary, (M bxor N):8>>);
-unmask_hybi_8_int(2, <<_:16, M:8, _/binary>>=Mask, <<N:8, Rest/binary>>, Acc) ->
- unmask_hybi_8_int(3, Mask, Rest, <<Acc/binary, (M bxor N):8>>);
-unmask_hybi_8_int(3, <<_:24, M:8>>=Mask, <<N:8, Rest/binary>>, Acc) ->
- unmask_hybi_8_int(0, Mask, Rest, <<Acc/binary, (M bxor N):8>>).
+unmask_hybi_8_int(0, <<M:32>> = Mask,
+ <<N:32, Rest/binary>>, Acc) ->
+ unmask_hybi_8_int(0, Mask, Rest,
+ <<Acc/binary, (M bxor N):32>>);
+unmask_hybi_8_int(0, <<M:8, _/binary>> = Mask,
+ <<N:8, Rest/binary>>, Acc) ->
+ unmask_hybi_8_int(1, Mask, Rest,
+ <<Acc/binary, (M bxor N):8>>);
+unmask_hybi_8_int(1, <<_:8, M:8, _/binary>> = Mask,
+ <<N:8, Rest/binary>>, Acc) ->
+ unmask_hybi_8_int(2, Mask, Rest,
+ <<Acc/binary, (M bxor N):8>>);
+unmask_hybi_8_int(2, <<_:16, M:8, _/binary>> = Mask,
+ <<N:8, Rest/binary>>, Acc) ->
+ unmask_hybi_8_int(3, Mask, Rest,
+ <<Acc/binary, (M bxor N):8>>);
+unmask_hybi_8_int(3, <<_:24, M:8>> = Mask,
+ <<N:8, Rest/binary>>, Acc) ->
+ unmask_hybi_8_int(0, Mask, Rest,
+ <<Acc/binary, (M bxor N):8>>).
-unmask_hybi_8(#hybi_8_state{mask=none}=State, Data) ->
+unmask_hybi_8(#hybi_8_state{mask = none} = State,
+ Data) ->
{State, Data};
-unmask_hybi_8(#hybi_8_state{mask=Mask, offset=Offset}=State, Data) ->
- {Unmasked, NewOffset} = unmask_hybi_8_int(Offset, Mask, Data, <<>>),
- {State#hybi_8_state{offset=NewOffset}, Unmasked}.
-
+unmask_hybi_8(#hybi_8_state{mask = Mask,
+ offset = Offset} =
+ State,
+ Data) ->
+ {Unmasked, NewOffset} = unmask_hybi_8_int(Offset, Mask,
+ Data, <<>>),
+ {State#hybi_8_state{offset = NewOffset}, Unmasked}.
process_hybi_8(none, Data) ->
process_hybi_8(#hybi_8_state{}, Data);
-process_hybi_8(State, <<>>) ->
- {State, [], []};
-process_hybi_8(#hybi_8_state{unprocessed=none, unmasked=UnmaskedPre, left=Left}=State,
- Data) when byte_size(Data) < Left ->
+process_hybi_8(State, <<>>) -> {State, [], []};
+process_hybi_8(#hybi_8_state{unprocessed = none,
+ unmasked = UnmaskedPre, left = Left} =
+ State,
+ Data)
+ when byte_size(Data) < Left ->
{State2, Unmasked} = unmask_hybi_8(State, Data),
- {State2#hybi_8_state{left=Left-byte_size(Data), unmasked=[UnmaskedPre, Unmasked]}, [], []};
-process_hybi_8(#hybi_8_state{unprocessed=none, unmasked=UnmaskedPre, opcode=Opcode,
- final_frame=Final, left=Left, unmasked_msg=UnmaskedMsg}=State, Data) ->
- {_State, Unmasked} = unmask_hybi_8(State, binary_part(Data, 0, Left)),
- Unprocessed = binary_part(Data, Left, byte_size(Data)-Left),
+ {State2#hybi_8_state{left = Left - byte_size(Data),
+ unmasked = [UnmaskedPre, Unmasked]},
+ [], []};
+process_hybi_8(#hybi_8_state{unprocessed = none,
+ unmasked = UnmaskedPre, opcode = Opcode,
+ final_frame = Final, left = Left,
+ unmasked_msg = UnmaskedMsg} =
+ State,
+ Data) ->
+ <<ToProcess:(Left)/binary, Unprocessed/binary>> = Data,
+ {_State, Unmasked} = unmask_hybi_8(State, ToProcess),
case Final of
- true ->
- {State3, Recv, Send} = process_hybi_8(#hybi_8_state{}, Unprocessed),
- case Opcode of
- 9 ->
- Frame = encode_frame({'draft-hybi', 8}, Unprocessed, 10),
- {State3#hybi_8_state{unmasked_msg=UnmaskedMsg}, Recv, [Frame|Send]};
- X when X < 3 ->
- {State3, [iolist_to_binary([UnmaskedMsg, UnmaskedPre, Unmasked])|Recv], Send};
- _ ->
- {State3#hybi_8_state{unmasked_msg=UnmaskedMsg}, Recv, Send}
- end;
- _ ->
- process_hybi_8(#hybi_8_state{unmasked_msg=[UnmaskedMsg, UnmaskedPre, Unmasked]}, Unprocessed)
+ true ->
+ {State3, Recv, Send} = process_hybi_8(#hybi_8_state{},
+ Unprocessed),
+ case Opcode of
+ 9 ->
+ Frame = encode_frame({'draft-hybi', 8}, Unprocessed,
+ 10),
+ {State3#hybi_8_state{unmasked_msg = UnmaskedMsg}, Recv,
+ [Frame | Send]};
+ X when X < 3 ->
+ {State3,
+ [iolist_to_binary([UnmaskedMsg, UnmaskedPre, Unmasked])
+ | Recv],
+ Send};
+ _ ->
+ {State3#hybi_8_state{unmasked_msg = UnmaskedMsg}, Recv,
+ Send}
+ end;
+ _ ->
+ process_hybi_8(#hybi_8_state{unmasked_msg =
+ [UnmaskedMsg, UnmaskedPre,
+ Unmasked]},
+ Unprocessed)
end;
-process_hybi_8(#hybi_8_state{unprocessed= <<>>}=State, Data) ->
+process_hybi_8(#hybi_8_state{unprocessed = <<>>} =
+ State,
+ Data) ->
case decode_hybi_8_header(Data) of
- none ->
- {State#hybi_8_state{unprocessed=Data}, [], []};
- {Len, Final, Opcode, Mask, Rest} ->
- process_hybi_8(State#hybi_8_state{mask=Mask, final_frame=Final==1,
- left=Len, opcode=Opcode,
- unprocessed=none}, Rest)
+ none ->
+ {State#hybi_8_state{unprocessed = Data}, [], []};
+ {Len, Final, Opcode, Mask, Rest} ->
+ process_hybi_8(State#hybi_8_state{mask = Mask,
+ final_frame = Final == 1,
+ left = Len, opcode = Opcode,
+ unprocessed = none},
+ Rest)
end;
-process_hybi_8(#hybi_8_state{unprocessed=UnprocessedPre}=State, Data) ->
- process_hybi_8(State#hybi_8_state{unprocessed = <<>>}, <<UnprocessedPre/binary, Data/binary>>).
+process_hybi_8(#hybi_8_state{unprocessed =
+ UnprocessedPre} =
+ State,
+ Data) ->
+ process_hybi_8(State#hybi_8_state{unprocessed = <<>>},
+ <<UnprocessedPre/binary, Data/binary>>).
-
-% Buffering and data handling
-handle_data({'draft-hybi', _}, State, Data, _Socket, WsHandleLoopPid, _SocketMode, _WsAutoExit) ->
+handle_data({'draft-hybi', _}, State, Data, _Socket,
+ WsHandleLoopPid, _SocketMode, _WsAutoExit) ->
{NewState, Recv, Send} = process_hybi_8(State, Data),
- lists:foreach(fun(El) ->
- WsHandleLoopPid ! {browser, El}
- end, Recv),
+ lists:foreach(fun (El) ->
+ WsHandleLoopPid ! {browser, El}
+ end,
+ Recv),
{NewState, Send};
-handle_data(_Vsn, State, Data, _Socket, WsHandleLoopPid, _SocketMode, _WsAutoExit) ->
+handle_data(_Vsn, State, Data, _Socket, WsHandleLoopPid,
+ _SocketMode, _WsAutoExit) ->
{NewState, Recv, Send} = process_hixie_68(State, Data),
- lists:foreach(fun(El) ->
- WsHandleLoopPid ! {browser, El}
- end, Recv),
- {NewState, Send};
-%% Invalid input
-handle_data(_Vsn, _State, _Data, Socket, WsHandleLoopPid, SocketMode, WsAutoExit) ->
- websocket_close(Socket, WsHandleLoopPid, SocketMode, WsAutoExit).
+ lists:foreach(fun (El) ->
+ WsHandleLoopPid ! {browser, El}
+ end,
+ Recv),
+ {NewState, Send}.
-% Close socket and custom handling loop dependency
-websocket_close(Socket, WsHandleLoopPid, SocketMode, WsAutoExit) ->
- case WsAutoExit of
- true ->
- % kill custom handling loop process
- exit(WsHandleLoopPid, kill);
- false ->
- % the killing of the custom handling loop process is handled in the loop itself -> send event
- WsHandleLoopPid ! closed
- end,
- % close main socket
- SocketMode:close(Socket).
+websocket_close(Socket, WsHandleLoopPid, SocketMode,
+ WsAutoExit) ->
+ case WsAutoExit of
+ true ->
+ % kill custom handling loop process
+ exit(WsHandleLoopPid, kill);
+ false -> WsHandleLoopPid ! closed
+ end,
+ SocketMode:close(Socket).
diff --git a/src/web/ejabberd_ws.erl b/src/web/ejabberd_ws.erl
index 5ccaadf34..2e47fe375 100644
--- a/src/web/ejabberd_ws.erl
+++ b/src/web/ejabberd_ws.erl
@@ -8,12 +8,12 @@
% MISULTIN - Websocket Request
%
% >-|-|-(°>
-%
+%
% Copyright (C) 2010, Roberto Ostinelli <roberto@ostinelli.net>.
% All rights reserved.
%
% BSD License
-%
+%
% Redistribution and use in source and binary forms, with or without modification, are permitted provided
% that the following conditions are met:
%
@@ -34,6 +34,7 @@
% POSSIBILITY OF SUCH DAMAGE.
% ==========================================================================================================
-module(ejabberd_ws, [Ws, SocketPid]).
+
-vsn("0.6.1").
% API
@@ -42,39 +43,24 @@
% includes
-include("ejabberd_http.hrl").
-
% ============================ \/ API ======================================================================
-% Description: Returns raw websocket content.
-raw() ->
- Ws.
+raw() -> Ws.
-% Description: Get websocket info.
-get(socket) ->
- Ws#ws.socket;
-get(socket_mode) ->
- Ws#ws.sockmod;
-get(ip) ->
- Ws#ws.ip;
-get(vsn) ->
- Ws#ws.vsn;
-get(origin) ->
- Ws#ws.origin;
-get(host) ->
- Ws#ws.host;
-get(path) ->
- Ws#ws.path;
-get(headers) ->
- Ws#ws.headers.
-
-% send data
-send(Data) ->
- SocketPid ! {send, Data}.
-
-% ============================ /\ API ======================================================================
+get(socket) -> Ws#ws.socket;
+get(socket_mode) -> Ws#ws.sockmod;
+get(ip) -> Ws#ws.ip;
+get(vsn) -> Ws#ws.vsn;
+get(origin) -> Ws#ws.origin;
+get(host) -> Ws#ws.host;
+get(path) -> Ws#ws.path;
+get(headers) -> Ws#ws.headers.
+send(Data) -> SocketPid ! {send, Data}.
+% ============================ /\ API ======================================================================
% ============================ \/ INTERNAL FUNCTIONS =======================================================
% ============================ /\ INTERNAL FUNCTIONS =======================================================
+
diff --git a/src/web/http_bind.hrl b/src/web/http_bind.hrl
index 433703a5b..409f765a7 100644
--- a/src/web/http_bind.hrl
+++ b/src/web/http_bind.hrl
@@ -19,14 +19,29 @@
%%%
%%%----------------------------------------------------------------------
--define(CT_XML, {"Content-Type", "text/xml; charset=utf-8"}).
--define(CT_PLAIN, {"Content-Type", "text/plain"}).
+-define(CT_XML,
+ {<<"Content-Type">>, <<"text/xml; charset=utf-8">>}).
--define(AC_ALLOW_ORIGIN, {"Access-Control-Allow-Origin", "*"}).
--define(AC_ALLOW_METHODS, {"Access-Control-Allow-Methods", "GET, POST, OPTIONS"}).
--define(AC_ALLOW_HEADERS, {"Access-Control-Allow-Headers", "Content-Type"}).
--define(AC_MAX_AGE, {"Access-Control-Max-Age", "86400"}).
+-define(CT_PLAIN,
+ {<<"Content-Type">>, <<"text/plain">>}).
--define(OPTIONS_HEADER, [?CT_PLAIN, ?AC_ALLOW_ORIGIN, ?AC_ALLOW_METHODS,
- ?AC_ALLOW_HEADERS, ?AC_MAX_AGE]).
--define(HEADER, [?CT_XML, ?AC_ALLOW_ORIGIN, ?AC_ALLOW_HEADERS]).
+-define(AC_ALLOW_ORIGIN,
+ {<<"Access-Control-Allow-Origin">>, <<"*">>}).
+
+-define(AC_ALLOW_METHODS,
+ {<<"Access-Control-Allow-Methods">>,
+ <<"GET, POST, OPTIONS">>}).
+
+-define(AC_ALLOW_HEADERS,
+ {<<"Access-Control-Allow-Headers">>,
+ <<"Content-Type">>}).
+
+-define(AC_MAX_AGE,
+ {<<"Access-Control-Max-Age">>, <<"86400">>}).
+
+-define(OPTIONS_HEADER,
+ [?CT_PLAIN, ?AC_ALLOW_ORIGIN, ?AC_ALLOW_METHODS,
+ ?AC_ALLOW_HEADERS, ?AC_MAX_AGE]).
+
+-define(HEADER,
+ [?CT_XML, ?AC_ALLOW_ORIGIN, ?AC_ALLOW_HEADERS]).
diff --git a/src/web/mochijson2.erl b/src/web/mochijson2.erl
deleted file mode 100644
index 710ae9bce..000000000
--- a/src/web/mochijson2.erl
+++ /dev/null
@@ -1,782 +0,0 @@
-%% @author Bob Ippolito <bob@mochimedia.com>
-%% @copyright 2007 Mochi Media, Inc.
-
-%% @doc Yet another JSON (RFC 4627) library for Erlang. mochijson2 works
-%% with binaries as strings, arrays as lists (without an {array, _})
-%% wrapper and it only knows how to decode UTF-8 (and ASCII).
-
--module(mochijson2).
--author('bob@mochimedia.com').
--export([encoder/1, encode/1]).
--export([decoder/1, decode/1]).
-
-% This is a macro to placate syntax highlighters..
--define(Q, $\").
--define(ADV_COL(S, N), S#decoder{offset=N+S#decoder.offset,
- column=N+S#decoder.column}).
--define(INC_COL(S), S#decoder{offset=1+S#decoder.offset,
- column=1+S#decoder.column}).
--define(INC_LINE(S), S#decoder{offset=1+S#decoder.offset,
- column=1,
- line=1+S#decoder.line}).
--define(INC_CHAR(S, C),
- case C of
- $\n ->
- S#decoder{column=1,
- line=1+S#decoder.line,
- offset=1+S#decoder.offset};
- _ ->
- S#decoder{column=1+S#decoder.column,
- offset=1+S#decoder.offset}
- end).
--define(IS_WHITESPACE(C),
- (C =:= $\s orelse C =:= $\t orelse C =:= $\r orelse C =:= $\n)).
-
-%% @type iolist() = [char() | binary() | iolist()]
-%% @type iodata() = iolist() | binary()
-%% @type json_string() = atom | binary()
-%% @type json_number() = integer() | float()
-%% @type json_array() = [json_term()]
-%% @type json_object() = {struct, [{json_string(), json_term()}]}
-%% @type json_iolist() = {json, iolist()}
-%% @type json_term() = json_string() | json_number() | json_array() |
-%% json_object() | json_iolist()
-
--record(encoder, {handler=null,
- utf8=false}).
-
--record(decoder, {object_hook=null,
- offset=0,
- line=1,
- column=1,
- state=null}).
-
-%% @spec encoder([encoder_option()]) -> function()
-%% @doc Create an encoder/1 with the given options.
-%% @type encoder_option() = handler_option() | utf8_option()
-%% @type utf8_option() = boolean(). Emit unicode as utf8 (default - false)
-encoder(Options) ->
- State = parse_encoder_options(Options, #encoder{}),
- fun (O) -> json_encode(O, State) end.
-
-%% @spec encode(json_term()) -> iolist()
-%% @doc Encode the given as JSON to an iolist.
-encode(Any) ->
- json_encode(Any, #encoder{}).
-
-%% @spec decoder([decoder_option()]) -> function()
-%% @doc Create a decoder/1 with the given options.
-decoder(Options) ->
- State = parse_decoder_options(Options, #decoder{}),
- fun (O) -> json_decode(O, State) end.
-
-%% @spec decode(iolist()) -> json_term()
-%% @doc Decode the given iolist to Erlang terms.
-decode(S) ->
- json_decode(S, #decoder{}).
-
-%% Internal API
-
-parse_encoder_options([], State) ->
- State;
-parse_encoder_options([{handler, Handler} | Rest], State) ->
- parse_encoder_options(Rest, State#encoder{handler=Handler});
-parse_encoder_options([{utf8, Switch} | Rest], State) ->
- parse_encoder_options(Rest, State#encoder{utf8=Switch}).
-
-parse_decoder_options([], State) ->
- State;
-parse_decoder_options([{object_hook, Hook} | Rest], State) ->
- parse_decoder_options(Rest, State#decoder{object_hook=Hook}).
-
-json_encode(true, _State) ->
- <<"true">>;
-json_encode(false, _State) ->
- <<"false">>;
-json_encode(null, _State) ->
- <<"null">>;
-json_encode(I, _State) when is_integer(I) andalso I >= -2147483648 andalso I =< 2147483647 ->
- %% Anything outside of 32-bit integers should be encoded as a float
- integer_to_list(I);
-json_encode(I, _State) when is_integer(I) ->
- mochinum:digits(float(I));
-json_encode(F, _State) when is_float(F) ->
- mochinum:digits(F);
-json_encode(S, State) when is_binary(S); is_atom(S) ->
- json_encode_string(S, State);
-json_encode(Array, State) when is_list(Array) ->
- json_encode_array(Array, State);
-json_encode({struct, Props}, State) when is_list(Props) ->
- json_encode_proplist(Props, State);
-json_encode({json, IoList}, _State) ->
- IoList;
-json_encode(Bad, #encoder{handler=null}) ->
- exit({json_encode, {bad_term, Bad}});
-json_encode(Bad, State=#encoder{handler=Handler}) ->
- json_encode(Handler(Bad), State).
-
-json_encode_array([], _State) ->
- <<"[]">>;
-json_encode_array(L, State) ->
- F = fun (O, Acc) ->
- [$,, json_encode(O, State) | Acc]
- end,
- [$, | Acc1] = lists:foldl(F, "[", L),
- lists:reverse([$\] | Acc1]).
-
-json_encode_proplist([], _State) ->
- <<"{}">>;
-json_encode_proplist(Props, State) ->
- F = fun ({K, V}, Acc) ->
- KS = json_encode_string(K, State),
- VS = json_encode(V, State),
- [$,, VS, $:, KS | Acc]
- end,
- [$, | Acc1] = lists:foldl(F, "{", Props),
- lists:reverse([$\} | Acc1]).
-
-json_encode_string(A, State) when is_atom(A) ->
- L = atom_to_list(A),
- case json_string_is_safe(L) of
- true ->
- [?Q, L, ?Q];
- false ->
- json_encode_string_unicode(xmerl_ucs:from_utf8(L), State, [?Q])
- end;
-json_encode_string(B, State) when is_binary(B) ->
- case json_bin_is_safe(B) of
- true ->
- [?Q, B, ?Q];
- false ->
- json_encode_string_unicode(xmerl_ucs:from_utf8(B), State, [?Q])
- end;
-json_encode_string(I, _State) when is_integer(I) ->
- [?Q, integer_to_list(I), ?Q];
-json_encode_string(L, State) when is_list(L) ->
- case json_string_is_safe(L) of
- true ->
- [?Q, L, ?Q];
- false ->
- json_encode_string_unicode(L, State, [?Q])
- end.
-
-json_string_is_safe([]) ->
- true;
-json_string_is_safe([C | Rest]) ->
- case C of
- ?Q ->
- false;
- $\\ ->
- false;
- $\b ->
- false;
- $\f ->
- false;
- $\n ->
- false;
- $\r ->
- false;
- $\t ->
- false;
- C when C >= 0, C < $\s; C >= 16#7f, C =< 16#10FFFF ->
- false;
- C when C < 16#7f ->
- json_string_is_safe(Rest);
- _ ->
- false
- end.
-
-json_bin_is_safe(<<>>) ->
- true;
-json_bin_is_safe(<<C, Rest/binary>>) ->
- case C of
- ?Q ->
- false;
- $\\ ->
- false;
- $\b ->
- false;
- $\f ->
- false;
- $\n ->
- false;
- $\r ->
- false;
- $\t ->
- false;
- C when C >= 0, C < $\s; C >= 16#7f ->
- false;
- C when C < 16#7f ->
- json_bin_is_safe(Rest)
- end.
-
-json_encode_string_unicode([], _State, Acc) ->
- lists:reverse([$\" | Acc]);
-json_encode_string_unicode([C | Cs], State, Acc) ->
- Acc1 = case C of
- ?Q ->
- [?Q, $\\ | Acc];
- %% Escaping solidus is only useful when trying to protect
- %% against "</script>" injection attacks which are only
- %% possible when JSON is inserted into a HTML document
- %% in-line. mochijson2 does not protect you from this, so
- %% if you do insert directly into HTML then you need to
- %% uncomment the following case or escape the output of encode.
- %%
- %% $/ ->
- %% [$/, $\\ | Acc];
- %%
- $\\ ->
- [$\\, $\\ | Acc];
- $\b ->
- [$b, $\\ | Acc];
- $\f ->
- [$f, $\\ | Acc];
- $\n ->
- [$n, $\\ | Acc];
- $\r ->
- [$r, $\\ | Acc];
- $\t ->
- [$t, $\\ | Acc];
- C when C >= 0, C < $\s ->
- [unihex(C) | Acc];
- C when C >= 16#7f, C =< 16#10FFFF, State#encoder.utf8 ->
- [xmerl_ucs:to_utf8(C) | Acc];
- C when C >= 16#7f, C =< 16#10FFFF, not State#encoder.utf8 ->
- [unihex(C) | Acc];
- C when C < 16#7f ->
- [C | Acc];
- _ ->
- exit({json_encode, {bad_char, C}})
- end,
- json_encode_string_unicode(Cs, State, Acc1).
-
-hexdigit(C) when C >= 0, C =< 9 ->
- C + $0;
-hexdigit(C) when C =< 15 ->
- C + $a - 10.
-
-unihex(C) when C < 16#10000 ->
- <<D3:4, D2:4, D1:4, D0:4>> = <<C:16>>,
- Digits = [hexdigit(D) || D <- [D3, D2, D1, D0]],
- [$\\, $u | Digits];
-unihex(C) when C =< 16#10FFFF ->
- N = C - 16#10000,
- S1 = 16#d800 bor ((N bsr 10) band 16#3ff),
- S2 = 16#dc00 bor (N band 16#3ff),
- [unihex(S1), unihex(S2)].
-
-json_decode(L, S) when is_list(L) ->
- json_decode(iolist_to_binary(L), S);
-json_decode(B, S) ->
- {Res, S1} = decode1(B, S),
- {eof, _} = tokenize(B, S1#decoder{state=trim}),
- Res.
-
-decode1(B, S=#decoder{state=null}) ->
- case tokenize(B, S#decoder{state=any}) of
- {{const, C}, S1} ->
- {C, S1};
- {start_array, S1} ->
- decode_array(B, S1);
- {start_object, S1} ->
- decode_object(B, S1)
- end.
-
-make_object(V, #decoder{object_hook=null}) ->
- V;
-make_object(V, #decoder{object_hook=Hook}) ->
- Hook(V).
-
-decode_object(B, S) ->
- decode_object(B, S#decoder{state=key}, []).
-
-decode_object(B, S=#decoder{state=key}, Acc) ->
- case tokenize(B, S) of
- {end_object, S1} ->
- V = make_object({struct, lists:reverse(Acc)}, S1),
- {V, S1#decoder{state=null}};
- {{const, K}, S1} ->
- {colon, S2} = tokenize(B, S1),
- {V, S3} = decode1(B, S2#decoder{state=null}),
- decode_object(B, S3#decoder{state=comma}, [{K, V} | Acc])
- end;
-decode_object(B, S=#decoder{state=comma}, Acc) ->
- case tokenize(B, S) of
- {end_object, S1} ->
- V = make_object({struct, lists:reverse(Acc)}, S1),
- {V, S1#decoder{state=null}};
- {comma, S1} ->
- decode_object(B, S1#decoder{state=key}, Acc)
- end.
-
-decode_array(B, S) ->
- decode_array(B, S#decoder{state=any}, []).
-
-decode_array(B, S=#decoder{state=any}, Acc) ->
- case tokenize(B, S) of
- {end_array, S1} ->
- {lists:reverse(Acc), S1#decoder{state=null}};
- {start_array, S1} ->
- {Array, S2} = decode_array(B, S1),
- decode_array(B, S2#decoder{state=comma}, [Array | Acc]);
- {start_object, S1} ->
- {Array, S2} = decode_object(B, S1),
- decode_array(B, S2#decoder{state=comma}, [Array | Acc]);
- {{const, Const}, S1} ->
- decode_array(B, S1#decoder{state=comma}, [Const | Acc])
- end;
-decode_array(B, S=#decoder{state=comma}, Acc) ->
- case tokenize(B, S) of
- {end_array, S1} ->
- {lists:reverse(Acc), S1#decoder{state=null}};
- {comma, S1} ->
- decode_array(B, S1#decoder{state=any}, Acc)
- end.
-
-tokenize_string(B, S=#decoder{offset=O}) ->
- case tokenize_string_fast(B, O) of
- {escape, O1} ->
- Length = O1 - O,
- S1 = ?ADV_COL(S, Length),
- <<_:O/binary, Head:Length/binary, _/binary>> = B,
- tokenize_string(B, S1, lists:reverse(binary_to_list(Head)));
- O1 ->
- Length = O1 - O,
- <<_:O/binary, String:Length/binary, ?Q, _/binary>> = B,
- {{const, String}, ?ADV_COL(S, Length + 1)}
- end.
-
-tokenize_string_fast(B, O) ->
- case B of
- <<_:O/binary, ?Q, _/binary>> ->
- O;
- <<_:O/binary, $\\, _/binary>> ->
- {escape, O};
- <<_:O/binary, C1, _/binary>> when C1 < 128 ->
- tokenize_string_fast(B, 1 + O);
- <<_:O/binary, C1, C2, _/binary>> when C1 >= 194, C1 =< 223,
- C2 >= 128, C2 =< 191 ->
- tokenize_string_fast(B, 2 + O);
- <<_:O/binary, C1, C2, C3, _/binary>> when C1 >= 224, C1 =< 239,
- C2 >= 128, C2 =< 191,
- C3 >= 128, C3 =< 191 ->
- tokenize_string_fast(B, 3 + O);
- <<_:O/binary, C1, C2, C3, C4, _/binary>> when C1 >= 240, C1 =< 244,
- C2 >= 128, C2 =< 191,
- C3 >= 128, C3 =< 191,
- C4 >= 128, C4 =< 191 ->
- tokenize_string_fast(B, 4 + O);
- _ ->
- throw(invalid_utf8)
- end.
-
-tokenize_string(B, S=#decoder{offset=O}, Acc) ->
- case B of
- <<_:O/binary, ?Q, _/binary>> ->
- {{const, iolist_to_binary(lists:reverse(Acc))}, ?INC_COL(S)};
- <<_:O/binary, "\\\"", _/binary>> ->
- tokenize_string(B, ?ADV_COL(S, 2), [$\" | Acc]);
- <<_:O/binary, "\\\\", _/binary>> ->
- tokenize_string(B, ?ADV_COL(S, 2), [$\\ | Acc]);
- <<_:O/binary, "\\/", _/binary>> ->
- tokenize_string(B, ?ADV_COL(S, 2), [$/ | Acc]);
- <<_:O/binary, "\\b", _/binary>> ->
- tokenize_string(B, ?ADV_COL(S, 2), [$\b | Acc]);
- <<_:O/binary, "\\f", _/binary>> ->
- tokenize_string(B, ?ADV_COL(S, 2), [$\f | Acc]);
- <<_:O/binary, "\\n", _/binary>> ->
- tokenize_string(B, ?ADV_COL(S, 2), [$\n | Acc]);
- <<_:O/binary, "\\r", _/binary>> ->
- tokenize_string(B, ?ADV_COL(S, 2), [$\r | Acc]);
- <<_:O/binary, "\\t", _/binary>> ->
- tokenize_string(B, ?ADV_COL(S, 2), [$\t | Acc]);
- <<_:O/binary, "\\u", C3, C2, C1, C0, Rest/binary>> ->
- C = erlang:list_to_integer([C3, C2, C1, C0], 16),
- if C > 16#D7FF, C < 16#DC00 ->
- %% coalesce UTF-16 surrogate pair
- <<"\\u", D3, D2, D1, D0, _/binary>> = Rest,
- D = erlang:list_to_integer([D3,D2,D1,D0], 16),
- [CodePoint] = xmerl_ucs:from_utf16be(<<C:16/big-unsigned-integer,
- D:16/big-unsigned-integer>>),
- Acc1 = lists:reverse(xmerl_ucs:to_utf8(CodePoint), Acc),
- tokenize_string(B, ?ADV_COL(S, 12), Acc1);
- true ->
- Acc1 = lists:reverse(xmerl_ucs:to_utf8(C), Acc),
- tokenize_string(B, ?ADV_COL(S, 6), Acc1)
- end;
- <<_:O/binary, C, _/binary>> ->
- tokenize_string(B, ?INC_CHAR(S, C), [C | Acc])
- end.
-
-tokenize_number(B, S) ->
- case tokenize_number(B, sign, S, []) of
- {{int, Int}, S1} ->
- {{const, list_to_integer(Int)}, S1};
- {{float, Float}, S1} ->
- {{const, list_to_float(Float)}, S1}
- end.
-
-tokenize_number(B, sign, S=#decoder{offset=O}, []) ->
- case B of
- <<_:O/binary, $-, _/binary>> ->
- tokenize_number(B, int, ?INC_COL(S), [$-]);
- _ ->
- tokenize_number(B, int, S, [])
- end;
-tokenize_number(B, int, S=#decoder{offset=O}, Acc) ->
- case B of
- <<_:O/binary, $0, _/binary>> ->
- tokenize_number(B, frac, ?INC_COL(S), [$0 | Acc]);
- <<_:O/binary, C, _/binary>> when C >= $1 andalso C =< $9 ->
- tokenize_number(B, int1, ?INC_COL(S), [C | Acc])
- end;
-tokenize_number(B, int1, S=#decoder{offset=O}, Acc) ->
- case B of
- <<_:O/binary, C, _/binary>> when C >= $0 andalso C =< $9 ->
- tokenize_number(B, int1, ?INC_COL(S), [C | Acc]);
- _ ->
- tokenize_number(B, frac, S, Acc)
- end;
-tokenize_number(B, frac, S=#decoder{offset=O}, Acc) ->
- case B of
- <<_:O/binary, $., C, _/binary>> when C >= $0, C =< $9 ->
- tokenize_number(B, frac1, ?ADV_COL(S, 2), [C, $. | Acc]);
- <<_:O/binary, E, _/binary>> when E =:= $e orelse E =:= $E ->
- tokenize_number(B, esign, ?INC_COL(S), [$e, $0, $. | Acc]);
- _ ->
- {{int, lists:reverse(Acc)}, S}
- end;
-tokenize_number(B, frac1, S=#decoder{offset=O}, Acc) ->
- case B of
- <<_:O/binary, C, _/binary>> when C >= $0 andalso C =< $9 ->
- tokenize_number(B, frac1, ?INC_COL(S), [C | Acc]);
- <<_:O/binary, E, _/binary>> when E =:= $e orelse E =:= $E ->
- tokenize_number(B, esign, ?INC_COL(S), [$e | Acc]);
- _ ->
- {{float, lists:reverse(Acc)}, S}
- end;
-tokenize_number(B, esign, S=#decoder{offset=O}, Acc) ->
- case B of
- <<_:O/binary, C, _/binary>> when C =:= $- orelse C=:= $+ ->
- tokenize_number(B, eint, ?INC_COL(S), [C | Acc]);
- _ ->
- tokenize_number(B, eint, S, Acc)
- end;
-tokenize_number(B, eint, S=#decoder{offset=O}, Acc) ->
- case B of
- <<_:O/binary, C, _/binary>> when C >= $0 andalso C =< $9 ->
- tokenize_number(B, eint1, ?INC_COL(S), [C | Acc])
- end;
-tokenize_number(B, eint1, S=#decoder{offset=O}, Acc) ->
- case B of
- <<_:O/binary, C, _/binary>> when C >= $0 andalso C =< $9 ->
- tokenize_number(B, eint1, ?INC_COL(S), [C | Acc]);
- _ ->
- {{float, lists:reverse(Acc)}, S}
- end.
-
-tokenize(B, S=#decoder{offset=O}) ->
- case B of
- <<_:O/binary, C, _/binary>> when ?IS_WHITESPACE(C) ->
- tokenize(B, ?INC_CHAR(S, C));
- <<_:O/binary, "{", _/binary>> ->
- {start_object, ?INC_COL(S)};
- <<_:O/binary, "}", _/binary>> ->
- {end_object, ?INC_COL(S)};
- <<_:O/binary, "[", _/binary>> ->
- {start_array, ?INC_COL(S)};
- <<_:O/binary, "]", _/binary>> ->
- {end_array, ?INC_COL(S)};
- <<_:O/binary, ",", _/binary>> ->
- {comma, ?INC_COL(S)};
- <<_:O/binary, ":", _/binary>> ->
- {colon, ?INC_COL(S)};
- <<_:O/binary, "null", _/binary>> ->
- {{const, null}, ?ADV_COL(S, 4)};
- <<_:O/binary, "true", _/binary>> ->
- {{const, true}, ?ADV_COL(S, 4)};
- <<_:O/binary, "false", _/binary>> ->
- {{const, false}, ?ADV_COL(S, 5)};
- <<_:O/binary, "\"", _/binary>> ->
- tokenize_string(B, ?INC_COL(S));
- <<_:O/binary, C, _/binary>> when (C >= $0 andalso C =< $9)
- orelse C =:= $- ->
- tokenize_number(B, S);
- <<_:O/binary>> ->
- trim = S#decoder.state,
- {eof, S}
- end.
-%%
-%% Tests
-%%
--include_lib("eunit/include/eunit.hrl").
--ifdef(TEST).
-
-
-%% testing constructs borrowed from the Yaws JSON implementation.
-
-%% Create an object from a list of Key/Value pairs.
-
-obj_new() ->
- {struct, []}.
-
-is_obj({struct, Props}) ->
- F = fun ({K, _}) when is_binary(K) -> true end,
- lists:all(F, Props).
-
-obj_from_list(Props) ->
- Obj = {struct, Props},
- ?assert(is_obj(Obj)),
- Obj.
-
-%% Test for equivalence of Erlang terms.
-%% Due to arbitrary order of construction, equivalent objects might
-%% compare unequal as erlang terms, so we need to carefully recurse
-%% through aggregates (tuples and objects).
-
-equiv({struct, Props1}, {struct, Props2}) ->
- equiv_object(Props1, Props2);
-equiv(L1, L2) when is_list(L1), is_list(L2) ->
- equiv_list(L1, L2);
-equiv(N1, N2) when is_number(N1), is_number(N2) -> N1 == N2;
-equiv(B1, B2) when is_binary(B1), is_binary(B2) -> B1 == B2;
-equiv(A, A) when A =:= true orelse A =:= false orelse A =:= null -> true.
-
-%% Object representation and traversal order is unknown.
-%% Use the sledgehammer and sort property lists.
-
-equiv_object(Props1, Props2) ->
- L1 = lists:keysort(1, Props1),
- L2 = lists:keysort(1, Props2),
- Pairs = lists:zip(L1, L2),
- true = lists:all(fun({{K1, V1}, {K2, V2}}) ->
- equiv(K1, K2) and equiv(V1, V2)
- end, Pairs).
-
-%% Recursively compare tuple elements for equivalence.
-
-equiv_list([], []) ->
- true;
-equiv_list([V1 | L1], [V2 | L2]) ->
- equiv(V1, V2) andalso equiv_list(L1, L2).
-
-decode_test() ->
- [1199344435545.0, 1] = decode(<<"[1199344435545.0,1]">>),
- <<16#F0,16#9D,16#9C,16#95>> = decode([34,"\\ud835","\\udf15",34]).
-
-e2j_vec_test() ->
- test_one(e2j_test_vec(utf8), 1).
-
-test_one([], _N) ->
- %% io:format("~p tests passed~n", [N-1]),
- ok;
-test_one([{E, J} | Rest], N) ->
- %% io:format("[~p] ~p ~p~n", [N, E, J]),
- true = equiv(E, decode(J)),
- true = equiv(E, decode(encode(E))),
- test_one(Rest, 1+N).
-
-e2j_test_vec(utf8) ->
- [
- {1, "1"},
- {3.1416, "3.14160"}, %% text representation may truncate, trail zeroes
- {-1, "-1"},
- {-3.1416, "-3.14160"},
- {12.0e10, "1.20000e+11"},
- {1.234E+10, "1.23400e+10"},
- {-1.234E-10, "-1.23400e-10"},
- {10.0, "1.0e+01"},
- {123.456, "1.23456E+2"},
- {10.0, "1e1"},
- {<<"foo">>, "\"foo\""},
- {<<"foo", 5, "bar">>, "\"foo\\u0005bar\""},
- {<<"">>, "\"\""},
- {<<"\n\n\n">>, "\"\\n\\n\\n\""},
- {<<"\" \b\f\r\n\t\"">>, "\"\\\" \\b\\f\\r\\n\\t\\\"\""},
- {obj_new(), "{}"},
- {obj_from_list([{<<"foo">>, <<"bar">>}]), "{\"foo\":\"bar\"}"},
- {obj_from_list([{<<"foo">>, <<"bar">>}, {<<"baz">>, 123}]),
- "{\"foo\":\"bar\",\"baz\":123}"},
- {[], "[]"},
- {[[]], "[[]]"},
- {[1, <<"foo">>], "[1,\"foo\"]"},
-
- %% json array in a json object
- {obj_from_list([{<<"foo">>, [123]}]),
- "{\"foo\":[123]}"},
-
- %% json object in a json object
- {obj_from_list([{<<"foo">>, obj_from_list([{<<"bar">>, true}])}]),
- "{\"foo\":{\"bar\":true}}"},
-
- %% fold evaluation order
- {obj_from_list([{<<"foo">>, []},
- {<<"bar">>, obj_from_list([{<<"baz">>, true}])},
- {<<"alice">>, <<"bob">>}]),
- "{\"foo\":[],\"bar\":{\"baz\":true},\"alice\":\"bob\"}"},
-
- %% json object in a json array
- {[-123, <<"foo">>, obj_from_list([{<<"bar">>, []}]), null],
- "[-123,\"foo\",{\"bar\":[]},null]"}
- ].
-
-%% test utf8 encoding
-encoder_utf8_test() ->
- %% safe conversion case (default)
- [34,"\\u0001","\\u0442","\\u0435","\\u0441","\\u0442",34] =
- encode(<<1,"\321\202\320\265\321\201\321\202">>),
-
- %% raw utf8 output (optional)
- Enc = mochijson2:encoder([{utf8, true}]),
- [34,"\\u0001",[209,130],[208,181],[209,129],[209,130],34] =
- Enc(<<1,"\321\202\320\265\321\201\321\202">>).
-
-input_validation_test() ->
- Good = [
- {16#00A3, <<?Q, 16#C2, 16#A3, ?Q>>}, %% pound
- {16#20AC, <<?Q, 16#E2, 16#82, 16#AC, ?Q>>}, %% euro
- {16#10196, <<?Q, 16#F0, 16#90, 16#86, 16#96, ?Q>>} %% denarius
- ],
- lists:foreach(fun({CodePoint, UTF8}) ->
- Expect = list_to_binary(xmerl_ucs:to_utf8(CodePoint)),
- Expect = decode(UTF8)
- end, Good),
-
- Bad = [
- %% 2nd, 3rd, or 4th byte of a multi-byte sequence w/o leading byte
- <<?Q, 16#80, ?Q>>,
- %% missing continuations, last byte in each should be 80-BF
- <<?Q, 16#C2, 16#7F, ?Q>>,
- <<?Q, 16#E0, 16#80,16#7F, ?Q>>,
- <<?Q, 16#F0, 16#80, 16#80, 16#7F, ?Q>>,
- %% we don't support code points > 10FFFF per RFC 3629
- <<?Q, 16#F5, 16#80, 16#80, 16#80, ?Q>>
- ],
- lists:foreach(
- fun(X) ->
- ok = try decode(X) catch invalid_utf8 -> ok end,
- %% could be {ucs,{bad_utf8_character_code}} or
- %% {json_encode,{bad_char,_}}
- {'EXIT', _} = (catch encode(X))
- end, Bad).
-
-inline_json_test() ->
- ?assertEqual(<<"\"iodata iodata\"">>,
- iolist_to_binary(
- encode({json, [<<"\"iodata">>, " iodata\""]}))),
- ?assertEqual({struct, [{<<"key">>, <<"iodata iodata">>}]},
- decode(
- encode({struct,
- [{key, {json, [<<"\"iodata">>, " iodata\""]}}]}))),
- ok.
-
-big_unicode_test() ->
- UTF8Seq = list_to_binary(xmerl_ucs:to_utf8(16#0001d120)),
- ?assertEqual(
- <<"\"\\ud834\\udd20\"">>,
- iolist_to_binary(encode(UTF8Seq))),
- ?assertEqual(
- UTF8Seq,
- decode(iolist_to_binary(encode(UTF8Seq)))),
- ok.
-
-custom_decoder_test() ->
- ?assertEqual(
- {struct, [{<<"key">>, <<"value">>}]},
- (decoder([]))("{\"key\": \"value\"}")),
- F = fun ({struct, [{<<"key">>, <<"value">>}]}) -> win end,
- ?assertEqual(
- win,
- (decoder([{object_hook, F}]))("{\"key\": \"value\"}")),
- ok.
-
-atom_test() ->
- %% JSON native atoms
- [begin
- ?assertEqual(A, decode(atom_to_list(A))),
- ?assertEqual(iolist_to_binary(atom_to_list(A)),
- iolist_to_binary(encode(A)))
- end || A <- [true, false, null]],
- %% Atom to string
- ?assertEqual(
- <<"\"foo\"">>,
- iolist_to_binary(encode(foo))),
- ?assertEqual(
- <<"\"\\ud834\\udd20\"">>,
- iolist_to_binary(encode(list_to_atom(xmerl_ucs:to_utf8(16#0001d120))))),
- ok.
-
-key_encode_test() ->
- %% Some forms are accepted as keys that would not be strings in other
- %% cases
- ?assertEqual(
- <<"{\"foo\":1}">>,
- iolist_to_binary(encode({struct, [{foo, 1}]}))),
- ?assertEqual(
- <<"{\"foo\":1}">>,
- iolist_to_binary(encode({struct, [{<<"foo">>, 1}]}))),
- ?assertEqual(
- <<"{\"foo\":1}">>,
- iolist_to_binary(encode({struct, [{"foo", 1}]}))),
- ?assertEqual(
- <<"{\"\\ud834\\udd20\":1}">>,
- iolist_to_binary(
- encode({struct, [{[16#0001d120], 1}]}))),
- ?assertEqual(
- <<"{\"1\":1}">>,
- iolist_to_binary(encode({struct, [{1, 1}]}))),
- ok.
-
-unsafe_chars_test() ->
- Chars = "\"\\\b\f\n\r\t",
- [begin
- ?assertEqual(false, json_string_is_safe([C])),
- ?assertEqual(false, json_bin_is_safe(<<C>>)),
- ?assertEqual(<<C>>, decode(encode(<<C>>)))
- end || C <- Chars],
- ?assertEqual(
- false,
- json_string_is_safe([16#0001d120])),
- ?assertEqual(
- false,
- json_bin_is_safe(list_to_binary(xmerl_ucs:to_utf8(16#0001d120)))),
- ?assertEqual(
- [16#0001d120],
- xmerl_ucs:from_utf8(
- binary_to_list(
- decode(encode(list_to_atom(xmerl_ucs:to_utf8(16#0001d120))))))),
- ?assertEqual(
- false,
- json_string_is_safe([16#110000])),
- ?assertEqual(
- false,
- json_bin_is_safe(list_to_binary(xmerl_ucs:to_utf8([16#110000])))),
- %% solidus can be escaped but isn't unsafe by default
- ?assertEqual(
- <<"/">>,
- decode(<<"\"\\/\"">>)),
- ok.
-
-int_test() ->
- ?assertEqual(0, decode("0")),
- ?assertEqual(1, decode("1")),
- ?assertEqual(11, decode("11")),
- ok.
-
-float_fallback_test() ->
- ?assertEqual(<<"-2147483649.0">>, iolist_to_binary(encode(-2147483649))),
- ?assertEqual(<<"2147483648.0">>, iolist_to_binary(encode(2147483648))),
- ok.
-
-handler_test() ->
- ?assertEqual(
- {'EXIT',{json_encode,{bad_term,{}}}},
- catch encode({})),
- F = fun ({}) -> [] end,
- ?assertEqual(
- <<"[]">>,
- iolist_to_binary((encoder([{handler, F}]))({}))),
- ok.
-
--endif. \ No newline at end of file
diff --git a/src/web/mod_bosh.erl b/src/web/mod_bosh.erl
index 64b85357b..70f8fce05 100644
--- a/src/web/mod_bosh.erl
+++ b/src/web/mod_bosh.erl
@@ -26,144 +26,139 @@
%%%
%%%-------------------------------------------------------------------
-module(mod_bosh).
+
-author('steve@zeank.in-berlin.de').
%%-define(ejabberd_debug, true).
-behaviour(gen_mod).
--export([
- start/2,
- stop/1,
- process/2,
- open_session/2,
- close_session/1,
- find_session/1,
- node_up/1,
- node_down/1,
- migrate/3
- ]).
+-export([start/2, stop/1, process/2, open_session/2,
+ close_session/1, find_session/1, node_up/1, node_down/1,
+ migrate/3]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
+
-include("ejabberd_http.hrl").
+
-include("bosh.hrl").
--record(bosh, {sid, pid}).
+-record(bosh, {sid = <<"">> :: binary() | '$1',
+ pid = self() :: pid() | '$2'}).
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
-process([], #request{method = 'POST',
- data = []}) ->
+process([], #request{method = 'POST', data = <<>>}) ->
?DEBUG("Bad Request: no data", []),
- {400, ?HEADER, {xmlelement, "h1", [],
- [{xmlcdata, "400 Bad Request"}]}};
-process([], #request{method = 'POST',
- data = Data,
- ip = IP}) ->
- ?DEBUG("Incoming data: ~s", [Data]),
- ejabberd_bosh:process_request(Data, IP);
-process([], #request{method = 'GET',
- data = []}) ->
- {200, ?HEADER, get_human_html_xmlel()};
-process([], #request{method = 'OPTIONS',
- data = []}) ->
+ {400, ?HEADER(?CT_XML),
+ #xmlel{name = <<"h1">>, attrs = [],
+ children = [{xmlcdata, <<"400 Bad Request">>}]}};
+process([],
+ #request{method = 'POST', data = Data, ip = IP, headers = Hdrs}) ->
+ ?DEBUG("Incoming data: ~p", [Data]),
+ Type = get_type(Hdrs),
+ ejabberd_bosh:process_request(Data, IP, Type);
+process([], #request{method = 'GET', data = <<>>}) ->
+ {200, ?HEADER(?CT_XML), get_human_html_xmlel()};
+process([], #request{method = 'OPTIONS', data = <<>>}) ->
{200, ?OPTIONS_HEADER, []};
process(_Path, _Request) ->
?DEBUG("Bad Request: ~p", [_Request]),
- {400, ?HEADER, {xmlelement, "h1", [],
- [{xmlcdata, "400 Bad Request"}]}}.
+ {400, ?HEADER(?CT_XML),
+ #xmlel{name = <<"h1">>, attrs = [],
+ children = [{xmlcdata, <<"400 Bad Request">>}]}}.
get_human_html_xmlel() ->
- Heading = "ejabberd " ++ atom_to_list(?MODULE),
- {xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"}],
- [{xmlelement, "head", [],
- [{xmlelement, "title", [], [{xmlcdata, Heading}]}]},
- {xmlelement, "body", [],
- [{xmlelement, "h1", [], [{xmlcdata, Heading}]},
- {xmlelement, "p", [],
- [{xmlcdata, "An implementation of "},
- {xmlelement, "a",
- [{"href", "http://xmpp.org/extensions/xep-0206.html"}],
- [{xmlcdata, "XMPP over BOSH (XEP-0206)"}]}]},
- {xmlelement, "p", [],
- [{xmlcdata, "This web page is only informative. "
- "To use HTTP-Bind you need a Jabber/XMPP client that supports it."}
- ]}
- ]}]}.
+ Heading = <<"ejabberd ", (jlib:atom_to_binary(?MODULE))/binary>>,
+ #xmlel{name = <<"html">>,
+ attrs =
+ [{<<"xmlns">>, <<"http://www.w3.org/1999/xhtml">>}],
+ children =
+ [#xmlel{name = <<"head">>, attrs = [],
+ children =
+ [#xmlel{name = <<"title">>, attrs = [],
+ children = [{xmlcdata, Heading}]}]},
+ #xmlel{name = <<"body">>, attrs = [],
+ children =
+ [#xmlel{name = <<"h1">>, attrs = [],
+ children = [{xmlcdata, Heading}]},
+ #xmlel{name = <<"p">>, attrs = [],
+ children =
+ [{xmlcdata, <<"An implementation of ">>},
+ #xmlel{name = <<"a">>,
+ attrs =
+ [{<<"href">>,
+ <<"http://xmpp.org/extensions/xep-0206.html">>}],
+ children =
+ [{xmlcdata,
+ <<"XMPP over BOSH (XEP-0206)">>}]}]},
+ #xmlel{name = <<"p">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<"This web page is only informative. To "
+ "use HTTP-Bind you need a Jabber/XMPP "
+ "client that supports it.">>}]}]}]}.
open_session(SID, Pid) ->
mnesia:dirty_write(#bosh{sid = SID, pid = Pid}).
-close_session(SID) ->
- mnesia:dirty_delete(bosh, SID).
+close_session(SID) -> mnesia:dirty_delete(bosh, SID).
find_session(SID) ->
Node = ejabberd_cluster:get_node(SID),
- case rpc:call(Node, mnesia, dirty_read, [bosh, SID], 5000) of
- [#bosh{pid = Pid}] ->
- {ok, Pid};
- _ ->
- error
+ case rpc:call(Node, mnesia, dirty_read, [bosh, SID],
+ 5000)
+ of
+ [#bosh{pid = Pid}] -> {ok, Pid};
+ _ -> error
end.
migrate(_Node, _UpOrDown, After) ->
- Rs = mnesia:dirty_select(
- bosh,
- [{#bosh{sid = '$1', pid = '$2', _ = '_'},
- [],
- ['$$']}]),
- lists:foreach(
- fun([SID, Pid]) ->
- case ejabberd_cluster:get_node(SID) of
- Node when Node /= node() ->
- ejabberd_bosh:migrate(Pid, Node, random:uniform(After));
- _ ->
- ok
- end
- end, Rs).
+ Rs = mnesia:dirty_select(bosh,
+ [{#bosh{sid = '$1', pid = '$2', _ = '_'}, [],
+ ['$$']}]),
+ lists:foreach(fun ([SID, Pid]) ->
+ case ejabberd_cluster:get_node(SID) of
+ Node when Node /= node() ->
+ ejabberd_bosh:migrate(Pid, Node,
+ random:uniform(After));
+ _ -> ok
+ end
+ end,
+ Rs).
node_up(_Node) ->
copy_entries(mnesia:dirty_first(bosh)).
node_down(Node) when Node == node() ->
copy_entries(mnesia:dirty_first(bosh));
-node_down(_) ->
- ok.
+node_down(_) -> ok.
-copy_entries('$end_of_table') ->
- ok;
+copy_entries('$end_of_table') -> ok;
copy_entries(Key) ->
case mnesia:dirty_read(bosh, Key) of
- [#bosh{sid = SID} = Entry] ->
- case ejabberd_cluster:get_node_new(SID) of
- Node when node() /= Node ->
- rpc:cast(Node, mnesia, dirty_write, [Entry]);
- _ ->
- ok
- end;
- _ ->
- ok
+ [#bosh{sid = SID} = Entry] ->
+ case ejabberd_cluster:get_node_new(SID) of
+ Node when node() /= Node ->
+ rpc:cast(Node, mnesia, dirty_write, [Entry]);
+ _ -> ok
+ end;
+ _ -> ok
end,
copy_entries(mnesia:dirty_next(bosh, Key)).
-%%%----------------------------------------------------------------------
-%%% BEHAVIOUR CALLBACKS
-%%%----------------------------------------------------------------------
-start(Host, _Opts) ->
+start(Host, Opts) ->
start_hook_handler(),
setup_database(),
+ start_jiffy(Opts),
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
- ChildSpec =
- {Proc,
- {ejabberd_tmp_sup, start_link,
- [Proc, ejabberd_bosh]},
- permanent,
- infinity,
- supervisor,
- [ejabberd_tmp_sup]},
+ ChildSpec = {Proc,
+ {ejabberd_tmp_sup, start_link, [Proc, ejabberd_bosh]},
+ permanent, infinity, supervisor, [ejabberd_tmp_sup]},
supervisor:start_child(ejabberd_sup, ChildSpec).
stop(Host) ->
@@ -173,40 +168,63 @@ stop(Host) ->
setup_database() ->
mnesia:create_table(bosh,
- [{ram_copies, [node()]},
- {local_content, true},
- {attributes, record_info(fields, bosh)}]),
+ [{ram_copies, [node()]}, {local_content, true},
+ {attributes, record_info(fields, bosh)}]),
mnesia:add_table_copy(bosh, node(), ram_copies).
-start_hook_handler() ->
- %% HACK: We need this in order to avoid
- %% hooks processing more than once
- %% when many vhosts are involved
- %% TODO: get rid of this stuff
- spawn(fun hook_handler/0).
+start_jiffy(Opts) ->
+ case gen_mod:get_opt(json, Opts,
+ fun(false) -> false;
+ (true) -> true
+ end, false) of
+ false ->
+ ok;
+ true ->
+ case application:start(jiffy) of
+ ok ->
+ ok;
+ {error, {already_started, _}} ->
+ ok;
+ Err ->
+ ?WARNING_MSG("Failed to start JSON codec (jiffy): ~p. "
+ "JSON support will be disabled", [Err])
+ end
+ end.
+
+start_hook_handler() -> spawn(fun hook_handler/0).
hook_handler() ->
- case catch register(ejabberd_bosh_hook_handler, self()) of
- true ->
- ejabberd_hooks:add(node_up, ?MODULE, node_up, 100),
- ejabberd_hooks:add(node_down, ?MODULE, node_down, 100),
- ejabberd_hooks:add(node_hash_update, ?MODULE, migrate, 100),
- %% Stop if ejabberd_sup goes down
- %% (i.e. the whole ejabberd goes down)
- MRef = erlang:monitor(process, ejabberd_sup),
- hook_handler_loop(MRef);
- _ ->
- ok
+ case catch register(ejabberd_bosh_hook_handler, self())
+ of
+ true ->
+ ejabberd_hooks:add(node_up, ?MODULE, node_up, 100),
+ ejabberd_hooks:add(node_down, ?MODULE, node_down, 100),
+ ejabberd_hooks:add(node_hash_update, ?MODULE, migrate,
+ 100),
+ MRef = erlang:monitor(process, ejabberd_sup),
+ hook_handler_loop(MRef);
+ _ -> ok
end.
hook_handler_loop(MRef) ->
receive
- {'DOWN', MRef, _Type, _Object, _Info} ->
- %% Unregister the hooks. I think this is useless, thus 'catch'
- catch ejabberd_hooks:delete(node_up, ?MODULE, node_up, 100),
- catch ejabberd_hooks:delete(node_down, ?MODULE, node_down, 100),
- catch ejabberd_hooks:delete(node_hash_update, ?MODULE, migrate, 100),
- ok;
- _ ->
- hook_handler_loop(MRef)
+ {'DOWN', MRef, _Type, _Object, _Info} ->
+ catch ejabberd_hooks:delete(node_up, ?MODULE, node_up,
+ 100),
+ catch ejabberd_hooks:delete(node_down, ?MODULE,
+ node_down, 100),
+ catch ejabberd_hooks:delete(node_hash_update, ?MODULE,
+ migrate, 100),
+ ok;
+ _ -> hook_handler_loop(MRef)
+ end.
+
+get_type(Hdrs) ->
+ try
+ {_, S} = lists:keyfind('Content-Type', 1, Hdrs),
+ [T|_] = str:tokens(S, <<";">>),
+ [_, <<"json">>] = str:tokens(T, <<"/">>),
+ json
+ catch _:_ ->
+ xml
end.
diff --git a/src/web/mod_http_bind.erl b/src/web/mod_http_bind.erl
index 341e9fa7d..c7de9c329 100644
--- a/src/web/mod_http_bind.erl
+++ b/src/web/mod_http_bind.erl
@@ -31,88 +31,90 @@
%%%----------------------------------------------------------------------
-module(mod_http_bind).
+
-author('steve@zeank.in-berlin.de').
%%-define(ejabberd_debug, true).
-behaviour(gen_mod).
--export([
- start/2,
- stop/1,
- process/2
- ]).
+-export([start/2, stop/1, process/2]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
+
-include("ejabberd_http.hrl").
+
-include("http_bind.hrl").
-define(PROCNAME_MHB, ejabberd_mod_http_bind).
-%% Duplicated from ejabberd_http_bind.
-%% TODO: move to hrl file.
--record(http_bind, {id, pid, to, hold, wait, process_delay, version}).
+-record(http_bind,
+ {id, pid, to, hold, wait, process_delay, version}).
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
-process([], #request{method = 'POST',
- data = []}) ->
+process([], #request{method = 'POST', data = <<>>}) ->
?DEBUG("Bad Request: no data", []),
- {400, ?HEADER, {xmlelement, "h1", [],
- [{xmlcdata, "400 Bad Request"}]}};
-process([], #request{method = 'POST',
- data = Data,
- ip = IP}) ->
+ {400, ?HEADER,
+ #xmlel{name = <<"h1">>, children = [{xmlcdata, <<"400 Bad Request">>}]}};
+process([],
+ #request{method = 'POST', data = Data, ip = IP}) ->
?DEBUG("Incoming data: ~s", [Data]),
ejabberd_http_bind:process_request(Data, IP);
-process([], #request{method = 'GET',
- data = []}) ->
+process([], #request{method = 'GET', data = <<>>}) ->
{200, ?HEADER, get_human_html_xmlel()};
-process([], #request{method = 'OPTIONS',
- data = []}) ->
- {200, ?OPTIONS_HEADER, []};
+process([], #request{method = 'OPTIONS', data = <<>>}) ->
+ {200, ?OPTIONS_HEADER, <<>>};
process([], #request{method = 'HEAD'}) ->
- {200, ?HEADER, []};
+ {200, ?HEADER, <<>>};
process(_Path, _Request) ->
?DEBUG("Bad Request: ~p", [_Request]),
- {400, ?HEADER, {xmlelement, "h1", [],
- [{xmlcdata, "400 Bad Request"}]}}.
+ {400, ?HEADER,
+ #xmlel{name = <<"h1">>, children = [{xmlcdata, <<"400 Bad Request">>}]}}.
get_human_html_xmlel() ->
- Heading = "ejabberd " ++ atom_to_list(?MODULE),
- {xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"}],
- [{xmlelement, "head", [],
- [{xmlelement, "title", [], [{xmlcdata, Heading}]}]},
- {xmlelement, "body", [],
- [{xmlelement, "h1", [], [{xmlcdata, Heading}]},
- {xmlelement, "p", [],
- [{xmlcdata, "An implementation of "},
- {xmlelement, "a",
- [{"href", "http://xmpp.org/extensions/xep-0206.html"}],
- [{xmlcdata, "XMPP over BOSH (XEP-0206)"}]}]},
- {xmlelement, "p", [],
- [{xmlcdata, "This web page is only informative. "
- "To use HTTP-Bind you need a Jabber/XMPP client that supports it."}
- ]}
- ]}]}.
+ Heading = <<"ejabberd ",
+ (iolist_to_binary(atom_to_list(?MODULE)))/binary>>,
+ #xmlel{name = <<"html">>,
+ attrs =
+ [{<<"xmlns">>, <<"http://www.w3.org/1999/xhtml">>}],
+ children =
+ [#xmlel{name = <<"head">>,
+ children =
+ [#xmlel{name = <<"title">>,
+ children = [{xmlcdata, Heading}]}]},
+ #xmlel{name = <<"body">>,
+ children =
+ [#xmlel{name = <<"h1">>,
+ children = [{xmlcdata, Heading}]},
+ #xmlel{name = <<"p">>,
+ children =
+ [{xmlcdata, <<"An implementation of ">>},
+ #xmlel{name = <<"a">>,
+ attrs =
+ [{<<"href">>,
+ <<"http://xmpp.org/extensions/xep-0206.html">>}],
+ children =
+ [{xmlcdata,
+ <<"XMPP over BOSH (XEP-0206)">>}]}]},
+ #xmlel{name = <<"p">>,
+ children =
+ [{xmlcdata,
+ <<"This web page is only informative. To "
+ "use HTTP-Bind you need a Jabber/XMPP "
+ "client that supports it.">>}]}]}]}.
-%%%----------------------------------------------------------------------
-%%% BEHAVIOUR CALLBACKS
-%%%----------------------------------------------------------------------
start(Host, _Opts) ->
setup_database(),
Proc = gen_mod:get_module_proc(Host, ?PROCNAME_MHB),
- ChildSpec =
- {Proc,
- {ejabberd_tmp_sup, start_link,
- [Proc, ejabberd_http_bind]},
- permanent,
- infinity,
- supervisor,
- [ejabberd_tmp_sup]},
+ ChildSpec = {Proc,
+ {ejabberd_tmp_sup, start_link,
+ [Proc, ejabberd_http_bind]},
+ permanent, infinity, supervisor, [ejabberd_tmp_sup]},
supervisor:start_child(ejabberd_sup, ChildSpec).
stop(Host) ->
@@ -123,23 +125,17 @@ stop(Host) ->
setup_database() ->
migrate_database(),
mnesia:create_table(http_bind,
- [{ram_copies, [node()]},
- {local_content, true},
+ [{ram_copies, [node()]}, {local_content, true},
{attributes, record_info(fields, http_bind)}]),
mnesia:add_table_copy(http_bind, node(), ram_copies).
migrate_database() ->
case catch mnesia:table_info(http_bind, attributes) of
- [id, pid, to, hold, wait, process_delay, version] ->
- ok;
- _ ->
- %% Since the stored information is not important, instead
- %% of actually migrating data, let's just destroy the table
- mnesia:delete_table(http_bind)
+ [id, pid, to, hold, wait, process_delay, version] -> ok;
+ _ -> mnesia:delete_table(http_bind)
end,
- case catch mnesia:table_info(http_bind, local_content) of
- false ->
- mnesia:delete_table(http_bind);
- _ ->
- ok
+ case catch mnesia:table_info(http_bind, local_content)
+ of
+ false -> mnesia:delete_table(http_bind);
+ _ -> ok
end.
diff --git a/src/web/mod_http_bindjson.erl b/src/web/mod_http_bindjson.erl
index 6090ee458..30f841c99 100644
--- a/src/web/mod_http_bindjson.erl
+++ b/src/web/mod_http_bindjson.erl
@@ -28,129 +28,126 @@
%%% the real stuff, this is to handle the new pluggable architecture for
%%% extending ejabberd's http service.
%%%----------------------------------------------------------------------
-%%% I will probable kill and merge code with the original mod_http_bind
+%%% I will probable kill and merge code with the original mod_http_bind
%%% if this feature gains traction.
%%% Eric Cestari
-module(mod_http_bindjson).
+
-author('steve@zeank.in-berlin.de').
%%-define(ejabberd_debug, true).
-behaviour(gen_mod).
--export([
- start/2,
- stop/1,
- process/2
- ]).
+-export([start/2, stop/1, process/2]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
+
-include("ejabberd_http.hrl").
+
-include("http_bind.hrl").
-%% Duplicated from ejabberd_http_bind.
-%% TODO: move to hrl file.
--record(http_bind, {id, pid, to, hold, wait, process_delay, version}).
+-record(http_bind,
+ {id, pid, to, hold, wait, process_delay, version}).
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
-process([], #request{method = 'POST',
- data = []}) ->
+process([], #request{method = 'POST', data = <<>>}) ->
?DEBUG("Bad Request: no data", []),
- {400, ?HEADER, {xmlelement, "h1", [],
- [{xmlcdata, "400 Bad Request"}]}};
-process([], #request{method = 'POST',
- data = Data,
- ip = IP}) ->
+ {400, ?HEADER,
+ #xmlel{name = <<"h1">>, attrs = [],
+ children = [{xmlcdata, <<"400 Bad Request">>}]}};
+process([],
+ #request{method = 'POST', data = Data, ip = IP}) ->
?DEBUG("Incoming data: ~s", [Data]),
- %NOTE the whole point of this file is this line.
ejabberd_http_bindjson:process_request(Data, IP);
-process([], #request{method = 'GET',
- data = []}) ->
+process([], #request{method = 'GET', data = <<>>}) ->
{200, ?HEADER, get_human_html_xmlel()};
-process([], #request{method = 'OPTIONS',
- data = []}) ->
+process([], #request{method = 'OPTIONS', data = <<>>}) ->
{200, ?OPTIONS_HEADER, []};
process(_Path, _Request) ->
?DEBUG("Bad Request: ~p", [_Request]),
- {400, ?HEADER, {xmlelement, "h1", [],
- [{xmlcdata, "400 Bad Request"}]}}.
+ {400, ?HEADER,
+ #xmlel{name = <<"h1">>, attrs = [],
+ children = [{xmlcdata, <<"400 Bad Request">>}]}}.
get_human_html_xmlel() ->
- Heading = "ejabberd " ++ atom_to_list(?MODULE),
- {xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"}],
- [{xmlelement, "head", [],
- [{xmlelement, "title", [], [{xmlcdata, Heading}]}]},
- {xmlelement, "body", [],
- [{xmlelement, "h1", [], [{xmlcdata, Heading}]},
- {xmlelement, "p", [],
- [{xmlcdata, "An implementation of "},
- {xmlelement, "a",
- [{"href", "http://xmpp.org/extensions/xep-0206.html"}],
- [{xmlcdata, "XMPP over BOSH (XEP-0206)"}]}]},
- {xmlelement, "p", [],
- [{xmlcdata, "This web page is only informative. "
- "To use HTTP-Bind you need a Jabber/XMPP client that supports it."}
- ]}
- ]}]}.
+ Heading = <<"ejabberd ",
+ (iolist_to_binary(atom_to_list(?MODULE)))/binary>>,
+ #xmlel{name = <<"html">>,
+ attrs =
+ [{<<"xmlns">>, <<"http://www.w3.org/1999/xhtml">>}],
+ children =
+ [#xmlel{name = <<"head">>, attrs = [],
+ children =
+ [#xmlel{name = <<"title">>, attrs = [],
+ children = [{xmlcdata, Heading}]}]},
+ #xmlel{name = <<"body">>, attrs = [],
+ children =
+ [#xmlel{name = <<"h1">>, attrs = [],
+ children = [{xmlcdata, Heading}]},
+ #xmlel{name = <<"p">>, attrs = [],
+ children =
+ [{xmlcdata, <<"An implementation of ">>},
+ #xmlel{name = <<"a">>,
+ attrs =
+ [{<<"href">>,
+ <<"http://xmpp.org/extensions/xep-0206.html">>}],
+ children =
+ [{xmlcdata,
+ <<"XMPP over BOSH (XEP-0206)">>}]}]},
+ #xmlel{name = <<"p">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<"This web page is only informative. To "
+ "use HTTP-Bind you need a Jabber/XMPP "
+ "client that supports it.">>}]}]}]}.
-%%%----------------------------------------------------------------------
-%%% BEHAVIOUR CALLBACKS
-%%%----------------------------------------------------------------------
start(_Host, _Opts) ->
setup_database(),
- HTTPBindSupervisor =
- {ejabberd_http_bind_sup,
- {ejabberd_tmp_sup, start_link,
- [ejabberd_http_bind_sup, ejabberd_http_bind]},
- permanent,
- infinity,
- supervisor,
- [ejabberd_tmp_sup]},
- case supervisor:start_child(ejabberd_sup, HTTPBindSupervisor) of
- {ok, _Pid} ->
- ok;
- {ok, _Pid, _Info} ->
- ok;
- {error, {already_started, _PidOther}} ->
- % mod_http_bind is already started so it will not be started again
- ok;
- {error, Error} ->
- {'EXIT', {start_child_error, Error}}
+ HTTPBindSupervisor = {ejabberd_http_bind_sup,
+ {ejabberd_tmp_sup, start_link,
+ [ejabberd_http_bind_sup, ejabberd_http_bind]},
+ permanent, infinity, supervisor, [ejabberd_tmp_sup]},
+ case supervisor:start_child(ejabberd_sup,
+ HTTPBindSupervisor)
+ of
+ {ok, _Pid} -> ok;
+ {ok, _Pid, _Info} -> ok;
+ {error, {already_started, _PidOther}} ->
+ % mod_http_bind is already started so it will not be started again
+ ok;
+ {error, Error} -> {'EXIT', {start_child_error, Error}}
end.
stop(_Host) ->
- case supervisor:terminate_child(ejabberd_sup, ejabberd_http_bind_sup) of
- ok ->
- ok;
- {error, Error} ->
- {'EXIT', {terminate_child_error, Error}}
+ case supervisor:terminate_child(ejabberd_sup,
+ ejabberd_http_bind_sup)
+ of
+ ok -> ok;
+ {error, Error} ->
+ {'EXIT', {terminate_child_error, Error}}
end.
setup_database() ->
migrate_database(),
mnesia:create_table(http_bind,
- [{ram_copies, [node()]},
- {local_content, true},
+ [{ram_copies, [node()]}, {local_content, true},
{attributes, record_info(fields, http_bind)}]),
mnesia:add_table_copy(http_bind, node(), ram_copies).
migrate_database() ->
case catch mnesia:table_info(http_bind, attributes) of
- [id, pid, to, hold, wait, process_delay, version] ->
- ok;
- _ ->
- %% Since the stored information is not important, instead
- %% of actually migrating data, let's just destroy the table
- mnesia:delete_table(http_bind)
+ [id, pid, to, hold, wait, process_delay, version] -> ok;
+ _ -> mnesia:delete_table(http_bind)
end,
- case catch mnesia:table_info(http_bind, local_content) of
- false ->
- mnesia:delete_table(http_bind);
- _ ->
- ok
+ case catch mnesia:table_info(http_bind, local_content)
+ of
+ false -> mnesia:delete_table(http_bind);
+ _ -> ok
end.
diff --git a/src/web/mod_http_fileserver.erl b/src/web/mod_http_fileserver.erl
index b7c16f1b5..b47d8c34d 100644
--- a/src/web/mod_http_fileserver.erl
+++ b/src/web/mod_http_fileserver.erl
@@ -25,8 +25,11 @@
%%%----------------------------------------------------------------------
-module(mod_http_fileserver).
+
-author('mmirra@process-one.net').
+
-author('ecestari@process-one.net').
+
-behaviour(gen_mod).
%% gen_mod callbacks
@@ -36,113 +39,119 @@
-export([process/2]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
+
-include_lib("kernel/include/file.hrl").
-include("ejabberd_http.hrl").
--ifdef(SSL40).
--define(STRING2LOWER, string).
--else.
--ifdef(SSL39).
--define(STRING2LOWER, string).
--else.
--define(STRING2LOWER, httpd_util).
--endif.
--endif.
-
-%% Response is {DataSize, Code, [{HeaderKey, HeaderValue}], Data}
--define(HTTP_ERR_FILE_NOT_FOUND, {-1, 404, [], "Not found"}).
--define(HTTP_ERR_FORBIDDEN, {-1, 403, [], "Forbidden"}).
-
--define(DEFAULT_CONTENT_TYPE, "application/octet-stream").
--define(DEFAULT_CONTENT_TYPES, [{".css", "text/css"},
- {".gif", "image/gif"},
- {".html", "text/html"},
- {".jar", "application/java-archive"},
- {".jpeg", "image/jpeg"},
- {".jpg", "image/jpeg"},
- {".js", "text/javascript"},
- {".png", "image/png"},
- {".svg", "image/svg+xml"},
- {".txt", "text/plain"},
- {".xml", "application/xml"},
- {".xpi", "application/x-xpinstall"},
- {".xul", "application/vnd.mozilla.xul+xml"}]).
-
--compile(export_all).
+-define(HTTP_ERR_FILE_NOT_FOUND,
+ {-1, 404, [], <<"Not found">>}).
+
+-define(HTTP_ERR_FORBIDDEN,
+ {-1, 403, [], <<"Forbidden">>}).
+-define(DEFAULT_CONTENT_TYPE,
+ <<"application/octet-stream">>).
+
+-define(DEFAULT_CONTENT_TYPES,
+ [{<<".css">>, <<"text/css">>},
+ {<<".gif">>, <<"image/gif">>},
+ {<<".html">>, <<"text/html">>},
+ {<<".jar">>, <<"application/java-archive">>},
+ {<<".jpeg">>, <<"image/jpeg">>},
+ {<<".jpg">>, <<"image/jpeg">>},
+ {<<".js">>, <<"text/javascript">>},
+ {<<".png">>, <<"image/png">>},
+ {<<".svg">>, <<"image/svg+xml">>},
+ {<<".txt">>, <<"text/plain">>},
+ {<<".xml">>, <<"application/xml">>},
+ {<<".xpi">>, <<"application/x-xpinstall">>},
+ {<<".xul">>, <<"application/vnd.mozilla.xul+xml">>}]).
start(Host, Opts) ->
- DocRoot = gen_mod:get_opt(docroot, Opts, undefined),
+ DocRoot = gen_mod:get_opt(docroot, Opts,
+ fun iolist_to_binary/1,
+ undefined),
set_default_host(Host, Opts),
conf_store(Host, docroot, DocRoot),
check_docroot_defined(DocRoot, Host),
DRInfo = check_docroot_exists(DocRoot),
check_docroot_is_dir(DRInfo, DocRoot),
check_docroot_is_readable(DRInfo, DocRoot),
- AccessLog = gen_mod:get_opt(accesslog, Opts, undefined),
+ AccessLog = gen_mod:get_opt(accesslog, Opts,
+ fun iolist_to_binary/1,
+ undefined),
start_log(Host, AccessLog),
- DirectoryIndices = gen_mod:get_opt(directory_indices, Opts, []),
+ DirectoryIndices = gen_mod:get_opt(directory_indices, Opts,
+ fun(L) when is_list(L) -> L end,
+ []),
conf_store(Host, directory_indices, DirectoryIndices),
- ServeStaticGzip = gen_mod:get_opt(serve_gzip, Opts, false),
+ ServeStaticGzip = gen_mod:get_opt(serve_gzip, Opts,
+ fun(B) when is_boolean(B) -> B end,
+ false),
conf_store(Host, serve_gzip, ServeStaticGzip),
- CustomHeaders = gen_mod:get_opt(custom_headers, Opts, []),
+ CustomHeaders = gen_mod:get_opt(custom_headers, Opts,
+ fun(L) when is_list(L) -> L end,
+ []),
conf_store(Host, custom_headers, CustomHeaders),
- DefaultContentType = gen_mod:get_opt(default_content_type, Opts,
- ?DEFAULT_CONTENT_TYPE),
- conf_store(Host, default_content_type, DefaultContentType),
- ContentTypes = build_list_content_types(gen_mod:get_opt(content_types, Opts, []), ?DEFAULT_CONTENT_TYPES),
+ DefaultContentType =
+ gen_mod:get_opt(default_content_type, Opts,
+ fun iolist_to_binary/1,
+ ?DEFAULT_CONTENT_TYPE),
+ conf_store(Host, default_content_type,
+ DefaultContentType),
+ ContentTypes = build_list_content_types(
+ gen_mod:get_opt(content_types, Opts,
+ fun(L) when is_list(L) -> L end,
+ []),
+ ?DEFAULT_CONTENT_TYPES),
conf_store(Host, content_types, ContentTypes),
?INFO_MSG("initialize: ~n ~p", [ContentTypes]),
ok.
-% Defines host that will answer request if hostname is not recognized.
-% The first configured host will be used.
-set_default_host(Host, _Opts)->
+set_default_host(Host, _Opts) ->
case mochiglobal:get(http_default_host) of
- undefined ->
- ?DEBUG("Setting default host to ~p", [Host]),
- mochiglobal:put(http_default_host, Host);
- _ ->
- ok
+ undefined ->
+ ?DEBUG("Setting default host to ~p", [Host]),
+ mochiglobal:put(http_default_host, Host);
+ _ -> ok
end.
-% Returns current host if it exists or default host
-get_host(Host)->
+
+get_host(Host) ->
DCT = mochiglobal:get(default_content_type),
case lists:keymember(Host, 1, DCT) of
- true -> Host;
- false -> mochiglobal:get(http_default_host)
+ true -> Host;
+ false -> mochiglobal:get(http_default_host)
end.
-
-conf_store(Host, Key, Value)->
+
+conf_store(Host, Key, Value) ->
R = case mochiglobal:get(Key) of
- undefined -> [{Host, Value}];
- A ->
- case lists:keymember(Host, 1, A) of
- true -> lists:keyreplace(Host, 1, A,{Host, Value});
- false -> [{Host, Value}|A]
- end
- end,
+ undefined -> [{Host, Value}];
+ A ->
+ case lists:keymember(Host, 1, A) of
+ true -> lists:keyreplace(Host, 1, A, {Host, Value});
+ false -> [{Host, Value} | A]
+ end
+ end,
mochiglobal:put(Key, R).
-
+
conf_get(Host, Key) ->
case mochiglobal:get(Key) of
- undefined-> undefined;
- A ->
- case lists:keyfind(Host, 1, A) of
- {Host, Val} -> Val;
- false ->
- case mochiglobal:get(http_default_host) of
- Host -> % stop recursion here
- undefined;
- DefaultHost ->
- conf_get(DefaultHost, Key)
- end
- end
+ undefined -> undefined;
+ A ->
+ case lists:keyfind(Host, 1, A) of
+ {Host, Val} -> Val;
+ false ->
+ case mochiglobal:get(http_default_host) of
+ Host -> % stop recursion here
+ undefined;
+ DefaultHost -> conf_get(DefaultHost, Key)
+ end
+ end
end.
-
%% @spec (AdminCTs::[CT], Default::[CT]) -> [CT]
%% where CT = {Extension::string(), Value}
%% Value = string() | undefined
@@ -150,45 +159,47 @@ conf_get(Host, Key) ->
%% Elements of AdminCTs have more priority.
%% If a CT is declared as 'undefined', then it is not included in the result.
-start_log(_Host, undefined)->
- ok;
+start_log(_Host, undefined) -> ok;
start_log(Host, FileName) ->
mod_http_fileserver_log:start(Host, FileName).
-
-build_list_content_types(AdminCTsUnsorted, DefaultCTsUnsorted) ->
+
+build_list_content_types(AdminCTsUnsorted,
+ DefaultCTsUnsorted) ->
AdminCTs = lists:ukeysort(1, AdminCTsUnsorted),
DefaultCTs = lists:ukeysort(1, DefaultCTsUnsorted),
- CTsUnfiltered = lists:ukeymerge(1, AdminCTs, DefaultCTs),
- [{Extension, Value} || {Extension, Value} <- CTsUnfiltered, Value /= undefined].
+ CTsUnfiltered = lists:ukeymerge(1, AdminCTs,
+ DefaultCTs),
+ [{Extension, Value}
+ || {Extension, Value} <- CTsUnfiltered,
+ Value /= undefined].
check_docroot_defined(DocRoot, Host) ->
case DocRoot of
- undefined -> throw({undefined_docroot_option, Host});
- _ -> ok
+ undefined -> throw({undefined_docroot_option, Host});
+ _ -> ok
end.
check_docroot_exists(DocRoot) ->
case file:read_file_info(DocRoot) of
- {error, Reason} -> throw({error_access_docroot, DocRoot, Reason});
- {ok, FI} -> FI
+ {error, Reason} ->
+ throw({error_access_docroot, DocRoot, Reason});
+ {ok, FI} -> FI
end.
check_docroot_is_dir(DRInfo, DocRoot) ->
case DRInfo#file_info.type of
- directory -> ok;
- _ -> throw({docroot_not_directory, DocRoot})
+ directory -> ok;
+ _ -> throw({docroot_not_directory, DocRoot})
end.
check_docroot_is_readable(DRInfo, DocRoot) ->
case DRInfo#file_info.access of
- read -> ok;
- read_write -> ok;
- _ -> throw({docroot_not_readable, DocRoot})
+ read -> ok;
+ read_write -> ok;
+ _ -> throw({docroot_not_readable, DocRoot})
end.
-
-stop(_Host) ->
- ok.
+stop(_Host) -> ok.
%%====================================================================
%% request_handlers callbacks
@@ -205,141 +216,161 @@ process(LocalPath, Request) ->
ClientHeaders = Request#request.headers,
DirectoryIndices = conf_get(Host, directory_indices),
CustomHeaders = conf_get(Host, custom_headers),
- DefaultContentType = conf_get(Host, default_content_type),
+ DefaultContentType = conf_get(Host,
+ default_content_type),
ContentTypes = conf_get(Host, content_types),
Encoding = conf_get(Host, serve_gzip),
Static = select_encoding(ClientHeaders, Encoding),
DocRoot = conf_get(Host, docroot),
- FileName = filename:join(filename:split(DocRoot) ++ LocalPath),
- {FileSize, Code, Headers, Contents} = case file:read_file_info(FileName) of
- {error, enoent} -> ?HTTP_ERR_FILE_NOT_FOUND;
- {error, eacces} -> ?HTTP_ERR_FORBIDDEN;
- {ok, #file_info{type = directory}} -> serve_index(FileName,
- DirectoryIndices,
- CustomHeaders,
- DefaultContentType,
- ContentTypes, Static);
- {ok, FileInfo} ->
- case should_serve(FileInfo, ClientHeaders) of
- true ->serve_file(FileInfo, FileName,
- CustomHeaders,
- DefaultContentType,
- ContentTypes, Static);
- false ->
- {0, 304, [], []}
- end
- end,
- mod_http_fileserver_log:add_to_log(Host,FileSize, Code, Request),
+ FileName = filename:join(filename:split(DocRoot) ++
+ LocalPath),
+ {FileSize, Code, Headers, Contents} = case
+ file:read_file_info(FileName)
+ of
+ {error, enoent} ->
+ ?HTTP_ERR_FILE_NOT_FOUND;
+ {error, eacces} ->
+ ?HTTP_ERR_FORBIDDEN;
+ {ok,
+ #file_info{type = directory}} ->
+ serve_index(FileName,
+ DirectoryIndices,
+ CustomHeaders,
+ DefaultContentType,
+ ContentTypes,
+ Static);
+ {ok, FileInfo} ->
+ case should_serve(FileInfo,
+ ClientHeaders)
+ of
+ true ->
+ serve_file(FileInfo,
+ FileName,
+ CustomHeaders,
+ DefaultContentType,
+ ContentTypes,
+ Static);
+ false -> {0, 304, [], []}
+ end
+ end,
+ mod_http_fileserver_log:add_to_log(Host, FileSize, Code,
+ Request),
{Code, Headers, Contents}.
should_serve(FileInfo, Headers) ->
- lists:foldl(fun({Header, Fun}, Acc)->
- case lists:keyfind(Header, 1, Headers) of
- {_, Val} ->
- Fun(FileInfo,Val);
- _O ->
- Acc
- end
- end, true, [{'If-None-Match',fun etag/2}
- ]).
-etag(FileInfo, Etag)->
+ lists:foldl(fun ({Header, Fun}, Acc) ->
+ case lists:keyfind(Header, 1, Headers) of
+ {_, Val} -> Fun(FileInfo, Val);
+ _O -> Acc
+ end
+ end,
+ true, [{'If-None-Match', fun etag/2}]).
+
+etag(FileInfo, Etag) ->
case httpd_util:create_etag(FileInfo) of
- Etag ->
- false;
- _ ->
- true
+ Etag -> false;
+ _ -> true
end.
-modified(FileInfo, LastModified)->
- AfterDate = calendar:datetime_to_gregorian_seconds(
- httpd_util:convert_request_date(LastModified)),
- Mtime = calendar:datetime_to_gregorian_seconds(FileInfo#file_info.mtime),
- ?DEBUG("Modified : ~p > ~p (serving: ~p)", [Mtime, AfterDate,Mtime > AfterDate]),
- Mtime > AfterDate.
-
-select_encoding(_Headers, false)->
- false;
-select_encoding(Headers, Configuration)->
- Value = find_header('Accept-Encoding', Headers, ""),
- Schemes = string:tokens(Value, ","),
- case lists:member("gzip",Schemes) of
- true -> Configuration;
- false -> false
+
+select_encoding(_Headers, false) -> false;
+select_encoding(Headers, Configuration) ->
+ Value = find_header('Accept-Encoding', Headers, <<"">>),
+ Schemes = str:tokens(Value, <<",">>),
+ case lists:member(<<"gzip">>, Schemes) of
+ true -> Configuration;
+ false -> false
end.
-%% Troll through the directory indices attempting to find one which
-%% works, if none can be found, return a 404.
-serve_index(_FileName, [], _CH, _DefaultContentType, _ContentTypes, _Static) ->
+serve_index(_FileName, [], _CH, _DefaultContentType,
+ _ContentTypes, _Static) ->
?HTTP_ERR_FILE_NOT_FOUND;
-serve_index(FileName, [Index | T], CH, DefaultContentType, ContentTypes, Static) ->
+serve_index(FileName, [Index | T], CH,
+ DefaultContentType, ContentTypes, Static) ->
IndexFileName = filename:join([FileName] ++ [Index]),
case file:read_file_info(IndexFileName) of
- {error, _Error} -> serve_index(FileName, T, CH, DefaultContentType, ContentTypes, Static);
- {ok, #file_info{type = directory}} -> serve_index(FileName, T, CH, DefaultContentType, ContentTypes, Static);
- {ok, FileInfo} -> serve_file(FileInfo, IndexFileName, CH, DefaultContentType, ContentTypes, Static)
+ {error, _Error} ->
+ serve_index(FileName, T, CH, DefaultContentType,
+ ContentTypes, Static);
+ {ok, #file_info{type = directory}} ->
+ serve_index(FileName, T, CH, DefaultContentType,
+ ContentTypes, Static);
+ {ok, FileInfo} ->
+ serve_file(FileInfo, IndexFileName, CH,
+ DefaultContentType, ContentTypes, Static)
end.
%% Assume the file exists if we got this far and attempt to read it in
%% and serve it up.
-
-serve_file(FileInfo, FileName, CustomHeaders, DefaultContentType, ContentTypes, false) ->
+
+serve_file(FileInfo, FileName, CustomHeaders,
+ DefaultContentType, ContentTypes, false) ->
?DEBUG("Delivering: ~s", [FileName]),
- ContentType = content_type(FileName, DefaultContentType, ContentTypes),
+ ContentType = content_type(FileName, DefaultContentType,
+ ContentTypes),
{ok, FileContents} = file:read_file(FileName),
- {FileInfo#file_info.size,
- 200, [{"Server", "ejabberd"},
- {"Last-Modified", last_modified(FileInfo)},
- {"Content-Type", ContentType} | CustomHeaders],
+ {FileInfo#file_info.size, 200,
+ [{<<"Server">>, <<"ejabberd">>},
+ {<<"Last-Modified">>, last_modified(FileInfo)},
+ {<<"Content-Type">>, ContentType}
+ | CustomHeaders],
FileContents};
-
-serve_file(FileInfo, FileName, CustomHeaders, DefaultContentType, ContentTypes, Gzip) ->
+serve_file(FileInfo, FileName, CustomHeaders,
+ DefaultContentType, ContentTypes, Gzip) ->
?DEBUG("Delivering: ~s", [FileName]),
- ContentType = content_type(FileName, DefaultContentType, ContentTypes),
- CompressedFileName = FileName ++ ".gz",
+ ContentType = content_type(FileName, DefaultContentType,
+ ContentTypes),
+ CompressedFileName = <<FileName/binary, ".gz">>,
case file:read_file_info(CompressedFileName) of
- {ok, FileInfoCompressed} -> %Found compressed
- ?INFO_MSG("Found compressed: ~s", [FileName]),
- {ok, FileContents} = file:read_file(CompressedFileName),
- {FileInfoCompressed#file_info.size,
- 200, [{"Server", "ejabberd"},
- {"Last-Modified", last_modified(FileInfoCompressed)},
- {"Content-Type", ContentType},
- {"Etag", httpd_util:create_etag(FileInfoCompressed)},
- {"Content-Encoding", "gzip"} | CustomHeaders],
- FileContents};
- {error, _} ->
- {FileContents, Size} = case Gzip of
- static ->
- {ok, Content} = file:read_file(FileName),
- {Content, FileInfo#file_info.size};
- always ->
- {ok, Content} = file:read_file(FileName),
- Compressed = zlib:gzip(Content),
- {Compressed, size(Compressed)}
- end,
- {Size,
- 200, [{"Server", "ejabberd"},
- {"Last-Modified", last_modified(FileInfo)},
- {"Etag", httpd_util:create_etag(FileInfo)},
- {"Content-Type", ContentType},
- {"Content-Encoding", "gzip"} | CustomHeaders],
- FileContents}
+ {ok, FileInfoCompressed} ->
+ ?INFO_MSG("Found compressed: ~s", [FileName]),
+ {ok, FileContents} = file:read_file(CompressedFileName),
+ {FileInfoCompressed#file_info.size, 200,
+ [{<<"Server">>, <<"ejabberd">>},
+ {<<"Last-Modified">>,
+ last_modified(FileInfoCompressed)},
+ {<<"Content-Type">>, ContentType},
+ {<<"Etag">>,
+ httpd_util:create_etag(FileInfoCompressed)},
+ {<<"Content-Encoding">>, <<"gzip">>}
+ | CustomHeaders],
+ FileContents};
+ {error, _} ->
+ {FileContents, Size} = case Gzip of
+ static ->
+ {ok, Content} = file:read_file(FileName),
+ {Content, FileInfo#file_info.size};
+ always ->
+ {ok, Content} = file:read_file(FileName),
+ Compressed = zlib:gzip(Content),
+ {Compressed, byte_size(Compressed)}
+ end,
+ {Size, 200,
+ [{<<"Server">>, <<"ejabberd">>},
+ {<<"Last-Modified">>, last_modified(FileInfo)},
+ {<<"Etag">>, httpd_util:create_etag(FileInfo)},
+ {<<"Content-Type">>, ContentType},
+ {<<"Content-Encoding">>, <<"gzip">>}
+ | CustomHeaders],
+ FileContents}
end.
find_header(Header, Headers, Default) ->
case lists:keysearch(Header, 1, Headers) of
- {value, {_, Value}} -> Value;
- false -> Default
+ {value, {_, Value}} -> Value;
+ false -> Default
end.
%%----------------------------------------------------------------------
%% Utilities
%%----------------------------------------------------------------------
-content_type(Filename, DefaultContentType, ContentTypes) ->
- Extension = ?STRING2LOWER:to_lower(filename:extension(Filename)),
+content_type(Filename, DefaultContentType,
+ ContentTypes) ->
+ Extension =
+ str:to_lower(filename:extension(Filename)),
case lists:keysearch(Extension, 1, ContentTypes) of
- {value, {_, ContentType}} -> ContentType;
- false -> DefaultContentType
+ {value, {_, ContentType}} -> ContentType;
+ false -> DefaultContentType
end.
last_modified(FileInfo) ->
diff --git a/src/web/mod_http_fileserver_log.erl b/src/web/mod_http_fileserver_log.erl
index b76f947c8..8f634f487 100644
--- a/src/web/mod_http_fileserver_log.erl
+++ b/src/web/mod_http_fileserver_log.erl
@@ -1,30 +1,35 @@
--module (mod_http_fileserver_log).
+-module(mod_http_fileserver_log).
--behaviour (gen_server).
+-behaviour(gen_server).
--export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
+-export([init/1, handle_call/3, handle_cast/2,
+ handle_info/2, terminate/2, code_change/3]).
--export ([start_link/2,start/2, stop/1, add_to_log/4,reopen_log/1]).
+-export([start_link/2, start/2, stop/1, add_to_log/4,
+ reopen_log/1]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
+
-include("ejabberd_http.hrl").
+
-include_lib("kernel/include/file.hrl").
-define(PROCNAME, ejabberd_mod_http_fileserver_log).
--record(state, {host,accesslog, accesslogfd}).
+-record(state, {host = <<"">>,
+ accesslog :: binary(),
+ accesslogfd :: file:io_device()}).
+
%% Public API
start(Host, Filename) ->
- Proc =gen_mod:get_module_proc(Host, ?PROCNAME),
- ChildSpec =
- {Proc,
- {?MODULE, start_link, [Host, Filename]},
- transient, % if process crashes abruptly, it gets restarted
- 1000,
- worker,
- [?MODULE]},
+ Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+ ChildSpec = {Proc,
+ {?MODULE, start_link, [Host, Filename]},
+ transient, % if process crashes abruptly, it gets restarted
+ 1000, worker, [?MODULE]},
supervisor:start_child(ejabberd_sup, ChildSpec).
stop(Host) ->
@@ -35,133 +40,139 @@ stop(Host) ->
start_link(Host, Filename) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
- gen_server:start_link({local, Proc}, ?MODULE, [Host, Filename], []).
+ gen_server:start_link({local, Proc}, ?MODULE,
+ [Host, Filename], []).
-add_to_log(Host,FileSize, Code, Request) ->
- gen_server:cast(gen_mod:get_module_proc(Host, ?PROCNAME),
+-spec add_to_log(binary(), non_neg_integer(), non_neg_integer(),
+ http_request()) -> ok.
+
+add_to_log(Host, FileSize, Code, Request) ->
+ gen_server:cast(gen_mod:get_module_proc(Host,
+ ?PROCNAME),
{add_to_log, FileSize, Code, Request}).
-
+
+-spec reopen_log(binary()) -> ok.
+
reopen_log(Host) ->
- gen_server:cast(gen_mod:get_module_proc(Host, ?PROCNAME), reopen_log).
+ gen_server:cast(gen_mod:get_module_proc(Host,
+ ?PROCNAME),
+ reopen_log).
%% Server implementation, a.k.a.: callbacks
init([Host, Filename]) ->
try try_open_log(Filename, Host) of
- AccessLogFD ->
- ?DEBUG("File opened !", []),
- {ok, #state{host = Host,
- accesslog = Filename,
- accesslogfd = AccessLogFD}}
+ AccessLogFD ->
+ ?DEBUG("File opened !", []),
+ {ok,
+ #state{host = Host, accesslog = Filename,
+ accesslogfd = AccessLogFD}}
catch
- throw:Reason ->
- {stop, Reason}
+ Reason -> {stop, Reason}
end.
try_open_log(FN, Host) ->
FD = try open_log(FN) of
- FD1 -> FD1
+ FD1 -> FD1
catch
- throw:{cannot_open_accesslog, FN, Reason} ->
- ?ERROR_MSG("Cannot open access log file: ~p~nReason: ~p", [FN, Reason]),
- undefined
+ {cannot_open_accesslog, FN, Reason} ->
+ ?ERROR_MSG("Cannot open access log file: ~p~nReason: ~p",
+ [FN, Reason]),
+ undefined
end,
- %HostB = list_to_binary(Host),
- ejabberd_hooks:add(reopen_log_hook, Host, ?MODULE, reopen_log, 50),
+ ejabberd_hooks:add(reopen_log_hook, Host, ?MODULE,
+ reopen_log, 50),
FD.
-
+
handle_call(_Request, _From, State) ->
- {reply, ok, State}.
+ {reply, ok, State}.
-handle_cast({add_to_log, FileSize, Code, Request}, State) ->
- add_to_log2(State#state.accesslogfd, FileSize, Code, Request),
+handle_cast({add_to_log, FileSize, Code, Request},
+ State) ->
+ add_to_log2(State#state.accesslogfd, FileSize, Code,
+ Request),
{noreply, State};
handle_cast(reopen_log, State) ->
- FD2 = reopen_log(State#state.accesslog, State#state.accesslogfd),
+ FD2 = reopen_log(State#state.accesslog,
+ State#state.accesslogfd),
{noreply, State#state{accesslogfd = FD2}};
-handle_cast(_Msg, State) ->
- {noreply, State}.
+handle_cast(_Msg, State) -> {noreply, State}.
-handle_info(_Info, State) ->
- {noreply, State}.
+handle_info(_Info, State) -> {noreply, State}.
terminate(_Reason, State) ->
close_log(State#state.accesslogfd),
- ejabberd_hooks:delete(reopen_log_hook, State#state.host, ?MODULE, reopen_log, 50),
+ ejabberd_hooks:delete(reopen_log_hook, State#state.host,
+ ?MODULE, reopen_log, 50),
ok.
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
-
-
+code_change(_OldVsn, State, _Extra) -> {ok, State}.
+
%%----------------------------------------------------------------------
%% Log file
%%----------------------------------------------------------------------
+-spec open_log(binary()) -> file:io_device().
+
open_log(FN) ->
case file:open(FN, [append]) of
- {ok, FD} ->
- FD;
- {error, Reason} ->
- throw({cannot_open_accesslog, FN, Reason})
+ {ok, FD} -> FD;
+ {error, Reason} ->
+ throw({cannot_open_accesslog, FN, Reason})
end.
-close_log(FD) ->
- file:close(FD).
+close_log(FD) -> file:close(FD).
-reopen_log(undefined, undefined) ->
- ok;
+reopen_log(undefined, undefined) -> ok;
reopen_log(FN, FD) ->
?DEBUG("reopening logs", []),
close_log(FD),
open_log(FN).
-
+-spec add_to_log2(file:io_device(), non_neg_integer(), non_neg_integer(),
+ http_request()) -> ok.
add_to_log2(undefined, _FileSize, _Code, _Request) ->
ok;
add_to_log2(File, FileSize, Code, Request) ->
- {{Year, Month, Day}, {Hour, Minute, Second}} = calendar:local_time(),
- IP = ip_to_string(element(1, Request#request.ip)),
- Path = join(Request#request.path, "/"),
- Query = case join(lists:map(fun(E) -> lists:concat([element(1, E), "=", element(2, E)]) end,
- Request#request.q), "&") of
- [] ->
- "";
- String ->
- [$? | String]
+ {{Year, Month, Day}, {Hour, Minute, Second}} =
+ calendar:local_time(),
+ IP = jlib:ip_to_list(Request#request.ip),
+ Path = str:join(Request#request.path, <<"/">>),
+ Query = case str:join(
+ lists:flatmap(fun ({nokey, []}) ->
+ [];
+ ({K, V}) ->
+ [<<K/binary, $=, V/binary>>]
+ end, Request#request.q),
+ <<"&">>) of
+ <<"">> -> <<"">>;
+ String -> <<$?, String/binary>>
end,
- UserAgent = find_header('User-Agent', Request#request.headers, "-"),
- Referer = find_header('Referer', Request#request.headers, "-"),
- %% Pseudo Combined Apache log format:
- %% 127.0.0.1 - - [28/Mar/2007:18:41:55 +0200] "GET / HTTP/1.1" 302 303 "-" "tsung"
- %% TODO some fields are harcoded/missing:
- %% The date/time integers should have always 2 digits. For example day "7" should be "07"
- %% Month should be 3*letter, not integer 1..12
- %% Missing time zone = (`+' | `-') 4*digit
- %% Missing protocol version: HTTP/1.1
- %% For reference: http://httpd.apache.org/docs/2.2/logs.html
- io:format(File, "~s - - [~p/~p/~p:~p:~p:~p] \"~s /~s~s\" ~p ~p ~p ~p~n",
- [IP, Day, Month, Year, Hour, Minute, Second, Request#request.method, Path, Query, Code,
- FileSize, Referer, UserAgent]).
-
+ UserAgent = find_header('User-Agent',
+ Request#request.headers, <<"-">>),
+ Referer = find_header('Referer',
+ Request#request.headers, <<"-">>),
+ io:format(File,
+ <<"~s - - [~p/~p/~p:~p:~p:~p] \"~s /~s~s\" "
+ "~p ~p \"~s\" \"~s\"~n">>,
+ [IP, Day, Month, Year, Hour, Minute, Second,
+ Request#request.method, Path, Query, Code, FileSize,
+ escape_quote(Referer), escape_quote(UserAgent)]).
+
find_header(Header, Headers, Default) ->
case lists:keysearch(Header, 1, Headers) of
- {value, {_, Value}} -> Value;
- false -> Default
+ {value, {_, Value}} -> Value;
+ false -> Default
end.
-
-join([], _) ->
- "";
-join([E], _) ->
- E;
-join([H | T], Separator) ->
- lists:foldl(fun(E, Acc) -> lists:concat([Acc, Separator, E]) end, H, T).
-
-%% Convert IP address tuple to string representation. Accepts either
-%% IPv4 or IPv6 address tuples.
-ip_to_string(Address) when size(Address) == 4 ->
- join(tuple_to_list(Address), ".");
-ip_to_string(Address) when size(Address) == 8 ->
- Parts = lists:map(fun (Int) -> io_lib:format("~.16B", [Int]) end, tuple_to_list(Address)),
- string:to_lower(lists:flatten(join(Parts, ":"))). \ No newline at end of file
+
+
+escape_quote(B) ->
+ escape_quote(B, <<>>).
+
+escape_quote(<<$", Rest/binary>>, Acc) ->
+ escape_quote(Rest, <<Acc/binary, $\\, $">>);
+escape_quote(<<C, Rest/binary>>, Acc) ->
+ escape_quote(Rest, <<Acc/binary, C>>);
+escape_quote(<<>>, Acc) ->
+ Acc.
diff --git a/src/web/mod_register_web.erl b/src/web/mod_register_web.erl
index b5c1f3801..b020052d6 100644
--- a/src/web/mod_register_web.erl
+++ b/src/web/mod_register_web.erl
@@ -51,18 +51,19 @@
%%% * Optionally register request is forwarded to admin, no account created.
-module(mod_register_web).
+
-author('badlop@process-one.net').
-behaviour(gen_mod).
--export([start/2,
- stop/1,
- process/2
- ]).
+-export([start/2, stop/1, process/2]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
+
-include("ejabberd_http.hrl").
+
-include("ejabberd_web_admin.hrl").
%%%----------------------------------------------------------------------
@@ -73,8 +74,7 @@ start(_Host, _Opts) ->
%% case gen_mod:get_opt(docroot, Opts, undefined) of
ok.
-stop(_Host) ->
- ok.
+stop(_Host) -> ok.
%%%----------------------------------------------------------------------
%%% HTTP handlers
@@ -82,55 +82,64 @@ stop(_Host) ->
process([], #request{method = 'GET', lang = Lang}) ->
index_page(Lang);
-
-process(["register.css"], #request{method = 'GET'}) ->
+process([<<"register.css">>],
+ #request{method = 'GET'}) ->
serve_css();
-
-process(["new"], #request{method = 'GET', lang = Lang, host = Host, ip = IP}) ->
- {Addr, _Port} = IP,
- form_new_get(Host, Lang, Addr);
-
-process(["delete"], #request{method = 'GET', lang = Lang, host = Host}) ->
+process([<<"new">>],
+ #request{method = 'GET', lang = Lang, host = Host,
+ ip = IP}) ->
+ {Addr, _Port} = IP, form_new_get(Host, Lang, Addr);
+process([<<"delete">>],
+ #request{method = 'GET', lang = Lang, host = Host}) ->
form_del_get(Host, Lang);
-
-process(["change_password"], #request{method = 'GET', lang = Lang, host = Host}) ->
+process([<<"change_password">>],
+ #request{method = 'GET', lang = Lang, host = Host}) ->
form_changepass_get(Host, Lang);
-
-process(["new"], #request{method = 'POST', q = Q, ip = {Ip,_Port}, lang = Lang, host = Host}) ->
+process([<<"new">>],
+ #request{method = 'POST', q = Q, ip = {Ip, _Port},
+ lang = Lang, host = Host}) ->
case form_new_post(Q, Host) of
- {success, ok, {Username, Host, _Password}} ->
- Jid = jlib:make_jid(Username, Host, ""),
- send_registration_notifications(Jid, Ip),
- Text = ?T("Your Jabber account was successfully created."),
- {200, [], Text};
- Error ->
- ErrorText = ?T("There was an error creating the account: ") ++
- ?T(get_error_text(Error)),
- {404, [], ErrorText}
+ {success, ok, {Username, Host, _Password}} ->
+ Jid = jlib:make_jid(Username, Host, <<"">>),
+ mod_register:send_registration_notifications(?MODULE, Jid, Ip),
+ Text = (?T(<<"Your Jabber account was successfully "
+ "created.">>)),
+ {200, [], Text};
+ Error ->
+ ErrorText =
+ list_to_binary([?T(<<"There was an error creating the account: ">>),
+ ?T(get_error_text(Error))]),
+ {404, [], ErrorText}
end;
-
-process(["delete"], #request{method = 'POST', q = Q, lang = Lang, host = Host}) ->
+process([<<"delete">>],
+ #request{method = 'POST', q = Q, lang = Lang,
+ host = Host}) ->
case form_del_post(Q, Host) of
- {atomic, ok} ->
- Text = ?T("Your Jabber account was successfully deleted."),
- {200, [], Text};
- Error ->
- ErrorText = ?T("There was an error deleting the account: ") ++
- ?T(get_error_text(Error)),
- {404, [], ErrorText}
+ {atomic, ok} ->
+ Text = (?T(<<"Your Jabber account was successfully "
+ "deleted.">>)),
+ {200, [], Text};
+ Error ->
+ ErrorText =
+ list_to_binary([?T(<<"There was an error deleting the account: ">>),
+ ?T(get_error_text(Error))]),
+ {404, [], ErrorText}
end;
-
%% TODO: Currently only the first vhost is usable. The web request record
%% should include the host where the POST was sent.
-process(["change_password"], #request{method = 'POST', q = Q, lang = Lang, host = Host}) ->
+process([<<"change_password">>],
+ #request{method = 'POST', q = Q, lang = Lang,
+ host = Host}) ->
case form_changepass_post(Q, Host) of
- {atomic, ok} ->
- Text = ?T("The password of your Jabber account was successfully changed."),
- {200, [], Text};
- Error ->
- ErrorText = ?T("There was an error changing the password: ") ++
- ?T(get_error_text(Error)),
- {404, [], ErrorText}
+ {atomic, ok} ->
+ Text = (?T(<<"The password of your Jabber account "
+ "was successfully changed.">>)),
+ {200, [], Text};
+ Error ->
+ ErrorText =
+ list_to_binary([?T(<<"There was an error changing the password: ">>),
+ ?T(get_error_text(Error))]),
+ {404, [], ErrorText}
end.
%%%----------------------------------------------------------------------
@@ -138,48 +147,48 @@ process(["change_password"], #request{method = 'POST', q = Q, lang = Lang, host
%%%----------------------------------------------------------------------
serve_css() ->
- {200, [{"Content-Type", "text/css"},
- last_modified(), cache_control_public()], css()}.
+ {200,
+ [{<<"Content-Type">>, <<"text/css">>}, last_modified(),
+ cache_control_public()],
+ css()}.
last_modified() ->
- {"Last-Modified", "Mon, 25 Feb 2008 13:23:30 GMT"}.
+ {<<"Last-Modified">>,
+ <<"Mon, 25 Feb 2008 13:23:30 GMT">>}.
+
cache_control_public() ->
- {"Cache-Control", "public"}.
+ {<<"Cache-Control">>, <<"public">>}.
css() ->
- "html,body {
-background: white;
-margin: 0;
-padding: 0;
-height: 100%;
-}".
+ <<"html,body {\nbackground: white;\nmargin: "
+ "0;\npadding: 0;\nheight: 100%;\n}">>.
%%%----------------------------------------------------------------------
%%% Index page
%%%----------------------------------------------------------------------
index_page(Lang) ->
- HeadEls = [
- ?XCT("title", "Jabber Account Registration"),
- ?XA("link",
- [{"href", "/register/register.css"},
- {"type", "text/css"},
- {"rel", "stylesheet"}])
- ],
- Els=[
- ?XACT("h1",
- [{"class", "title"}, {"style", "text-align:center;"}],
- "Jabber Account Registration"),
- ?XE("ul", [
- ?XE("li", [?ACT("new", "Register a Jabber account")]),
- ?XE("li", [?ACT("change_password", "Change Password")]),
- ?XE("li", [?ACT("delete", "Unregister a Jabber account")])
- ]
- )
- ],
+ HeadEls = [?XCT(<<"title">>,
+ <<"Jabber Account Registration">>),
+ ?XA(<<"link">>,
+ [{<<"href">>, <<"/register/register.css">>},
+ {<<"type">>, <<"text/css">>},
+ {<<"rel">>, <<"stylesheet">>}])],
+ Els = [?XACT(<<"h1">>,
+ [{<<"class">>, <<"title">>},
+ {<<"style">>, <<"text-align:center;">>}],
+ <<"Jabber Account Registration">>),
+ ?XE(<<"ul">>,
+ [?XE(<<"li">>,
+ [?ACT(<<"new">>, <<"Register a Jabber account">>)]),
+ ?XE(<<"li">>,
+ [?ACT(<<"change_password">>, <<"Change Password">>)]),
+ ?XE(<<"li">>,
+ [?ACT(<<"delete">>,
+ <<"Unregister a Jabber account">>)])])],
{200,
- [{"Server", "ejabberd"},
- {"Content-Type", "text/html"}],
+ [{<<"Server">>, <<"ejabberd">>},
+ {<<"Content-Type">>, <<"text/html">>}],
ejabberd_web:make_xhtml(HeadEls, Els)}.
%%%----------------------------------------------------------------------
@@ -188,149 +197,112 @@ index_page(Lang) ->
form_new_get(Host, Lang, IP) ->
CaptchaEls = build_captcha_li_list(Lang, IP),
- HeadEls = [
- ?XCT("title", "Register a Jabber account"),
- ?XA("link",
- [{"href", "/register/register.css"},
- {"type", "text/css"},
- {"rel", "stylesheet"}])
- ],
- Els=[
- ?XACT("h1",
- [{"class", "title"}, {"style", "text-align:center;"}],
- "Register a Jabber account"),
- ?XCT("p",
- "This page allows to create a Jabber account in this Jabber server. "
- "Your JID (Jabber IDentifier) will be of the form: username@server. "
- "Please read carefully the instructions to fill correctly the fields."),
- %% <!-- JID's take the form of 'username@server.com'. For example, my JID is 'kirjava@jabber.org'.
- %% The maximum length for a JID is 255 characters. -->
- ?XAE("form", [{"action", ""}, {"method", "post"}],
- [
- ?XE("ol", [
- ?XE("li", [
- ?CT("Username:"),
- ?C(" "),
- ?INPUTS("text", "username", "", "20"),
- ?BR,
- ?XE("ul", [
- ?XCT("li", "This is case insensitive: macbeth is the same that MacBeth and Macbeth."),
- ?XC("li", ?T("Characters not allowed:") ++ " \" & ' / : < > @ ")
- ])
- ]),
- ?XE("li", [
- ?CT("Server:"),
- ?C(" "),
- ?C(Host)
- ]),
- ?XE("li", [
- ?CT("Password:"),
- ?C(" "),
- ?INPUTS("password", "password", "", "20"),
- ?BR,
- ?XE("ul", [
- ?XCT("li", "Don't tell your password to anybody, "
- "not even the administrators of the Jabber server."),
- ?XCT("li", "You can later change your password using a Jabber client."),
- ?XCT("li", "Some Jabber clients can store your password in your computer. "
- "Use that feature only if you trust your computer is safe."),
- ?XCT("li", "Memorize your password, or write it in a paper placed in a safe place. "
- "In Jabber there isn't an automated way to recover your password if you forget it.")
- ])
- ]),
- ?XE("li", [
- ?CT("Password Verification:"),
- ?C(" "),
- ?INPUTS("password", "password2", "", "20")
- ])] ++ CaptchaEls ++ [
- %% Nombre</b> (opcional)<b>:</b> <input type="text" size="20" name="name" maxlength="255"> <br /> <br /> -->
- %%
- %% Direcci&oacute;n de correo</b> (opcional)<b>:</b> <input type="text" size="20" name="email" maxlength="255"> <br /> <br /> -->
- ?XE("li", [
- ?INPUTT("submit", "register", "Register")
- ])
- ])
- ])
- ],
+ HeadEls = [?XCT(<<"title">>,
+ <<"Register a Jabber account">>),
+ ?XA(<<"link">>,
+ [{<<"href">>, <<"/register/register.css">>},
+ {<<"type">>, <<"text/css">>},
+ {<<"rel">>, <<"stylesheet">>}])],
+ Els = [?XACT(<<"h1">>,
+ [{<<"class">>, <<"title">>},
+ {<<"style">>, <<"text-align:center;">>}],
+ <<"Register a Jabber account">>),
+ ?XCT(<<"p">>,
+ <<"This page allows to create a Jabber "
+ "account in this Jabber server. Your "
+ "JID (Jabber IDentifier) will be of the "
+ "form: username@server. Please read carefully "
+ "the instructions to fill correctly the "
+ "fields.">>),
+ ?XAE(<<"form">>,
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ [?XE(<<"ol">>,
+ ([?XE(<<"li">>,
+ [?CT(<<"Username:">>), ?C(<<" ">>),
+ ?INPUTS(<<"text">>, <<"username">>, <<"">>,
+ <<"20">>),
+ ?BR,
+ ?XE(<<"ul">>,
+ [?XCT(<<"li">>,
+ <<"This is case insensitive: macbeth is "
+ "the same that MacBeth and Macbeth.">>),
+ ?XC(<<"li">>,
+ <<(?T(<<"Characters not allowed:">>))/binary,
+ " \" & ' / : < > @ ">>)])]),
+ ?XE(<<"li">>,
+ [?CT(<<"Server:">>), ?C(<<" ">>), ?C(Host)]),
+ ?XE(<<"li">>,
+ [?CT(<<"Password:">>), ?C(<<" ">>),
+ ?INPUTS(<<"password">>, <<"password">>, <<"">>,
+ <<"20">>),
+ ?BR,
+ ?XE(<<"ul">>,
+ [?XCT(<<"li">>,
+ <<"Don't tell your password to anybody, "
+ "not even the administrators of the Jabber "
+ "server.">>),
+ ?XCT(<<"li">>,
+ <<"You can later change your password using "
+ "a Jabber client.">>),
+ ?XCT(<<"li">>,
+ <<"Some Jabber clients can store your password "
+ "in your computer. Use that feature only "
+ "if you trust your computer is safe.">>),
+ ?XCT(<<"li">>,
+ <<"Memorize your password, or write it "
+ "in a paper placed in a safe place. In "
+ "Jabber there isn't an automated way "
+ "to recover your password if you forget "
+ "it.">>)])]),
+ ?XE(<<"li">>,
+ [?CT(<<"Password Verification:">>), ?C(<<" ">>),
+ ?INPUTS(<<"password">>, <<"password2">>, <<"">>,
+ <<"20">>)])]
+ ++
+ CaptchaEls ++
+ [?XE(<<"li">>,
+ [?INPUTT(<<"submit">>, <<"register">>,
+ <<"Register">>)])]))])],
{200,
- [{"Server", "ejabberd"},
- {"Content-Type", "text/html"}],
+ [{<<"Server">>, <<"ejabberd">>},
+ {<<"Content-Type">>, <<"text/html">>}],
ejabberd_web:make_xhtml(HeadEls, Els)}.
-%% Copied from mod_register.erl
-send_registration_notifications(UJID, Source) ->
- Host = UJID#jid.lserver,
- case gen_mod:get_module_opt(Host, ?MODULE, registration_watchers, []) of
- [] -> ok;
- JIDs when is_list(JIDs) ->
- Body = lists:flatten(
- io_lib:format(
- "[~s] The account ~s was registered from IP address ~s "
- "on node ~w using ~p.",
- [get_time_string(), jlib:jid_to_string(UJID),
- ip_to_string(Source), node(), ?MODULE])),
- lists:foreach(
- fun(S) ->
- case jlib:string_to_jid(S) of
- error -> ok;
- JID ->
- ejabberd_router:route(
- jlib:make_jid("", Host, ""),
- JID,
- {xmlelement, "message", [{"type", "chat"}],
- [{xmlelement, "body", [],
- [{xmlcdata, Body}]}]})
- end
- end, JIDs);
- _ ->
- ok
- end.
-ip_to_string(Source) when is_tuple(Source) -> inet_parse:ntoa(Source);
-ip_to_string(undefined) -> "undefined";
-ip_to_string(_) -> "unknown".
-get_time_string() -> write_time(erlang:localtime()).
-%% Function copied from ejabberd_logger_h.erl and customized
-write_time({{Y,Mo,D},{H,Mi,S}}) ->
- io_lib:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
- [Y, Mo, D, H, Mi, S]).
-
%%%----------------------------------------------------------------------
%%% Formulary new POST
%%%----------------------------------------------------------------------
form_new_post(Q, Host) ->
case catch get_register_parameters(Q) of
- [Username, Password, Password, Id, Key] ->
- form_new_post(Username, Host, Password, {Id, Key});
- [_Username, _Password, _Password2, false, false] ->
- {error, passwords_not_identical};
- [_Username, _Password, _Password2, Id, Key] ->
- ejabberd_captcha:check_captcha(Id, Key), %% This deletes the captcha
- {error, passwords_not_identical};
- _ ->
- {error, wrong_parameters}
+ [Username, Password, Password, Id, Key] ->
+ form_new_post(Username, Host, Password, {Id, Key});
+ [_Username, _Password, _Password2, false, false] ->
+ {error, passwords_not_identical};
+ [_Username, _Password, _Password2, Id, Key] ->
+ ejabberd_captcha:check_captcha(Id, Key),
+ {error, passwords_not_identical};
+ _ -> {error, wrong_parameters}
end.
get_register_parameters(Q) ->
- lists:map(
- fun(Key) ->
- case lists:keysearch(Key, 1, Q) of
- {value, {_Key, Value}} -> Value;
- false -> false
- end
- end,
- ["username", "password", "password2", "id", "key"]).
-
-form_new_post(Username, Host, Password, {false, false}) ->
+ lists:map(fun (Key) ->
+ case lists:keysearch(Key, 1, Q) of
+ {value, {_Key, Value}} -> Value;
+ false -> false
+ end
+ end,
+ [<<"username">>, <<"password">>, <<"password2">>,
+ <<"id">>, <<"key">>]).
+
+form_new_post(Username, Host, Password,
+ {false, false}) ->
register_account(Username, Host, Password);
form_new_post(Username, Host, Password, {Id, Key}) ->
case ejabberd_captcha:check_captcha(Id, Key) of
- captcha_valid ->
- register_account(Username, Host, Password);
- captcha_non_valid ->
- {error, captcha_non_valid};
- captcha_not_found ->
- {error, captcha_non_valid}
+ captcha_valid ->
+ register_account(Username, Host, Password);
+ captcha_non_valid -> {error, captcha_non_valid};
+ captcha_not_found -> {error, captcha_non_valid}
end.
%%%----------------------------------------------------------------------
@@ -339,28 +311,26 @@ form_new_post(Username, Host, Password, {Id, Key}) ->
build_captcha_li_list(Lang, IP) ->
case ejabberd_captcha:is_feature_available() of
- true -> build_captcha_li_list2(Lang, IP);
- false -> []
+ true -> build_captcha_li_list2(Lang, IP);
+ false -> []
end.
build_captcha_li_list2(Lang, IP) ->
- SID = "",
- From = #jid{user = "", server = "test", resource = ""},
- To = #jid{user = "", server = "test", resource = ""},
+ SID = <<"">>,
+ From = #jid{user = <<"">>, server = <<"test">>,
+ resource = <<"">>},
+ To = #jid{user = <<"">>, server = <<"test">>,
+ resource = <<"">>},
Args = [],
- case ejabberd_captcha:create_captcha(SID, From, To, Lang, IP, Args) of
- {ok, Id, _} ->
- {_, {CImg,CText,CId,CKey}} =
- ejabberd_captcha:build_captcha_html(Id, Lang),
- [?XE("li", [CText,
- ?C(" "),
- CId,
- CKey,
- ?BR,
- CImg]
- )];
- _ ->
- []
+ case ejabberd_captcha:create_captcha(SID, From, To,
+ Lang, IP, Args)
+ of
+ {ok, Id, _} ->
+ {_, {CImg, CText, CId, CKey}} =
+ ejabberd_captcha:build_captcha_html(Id, Lang),
+ [?XE(<<"li">>,
+ [CText, ?C(<<" ">>), CId, CKey, ?BR, CImg])];
+ _ -> []
end.
%%%----------------------------------------------------------------------
@@ -368,54 +338,42 @@ build_captcha_li_list2(Lang, IP) ->
%%%----------------------------------------------------------------------
form_changepass_get(Host, Lang) ->
- HeadEls = [
- ?XCT("title", "Change Password"),
- ?XA("link",
- [{"href", "/register/register.css"},
- {"type", "text/css"},
- {"rel", "stylesheet"}])
- ],
- Els=[
- ?XACT("h1",
- [{"class", "title"}, {"style", "text-align:center;"}],
- "Change Password"),
- ?XAE("form", [{"action", ""}, {"method", "post"}],
- [
- ?XE("ol", [
- ?XE("li", [
- ?CT("Username:"),
- ?C(" "),
- ?INPUTS("text", "username", "", "20")
- ]),
- ?XE("li", [
- ?CT("Server:"),
- ?C(" "),
- ?C(Host)
- ]),
- ?XE("li", [
- ?CT("Old Password:"),
- ?C(" "),
- ?INPUTS("password", "passwordold", "", "20")
- ]),
- ?XE("li", [
- ?CT("New Password:"),
- ?C(" "),
- ?INPUTS("password", "password", "", "20")
- ]),
- ?XE("li", [
- ?CT("Password Verification:"),
- ?C(" "),
- ?INPUTS("password", "password2", "", "20")
- ]),
- ?XE("li", [
- ?INPUTT("submit", "changepass", "Change Password")
- ])
- ])
- ])
- ],
+ HeadEls = [?XCT(<<"title">>, <<"Change Password">>),
+ ?XA(<<"link">>,
+ [{<<"href">>, <<"/register/register.css">>},
+ {<<"type">>, <<"text/css">>},
+ {<<"rel">>, <<"stylesheet">>}])],
+ Els = [?XACT(<<"h1">>,
+ [{<<"class">>, <<"title">>},
+ {<<"style">>, <<"text-align:center;">>}],
+ <<"Change Password">>),
+ ?XAE(<<"form">>,
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ [?XE(<<"ol">>,
+ [?XE(<<"li">>,
+ [?CT(<<"Username:">>), ?C(<<" ">>),
+ ?INPUTS(<<"text">>, <<"username">>, <<"">>,
+ <<"20">>)]),
+ ?XE(<<"li">>,
+ [?CT(<<"Server:">>), ?C(<<" ">>), ?C(Host)]),
+ ?XE(<<"li">>,
+ [?CT(<<"Old Password:">>), ?C(<<" ">>),
+ ?INPUTS(<<"password">>, <<"passwordold">>, <<"">>,
+ <<"20">>)]),
+ ?XE(<<"li">>,
+ [?CT(<<"New Password:">>), ?C(<<" ">>),
+ ?INPUTS(<<"password">>, <<"password">>, <<"">>,
+ <<"20">>)]),
+ ?XE(<<"li">>,
+ [?CT(<<"Password Verification:">>), ?C(<<" ">>),
+ ?INPUTS(<<"password">>, <<"password2">>, <<"">>,
+ <<"20">>)]),
+ ?XE(<<"li">>,
+ [?INPUTT(<<"submit">>, <<"changepass">>,
+ <<"Change Password">>)])])])],
{200,
- [{"Server", "ejabberd"},
- {"Content-Type", "text/html"}],
+ [{<<"Server">>, <<"ejabberd">>},
+ {<<"Content-Type">>, <<"text/html">>}],
ejabberd_web:make_xhtml(HeadEls, Els)}.
%%%----------------------------------------------------------------------
@@ -424,64 +382,56 @@ form_changepass_get(Host, Lang) ->
form_changepass_post(Q, Host) ->
case catch get_changepass_parameters(Q) of
- [Username, PasswordOld, Password, Password] ->
- try_change_password(Username, Host, PasswordOld, Password);
- [_Username, _PasswordOld, _Password, _Password2] ->
- {error, passwords_not_identical};
- _ ->
- {error, wrong_parameters}
+ [Username, PasswordOld, Password, Password] ->
+ try_change_password(Username, Host, PasswordOld,
+ Password);
+ [_Username, _PasswordOld, _Password, _Password2] ->
+ {error, passwords_not_identical};
+ _ -> {error, wrong_parameters}
end.
get_changepass_parameters(Q) ->
- lists:map(
- fun(Key) ->
- {value, {_Key, Value}} = lists:keysearch(Key, 1, Q),
- Value
- end,
- ["username", "passwordold", "password", "password2"]).
-
-%% @spec(Username,Host,PasswordOld,Password) -> {atomic, ok} |
-%% {error, account_doesnt_exist} |
-%% {error, password_not_changed} |
-%% {error, password_incorrect}
-try_change_password(Username, Host, PasswordOld, Password) ->
- try change_password(Username, Host, PasswordOld, Password) of
- {atomic, ok} ->
- {atomic, ok}
+ lists:map(fun (Key) ->
+ {value, {_Key, Value}} = lists:keysearch(Key, 1, Q),
+ Value
+ end,
+ [<<"username">>, <<"passwordold">>, <<"password">>,
+ <<"password2">>]).
+
+try_change_password(Username, Host, PasswordOld,
+ Password) ->
+ try change_password(Username, Host, PasswordOld,
+ Password)
+ of
+ {atomic, ok} -> {atomic, ok}
catch
- error:{badmatch, Error} ->
- {error, Error}
+ error:{badmatch, Error} -> {error, Error}
end.
-change_password(Username, Host, PasswordOld, Password) ->
- %% Check the account exists
+change_password(Username, Host, PasswordOld,
+ Password) ->
account_exists = check_account_exists(Username, Host),
-
- %% Check the old password is correct
- password_correct = check_password(Username, Host, PasswordOld),
-
- %% This function always returns: ok
- %% Change the password
- ok = ejabberd_auth:set_password(Username, Host, Password),
-
- %% Check the new password is correct
+ password_correct = check_password(Username, Host,
+ PasswordOld),
+ ok = ejabberd_auth:set_password(Username, Host,
+ Password),
case check_password(Username, Host, Password) of
- password_correct ->
- {atomic, ok};
- password_incorrect ->
- {error, password_not_changed}
+ password_correct -> {atomic, ok};
+ password_incorrect -> {error, password_not_changed}
end.
check_account_exists(Username, Host) ->
case ejabberd_auth:is_user_exists(Username, Host) of
- true -> account_exists;
- false -> account_doesnt_exist
+ true -> account_exists;
+ false -> account_doesnt_exist
end.
check_password(Username, Host, Password) ->
- case ejabberd_auth:check_password(Username, Host, Password) of
- true -> password_correct;
- false -> password_incorrect
+ case ejabberd_auth:check_password(Username, Host,
+ Password)
+ of
+ true -> password_correct;
+ false -> password_incorrect
end.
%%%----------------------------------------------------------------------
@@ -489,63 +439,53 @@ check_password(Username, Host, Password) ->
%%%----------------------------------------------------------------------
form_del_get(Host, Lang) ->
- HeadEls = [
- ?XCT("title", "Unregister a Jabber account"),
- ?XA("link",
- [{"href", "/register/register.css"},
- {"type", "text/css"},
- {"rel", "stylesheet"}])
- ],
- Els=[
- ?XACT("h1",
- [{"class", "title"}, {"style", "text-align:center;"}],
- "Unregister a Jabber account"),
- ?XCT("p",
- "This page allows to unregister a Jabber account in this Jabber server."),
- ?XAE("form", [{"action", ""}, {"method", "post"}],
- [
- ?XE("ol", [
- ?XE("li", [
- ?CT("Username:"),
- ?C(" "),
- ?INPUTS("text", "username", "", "20")
- ]),
- ?XE("li", [
- ?CT("Server:"),
- ?C(" "),
- ?C(Host)
- ]),
- ?XE("li", [
- ?CT("Password:"),
- ?C(" "),
- ?INPUTS("password", "password", "", "20")
- ]),
- ?XE("li", [
- ?INPUTT("submit", "unregister", "Unregister")
- ])
- ])
- ])
- ],
+ HeadEls = [?XCT(<<"title">>,
+ <<"Unregister a Jabber account">>),
+ ?XA(<<"link">>,
+ [{<<"href">>, <<"/register/register.css">>},
+ {<<"type">>, <<"text/css">>},
+ {<<"rel">>, <<"stylesheet">>}])],
+ Els = [?XACT(<<"h1">>,
+ [{<<"class">>, <<"title">>},
+ {<<"style">>, <<"text-align:center;">>}],
+ <<"Unregister a Jabber account">>),
+ ?XCT(<<"p">>,
+ <<"This page allows to unregister a Jabber "
+ "account in this Jabber server.">>),
+ ?XAE(<<"form">>,
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ [?XE(<<"ol">>,
+ [?XE(<<"li">>,
+ [?CT(<<"Username:">>), ?C(<<" ">>),
+ ?INPUTS(<<"text">>, <<"username">>, <<"">>,
+ <<"20">>)]),
+ ?XE(<<"li">>,
+ [?CT(<<"Server:">>), ?C(<<" ">>), ?C(Host)]),
+ ?XE(<<"li">>,
+ [?CT(<<"Password:">>), ?C(<<" ">>),
+ ?INPUTS(<<"password">>, <<"password">>, <<"">>,
+ <<"20">>)]),
+ ?XE(<<"li">>,
+ [?INPUTT(<<"submit">>, <<"unregister">>,
+ <<"Unregister">>)])])])],
{200,
- [{"Server", "ejabberd"},
- {"Content-Type", "text/html"}],
+ [{<<"Server">>, <<"ejabberd">>},
+ {<<"Content-Type">>, <<"text/html">>}],
ejabberd_web:make_xhtml(HeadEls, Els)}.
-%% @spec(Username, Host, Password) -> {success, ok, {Username, Host, Password} |
-%% {success, exists, {Username, Host, Password}} |
-%% {error, not_allowed} |
-%% {error, invalid_jid}
register_account(Username, Host, Password) ->
- case jlib:make_jid(Username, Host, "") of
- error -> {error, invalid_jid};
- _ -> register_account2(Username, Host, Password)
+ case jlib:make_jid(Username, Host, <<"">>) of
+ error -> {error, invalid_jid};
+ _ -> register_account2(Username, Host, Password)
end.
+
register_account2(Username, Host, Password) ->
- case ejabberd_auth:try_register(Username, Host, Password) of
- {atomic, Res} ->
- {success, Res, {Username, Host, Password}};
- Other ->
- Other
+ case ejabberd_auth:try_register(Username, Host,
+ Password)
+ of
+ {atomic, Res} ->
+ {success, Res, {Username, Host, Password}};
+ Other -> Other
end.
%%%----------------------------------------------------------------------
@@ -554,47 +494,33 @@ register_account2(Username, Host, Password) ->
form_del_post(Q, Host) ->
case catch get_unregister_parameters(Q) of
- [Username, Password] ->
- try_unregister_account(Username, Host, Password);
- _ ->
- {error, wrong_parameters}
+ [Username, Password] ->
+ try_unregister_account(Username, Host, Password);
+ _ -> {error, wrong_parameters}
end.
get_unregister_parameters(Q) ->
- lists:map(
- fun(Key) ->
- {value, {_Key, Value}} = lists:keysearch(Key, 1, Q),
- Value
- end,
- ["username", "password"]).
-
-%% @spec(Username, Host, Password) -> {atomic, ok} |
-%% {error, account_doesnt_exist} |
-%% {error, account_exists} |
-%% {error, password_incorrect}
+ lists:map(fun (Key) ->
+ {value, {_Key, Value}} = lists:keysearch(Key, 1, Q),
+ Value
+ end,
+ [<<"username">>, <<"password">>]).
+
try_unregister_account(Username, Host, Password) ->
try unregister_account(Username, Host, Password) of
- {atomic, ok} ->
- {atomic, ok}
+ {atomic, ok} -> {atomic, ok}
catch
- error:{badmatch, Error} ->
- {error, Error}
+ error:{badmatch, Error} -> {error, Error}
end.
unregister_account(Username, Host, Password) ->
- %% Check the account exists
account_exists = check_account_exists(Username, Host),
-
- %% Check the password is correct
- password_correct = check_password(Username, Host, Password),
-
- %% This function always returns: ok
- ok = ejabberd_auth:remove_user(Username, Host, Password),
-
- %% Check the account does not exist anymore
- account_doesnt_exist = check_account_exists(Username, Host),
-
- %% If we reached this point, return success
+ password_correct = check_password(Username, Host,
+ Password),
+ ok = ejabberd_auth:remove_user(Username, Host,
+ Password),
+ account_doesnt_exist = check_account_exists(Username,
+ Host),
{atomic, ok}.
%%%----------------------------------------------------------------------
@@ -602,24 +528,24 @@ unregister_account(Username, Host, Password) ->
%%%----------------------------------------------------------------------
get_error_text({error, captcha_non_valid}) ->
- "The captcha you entered is wrong";
+ <<"The captcha you entered is wrong">>;
get_error_text({success, exists, _}) ->
get_error_text({atomic, exists});
get_error_text({atomic, exists}) ->
- "The account already exists";
+ <<"The account already exists">>;
get_error_text({error, password_incorrect}) ->
- "Incorrect password";
+ <<"Incorrect password">>;
get_error_text({error, invalid_jid}) ->
- "The username is not valid";
+ <<"The username is not valid">>;
get_error_text({error, not_allowed}) ->
- "Not allowed";
+ <<"Not allowed">>;
get_error_text({error, account_doesnt_exist}) ->
- "Account doesn't exist";
+ <<"Account doesn't exist">>;
get_error_text({error, account_exists}) ->
- "The account was not deleted";
+ <<"The account was not deleted">>;
get_error_text({error, password_not_changed}) ->
- "The password was not changed";
+ <<"The password was not changed">>;
get_error_text({error, passwords_not_identical}) ->
- "The passwords are different";
+ <<"The passwords are different">>;
get_error_text({error, wrong_parameters}) ->
- "Wrong parameters in the web formulary".
+ <<"Wrong parameters in the web formulary">>.
diff --git a/src/web/pshb_http.erl b/src/web/pshb_http.erl
index 02eb0a4cc..6f1a7a52c 100644
--- a/src/web/pshb_http.erl
+++ b/src/web/pshb_http.erl
@@ -25,7 +25,7 @@
%%%----------------------------------------------------------------------
%%%
%%% {5280, ejabberd_http, [
-%%% http_poll,
+%%% http_poll,
%%% web_admin,
%%% {request_handlers, [{["pshb"], pshb_http}]} % this should be added
%%% ]}
@@ -34,383 +34,465 @@
%%% curl -u cstar@localhost:encore -i -X POST http://localhost:5280/pshb/localhost/foo -d @sam.atom
%%%
+-module(pshb_http).
--module (pshb_http).
-author('ecestari@process-one.net').
--compile({no_auto_import,[error/1]}).
+-compile({no_auto_import, [{error, 1}]}).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
+
-include("ejabberd_http.hrl").
-include("mod_pubsub/pubsub.hrl").
--export([process/2]).
-
-process([Domain | _Rest] = LocalPath, #request{auth = Auth} = Request)->
- UD = get_auth(Auth),
- Module = backend(Domain),
- case catch out(Module, Request, Request#request.method, LocalPath,UD) of
- {'EXIT', Error} ->
- ?ERROR_MSG("Error while processing ~p : ~n~p", [LocalPath, Error]),
- error(500);
- Result ->
- Result
- end.
-
+-export([process/2]).
+
+process([Domain | _Rest] = LocalPath,
+ #request{auth = Auth} = Request) ->
+ UD = get_auth(Auth),
+ Module = backend(Domain),
+ case catch out(Module, Request, Request#request.method,
+ LocalPath, UD)
+ of
+ {'EXIT', Error} ->
+ ?ERROR_MSG("Error while processing ~p : ~n~p",
+ [LocalPath, Error]),
+ error(500);
+ Result -> Result
+ end.
+
get_auth(Auth) ->
case Auth of
- {SJID, P} ->
- case jlib:string_to_jid(SJID) of
- error ->
- undefined;
- #jid{user = U, server = S} ->
- case ejabberd_auth:check_password(U, S, P) of
- true ->
- {U, S};
- false ->
- undefined
- end
- end;
- _ ->
- undefined
+ {SJID, P} ->
+ case jlib:string_to_jid(SJID) of
+ error -> undefined;
+ #jid{user = U, server = S} ->
+ case ejabberd_auth:check_password(U, S, P) of
+ true -> {U, S};
+ false -> undefined
+ end
+ end;
+ _ -> undefined
end.
-out(Module, Args, 'GET', [Domain,Node]=Uri, _User) ->
- case Module:tree_action(get_host(Uri), get_node, [get_host(Uri),get_collection(Uri)]) of
- {error, Error} ->
- error(Error);
- #pubsub_node{options = Options}->
+out(Module, Args, 'GET', [Domain, Node] = Uri, _User) ->
+ case Module:tree_action(get_host(Uri), get_node,
+ [get_host(Uri), get_collection(Uri)])
+ of
+ {error, Error} -> error(Error);
+ #pubsub_node{options = Options} ->
AccessModel = lists:keyfind(access_model, 1, Options),
case AccessModel of
{access_model, open} ->
- Items = lists:sort(fun(X,Y)->
- {DateX, _} = X#pubsub_item.modification,
- {DateY, _} = Y#pubsub_item.modification,
- DateX > DateY
- end, Module:get_items(
- get_host(Uri),
- get_collection(Uri))),
- case Items of
- [] -> ?DEBUG("Items : ~p ~n", [collection(get_collection(Uri),
- collection_uri(Args,Domain,Node), calendar:now_to_universal_time(erlang:now()), "", [])]),
- {200, [{"Content-Type", "application/atom+xml"}],
- collection(get_collection(Uri),
- collection_uri(Args,Domain,Node), calendar:now_to_universal_time(erlang:now()), "", [])};
- _ ->
- #pubsub_item{modification = {LastDate, _JID}} = LastItem = hd(Items),
- Etag =generate_etag(LastItem),
- IfNoneMatch=proplists:get_value('If-None-Match', Args#request.headers),
- if IfNoneMatch==Etag
- ->
- success(304);
- true ->
- XMLEntries= [item_to_entry(Args,Domain, Node,Entry)||Entry <- Items],
- {200, [{"Content-Type", "application/atom+xml"},{"Etag", Etag}],
- "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- ++ xml:element_to_string(
- collection(get_collection(Uri), collection_uri(Args,Domain,Node),
- calendar:now_to_universal_time(LastDate), "", XMLEntries))}
- end
- end;
- {access_model, Access} ->
- ?INFO_MSG("Uri ~p requested. access_model is ~p. HTTP access denied unless access_model =:= open",
- [Uri, Access]),
- error(?ERR_FORBIDDEN)
- end
- end;
-
-out(Module, Args, 'POST', [_D, _Node]=Uri, {_User, _Domain} = UD) ->
- publish_item(Module, Args, Uri, uniqid(false), UD);
-
-out(Module, Args, 'PUT', [_D, _Node, Slug]=Uri, {_User, _Domain} = UD) ->
- publish_item(Module, Args, Uri, Slug, UD);
-
-out(Module, _Args, 'DELETE', [_D, Node, Id]= Uri, {User, UDomain}) ->
- Jid = jlib:make_jid({User, UDomain, ""}),
- case Module:delete_item(get_host(Uri), list_to_binary(Node), Jid, Id) of
- {error, Error} -> error(Error);
- {result, _Res} -> success(200)
- end;
-
-
-out(Module, Args, 'PUT', [_Domain, Node]= Uri, {User, UDomain}) ->
- Host = get_host(Uri),
- Jid = jlib:make_jid({User, UDomain, ""}),
- Payload = xml_stream:parse_element(Args#request.data),
- ConfigureElement = case xml:get_subtag(Payload, "configure") of
- false ->[];
- {xmlelement, _, _, SubEls}->SubEls
- end,
- case Module:set_configure(Host, list_to_binary(Node), Jid, ConfigureElement, Args#request.lang) of
- {result, []} -> success(200);
- {error, Error} -> error(Error)
- end;
-
-out(Module, Args, 'GET', [Domain]=Uri, From)->
- Host = get_host(Uri),
- ?DEBUG("Host = ~p", [Host]),
- case Module:tree_action(Host, get_subnodes, [Host, <<>>, From ]) of
- [] ->
- ?DEBUG("Error getting URI ~p : ~p",[Uri, From]),
- error(?ERR_ITEM_NOT_FOUND);
- Collections ->
- {200, [{"Content-Type", "application/atomsvc+xml"}], "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
- ++ xml:element_to_string(service(Args,Domain, Collections))}
- end;
-
-out(Module, Args, 'POST', [Domain]=Uri, {User, UDomain})->
- Host = get_host(Uri),
- Payload = xml_stream:parse_element(Args#request.data),
- {Node, Type} = case xml:get_subtag(Payload, "create") of
- false -> {<<>>,"flat"};
- E ->
- {list_to_binary(get_tag_attr_or_default("node", E,"")),
- get_tag_attr_or_default("type", E,"flat")}
- end,
- ConfigureElement = case xml:get_subtag(Payload, "configure") of
- false ->[];
- {xmlelement, _, _, SubEls}->SubEls
- end,
- Jid = jlib:make_jid({User, UDomain, ""}),
- case Module:create_node(Host, Domain, Node, Jid, Type, all, ConfigureElement) of
- {error, Error} ->
- ?ERROR_MSG("Error create node via HTTP : ~p",[Error]),
- error(Error); % will probably detail more
- {result, [Result]} ->
- {200, [{"Content-Type", "application/xml"}], "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
- ++ xml:element_to_string(Result)}
- end;
-
-out(Module,_Args, 'DELETE', [_Domain, Node] = Uri, {User, UDomain})->
- Host = get_host(Uri),
- Jid = jlib:make_jid({User, UDomain, ""}),
- BinNode = list_to_binary(Node),
- case Module:delete_node(Host, BinNode, Jid) of
- {error, Error} -> error(Error);
- {result, []} ->
- {200, [],[]}
- end;
-
-
-
-out(Module, Args, 'GET', [Domain, Node, _Item]=URI, _) ->
- Failure = fun(Error)->
- ?DEBUG("Error getting URI ~p : ~p",[URI, Error]),
- error(Error)
- end,
- Success = fun(Item)->
- Etag =generate_etag(Item),
- IfNoneMatch=proplists:get_value('If-None-Match', Args#request.headers),
- if IfNoneMatch==Etag
- ->
- success(304);
- true ->
- {200, [{"Content-Type", "application/atom+xml"},{"Etag", Etag}], "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
- ++ xml:element_to_string(item_to_entry(Args, Domain,Node, Item))}
- end
- end,
- get_item(Module, URI, Failure, Success);
-
-out(_Module,_,Method,Uri,undefined) ->
- ?DEBUG("Error, ~p not authorized for ~p : ~p",[ Method,Uri]),
- error(?ERR_FORBIDDEN).
-
-get_item(Module, Uri, Failure, Success)->
- ?DEBUG(" Module:get_item(~p, ~p,~p)", [get_host(Uri), get_collection(Uri), get_member(Uri)]),
- case Module:get_item(get_host(Uri), get_collection(Uri), get_member(Uri)) of
- {error, Reason} ->
- Failure(Reason);
- #pubsub_item{}=Item ->
- Success(Item)
- end.
-
-publish_item(Module, Args, [Domain, Node | _R] = Uri, Slug, {User, Domain})->
-
- Payload = xml_stream:parse_element(Args#request.data),
- [FilteredPayload]=xml:remove_cdata([Payload]),
-
- %FilteredPayload2 = case xml:get_subtag(FilteredPayload, "app:edited") ->
- % {xmlelement, Name, Attrs, [{cdata, }]}
- case Module:publish_item(get_host(Uri),
- Domain,
- get_collection(Uri),
- jlib:make_jid(User,Domain, ""),
- Slug,
- [FilteredPayload]) of
- {result, [_]} ->
- ?DEBUG("Publishing to ~p~n",[entry_uri(Args, Domain, Node,Slug)]),
- {201, [{"location", entry_uri(Args, Domain,Node,Slug)}], Payload};
- {error, Error} ->
- error(Error)
- end.
-
-generate_etag(#pubsub_item{modification={{_, D2, D3}, _JID}})->integer_to_list(D3+D2).
-get_host([Domain|_Rest])-> "pubsub."++Domain.
-get_collection([_Domain, Node|_Rest])->list_to_binary(Node).
-get_member([_Domain, _Node, Member])->
- Member.
+ Items = lists:sort(fun (X, Y) ->
+ {DateX, _} =
+ X#pubsub_item.modification,
+ {DateY, _} =
+ Y#pubsub_item.modification,
+ DateX > DateY
+ end,
+ Module:get_items(get_host(Uri),
+ get_collection(Uri))),
+ case Items of
+ [] ->
+ ?DEBUG("Items : ~p ~n",
+ [collection(get_collection(Uri),
+ collection_uri(Args, Domain, Node),
+ calendar:now_to_universal_time(erlang:now()),
+ <<"">>, [])]),
+ {200,
+ [{<<"Content-Type">>, <<"application/atom+xml">>}],
+ collection(get_collection(Uri),
+ collection_uri(Args, Domain, Node),
+ calendar:now_to_universal_time(erlang:now()),
+ <<"">>, [])};
+ _ ->
+ #pubsub_item{modification = {LastDate, _JID}} =
+ LastItem = hd(Items),
+ Etag = generate_etag(LastItem),
+ IfNoneMatch = proplists:get_value('If-None-Match',
+ Args#request.headers),
+ if IfNoneMatch == Etag -> success(304);
+ true ->
+ XMLEntries = [item_to_entry(Args, Domain, Node,
+ Entry)
+ || Entry <- Items],
+ {200,
+ [{<<"Content-Type">>, <<"application/atom+xml">>},
+ {<<"Etag">>, Etag}],
+ <<"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n",
+ (xml:element_to_binary(collection(get_collection(Uri),
+ collection_uri(Args,
+ Domain,
+ Node),
+ calendar:now_to_universal_time(LastDate),
+ <<"">>,
+ XMLEntries)))/binary>>}
+ end
+ end;
+ {access_model, Access} ->
+ ?INFO_MSG("Uri ~p requested. access_model is ~p. "
+ "HTTP access denied unless access_model "
+ "=:= open",
+ [Uri, Access]),
+ error(?ERR_FORBIDDEN)
+ end
+ end;
+out(Module, Args, 'POST', [_D, _Node] = Uri,
+ {_User, _Domain} = UD) ->
+ publish_item(Module, Args, Uri, uniqid(false), UD);
+out(Module, Args, 'PUT', [_D, _Node, Slug] = Uri,
+ {_User, _Domain} = UD) ->
+ publish_item(Module, Args, Uri, Slug, UD);
+out(Module, _Args, 'DELETE', [_D, Node, Id] = Uri,
+ {User, UDomain}) ->
+ Jid = jlib:make_jid({User, UDomain, <<"">>}),
+ case Module:delete_item(get_host(Uri),
+ iolist_to_binary(Node), Jid, Id)
+ of
+ {error, Error} -> error(Error);
+ {result, _Res} -> success(200)
+ end;
+out(Module, Args, 'PUT', [_Domain, Node] = Uri,
+ {User, UDomain}) ->
+ Host = get_host(Uri),
+ Jid = jlib:make_jid({User, UDomain, <<"">>}),
+ Payload = xml_stream:parse_element(Args#request.data),
+ ConfigureElement = case xml:get_subtag(Payload,
+ <<"configure">>)
+ of
+ false -> [];
+ #xmlel{children = SubEls} -> SubEls
+ end,
+ case Module:set_configure(Host, iolist_to_binary(Node),
+ Jid, ConfigureElement, Args#request.lang)
+ of
+ {result, []} -> success(200);
+ {error, Error} -> error(Error)
+ end;
+out(Module, Args, 'GET', [Domain] = Uri, From) ->
+ Host = get_host(Uri),
+ ?DEBUG("Host = ~p", [Host]),
+ case Module:tree_action(Host, get_subnodes,
+ [Host, <<>>, From])
+ of
+ [] ->
+ ?DEBUG("Error getting URI ~p : ~p", [Uri, From]),
+ error(?ERR_ITEM_NOT_FOUND);
+ Collections ->
+ {200,
+ [{<<"Content-Type">>, <<"application/atomsvc+xml">>}],
+ <<"<?xml version=\"1.0\" encoding=\"utf-8\"?>",
+ (xml:element_to_binary(service(Args, Domain,
+ Collections)))/binary>>}
+ end;
+out(Module, Args, 'POST', [Domain] = Uri,
+ {User, UDomain}) ->
+ Host = get_host(Uri),
+ Payload = xml_stream:parse_element(Args#request.data),
+ {Node, Type} = case xml:get_subtag(Payload,
+ <<"create">>)
+ of
+ false -> {<<>>, <<"flat">>};
+ E ->
+ {get_tag_attr_or_default(<<"node">>, E, <<"">>),
+ get_tag_attr_or_default(<<"type">>, E, <<"flat">>)}
+ end,
+ ConfigureElement = case xml:get_subtag(Payload,
+ <<"configure">>)
+ of
+ false -> [];
+ #xmlel{children = SubEls} -> SubEls
+ end,
+ Jid = jlib:make_jid({User, UDomain, <<"">>}),
+ case Module:create_node(Host, Domain, Node, Jid, Type,
+ all, ConfigureElement)
+ of
+ {error, Error} ->
+ ?ERROR_MSG("Error create node via HTTP : ~p", [Error]),
+ error(Error);
+ {result, [Result]} ->
+ {200, [{<<"Content-Type">>, <<"application/xml">>}],
+ <<"<?xml version=\"1.0\" encoding=\"utf-8\"?>",
+ (xml:element_to_binary(Result))/binary>>}
+ end;
+out(Module, _Args, 'DELETE', [_Domain, Node] = Uri,
+ {User, UDomain}) ->
+ Host = get_host(Uri),
+ Jid = jlib:make_jid({User, UDomain, <<"">>}),
+ case Module:delete_node(Host, Node, Jid) of
+ {error, Error} -> error(Error);
+ {result, []} -> {200, [], []}
+ end;
+out(Module, Args, 'GET', [Domain, Node, _Item] = URI,
+ _) ->
+ Failure = fun (Error) ->
+ ?DEBUG("Error getting URI ~p : ~p", [URI, Error]),
+ error(Error)
+ end,
+ Success = fun (Item) ->
+ Etag = generate_etag(Item),
+ IfNoneMatch = proplists:get_value('If-None-Match',
+ Args#request.headers),
+ if IfNoneMatch == Etag -> success(304);
+ true ->
+ {200,
+ [{<<"Content-Type">>, <<"application/atom+xml">>},
+ {<<"Etag">>, Etag}],
+ <<"<?xml version=\"1.0\" encoding=\"utf-8\"?>",
+ (xml:element_to_binary(item_to_entry(Args,
+ Domain,
+ Node,
+ Item)))/binary>>}
+ end
+ end,
+ get_item(Module, URI, Failure, Success);
+out(_Module, _, Method, Uri, undefined) ->
+ ?DEBUG("Error, ~p not authorized for ~p : ~p",
+ [Method, Uri]),
+ error(?ERR_FORBIDDEN).
+
+get_item(Module, Uri, Failure, Success) ->
+ ?DEBUG(" Module:get_item(~p, ~p,~p)",
+ [get_host(Uri), get_collection(Uri), get_member(Uri)]),
+ case Module:get_item(get_host(Uri), get_collection(Uri),
+ get_member(Uri))
+ of
+ {error, Reason} -> Failure(Reason);
+ #pubsub_item{} = Item -> Success(Item)
+ end.
+
+publish_item(Module, Args, [Domain, Node | _R] = Uri,
+ Slug, {User, Domain}) ->
+ Payload = xml_stream:parse_element(Args#request.data),
+ [FilteredPayload] = xml:remove_cdata([Payload]),
+ case Module:publish_item(get_host(Uri), Domain,
+ get_collection(Uri),
+ jlib:make_jid(User, Domain, <<"">>), Slug,
+ [FilteredPayload])
+ of
+ {result, [_]} ->
+ ?DEBUG("Publishing to ~p~n",
+ [entry_uri(Args, Domain, Node, Slug)]),
+ {201,
+ [{<<"location">>, entry_uri(Args, Domain, Node, Slug)}],
+ Payload};
+ {error, Error} -> error(Error)
+ end.
+
+generate_etag(#pubsub_item{modification =
+ {{_, D2, D3}, _JID}}) ->
+ jlib:integer_to_binary(D3 + D2).
+
+get_host([Domain | _Rest]) ->
+ <<"pubsub.", Domain/binary>>.
+
+get_collection([_Domain, Node | _Rest]) ->
+ Node.
+
+get_member([_Domain, _Node, Member]) -> Member.
collection_uri(R, Domain, Node) ->
- base_uri(R, Domain)++ "/" ++ b2l(Node).
-
-entry_uri(R,Domain, Node, Id)->
- collection_uri(R,Domain, Node)++"/"++b2l(Id).
-
-base_uri(#request{host=Host, port=Port}, Domain)->
- "http://"++Host++":"++i2l(Port)++"/pshb/" ++ Domain.
-
-item_to_entry(Args,Domain, Node,#pubsub_item{itemid={Id,_}, payload=Entry}=Item)->
- [R]=xml:remove_cdata(Entry),
- item_to_entry(Args, Domain, Node, Id, R, Item).
-
-item_to_entry(Args,Domain, Node, Id,{xmlelement, "entry", Attrs, SubEl},
- #pubsub_item{modification={ Secs, JID} }) ->
- Date = calendar:now_to_local_time(Secs),
- {_User, Domain, _}=jlib:jid_tolower(JID),
- SubEl2=[{xmlelement, "app:edited", [], [{xmlcdata, w3cdtf(Date)}]},
- {xmlelement, "updated", [],[{xmlcdata, w3cdtf(Date)}]},
- {xmlelement, "author", [],[{xmlelement, "name", [], [{xmlcdata, list_to_binary(jlib:jid_to_string(JID))}]}]},
- {xmlelement, "link",[{"rel", "edit"},
- {"href", entry_uri(Args,Domain,Node, Id)}],[] },
- {xmlelement, "id", [],[{xmlcdata, entry_uri(Args, Domain, Node, Id)}]}
- | SubEl],
- {xmlelement, "entry", [{"xmlns:app","http://www.w3.org/2007/app"}|Attrs], SubEl2};
-
+ <<(base_uri(R, Domain))/binary, "/",
+ Node/binary>>.
+
+entry_uri(R, Domain, Node, Id) ->
+ <<(collection_uri(R, Domain, Node))/binary, "/",
+ Id/binary>>.
+
+base_uri(#request{host = Host, port = Port}, Domain) ->
+ <<"http://", Host/binary, ":", (i2l(Port))/binary,
+ "/pshb/", Domain/binary>>.
+
+item_to_entry(Args, Domain, Node,
+ #pubsub_item{itemid = {Id, _}, payload = Entry} =
+ Item) ->
+ [R] = xml:remove_cdata(Entry),
+ item_to_entry(Args, Domain, Node, Id, R, Item).
+
+item_to_entry(Args, Domain, Node, Id,
+ #xmlel{name = <<"entry">>, attrs = Attrs,
+ children = SubEl},
+ #pubsub_item{modification = {Secs, JID}}) ->
+ Date = calendar:now_to_local_time(Secs),
+ {_User, Domain, _} = jlib:jid_tolower(JID),
+ SubEl2 = [#xmlel{name = <<"app:edited">>, attrs = [],
+ children = [{xmlcdata, w3cdtf(Date)}]},
+ #xmlel{name = <<"updated">>, attrs = [],
+ children = [{xmlcdata, w3cdtf(Date)}]},
+ #xmlel{name = <<"author">>, attrs = [],
+ children =
+ [#xmlel{name = <<"name">>, attrs = [],
+ children =
+ [{xmlcdata,
+ jlib:jid_to_string(JID)}]}]},
+ #xmlel{name = <<"link">>,
+ attrs =
+ [{<<"rel">>, <<"edit">>},
+ {<<"href">>, entry_uri(Args, Domain, Node, Id)}],
+ children = []},
+ #xmlel{name = <<"id">>, attrs = [],
+ children =
+ [{xmlcdata, entry_uri(Args, Domain, Node, Id)}]}
+ | SubEl],
+ #xmlel{name = <<"entry">>,
+ attrs =
+ [{<<"xmlns:app">>, <<"http://www.w3.org/2007/app">>}
+ | Attrs],
+ children = SubEl2};
% Don't do anything except adding xmlns
-item_to_entry(_Args,_Domain, Node, _Id, {xmlelement, Name, Attrs, Subels}=Element, _Item)->
- case proplists:is_defined("xmlns",Attrs) of
- true -> Element;
- false -> {xmlelement, Name, [{"xmlns", Node}|Attrs], Subels}
- end.
-
-collection(Title, Link, Updated, _Id, Entries)->
- {xmlelement, "feed", [{"xmlns", "http://www.w3.org/2005/Atom"},
- {"xmlns:app", "http://www.w3.org/2007/app"}], [
- {xmlelement, "title", [],[{xmlcdata, Title}]},
- {xmlelement, "generator", [],[{xmlcdata, <<"ejabberd">>}]},
- {xmlelement, "updated", [],[{xmlcdata, w3cdtf(Updated)}]},
- {xmlelement, "link", [{"href", Link}, {"rel", "self"}], []},
- {xmlelement, "id", [], [{xmlcdata, list_to_binary(Link)}]},
- {xmlelement, "title", [],[{xmlcdata, Title}]} |
- Entries
- ]}.
-
-service(Args, Domain,Collections)->
- {xmlelement, "service", [{"xmlns", "http://www.w3.org/2007/app"},
- {"xmlns:atom", "http://www.w3.org/2005/Atom"},
- {"xmlns:app", "http://www.w3.org/2007/app"}],[
- {xmlelement, "workspace", [],[
- {xmlelement, "atom:title", [],[{xmlcdata,"Pubsub node Feed for " ++Domain}]} |
- lists:map(fun(#pubsub_node{nodeid={_Server, Id}, type=_Type})->
- {xmlelement, "collection", [{"href", collection_uri(Args,Domain, Id)}], [
- {xmlelement, "atom:title", [], [{xmlcdata, Id}]}
- ]}
- end, Collections)
- ]}
- ]}.
-
-%% simple output functions
-error({xmlelement, "error", Attrs, _}=Error) ->
- Value = list_to_integer(xml:get_attr_s("code", Attrs)),
- {Value, [{"Content-type", "application/xml"}], xml:element_to_string(Error)};
-error(404)->
- {404, [], "Not Found"};
-error(403)->
- {403, [], "Forbidden"};
-error(500)->
- {500, [], "Internal server error"};
-error(401)->
- {401, [{"WWW-Authenticate", "basic realm=\"ejabberd\""}],"Unauthorized"};
-error(Code)->
- {Code, [], ""}.
-success(200)->
- {200, [], ""};
-success(Code)->
- {Code, [], ""}.
-
-backend(Domain)->
- Modules = gen_mod:loaded_modules(Domain),
- case lists:member(mod_pubsub_odbc, Modules) of
- true -> mod_pubsub_odbc;
- _ -> mod_pubsub
- 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.
-%%% Create W3CDTF (http://www.w3.org/TR/NOTE-datetime) formatted date
-%%% w3cdtf(GregSecs) -> "YYYY-MM-DDThh:mm:ssTZD"
-%%%
-uniqid(false)->
- {T1, T2, T3} = now(),
- lists:flatten(io_lib:fwrite("~.16B~.16B~.16B", [T1, T2, T3]));
-uniqid(Slug) ->
- Slut = string:to_lower(Slug),
- S = string:substr(Slut, 1, 9),
- {_T1, T2, T3} = now(),
- lists:flatten(io_lib:fwrite("~s-~.16B~.16B", [S, T2, T3])).
-
-w3cdtf(Date) -> %1 Date = calendar:gregorian_seconds_to_datetime(GregSecs),
- {{Y, Mo, D},{H, Mi, S}} = Date,
- [UDate|_] = calendar:local_time_to_universal_time_dst(Date),
- {DiffD,{DiffH,DiffMi,_}}=calendar:time_difference(UDate,Date),
- w3cdtf_diff(Y, Mo, D, H, Mi, S, DiffD, DiffH, DiffMi).
-
-%%% w3cdtf's helper function
-w3cdtf_diff(Y, Mo, D, H, Mi, S, _DiffD, DiffH, DiffMi) when DiffH < 12, DiffH /= 0 ->
- i2l(Y) ++ "-" ++ add_zero(Mo) ++ "-" ++ add_zero(D) ++ "T" ++
- add_zero(H) ++ ":" ++ add_zero(Mi) ++ ":" ++
- add_zero(S) ++ "+" ++ add_zero(DiffH) ++ ":" ++ add_zero(DiffMi);
-
-w3cdtf_diff(Y, Mo, D, H, Mi, S, DiffD, DiffH, DiffMi) when DiffH > 12, DiffD == 0 ->
- i2l(Y) ++ "-" ++ add_zero(Mo) ++ "-" ++ add_zero(D) ++ "T" ++
- add_zero(H) ++ ":" ++ add_zero(Mi) ++ ":" ++
- add_zero(S) ++ "+" ++ add_zero(DiffH) ++ ":" ++
- add_zero(DiffMi);
-
-w3cdtf_diff(Y, Mo, D, H, Mi, S, DiffD, DiffH, DiffMi) when DiffH > 12, DiffD /= 0, DiffMi /= 0 ->
- i2l(Y) ++ "-" ++ add_zero(Mo) ++ "-" ++ add_zero(D) ++ "T" ++
- add_zero(H) ++ ":" ++ add_zero(Mi) ++ ":" ++
- add_zero(S) ++ "-" ++ add_zero(23-DiffH) ++
- ":" ++ add_zero(60-DiffMi);
-
-w3cdtf_diff(Y, Mo, D, H, Mi, S, DiffD, DiffH, DiffMi) when DiffH > 12, DiffD /= 0, DiffMi == 0 ->
- i2l(Y) ++ "-" ++ add_zero(Mo) ++ "-" ++ add_zero(D) ++ "T" ++
- add_zero(H) ++ ":" ++ add_zero(Mi) ++ ":" ++
- add_zero(S) ++ "-" ++ add_zero(24-DiffH) ++
- ":" ++ add_zero(DiffMi);
-
-w3cdtf_diff(Y, Mo, D, H, Mi, S, _DiffD, DiffH, _DiffMi) when DiffH == 0 ->
- i2l(Y) ++ "-" ++ add_zero(Mo) ++ "-" ++ add_zero(D) ++ "T" ++
- add_zero(H) ++ ":" ++ add_zero(Mi) ++ ":" ++
- add_zero(S) ++ "Z".
+item_to_entry(_Args, _Domain, Node, _Id,
+ #xmlel{name = Name, attrs = Attrs, children = Subels} =
+ Element,
+ _Item) ->
+ case proplists:is_defined(<<"xmlns">>, Attrs) of
+ true -> Element;
+ false ->
+ #xmlel{name = Name,
+ attrs = [{<<"xmlns">>, Node} | Attrs],
+ children = Subels}
+ end.
-add_zero(I) when is_integer(I) -> add_zero(i2l(I));
-add_zero([A]) -> [$0,A];
-add_zero(L) when is_list(L) -> L.
+collection(Title, Link, Updated, _Id, Entries) ->
+ #xmlel{name = <<"feed">>,
+ attrs =
+ [{<<"xmlns">>, <<"http://www.w3.org/2005/Atom">>},
+ {<<"xmlns:app">>, <<"http://www.w3.org/2007/app">>}],
+ children =
+ [#xmlel{name = <<"title">>, attrs = [],
+ children = [{xmlcdata, Title}]},
+ #xmlel{name = <<"generator">>, attrs = [],
+ children = [{xmlcdata, <<"ejabberd">>}]},
+ #xmlel{name = <<"updated">>, attrs = [],
+ children = [{xmlcdata, w3cdtf(Updated)}]},
+ #xmlel{name = <<"link">>,
+ attrs = [{<<"href">>, Link}, {<<"rel">>, <<"self">>}],
+ children = []},
+ #xmlel{name = <<"id">>, attrs = [],
+ children = [{xmlcdata, iolist_to_binary(Link)}]},
+ #xmlel{name = <<"title">>, attrs = [],
+ children = [{xmlcdata, Title}]}
+ | Entries]}.
+
+service(Args, Domain, Collections) ->
+ #xmlel{name = <<"service">>,
+ attrs =
+ [{<<"xmlns">>, <<"http://www.w3.org/2007/app">>},
+ {<<"xmlns:atom">>, <<"http://www.w3.org/2005/Atom">>},
+ {<<"xmlns:app">>, <<"http://www.w3.org/2007/app">>}],
+ children =
+ [#xmlel{name = <<"workspace">>, attrs = [],
+ children =
+ [#xmlel{name = <<"atom:title">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<"Pubsub node Feed for ",
+ Domain/binary>>}]}
+ | lists:map(fun (#pubsub_node{nodeid =
+ {_Server, Id},
+ type = _Type}) ->
+ #xmlel{name = <<"collection">>,
+ attrs =
+ [{<<"href">>,
+ collection_uri(Args,
+ Domain,
+ Id)}],
+ children =
+ [#xmlel{name =
+ <<"atom:title">>,
+ attrs = [],
+ children =
+ [{xmlcdata,
+ Id}]}]}
+ end,
+ Collections)]}]}.
+
+error(#xmlel{name = <<"error">>, attrs = Attrs} =
+ Error) ->
+ Value =
+ jlib:binary_to_integer(xml:get_attr_s(<<"code">>,
+ Attrs)),
+ {Value, [{<<"Content-type">>, <<"application/xml">>}],
+ xml:element_to_binary(Error)};
+error(404) -> {404, [], <<"Not Found">>};
+error(403) -> {403, [], <<"Forbidden">>};
+error(500) -> {500, [], <<"Internal server error">>};
+error(401) ->
+ {401,
+ [{<<"WWW-Authenticate">>,
+ <<"basic realm=\"ejabberd\"">>}],
+ <<"Unauthorized">>};
+error(Code) -> {Code, [], <<"">>}.
+
+success(200) -> {200, [], <<"">>};
+success(Code) -> {Code, [], <<"">>}.
+
+backend(Domain) ->
+ Modules = gen_mod:loaded_modules(Domain),
+ case lists:member(mod_pubsub_odbc, Modules) of
+ true -> mod_pubsub_odbc;
+ _ -> mod_pubsub
+ end.
+uniqid(false) ->
+ {T1, T2, T3} = now(),
+ list_to_binary(io_lib:fwrite("~.16B~.16B~.16B",
+ [T1, T2, T3])).
+w3cdtf(Date) -> %1 Date = calendar:gregorian_seconds_to_datetime(GregSecs),
+ {{Y, Mo, D}, {H, Mi, S}} = Date,
+ [UDate | _] =
+ calendar:local_time_to_universal_time_dst(Date),
+ {DiffD, {DiffH, DiffMi, _}} =
+ calendar:time_difference(UDate, Date),
+ w3cdtf_diff(Y, Mo, D, H, Mi, S, DiffD, DiffH, DiffMi).
+
+w3cdtf_diff(Y, Mo, D, H, Mi, S, _DiffD, DiffH, DiffMi)
+ when DiffH < 12, DiffH /= 0 ->
+ <<(i2l(Y))/binary, "-", (add_zero(Mo))/binary, "-",
+ (add_zero(D))/binary, "T", (add_zero(H))/binary, ":",
+ (add_zero(Mi))/binary, ":", (add_zero(S))/binary, "+",
+ (add_zero(DiffH))/binary, ":",
+ (add_zero(DiffMi))/binary>>;
+w3cdtf_diff(Y, Mo, D, H, Mi, S, DiffD, DiffH, DiffMi)
+ when DiffH > 12, DiffD == 0 ->
+ <<(i2l(Y))/binary, "-", (add_zero(Mo))/binary, "-",
+ (add_zero(D))/binary, "T", (add_zero(H))/binary, ":",
+ (add_zero(Mi))/binary, ":", (add_zero(S))/binary, "+",
+ (add_zero(DiffH))/binary, ":",
+ (add_zero(DiffMi))/binary>>;
+w3cdtf_diff(Y, Mo, D, H, Mi, S, DiffD, DiffH, DiffMi)
+ when DiffH > 12, DiffD /= 0, DiffMi /= 0 ->
+ <<(i2l(Y))/binary, "-", (add_zero(Mo))/binary, "-",
+ (add_zero(D))/binary, "T", (add_zero(H))/binary, ":",
+ (add_zero(Mi))/binary, ":", (add_zero(S))/binary, "-",
+ (add_zero(23 - DiffH))/binary, ":",
+ (add_zero(60 - DiffMi))/binary>>;
+w3cdtf_diff(Y, Mo, D, H, Mi, S, DiffD, DiffH, DiffMi)
+ when DiffH > 12, DiffD /= 0, DiffMi == 0 ->
+ <<(i2l(Y))/binary, "-", (add_zero(Mo))/binary, "-",
+ (add_zero(D))/binary, "T", (add_zero(H))/binary, ":",
+ (add_zero(Mi))/binary, ":", (add_zero(S))/binary, "-",
+ (add_zero(24 - DiffH))/binary, ":",
+ (add_zero(DiffMi))/binary>>;
+w3cdtf_diff(Y, Mo, D, H, Mi, S, _DiffD, DiffH, _DiffMi)
+ when DiffH == 0 ->
+ <<(i2l(Y))/binary, "-", (add_zero(Mo))/binary, "-",
+ (add_zero(D))/binary, "T", (add_zero(H))/binary, ":",
+ (add_zero(Mi))/binary, ":", (add_zero(S))/binary, "Z">>.
-i2l(I) when is_integer(I) -> integer_to_list(I);
-i2l(L) when is_list(L) -> L.
+add_zero(I) when is_integer(I) -> add_zero(i2l(I));
+add_zero(<<A>>) -> <<$0, A>>;
+add_zero(L) when is_binary(L) -> L.
-b2l(B) when is_binary(B) -> binary_to_list(B);
-b2l(L) when is_list(L) -> L.
+i2l(I) when is_integer(I) ->
+ jlib:integer_to_binary(I).
-get_tag_attr_or_default(AttrName, Element, Default)->
- case xml:get_tag_attr_s(AttrName, Element) of
- "" -> Default;
- Val -> Val
- end.
+get_tag_attr_or_default(AttrName, Element, Default) ->
+ case xml:get_tag_attr_s(AttrName, Element) of
+ <<"">> -> Default;
+ Val -> Val
+ end.
diff --git a/src/web/simple_ws_check.erl b/src/web/simple_ws_check.erl
index 8ef160980..d897b1ecc 100644
--- a/src/web/simple_ws_check.erl
+++ b/src/web/simple_ws_check.erl
@@ -1,11 +1,20 @@
--module (simple_ws_check).
--export ([is_acceptable/6]).
+-module(simple_ws_check).
+
+-export([is_acceptable/6]).
+
-include("ejabberd.hrl").
-is_acceptable(["true"]=Path, Q, Origin, Protocol, IP, Headers)->
- ?INFO_MSG("Authorized Websocket ~p with: ~n Q = ~p~n Origin = ~p~n Protocol = ~p~n IP = ~p~n Headers = ~p~n",
- [Path, Q, Origin, Protocol, IP, Headers]),
+
+is_acceptable([<<"true">>] = Path, Q, Origin, Protocol,
+ IP, Headers) ->
+ ?INFO_MSG("Authorized Websocket ~p with: ~n Q = "
+ "~p~n Origin = ~p~n Protocol = ~p~n IP "
+ "= ~p~n Headers = ~p~n",
+ [Path, Q, Origin, Protocol, IP, Headers]),
true;
-is_acceptable(["false"]=Path, Q, Origin, Protocol, IP, Headers)->
- ?INFO_MSG("Failed Websocket ~p with: ~n Q = ~p~n Origin = ~p~n Protocol = ~p~n IP = ~p~n Headers = ~p~n",
- [Path, Q, Origin, Protocol, IP, Headers]),
- false. \ No newline at end of file
+is_acceptable([<<"false">>] = Path, Q, Origin, Protocol,
+ IP, Headers) ->
+ ?INFO_MSG("Failed Websocket ~p with: ~n Q = ~p~n "
+ "Origin = ~p~n Protocol = ~p~n IP = ~p~n "
+ "Headers = ~p~n",
+ [Path, Q, Origin, Protocol, IP, Headers]),
+ false.
diff --git a/src/web/websocket_test.erl b/src/web/websocket_test.erl
index b5491bc08..cbe65392a 100644
--- a/src/web/websocket_test.erl
+++ b/src/web/websocket_test.erl
@@ -1,19 +1,14 @@
--module (websocket_test).
+-module(websocket_test).
+
-export([start_link/1, loop/1]).
-% callback on received websockets data
start_link(Ws) ->
- Pid = spawn_link(?MODULE, loop, [Ws]),
- {ok, Pid}.
+ Pid = spawn_link(?MODULE, loop, [Ws]), {ok, Pid}.
loop(Ws) ->
- receive
- {browser, Data} ->
- Ws:send(["received '", Data, "'"]),
- loop(Ws);
- _Ignore ->
- loop(Ws)
- after 5000 ->
- Ws:send("pushing!"),
- loop(Ws)
- end.
+ receive
+ {browser, Data} ->
+ Ws:send([<<"received '">>, Data, <<"'">>]), loop(Ws);
+ _Ignore -> loop(Ws)
+ after 5000 -> Ws:send(<<"pushing!">>), loop(Ws)
+ end.
diff --git a/src/web/xmpp_json.erl b/src/web/xmpp_json.erl
index 6fae2ab08..258d573cc 100644
--- a/src/web/xmpp_json.erl
+++ b/src/web/xmpp_json.erl
@@ -1,8 +1,8 @@
%%%----------------------------------------------------------------------
%%% File : xmpp_json.erl
%%% Author : Eric Cestari <ecestari@process-one.net>
-%%% Purpose : Converts {xmlelement,Name, A, Sub} to/from JSON as per protoxep
-%%% Created : 09-20-2010
+%%% Purpose : Converts {xmlelement,Name, A, Sub} to/from JSON as per protoxep
+%%% Created : 09-20-2010
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2010 ProcessOne
@@ -22,341 +22,396 @@
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
%%% 02111-1307 USA
--module (xmpp_json).
+-module(xmpp_json).
-export([to_json/1, from_json/1]).
+-include("jlib.hrl").
-
%%% FROM JSON TO XML
-from_json({struct, [{<<"stream">>, _Attr}]=Elems}) ->
- parse_start(Elems);
-
-from_json({struct, Elems}) ->
- {xmlstreamelement, hd(from_json2({struct, Elems}))}.
-
-from_json2({struct, Elems}) ->
- lists:map(fun parse_json/1, Elems).
-
-parse_start([{BinName, {struct, JAttrs}}]) ->
- Name = binary_to_list(BinName),
- {FullName, Attrs} = lists:foldl(
- fun({<<"xml">>, {struct, XML}}, {N, Attrs}) ->
- XmlAttrs = parse_json_special_attrs("xml", XML),
- {N, lists:merge(Attrs, XmlAttrs)};
- ({<<"xmlns">>, {struct, XMLNS}}, {N, Attrs}) ->
- XmlNsAttrs = parse_json_special_attrs("xmlns", XMLNS),
- {N, lists:merge(Attrs, XmlNsAttrs)};
- ({<<"$$">>, BaseNS}, {N, Attrs})->
- {binary_to_list(BaseNS)++":"++N, Attrs};
- ({Key, Value}, {N, Attrs})->
- {N, [{ib2tol(Key), ib2tol(Value)}|Attrs]}
- end, {Name, []}, JAttrs),
- {xmlstreamstart, FullName, Attrs}.
-
-parse_json({Name, CData}) when is_binary(CData)->
- {xmlelement, binary_to_list(Name), [], [{xmlcdata, CData}]};
-
-parse_json({Name, CDatas}) when is_list(CDatas)->
- lists:map(fun(CData)->
- {xmlelement, binary_to_list(Name), [], [{xmlcdata, CData}]}
- end, CDatas);
-
-parse_json({BinName, {struct, JAttrs}}) ->
- Name = binary_to_list(BinName),
- {FullName, Attrs, SubEls} = lists:foldl(
- fun({<<"$">>, Cdata}, {N, Attrs, _SubEls}) when is_binary(Cdata)->
- {N, Attrs, [{xmlcdata, Cdata}]};
- ({<<"$">>, {struct, Elems}}, {N, Attrs, _SubEls}) ->
- SE = lists:map(fun parse_json/1, Elems),
- {N, Attrs, lists:flatten(SE)}; % due to 4.2.3.3
- ({<<"xml">>, {struct, XML}}, {N, Attrs, SubEls}) ->
- XmlAttrs = parse_json_special_attrs("xml", XML),
- {N, lists:merge(Attrs, XmlAttrs), SubEls};
- ({<<"xmlns">>, {struct, XMLNS}}, {N, Attrs, SubEls}) ->
- XmlNsAttrs = parse_json_special_attrs("xmlns", XMLNS),
- {N, lists:merge(Attrs, XmlNsAttrs), SubEls};
- ({Key, {struct, []}}, {N, Attrs, SubEls})->
- {N, Attrs, [{xmlelement, ib2tol(Key), [], []}|SubEls]};
- ({Key, Value}, {N, Attrs, SubEls})->
- {N, [{binary_to_list(Key), ib2tol(Value)}|Attrs], SubEls}
- end, {Name, [], []}, JAttrs),
- {xmlelement, FullName, Attrs, SubEls}.
-
-parse_json_special_attrs(Prefix, XMLNS)->
- lists:reverse(lists:map(
- fun({<<"$">>, Value})->
- {Prefix, ib2tol(Value)};
- ({<<"@",NS/binary>>, Value})->
- {Prefix ++ ":"++binary_to_list(NS), ib2tol(Value)}
- end, XMLNS)).
-
-%%% FROM XML TO JSON
-to_json({xmlstreamelement, XMLElement})->
- to_json(XMLElement);
-to_json({xmlelement, _Name, [], []})->
- {struct, []};
-to_json({xmlelement, Name, [], [{xmlcdata, Cdata}]})->
- {SName, JsonAttrs2} = parse_namespace(Name, []),
- {struct, [{SName, Cdata}|JsonAttrs2]};
-to_json({xmlstreamstart, Name, Attrs})->
- JsonAttrs = parse_attrs(Attrs),
- {SName, Members2} = parse_namespace(Name, JsonAttrs),
- {struct, [{SName, {struct, Members2}}]};
-to_json({xmlelement, Name, Attrs, SubEls})->
- JsonAttrs = parse_attrs(Attrs),
- Members = case parse_subels(SubEls) of
- [] ->
- JsonAttrs;
- [Elem] ->
- [{<<"$">>,Elem}|JsonAttrs];
- Elems ->
- [{<<"$">>,Elems}|JsonAttrs]
- end,
- {SName, Members2} = parse_namespace(Name, Members),
- {struct, [{SName, {struct, Members2}}]}.
-
-parse_namespace(Name, AttrsList)->
- {l2b(Name), AttrsList}.
-
-parse_subels([{xmlcdata, Cdata}])->
- l2b(Cdata);
-parse_subels([])->
- [];
-parse_subels(SubEls)->
- {struct, lists:reverse(lists:foldl(
- fun({xmlelement, SName, [], [{xmlcdata, UCdata}]}, Acc)->
- Cdata = l2b(UCdata),
- Name = l2b(SName),
- case lists:keyfind(Name, 1, Acc) of
- {Name, PrevCdata} when is_binary(PrevCdata) ->
- Acc1 = lists:keydelete(Name, 1, Acc),
- [{Name,[PrevCdata, Cdata]} | Acc1];
- {Name, CDatas} when is_list(CDatas) ->
- Acc1 = lists:keydelete(Name, 1, Acc),
- [{Name,lists:append(CDatas, [Cdata])} | Acc1];
- _ ->
- [{Name, Cdata}| Acc]
- end;
- ({xmlelement, SName, _, _} = Elem, Acc) ->
- E = case to_json(Elem) of %TODO There could be a better way to iterate
- {struct, [{_, ToKeep}]} -> ToKeep;
- {struct, []} = Empty -> Empty
- end,
- [{l2b(SName), E}|Acc];
- ({xmlcdata,<<"\n">>}, Acc) ->
- Acc
- end,[], SubEls))}.
-
-
-parse_attrs(XmlAttrs)->
- {Normal, XMLNS} = lists:foldl(
- fun({"xmlns", NS}, {Attrs, XMLNS}) ->
- {Attrs,[{<<"$">>, l2b(NS)}| XMLNS]};
- ({"xmlns:" ++ Short, NS}, {Attrs, XMLNS})->
- AttrName = iolist_to_binary([<<"@">>,l2b(Short)]),
- {Attrs,[{AttrName, list_to_binary(NS)}| XMLNS]};
- ({"xml:" ++ Short, Val}, {Attrs, XMLNS})->
- % TODO currently tolerates only one xml:* attr per element
- AttrName = iolist_to_binary([<<"@">>,l2b(Short)]),
- {[{<<"xml">>,{struct, [{AttrName, l2b(Val)}]}}|Attrs], XMLNS};
- ({K, V}, {Attrs, XMLNS})->
- {[{l2b(K), l2b(V)}|Attrs], XMLNS}
- end,{[], []}, XmlAttrs),
-
- case XMLNS of
- [{<<"$">>, NS}]->
- [{<<"xmlns">>, NS}|Normal];
- []->
- Normal;
- _ ->
- [{<<"xmlns">>,{struct, XMLNS} }| Normal]
- end.
-
-l2b(List) when is_list(List) -> list_to_binary(List);
-l2b(Bin) when is_binary(Bin) -> Bin.
-
-ib2tol(Bin) when is_binary(Bin) -> binary_to_list(Bin );
-ib2tol(Integer) when is_integer(Integer) -> integer_to_list(Integer);
-ib2tol(List) when is_list(List) -> List.
+from_json({[{<<"stream">>, _Attr}] = Elems}) ->
+ parse_start(Elems);
+from_json({Elems}) ->
+ {xmlstreamelement, hd(from_json2({Elems}))}.
+
+from_json2({Elems}) ->
+ lists:map(fun parse_json/1, Elems).
+
+parse_start([{BinName, {JAttrs}}]) ->
+ Name = (BinName),
+ {FullName, Attrs} = lists:foldl(fun ({<<"xml">>,
+ {XML}},
+ {N, Attrs}) ->
+ XmlAttrs =
+ parse_json_special_attrs(<<"xml">>,
+ XML),
+ {N, lists:merge(Attrs, XmlAttrs)};
+ ({<<"xmlns">>, {XMLNS}},
+ {N, Attrs}) ->
+ XmlNsAttrs =
+ parse_json_special_attrs(<<"xmlns">>,
+ XMLNS),
+ {N, lists:merge(Attrs, XmlNsAttrs)};
+ ({<<"$$">>, BaseNS}, {N, Attrs}) ->
+ {<<((BaseNS))/binary, ":",
+ N/binary>>,
+ Attrs};
+ ({Key, Value}, {N, Attrs}) ->
+ {N,
+ [{ib2tol(Key), ib2tol(Value)}
+ | Attrs]}
+ end,
+ {Name, []}, JAttrs),
+ {xmlstreamstart, FullName, Attrs}.
+
+parse_json({Name, CData}) when is_binary(CData) ->
+ #xmlel{name = (Name), attrs = [],
+ children = [{xmlcdata, CData}]};
+parse_json({Name, CDatas}) when is_list(CDatas) ->
+ lists:map(fun (CData) ->
+ #xmlel{name = (Name), attrs = [],
+ children = [{xmlcdata, CData}]}
+ end,
+ CDatas);
+parse_json({BinName, {JAttrs}}) ->
+ Name = (BinName),
+ {FullName, Attrs, SubEls} = lists:foldl(fun ({<<"$">>,
+ Cdata},
+ {N, Attrs, _SubEls})
+ when is_binary(Cdata) ->
+ {N, Attrs,
+ [{xmlcdata, Cdata}]};
+ ({<<"$">>, {Elems}},
+ {N, Attrs, _SubEls}) ->
+ SE =
+ lists:map(fun parse_json/1,
+ Elems),
+ {N, Attrs,
+ lists:flatten(SE)};
+ ({<<"xml">>, {XML}},
+ {N, Attrs, SubEls}) ->
+ XmlAttrs =
+ parse_json_special_attrs(<<"xml">>,
+ XML),
+ {N,
+ lists:merge(Attrs,
+ XmlAttrs),
+ SubEls};
+ ({<<"xmlns">>, {XMLNS}},
+ {N, Attrs, SubEls}) ->
+ XmlNsAttrs =
+ parse_json_special_attrs(<<"xmlns">>,
+ XMLNS),
+ {N,
+ lists:merge(Attrs,
+ XmlNsAttrs),
+ SubEls};
+ ({Key, {[]}},
+ {N, Attrs, SubEls}) ->
+ {N, Attrs,
+ [#xmlel{name = ib2tol(Key),
+ attrs = [],
+ children = []}
+ | SubEls]};
+ ({Key, Value},
+ {N, Attrs, SubEls}) ->
+ {N,
+ [{(Key), ib2tol(Value)}
+ | Attrs],
+ SubEls}
+ end,
+ {Name, [], []}, JAttrs),
+ #xmlel{name = FullName, attrs = Attrs,
+ children = SubEls}.
+
+parse_json_special_attrs(Prefix, XMLNS) ->
+ lists:reverse(lists:map(fun ({<<"$">>, Value}) ->
+ {Prefix, ib2tol(Value)};
+ ({<<"@", NS/binary>>, Value}) ->
+ {<<Prefix/binary, ":", ((NS))/binary>>,
+ ib2tol(Value)}
+ end,
+ XMLNS)).
+
+to_json({xmlstreamelement, XMLElement}) ->
+ to_json(XMLElement);
+to_json(#xmlel{attrs = [], children = []}) ->
+ {[]};
+to_json(#xmlel{name = Name, attrs = [],
+ children = [{xmlcdata, Cdata}]}) ->
+ {SName, JsonAttrs2} = parse_namespace(Name, []),
+ {[{SName, Cdata} | JsonAttrs2]};
+to_json({xmlstreamstart, Name, Attrs}) ->
+ JsonAttrs = parse_attrs(Attrs),
+ {SName, Members2} = parse_namespace(Name, JsonAttrs),
+ {[{SName, {Members2}}]};
+to_json(#xmlel{name = Name, attrs = Attrs,
+ children = SubEls}) ->
+ JsonAttrs = parse_attrs(Attrs),
+ Members = case parse_subels(SubEls) of
+ [] -> JsonAttrs;
+ Elems -> [{<<"$">>, Elems} | JsonAttrs]
+ end,
+ {SName, Members2} = parse_namespace(Name, Members),
+ {[{SName, {Members2}}]}.
+
+parse_namespace(Name, AttrsList) ->
+ {Name, AttrsList}.
+
+parse_subels([{xmlcdata, Cdata}]) -> Cdata;
+parse_subels([]) -> [];
+parse_subels(SubEls) ->
+ {lists:reverse(lists:foldl(fun (#xmlel{name = SName,
+ attrs = [],
+ children = [{xmlcdata, UCdata}]},
+ Acc) ->
+ Cdata = UCdata,
+ Name = SName,
+ case lists:keyfind(Name, 1, Acc) of
+ {Name, PrevCdata}
+ when is_binary(PrevCdata) ->
+ Acc1 = lists:keydelete(Name, 1,
+ Acc),
+ [{Name, [PrevCdata, Cdata]}
+ | Acc1];
+ {Name, CDatas}
+ when is_list(CDatas) ->
+ Acc1 = lists:keydelete(Name, 1,
+ Acc),
+ [{Name,
+ lists:append(CDatas, [Cdata])}
+ | Acc1];
+ _ -> [{Name, Cdata} | Acc]
+ end;
+ (#xmlel{name = SName} = Elem, Acc) ->
+ E = case to_json(Elem) of
+ {[{_, ToKeep}]} -> ToKeep;
+ {[]} = Empty -> Empty
+ end,
+ [{SName, E} | Acc];
+ ({xmlcdata, <<"\n">>}, Acc) -> Acc
+ end,
+ [], SubEls))}.
+
+parse_attrs(XmlAttrs) ->
+ {Normal, XMLNS} = lists:foldl(fun ({<<"xmlns">>, NS},
+ {Attrs, XMLNS}) ->
+ {Attrs, [{<<"$">>, NS} | XMLNS]};
+ ({<<"xmlns:", Short/binary>>, NS},
+ {Attrs, XMLNS}) ->
+ AttrName = <<$@, Short/binary>>,
+ {Attrs,
+ [{AttrName, NS}
+ | XMLNS]};
+ ({<<"xml:", Short/binary>>, Val},
+ {Attrs, XMLNS}) ->
+ AttrName = <<$@, Short/binary>>,
+ {[{<<"xml">>,
+ {[{AttrName, Val}]}}
+ | Attrs],
+ XMLNS};
+ ({K, V}, {Attrs, XMLNS}) ->
+ {[{K, V} | Attrs], XMLNS}
+ end,
+ {[], []}, XmlAttrs),
+ case XMLNS of
+ [{<<"$">>, NS}] -> [{<<"xmlns">>, NS} | Normal];
+ [] -> Normal;
+ _ -> [{<<"xmlns">>, {XMLNS}} | Normal]
+ end.
+
+ib2tol(Bin) when is_binary(Bin) -> Bin;
+ib2tol(Integer) when is_integer(Integer) ->
+ jlib:integer_to_binary(Integer).
%%
%% Tests
%% erlc -DTEST web/xmpp_json.erl && erl -pa web/ -run xmpp_json test -run init stop -noshell
-include_lib("eunit/include/eunit.hrl").
+
-ifdef(TEST).
-% 4.2.3.1 Tag with text value
-to_text_value_test()->
- In = {xmlstreamelement, {xmlelement, "tag", [], [{xmlcdata, <<"txt-value">>}]}},
- Out = {struct, [{<<"tag">>, <<"txt-value">>}]},
- ?assertEqual(Out, to_json(In)),
- ?assertEqual(In, from_json(Out)).
-
-% 4.2.3.2 Tag with recursive tags
-to_tag_with_recursive_tags_test()->
- In = {xmlstreamelement, {xmlelement, "tag", [],
- [{xmlelement,"tag2",[], [{xmlcdata, <<"txt-value">>}]},
- {xmlelement,"tag3",[], [
- {xmlelement,"tag4",[], [{xmlcdata, <<"txt2-value">>}]}]}]}},
- Out = {struct, [{<<"tag">>,
- {struct, [{<<"$">>,
- {struct, [
- {<<"tag2">>,<<"txt-value">>},
- {<<"tag3">>,{struct, [{<<"$">>,{struct, [{<<"tag4">>,<<"txt2-value">>}]}}]}}
- ]}
- }]}
- }]
- },
- %io:format("~n~p", [list_to_binary(mochijson2:encode(to_json(In)))]),
- io:format("~n~p", [from_json(Out)]),
- io:format("~n~p", [to_json(In)]),
- ?assertEqual(Out, to_json(In)),
- ?assertEqual(In, from_json(Out)).
-
-% 4.2.3.3 Multiple text value tags as array
-multiple_text_value_tags_as_array_test()->
- In = {xmlstreamelement, {xmlelement, "tag", [], [
- {xmlelement,"tag2",[], [
- {xmlcdata, <<"txt-value">>}]},
- {xmlelement,"tag2",[], [
- {xmlcdata, <<"txt-value2">>}]}]}},
- Out = {struct, [{<<"tag">>,
- {struct, [{<<"$">>,
- {struct, [{<<"tag2">>,
- [<<"txt-value">>, <<"txt-value2">>]}]}
- }]}
- }]
- },
- io:format("~p~n", [to_json(In)]),
- io:format("~p~n", [from_json(Out)]),
- ?assertEqual(Out, to_json(In)),
- ?assertEqual(In, from_json(Out)).
-
-% 4.2.3.4 Tag with attribute, no value
+to_text_value_test() ->
+ In = {xmlstreamelement,
+ #xmlel{name = <<"tag">>, attrs = [],
+ children = [{xmlcdata, <<"txt-value">>}]}},
+ Out = {[{<<"tag">>, <<"txt-value">>}]},
+ ?assertEqual(Out, (to_json(In))),
+ ?assertEqual(In, (from_json(Out))).
+
+to_tag_with_recursive_tags_test() ->
+ In = {xmlstreamelement,
+ #xmlel{name = <<"tag">>, attrs = [],
+ children =
+ [#xmlel{name = <<"tag2">>, attrs = [],
+ children = [{xmlcdata, <<"txt-value">>}]},
+ #xmlel{name = <<"tag3">>, attrs = [],
+ children =
+ [#xmlel{name = <<"tag4">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<"txt2-value">>}]}]}]}},
+ Out = {[{<<"tag">>,
+ {[{<<"$">>,
+ {[{<<"tag2">>, <<"txt-value">>},
+ {<<"tag3">>,
+ {[{<<"$">>,
+ {[{<<"tag4">>, <<"txt2-value">>}]}}]}}]}}]}}]},
+ io:format("~n~p", [from_json(Out)]),
+ io:format("~n~p", [to_json(In)]),
+ ?assertEqual(Out, (to_json(In))),
+ ?assertEqual(In, (from_json(Out))).
+
+multiple_text_value_tags_as_array_test() ->
+ In = {xmlstreamelement,
+ #xmlel{name = <<"tag">>, attrs = [],
+ children =
+ [#xmlel{name = <<"tag2">>, attrs = [],
+ children = [{xmlcdata, <<"txt-value">>}]},
+ #xmlel{name = <<"tag2">>, attrs = [],
+ children = [{xmlcdata, <<"txt-value2">>}]}]}},
+ Out = {[{<<"tag">>,
+ {[{<<"$">>,
+ {[{<<"tag2">>,
+ [<<"txt-value">>, <<"txt-value2">>]}]}}]}}]},
+ io:format("~p~n", [to_json(In)]),
+ io:format("~p~n", [from_json(Out)]),
+ ?assertEqual(Out, (to_json(In))),
+ ?assertEqual(In, (from_json(Out))).
+
tag_attr_no_value_test() ->
- In = {xmlstreamelement, {xmlelement, "tag", [{"attr", "attr-value"}], []}},
- Out = {struct, [{<<"tag">>, {struct, [
- {<<"attr">>,<<"attr-value">>}
- ]}}]},
- io:format("~p", [list_to_binary(mochijson2:encode(to_json(In)))]),
- io:format("~p", [from_json(Out)]),
- ?assertEqual(Out, to_json(In)),
- ?assertEqual(In, from_json(Out)).
-
+ In = {xmlstreamelement,
+ #xmlel{name = <<"tag">>,
+ attrs = [{<<"attr">>, <<"attr-value">>}],
+ children = []}},
+ Out = {[{<<"tag">>,
+ {[{<<"attr">>, <<"attr-value">>}]}}]},
+ io:format("~s", [jiffy:encode(to_json(In))]),
+ io:format("~p", [from_json(Out)]),
+ ?assertEqual(Out, (to_json(In))),
+ ?assertEqual(In, (from_json(Out))).
+
% 4.2.3.5 Tag with multiple attributes as array, no value
% Not wellformed XML.
% 4.2.3.6 Tags as array with unique attributes, no value
+tag_with_namespace_no_value_test() ->
+ In = {xmlstreamelement,
+ #xmlel{name = <<"tag">>,
+ attrs = [{<<"xmlns:ns">>, <<"ns-value">>}],
+ children = []}},
+ Out = {[{<<"tag">>,
+ {[{<<"xmlns">>,
+ {[{<<"@ns">>, <<"ns-value">>}]}}]}}]},
+ io:format("~s", [jiffy:encode(to_json(In))]),
+ ?assertEqual(Out, (to_json(In))),
+ ?assertEqual(In, (from_json(Out))).
+
+two_namespaces_tag_no_value_test() ->
+ In = {xmlstreamelement,
+ #xmlel{name = <<"tag">>,
+ attrs =
+ [{<<"xmlns:ns">>, <<"ns-value">>},
+ {<<"xmlns">>, <<"root-value">>}],
+ children = []}},
+ Out = {[{<<"tag">>,
+ {[{<<"xmlns">>,
+ {[{<<"$">>, <<"root-value">>},
+ {<<"@ns">>, <<"ns-value">>}]}}]}}]},
+ io:format("~s", [jiffy:encode(to_json(In))]),
+ ?assertEqual(Out, (to_json(In))),
+ ?assertEqual(In, (from_json(Out))).
+
+namespaced_tag_no_value_test() ->
+ In = {xmlstreamelement,
+ #xmlel{name = <<"ns:tag">>,
+ attrs = [{<<"attr">>, <<"attr-value">>}],
+ children = []}},
+ Out = {[{<<"ns:tag">>,
+ {[{<<"attr">>, <<"attr-value">>}]}}]},
+ io:format("~s", [jiffy:encode(to_json(In))]),
+ ?assertEqual(Out, (to_json(In))),
+ ?assertEqual(In, (from_json(Out))).
-% 4.2.3.7 Tag with namespace attribute, no value
-tag_with_namespace_no_value_test()->
- In = {xmlstreamelement, {xmlelement, "tag", [{"xmlns:ns", "ns-value"}], []}},
- Out = {struct, [{<<"tag">>, {struct, [
- {<<"xmlns">>,{struct, [{<<"@ns">>, <<"ns-value">>}]}}
- ]}}]},
- io:format("~p", [list_to_binary(mochijson2:encode(to_json(In)))]),
- ?assertEqual(Out, to_json(In)),
- ?assertEqual(In, from_json(Out)).
-
-
-% 4.2.3.8 Tag with many attributes to namespace, no value
-two_namespaces_tag_no_value_test()->
- In = {xmlstreamelement,{xmlelement, "tag", [{"xmlns:ns", "ns-value"},
- {"xmlns", "root-value"}], []}},
- Out = {struct, [{<<"tag">>, {struct, [
- {<<"xmlns">>,{struct, [
- {<<"$">>, <<"root-value">>},
- {<<"@ns">>, <<"ns-value">>}]}}
- ]}}]},
- io:format("~p", [list_to_binary(mochijson2:encode(to_json(In)))]),
- ?assertEqual(Out, to_json(In)),
- ?assertEqual(In, from_json(Out)).
-
-% 4.2.3.9 Tag with namespace attribute, no value
-% Removed namespace handling. More complex on both sides.
-namespaced_tag_no_value_test()->
- In = {xmlstreamelement,{xmlelement, "ns:tag", [{"attr", "attr-value"}], []}},
- Out = {struct, [{<<"ns:tag">>, {struct, [
- {<<"attr">>,<<"attr-value">>}
- ]}}]},
- io:format("~p", [list_to_binary(mochijson2:encode(to_json(In)))]),
- ?assertEqual(Out, to_json(In)),
- ?assertEqual(In, from_json(Out)).
-
-% 4.2.3.10 Tag with attribute and text value
-tag_with_attribute_and_value_test()->
- In = {xmlstreamelement,{xmlelement, "tag", [{"attr", "attr-value"}],
- [{xmlcdata, <<"txt-value">>}]}},
- Out = {struct, [{<<"tag">>, {struct, [
- {<<"$">>, <<"txt-value">>},
- {<<"attr">>,<<"attr-value">>}
- ]}}]},
- %io:format("~p", [list_to_binary(mochijson2:encode(to_json(In)))]),
- ?assertEqual(Out, to_json(In)),
- ?assertEqual(In, from_json(Out)).
-
-% 4.2.3.11 Namespace tag with attribute and text value
-% Removed namespace handling. More complex on both sides
-namespaced_tag_with_value_test()->
- In = {xmlstreamelement,{xmlelement, "ns:tag", [{"attr", "attr-value"}], [{xmlcdata, <<"txt-value">>}]}},
- Out = {struct, [{<<"ns:tag">>, {struct, [
- {<<"$">>,<<"txt-value">>},
- {<<"attr">>,<<"attr-value">>}
- ]}}]},
- io:format("~p", [list_to_binary(mochijson2:encode(to_json(In)))]),
- ?assertEqual(Out, to_json(In)),
- ?assertEqual(In, from_json(Out)).
-
-xml_lang_attr_test()->
- In = {xmlstreamelement,{xmlelement, "tag", [{"xml:lang", "en"}], []}},
- Out = {struct, [{<<"tag">>, {struct, [
- {<<"xml">>,{struct,[{<<"@lang">>,<<"en">>}]}}
- ]}}]},
- io:format("~p", [list_to_binary(mochijson2:encode(to_json(In)))]),
- ?assertEqual(Out, to_json(In)),
- ?assertEqual(In, from_json(Out)).
-
-xmlns_tag_with_value_test()->
- Out = {struct,[{<<"response">>,
- {struct,[{<<"$">>,<<"dXNlcm5hbWU9I">>},
- {<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-sasl">>}]}}
- ]},
- Out2 = {struct,[{<<"response">>,
- {struct,[{<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-sasl">>},
- {<<"$">>,<<"dXNlcm5hbWU9I">>}
- ]}}
- ]},
- In = {xmlstreamelement,{xmlelement,"response",
- [{"xmlns","urn:ietf:params:xml:ns:xmpp-sasl"}],
- [{xmlcdata, <<"dXNlcm5hbWU9I">>}]}},
- io:format("~p", [list_to_binary(mochijson2:encode(to_json(In)))]),
- ?assertEqual(Out, to_json(In)),
- ?assertEqual(In, from_json(Out)),
- ?assertEqual(In, from_json(Out2)).
-
-no_attr_no_value_test()->
- In = {xmlstreamelement, {xmlelement,"failure",
- [{"xmlns","urn:ietf:params:xml:ns:xmpp-sasl"}],
- [{xmlelement,"not-authorized",[],[]}]}},
- Out = {struct, [{<<"failure">>,{struct, [
- {<<"$">>, {struct, [{<<"not-authorized">>, {struct, []}}]}},
- {<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-sasl">>}
- ]}}]},
- io:format("~p", [list_to_binary(mochijson2:encode(to_json(In)))]),
+tag_with_attribute_and_value_test() ->
+ In = {xmlstreamelement,
+ #xmlel{name = <<"tag">>,
+ attrs = [{<<"attr">>, <<"attr-value">>}],
+ children = [{xmlcdata, <<"txt-value">>}]}},
+ Out = {[{<<"tag">>,
+ {[{<<"$">>, <<"txt-value">>},
+ {<<"attr">>, <<"attr-value">>}]}}]},
+ ?assertEqual(Out, (to_json(In))),
+ ?assertEqual(In, (from_json(Out))).
+
+namespaced_tag_with_value_test() ->
+ In = {xmlstreamelement,
+ #xmlel{name = <<"ns:tag">>,
+ attrs = [{<<"attr">>, <<"attr-value">>}],
+ children = [{xmlcdata, <<"txt-value">>}]}},
+ Out = {[{<<"ns:tag">>,
+ {[{<<"$">>, <<"txt-value">>},
+ {<<"attr">>, <<"attr-value">>}]}}]},
+ io:format("~s", [jiffy:encode(to_json(In))]),
+ ?assertEqual(Out, (to_json(In))),
+ ?assertEqual(In, (from_json(Out))).
+
+xml_lang_attr_test() ->
+ In = {xmlstreamelement,
+ #xmlel{name = <<"tag">>,
+ attrs = [{<<"xml:lang">>, <<"en">>}], children = []}},
+ Out = {[{<<"tag">>,
+ {[{<<"xml">>, {[{<<"@lang">>, <<"en">>}]}}]}}]},
+ io:format("~s", [jiffy:encode(to_json(In))]),
+ ?assertEqual(Out, (to_json(In))),
+ ?assertEqual(In, (from_json(Out))).
+
+xmlns_tag_with_value_test() ->
+ Out = {[{<<"response">>,
+ {[{<<"$">>, <<"dXNlcm5hbWU9I">>},
+ {<<"xmlns">>,
+ <<"urn:ietf:params:xml:ns:xmpp-sasl">>}]}}]},
+ Out2 = {[{<<"response">>,
+ {[{<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-sasl">>},
+ {<<"$">>, <<"dXNlcm5hbWU9I">>}]}}]},
+ In = {xmlstreamelement,
+ #xmlel{name = <<"response">>,
+ attrs =
+ [{<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-sasl">>}],
+ children = [{xmlcdata, <<"dXNlcm5hbWU9I">>}]}},
+ io:format("~s", [jiffy:encode(to_json(In))]),
+ ?assertEqual(Out, (to_json(In))),
+ ?assertEqual(In, (from_json(Out))),
+ ?assertEqual(In, (from_json(Out2))).
+
+no_attr_no_value_test() ->
+ In = {xmlstreamelement,
+ #xmlel{name = <<"failure">>,
+ attrs =
+ [{<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-sasl">>}],
+ children =
+ [#xmlel{name = <<"not-authorized">>, attrs = [],
+ children = []}]}},
+ Out = {[{<<"failure">>,
+ {[{<<"$">>,
+ {[{<<"not-authorized">>, {[]}}]}},
+ {<<"xmlns">>,
+ <<"urn:ietf:params:xml:ns:xmpp-sasl">>}]}}]},
+ io:format("~s", [jiffy:encode(to_json(In))]),
io:format("~p~n", [to_json(In)]),
io:format("~p~n", [from_json(Out)]),
- ?assertEqual(Out, to_json(In)),
- ?assertEqual(In, from_json(Out)).
-
-xmlstream_test()->
- In = {xmlstreamstart, "stream", [{"xml:lang", "en"}]},
- Out = {struct, [{<<"stream">>, {struct, [
- {<<"xml">>,{struct,[{<<"@lang">>,<<"en">>}]}}
- ]}}]},
- io:format("~p", [list_to_binary(mochijson2:encode(to_json(In)))]),
- ?assertEqual(Out, to_json(In)),
- ?assertEqual(In, from_json(Out)).
--endif. \ No newline at end of file
+ ?assertEqual(Out, (to_json(In))),
+ ?assertEqual(In, (from_json(Out))).
+
+xmlstream_test() ->
+ In = {xmlstreamstart, <<"stream">>,
+ [{<<"xml:lang">>, <<"en">>}]},
+ Out = {[{<<"stream">>,
+ {[{<<"xml">>, {[{<<"@lang">>, <<"en">>}]}}]}}]},
+ io:format("~s", [jiffy:encode(to_json(In))]),
+ ?assertEqual(Out, (to_json(In))),
+ ?assertEqual(In, (from_json(Out))).
+
+-endif.