mirror of
https://github.com/processone/ejabberd.git
synced 2024-12-24 17:29:28 +01:00
Initial version based on XML generator
This commit is contained in:
parent
749033598d
commit
9a8e197d7e
@ -110,7 +110,7 @@ edoc:
|
||||
|
||||
spec:
|
||||
$(ERL) -noinput +B -pa ebin -pa deps/*/ebin -eval \
|
||||
'case fxml_gen:compile("tools/xmpp_codec.spec") of ok -> halt(0); _ -> halt(1) end.'
|
||||
'case fxml_gen:compile("tools/xmpp_codec.spec", [{add_type_specs, xmpp_element}, {erl_dir, "src"}, {hrl_dir, "include"}]) of ok -> halt(0); _ -> halt(1) end.'
|
||||
|
||||
JOIN_PATHS=$(if $(wordlist 2,1000,$(1)),$(firstword $(1))/$(call JOIN_PATHS,$(wordlist 2,1000,$(1))),$(1))
|
||||
|
||||
|
17
include/jid.hrl
Normal file
17
include/jid.hrl
Normal file
@ -0,0 +1,17 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% @copyright (C) 2016, Evgeny Khramtsov
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 10 Jul 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%-------------------------------------------------------------------
|
||||
-record(jid, {user = <<"">> :: binary(),
|
||||
server = <<"">> :: binary(),
|
||||
resource = <<"">> :: binary(),
|
||||
luser = <<"">> :: binary(),
|
||||
lserver = <<"">> :: binary(),
|
||||
lresource = <<"">> :: binary()}).
|
||||
|
||||
-type(jid() :: #jid{}).
|
||||
-type(ljid() :: {binary(), binary(), binary()}).
|
@ -22,9 +22,11 @@
|
||||
default = none :: none | binary(),
|
||||
lists = [] :: [{binary(), [listitem()]}]}).
|
||||
|
||||
-record(listitem, {type = none :: none | jid | group | subscription,
|
||||
value = none :: none | both | from | to | ljid() | binary(),
|
||||
action = allow :: allow | deny,
|
||||
-type privacy() :: #privacy{}.
|
||||
|
||||
-record(listitem, {type = none :: listitem_type(),
|
||||
value = none :: listitem_value(),
|
||||
action = allow :: listitem_action(),
|
||||
order = 0 :: integer(),
|
||||
match_all = false :: boolean(),
|
||||
match_iq = false :: boolean(),
|
||||
@ -33,6 +35,9 @@
|
||||
match_presence_out = false :: boolean()}).
|
||||
|
||||
-type listitem() :: #listitem{}.
|
||||
-type listitem_type() :: none | jid | group | subscription.
|
||||
-type listitem_value() :: none | both | from | to | ljid() | binary().
|
||||
-type listitem_action() :: allow | deny.
|
||||
|
||||
-record(userlist, {name = none :: none | binary(),
|
||||
list = [] :: [listitem()],
|
||||
|
29
include/xmpp.hrl
Normal file
29
include/xmpp.hrl
Normal file
@ -0,0 +1,29 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% @copyright (C) 2015, Evgeny Khramtsov
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 10 Dec 2015 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%-------------------------------------------------------------------
|
||||
-include("ns.hrl").
|
||||
-include("jid.hrl").
|
||||
-include("xmpp_codec.hrl").
|
||||
-ifdef(NO_EXT_LIB).
|
||||
-include("fxml.hrl").
|
||||
-else.
|
||||
-include_lib("fast_xml/include/fxml.hrl").
|
||||
-endif.
|
||||
|
||||
-type iq_type() :: get | set | result | error.
|
||||
-type message_type() :: chat | error | groupchat | headline | normal.
|
||||
-type presence_type() :: available | error | probe | subscribe |
|
||||
subscribed | unavailable | unsubscribe |
|
||||
unsubscribed.
|
||||
|
||||
-type stanza() :: iq() | presence() | message().
|
||||
|
||||
-define(is_stanza(Pkt),
|
||||
(is_record(Pkt, iq) or
|
||||
is_record(Pkt, message) or
|
||||
is_record(Pkt, presence))).
|
@ -1,179 +1,249 @@
|
||||
%% Created automatically by XML generator (fxml_gen.erl)
|
||||
%% Source: xmpp_codec.spec
|
||||
|
||||
-record(vcard_xupdate, {us :: {binary(), binary()},
|
||||
hash :: binary()}).
|
||||
-type vcard_xupdate() :: #vcard_xupdate{}.
|
||||
|
||||
-record(chatstate, {type :: active | composing | gone | inactive | paused}).
|
||||
-type chatstate() :: #chatstate{}.
|
||||
|
||||
-record(csi, {type :: active | inactive}).
|
||||
-type csi() :: #csi{}.
|
||||
|
||||
-record(hint, {type :: 'no-copy' | 'no-store' | 'store' | 'no-permanent-store'}).
|
||||
-type hint() :: #hint{}.
|
||||
|
||||
-record(feature_register, {}).
|
||||
-type feature_register() :: #feature_register{}.
|
||||
|
||||
-record(sasl_success, {text :: any()}).
|
||||
-type sasl_success() :: #sasl_success{}.
|
||||
|
||||
-record(mam_result, {xmlns :: binary(),
|
||||
queryid :: binary(),
|
||||
id :: binary(),
|
||||
sub_els = [] :: [any()]}).
|
||||
-type mam_result() :: #mam_result{}.
|
||||
|
||||
-record(rsm_first, {index :: non_neg_integer(),
|
||||
data :: binary()}).
|
||||
-type rsm_first() :: #rsm_first{}.
|
||||
|
||||
-record(text, {lang :: binary(),
|
||||
data :: binary()}).
|
||||
-type text() :: #text{}.
|
||||
|
||||
-record(streamhost, {jid :: any(),
|
||||
host :: binary(),
|
||||
port = 1080 :: non_neg_integer()}).
|
||||
-type streamhost() :: #streamhost{}.
|
||||
|
||||
-record(sm_resume, {h :: non_neg_integer(),
|
||||
previd :: binary(),
|
||||
xmlns :: binary()}).
|
||||
-type sm_resume() :: #sm_resume{}.
|
||||
|
||||
-record(carbons_enable, {}).
|
||||
-type carbons_enable() :: #carbons_enable{}.
|
||||
|
||||
-record(carbons_private, {}).
|
||||
-type carbons_private() :: #carbons_private{}.
|
||||
|
||||
-record(pubsub_unsubscribe, {node :: binary(),
|
||||
jid :: any(),
|
||||
subid :: binary()}).
|
||||
-type pubsub_unsubscribe() :: #pubsub_unsubscribe{}.
|
||||
|
||||
-record(mix_leave, {}).
|
||||
-type mix_leave() :: #mix_leave{}.
|
||||
|
||||
-record(ping, {}).
|
||||
-type ping() :: #ping{}.
|
||||
|
||||
-record(delay, {stamp :: any(),
|
||||
from :: any()}).
|
||||
from :: any(),
|
||||
desc = <<>> :: binary()}).
|
||||
-type delay() :: #delay{}.
|
||||
|
||||
-record(muc_history, {maxchars :: non_neg_integer(),
|
||||
maxstanzas :: non_neg_integer(),
|
||||
seconds :: non_neg_integer(),
|
||||
since :: any()}).
|
||||
-type muc_history() :: #muc_history{}.
|
||||
|
||||
-record(pubsub_affiliation, {node :: binary(),
|
||||
type :: 'member' | 'none' | 'outcast' | 'owner' | 'publish-only' | 'publisher'}).
|
||||
-type pubsub_affiliation() :: #pubsub_affiliation{}.
|
||||
|
||||
-record(muc_decline, {reason :: binary(),
|
||||
from :: any(),
|
||||
to :: any()}).
|
||||
-type muc_decline() :: #muc_decline{}.
|
||||
|
||||
-record(sm_a, {h :: non_neg_integer(),
|
||||
xmlns :: binary()}).
|
||||
-type sm_a() :: #sm_a{}.
|
||||
|
||||
-record(starttls_proceed, {}).
|
||||
-type starttls_proceed() :: #starttls_proceed{}.
|
||||
|
||||
-record(sm_resumed, {h :: non_neg_integer(),
|
||||
previd :: binary(),
|
||||
xmlns :: binary()}).
|
||||
-type sm_resumed() :: #sm_resumed{}.
|
||||
|
||||
-record(forwarded, {delay :: #delay{},
|
||||
sub_els = [] :: [any()]}).
|
||||
-type forwarded() :: #forwarded{}.
|
||||
|
||||
-record(sm_enable, {max :: non_neg_integer(),
|
||||
resume = false :: any(),
|
||||
xmlns :: binary()}).
|
||||
-type sm_enable() :: #sm_enable{}.
|
||||
|
||||
-record(starttls_failure, {}).
|
||||
-type starttls_failure() :: #starttls_failure{}.
|
||||
|
||||
-record(sasl_challenge, {text :: any()}).
|
||||
-type sasl_challenge() :: #sasl_challenge{}.
|
||||
|
||||
-record(gone, {uri :: binary()}).
|
||||
-type gone() :: #gone{}.
|
||||
|
||||
-record(private, {xml_els = [] :: [any()]}).
|
||||
-type private() :: #private{}.
|
||||
|
||||
-record(p1_ack, {}).
|
||||
-type p1_ack() :: #p1_ack{}.
|
||||
|
||||
-record(feature_sm, {xmlns :: binary()}).
|
||||
-type feature_sm() :: #feature_sm{}.
|
||||
|
||||
-record(pubsub_item, {id :: binary(),
|
||||
xml_els = [] :: [any()]}).
|
||||
-type pubsub_item() :: #pubsub_item{}.
|
||||
|
||||
-record(pubsub_publish, {node :: binary(),
|
||||
items = [] :: [#pubsub_item{}]}).
|
||||
-type pubsub_publish() :: #pubsub_publish{}.
|
||||
|
||||
-record(roster_item, {jid :: any(),
|
||||
name :: binary(),
|
||||
name = <<>> :: binary(),
|
||||
groups = [] :: [binary()],
|
||||
subscription = none :: 'both' | 'from' | 'none' | 'remove' | 'to',
|
||||
ask :: 'subscribe'}).
|
||||
-type roster_item() :: #roster_item{}.
|
||||
|
||||
-record(roster, {items = [] :: [#roster_item{}],
|
||||
ver :: binary()}).
|
||||
-record(roster_query, {items = [] :: [#roster_item{}],
|
||||
ver :: binary()}).
|
||||
-type roster_query() :: #roster_query{}.
|
||||
|
||||
-record(pubsub_event_item, {id :: binary(),
|
||||
node :: binary(),
|
||||
publisher :: binary(),
|
||||
xml_els = [] :: [any()]}).
|
||||
-type pubsub_event_item() :: #pubsub_event_item{}.
|
||||
|
||||
-record(sm_r, {xmlns :: binary()}).
|
||||
-type sm_r() :: #sm_r{}.
|
||||
|
||||
-record(muc_actor, {jid :: any(),
|
||||
nick :: binary()}).
|
||||
-type muc_actor() :: #muc_actor{}.
|
||||
|
||||
-record(stat, {name :: binary(),
|
||||
units :: binary(),
|
||||
value :: binary(),
|
||||
error = [] :: [{integer(),'undefined' | binary()}]}).
|
||||
-type stat() :: #stat{}.
|
||||
|
||||
-record('see-other-host', {host :: binary()}).
|
||||
-type 'see-other-host'() :: #'see-other-host'{}.
|
||||
|
||||
-record(compress, {methods = [] :: [binary()]}).
|
||||
-type compress() :: #compress{}.
|
||||
|
||||
-record(starttls, {required = false :: boolean()}).
|
||||
-type starttls() :: #starttls{}.
|
||||
|
||||
-record(last, {seconds :: non_neg_integer(),
|
||||
text :: binary()}).
|
||||
status = <<>> :: binary()}).
|
||||
-type last() :: #last{}.
|
||||
|
||||
-record(redirect, {uri :: binary()}).
|
||||
-type redirect() :: #redirect{}.
|
||||
|
||||
-record(sm_enabled, {id :: binary(),
|
||||
location :: binary(),
|
||||
max :: non_neg_integer(),
|
||||
resume = false :: any(),
|
||||
xmlns :: binary()}).
|
||||
-type sm_enabled() :: #sm_enabled{}.
|
||||
|
||||
-record(pubsub_event_items, {node :: binary(),
|
||||
retract = [] :: [binary()],
|
||||
items = [] :: [#pubsub_event_item{}]}).
|
||||
-type pubsub_event_items() :: #pubsub_event_items{}.
|
||||
|
||||
-record(pubsub_event, {items = [] :: [#pubsub_event_items{}]}).
|
||||
-type pubsub_event() :: #pubsub_event{}.
|
||||
|
||||
-record(sasl_response, {text :: any()}).
|
||||
-type sasl_response() :: #sasl_response{}.
|
||||
|
||||
-record(legacy_auth, {username :: 'none' | binary(),
|
||||
password :: 'none' | binary(),
|
||||
digest :: 'none' | binary(),
|
||||
resource :: 'none' | binary()}).
|
||||
-type legacy_auth() :: #legacy_auth{}.
|
||||
|
||||
-record(pubsub_subscribe, {node :: binary(),
|
||||
jid :: any()}).
|
||||
-type pubsub_subscribe() :: #pubsub_subscribe{}.
|
||||
|
||||
-record(sasl_auth, {mechanism :: binary(),
|
||||
text :: any()}).
|
||||
-type sasl_auth() :: #sasl_auth{}.
|
||||
|
||||
-record(p1_push, {}).
|
||||
-type p1_push() :: #p1_push{}.
|
||||
|
||||
-record(feature_csi, {xmlns :: binary()}).
|
||||
-type feature_csi() :: #feature_csi{}.
|
||||
|
||||
-record(muc_user_destroy, {reason :: binary(),
|
||||
jid :: any()}).
|
||||
-type muc_user_destroy() :: #muc_user_destroy{}.
|
||||
|
||||
-record(disco_item, {jid :: any(),
|
||||
name :: binary(),
|
||||
node :: binary()}).
|
||||
-type disco_item() :: #disco_item{}.
|
||||
|
||||
-record(disco_items, {node :: binary(),
|
||||
items = [] :: [#disco_item{}]}).
|
||||
-type disco_items() :: #disco_items{}.
|
||||
|
||||
-record(unblock, {items = [] :: [any()]}).
|
||||
-type unblock() :: #unblock{}.
|
||||
|
||||
-record(block, {items = [] :: [any()]}).
|
||||
|
||||
-record(session, {}).
|
||||
-type block() :: #block{}.
|
||||
|
||||
-record(compression, {methods = [] :: [binary()]}).
|
||||
-type compression() :: #compression{}.
|
||||
|
||||
-record(muc_owner_destroy, {jid :: any(),
|
||||
reason :: binary(),
|
||||
password :: binary()}).
|
||||
-type muc_owner_destroy() :: #muc_owner_destroy{}.
|
||||
|
||||
-record(pubsub_subscription, {jid :: any(),
|
||||
node :: binary(),
|
||||
subid :: binary(),
|
||||
type :: 'none' | 'pending' | 'subscribed' | 'unconfigured'}).
|
||||
-type pubsub_subscription() :: #pubsub_subscription{}.
|
||||
|
||||
-record(muc_item, {actor :: #muc_actor{},
|
||||
continue :: binary(),
|
||||
@ -182,42 +252,57 @@
|
||||
role :: 'moderator' | 'none' | 'participant' | 'visitor',
|
||||
jid :: any(),
|
||||
nick :: binary()}).
|
||||
-type muc_item() :: #muc_item{}.
|
||||
|
||||
-record(muc_admin, {items = [] :: [#muc_item{}]}).
|
||||
-type muc_admin() :: #muc_admin{}.
|
||||
|
||||
-record(shim, {headers = [] :: [{binary(),'undefined' | binary()}]}).
|
||||
-type shim() :: #shim{}.
|
||||
|
||||
-record(mam_prefs, {xmlns :: binary(),
|
||||
default :: 'always' | 'never' | 'roster',
|
||||
always = [] :: [any()],
|
||||
never = [] :: [any()]}).
|
||||
-type mam_prefs() :: #mam_prefs{}.
|
||||
|
||||
-record(caps, {hash :: binary(),
|
||||
node :: binary(),
|
||||
ver :: any()}).
|
||||
-record(caps, {node :: binary(),
|
||||
version :: binary(),
|
||||
hash :: binary(),
|
||||
exts = [] :: any()}).
|
||||
-type caps() :: #caps{}.
|
||||
|
||||
-record(muc, {history :: #muc_history{},
|
||||
password :: binary()}).
|
||||
-type muc() :: #muc{}.
|
||||
|
||||
-record(stream_features, {sub_els = [] :: [any()]}).
|
||||
-type stream_features() :: #stream_features{}.
|
||||
|
||||
-record(stats, {stat = [] :: [#stat{}]}).
|
||||
-type stats() :: #stats{}.
|
||||
|
||||
-record(pubsub_items, {node :: binary(),
|
||||
max_items :: non_neg_integer(),
|
||||
subid :: binary(),
|
||||
items = [] :: [#pubsub_item{}]}).
|
||||
-type pubsub_items() :: #pubsub_items{}.
|
||||
|
||||
-record(carbons_sent, {forwarded :: #forwarded{}}).
|
||||
-type carbons_sent() :: #carbons_sent{}.
|
||||
|
||||
-record(mam_archived, {by :: any(),
|
||||
id :: binary()}).
|
||||
-type mam_archived() :: #mam_archived{}.
|
||||
|
||||
-record(p1_rebind, {}).
|
||||
-type p1_rebind() :: #p1_rebind{}.
|
||||
|
||||
-record(compress_failure, {reason :: 'processing-failed' | 'setup-failed' | 'unsupported-method'}).
|
||||
-type compress_failure() :: #compress_failure{}.
|
||||
|
||||
-record(sasl_abort, {}).
|
||||
-type sasl_abort() :: #sasl_abort{}.
|
||||
|
||||
-record(vcard_email, {home = false :: boolean(),
|
||||
work = false :: boolean(),
|
||||
@ -225,25 +310,33 @@
|
||||
pref = false :: boolean(),
|
||||
x400 = false :: boolean(),
|
||||
userid :: binary()}).
|
||||
-type vcard_email() :: #vcard_email{}.
|
||||
|
||||
-record(carbons_received, {forwarded :: #forwarded{}}).
|
||||
-type carbons_received() :: #carbons_received{}.
|
||||
|
||||
-record(pubsub_retract, {node :: binary(),
|
||||
notify = false :: any(),
|
||||
items = [] :: [#pubsub_item{}]}).
|
||||
-type pubsub_retract() :: #pubsub_retract{}.
|
||||
|
||||
-record(mix_participant, {jid :: any(),
|
||||
nick :: binary()}).
|
||||
-type mix_participant() :: #mix_participant{}.
|
||||
|
||||
-record(vcard_geo, {lat :: binary(),
|
||||
lon :: binary()}).
|
||||
-type vcard_geo() :: #vcard_geo{}.
|
||||
|
||||
-record(compressed, {}).
|
||||
-type compressed() :: #compressed{}.
|
||||
|
||||
-record(sasl_failure, {reason :: 'aborted' | 'account-disabled' | 'credentials-expired' | 'encryption-required' | 'incorrect-encoding' | 'invalid-authzid' | 'invalid-mechanism' | 'malformed-request' | 'mechanism-too-weak' | 'not-authorized' | 'temporary-auth-failure',
|
||||
-record(sasl_failure, {reason :: 'aborted' | 'account-disabled' | 'bad-protocol' | 'credentials-expired' | 'encryption-required' | 'incorrect-encoding' | 'invalid-authzid' | 'invalid-mechanism' | 'malformed-request' | 'mechanism-too-weak' | 'not-authorized' | 'temporary-auth-failure',
|
||||
text = [] :: [#text{}]}).
|
||||
-type sasl_failure() :: #sasl_failure{}.
|
||||
|
||||
-record(block_list, {}).
|
||||
-type block_list() :: #block_list{}.
|
||||
|
||||
-record(xdata_field, {label :: binary(),
|
||||
type :: 'boolean' | 'fixed' | 'hidden' | 'jid-multi' | 'jid-single' | 'list-multi' | 'list-single' | 'text-multi' | 'text-private' | 'text-single',
|
||||
@ -252,17 +345,24 @@
|
||||
desc :: binary(),
|
||||
values = [] :: [binary()],
|
||||
options = [] :: [binary()]}).
|
||||
-type xdata_field() :: #xdata_field{}.
|
||||
|
||||
-record(version, {name :: binary(),
|
||||
ver :: binary(),
|
||||
os :: binary()}).
|
||||
-type version() :: #version{}.
|
||||
|
||||
-record(muc_invite, {reason :: binary(),
|
||||
from :: any(),
|
||||
to :: any()}).
|
||||
-type muc_invite() :: #muc_invite{}.
|
||||
|
||||
-record(bind, {jid :: any(),
|
||||
resource :: any()}).
|
||||
-type bind() :: #bind{}.
|
||||
|
||||
-record(rosterver_feature, {}).
|
||||
-type rosterver_feature() :: #rosterver_feature{}.
|
||||
|
||||
-record(muc_user, {decline :: #muc_decline{},
|
||||
destroy :: #muc_user_destroy{},
|
||||
@ -270,10 +370,10 @@
|
||||
items = [] :: [#muc_item{}],
|
||||
status_codes = [] :: [pos_integer()],
|
||||
password :: binary()}).
|
||||
|
||||
-record(vcard_xupdate, {photo :: binary()}).
|
||||
-type muc_user() :: #muc_user{}.
|
||||
|
||||
-record(carbons_disable, {}).
|
||||
-type carbons_disable() :: #carbons_disable{}.
|
||||
|
||||
-record(bytestreams, {hosts = [] :: [#streamhost{}],
|
||||
used :: any(),
|
||||
@ -281,9 +381,11 @@
|
||||
dstaddr :: binary(),
|
||||
mode = tcp :: 'tcp' | 'udp',
|
||||
sid :: binary()}).
|
||||
-type bytestreams() :: #bytestreams{}.
|
||||
|
||||
-record(vcard_org, {name :: binary(),
|
||||
units = [] :: [binary()]}).
|
||||
-type vcard_org() :: #vcard_org{}.
|
||||
|
||||
-record(rsm_set, {'after' :: binary(),
|
||||
before :: 'none' | binary(),
|
||||
@ -292,11 +394,13 @@
|
||||
index :: non_neg_integer(),
|
||||
last :: binary(),
|
||||
max :: non_neg_integer()}).
|
||||
-type rsm_set() :: #rsm_set{}.
|
||||
|
||||
-record(mam_fin, {id :: binary(),
|
||||
rsm :: #rsm_set{},
|
||||
stable :: any(),
|
||||
complete :: any()}).
|
||||
-type mam_fin() :: #mam_fin{}.
|
||||
|
||||
-record(vcard_tel, {home = false :: boolean(),
|
||||
work = false :: boolean(),
|
||||
@ -312,40 +416,52 @@
|
||||
pcs = false :: boolean(),
|
||||
pref = false :: boolean(),
|
||||
number :: binary()}).
|
||||
-type vcard_tel() :: #vcard_tel{}.
|
||||
|
||||
-record(vcard_key, {type :: binary(),
|
||||
cred :: binary()}).
|
||||
-type vcard_key() :: #vcard_key{}.
|
||||
|
||||
-record(vcard_name, {family :: binary(),
|
||||
given :: binary(),
|
||||
middle :: binary(),
|
||||
prefix :: binary(),
|
||||
suffix :: binary()}).
|
||||
-type vcard_name() :: #vcard_name{}.
|
||||
|
||||
-record(identity, {category :: binary(),
|
||||
type :: binary(),
|
||||
lang :: binary(),
|
||||
name :: binary()}).
|
||||
-type identity() :: #identity{}.
|
||||
|
||||
-record(bookmark_conference, {name :: binary(),
|
||||
jid :: any(),
|
||||
autojoin = false :: any(),
|
||||
nick :: binary(),
|
||||
password :: binary()}).
|
||||
-type bookmark_conference() :: #bookmark_conference{}.
|
||||
|
||||
-record(xmpp_session, {optional = false :: boolean()}).
|
||||
-type xmpp_session() :: #xmpp_session{}.
|
||||
|
||||
-record(bookmark_url, {name :: binary(),
|
||||
url :: binary()}).
|
||||
-type bookmark_url() :: #bookmark_url{}.
|
||||
|
||||
-record(bookmark_storage, {conference = [] :: [#bookmark_conference{}],
|
||||
url = [] :: [#bookmark_url{}]}).
|
||||
-type bookmark_storage() :: #bookmark_storage{}.
|
||||
|
||||
-record(vcard_sound, {phonetic :: binary(),
|
||||
binval :: any(),
|
||||
extval :: binary()}).
|
||||
-type vcard_sound() :: #vcard_sound{}.
|
||||
|
||||
-record(vcard_photo, {type :: binary(),
|
||||
binval :: any(),
|
||||
extval :: binary()}).
|
||||
-type vcard_photo() :: #vcard_photo{}.
|
||||
|
||||
-record(vcard_label, {home = false :: boolean(),
|
||||
work = false :: boolean(),
|
||||
@ -355,6 +471,7 @@
|
||||
intl = false :: boolean(),
|
||||
pref = false :: boolean(),
|
||||
line = [] :: [binary()]}).
|
||||
-type vcard_label() :: #vcard_label{}.
|
||||
|
||||
-record(vcard_adr, {home = false :: boolean(),
|
||||
work = false :: boolean(),
|
||||
@ -370,6 +487,14 @@
|
||||
region :: binary(),
|
||||
pcode :: binary(),
|
||||
ctry :: binary()}).
|
||||
-type vcard_adr() :: #vcard_adr{}.
|
||||
|
||||
-record(search_item, {jid :: any(),
|
||||
first :: binary(),
|
||||
last :: binary(),
|
||||
nick :: binary(),
|
||||
email :: binary()}).
|
||||
-type search_item() :: #search_item{}.
|
||||
|
||||
-record(xdata, {type :: 'cancel' | 'form' | 'result' | 'submit',
|
||||
instructions = [] :: [binary()],
|
||||
@ -377,6 +502,16 @@
|
||||
reported :: [#xdata_field{}],
|
||||
items = [] :: [[#xdata_field{}]],
|
||||
fields = [] :: [#xdata_field{}]}).
|
||||
-type xdata() :: #xdata{}.
|
||||
|
||||
-record(search, {instructions :: binary(),
|
||||
first :: binary(),
|
||||
last :: binary(),
|
||||
nick :: binary(),
|
||||
email :: binary(),
|
||||
items = [] :: [#search_item{}],
|
||||
xdata :: #xdata{}}).
|
||||
-type search() :: #search{}.
|
||||
|
||||
-record(mam_query, {xmlns :: binary(),
|
||||
id :: binary(),
|
||||
@ -385,14 +520,17 @@
|
||||
with :: any(),
|
||||
rsm :: #rsm_set{},
|
||||
xdata :: #xdata{}}).
|
||||
-type mam_query() :: #mam_query{}.
|
||||
|
||||
-record(muc_owner, {destroy :: #muc_owner_destroy{},
|
||||
config :: #xdata{}}).
|
||||
-type muc_owner() :: #muc_owner{}.
|
||||
|
||||
-record(pubsub_options, {node :: binary(),
|
||||
jid :: any(),
|
||||
subid :: binary(),
|
||||
xdata :: #xdata{}}).
|
||||
-type pubsub_options() :: #pubsub_options{}.
|
||||
|
||||
-record(pubsub, {subscriptions :: {'none' | binary(),[#pubsub_subscription{}]},
|
||||
affiliations :: [#pubsub_affiliation{}],
|
||||
@ -402,6 +540,7 @@
|
||||
options :: #pubsub_options{},
|
||||
items :: #pubsub_items{},
|
||||
retract :: #pubsub_retract{}}).
|
||||
-type pubsub() :: #pubsub{}.
|
||||
|
||||
-record(register, {registered = false :: boolean(),
|
||||
remove = false :: boolean(),
|
||||
@ -424,32 +563,40 @@
|
||||
text :: 'none' | binary(),
|
||||
key :: 'none' | binary(),
|
||||
xdata :: #xdata{}}).
|
||||
-type register() :: #register{}.
|
||||
|
||||
-record(disco_info, {node :: binary(),
|
||||
identities = [] :: [#identity{}],
|
||||
features = [] :: [binary()],
|
||||
xdata = [] :: [#xdata{}]}).
|
||||
-type disco_info() :: #disco_info{}.
|
||||
|
||||
-record(offline_item, {node :: binary(),
|
||||
action :: 'remove' | 'view'}).
|
||||
-type offline_item() :: #offline_item{}.
|
||||
|
||||
-record(offline, {items = [] :: [#offline_item{}],
|
||||
purge = false :: boolean(),
|
||||
fetch = false :: boolean()}).
|
||||
-type offline() :: #offline{}.
|
||||
|
||||
-record(sasl_mechanisms, {list = [] :: [binary()]}).
|
||||
-type sasl_mechanisms() :: #sasl_mechanisms{}.
|
||||
|
||||
-record(sm_failed, {reason :: atom() | #gone{} | #redirect{},
|
||||
h :: non_neg_integer(),
|
||||
xmlns :: binary()}).
|
||||
-type sm_failed() :: #sm_failed{}.
|
||||
|
||||
-record(error, {type :: 'auth' | 'cancel' | 'continue' | 'modify' | 'wait',
|
||||
code :: non_neg_integer(),
|
||||
by :: binary(),
|
||||
reason :: atom() | #gone{} | #redirect{},
|
||||
text :: #text{}}).
|
||||
-type error() :: #error{}.
|
||||
|
||||
-record(presence, {id :: binary(),
|
||||
type :: 'error' | 'probe' | 'subscribe' | 'subscribed' | 'unavailable' | 'unsubscribe' | 'unsubscribed',
|
||||
type = available :: 'available' | 'error' | 'probe' | 'subscribe' | 'subscribed' | 'unavailable' | 'unsubscribe' | 'unsubscribed',
|
||||
lang :: binary(),
|
||||
from :: any(),
|
||||
to :: any(),
|
||||
@ -458,6 +605,7 @@
|
||||
priority :: integer(),
|
||||
error :: #error{},
|
||||
sub_els = [] :: [any()]}).
|
||||
-type presence() :: #presence{}.
|
||||
|
||||
-record(message, {id :: binary(),
|
||||
type = normal :: 'chat' | 'error' | 'groupchat' | 'headline' | 'normal',
|
||||
@ -469,6 +617,7 @@
|
||||
thread :: binary(),
|
||||
error :: #error{},
|
||||
sub_els = [] :: [any()]}).
|
||||
-type message() :: #message{}.
|
||||
|
||||
-record(iq, {id :: binary(),
|
||||
type :: 'error' | 'get' | 'result' | 'set',
|
||||
@ -477,61 +626,203 @@
|
||||
to :: any(),
|
||||
error :: #error{},
|
||||
sub_els = [] :: [any()]}).
|
||||
-type iq() :: #iq{}.
|
||||
|
||||
-record(mix_join, {jid :: any(),
|
||||
subscribe = [] :: [binary()]}).
|
||||
-type mix_join() :: #mix_join{}.
|
||||
|
||||
-record(privacy_item, {order :: non_neg_integer(),
|
||||
action :: 'allow' | 'deny',
|
||||
type :: 'group' | 'jid' | 'subscription',
|
||||
value :: binary(),
|
||||
kinds = [] :: ['iq' | 'message' | 'presence-in' | 'presence-out']}).
|
||||
message = false :: boolean(),
|
||||
iq = false :: boolean(),
|
||||
presence_in = false :: boolean(),
|
||||
presence_out = false :: boolean()}).
|
||||
-type privacy_item() :: #privacy_item{}.
|
||||
|
||||
-record(privacy_list, {name :: binary(),
|
||||
items = [] :: [#privacy_item{}]}).
|
||||
-type privacy_list() :: #privacy_list{}.
|
||||
|
||||
-record(privacy, {lists = [] :: [#privacy_list{}],
|
||||
default :: 'none' | binary(),
|
||||
active :: 'none' | binary()}).
|
||||
-record(privacy_query, {lists = [] :: [#privacy_list{}],
|
||||
default :: 'none' | binary(),
|
||||
active :: 'none' | binary()}).
|
||||
-type privacy_query() :: #privacy_query{}.
|
||||
|
||||
-record(stream_error, {reason :: atom() | #'see-other-host'{},
|
||||
text :: #text{}}).
|
||||
-type stream_error() :: #stream_error{}.
|
||||
|
||||
-record(vcard_logo, {type :: binary(),
|
||||
binval :: any(),
|
||||
extval :: binary()}).
|
||||
-type vcard_logo() :: #vcard_logo{}.
|
||||
|
||||
-record(vcard, {version :: binary(),
|
||||
fn :: binary(),
|
||||
n :: #vcard_name{},
|
||||
nickname :: binary(),
|
||||
photo :: #vcard_photo{},
|
||||
bday :: binary(),
|
||||
adr = [] :: [#vcard_adr{}],
|
||||
label = [] :: [#vcard_label{}],
|
||||
tel = [] :: [#vcard_tel{}],
|
||||
email = [] :: [#vcard_email{}],
|
||||
jabberid :: binary(),
|
||||
mailer :: binary(),
|
||||
tz :: binary(),
|
||||
geo :: #vcard_geo{},
|
||||
title :: binary(),
|
||||
role :: binary(),
|
||||
logo :: #vcard_logo{},
|
||||
org :: #vcard_org{},
|
||||
categories = [] :: [binary()],
|
||||
note :: binary(),
|
||||
prodid :: binary(),
|
||||
rev :: binary(),
|
||||
sort_string :: binary(),
|
||||
sound :: #vcard_sound{},
|
||||
uid :: binary(),
|
||||
url :: binary(),
|
||||
class :: 'confidential' | 'private' | 'public',
|
||||
key :: #vcard_key{},
|
||||
desc :: binary()}).
|
||||
-record(vcard_temp, {version :: binary(),
|
||||
fn :: binary(),
|
||||
n :: #vcard_name{},
|
||||
nickname :: binary(),
|
||||
photo :: #vcard_photo{},
|
||||
bday :: binary(),
|
||||
adr = [] :: [#vcard_adr{}],
|
||||
label = [] :: [#vcard_label{}],
|
||||
tel = [] :: [#vcard_tel{}],
|
||||
email = [] :: [#vcard_email{}],
|
||||
jabberid :: binary(),
|
||||
mailer :: binary(),
|
||||
tz :: binary(),
|
||||
geo :: #vcard_geo{},
|
||||
title :: binary(),
|
||||
role :: binary(),
|
||||
logo :: #vcard_logo{},
|
||||
org :: #vcard_org{},
|
||||
categories = [] :: [binary()],
|
||||
note :: binary(),
|
||||
prodid :: binary(),
|
||||
rev :: binary(),
|
||||
sort_string :: binary(),
|
||||
sound :: #vcard_sound{},
|
||||
uid :: binary(),
|
||||
url :: binary(),
|
||||
class :: 'confidential' | 'private' | 'public',
|
||||
key :: #vcard_key{},
|
||||
desc :: binary()}).
|
||||
-type vcard_temp() :: #vcard_temp{}.
|
||||
|
||||
-record(time, {tzo :: any(),
|
||||
utc :: any()}).
|
||||
-type time() :: #time{}.
|
||||
|
||||
|
||||
-type xmpp_element() :: compression() |
|
||||
pubsub_subscription() |
|
||||
version() |
|
||||
pubsub_affiliation() |
|
||||
muc_admin() |
|
||||
mam_fin() |
|
||||
sm_a() |
|
||||
carbons_sent() |
|
||||
mam_archived() |
|
||||
p1_rebind() |
|
||||
sasl_abort() |
|
||||
carbons_received() |
|
||||
pubsub_retract() |
|
||||
mix_participant() |
|
||||
compressed() |
|
||||
block_list() |
|
||||
rsm_set() |
|
||||
'see-other-host'() |
|
||||
hint() |
|
||||
starttls_proceed() |
|
||||
sm_resumed() |
|
||||
forwarded() |
|
||||
privacy_list() |
|
||||
text() |
|
||||
vcard_org() |
|
||||
shim() |
|
||||
search_item() |
|
||||
offline_item() |
|
||||
feature_sm() |
|
||||
pubsub_item() |
|
||||
roster_item() |
|
||||
pubsub_event_item() |
|
||||
muc_item() |
|
||||
vcard_temp() |
|
||||
sasl_success() |
|
||||
pubsub_event_items() |
|
||||
disco_items() |
|
||||
pubsub_options() |
|
||||
compress() |
|
||||
bytestreams() |
|
||||
identity() |
|
||||
feature_csi() |
|
||||
muc_user_destroy() |
|
||||
privacy_query() |
|
||||
delay() |
|
||||
muc_history() |
|
||||
vcard_tel() |
|
||||
vcard_logo() |
|
||||
disco_info() |
|
||||
vcard_geo() |
|
||||
vcard_photo() |
|
||||
feature_register() |
|
||||
register() |
|
||||
muc_owner() |
|
||||
pubsub() |
|
||||
sm_r() |
|
||||
muc_actor() |
|
||||
error() |
|
||||
stream_error() |
|
||||
muc_user() |
|
||||
vcard_adr() |
|
||||
carbons_private() |
|
||||
mix_leave() |
|
||||
muc_invite() |
|
||||
rosterver_feature() |
|
||||
vcard_xupdate() |
|
||||
carbons_disable() |
|
||||
bookmark_conference() |
|
||||
offline() |
|
||||
time() |
|
||||
sasl_response() |
|
||||
pubsub_subscribe() |
|
||||
presence() |
|
||||
message() |
|
||||
sm_enable() |
|
||||
starttls_failure() |
|
||||
sasl_challenge() |
|
||||
gone() |
|
||||
private() |
|
||||
compress_failure() |
|
||||
sasl_failure() |
|
||||
bookmark_storage() |
|
||||
vcard_name() |
|
||||
sm_resume() |
|
||||
carbons_enable() |
|
||||
pubsub_unsubscribe() |
|
||||
muc_decline() |
|
||||
chatstate() |
|
||||
sasl_auth() |
|
||||
p1_push() |
|
||||
legacy_auth() |
|
||||
search() |
|
||||
pubsub_publish() |
|
||||
unblock() |
|
||||
p1_ack() |
|
||||
block() |
|
||||
mix_join() |
|
||||
xmpp_session() |
|
||||
xdata() |
|
||||
iq() |
|
||||
streamhost() |
|
||||
bind() |
|
||||
last() |
|
||||
redirect() |
|
||||
sm_enabled() |
|
||||
pubsub_event() |
|
||||
vcard_sound() |
|
||||
mam_result() |
|
||||
rsm_first() |
|
||||
stat() |
|
||||
xdata_field() |
|
||||
sm_failed() |
|
||||
ping() |
|
||||
disco_item() |
|
||||
privacy_item() |
|
||||
caps() |
|
||||
muc() |
|
||||
stream_features() |
|
||||
stats() |
|
||||
pubsub_items() |
|
||||
starttls() |
|
||||
mam_prefs() |
|
||||
sasl_mechanisms() |
|
||||
vcard_key() |
|
||||
csi() |
|
||||
roster_query() |
|
||||
muc_owner_destroy() |
|
||||
mam_query() |
|
||||
bookmark_url() |
|
||||
vcard_email() |
|
||||
vcard_label().
|
@ -73,8 +73,8 @@
|
||||
-callback mech_step(any(), binary()) -> {ok, props()} |
|
||||
{ok, props(), binary()} |
|
||||
{continue, binary(), any()} |
|
||||
{error, binary()} |
|
||||
{error, binary(), binary()}.
|
||||
{error, atom()} |
|
||||
{error, atom(), binary()}.
|
||||
|
||||
start() ->
|
||||
ets:new(sasl_mechanism,
|
||||
@ -129,8 +129,8 @@ register_mechanism(Mechanism, Module, PasswordType) ->
|
||||
check_credentials(_State, Props) ->
|
||||
User = proplists:get_value(authzid, Props, <<>>),
|
||||
case jid:nodeprep(User) of
|
||||
error -> {error, <<"not-authorized">>};
|
||||
<<"">> -> {error, <<"not-authorized">>};
|
||||
error -> {error, 'not-authorized'};
|
||||
<<"">> -> {error, 'not-authorized'};
|
||||
_LUser -> ok
|
||||
end.
|
||||
|
||||
@ -159,6 +159,8 @@ server_new(Service, ServerFQDN, UserRealm, _SecFlags,
|
||||
check_password = CheckPassword,
|
||||
check_password_digest = CheckPasswordDigest}.
|
||||
|
||||
server_start(State, Mech, undefined) ->
|
||||
server_start(State, Mech, <<"">>);
|
||||
server_start(State, Mech, ClientIn) ->
|
||||
case lists:member(Mech,
|
||||
listmech(State#sasl_state.myname))
|
||||
@ -174,11 +176,13 @@ server_start(State, Mech, ClientIn) ->
|
||||
server_step(State#sasl_state{mech_mod = Module,
|
||||
mech_state = MechState},
|
||||
ClientIn);
|
||||
_ -> {error, <<"no-mechanism">>}
|
||||
_ -> {error, 'no-mechanism'}
|
||||
end;
|
||||
false -> {error, <<"no-mechanism">>}
|
||||
false -> {error, 'no-mechanism'}
|
||||
end.
|
||||
|
||||
server_step(State, undefined) ->
|
||||
server_step(State, <<"">>);
|
||||
server_step(State, ClientIn) ->
|
||||
Module = State#sasl_state.mech_mod,
|
||||
MechState = State#sasl_state.mech_state,
|
||||
|
@ -80,7 +80,7 @@ mech_step(#state{step = 1, nonce = Nonce} = State, _) ->
|
||||
mech_step(#state{step = 3, nonce = Nonce} = State,
|
||||
ClientIn) ->
|
||||
case parse(ClientIn) of
|
||||
bad -> {error, <<"bad-protocol">>};
|
||||
bad -> {error, 'bad-protocol'};
|
||||
KeyVals ->
|
||||
DigestURI = proplists:get_value(<<"digest-uri">>, KeyVals, <<>>),
|
||||
UserName = proplists:get_value(<<"username">>, KeyVals, <<>>),
|
||||
@ -92,11 +92,11 @@ mech_step(#state{step = 3, nonce = Nonce} = State,
|
||||
"seems invalid: ~p (checking for Host "
|
||||
"~p, FQDN ~p)",
|
||||
[DigestURI, State#state.host, State#state.hostfqdn]),
|
||||
{error, <<"not-authorized">>, UserName};
|
||||
{error, 'not-authorized', UserName};
|
||||
true ->
|
||||
AuthzId = proplists:get_value(<<"authzid">>, KeyVals, <<>>),
|
||||
case (State#state.get_password)(UserName) of
|
||||
{false, _} -> {error, <<"not-authorized">>, UserName};
|
||||
{false, _} -> {error, 'not-authorized', UserName};
|
||||
{Passwd, AuthModule} ->
|
||||
case (State#state.check_password)(UserName, UserName, <<"">>,
|
||||
proplists:get_value(<<"response">>, KeyVals, <<>>),
|
||||
@ -116,8 +116,8 @@ mech_step(#state{step = 3, nonce = Nonce} = State,
|
||||
State#state{step = 5, auth_module = AuthModule,
|
||||
username = UserName,
|
||||
authzid = AuthzId}};
|
||||
false -> {error, <<"not-authorized">>, UserName};
|
||||
{false, _} -> {error, <<"not-authorized">>, UserName}
|
||||
false -> {error, 'not-authorized', UserName};
|
||||
{false, _} -> {error, 'not-authorized', UserName}
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -134,7 +134,7 @@ mech_step(#state{step = 5, auth_module = AuthModule,
|
||||
{auth_module, AuthModule}]};
|
||||
mech_step(A, B) ->
|
||||
?DEBUG("SASL DIGEST: A ~p B ~p", [A, B]),
|
||||
{error, <<"bad-protocol">>}.
|
||||
{error, 'bad-protocol'}.
|
||||
|
||||
parse(S) -> parse1(binary_to_list(S), "", []).
|
||||
|
||||
|
@ -52,9 +52,9 @@ mech_step(State, ClientIn) ->
|
||||
[{username, User}, {authzid, AuthzId},
|
||||
{auth_module, ejabberd_oauth}]};
|
||||
false ->
|
||||
{error, <<"not-authorized">>, User}
|
||||
{error, 'not-authorized', User}
|
||||
end;
|
||||
_ -> {error, <<"bad-protocol">>}
|
||||
_ -> {error, 'bad-protocol'}
|
||||
end.
|
||||
|
||||
prepare(ClientIn) ->
|
||||
|
@ -50,9 +50,9 @@ mech_step(State, ClientIn) ->
|
||||
{ok,
|
||||
[{username, User}, {authzid, AuthzId},
|
||||
{auth_module, AuthModule}]};
|
||||
_ -> {error, <<"not-authorized">>, User}
|
||||
_ -> {error, 'not-authorized', User}
|
||||
end;
|
||||
_ -> {error, <<"bad-protocol">>}
|
||||
_ -> {error, 'bad-protocol'}
|
||||
end.
|
||||
|
||||
prepare(ClientIn) ->
|
||||
|
@ -67,21 +67,21 @@ mech_step(#state{step = 2} = State, ClientIn) ->
|
||||
case re:split(ClientIn, <<",">>, [{return, binary}]) of
|
||||
[_CBind, _AuthorizationIdentity, _UserNameAttribute, _ClientNonceAttribute, ExtensionAttribute | _]
|
||||
when ExtensionAttribute /= [] ->
|
||||
{error, <<"protocol-error-extension-not-supported">>};
|
||||
{error, 'protocol-error-extension-not-supported'};
|
||||
[CBind, _AuthorizationIdentity, UserNameAttribute, ClientNonceAttribute | _]
|
||||
when (CBind == <<"y">>) or (CBind == <<"n">>) ->
|
||||
case parse_attribute(UserNameAttribute) of
|
||||
{error, Reason} -> {error, Reason};
|
||||
{_, EscapedUserName} ->
|
||||
case unescape_username(EscapedUserName) of
|
||||
error -> {error, <<"protocol-error-bad-username">>};
|
||||
error -> {error, 'protocol-error-bad-username'};
|
||||
UserName ->
|
||||
case parse_attribute(ClientNonceAttribute) of
|
||||
{$r, ClientNonce} ->
|
||||
{Ret, _AuthModule} = (State#state.get_password)(UserName),
|
||||
case {Ret, jid:resourceprep(Ret)} of
|
||||
{false, _} -> {error, <<"not-authorized">>, UserName};
|
||||
{_, error} when is_binary(Ret) -> ?WARNING_MSG("invalid plain password", []), {error, <<"not-authorized">>, UserName};
|
||||
{false, _} -> {error, 'not-authorized', UserName};
|
||||
{_, error} when is_binary(Ret) -> ?WARNING_MSG("invalid plain password", []), {error, 'not-authorized', UserName};
|
||||
{Ret, _} ->
|
||||
{StoredKey, ServerKey, Salt, IterationCount} =
|
||||
if is_tuple(Ret) -> Ret;
|
||||
@ -121,11 +121,11 @@ mech_step(#state{step = 2} = State, ClientIn) ->
|
||||
server_nonce = ServerNonce,
|
||||
username = UserName}}
|
||||
end;
|
||||
_Else -> {error, <<"not-supported">>}
|
||||
_Else -> {error, 'not-supported'}
|
||||
end
|
||||
end
|
||||
end;
|
||||
_Else -> {error, <<"bad-protocol">>}
|
||||
_Else -> {error, 'bad-protocol'}
|
||||
end;
|
||||
mech_step(#state{step = 4} = State, ClientIn) ->
|
||||
case str:tokens(ClientIn, <<",">>) of
|
||||
@ -163,18 +163,18 @@ mech_step(#state{step = 4} = State, ClientIn) ->
|
||||
{authzid, State#state.username}],
|
||||
<<"v=",
|
||||
(jlib:encode_base64(ServerSignature))/binary>>};
|
||||
true -> {error, <<"bad-auth">>, State#state.username}
|
||||
true -> {error, 'bad-auth', State#state.username}
|
||||
end;
|
||||
_Else -> {error, <<"bad-protocol">>}
|
||||
_Else -> {error, 'bad-protocol'}
|
||||
end;
|
||||
{$r, _} -> {error, <<"bad-nonce">>};
|
||||
_Else -> {error, <<"bad-protocol">>}
|
||||
{$r, _} -> {error, 'bad-nonce'};
|
||||
_Else -> {error, 'bad-protocol'}
|
||||
end;
|
||||
true -> {error, <<"bad-channel-binding">>}
|
||||
true -> {error, 'bad-channel-binding'}
|
||||
end;
|
||||
_Else -> {error, <<"bad-protocol">>}
|
||||
_Else -> {error, 'bad-protocol'}
|
||||
end;
|
||||
_Else -> {error, <<"bad-protocol">>}
|
||||
_Else -> {error, 'bad-protocol'}
|
||||
end.
|
||||
|
||||
parse_attribute(Attribute) ->
|
||||
@ -187,11 +187,11 @@ parse_attribute(Attribute) ->
|
||||
if SecondChar == $= ->
|
||||
String = str:substr(Attribute, 3),
|
||||
{lists:nth(1, AttributeS), String};
|
||||
true -> {error, <<"bad-format second char not equal sign">>}
|
||||
true -> {error, 'bad-format-second-char-not-equal-sign'}
|
||||
end;
|
||||
_Else -> {error, <<"bad-format first char not a letter">>}
|
||||
_Else -> {error, 'bad-format-first-char-not-a-letter'}
|
||||
end;
|
||||
true -> {error, <<"bad-format attribute too short">>}
|
||||
true -> {error, 'bad-format-attribute-too-short'}
|
||||
end.
|
||||
|
||||
unescape_username(<<"">>) -> <<"">>;
|
||||
|
2304
src/ejabberd_c2s.erl
2304
src/ejabberd_c2s.erl
File diff suppressed because it is too large
Load Diff
@ -42,6 +42,7 @@
|
||||
change_shaper/2,
|
||||
monitor/1,
|
||||
get_sockmod/1,
|
||||
get_transport/1,
|
||||
get_peer_certificate/1,
|
||||
get_verify_result/1,
|
||||
close/1,
|
||||
@ -118,6 +119,9 @@ monitor(FsmRef) -> erlang:monitor(process, FsmRef).
|
||||
get_sockmod(FsmRef) ->
|
||||
gen_server:call(FsmRef, get_sockmod).
|
||||
|
||||
get_transport(FsmRef) ->
|
||||
gen_server:call(FsmRef, get_transport).
|
||||
|
||||
get_peer_certificate(FsmRef) ->
|
||||
gen_server:call(FsmRef, get_peer_certificate).
|
||||
|
||||
@ -186,6 +190,19 @@ handle_call({change_shaper, Shaper}, _From, State) ->
|
||||
handle_call(get_sockmod, _From, State) ->
|
||||
Reply = State#state.sockmod,
|
||||
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
|
||||
handle_call(get_transport, _From, State) ->
|
||||
Reply = case State#state.sockmod of
|
||||
gen_tcp -> tcp;
|
||||
fast_tls -> tls;
|
||||
ezlib ->
|
||||
case ezlib:get_sockmod(State#state.socket) of
|
||||
tcp -> tcp_zlib;
|
||||
tls -> tls_zlib
|
||||
end;
|
||||
ejabberd_http_bind -> http_bind;
|
||||
ejabberd_http_ws -> websocket
|
||||
end,
|
||||
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
|
||||
handle_call(get_peer_certificate, _From, State) ->
|
||||
Reply = fast_tls:get_peer_certificate(State#state.socket),
|
||||
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
|
||||
|
@ -46,7 +46,7 @@
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("xmpp.hrl").
|
||||
|
||||
-record(state, {}).
|
||||
|
||||
@ -60,6 +60,8 @@
|
||||
%% This value is used in SIP and Megaco for a transaction lifetime.
|
||||
-define(IQ_TIMEOUT, 32000).
|
||||
|
||||
-type ping_timeout() :: non_neg_integer() | undefined.
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
@ -71,37 +73,38 @@ start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [],
|
||||
[]).
|
||||
|
||||
process_iq(From, To, Packet) ->
|
||||
IQ = jlib:iq_query_info(Packet),
|
||||
case IQ of
|
||||
#iq{xmlns = XMLNS, lang = Lang} ->
|
||||
Host = To#jid.lserver,
|
||||
case ets:lookup(?IQTABLE, {XMLNS, Host}) of
|
||||
[{_, Module, Function}] ->
|
||||
ResIQ = Module:Function(From, To, IQ),
|
||||
if ResIQ /= ignore ->
|
||||
ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ));
|
||||
true -> ok
|
||||
end;
|
||||
[{_, Module, Function, Opts}] ->
|
||||
gen_iq_handler:handle(Host, Module, Function, Opts,
|
||||
From, To, IQ);
|
||||
[] ->
|
||||
Txt = <<"No module is handling this query">>,
|
||||
Err = jlib:make_error_reply(
|
||||
Packet,
|
||||
?ERRT_FEATURE_NOT_IMPLEMENTED(Lang, Txt)),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end;
|
||||
reply ->
|
||||
IQReply = jlib:iq_query_or_response_info(Packet),
|
||||
process_iq_reply(From, To, IQReply);
|
||||
_ ->
|
||||
Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST),
|
||||
ejabberd_router:route(To, From, Err),
|
||||
ok
|
||||
-spec process_iq(jid(), jid(), iq()) -> any().
|
||||
process_iq(From, To, #iq{type = T, lang = Lang, sub_els = [El]} = Packet)
|
||||
when T == get; T == set ->
|
||||
XMLNS = xmpp:get_ns(El),
|
||||
Host = To#jid.lserver,
|
||||
case ets:lookup(?IQTABLE, {XMLNS, Host}) of
|
||||
[{_, Module, Function}] ->
|
||||
gen_iq_handler:handle(Host, Module, Function, no_queue,
|
||||
From, To, Packet);
|
||||
[{_, Module, Function, Opts}] ->
|
||||
gen_iq_handler:handle(Host, Module, Function, Opts,
|
||||
From, To, Packet);
|
||||
[] ->
|
||||
Txt = <<"No module is handling this query">>,
|
||||
Err = xmpp:make_error(
|
||||
Packet,
|
||||
xmpp:err_service_unavailable(Txt, Lang)),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end;
|
||||
process_iq(From, To, #iq{type = T} = Packet) when T == get; T == set ->
|
||||
Err = xmpp:make_error(Packet, xmpp:err_bad_request()),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
process_iq(From, To, #iq{type = T} = Packet) when T == result; T == error ->
|
||||
try
|
||||
NewPacket = xmpp:decode_els(Packet),
|
||||
process_iq_reply(From, To, NewPacket)
|
||||
catch _:{xmpp_codec, Why} ->
|
||||
?DEBUG("failed to decode iq-result ~p: ~s",
|
||||
[Packet, xmpp:format_error(Why)])
|
||||
end.
|
||||
|
||||
-spec process_iq_reply(jid(), jid(), iq()) -> any().
|
||||
process_iq_reply(From, To, #iq{id = ID} = IQ) ->
|
||||
case get_iq_callback(ID) of
|
||||
{ok, undefined, Function} -> Function(IQ), ok;
|
||||
@ -110,6 +113,7 @@ process_iq_reply(From, To, #iq{id = ID} = IQ) ->
|
||||
_ -> nothing
|
||||
end.
|
||||
|
||||
-spec route(jid(), jid(), stanza()) -> any().
|
||||
route(From, To, Packet) ->
|
||||
case catch do_route(From, To, Packet) of
|
||||
{'EXIT', Reason} ->
|
||||
@ -118,26 +122,32 @@ route(From, To, Packet) ->
|
||||
_ -> ok
|
||||
end.
|
||||
|
||||
-spec route_iq(jid(), jid(), iq(), function()) -> any().
|
||||
route_iq(From, To, IQ, F) ->
|
||||
route_iq(From, To, IQ, F, undefined).
|
||||
|
||||
-spec route_iq(jid(), jid(), iq(), function(), ping_timeout()) -> any().
|
||||
route_iq(From, To, #iq{type = Type} = IQ, F, Timeout)
|
||||
when is_function(F) ->
|
||||
Packet = if Type == set; Type == get ->
|
||||
ID = randoms:get_string(),
|
||||
Host = From#jid.lserver,
|
||||
register_iq_response_handler(Host, ID, undefined, F, Timeout),
|
||||
jlib:iq_to_xml(IQ#iq{id = ID});
|
||||
IQ#iq{id = ID};
|
||||
true ->
|
||||
jlib:iq_to_xml(IQ)
|
||||
IQ
|
||||
end,
|
||||
ejabberd_router:route(From, To, Packet).
|
||||
|
||||
-spec register_iq_response_handler(binary(), binary(), module(),
|
||||
atom() | function()) -> any().
|
||||
register_iq_response_handler(Host, ID, Module,
|
||||
Function) ->
|
||||
register_iq_response_handler(Host, ID, Module, Function,
|
||||
undefined).
|
||||
|
||||
-spec register_iq_response_handler(binary(), binary(), module(),
|
||||
atom() | function(), ping_timeout()) -> any().
|
||||
register_iq_response_handler(_Host, ID, Module,
|
||||
Function, Timeout0) ->
|
||||
Timeout = case Timeout0 of
|
||||
@ -150,28 +160,35 @@ register_iq_response_handler(_Host, ID, Module,
|
||||
function = Function,
|
||||
timer = TRef}).
|
||||
|
||||
-spec register_iq_handler(binary(), binary(), module(), function()) -> any().
|
||||
register_iq_handler(Host, XMLNS, Module, Fun) ->
|
||||
ejabberd_local !
|
||||
{register_iq_handler, Host, XMLNS, Module, Fun}.
|
||||
|
||||
-spec register_iq_handler(binary(), binary(), module(), function(),
|
||||
gen_iq_handler:opts()) -> any().
|
||||
register_iq_handler(Host, XMLNS, Module, Fun, Opts) ->
|
||||
ejabberd_local !
|
||||
{register_iq_handler, Host, XMLNS, Module, Fun, Opts}.
|
||||
|
||||
-spec unregister_iq_response_handler(binary(), binary()) -> ok.
|
||||
unregister_iq_response_handler(_Host, ID) ->
|
||||
catch get_iq_callback(ID), ok.
|
||||
|
||||
-spec unregister_iq_handler(binary(), binary()) -> any().
|
||||
unregister_iq_handler(Host, XMLNS) ->
|
||||
ejabberd_local ! {unregister_iq_handler, Host, XMLNS}.
|
||||
|
||||
-spec refresh_iq_handlers() -> any().
|
||||
refresh_iq_handlers() ->
|
||||
ejabberd_local ! refresh_iq_handlers.
|
||||
|
||||
-spec bounce_resource_packet(jid(), jid(), stanza()) -> stop.
|
||||
bounce_resource_packet(From, To, Packet) ->
|
||||
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
|
||||
Lang = xmpp:get_lang(Packet),
|
||||
Txt = <<"No available resource found">>,
|
||||
Err = jlib:make_error_reply(Packet,
|
||||
?ERRT_ITEM_NOT_FOUND(Lang, Txt)),
|
||||
Err = xmpp:make_error(Packet,
|
||||
xmpp:err_item_not_found(Txt, Lang)),
|
||||
ejabberd_router:route(To, From, Err),
|
||||
stop.
|
||||
|
||||
@ -261,50 +278,45 @@ code_change(_OldVsn, State, _Extra) ->
|
||||
%%--------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%--------------------------------------------------------------------
|
||||
-spec do_route(jid(), jid(), stanza()) -> any().
|
||||
do_route(From, To, Packet) ->
|
||||
?DEBUG("local route~n\tfrom ~p~n\tto ~p~n\tpacket "
|
||||
"~P~n",
|
||||
[From, To, Packet, 8]),
|
||||
if To#jid.luser /= <<"">> ->
|
||||
ejabberd_sm:route(From, To, Packet);
|
||||
ejabberd_sm:route(From, To, Packet);
|
||||
To#jid.lresource == <<"">> ->
|
||||
#xmlel{name = Name} = Packet,
|
||||
case Name of
|
||||
<<"iq">> -> process_iq(From, To, Packet);
|
||||
<<"message">> ->
|
||||
#xmlel{attrs = Attrs} = Packet,
|
||||
case fxml:get_attr_s(<<"type">>, Attrs) of
|
||||
<<"headline">> -> ok;
|
||||
<<"error">> -> ok;
|
||||
_ ->
|
||||
Err = jlib:make_error_reply(Packet,
|
||||
?ERR_SERVICE_UNAVAILABLE),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end;
|
||||
<<"presence">> -> ok;
|
||||
_ -> ok
|
||||
end;
|
||||
case Packet of
|
||||
#iq{} ->
|
||||
process_iq(From, To, Packet);
|
||||
#message{type = T} when T /= headline, T /= error ->
|
||||
Err = xmpp:make_error(Packet, xmpp:err_service_unavailable()),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
_ -> ok
|
||||
end;
|
||||
true ->
|
||||
#xmlel{attrs = Attrs} = Packet,
|
||||
case fxml:get_attr_s(<<"type">>, Attrs) of
|
||||
<<"error">> -> ok;
|
||||
<<"result">> -> ok;
|
||||
_ ->
|
||||
ejabberd_hooks:run(local_send_to_resource_hook,
|
||||
To#jid.lserver, [From, To, Packet])
|
||||
end
|
||||
case xmpp:get_type(Packet) of
|
||||
error -> ok;
|
||||
result -> ok;
|
||||
_ ->
|
||||
ejabberd_hooks:run(local_send_to_resource_hook,
|
||||
To#jid.lserver, [From, To, Packet])
|
||||
end
|
||||
end.
|
||||
|
||||
-spec update_table() -> ok.
|
||||
update_table() ->
|
||||
case catch mnesia:table_info(iq_response, attributes) of
|
||||
[id, module, function] ->
|
||||
mnesia:delete_table(iq_response);
|
||||
mnesia:delete_table(iq_response),
|
||||
ok;
|
||||
[id, module, function, timer] ->
|
||||
ok;
|
||||
{'EXIT', _} ->
|
||||
ok
|
||||
end.
|
||||
|
||||
-spec get_iq_callback(binary()) -> {ok, module(), atom() | function()} | error.
|
||||
get_iq_callback(ID) ->
|
||||
case mnesia:dirty_read(iq_response, ID) of
|
||||
[#iq_response{module = Module, timer = TRef,
|
||||
@ -316,9 +328,11 @@ get_iq_callback(ID) ->
|
||||
error
|
||||
end.
|
||||
|
||||
-spec process_iq_timeout(binary()) -> any().
|
||||
process_iq_timeout(ID) ->
|
||||
spawn(fun process_iq_timeout/0) ! ID.
|
||||
|
||||
-spec process_iq_timeout() -> any().
|
||||
process_iq_timeout() ->
|
||||
receive
|
||||
ID ->
|
||||
@ -332,6 +346,7 @@ process_iq_timeout() ->
|
||||
ok
|
||||
end.
|
||||
|
||||
-spec cancel_timer(reference()) -> ok.
|
||||
cancel_timer(TRef) ->
|
||||
case erlang:cancel_timer(TRef) of
|
||||
false ->
|
||||
|
@ -39,6 +39,7 @@
|
||||
register_route/3,
|
||||
register_routes/1,
|
||||
host_of_route/1,
|
||||
process_iq/3,
|
||||
unregister_route/1,
|
||||
unregister_routes/1,
|
||||
dirty_get_all_routes/0,
|
||||
@ -53,7 +54,7 @@
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("xmpp.hrl").
|
||||
|
||||
-type local_hint() :: undefined | integer() | {apply, atom(), atom()}.
|
||||
|
||||
@ -71,7 +72,7 @@
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
-spec route(jid(), jid(), xmlel()) -> ok.
|
||||
-spec route(jid(), jid(), xmlel() | xmpp_element()) -> ok.
|
||||
|
||||
route(From, To, Packet) ->
|
||||
case catch do_route(From, To, Packet) of
|
||||
@ -236,6 +237,28 @@ host_of_route(Domain) ->
|
||||
end
|
||||
end.
|
||||
|
||||
-spec process_iq(jid(), jid(), iq() | xmlel()) -> any().
|
||||
process_iq(From, To, #iq{} = IQ) ->
|
||||
if To#jid.luser == <<"">> ->
|
||||
ejabberd_local:process_iq(From, To, IQ);
|
||||
true ->
|
||||
ejabberd_sm:process_iq(From, To, IQ)
|
||||
end;
|
||||
process_iq(From, To, El) ->
|
||||
try xmpp:decode(El, [ignore_els]) of
|
||||
IQ -> process_iq(From, To, IQ)
|
||||
catch _:{xmpp_codec, Why} ->
|
||||
Type = xmpp:get_type(El),
|
||||
if Type == <<"get">>; Type == <<"set">> ->
|
||||
Txt = xmpp:format_error(Why),
|
||||
Lang = xmpp:get_lang(El),
|
||||
Err = xmpp:make_error(El, xmpp:err_bad_request(Txt, Lang)),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
true ->
|
||||
ok
|
||||
end
|
||||
end.
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
@ -347,6 +370,7 @@ code_change(_OldVsn, State, _Extra) ->
|
||||
%%--------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%--------------------------------------------------------------------
|
||||
-spec do_route(jid(), jid(), xmlel() | xmpp_element()) -> any().
|
||||
do_route(OrigFrom, OrigTo, OrigPacket) ->
|
||||
?DEBUG("route~n\tfrom ~p~n\tto ~p~n\tpacket "
|
||||
"~p~n",
|
||||
@ -359,67 +383,66 @@ do_route(OrigFrom, OrigTo, OrigPacket) ->
|
||||
case mnesia:dirty_read(route, LDstDomain) of
|
||||
[] -> ejabberd_s2s:route(From, To, Packet);
|
||||
[R] ->
|
||||
Pid = R#route.pid,
|
||||
if node(Pid) == node() ->
|
||||
case R#route.local_hint of
|
||||
{apply, Module, Function} ->
|
||||
Module:Function(From, To, Packet);
|
||||
_ -> Pid ! {route, From, To, Packet}
|
||||
end;
|
||||
is_pid(Pid) -> Pid ! {route, From, To, Packet};
|
||||
true -> drop
|
||||
end;
|
||||
do_route(From, To, Packet, R);
|
||||
Rs ->
|
||||
Value = case
|
||||
ejabberd_config:get_option({domain_balancing,
|
||||
LDstDomain}, fun(D) when is_atom(D) -> D end)
|
||||
of
|
||||
undefined -> p1_time_compat:monotonic_time();
|
||||
random -> p1_time_compat:monotonic_time();
|
||||
source -> jid:tolower(From);
|
||||
destination -> jid:tolower(To);
|
||||
bare_source ->
|
||||
jid:remove_resource(jid:tolower(From));
|
||||
bare_destination ->
|
||||
jid:remove_resource(jid:tolower(To))
|
||||
end,
|
||||
Value = get_domain_balancing(From, To, LDstDomain),
|
||||
case get_component_number(LDstDomain) of
|
||||
undefined ->
|
||||
case [R || R <- Rs, node(R#route.pid) == node()] of
|
||||
[] ->
|
||||
R = lists:nth(erlang:phash(Value, length(Rs)), Rs),
|
||||
Pid = R#route.pid,
|
||||
if is_pid(Pid) -> Pid ! {route, From, To, Packet};
|
||||
true -> drop
|
||||
end;
|
||||
do_route(From, To, Packet, R);
|
||||
LRs ->
|
||||
R = lists:nth(erlang:phash(Value, length(LRs)),
|
||||
LRs),
|
||||
Pid = R#route.pid,
|
||||
case R#route.local_hint of
|
||||
{apply, Module, Function} ->
|
||||
Module:Function(From, To, Packet);
|
||||
_ -> Pid ! {route, From, To, Packet}
|
||||
end
|
||||
R = lists:nth(erlang:phash(Value, length(LRs)), LRs),
|
||||
do_route(From, To, Packet, R)
|
||||
end;
|
||||
_ ->
|
||||
SRs = lists:ukeysort(#route.local_hint, Rs),
|
||||
R = lists:nth(erlang:phash(Value, length(SRs)), SRs),
|
||||
Pid = R#route.pid,
|
||||
if is_pid(Pid) -> Pid ! {route, From, To, Packet};
|
||||
true -> drop
|
||||
end
|
||||
do_route(From, To, Packet, R)
|
||||
end
|
||||
end;
|
||||
drop -> ok
|
||||
end.
|
||||
|
||||
-spec do_route(jid(), jid(), xmlel() | xmpp_element(), #route{}) -> any().
|
||||
do_route(From, To, Packet,
|
||||
#route{local_hint = {apply, Module, Function}, pid = Pid})
|
||||
when is_pid(Pid) andalso node(Pid) == node() ->
|
||||
try
|
||||
Module:Function(From, To, xmpp:decode(Packet, [ignore_els]))
|
||||
catch error:{xmpp_codec, Why} ->
|
||||
?ERROR_MSG("failed to decode xml element ~p when "
|
||||
"routing from ~s to ~s: ~s",
|
||||
[Packet, jid:to_string(From), jid:to_string(To),
|
||||
xmpp:format_error(Why)]),
|
||||
drop
|
||||
end;
|
||||
do_route(From, To, Packet, #route{pid = Pid}) when is_pid(Pid) ->
|
||||
Pid ! {route, From, To, xmpp:encode(Packet)};
|
||||
do_route(_From, _To, _Packet, _Route) ->
|
||||
drop.
|
||||
|
||||
-spec get_component_number(binary()) -> pos_integer() | undefined.
|
||||
get_component_number(LDomain) ->
|
||||
ejabberd_config:get_option(
|
||||
{domain_balancing_component_number, LDomain},
|
||||
fun(N) when is_integer(N), N > 1 -> N end,
|
||||
undefined).
|
||||
|
||||
-spec get_domain_balancing(jid(), jid(), binary()) -> any().
|
||||
get_domain_balancing(From, To, LDomain) ->
|
||||
case ejabberd_config:get_option(
|
||||
{domain_balancing, LDomain}, fun(D) when is_atom(D) -> D end) of
|
||||
undefined -> p1_time_compat:monotonic_time();
|
||||
random -> p1_time_compat:monotonic_time();
|
||||
source -> jid:tolower(From);
|
||||
destination -> jid:tolower(To);
|
||||
bare_source -> jid:remove_resource(jid:tolower(From));
|
||||
bare_destination -> jid:remove_resource(jid:tolower(To))
|
||||
end.
|
||||
|
||||
-spec update_tables() -> ok.
|
||||
update_tables() ->
|
||||
try
|
||||
mnesia:transform_table(route, ignore, record_info(fields, route))
|
||||
|
@ -78,7 +78,8 @@
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
%%-include("jlib.hrl").
|
||||
-include("xmpp.hrl").
|
||||
|
||||
-include("ejabberd_commands.hrl").
|
||||
-include("mod_privacy.hrl").
|
||||
@ -98,6 +99,15 @@
|
||||
%% default value for the maximum number of user connections
|
||||
-define(MAX_USER_SESSIONS, infinity).
|
||||
|
||||
-type broadcast() :: {broadcast, broadcast_data()}.
|
||||
|
||||
-type broadcast_data() ::
|
||||
{rebind, pid(), binary()} | %% ejabberd_c2s
|
||||
{item, ljid(), mod_roster:subscription()} | %% mod_roster/mod_shared_roster
|
||||
{exit, binary()} | %% mod_roster/mod_shared_roster
|
||||
{privacy_list, mod_privacy:userlist(), binary()} | %% mod_privacy
|
||||
{blocking, unblock_all | {block | unblock, [ljid()]}}. %% mod_blocking
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
@ -111,7 +121,7 @@ start() ->
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
-spec route(jid(), jid(), xmlel() | broadcast()) -> ok.
|
||||
-spec route(jid(), jid(), stanza() | broadcast()) -> ok.
|
||||
|
||||
route(From, To, Packet) ->
|
||||
case catch do_route(From, To, Packet) of
|
||||
@ -162,10 +172,10 @@ check_in_subscription(Acc, User, Server, _JID, _Type, _Reason) ->
|
||||
-spec bounce_offline_message(jid(), jid(), xmlel()) -> stop.
|
||||
|
||||
bounce_offline_message(From, To, Packet) ->
|
||||
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
|
||||
Lang = xmpp:get_lang(Packet),
|
||||
Txt = <<"User session not found">>,
|
||||
Err = jlib:make_error_reply(
|
||||
Packet, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)),
|
||||
Err = xmpp:make_error(
|
||||
Packet, xmpp:err_service_unavailable(Txt, Lang)),
|
||||
ejabberd_router:route(To, From, Err),
|
||||
stop.
|
||||
|
||||
@ -432,7 +442,7 @@ online(Sessions) ->
|
||||
end, Sessions).
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
-spec do_route(jid(), jid(), stanza() | broadcast()) -> any().
|
||||
do_route(From, To, {broadcast, _} = Packet) ->
|
||||
case To#jid.lresource of
|
||||
<<"">> ->
|
||||
@ -455,25 +465,20 @@ do_route(From, To, {broadcast, _} = Packet) ->
|
||||
Pid ! {route, From, To, Packet}
|
||||
end
|
||||
end;
|
||||
do_route(From, To, #xmlel{} = Packet) ->
|
||||
do_route(From, To, Packet) ->
|
||||
?DEBUG("session manager~n\tfrom ~p~n\tto ~p~n\tpacket "
|
||||
"~P~n",
|
||||
[From, To, Packet, 8]),
|
||||
#jid{user = User, server = Server,
|
||||
luser = LUser, lserver = LServer, lresource = LResource} = To,
|
||||
#xmlel{name = Name, attrs = Attrs} = Packet,
|
||||
Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
|
||||
Lang = xmpp:get_lang(Packet),
|
||||
case LResource of
|
||||
<<"">> ->
|
||||
case Name of
|
||||
<<"presence">> ->
|
||||
{Pass, _Subsc} = case fxml:get_attr_s(<<"type">>, Attrs)
|
||||
of
|
||||
<<"subscribe">> ->
|
||||
Reason = fxml:get_path_s(Packet,
|
||||
[{elem,
|
||||
<<"status">>},
|
||||
cdata]),
|
||||
case Packet of
|
||||
#presence{type = T, status = Status} ->
|
||||
{Pass, _Subsc} = case T of
|
||||
subscribe ->
|
||||
Reason = xmpp:get_text(Status),
|
||||
{is_privacy_allow(From, To, Packet)
|
||||
andalso
|
||||
ejabberd_hooks:run_fold(roster_in_subscription,
|
||||
@ -484,7 +489,7 @@ do_route(From, To, #xmlel{} = Packet) ->
|
||||
subscribe,
|
||||
Reason]),
|
||||
true};
|
||||
<<"subscribed">> ->
|
||||
subscribed ->
|
||||
{is_privacy_allow(From, To, Packet)
|
||||
andalso
|
||||
ejabberd_hooks:run_fold(roster_in_subscription,
|
||||
@ -495,7 +500,7 @@ do_route(From, To, #xmlel{} = Packet) ->
|
||||
subscribed,
|
||||
<<"">>]),
|
||||
true};
|
||||
<<"unsubscribe">> ->
|
||||
unsubscribe ->
|
||||
{is_privacy_allow(From, To, Packet)
|
||||
andalso
|
||||
ejabberd_hooks:run_fold(roster_in_subscription,
|
||||
@ -506,7 +511,7 @@ do_route(From, To, #xmlel{} = Packet) ->
|
||||
unsubscribe,
|
||||
<<"">>]),
|
||||
true};
|
||||
<<"unsubscribed">> ->
|
||||
unsubscribed ->
|
||||
{is_privacy_allow(From, To, Packet)
|
||||
andalso
|
||||
ejabberd_hooks:run_fold(roster_in_subscription,
|
||||
@ -530,53 +535,36 @@ do_route(From, To, #xmlel{} = Packet) ->
|
||||
PResources);
|
||||
true -> ok
|
||||
end;
|
||||
<<"message">> ->
|
||||
case fxml:get_attr_s(<<"type">>, Attrs) of
|
||||
<<"chat">> -> route_message(From, To, Packet, chat);
|
||||
<<"headline">> -> route_message(From, To, Packet, headline);
|
||||
<<"error">> -> ok;
|
||||
<<"groupchat">> ->
|
||||
ErrTxt = <<"User session not found">>,
|
||||
Err = jlib:make_error_reply(
|
||||
Packet, ?ERRT_SERVICE_UNAVAILABLE(Lang, ErrTxt)),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
_ ->
|
||||
route_message(From, To, Packet, normal)
|
||||
end;
|
||||
<<"iq">> -> process_iq(From, To, Packet);
|
||||
_ -> ok
|
||||
#message{type = T} when T == chat; T == headline; T == normal ->
|
||||
route_message(From, To, Packet, T);
|
||||
#message{type = groupchat} ->
|
||||
ErrTxt = <<"User session not found">>,
|
||||
Err = xmpp:make_error(
|
||||
Packet, xmpp:err_service_unavailable(ErrTxt, Lang)),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
#iq{} -> process_iq(From, To, Packet);
|
||||
_ -> ok
|
||||
end;
|
||||
_ ->
|
||||
Mod = get_sm_backend(LServer),
|
||||
case online(Mod:get_sessions(LUser, LServer, LResource)) of
|
||||
[] ->
|
||||
case Name of
|
||||
<<"message">> ->
|
||||
case fxml:get_attr_s(<<"type">>, Attrs) of
|
||||
<<"chat">> -> route_message(From, To, Packet, chat);
|
||||
<<"headline">> -> ok;
|
||||
<<"error">> -> ok;
|
||||
<<"groupchat">> ->
|
||||
ErrTxt = <<"User session not found">>,
|
||||
Err = jlib:make_error_reply(
|
||||
Packet,
|
||||
?ERRT_SERVICE_UNAVAILABLE(Lang, ErrTxt)),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
_ ->
|
||||
route_message(From, To, Packet, normal)
|
||||
end;
|
||||
<<"iq">> ->
|
||||
case fxml:get_attr_s(<<"type">>, Attrs) of
|
||||
<<"error">> -> ok;
|
||||
<<"result">> -> ok;
|
||||
_ ->
|
||||
ErrTxt = <<"User session not found">>,
|
||||
Err = jlib:make_error_reply(
|
||||
Packet,
|
||||
?ERRT_SERVICE_UNAVAILABLE(Lang, ErrTxt)),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end;
|
||||
_ -> ?DEBUG("packet dropped~n", [])
|
||||
case Packet of
|
||||
#message{type = T} when T == chat; T == normal ->
|
||||
route_message(From, To, Packet, T);
|
||||
#message{type = groupchat} ->
|
||||
ErrTxt = <<"User session not found">>,
|
||||
Err = xmpp:make_error(
|
||||
Packet,
|
||||
xmpp:err_service_unavailable(ErrTxt, Lang)),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
#iq{type = T} when T == get; T == set ->
|
||||
ErrTxt = <<"User session not found">>,
|
||||
Err = xmpp:make_error(
|
||||
Packet,
|
||||
xmpp:err_service_unavailable(ErrTxt, Lang)),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
_ -> ?DEBUG("packet dropped~n", [])
|
||||
end;
|
||||
Ss ->
|
||||
Session = lists:max(Ss),
|
||||
@ -590,6 +578,7 @@ do_route(From, To, #xmlel{} = Packet) ->
|
||||
%% and is processed if there is no active list set
|
||||
%% for the target session/resource to which a stanza is addressed,
|
||||
%% or if there are no current sessions for the user.
|
||||
-spec is_privacy_allow(jid(), jid(), stanza()) -> boolean().
|
||||
is_privacy_allow(From, To, Packet) ->
|
||||
User = To#jid.user,
|
||||
Server = To#jid.server,
|
||||
@ -600,6 +589,7 @@ is_privacy_allow(From, To, Packet) ->
|
||||
|
||||
%% Check if privacy rules allow this delivery
|
||||
%% Function copied from ejabberd_c2s.erl
|
||||
-spec is_privacy_allow(jid(), jid(), stanza(), #userlist{}) -> boolean().
|
||||
is_privacy_allow(From, To, Packet, PrivacyList) ->
|
||||
User = To#jid.user,
|
||||
Server = To#jid.server,
|
||||
@ -609,6 +599,7 @@ is_privacy_allow(From, To, Packet, PrivacyList) ->
|
||||
[User, Server, PrivacyList, {From, To, Packet},
|
||||
in]).
|
||||
|
||||
-spec route_message(jid(), jid(), message(), message_type()) -> any().
|
||||
route_message(From, To, Packet, Type) ->
|
||||
LUser = To#jid.luser,
|
||||
LServer = To#jid.lserver,
|
||||
@ -644,18 +635,19 @@ route_message(From, To, Packet, Type) ->
|
||||
ejabberd_hooks:run(offline_message_hook, LServer,
|
||||
[From, To, Packet]);
|
||||
false ->
|
||||
Err = jlib:make_error_reply(Packet,
|
||||
?ERR_SERVICE_UNAVAILABLE),
|
||||
Err = xmpp:make_error(Packet,
|
||||
xmpp:err_service_unavailable()),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end
|
||||
end
|
||||
end.
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
-spec clean_session_list([#session{}]) -> [#session{}].
|
||||
clean_session_list(Ss) ->
|
||||
clean_session_list(lists:keysort(#session.usr, Ss), []).
|
||||
|
||||
-spec clean_session_list([#session{}], [#session{}]) -> [#session{}].
|
||||
clean_session_list([], Res) -> Res;
|
||||
clean_session_list([S], Res) -> [S | Res];
|
||||
clean_session_list([S1, S2 | Rest], Res) ->
|
||||
@ -670,6 +662,7 @@ clean_session_list([S1, S2 | Rest], Res) ->
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
%% On new session, check if some existing connections need to be replace
|
||||
-spec check_for_sessions_to_replace(binary(), binary(), binary()) -> ok | replaced.
|
||||
check_for_sessions_to_replace(User, Server, Resource) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
@ -677,6 +670,7 @@ check_for_sessions_to_replace(User, Server, Resource) ->
|
||||
check_existing_resources(LUser, LServer, LResource),
|
||||
check_max_sessions(LUser, LServer).
|
||||
|
||||
-spec check_existing_resources(binary(), binary(), binary()) -> ok.
|
||||
check_existing_resources(LUser, LServer, LResource) ->
|
||||
SIDs = get_resource_sessions(LUser, LServer, LResource),
|
||||
if SIDs == [] -> ok;
|
||||
@ -698,6 +692,7 @@ check_existing_resources(LUser, LServer, LResource) ->
|
||||
is_existing_resource(LUser, LServer, LResource) ->
|
||||
[] /= get_resource_sessions(LUser, LServer, LResource).
|
||||
|
||||
-spec get_resource_sessions(binary(), binary(), binary()) -> [sid()].
|
||||
get_resource_sessions(User, Server, Resource) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
@ -705,6 +700,7 @@ get_resource_sessions(User, Server, Resource) ->
|
||||
Mod = get_sm_backend(LServer),
|
||||
[S#session.sid || S <- online(Mod:get_sessions(LUser, LServer, LResource))].
|
||||
|
||||
-spec check_max_sessions(binary(), binary()) -> ok | replaced.
|
||||
check_max_sessions(LUser, LServer) ->
|
||||
Mod = get_sm_backend(LServer),
|
||||
SIDs = [S#session.sid || S <- online(Mod:get_sessions(LUser, LServer))],
|
||||
@ -717,6 +713,7 @@ check_max_sessions(LUser, LServer) ->
|
||||
%% This option defines the max number of time a given users are allowed to
|
||||
%% log in
|
||||
%% Defaults to infinity
|
||||
-spec get_max_user_sessions(binary(), binary()) -> infinity | non_neg_integer().
|
||||
get_max_user_sessions(LUser, Host) ->
|
||||
case acl:match_rule(Host, max_user_sessions,
|
||||
jid:make(LUser, Host, <<"">>))
|
||||
@ -728,34 +725,31 @@ get_max_user_sessions(LUser, Host) ->
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
process_iq(From, To, Packet) ->
|
||||
IQ = jlib:iq_query_info(Packet),
|
||||
case IQ of
|
||||
#iq{xmlns = XMLNS, lang = Lang} ->
|
||||
Host = To#jid.lserver,
|
||||
case ets:lookup(sm_iqtable, {XMLNS, Host}) of
|
||||
[{_, Module, Function}] ->
|
||||
ResIQ = Module:Function(From, To, IQ),
|
||||
if ResIQ /= ignore ->
|
||||
ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ));
|
||||
true -> ok
|
||||
end;
|
||||
[{_, Module, Function, Opts}] ->
|
||||
gen_iq_handler:handle(Host, Module, Function, Opts,
|
||||
From, To, IQ);
|
||||
[] ->
|
||||
Txt = <<"No module is handling this query">>,
|
||||
Err = jlib:make_error_reply(
|
||||
Packet,
|
||||
?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end;
|
||||
reply -> ok;
|
||||
_ ->
|
||||
Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST),
|
||||
ejabberd_router:route(To, From, Err),
|
||||
ok
|
||||
end.
|
||||
-spec process_iq(jid(), jid(), iq()) -> any().
|
||||
process_iq(From, To, #iq{type = T, lang = Lang, sub_els = [El]} = Packet)
|
||||
when T == get; T == set ->
|
||||
XMLNS = xmpp:get_ns(El),
|
||||
Host = To#jid.lserver,
|
||||
case ets:lookup(sm_iqtable, {XMLNS, Host}) of
|
||||
[{_, Module, Function}] ->
|
||||
gen_iq_handler:handle(Host, Module, Function, no_queue,
|
||||
From, To, Packet);
|
||||
[{_, Module, Function, Opts}] ->
|
||||
gen_iq_handler:handle(Host, Module, Function, Opts,
|
||||
From, To, Packet);
|
||||
[] ->
|
||||
Txt = <<"No module is handling this query">>,
|
||||
Err = xmpp:make_error(
|
||||
Packet,
|
||||
xmpp:err_service_unavailable(Txt, Lang)),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end;
|
||||
process_iq(From, To, #iq{type = T} = Packet) when T == get; T == set ->
|
||||
Err = xmpp:make_error(Packet, xmpp:err_bad_request()),
|
||||
ejabberd_router:route(To, From, Err),
|
||||
ok;
|
||||
process_iq(_From, _To, #iq{}) ->
|
||||
ok.
|
||||
|
||||
-spec force_update_presence({binary(), binary()}) -> any().
|
||||
|
||||
|
@ -41,6 +41,7 @@
|
||||
change_shaper/2,
|
||||
monitor/1,
|
||||
get_sockmod/1,
|
||||
get_transport/1,
|
||||
get_peer_certificate/1,
|
||||
get_verify_result/1,
|
||||
close/1,
|
||||
@ -215,6 +216,20 @@ monitor(SocketData)
|
||||
get_sockmod(SocketData) ->
|
||||
SocketData#socket_state.sockmod.
|
||||
|
||||
get_transport(#socket_state{sockmod = SockMod,
|
||||
socket = Socket}) ->
|
||||
case SockMod of
|
||||
gen_tcp -> tcp;
|
||||
fast_tls -> tls;
|
||||
ezlib ->
|
||||
case ezlib:get_sockmod(Socket) of
|
||||
tcp -> tcp_zlib;
|
||||
tls -> tls_zlib
|
||||
end;
|
||||
ejabberd_http_bind -> http_bind;
|
||||
ejabberd_http_ws -> websocket
|
||||
end.
|
||||
|
||||
get_peer_certificate(SocketData) ->
|
||||
fast_tls:get_peer_certificate(SocketData#socket_state.socket).
|
||||
|
||||
|
@ -41,7 +41,7 @@
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("xmpp.hrl").
|
||||
|
||||
-record(state, {}).
|
||||
|
||||
@ -64,18 +64,16 @@ start_link() ->
|
||||
process_command(From, To, Packet) ->
|
||||
case To of
|
||||
#jid{luser = <<"">>, lresource = <<"watchdog">>} ->
|
||||
#xmlel{name = Name} = Packet,
|
||||
case Name of
|
||||
<<"message">> ->
|
||||
case Packet of
|
||||
#message{body = Body} ->
|
||||
LFrom =
|
||||
jid:tolower(jid:remove_resource(From)),
|
||||
case lists:member(LFrom, get_admin_jids()) of
|
||||
true ->
|
||||
Body = fxml:get_path_s(Packet,
|
||||
[{elem, <<"body">>}, cdata]),
|
||||
BodyText = xmpp:get_text(Body),
|
||||
spawn(fun () ->
|
||||
process_flag(priority, high),
|
||||
process_command1(From, To, Body)
|
||||
process_command1(From, To, BodyText)
|
||||
end),
|
||||
stop;
|
||||
false -> ok
|
||||
@ -186,24 +184,20 @@ process_large_heap(Pid, Info) ->
|
||||
"much memory:~n~p~n~s",
|
||||
[node(), Pid, Info, DetailedInfo])),
|
||||
From = jid:make(<<"">>, Host, <<"watchdog">>),
|
||||
Hint = [#xmlel{name = <<"no-permanent-store">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_HINTS}]}],
|
||||
lists:foreach(fun (JID) ->
|
||||
send_message(From, jid:make(JID), Body, Hint)
|
||||
end, JIDs).
|
||||
Hint = [#hint{type = 'no-permanent-store'}],
|
||||
lists:foreach(
|
||||
fun(JID) ->
|
||||
send_message(From, jid:make(JID), Body, Hint)
|
||||
end, JIDs).
|
||||
|
||||
send_message(From, To, Body) ->
|
||||
send_message(From, To, Body, []).
|
||||
|
||||
send_message(From, To, Body, ExtraEls) ->
|
||||
ejabberd_router:route(From, To,
|
||||
#xmlel{name = <<"message">>,
|
||||
attrs = [{<<"type">>, <<"chat">>}],
|
||||
children =
|
||||
[#xmlel{name = <<"body">>, attrs = [],
|
||||
children =
|
||||
[{xmlcdata, Body}]}
|
||||
| ExtraEls]}).
|
||||
#message{type = chat,
|
||||
body = xmpp:mk_text(Body),
|
||||
sub_els = ExtraEls}).
|
||||
|
||||
get_admin_jids() ->
|
||||
ejabberd_config:get_option(
|
||||
|
@ -40,7 +40,7 @@
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("xmpp.hrl").
|
||||
|
||||
-record(state, {host, module, function}).
|
||||
|
||||
@ -59,6 +59,8 @@ start_link(Host, Module, Function) ->
|
||||
gen_server:start_link(?MODULE, [Host, Module, Function],
|
||||
[]).
|
||||
|
||||
-spec add_iq_handler(module(), binary(), binary(), module(), atom(), type()) -> any().
|
||||
|
||||
add_iq_handler(Component, Host, NS, Module, Function,
|
||||
Type) ->
|
||||
case Type of
|
||||
@ -124,14 +126,49 @@ handle(Host, Module, Function, Opts, From, To, IQ) ->
|
||||
|
||||
-spec process_iq(binary(), atom(), atom(), jid(), jid(), iq()) -> any().
|
||||
|
||||
process_iq(_Host, Module, Function, From, To, IQ) ->
|
||||
case catch Module:Function(From, To, IQ) of
|
||||
{'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]);
|
||||
ResIQ ->
|
||||
if ResIQ /= ignore ->
|
||||
ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ));
|
||||
true -> ok
|
||||
end
|
||||
process_iq(_Host, Module, Function, From, To, IQ0) ->
|
||||
IQ = xmpp:set_from_to(IQ0, From, To),
|
||||
try
|
||||
ResIQ = case erlang:function_exported(Module, Function, 1) of
|
||||
true ->
|
||||
process_iq(Module, Function, IQ);
|
||||
false ->
|
||||
process_iq(Module, Function, From, To,
|
||||
jlib:iq_query_info(xmpp:encode(IQ)))
|
||||
end,
|
||||
if ResIQ /= ignore ->
|
||||
ejabberd_router:route(To, From, ResIQ);
|
||||
true ->
|
||||
ok
|
||||
end
|
||||
catch E:R ->
|
||||
?ERROR_MSG("failed to process iq:~n~s~nReason = ~p",
|
||||
[xmpp_codec:pp(IQ), {E, {R, erlang:get_stacktrace()}}]),
|
||||
Txt = <<"Module failed to handle the query">>,
|
||||
Err = xmpp:err_internal_server_error(Txt, IQ#iq.lang),
|
||||
ejabberd_router:route(To, From, xmpp:make_error(IQ, Err))
|
||||
end.
|
||||
|
||||
-spec process_iq(module(), atom(), iq()) -> ignore | iq().
|
||||
process_iq(Module, Function, #iq{lang = Lang, sub_els = [El]} = IQ) ->
|
||||
try
|
||||
%% TODO: move this 'conditional' decoding somewhere
|
||||
%% IQ handler should know *nothing* about vCards.
|
||||
Pkt = case xmpp:get_ns(El) of
|
||||
?NS_VCARD -> El;
|
||||
_ -> xmpp:decode(El)
|
||||
end,
|
||||
Module:Function(IQ#iq{sub_els = [Pkt]})
|
||||
catch error:{xmpp_codec, Why} ->
|
||||
Txt = xmpp:format_error(Why),
|
||||
xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang))
|
||||
end.
|
||||
|
||||
-spec process_iq(module(), atom(), jid(), jid(), term()) -> iq().
|
||||
process_iq(Module, Function, From, To, IQ) ->
|
||||
case Module:Function(From, To, IQ) of
|
||||
ignore -> ignore;
|
||||
ResIQ -> xmpp:decode(jlib:iq_to_xml(ResIQ), [ignore_els])
|
||||
end.
|
||||
|
||||
-spec check_type(type()) -> type().
|
||||
|
15
src/jid.erl
15
src/jid.erl
@ -28,6 +28,7 @@
|
||||
%% API
|
||||
-export([start/0,
|
||||
make/1,
|
||||
make/2,
|
||||
make/3,
|
||||
split/1,
|
||||
from_string/1,
|
||||
@ -40,7 +41,7 @@
|
||||
remove_resource/1,
|
||||
replace_resource/2]).
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("jid.hrl").
|
||||
|
||||
-export_type([jid/0]).
|
||||
|
||||
@ -74,10 +75,16 @@ make(User, Server, Resource) ->
|
||||
end
|
||||
end.
|
||||
|
||||
-spec make({binary(), binary(), binary()}) -> jid() | error.
|
||||
-spec make(binary(), binary()) -> jid() | error.
|
||||
make(User, Server) ->
|
||||
make(User, Server, <<"">>).
|
||||
|
||||
-spec make({binary(), binary(), binary()} | binary()) -> jid() | error.
|
||||
|
||||
make({User, Server, Resource}) ->
|
||||
make(User, Server, Resource).
|
||||
make(User, Server, Resource);
|
||||
make(Server) ->
|
||||
make(<<"">>, Server, <<"">>).
|
||||
|
||||
%% This is the reverse of make_jid/1
|
||||
-spec split(jid()) -> {binary(), binary(), binary()} | error.
|
||||
@ -93,6 +100,8 @@ from_string(S) when is_list(S) ->
|
||||
%% using binaries for string. However, we do not let it crash to avoid
|
||||
%% losing associated ets table.
|
||||
{error, need_jid_as_binary};
|
||||
from_string(<<>>) ->
|
||||
error;
|
||||
from_string(S) when is_binary(S) ->
|
||||
SplitPattern = ets:lookup_element(jlib, string_to_jid_pattern, 2),
|
||||
Size = size(S),
|
||||
|
292
src/mod_caps.erl
292
src/mod_caps.erl
@ -35,6 +35,8 @@
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-compile(export_all).
|
||||
|
||||
-export([read_caps/1, caps_stream_features/2,
|
||||
disco_features/5, disco_identity/5, disco_info/5,
|
||||
get_features/2, export/1, import_info/0, import/5,
|
||||
@ -54,24 +56,12 @@
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("xmpp.hrl").
|
||||
|
||||
-define(PROCNAME, ejabberd_mod_caps).
|
||||
|
||||
-define(BAD_HASH_LIFETIME, 600).
|
||||
|
||||
-record(caps,
|
||||
{
|
||||
node = <<"">> :: binary(),
|
||||
version = <<"">> :: binary(),
|
||||
hash = <<"">> :: binary(),
|
||||
exts = [] :: [binary()]
|
||||
}).
|
||||
|
||||
-type caps() :: #caps{}.
|
||||
|
||||
-export_type([caps/0]).
|
||||
|
||||
-record(caps_features,
|
||||
{
|
||||
node_pair = {<<"">>, <<"">>} :: {binary(), binary()},
|
||||
@ -103,6 +93,7 @@ stop(Host) ->
|
||||
supervisor:terminate_child(ejabberd_sup, Proc),
|
||||
supervisor:delete_child(ejabberd_sup, Proc).
|
||||
|
||||
-spec get_features(binary(), nothing | caps()) -> [binary()].
|
||||
get_features(_Host, nothing) -> [];
|
||||
get_features(Host, #caps{node = Node, version = Version,
|
||||
exts = Exts}) ->
|
||||
@ -119,65 +110,37 @@ get_features(Host, #caps{node = Node, version = Version,
|
||||
end,
|
||||
[], SubNodes).
|
||||
|
||||
-spec read_caps([xmlel()]) -> nothing | caps().
|
||||
-spec read_caps(#presence{}) -> nothing | caps().
|
||||
read_caps(Presence) ->
|
||||
case xmpp:get_subtag(Presence, #caps{}) of
|
||||
false -> nothing;
|
||||
Caps -> Caps
|
||||
end.
|
||||
|
||||
read_caps(Els) -> read_caps(Els, nothing).
|
||||
|
||||
read_caps([#xmlel{name = <<"c">>, attrs = Attrs}
|
||||
| Tail],
|
||||
Result) ->
|
||||
case fxml:get_attr_s(<<"xmlns">>, Attrs) of
|
||||
?NS_CAPS ->
|
||||
Node = fxml:get_attr_s(<<"node">>, Attrs),
|
||||
Version = fxml:get_attr_s(<<"ver">>, Attrs),
|
||||
Hash = fxml:get_attr_s(<<"hash">>, Attrs),
|
||||
Exts = str:tokens(fxml:get_attr_s(<<"ext">>, Attrs),
|
||||
<<" ">>),
|
||||
read_caps(Tail,
|
||||
#caps{node = Node, hash = Hash, version = Version,
|
||||
exts = Exts});
|
||||
_ -> read_caps(Tail, Result)
|
||||
end;
|
||||
read_caps([#xmlel{name = <<"x">>, attrs = Attrs}
|
||||
| Tail],
|
||||
Result) ->
|
||||
case fxml:get_attr_s(<<"xmlns">>, Attrs) of
|
||||
?NS_MUC_USER -> nothing;
|
||||
_ -> read_caps(Tail, Result)
|
||||
end;
|
||||
read_caps([_ | Tail], Result) ->
|
||||
read_caps(Tail, Result);
|
||||
read_caps([], Result) -> Result.
|
||||
|
||||
user_send_packet(#xmlel{name = <<"presence">>, attrs = Attrs,
|
||||
children = Els} = Pkt,
|
||||
-spec user_send_packet(stanza(), ejabberd_c2s:state(), jid(), jid()) -> stanza().
|
||||
user_send_packet(#presence{type = available} = Pkt,
|
||||
_C2SState,
|
||||
#jid{luser = User, lserver = Server} = From,
|
||||
#jid{luser = User, lserver = Server,
|
||||
lresource = <<"">>}) ->
|
||||
Type = fxml:get_attr_s(<<"type">>, Attrs),
|
||||
if Type == <<"">>; Type == <<"available">> ->
|
||||
case read_caps(Els) of
|
||||
nothing -> ok;
|
||||
#caps{version = Version, exts = Exts} = Caps ->
|
||||
feature_request(Server, From, Caps, [Version | Exts])
|
||||
end;
|
||||
true -> ok
|
||||
case read_caps(Pkt) of
|
||||
nothing -> ok;
|
||||
#caps{version = Version, exts = Exts} = Caps ->
|
||||
feature_request(Server, From, Caps, [Version | Exts])
|
||||
end,
|
||||
Pkt;
|
||||
user_send_packet(Pkt, _C2SState, _From, _To) ->
|
||||
Pkt.
|
||||
|
||||
user_receive_packet(#xmlel{name = <<"presence">>, attrs = Attrs,
|
||||
children = Els} = Pkt,
|
||||
-spec user_receive_packet(stanza(), ejabberd_c2s:state(),
|
||||
jid(), jid(), jid()) -> stanza().
|
||||
user_receive_packet(#presence{type = available} = Pkt,
|
||||
_C2SState,
|
||||
#jid{lserver = Server},
|
||||
From, _To) ->
|
||||
Type = fxml:get_attr_s(<<"type">>, Attrs),
|
||||
IsRemote = not lists:member(From#jid.lserver, ?MYHOSTS),
|
||||
if IsRemote and
|
||||
((Type == <<"">>) or (Type == <<"available">>)) ->
|
||||
case read_caps(Els) of
|
||||
if IsRemote ->
|
||||
case read_caps(Pkt) of
|
||||
nothing -> ok;
|
||||
#caps{version = Version, exts = Exts} = Caps ->
|
||||
feature_request(Server, From, Caps, [Version | Exts])
|
||||
@ -188,58 +151,62 @@ user_receive_packet(#xmlel{name = <<"presence">>, attrs = Attrs,
|
||||
user_receive_packet(Pkt, _C2SState, _JID, _From, _To) ->
|
||||
Pkt.
|
||||
|
||||
-spec caps_stream_features([xmlel()], binary()) -> [xmlel()].
|
||||
-spec caps_stream_features([xmpp_element()], binary()) -> [xmpp_element()].
|
||||
|
||||
caps_stream_features(Acc, MyHost) ->
|
||||
case make_my_disco_hash(MyHost) of
|
||||
<<"">> -> Acc;
|
||||
Hash ->
|
||||
[#xmlel{name = <<"c">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>, ?NS_CAPS}, {<<"hash">>, <<"sha-1">>},
|
||||
{<<"node">>, ?EJABBERD_URI}, {<<"ver">>, Hash}],
|
||||
children = []}
|
||||
| Acc]
|
||||
[#caps{hash = <<"sha-1">>, node = ?EJABBERD_URI, version = Hash}|Acc]
|
||||
end.
|
||||
|
||||
-spec disco_features({error, error()} | {result, [binary()]} | empty,
|
||||
jid(), jid(),
|
||||
undefined | binary(), undefined | binary()) ->
|
||||
{error, error()} | {result, [binary()]}.
|
||||
disco_features(Acc, From, To, Node, Lang) ->
|
||||
case is_valid_node(Node) of
|
||||
true ->
|
||||
ejabberd_hooks:run_fold(disco_local_features,
|
||||
To#jid.lserver, empty,
|
||||
[From, To, <<"">>, Lang]);
|
||||
[From, To, undefined, Lang]);
|
||||
false ->
|
||||
Acc
|
||||
end.
|
||||
|
||||
-spec disco_identity([identity()], jid(), jid(),
|
||||
undefined | binary(), undefined | binary()) ->
|
||||
[identity()].
|
||||
disco_identity(Acc, From, To, Node, Lang) ->
|
||||
case is_valid_node(Node) of
|
||||
true ->
|
||||
ejabberd_hooks:run_fold(disco_local_identity,
|
||||
To#jid.lserver, [],
|
||||
[From, To, <<"">>, Lang]);
|
||||
[From, To, undefined, Lang]);
|
||||
false ->
|
||||
Acc
|
||||
end.
|
||||
|
||||
-spec disco_info([xdata()], binary(), module(),
|
||||
undefined | binary(), undefined | binary()) -> [xdata()].
|
||||
disco_info(Acc, Host, Module, Node, Lang) ->
|
||||
case is_valid_node(Node) of
|
||||
true ->
|
||||
ejabberd_hooks:run_fold(disco_info, Host, [],
|
||||
[Host, Module, <<"">>, Lang]);
|
||||
[Host, Module, undefined, Lang]);
|
||||
false ->
|
||||
Acc
|
||||
end.
|
||||
|
||||
-spec c2s_presence_in(ejabberd_c2s:state(), {jid(), jid(), presence()}) ->
|
||||
ejabberd_c2s:state().
|
||||
c2s_presence_in(C2SState,
|
||||
{From, To, {_, _, Attrs, Els}}) ->
|
||||
Type = fxml:get_attr_s(<<"type">>, Attrs),
|
||||
{From, To, #presence{type = Type} = Presence}) ->
|
||||
Subscription = ejabberd_c2s:get_subscription(From,
|
||||
C2SState),
|
||||
Insert = ((Type == <<"">>) or (Type == <<"available">>))
|
||||
Insert = (Type == available)
|
||||
and ((Subscription == both) or (Subscription == to)),
|
||||
Delete = (Type == <<"unavailable">>) or
|
||||
(Type == <<"error">>),
|
||||
Delete = (Type == unavailable) or (Type == error),
|
||||
if Insert or Delete ->
|
||||
LFrom = jid:tolower(From),
|
||||
Rs = case ejabberd_c2s:get_aux_field(caps_resources,
|
||||
@ -248,7 +215,7 @@ c2s_presence_in(C2SState,
|
||||
{ok, Rs1} -> Rs1;
|
||||
error -> gb_trees:empty()
|
||||
end,
|
||||
Caps = read_caps(Els),
|
||||
Caps = read_caps(Presence),
|
||||
NewRs = case Caps of
|
||||
nothing when Insert == true -> Rs;
|
||||
_ when Insert == true ->
|
||||
@ -272,6 +239,9 @@ c2s_presence_in(C2SState,
|
||||
true -> C2SState
|
||||
end.
|
||||
|
||||
-spec c2s_filter_packet(boolean(), binary(), ejabberd_c2s:state(),
|
||||
{pep_message, binary()}, jid(), stanza()) ->
|
||||
boolean().
|
||||
c2s_filter_packet(InAcc, Host, C2SState, {pep_message, Feature}, To, _Packet) ->
|
||||
case ejabberd_c2s:get_aux_field(caps_resources, C2SState) of
|
||||
{ok, Rs} ->
|
||||
@ -287,6 +257,9 @@ c2s_filter_packet(InAcc, Host, C2SState, {pep_message, Feature}, To, _Packet) ->
|
||||
end;
|
||||
c2s_filter_packet(Acc, _, _, _, _, _) -> Acc.
|
||||
|
||||
-spec c2s_broadcast_recipients([ljid()], binary(), ejabberd_c2s:state(),
|
||||
{pep_message, binary()}, jid(), stanza()) ->
|
||||
[ljid()].
|
||||
c2s_broadcast_recipients(InAcc, Host, C2SState,
|
||||
{pep_message, Feature}, _From, _Packet) ->
|
||||
case ejabberd_c2s:get_aux_field(caps_resources,
|
||||
@ -377,6 +350,7 @@ terminate(_Reason, State) ->
|
||||
|
||||
code_change(_OldVsn, State, _Extra) -> {ok, State}.
|
||||
|
||||
-spec feature_request(binary(), jid(), caps(), [binary()]) -> any().
|
||||
feature_request(Host, From, Caps,
|
||||
[SubNode | Tail] = SubNodes) ->
|
||||
Node = Caps#caps.node,
|
||||
@ -392,15 +366,9 @@ feature_request(Host, From, Caps,
|
||||
_ -> true
|
||||
end,
|
||||
if NeedRequest ->
|
||||
IQ = #iq{type = get, xmlns = ?NS_DISCO_INFO,
|
||||
sub_el =
|
||||
[#xmlel{name = <<"query">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>, ?NS_DISCO_INFO},
|
||||
{<<"node">>,
|
||||
<<Node/binary, "#",
|
||||
SubNode/binary>>}],
|
||||
children = []}]},
|
||||
IQ = #iq{type = get,
|
||||
sub_els = [#disco_info{node = <<Node/binary, "#",
|
||||
SubNode/binary>>}]},
|
||||
cache_tab:insert(caps_features, NodePair, now_ts(),
|
||||
caps_write_fun(Host, NodePair, now_ts())),
|
||||
F = fun (IQReply) ->
|
||||
@ -415,39 +383,41 @@ feature_request(Host, From, Caps,
|
||||
end;
|
||||
feature_request(_Host, _From, _Caps, []) -> ok.
|
||||
|
||||
feature_response(#iq{type = result,
|
||||
sub_el = [#xmlel{children = Els}]},
|
||||
-spec feature_response(iq(), binary(), jid(), caps(), [binary()]) -> any().
|
||||
feature_response(#iq{type = result, sub_els = [El]},
|
||||
Host, From, Caps, [SubNode | SubNodes]) ->
|
||||
NodePair = {Caps#caps.node, SubNode},
|
||||
case check_hash(Caps, Els) of
|
||||
true ->
|
||||
Features = lists:flatmap(fun (#xmlel{name =
|
||||
<<"feature">>,
|
||||
attrs = FAttrs}) ->
|
||||
[fxml:get_attr_s(<<"var">>, FAttrs)];
|
||||
(_) -> []
|
||||
end,
|
||||
Els),
|
||||
cache_tab:insert(caps_features, NodePair,
|
||||
Features,
|
||||
caps_write_fun(Host, NodePair, Features));
|
||||
false -> ok
|
||||
try
|
||||
DiscoInfo = xmpp:decode(El),
|
||||
case check_hash(Caps, DiscoInfo) of
|
||||
true ->
|
||||
Features = DiscoInfo#disco_info.features,
|
||||
cache_tab:insert(caps_features, NodePair,
|
||||
Features,
|
||||
caps_write_fun(Host, NodePair, Features));
|
||||
false -> ok
|
||||
end
|
||||
catch _:{xmpp_codec, _Why} ->
|
||||
ok
|
||||
end,
|
||||
feature_request(Host, From, Caps, SubNodes);
|
||||
feature_response(_IQResult, Host, From, Caps,
|
||||
[_SubNode | SubNodes]) ->
|
||||
feature_request(Host, From, Caps, SubNodes).
|
||||
|
||||
-spec caps_read_fun(binary(), binary()) -> function().
|
||||
caps_read_fun(Host, Node) ->
|
||||
LServer = jid:nameprep(Host),
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
fun() -> Mod:caps_read(LServer, Node) end.
|
||||
|
||||
-spec caps_write_fun(binary(), binary(), [binary()]) -> function().
|
||||
caps_write_fun(Host, Node, Features) ->
|
||||
LServer = jid:nameprep(Host),
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
fun() -> Mod:caps_write(LServer, Node, Features) end.
|
||||
|
||||
-spec make_my_disco_hash(binary()) -> binary().
|
||||
make_my_disco_hash(Host) ->
|
||||
JID = jid:make(<<"">>, Host, <<"">>),
|
||||
case {ejabberd_hooks:run_fold(disco_local_features,
|
||||
@ -458,119 +428,70 @@ make_my_disco_hash(Host) ->
|
||||
[Host, undefined, <<"">>, <<"">>])}
|
||||
of
|
||||
{{result, Features}, Identities, Info} ->
|
||||
Feats = lists:map(fun ({{Feat, _Host}}) ->
|
||||
#xmlel{name = <<"feature">>,
|
||||
attrs = [{<<"var">>, Feat}],
|
||||
children = []};
|
||||
(Feat) ->
|
||||
#xmlel{name = <<"feature">>,
|
||||
attrs = [{<<"var">>, Feat}],
|
||||
children = []}
|
||||
Feats = lists:map(fun ({{Feat, _Host}}) -> Feat;
|
||||
(Feat) -> Feat
|
||||
end,
|
||||
Features),
|
||||
make_disco_hash(Identities ++ Info ++ Feats, sha1);
|
||||
DiscoInfo = #disco_info{identities = Identities,
|
||||
features = Feats,
|
||||
xdata = Info},
|
||||
make_disco_hash(DiscoInfo, sha);
|
||||
_Err -> <<"">>
|
||||
end.
|
||||
|
||||
make_disco_hash(DiscoEls, Algo) ->
|
||||
Concat = list_to_binary([concat_identities(DiscoEls),
|
||||
concat_features(DiscoEls), concat_info(DiscoEls)]),
|
||||
-spec make_disco_hash(disco_info(), crypto:digest_type()) -> binary().
|
||||
|
||||
make_disco_hash(DiscoInfo, Algo) ->
|
||||
Concat = list_to_binary([concat_identities(DiscoInfo),
|
||||
concat_features(DiscoInfo), concat_info(DiscoInfo)]),
|
||||
jlib:encode_base64(case Algo of
|
||||
md5 -> erlang:md5(Concat);
|
||||
sha1 -> p1_sha:sha1(Concat);
|
||||
sha -> p1_sha:sha1(Concat);
|
||||
sha224 -> p1_sha:sha224(Concat);
|
||||
sha256 -> p1_sha:sha256(Concat);
|
||||
sha384 -> p1_sha:sha384(Concat);
|
||||
sha512 -> p1_sha:sha512(Concat)
|
||||
end).
|
||||
|
||||
check_hash(Caps, Els) ->
|
||||
-spec check_hash(caps(), disco_info()) -> boolean().
|
||||
check_hash(Caps, DiscoInfo) ->
|
||||
case Caps#caps.hash of
|
||||
<<"md5">> ->
|
||||
Caps#caps.version == make_disco_hash(Els, md5);
|
||||
Caps#caps.version == make_disco_hash(DiscoInfo, md5);
|
||||
<<"sha-1">> ->
|
||||
Caps#caps.version == make_disco_hash(Els, sha1);
|
||||
Caps#caps.version == make_disco_hash(DiscoInfo, sha);
|
||||
<<"sha-224">> ->
|
||||
Caps#caps.version == make_disco_hash(Els, sha224);
|
||||
Caps#caps.version == make_disco_hash(DiscoInfo, sha224);
|
||||
<<"sha-256">> ->
|
||||
Caps#caps.version == make_disco_hash(Els, sha256);
|
||||
Caps#caps.version == make_disco_hash(DiscoInfo, sha256);
|
||||
<<"sha-384">> ->
|
||||
Caps#caps.version == make_disco_hash(Els, sha384);
|
||||
Caps#caps.version == make_disco_hash(DiscoInfo, sha384);
|
||||
<<"sha-512">> ->
|
||||
Caps#caps.version == make_disco_hash(Els, sha512);
|
||||
Caps#caps.version == make_disco_hash(DiscoInfo, sha512);
|
||||
_ -> true
|
||||
end.
|
||||
|
||||
concat_features(Els) ->
|
||||
lists:usort(lists:flatmap(fun (#xmlel{name =
|
||||
<<"feature">>,
|
||||
attrs = Attrs}) ->
|
||||
[[fxml:get_attr_s(<<"var">>, Attrs), $<]];
|
||||
(_) -> []
|
||||
end,
|
||||
Els)).
|
||||
concat_features(#disco_info{features = Features}) ->
|
||||
lists:usort([[Feat, $<] || Feat <- Features]).
|
||||
|
||||
concat_identities(Els) ->
|
||||
lists:sort(lists:flatmap(fun (#xmlel{name =
|
||||
<<"identity">>,
|
||||
attrs = Attrs}) ->
|
||||
[[fxml:get_attr_s(<<"category">>, Attrs),
|
||||
$/, fxml:get_attr_s(<<"type">>, Attrs),
|
||||
$/,
|
||||
fxml:get_attr_s(<<"xml:lang">>, Attrs),
|
||||
$/, fxml:get_attr_s(<<"name">>, Attrs),
|
||||
$<]];
|
||||
(_) -> []
|
||||
end,
|
||||
Els)).
|
||||
concat_identities(#disco_info{identities = Identities}) ->
|
||||
lists:sort(
|
||||
[[Cat, $/, T, $/, Lang, $/, Name, $<] ||
|
||||
#identity{category = Cat, type = T,
|
||||
lang = Lang, name = Name} <- Identities]).
|
||||
|
||||
concat_info(Els) ->
|
||||
lists:sort(lists:flatmap(fun (#xmlel{name = <<"x">>,
|
||||
attrs = Attrs, children = Fields}) ->
|
||||
case {fxml:get_attr_s(<<"xmlns">>, Attrs),
|
||||
fxml:get_attr_s(<<"type">>, Attrs)}
|
||||
of
|
||||
{?NS_XDATA, <<"result">>} ->
|
||||
[concat_xdata_fields(Fields)];
|
||||
_ -> []
|
||||
end;
|
||||
(_) -> []
|
||||
end,
|
||||
Els)).
|
||||
concat_info(#disco_info{xdata = Xs}) ->
|
||||
lists:sort(
|
||||
[concat_xdata_fields(Fs) || #xdata{type = result, fields = Fs} <- Xs]).
|
||||
|
||||
concat_xdata_fields(Fields) ->
|
||||
[Form, Res] = lists:foldl(fun (#xmlel{name =
|
||||
<<"field">>,
|
||||
attrs = Attrs, children = Els} =
|
||||
El,
|
||||
[FormType, VarFields] = Acc) ->
|
||||
case fxml:get_attr_s(<<"var">>, Attrs) of
|
||||
<<"">> -> Acc;
|
||||
<<"FORM_TYPE">> ->
|
||||
[fxml:get_subtag_cdata(El,
|
||||
<<"value">>),
|
||||
VarFields];
|
||||
Var ->
|
||||
[FormType,
|
||||
[[[Var, $<],
|
||||
lists:sort(lists:flatmap(fun
|
||||
(#xmlel{name
|
||||
=
|
||||
<<"value">>,
|
||||
children
|
||||
=
|
||||
VEls}) ->
|
||||
[[fxml:get_cdata(VEls),
|
||||
$<]];
|
||||
(_) ->
|
||||
[]
|
||||
end,
|
||||
Els))]
|
||||
| VarFields]]
|
||||
end;
|
||||
(_, Acc) -> Acc
|
||||
end,
|
||||
[<<"">>, []], Fields),
|
||||
Form = case lists:keysearch(<<"FORM_TYPE">>, #xdata_field.var, Fields) of
|
||||
#xdata_field{values = Values} -> Values;
|
||||
false -> []
|
||||
end,
|
||||
Res = [[Var, $<, lists:sort([[Val, $<] || Val <- Values])]
|
||||
|| #xdata_field{var = Var, values = Values} <- Fields,
|
||||
is_binary(Var), Var /= <<"FORM_TYPE">>],
|
||||
[Form, $<, lists:sort(Res)].
|
||||
|
||||
gb_trees_fold(F, Acc, Tree) ->
|
||||
@ -588,6 +509,9 @@ gb_trees_fold_iter(F, Acc, Iter) ->
|
||||
now_ts() ->
|
||||
p1_time_compat:system_time(seconds).
|
||||
|
||||
-spec is_valid_node(undefined | binary()) -> boolean().
|
||||
is_valid_node(undefined) ->
|
||||
false;
|
||||
is_valid_node(Node) ->
|
||||
case str:tokens(Node, <<"#">>) of
|
||||
[?EJABBERD_URI|_] ->
|
||||
|
@ -36,37 +36,28 @@
|
||||
stop/1]).
|
||||
|
||||
-export([user_send_packet/4, user_receive_packet/5,
|
||||
iq_handler2/3, iq_handler1/3, remove_connection/4,
|
||||
iq_handler/1, remove_connection/4,
|
||||
is_carbon_copy/1, mod_opt_type/1, depends/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("xmpp.hrl").
|
||||
-define(PROCNAME, ?MODULE).
|
||||
|
||||
-type direction() :: sent | received.
|
||||
|
||||
-callback init(binary(), gen_mod:opts()) -> any().
|
||||
-callback enable(binary(), binary(), binary(), binary()) -> ok | {error, any()}.
|
||||
-callback disable(binary(), binary(), binary()) -> ok | {error, any()}.
|
||||
-callback list(binary(), binary()) -> [{binary(), binary()}].
|
||||
|
||||
-spec is_carbon_copy(stanza()) -> boolean().
|
||||
is_carbon_copy(Packet) ->
|
||||
is_carbon_copy(Packet, <<"sent">>) orelse
|
||||
is_carbon_copy(Packet, <<"received">>).
|
||||
|
||||
is_carbon_copy(Packet, Direction) ->
|
||||
case fxml:get_subtag(Packet, Direction) of
|
||||
#xmlel{name = Direction, attrs = Attrs} ->
|
||||
case fxml:get_attr_s(<<"xmlns">>, Attrs) of
|
||||
?NS_CARBONS_2 -> true;
|
||||
?NS_CARBONS_1 -> true;
|
||||
_ -> false
|
||||
end;
|
||||
_ -> false
|
||||
end.
|
||||
xmpp:has_subtag(Packet, #carbons_sent{}) orelse
|
||||
xmpp:has_subtag(Packet, #carbons_received{}).
|
||||
|
||||
start(Host, Opts) ->
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts,fun gen_iq_handler:check_type/1, one_queue),
|
||||
mod_disco:register_feature(Host, ?NS_CARBONS_1),
|
||||
mod_disco:register_feature(Host, ?NS_CARBONS_2),
|
||||
Mod = gen_mod:db_mod(Host, ?MODULE),
|
||||
Mod:init(Host, Opts),
|
||||
@ -74,54 +65,53 @@ start(Host, Opts) ->
|
||||
%% why priority 89: to define clearly that we must run BEFORE mod_logdb hook (90)
|
||||
ejabberd_hooks:add(user_send_packet,Host, ?MODULE, user_send_packet, 89),
|
||||
ejabberd_hooks:add(user_receive_packet,Host, ?MODULE, user_receive_packet, 89),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_CARBONS_2, ?MODULE, iq_handler2, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_CARBONS_1, ?MODULE, iq_handler1, IQDisc).
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_CARBONS_2, ?MODULE, iq_handler, IQDisc).
|
||||
|
||||
stop(Host) ->
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_CARBONS_1),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_CARBONS_2),
|
||||
mod_disco:unregister_feature(Host, ?NS_CARBONS_2),
|
||||
mod_disco:unregister_feature(Host, ?NS_CARBONS_1),
|
||||
%% why priority 89: to define clearly that we must run BEFORE mod_logdb hook (90)
|
||||
ejabberd_hooks:delete(user_send_packet,Host, ?MODULE, user_send_packet, 89),
|
||||
ejabberd_hooks:delete(user_receive_packet,Host, ?MODULE, user_receive_packet, 89),
|
||||
ejabberd_hooks:delete(unset_presence_hook,Host, ?MODULE, remove_connection, 10).
|
||||
|
||||
iq_handler2(From, To, IQ) ->
|
||||
iq_handler(From, To, IQ, ?NS_CARBONS_2).
|
||||
iq_handler1(From, To, IQ) ->
|
||||
iq_handler(From, To, IQ, ?NS_CARBONS_1).
|
||||
|
||||
iq_handler(From, _To,
|
||||
#iq{type=set, lang = Lang,
|
||||
sub_el = #xmlel{name = Operation} = SubEl} = IQ, CC)->
|
||||
?DEBUG("carbons IQ received: ~p", [IQ]),
|
||||
-spec iq_handler(iq()) -> iq().
|
||||
iq_handler(#iq{type = set, lang = Lang, from = From,
|
||||
sub_els = [El]} = IQ) when is_record(El, carbons_enable);
|
||||
is_record(El, carbons_disable) ->
|
||||
{U, S, R} = jid:tolower(From),
|
||||
Result = case Operation of
|
||||
<<"enable">>->
|
||||
?INFO_MSG("carbons enabled for user ~s@~s/~s", [U,S,R]),
|
||||
enable(S,U,R,CC);
|
||||
<<"disable">>->
|
||||
?INFO_MSG("carbons disabled for user ~s@~s/~s", [U,S,R]),
|
||||
disable(S, U, R)
|
||||
end,
|
||||
Result = case El of
|
||||
#carbons_enable{} ->
|
||||
?INFO_MSG("carbons enabled for user ~s@~s/~s", [U,S,R]),
|
||||
enable(S, U, R, ?NS_CARBONS_2);
|
||||
#carbons_disable{} ->
|
||||
?INFO_MSG("carbons disabled for user ~s@~s/~s", [U,S,R]),
|
||||
disable(S, U, R)
|
||||
end,
|
||||
case Result of
|
||||
ok ->
|
||||
ok ->
|
||||
?DEBUG("carbons IQ result: ok", []),
|
||||
IQ#iq{type=result, sub_el=[]};
|
||||
xmpp:make_iq_result(IQ);
|
||||
{error,_Error} ->
|
||||
?ERROR_MSG("Error enabling / disabling carbons: ~p", [Result]),
|
||||
Txt = <<"Database failure">>,
|
||||
IQ#iq{type=error,sub_el = [SubEl, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)]}
|
||||
xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
|
||||
end;
|
||||
|
||||
iq_handler(_From, _To, #iq{lang = Lang, sub_el = SubEl} = IQ, _CC)->
|
||||
iq_handler(#iq{type = set, lang = Lang} = IQ) ->
|
||||
Txt = <<"Only <enable/> or <disable/> tags are allowed">>,
|
||||
xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang));
|
||||
iq_handler(#iq{type = get, lang = Lang} = IQ)->
|
||||
Txt = <<"Value 'get' of 'type' attribute is not allowed">>,
|
||||
IQ#iq{type=error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}.
|
||||
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)).
|
||||
|
||||
-spec user_send_packet(stanza(), ejabberd_c2s:state(), jid(), jid()) ->
|
||||
stanza() | {stop, stanza()}.
|
||||
user_send_packet(Packet, _C2SState, From, To) ->
|
||||
check_and_forward(From, To, Packet, sent).
|
||||
|
||||
-spec user_receive_packet(stanza(), ejabberd_c2s:state(),
|
||||
jid(), jid(), jid()) ->
|
||||
stanza() | {stop, stanza()}.
|
||||
user_receive_packet(Packet, _C2SState, JID, _From, To) ->
|
||||
check_and_forward(JID, To, Packet, received).
|
||||
|
||||
@ -129,10 +119,12 @@ user_receive_packet(Packet, _C2SState, JID, _From, To) ->
|
||||
% - registered to the user_send_packet hook, to be called only once even for multicast
|
||||
% - do not support "private" message mode, and do not modify the original packet in any way
|
||||
% - we also replicate "read" notifications
|
||||
-spec check_and_forward(jid(), jid(), stanza(), direction()) ->
|
||||
stanza() | {stop, stanza()}.
|
||||
check_and_forward(JID, To, Packet, Direction)->
|
||||
case is_chat_message(Packet) andalso
|
||||
fxml:get_subtag(Packet, <<"private">>) == false andalso
|
||||
fxml:get_subtag(Packet, <<"no-copy">>) == false of
|
||||
xmpp:has_subtag(Packet, #carbons_private{}) == false andalso
|
||||
xmpp:has_subtag(Packet, #hint{type = 'no-copy'}) == false of
|
||||
true ->
|
||||
case is_carbon_copy(Packet) of
|
||||
false ->
|
||||
@ -147,6 +139,7 @@ check_and_forward(JID, To, Packet, Direction)->
|
||||
Packet
|
||||
end.
|
||||
|
||||
-spec remove_connection(binary(), binary(), binary(), binary()) -> ok.
|
||||
remove_connection(User, Server, Resource, _Status)->
|
||||
disable(Server, User, Resource),
|
||||
ok.
|
||||
@ -154,6 +147,7 @@ remove_connection(User, Server, Resource, _Status)->
|
||||
|
||||
%%% Internal
|
||||
%% Direction = received | sent <received xmlns='urn:xmpp:carbons:1'/>
|
||||
-spec send_copies(jid(), jid(), message(), direction()) -> ok.
|
||||
send_copies(JID, To, Packet, Direction)->
|
||||
{U, S, R} = jid:tolower(JID),
|
||||
PrioRes = ejabberd_sm:get_user_present_resources(U, S),
|
||||
@ -191,88 +185,54 @@ send_copies(JID, To, Packet, Direction)->
|
||||
%TargetJIDs = lists:delete(JID, [ jid:make({U, S, CCRes}) || CCRes <- list(U, S) ]),
|
||||
end,
|
||||
|
||||
lists:map(fun({Dest,Version}) ->
|
||||
lists:map(fun({Dest, _Version}) ->
|
||||
{_, _, Resource} = jid:tolower(Dest),
|
||||
?DEBUG("Sending: ~p =/= ~p", [R, Resource]),
|
||||
Sender = jid:make({U, S, <<>>}),
|
||||
%{xmlelement, N, A, C} = Packet,
|
||||
New = build_forward_packet(JID, Packet, Sender, Dest, Direction, Version),
|
||||
New = build_forward_packet(JID, Packet, Sender, Dest, Direction),
|
||||
ejabberd_router:route(Sender, Dest, New)
|
||||
end, TargetJIDs),
|
||||
ok.
|
||||
|
||||
build_forward_packet(JID, Packet, Sender, Dest, Direction, ?NS_CARBONS_2) ->
|
||||
#xmlel{name = <<"message">>,
|
||||
attrs = [{<<"xmlns">>, <<"jabber:client">>},
|
||||
{<<"type">>, message_type(Packet)},
|
||||
{<<"from">>, jid:to_string(Sender)},
|
||||
{<<"to">>, jid:to_string(Dest)}],
|
||||
children = [
|
||||
#xmlel{name = list_to_binary(atom_to_list(Direction)),
|
||||
attrs = [{<<"xmlns">>, ?NS_CARBONS_2}],
|
||||
children = [
|
||||
#xmlel{name = <<"forwarded">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_FORWARD}],
|
||||
children = [
|
||||
complete_packet(JID, Packet, Direction)]}
|
||||
]}
|
||||
]};
|
||||
build_forward_packet(JID, Packet, Sender, Dest, Direction, ?NS_CARBONS_1) ->
|
||||
#xmlel{name = <<"message">>,
|
||||
attrs = [{<<"xmlns">>, <<"jabber:client">>},
|
||||
{<<"type">>, message_type(Packet)},
|
||||
{<<"from">>, jid:to_string(Sender)},
|
||||
{<<"to">>, jid:to_string(Dest)}],
|
||||
children = [
|
||||
#xmlel{name = list_to_binary(atom_to_list(Direction)),
|
||||
attrs = [{<<"xmlns">>, ?NS_CARBONS_1}]},
|
||||
#xmlel{name = <<"forwarded">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_FORWARD}],
|
||||
children = [complete_packet(JID, Packet, Direction)]}
|
||||
]}.
|
||||
|
||||
-spec build_forward_packet(jid(), message(), jid(), jid(), direction()) -> message().
|
||||
build_forward_packet(JID, #message{type = T} = Msg, Sender, Dest, Direction) ->
|
||||
Forwarded = #forwarded{sub_els = complete_packet(JID, Msg, Direction)},
|
||||
Carbon = case Direction of
|
||||
sent -> #carbons_sent{forwarded = Forwarded};
|
||||
received -> #carbons_received{forwarded = Forwarded}
|
||||
end,
|
||||
#message{from = Sender, to = Dest, type = T, sub_els = [Carbon]}.
|
||||
|
||||
-spec enable(binary(), binary(), binary(), binary()) -> ok | {error, any()}.
|
||||
enable(Host, U, R, CC)->
|
||||
?DEBUG("enabling for ~p", [U]),
|
||||
Mod = gen_mod:db_mod(Host, ?MODULE),
|
||||
Mod:enable(U, Host, R, CC).
|
||||
|
||||
-spec disable(binary(), binary(), binary()) -> ok | {error, any()}.
|
||||
disable(Host, U, R)->
|
||||
?DEBUG("disabling for ~p", [U]),
|
||||
Mod = gen_mod:db_mod(Host, ?MODULE),
|
||||
Mod:disable(U, Host, R).
|
||||
|
||||
complete_packet(From, #xmlel{name = <<"message">>, attrs = OrigAttrs} = Packet, sent) ->
|
||||
-spec complete_packet(jid(), message(), direction()) -> message().
|
||||
complete_packet(From, #message{from = undefined} = Msg, sent) ->
|
||||
%% if this is a packet sent by user on this host, then Packet doesn't
|
||||
%% include the 'from' attribute. We must add it.
|
||||
Attrs = lists:keystore(<<"xmlns">>, 1, OrigAttrs, {<<"xmlns">>, <<"jabber:client">>}),
|
||||
case proplists:get_value(<<"from">>, Attrs) of
|
||||
undefined ->
|
||||
Packet#xmlel{attrs = [{<<"from">>, jid:to_string(From)}|Attrs]};
|
||||
_ ->
|
||||
Packet#xmlel{attrs = Attrs}
|
||||
end;
|
||||
complete_packet(_From, #xmlel{name = <<"message">>, attrs=OrigAttrs} = Packet, received) ->
|
||||
Attrs = lists:keystore(<<"xmlns">>, 1, OrigAttrs, {<<"xmlns">>, <<"jabber:client">>}),
|
||||
Packet#xmlel{attrs = Attrs}.
|
||||
Msg#message{from = From};
|
||||
complete_packet(_From, Msg, _Direction) ->
|
||||
Msg.
|
||||
|
||||
message_type(#xmlel{attrs = Attrs}) ->
|
||||
case fxml:get_attr(<<"type">>, Attrs) of
|
||||
{value, Type} -> Type;
|
||||
false -> <<"normal">>
|
||||
end.
|
||||
|
||||
is_chat_message(#xmlel{name = <<"message">>} = Packet) ->
|
||||
case message_type(Packet) of
|
||||
<<"chat">> -> true;
|
||||
<<"normal">> -> has_non_empty_body(Packet);
|
||||
_ -> false
|
||||
end;
|
||||
is_chat_message(_Packet) -> false.
|
||||
|
||||
has_non_empty_body(Packet) ->
|
||||
fxml:get_subtag_cdata(Packet, <<"body">>) =/= <<"">>.
|
||||
-spec is_chat_message(stanza()) -> boolean().
|
||||
is_chat_message(#message{type = chat}) ->
|
||||
true;
|
||||
is_chat_message(#message{type = normal, body = Body}) ->
|
||||
xmpp:get_text(Body) /= <<"">>;
|
||||
is_chat_message(_) ->
|
||||
false.
|
||||
|
||||
-spec list(binary(), binary()) -> [{binary(), binary()}].
|
||||
%% list {resource, cc_version} with carbons enabled for given user and host
|
||||
list(User, Server) ->
|
||||
Mod = gen_mod:db_mod(Server, ?MODULE),
|
||||
|
@ -39,7 +39,7 @@
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("xmpp.hrl").
|
||||
|
||||
-define(CSI_QUEUE_MAX, 100).
|
||||
|
||||
@ -151,66 +151,62 @@ depends(_Host, _Opts) ->
|
||||
%% ejabberd_hooks callbacks.
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-spec filter_presence({term(), [xmlel()]}, binary(), xmlel())
|
||||
-> {term(), [xmlel()]} | {stop, {term(), [xmlel()]}}.
|
||||
-spec filter_presence({term(), [stanza()]}, binary(), stanza())
|
||||
-> {term(), [stanza()]} | {stop, {term(), [stanza()]}}.
|
||||
|
||||
filter_presence({C2SState, _OutStanzas} = Acc, Host,
|
||||
#xmlel{name = <<"presence">>, attrs = Attrs} = Stanza) ->
|
||||
case fxml:get_attr(<<"type">>, Attrs) of
|
||||
{value, Type} when Type /= <<"unavailable">> ->
|
||||
Acc;
|
||||
_ ->
|
||||
?DEBUG("Got availability presence stanza", []),
|
||||
queue_add(presence, Stanza, Host, C2SState)
|
||||
#presence{type = Type} = Stanza) ->
|
||||
if Type == available, Type == unavailable ->
|
||||
?DEBUG("Got availability presence stanza", []),
|
||||
queue_add(presence, Stanza, Host, C2SState);
|
||||
true ->
|
||||
Acc
|
||||
end;
|
||||
filter_presence(Acc, _Host, _Stanza) -> Acc.
|
||||
|
||||
-spec filter_chat_states({term(), [xmlel()]}, binary(), xmlel())
|
||||
-> {term(), [xmlel()]} | {stop, {term(), [xmlel()]}}.
|
||||
-spec filter_chat_states({term(), [stanza()]}, binary(), stanza())
|
||||
-> {term(), [stanza()]} | {stop, {term(), [stanza()]}}.
|
||||
|
||||
filter_chat_states({C2SState, _OutStanzas} = Acc, Host,
|
||||
#xmlel{name = <<"message">>} = Stanza) ->
|
||||
case jlib:is_standalone_chat_state(Stanza) of
|
||||
true ->
|
||||
From = fxml:get_tag_attr_s(<<"from">>, Stanza),
|
||||
To = fxml:get_tag_attr_s(<<"to">>, Stanza),
|
||||
case {jid:from_string(From), jid:from_string(To)} of
|
||||
{#jid{luser = U, lserver = S}, #jid{luser = U, lserver = S}} ->
|
||||
%% Don't queue (carbon copies of) chat states from other
|
||||
%% resources, as they might be used to sync the state of
|
||||
%% conversations across clients.
|
||||
Acc;
|
||||
_ ->
|
||||
?DEBUG("Got standalone chat state notification", []),
|
||||
queue_add(chatstate, Stanza, Host, C2SState)
|
||||
end;
|
||||
false ->
|
||||
Acc
|
||||
#message{from = From, to = To} = Stanza) ->
|
||||
case xmpp_util:is_standalone_chat_state(Stanza) of
|
||||
true ->
|
||||
case {From, To} of
|
||||
{#jid{luser = U, lserver = S}, #jid{luser = U, lserver = S}} ->
|
||||
%% Don't queue (carbon copies of) chat states from other
|
||||
%% resources, as they might be used to sync the state of
|
||||
%% conversations across clients.
|
||||
Acc;
|
||||
_ ->
|
||||
?DEBUG("Got standalone chat state notification", []),
|
||||
queue_add(chatstate, Stanza, Host, C2SState)
|
||||
end;
|
||||
false ->
|
||||
Acc
|
||||
end;
|
||||
filter_chat_states(Acc, _Host, _Stanza) -> Acc.
|
||||
|
||||
-spec filter_pep({term(), [xmlel()]}, binary(), xmlel())
|
||||
-> {term(), [xmlel()]} | {stop, {term(), [xmlel()]}}.
|
||||
-spec filter_pep({term(), [stanza()]}, binary(), stanza())
|
||||
-> {term(), [stanza()]} | {stop, {term(), [stanza()]}}.
|
||||
|
||||
filter_pep({C2SState, _OutStanzas} = Acc, Host,
|
||||
#xmlel{name = <<"message">>} = Stanza) ->
|
||||
filter_pep({C2SState, _OutStanzas} = Acc, Host, #message{} = Stanza) ->
|
||||
case get_pep_node(Stanza) of
|
||||
{value, Node} ->
|
||||
?DEBUG("Got PEP notification", []),
|
||||
queue_add({pep, Node}, Stanza, Host, C2SState);
|
||||
false ->
|
||||
Acc
|
||||
undefined ->
|
||||
Acc;
|
||||
Node ->
|
||||
?DEBUG("Got PEP notification", []),
|
||||
queue_add({pep, Node}, Stanza, Host, C2SState)
|
||||
end;
|
||||
filter_pep(Acc, _Host, _Stanza) -> Acc.
|
||||
|
||||
-spec filter_other({term(), [xmlel()]}, binary(), xmlel())
|
||||
-> {stop, {term(), [xmlel()]}}.
|
||||
-spec filter_other({term(), [stanza()]}, binary(), stanza())
|
||||
-> {stop, {term(), [stanza()]}}.
|
||||
|
||||
filter_other({C2SState, _OutStanzas}, Host, Stanza) ->
|
||||
?DEBUG("Won't add stanza to CSI queue", []),
|
||||
queue_take(Stanza, Host, C2SState).
|
||||
|
||||
-spec flush_queue({term(), [xmlel()]}, binary()) -> {term(), [xmlel()]}.
|
||||
-spec flush_queue({term(), [stanza()]}, binary()) -> {term(), [stanza()]}.
|
||||
|
||||
flush_queue({C2SState, _OutStanzas}, Host) ->
|
||||
?DEBUG("Going to flush CSI queue", []),
|
||||
@ -218,20 +214,17 @@ flush_queue({C2SState, _OutStanzas}, Host) ->
|
||||
NewState = set_queue([], C2SState),
|
||||
{NewState, get_stanzas(Queue, Host)}.
|
||||
|
||||
-spec add_stream_feature([xmlel()], binary) -> [xmlel()].
|
||||
-spec add_stream_feature([stanza()], binary) -> [stanza()].
|
||||
|
||||
add_stream_feature(Features, _Host) ->
|
||||
Feature = #xmlel{name = <<"csi">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_CLIENT_STATE}],
|
||||
children = []},
|
||||
[Feature | Features].
|
||||
[#feature_csi{xmlns = <<"urn:xmpp:csi:0">>} | Features].
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Internal functions.
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-spec queue_add(csi_type(), xmlel(), binary(), term())
|
||||
-> {stop, {term(), [xmlel()]}}.
|
||||
-spec queue_add(csi_type(), stanza(), binary(), term())
|
||||
-> {stop, {term(), [stanza()]}}.
|
||||
|
||||
queue_add(Type, Stanza, Host, C2SState) ->
|
||||
case get_queue(C2SState) of
|
||||
@ -241,19 +234,19 @@ queue_add(Type, Stanza, Host, C2SState) ->
|
||||
{stop, {NewState, get_stanzas(Queue, Host) ++ [Stanza]}};
|
||||
Queue ->
|
||||
?DEBUG("Adding stanza to CSI queue", []),
|
||||
From = fxml:get_tag_attr_s(<<"from">>, Stanza),
|
||||
Key = {jid:tolower(jid:from_string(From)), Type},
|
||||
From = xmpp:get_from(Stanza),
|
||||
Key = {jid:tolower(From), Type},
|
||||
Entry = {Key, p1_time_compat:timestamp(), Stanza},
|
||||
NewQueue = lists:keystore(Key, 1, Queue, Entry),
|
||||
NewState = set_queue(NewQueue, C2SState),
|
||||
{stop, {NewState, []}}
|
||||
end.
|
||||
|
||||
-spec queue_take(xmlel(), binary(), term()) -> {stop, {term(), [xmlel()]}}.
|
||||
-spec queue_take(stanza(), binary(), term()) -> {stop, {term(), [stanza()]}}.
|
||||
|
||||
queue_take(Stanza, Host, C2SState) ->
|
||||
From = fxml:get_tag_attr_s(<<"from">>, Stanza),
|
||||
{LUser, LServer, _LResource} = jid:tolower(jid:from_string(From)),
|
||||
From = xmpp:get_from(Stanza),
|
||||
{LUser, LServer, _LResource} = jid:tolower(From),
|
||||
{Selected, Rest} = lists:partition(
|
||||
fun({{{U, S, _R}, _Type}, _Time, _Stanza}) ->
|
||||
U == LUser andalso S == LServer
|
||||
@ -276,32 +269,23 @@ get_queue(C2SState) ->
|
||||
[]
|
||||
end.
|
||||
|
||||
-spec get_stanzas(csi_queue(), binary()) -> [xmlel()].
|
||||
-spec get_stanzas(csi_queue(), binary()) -> [stanza()].
|
||||
|
||||
get_stanzas(Queue, Host) ->
|
||||
lists:map(fun({_Key, Time, Stanza}) ->
|
||||
jlib:add_delay_info(Stanza, Host, Time,
|
||||
<<"Client Inactive">>)
|
||||
xmpp_util:add_delay_info(Stanza, Host, Time,
|
||||
<<"Client Inactive">>)
|
||||
end, Queue).
|
||||
|
||||
-spec get_pep_node(xmlel()) -> {value, binary()} | false.
|
||||
-spec get_pep_node(message()) -> binary() | undefined.
|
||||
|
||||
get_pep_node(#xmlel{name = <<"message">>} = Stanza) ->
|
||||
From = fxml:get_tag_attr_s(<<"from">>, Stanza),
|
||||
case jid:from_string(From) of
|
||||
#jid{luser = <<>>} -> % It's not PEP.
|
||||
false;
|
||||
_ ->
|
||||
case fxml:get_subtag_with_xmlns(Stanza, <<"event">>,
|
||||
?NS_PUBSUB_EVENT) of
|
||||
#xmlel{children = Els} ->
|
||||
case fxml:remove_cdata(Els) of
|
||||
[#xmlel{name = <<"items">>, attrs = ItemsAttrs}] ->
|
||||
fxml:get_attr(<<"node">>, ItemsAttrs);
|
||||
_ ->
|
||||
false
|
||||
end;
|
||||
false ->
|
||||
false
|
||||
end
|
||||
get_pep_node(#message{from = #jid{luser = <<>>}}) ->
|
||||
%% It's not PEP.
|
||||
undefined;
|
||||
get_pep_node(#message{} = Msg) ->
|
||||
case xmpp:get_subtag(Msg, #pubsub_event{}) of
|
||||
#pubsub_event{items = [#pubsub_event_item{node = Node}]} ->
|
||||
Node;
|
||||
_ ->
|
||||
undefined
|
||||
end.
|
||||
|
@ -32,10 +32,10 @@
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([start/2, stop/1, process_local_iq_items/3,
|
||||
process_local_iq_info/3, get_local_identity/5,
|
||||
-export([start/2, stop/1, process_local_iq_items/1,
|
||||
process_local_iq_info/1, get_local_identity/5,
|
||||
get_local_features/5, get_local_services/5,
|
||||
process_sm_iq_items/3, process_sm_iq_info/3,
|
||||
process_sm_iq_items/1, process_sm_iq_info/1,
|
||||
get_sm_identity/5, get_sm_features/5, get_sm_items/5,
|
||||
get_info/5, register_feature/2, unregister_feature/2,
|
||||
register_extra_domain/2, unregister_extra_domain/2,
|
||||
@ -44,8 +44,8 @@
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
|
||||
-include("xmpp.hrl").
|
||||
-include_lib("stdlib/include/ms_transform.hrl").
|
||||
-include("mod_roster.hrl").
|
||||
|
||||
start(Host, Opts) ->
|
||||
@ -126,158 +126,133 @@ stop(Host) ->
|
||||
{{'_', Host}}),
|
||||
ok.
|
||||
|
||||
-spec register_feature(binary(), binary()) -> true.
|
||||
register_feature(Host, Feature) ->
|
||||
catch ets:new(disco_features,
|
||||
[named_table, ordered_set, public]),
|
||||
ets:insert(disco_features, {{Feature, Host}}).
|
||||
|
||||
-spec unregister_feature(binary(), binary()) -> true.
|
||||
unregister_feature(Host, Feature) ->
|
||||
catch ets:new(disco_features,
|
||||
[named_table, ordered_set, public]),
|
||||
ets:delete(disco_features, {Feature, Host}).
|
||||
|
||||
-spec register_extra_domain(binary(), binary()) -> true.
|
||||
register_extra_domain(Host, Domain) ->
|
||||
catch ets:new(disco_extra_domains,
|
||||
[named_table, ordered_set, public]),
|
||||
ets:insert(disco_extra_domains, {{Domain, Host}}).
|
||||
|
||||
-spec unregister_extra_domain(binary(), binary()) -> true.
|
||||
unregister_extra_domain(Host, Domain) ->
|
||||
catch ets:new(disco_extra_domains,
|
||||
[named_table, ordered_set, public]),
|
||||
ets:delete(disco_extra_domains, {Domain, Host}).
|
||||
|
||||
process_local_iq_items(From, To,
|
||||
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
|
||||
case Type of
|
||||
set ->
|
||||
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
|
||||
get ->
|
||||
Node = fxml:get_tag_attr_s(<<"node">>, SubEl),
|
||||
Host = To#jid.lserver,
|
||||
case ejabberd_hooks:run_fold(disco_local_items, Host,
|
||||
empty, [From, To, Node, Lang])
|
||||
of
|
||||
{result, Items} ->
|
||||
ANode = case Node of
|
||||
<<"">> -> [];
|
||||
_ -> [{<<"node">>, Node}]
|
||||
end,
|
||||
IQ#iq{type = result,
|
||||
sub_el =
|
||||
[#xmlel{name = <<"query">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>, ?NS_DISCO_ITEMS} | ANode],
|
||||
children = Items}]};
|
||||
{error, Error} ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, Error]}
|
||||
end
|
||||
-spec process_local_iq_items(iq()) -> iq().
|
||||
process_local_iq_items(#iq{type = set, lang = Lang} = IQ) ->
|
||||
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
|
||||
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
|
||||
process_local_iq_items(#iq{type = get, lang = Lang,
|
||||
from = From, to = To,
|
||||
sub_els = [#disco_items{node = Node}]} = IQ) ->
|
||||
Host = To#jid.lserver,
|
||||
case ejabberd_hooks:run_fold(disco_local_items, Host,
|
||||
empty, [From, To, Node, Lang]) of
|
||||
{result, Items} ->
|
||||
xmpp:make_iq_result(IQ, #disco_items{node = Node, items = Items});
|
||||
{error, Error} ->
|
||||
xmpp:make_error(IQ, Error)
|
||||
end.
|
||||
|
||||
process_local_iq_info(From, To,
|
||||
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
|
||||
case Type of
|
||||
set ->
|
||||
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
|
||||
get ->
|
||||
Host = To#jid.lserver,
|
||||
Node = fxml:get_tag_attr_s(<<"node">>, SubEl),
|
||||
Identity = ejabberd_hooks:run_fold(disco_local_identity,
|
||||
Host, [], [From, To, Node, Lang]),
|
||||
Info = ejabberd_hooks:run_fold(disco_info, Host, [],
|
||||
[Host, ?MODULE, Node, Lang]),
|
||||
case ejabberd_hooks:run_fold(disco_local_features, Host,
|
||||
empty, [From, To, Node, Lang])
|
||||
of
|
||||
{result, Features} ->
|
||||
ANode = case Node of
|
||||
<<"">> -> [];
|
||||
_ -> [{<<"node">>, Node}]
|
||||
end,
|
||||
IQ#iq{type = result,
|
||||
sub_el =
|
||||
[#xmlel{name = <<"query">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>, ?NS_DISCO_INFO} | ANode],
|
||||
children =
|
||||
Identity ++
|
||||
Info ++ features_to_xml(Features)}]};
|
||||
{error, Error} ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, Error]}
|
||||
end
|
||||
-spec process_local_iq_info(iq()) -> iq().
|
||||
process_local_iq_info(#iq{type = set, lang = Lang} = IQ) ->
|
||||
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
|
||||
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
|
||||
process_local_iq_info(#iq{type = get, lang = Lang,
|
||||
from = From, to = To,
|
||||
sub_els = [#disco_info{node = Node}]} = IQ) ->
|
||||
Host = To#jid.lserver,
|
||||
Identity = ejabberd_hooks:run_fold(disco_local_identity,
|
||||
Host, [], [From, To, Node, Lang]),
|
||||
Info = ejabberd_hooks:run_fold(disco_info, Host, [],
|
||||
[Host, ?MODULE, Node, Lang]),
|
||||
case ejabberd_hooks:run_fold(disco_local_features, Host,
|
||||
empty, [From, To, Node, Lang]) of
|
||||
{result, Features} ->
|
||||
xmpp:make_iq_result(IQ, #disco_info{node = Node,
|
||||
identities = Identity,
|
||||
xdata = Info,
|
||||
features = Features});
|
||||
{error, Error} ->
|
||||
xmpp:make_error(IQ, Error)
|
||||
end.
|
||||
|
||||
get_local_identity(Acc, _From, _To, <<>>, _Lang) ->
|
||||
Acc ++
|
||||
[#xmlel{name = <<"identity">>,
|
||||
attrs =
|
||||
[{<<"category">>, <<"server">>}, {<<"type">>, <<"im">>},
|
||||
{<<"name">>, <<"ejabberd">>}],
|
||||
children = []}];
|
||||
-spec get_local_identity([identity()], jid(), jid(),
|
||||
undefined | binary(), undefined | binary()) ->
|
||||
[identity()].
|
||||
get_local_identity(Acc, _From, _To, undefined, _Lang) ->
|
||||
Acc ++ [#identity{category = <<"server">>,
|
||||
type = <<"im">>,
|
||||
name = <<"ejabberd">>}];
|
||||
get_local_identity(Acc, _From, _To, _Node, _Lang) ->
|
||||
Acc.
|
||||
|
||||
-spec get_local_features({error, error()} | {result, [binary()]} | empty,
|
||||
jid(), jid(),
|
||||
undefined | binary(), undefined | binary()) ->
|
||||
{error, error()} | {result, [binary()]}.
|
||||
get_local_features({error, _Error} = Acc, _From, _To,
|
||||
_Node, _Lang) ->
|
||||
Acc;
|
||||
get_local_features(Acc, _From, To, <<>>, _Lang) ->
|
||||
get_local_features(Acc, _From, To, undefined, _Lang) ->
|
||||
Feats = case Acc of
|
||||
{result, Features} -> Features;
|
||||
empty -> []
|
||||
{result, Features} -> Features;
|
||||
empty -> []
|
||||
end,
|
||||
Host = To#jid.lserver,
|
||||
{result,
|
||||
ets:select(disco_features,
|
||||
[{{{'_', Host}}, [], ['$_']}])
|
||||
++ Feats};
|
||||
ets:fun2ms(fun({{F, H}}) when H == Host -> F end))
|
||||
++ Feats};
|
||||
get_local_features(Acc, _From, _To, _Node, Lang) ->
|
||||
case Acc of
|
||||
{result, _Features} -> Acc;
|
||||
empty ->
|
||||
Txt = <<"No features available">>,
|
||||
{error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)}
|
||||
{error, xmpp:err_item_not_found(Txt, Lang)}
|
||||
end.
|
||||
|
||||
features_to_xml(FeatureList) ->
|
||||
[#xmlel{name = <<"feature">>,
|
||||
attrs = [{<<"var">>, Feat}], children = []}
|
||||
|| Feat
|
||||
<- lists:usort(lists:map(fun ({{Feature, _Host}}) ->
|
||||
Feature;
|
||||
(Feature) when is_binary(Feature) ->
|
||||
Feature
|
||||
end,
|
||||
FeatureList))].
|
||||
|
||||
domain_to_xml({Domain}) ->
|
||||
#xmlel{name = <<"item">>, attrs = [{<<"jid">>, Domain}],
|
||||
children = []};
|
||||
domain_to_xml(Domain) ->
|
||||
#xmlel{name = <<"item">>, attrs = [{<<"jid">>, Domain}],
|
||||
children = []}.
|
||||
|
||||
-spec get_local_services({error, error()} | {result, [disco_item()]} | empty,
|
||||
jid(), jid(),
|
||||
undefined | binary(), undefined | binary()) ->
|
||||
{error, error()} | {result, [disco_item()]}.
|
||||
get_local_services({error, _Error} = Acc, _From, _To,
|
||||
_Node, _Lang) ->
|
||||
Acc;
|
||||
get_local_services(Acc, _From, To, <<>>, _Lang) ->
|
||||
get_local_services(Acc, _From, To, undefined, _Lang) ->
|
||||
Items = case Acc of
|
||||
{result, Its} -> Its;
|
||||
empty -> []
|
||||
end,
|
||||
Host = To#jid.lserver,
|
||||
{result,
|
||||
lists:usort(lists:map(fun domain_to_xml/1,
|
||||
get_vh_services(Host) ++
|
||||
ets:select(disco_extra_domains,
|
||||
[{{{'$1', Host}}, [], ['$1']}])))
|
||||
++ Items};
|
||||
lists:usort(
|
||||
lists:map(
|
||||
fun(Domain) -> #disco_item{jid = jid:make(Domain)} end,
|
||||
get_vh_services(Host) ++
|
||||
ets:select(disco_extra_domains,
|
||||
ets:fun2ms(
|
||||
fun({{D, H}}) when H == Host -> D end))))
|
||||
++ Items};
|
||||
get_local_services({result, _} = Acc, _From, _To, _Node,
|
||||
_Lang) ->
|
||||
Acc;
|
||||
get_local_services(empty, _From, _To, _Node, Lang) ->
|
||||
{error, ?ERRT_ITEM_NOT_FOUND(Lang, <<"No services available">>)}.
|
||||
{error, xmpp:err_item_not_found(<<"No services available">>, Lang)}.
|
||||
|
||||
-spec get_vh_services(binary()) -> [binary()].
|
||||
get_vh_services(Host) ->
|
||||
Hosts = lists:sort(fun (H1, H2) ->
|
||||
byte_size(H1) >= byte_size(H2)
|
||||
@ -300,47 +275,38 @@ get_vh_services(Host) ->
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
process_sm_iq_items(From, To,
|
||||
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
|
||||
case Type of
|
||||
set ->
|
||||
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
|
||||
get ->
|
||||
case is_presence_subscribed(From, To) of
|
||||
true ->
|
||||
Host = To#jid.lserver,
|
||||
Node = fxml:get_tag_attr_s(<<"node">>, SubEl),
|
||||
case ejabberd_hooks:run_fold(disco_sm_items, Host,
|
||||
empty, [From, To, Node, Lang])
|
||||
of
|
||||
{result, Items} ->
|
||||
ANode = case Node of
|
||||
<<"">> -> [];
|
||||
_ -> [{<<"node">>, Node}]
|
||||
end,
|
||||
IQ#iq{type = result,
|
||||
sub_el =
|
||||
[#xmlel{name = <<"query">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>, ?NS_DISCO_ITEMS}
|
||||
| ANode],
|
||||
children = Items}]};
|
||||
{error, Error} ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, Error]}
|
||||
end;
|
||||
false ->
|
||||
Txt = <<"Not subscribed">>,
|
||||
IQ#iq{type = error,
|
||||
sub_el = [SubEl, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)]}
|
||||
end
|
||||
-spec process_sm_iq_items(iq()) -> iq().
|
||||
process_sm_iq_items(#iq{type = set, lang = Lang} = IQ) ->
|
||||
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
|
||||
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
|
||||
process_sm_iq_items(#iq{type = get, lang = Lang,
|
||||
from = From, to = To,
|
||||
sub_els = [#disco_items{node = Node}]} = IQ) ->
|
||||
case is_presence_subscribed(From, To) of
|
||||
true ->
|
||||
Host = To#jid.lserver,
|
||||
case ejabberd_hooks:run_fold(disco_sm_items, Host,
|
||||
empty, [From, To, Node, Lang]) of
|
||||
{result, Items} ->
|
||||
xmpp:make_iq_result(
|
||||
IQ, #disco_items{node = Node, items = Items});
|
||||
{error, Error} ->
|
||||
xmpp:make_error(IQ, Error)
|
||||
end;
|
||||
false ->
|
||||
Txt = <<"Not subscribed">>,
|
||||
xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang))
|
||||
end.
|
||||
|
||||
-spec get_sm_items({error, error()} | {result, [disco_item()]} | empty,
|
||||
jid(), jid(),
|
||||
undefined | binary(), undefined | binary()) ->
|
||||
{error, error()} | {result, [disco_item()]}.
|
||||
get_sm_items({error, _Error} = Acc, _From, _To, _Node,
|
||||
_Lang) ->
|
||||
Acc;
|
||||
get_sm_items(Acc, From,
|
||||
#jid{user = User, server = Server} = To, <<>>, _Lang) ->
|
||||
#jid{user = User, server = Server} = To, undefined, _Lang) ->
|
||||
Items = case Acc of
|
||||
{result, Its} -> Its;
|
||||
empty -> []
|
||||
@ -357,12 +323,13 @@ get_sm_items(empty, From, To, _Node, Lang) ->
|
||||
#jid{luser = LFrom, lserver = LSFrom} = From,
|
||||
#jid{luser = LTo, lserver = LSTo} = To,
|
||||
case {LFrom, LSFrom} of
|
||||
{LTo, LSTo} -> {error, ?ERR_ITEM_NOT_FOUND};
|
||||
{LTo, LSTo} -> {error, xmpp:err_item_not_found()};
|
||||
_ ->
|
||||
Txt = <<"Query to another users is forbidden">>,
|
||||
{error, ?ERRT_NOT_ALLOWED(Lang, Txt)}
|
||||
{error, xmpp:err_not_allowed(Txt, Lang)}
|
||||
end.
|
||||
|
||||
-spec is_presence_subscribed(jid(), jid()) -> boolean().
|
||||
is_presence_subscribed(#jid{luser = User, lserver = Server},
|
||||
#jid{luser = User, lserver = Server}) -> true;
|
||||
is_presence_subscribed(#jid{luser = FromUser, lserver = FromServer},
|
||||
@ -377,86 +344,70 @@ is_presence_subscribed(#jid{luser = FromUser, lserver = FromServer},
|
||||
ejabberd_hooks:run_fold(roster_get, ToServer, [],
|
||||
[{ToUser, ToServer}])).
|
||||
|
||||
process_sm_iq_info(From, To,
|
||||
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
|
||||
case Type of
|
||||
set ->
|
||||
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
|
||||
get ->
|
||||
case is_presence_subscribed(From, To) of
|
||||
true ->
|
||||
Host = To#jid.lserver,
|
||||
Node = fxml:get_tag_attr_s(<<"node">>, SubEl),
|
||||
Identity = ejabberd_hooks:run_fold(disco_sm_identity,
|
||||
Host, [],
|
||||
[From, To, Node, Lang]),
|
||||
Info = ejabberd_hooks:run_fold(disco_info, Host, [],
|
||||
-spec process_sm_iq_info(iq()) -> iq().
|
||||
process_sm_iq_info(#iq{type = set, lang = Lang} = IQ) ->
|
||||
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
|
||||
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
|
||||
process_sm_iq_info(#iq{type = get, lang = Lang,
|
||||
from = From, to = To,
|
||||
sub_els = [#disco_info{node = Node}]} = IQ) ->
|
||||
case is_presence_subscribed(From, To) of
|
||||
true ->
|
||||
Host = To#jid.lserver,
|
||||
Identity = ejabberd_hooks:run_fold(disco_sm_identity,
|
||||
Host, [],
|
||||
[From, To, Node, Lang]),
|
||||
case ejabberd_hooks:run_fold(disco_sm_features, Host,
|
||||
empty, [From, To, Node, Lang])
|
||||
of
|
||||
{result, Features} ->
|
||||
ANode = case Node of
|
||||
<<"">> -> [];
|
||||
_ -> [{<<"node">>, Node}]
|
||||
end,
|
||||
IQ#iq{type = result,
|
||||
sub_el =
|
||||
[#xmlel{name = <<"query">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>, ?NS_DISCO_INFO}
|
||||
| ANode],
|
||||
children =
|
||||
Identity ++ Info ++
|
||||
features_to_xml(Features)}]};
|
||||
{error, Error} ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, Error]}
|
||||
end;
|
||||
false ->
|
||||
Txt = <<"Not subscribed">>,
|
||||
IQ#iq{type = error,
|
||||
sub_el = [SubEl, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)]}
|
||||
end
|
||||
Info = ejabberd_hooks:run_fold(disco_info, Host, [],
|
||||
[From, To, Node, Lang]),
|
||||
case ejabberd_hooks:run_fold(disco_sm_features, Host,
|
||||
empty, [From, To, Node, Lang]) of
|
||||
{result, Features} ->
|
||||
xmpp:make_iq_result(IQ, #disco_info{node = Node,
|
||||
identities = Identity,
|
||||
xdata = Info,
|
||||
features = Features});
|
||||
{error, Error} ->
|
||||
xmpp:make_error(IQ, Error)
|
||||
end;
|
||||
false ->
|
||||
Txt = <<"Not subscribed">>,
|
||||
xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang))
|
||||
end.
|
||||
|
||||
-spec get_sm_identity([identity()], jid(), jid(),
|
||||
undefined | binary(), undefined | binary()) ->
|
||||
[identity()].
|
||||
get_sm_identity(Acc, _From,
|
||||
#jid{luser = LUser, lserver = LServer}, _Node, _Lang) ->
|
||||
Acc ++
|
||||
case ejabberd_auth:is_user_exists(LUser, LServer) of
|
||||
true ->
|
||||
[#xmlel{name = <<"identity">>,
|
||||
attrs =
|
||||
[{<<"category">>, <<"account">>},
|
||||
{<<"type">>, <<"registered">>}],
|
||||
children = []}];
|
||||
[#identity{category = <<"account">>, type = <<"registered">>}];
|
||||
_ -> []
|
||||
end.
|
||||
|
||||
-spec get_sm_features({error, error()} | {result, [binary()]} | empty,
|
||||
jid(), jid(),
|
||||
undefined | binary(), undefined | binary()) ->
|
||||
{error, error()} | {result, [binary()]}.
|
||||
get_sm_features(empty, From, To, _Node, Lang) ->
|
||||
#jid{luser = LFrom, lserver = LSFrom} = From,
|
||||
#jid{luser = LTo, lserver = LSTo} = To,
|
||||
case {LFrom, LSFrom} of
|
||||
{LTo, LSTo} -> {error, ?ERR_ITEM_NOT_FOUND};
|
||||
{LTo, LSTo} -> {error, xmpp:err_item_not_found()};
|
||||
_ ->
|
||||
Txt = <<"Query to another users is forbidden">>,
|
||||
{error, ?ERRT_NOT_ALLOWED(Lang, Txt)}
|
||||
{error, xmpp:err_not_allowed(Txt, Lang)}
|
||||
end;
|
||||
get_sm_features(Acc, _From, _To, _Node, _Lang) -> Acc.
|
||||
|
||||
-spec get_user_resources(binary(), binary()) -> [disco_item()].
|
||||
get_user_resources(User, Server) ->
|
||||
Rs = ejabberd_sm:get_user_resources(User, Server),
|
||||
lists:map(fun (R) ->
|
||||
#xmlel{name = <<"item">>,
|
||||
attrs =
|
||||
[{<<"jid">>,
|
||||
<<User/binary, "@", Server/binary, "/",
|
||||
R/binary>>},
|
||||
{<<"name">>, User}],
|
||||
children = []}
|
||||
end,
|
||||
lists:sort(Rs)).
|
||||
[#disco_item{jid = jid:make(User, Server, Resource), name = User}
|
||||
|| Resource <- lists:sort(Rs)].
|
||||
|
||||
-spec transform_module_options(gen_mod:opts()) -> gen_mod:opts().
|
||||
transform_module_options(Opts) ->
|
||||
lists:map(
|
||||
fun({server_info, Infos}) ->
|
||||
@ -477,27 +428,23 @@ transform_module_options(Opts) ->
|
||||
|
||||
%%% Support for: XEP-0157 Contact Addresses for XMPP Services
|
||||
|
||||
get_info(_A, Host, Mod, Node, _Lang) when Node == <<>> ->
|
||||
-spec get_info([xdata()], binary(), module(),
|
||||
undefined | binary(), undefined | binary()) ->
|
||||
[xdata()].
|
||||
get_info(_A, Host, Mod, Node, _Lang) when Node == undefined ->
|
||||
Module = case Mod of
|
||||
undefined -> ?MODULE;
|
||||
_ -> Mod
|
||||
end,
|
||||
Serverinfo_fields = get_fields_xml(Host, Module),
|
||||
[#xmlel{name = <<"x">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"result">>}],
|
||||
children =
|
||||
[#xmlel{name = <<"field">>,
|
||||
attrs =
|
||||
[{<<"var">>, <<"FORM_TYPE">>},
|
||||
{<<"type">>, <<"hidden">>}],
|
||||
children =
|
||||
[#xmlel{name = <<"value">>, attrs = [],
|
||||
children = [{xmlcdata, ?NS_SERVERINFO}]}]}]
|
||||
++ Serverinfo_fields}];
|
||||
[#xdata{type = result,
|
||||
fields = [#xdata_field{type = hidden,
|
||||
var = <<"FORM_TYPE">>,
|
||||
values = [?NS_SERVERINFO]}
|
||||
| get_fields(Host, Module)]}];
|
||||
get_info(Acc, _, _, _Node, _) -> Acc.
|
||||
|
||||
get_fields_xml(Host, Module) ->
|
||||
-spec get_fields(binary(), module()) -> [xdata_field()].
|
||||
get_fields(Host, Module) ->
|
||||
Fields = gen_mod:get_module_opt(
|
||||
Host, ?MODULE, server_info,
|
||||
fun(L) ->
|
||||
@ -509,31 +456,17 @@ get_fields_xml(Host, Module) ->
|
||||
{Mods, Name, URLs}
|
||||
end, L)
|
||||
end, []),
|
||||
Fields_good = lists:filter(fun ({Modules, _, _}) ->
|
||||
case Modules of
|
||||
all -> true;
|
||||
Modules ->
|
||||
lists:member(Module, Modules)
|
||||
end
|
||||
end,
|
||||
Fields),
|
||||
fields_to_xml(Fields_good).
|
||||
|
||||
fields_to_xml(Fields) ->
|
||||
[field_to_xml(Field) || Field <- Fields].
|
||||
|
||||
field_to_xml({_, Var, Values}) ->
|
||||
Values_xml = values_to_xml(Values),
|
||||
#xmlel{name = <<"field">>, attrs = [{<<"var">>, Var}],
|
||||
children = Values_xml}.
|
||||
|
||||
values_to_xml(Values) ->
|
||||
lists:map(fun (Value) ->
|
||||
#xmlel{name = <<"value">>, attrs = [],
|
||||
children = [{xmlcdata, Value}]}
|
||||
end,
|
||||
Values).
|
||||
Fields1 = lists:filter(fun ({Modules, _, _}) ->
|
||||
case Modules of
|
||||
all -> true;
|
||||
Modules ->
|
||||
lists:member(Module, Modules)
|
||||
end
|
||||
end,
|
||||
Fields),
|
||||
[#xdata_field{var = Var, values = Values} || {_, Var, Values} <- Fields1].
|
||||
|
||||
-spec depends(binary(), gen_mod:opts()) -> [].
|
||||
depends(_Host, _Opts) ->
|
||||
[].
|
||||
|
||||
|
@ -42,7 +42,7 @@
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("xmpp.hrl").
|
||||
|
||||
-record(state, {host = <<"">> :: binary()}).
|
||||
|
||||
@ -118,10 +118,10 @@ handle_cast(_Msg, State) -> {noreply, State}.
|
||||
handle_info({route, From, To, Packet}, State) ->
|
||||
Packet2 = case From#jid.user of
|
||||
<<"">> ->
|
||||
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
|
||||
Lang = xmpp:get_lang(Packet),
|
||||
Txt = <<"User part of JID in 'from' is empty">>,
|
||||
jlib:make_error_reply(
|
||||
Packet, ?ERRT_BAD_REQUEST(Lang, Txt));
|
||||
xmpp:make_error(
|
||||
Packet, xmpp:err_bad_request(Txt, Lang));
|
||||
_ -> Packet
|
||||
end,
|
||||
do_client_version(disabled, To, From),
|
||||
@ -168,37 +168,27 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}.
|
||||
%% using exactly the same JID. We add a (mostly) random resource to
|
||||
%% try to guarantee that the received response matches the request sent.
|
||||
%% Finally, the received response is printed in the ejabberd log file.
|
||||
|
||||
%% THIS IS **NOT** HOW TO WRITE ejabberd CODE. THIS CODE IS RETARDED.
|
||||
|
||||
do_client_version(disabled, _From, _To) -> ok;
|
||||
do_client_version(enabled, From, To) ->
|
||||
ToS = jid:to_string(To),
|
||||
Random_resource =
|
||||
iolist_to_binary(integer_to_list(random:uniform(100000))),
|
||||
Random_resource = randoms:get_string(),
|
||||
From2 = From#jid{resource = Random_resource,
|
||||
lresource = Random_resource},
|
||||
Packet = #xmlel{name = <<"iq">>,
|
||||
attrs = [{<<"to">>, ToS}, {<<"type">>, <<"get">>}],
|
||||
children =
|
||||
[#xmlel{name = <<"query">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_VERSION}],
|
||||
children = []}]},
|
||||
ID = randoms:get_string(),
|
||||
Packet = #iq{from = From, to = To, type = get,
|
||||
id = randoms:get_string(),
|
||||
sub_els = [#version{}]},
|
||||
ejabberd_router:route(From2, To, Packet),
|
||||
Els = receive
|
||||
{route, To, From2, IQ} ->
|
||||
#xmlel{name = <<"query">>, children = List} =
|
||||
fxml:get_subtag(IQ, <<"query">>),
|
||||
List
|
||||
after 5000 -> % Timeout in miliseconds: 5 seconds
|
||||
[]
|
||||
end,
|
||||
Values = [{Name, Value}
|
||||
|| #xmlel{name = Name, attrs = [],
|
||||
children = [{xmlcdata, Value}]}
|
||||
<- Els],
|
||||
Values_string1 = [io_lib:format("~n~s: ~p", [N, V])
|
||||
|| {N, V} <- Values],
|
||||
Values_string2 = iolist_to_binary(Values_string1),
|
||||
?INFO_MSG("Information of the client: ~s~s",
|
||||
[ToS, Values_string2]).
|
||||
receive
|
||||
{route, To, From2,
|
||||
#iq{id = ID, type = result, sub_els = [#version{} = V]}} ->
|
||||
?INFO_MSG("Version of the client ~s:~n~s",
|
||||
[jid:to_string(To), xmpp:pp(V)])
|
||||
after 5000 -> % Timeout in miliseconds: 5 seconds
|
||||
[]
|
||||
end.
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[].
|
||||
|
129
src/mod_last.erl
129
src/mod_last.erl
@ -33,8 +33,8 @@
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([start/2, stop/1, process_local_iq/3, export/1,
|
||||
process_sm_iq/3, on_presence_update/4, import/1,
|
||||
-export([start/2, stop/1, process_local_iq/1, export/1,
|
||||
process_sm_iq/1, on_presence_update/4, import/1,
|
||||
import/3, store_last_info/4, get_last_info/2,
|
||||
remove_user/2, transform_options/1, mod_opt_type/1,
|
||||
opt_type/1, register_user/2, depends/2]).
|
||||
@ -42,7 +42,7 @@
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("xmpp.hrl").
|
||||
|
||||
-include("mod_privacy.hrl").
|
||||
-include("mod_last.hrl").
|
||||
@ -87,25 +87,14 @@ stop(Host) ->
|
||||
%%% Uptime of ejabberd node
|
||||
%%%
|
||||
|
||||
process_local_iq(_From, _To,
|
||||
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
|
||||
case Type of
|
||||
set ->
|
||||
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
|
||||
get ->
|
||||
Sec = get_node_uptime(),
|
||||
IQ#iq{type = result,
|
||||
sub_el =
|
||||
[#xmlel{name = <<"query">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>, ?NS_LAST},
|
||||
{<<"seconds">>,
|
||||
iolist_to_binary(integer_to_list(Sec))}],
|
||||
children = []}]}
|
||||
end.
|
||||
-spec process_local_iq(iq()) -> iq().
|
||||
process_local_iq(#iq{type = set, lang = Lang} = IQ) ->
|
||||
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
|
||||
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
|
||||
process_local_iq(#iq{type = get} = IQ) ->
|
||||
xmpp:make_iq_result(IQ, #last{seconds = get_node_uptime()}).
|
||||
|
||||
%% @spec () -> integer()
|
||||
-spec get_node_uptime() -> non_neg_integer().
|
||||
%% @doc Get the uptime of the ejabberd node, expressed in seconds.
|
||||
%% When ejabberd is starting, ejabberd_config:start/0 stores the datetime.
|
||||
get_node_uptime() ->
|
||||
@ -118,6 +107,7 @@ get_node_uptime() ->
|
||||
p1_time_compat:system_time(seconds) - Now
|
||||
end.
|
||||
|
||||
-spec now_to_seconds(erlang:timestamp()) -> non_neg_integer().
|
||||
now_to_seconds({MegaSecs, Secs, _MicroSecs}) ->
|
||||
MegaSecs * 1000000 + Secs.
|
||||
|
||||
@ -125,83 +115,63 @@ now_to_seconds({MegaSecs, Secs, _MicroSecs}) ->
|
||||
%%% Serve queries about user last online
|
||||
%%%
|
||||
|
||||
process_sm_iq(From, To,
|
||||
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
|
||||
case Type of
|
||||
set ->
|
||||
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
|
||||
get ->
|
||||
User = To#jid.luser,
|
||||
Server = To#jid.lserver,
|
||||
{Subscription, _Groups} =
|
||||
ejabberd_hooks:run_fold(roster_get_jid_info, Server,
|
||||
{none, []}, [User, Server, From]),
|
||||
if (Subscription == both) or (Subscription == from) or
|
||||
(From#jid.luser == To#jid.luser) and
|
||||
(From#jid.lserver == To#jid.lserver) ->
|
||||
UserListRecord =
|
||||
ejabberd_hooks:run_fold(privacy_get_user_list, Server,
|
||||
#userlist{}, [User, Server]),
|
||||
case ejabberd_hooks:run_fold(privacy_check_packet,
|
||||
Server, allow,
|
||||
[User, Server, UserListRecord,
|
||||
{To, From,
|
||||
#xmlel{name = <<"presence">>,
|
||||
attrs = [],
|
||||
children = []}},
|
||||
out])
|
||||
of
|
||||
allow -> get_last_iq(IQ, SubEl, User, Server);
|
||||
deny ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]}
|
||||
end;
|
||||
true ->
|
||||
Txt = <<"Not subscribed">>,
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_FORBIDDEN(Lang, Txt)]}
|
||||
end
|
||||
-spec process_sm_iq(iq()) -> iq().
|
||||
process_sm_iq(#iq{type = set, lang = Lang} = IQ) ->
|
||||
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
|
||||
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
|
||||
process_sm_iq(#iq{from = From, to = To, lang = Lang} = IQ) ->
|
||||
User = To#jid.luser,
|
||||
Server = To#jid.lserver,
|
||||
{Subscription, _Groups} =
|
||||
ejabberd_hooks:run_fold(roster_get_jid_info, Server,
|
||||
{none, []}, [User, Server, From]),
|
||||
if (Subscription == both) or (Subscription == from) or
|
||||
(From#jid.luser == To#jid.luser) and
|
||||
(From#jid.lserver == To#jid.lserver) ->
|
||||
UserListRecord =
|
||||
ejabberd_hooks:run_fold(privacy_get_user_list, Server,
|
||||
#userlist{}, [User, Server]),
|
||||
case ejabberd_hooks:run_fold(privacy_check_packet,
|
||||
Server, allow,
|
||||
[User, Server, UserListRecord,
|
||||
{To, From, #presence{}}, out]) of
|
||||
allow -> get_last_iq(IQ, User, Server);
|
||||
deny -> xmpp:make_error(IQ, xmpp:err_forbidden())
|
||||
end;
|
||||
true ->
|
||||
Txt = <<"Not subscribed">>,
|
||||
xmpp:make_error(IQ, xmpp:err_not_subscribed(Txt, Lang))
|
||||
end.
|
||||
|
||||
%% @spec (LUser::string(), LServer::string()) ->
|
||||
%% {ok, TimeStamp::integer(), Status::string()} | not_found | {error, Reason}
|
||||
-spec get_last(binary(), binary()) -> {ok, non_neg_integer(), binary()} |
|
||||
not_found | {error, any()}.
|
||||
get_last(LUser, LServer) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:get_last(LUser, LServer).
|
||||
|
||||
get_last_iq(#iq{lang = Lang} = IQ, SubEl, LUser, LServer) ->
|
||||
-spec get_last_iq(iq(), binary(), binary()) -> iq().
|
||||
get_last_iq(#iq{lang = Lang} = IQ, LUser, LServer) ->
|
||||
case ejabberd_sm:get_user_resources(LUser, LServer) of
|
||||
[] ->
|
||||
case get_last(LUser, LServer) of
|
||||
{error, _Reason} ->
|
||||
Txt = <<"Database failure">>,
|
||||
IQ#iq{type = error,
|
||||
sub_el = [SubEl, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)]};
|
||||
xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang));
|
||||
not_found ->
|
||||
Txt = <<"No info about last activity found">>,
|
||||
IQ#iq{type = error,
|
||||
sub_el = [SubEl, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)]};
|
||||
xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang));
|
||||
{ok, TimeStamp, Status} ->
|
||||
TimeStamp2 = p1_time_compat:system_time(seconds),
|
||||
Sec = TimeStamp2 - TimeStamp,
|
||||
IQ#iq{type = result,
|
||||
sub_el =
|
||||
[#xmlel{name = <<"query">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>, ?NS_LAST},
|
||||
{<<"seconds">>,
|
||||
iolist_to_binary(integer_to_list(Sec))}],
|
||||
children = [{xmlcdata, Status}]}]}
|
||||
xmpp:make_iq_result(IQ, #last{seconds = Sec, status = Status})
|
||||
end;
|
||||
_ ->
|
||||
IQ#iq{type = result,
|
||||
sub_el =
|
||||
[#xmlel{name = <<"query">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>, ?NS_LAST},
|
||||
{<<"seconds">>, <<"0">>}],
|
||||
children = []}]}
|
||||
xmpp:make_iq_result(IQ, #last{seconds = 0})
|
||||
end.
|
||||
|
||||
-spec register_user(binary(), binary()) -> {atomic, any()}.
|
||||
register_user(User, Server) ->
|
||||
on_presence_update(
|
||||
User,
|
||||
@ -209,18 +179,21 @@ register_user(User, Server) ->
|
||||
<<"RegisterResource">>,
|
||||
<<"Registered but didn't login">>).
|
||||
|
||||
-spec on_presence_update(binary(), binary(), binary(), binary()) -> {atomic, any()}.
|
||||
on_presence_update(User, Server, _Resource, Status) ->
|
||||
TimeStamp = p1_time_compat:system_time(seconds),
|
||||
store_last_info(User, Server, TimeStamp, Status).
|
||||
|
||||
-spec store_last_info(binary(), binary(), non_neg_integer(), binary()) ->
|
||||
{atomic, any()}.
|
||||
store_last_info(User, Server, TimeStamp, Status) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:store_last_info(LUser, LServer, TimeStamp, Status).
|
||||
|
||||
%% @spec (LUser::string(), LServer::string()) ->
|
||||
%% {ok, TimeStamp::integer(), Status::string()} | not_found
|
||||
-spec get_last_info(binary(), binary()) -> {ok, non_neg_integer(), binary()} |
|
||||
not_found.
|
||||
get_last_info(LUser, LServer) ->
|
||||
case get_last(LUser, LServer) of
|
||||
{error, _Reason} -> not_found;
|
||||
|
@ -36,7 +36,7 @@
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("xmpp.hrl").
|
||||
|
||||
-define(SUPERVISOR, ejabberd_sup).
|
||||
|
||||
@ -54,7 +54,7 @@
|
||||
-export([init/1, terminate/2, handle_call/3,
|
||||
handle_cast/2, handle_info/2, code_change/3]).
|
||||
|
||||
-export([iq_ping/3, user_online/3, user_offline/3,
|
||||
-export([iq_ping/1, user_online/3, user_offline/3,
|
||||
user_send/4, mod_opt_type/1, depends/2]).
|
||||
|
||||
-record(state,
|
||||
@ -73,10 +73,12 @@ start_link(Host, Opts) ->
|
||||
gen_server:start_link({local, Proc}, ?MODULE,
|
||||
[Host, Opts], []).
|
||||
|
||||
-spec start_ping(binary(), jid()) -> ok.
|
||||
start_ping(Host, JID) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?MODULE),
|
||||
gen_server:cast(Proc, {start_ping, JID}).
|
||||
|
||||
-spec stop_ping(binary(), jid()) -> ok.
|
||||
stop_ping(Host, JID) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?MODULE),
|
||||
gen_server:cast(Proc, {stop_ping, JID}).
|
||||
@ -181,10 +183,7 @@ handle_cast({iq_pong, JID, timeout}, State) ->
|
||||
handle_cast(_Msg, State) -> {noreply, State}.
|
||||
|
||||
handle_info({timeout, _TRef, {ping, JID}}, State) ->
|
||||
IQ = #iq{type = get,
|
||||
sub_el =
|
||||
[#xmlel{name = <<"ping">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_PING}], children = []}]},
|
||||
IQ = #iq{type = get, sub_els = [#ping{}]},
|
||||
Pid = self(),
|
||||
F = fun (Response) ->
|
||||
gen_server:cast(Pid, {iq_pong, JID, Response})
|
||||
@ -201,23 +200,22 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}.
|
||||
%%====================================================================
|
||||
%% Hook callbacks
|
||||
%%====================================================================
|
||||
iq_ping(_From, _To,
|
||||
#iq{type = Type, sub_el = SubEl, lang = Lang} = IQ) ->
|
||||
case {Type, SubEl} of
|
||||
{get, #xmlel{name = <<"ping">>}} ->
|
||||
IQ#iq{type = result, sub_el = []};
|
||||
_ ->
|
||||
Txt = <<"Ping query is incorrect">>,
|
||||
IQ#iq{type = error,
|
||||
sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt)]}
|
||||
end.
|
||||
-spec iq_ping(iq()) -> iq().
|
||||
iq_ping(#iq{type = get, sub_els = [#ping{}]} = IQ) ->
|
||||
xmpp:make_iq_result(IQ);
|
||||
iq_ping(#iq{lang = Lang} = IQ) ->
|
||||
Txt = <<"Ping query is incorrect">>,
|
||||
xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)).
|
||||
|
||||
-spec user_online(ejabberd_sm:sid(), jid(), any()) -> ok.
|
||||
user_online(_SID, JID, _Info) ->
|
||||
start_ping(JID#jid.lserver, JID).
|
||||
|
||||
-spec user_offline(ejabberd_sm:sid(), jid(), any()) -> ok.
|
||||
user_offline(_SID, JID, _Info) ->
|
||||
stop_ping(JID#jid.lserver, JID).
|
||||
|
||||
-spec user_send(stanza(), ejabberd_c2s:state(), jid(), jid()) -> stanza().
|
||||
user_send(Packet, _C2SState, JID, _From) ->
|
||||
start_ping(JID#jid.lserver, JID),
|
||||
Packet.
|
||||
@ -225,6 +223,7 @@ user_send(Packet, _C2SState, JID, _From) ->
|
||||
%%====================================================================
|
||||
%% Internal functions
|
||||
%%====================================================================
|
||||
-spec add_timer(jid(), non_neg_integer(), map()) -> map().
|
||||
add_timer(JID, Interval, Timers) ->
|
||||
LJID = jid:tolower(JID),
|
||||
NewTimers = case maps:find(LJID, Timers) of
|
||||
@ -237,6 +236,7 @@ add_timer(JID, Interval, Timers) ->
|
||||
{ping, JID}),
|
||||
maps:put(LJID, TRef, NewTimers).
|
||||
|
||||
-spec del_timer(jid(), map()) -> map().
|
||||
del_timer(JID, Timers) ->
|
||||
LJID = jid:tolower(JID),
|
||||
case maps:find(LJID, Timers) of
|
||||
@ -246,6 +246,7 @@ del_timer(JID, Timers) ->
|
||||
_ -> Timers
|
||||
end.
|
||||
|
||||
-spec cancel_timer(reference()) -> ok.
|
||||
cancel_timer(TRef) ->
|
||||
case erlang:cancel_timer(TRef) of
|
||||
false ->
|
||||
|
@ -31,8 +31,8 @@
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([start/2, stop/1, process_iq/3, export/1, import/1,
|
||||
process_iq_set/4, process_iq_get/5, get_user_list/3,
|
||||
-export([start/2, stop/1, process_iq/1, export/1, import/1,
|
||||
process_iq_set/2, process_iq_get/3, get_user_list/3,
|
||||
check_packet/6, remove_user/2,
|
||||
is_list_needdb/1, updated_list/3,
|
||||
item_to_xml/1, get_user_lists/2, import/3,
|
||||
@ -41,15 +41,15 @@
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("xmpp.hrl").
|
||||
|
||||
-include("mod_privacy.hrl").
|
||||
|
||||
-callback init(binary(), gen_mod:opts()) -> any().
|
||||
-callback import(binary(), #privacy{}) -> ok | pass.
|
||||
-callback process_lists_get(binary(), binary()) -> {none | binary(), [xmlel()]} | error.
|
||||
-callback process_lists_get(binary(), binary()) -> {none | binary(), [binary()]} | error.
|
||||
-callback process_list_get(binary(), binary(), binary()) -> [listitem()] | error | not_found.
|
||||
-callback process_default_set(binary(), binary(), {value, binary()} | false) -> {atomic, any()}.
|
||||
-callback process_default_set(binary(), binary(), binary() | none) -> {atomic, any()}.
|
||||
-callback process_active_set(binary(), binary(), binary()) -> [listitem()] | error.
|
||||
-callback remove_privacy_list(binary(), binary(), binary()) -> {atomic, any()}.
|
||||
-callback set_privacy_list(#privacy{}) -> any().
|
||||
@ -96,335 +96,276 @@ stop(Host) ->
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
|
||||
?NS_PRIVACY).
|
||||
|
||||
process_iq(_From, _To, IQ) ->
|
||||
SubEl = IQ#iq.sub_el,
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}.
|
||||
-spec process_iq(iq()) -> iq().
|
||||
process_iq(IQ) ->
|
||||
xmpp:make_error(IQ, xmpp:err_not_allowed()).
|
||||
|
||||
process_iq_get(_, From, _To, #iq{lang = Lang, sub_el = SubEl},
|
||||
-spec process_iq_get({error, error()} | iq(),
|
||||
iq(), userlist()) -> {error, error()} | {result, privacy_query()}.
|
||||
process_iq_get(_, #iq{from = From, lang = Lang,
|
||||
sub_els = [#privacy_query{lists = Lists}]},
|
||||
#userlist{name = Active}) ->
|
||||
#jid{luser = LUser, lserver = LServer} = From,
|
||||
#xmlel{children = Els} = SubEl,
|
||||
case fxml:remove_cdata(Els) of
|
||||
[] -> process_lists_get(LUser, LServer, Active, Lang);
|
||||
[#xmlel{name = Name, attrs = Attrs}] ->
|
||||
case Name of
|
||||
<<"list">> ->
|
||||
ListName = fxml:get_attr(<<"name">>, Attrs),
|
||||
process_list_get(LUser, LServer, ListName, Lang);
|
||||
_ ->
|
||||
Txt = <<"Unsupported tag name">>,
|
||||
{error, ?ERRT_BAD_REQUEST(Lang, Txt)}
|
||||
end;
|
||||
_ -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Too many elements">>)}
|
||||
case Lists of
|
||||
[] ->
|
||||
process_lists_get(LUser, LServer, Active, Lang);
|
||||
[#privacy_list{name = ListName}] ->
|
||||
process_list_get(LUser, LServer, ListName, Lang);
|
||||
_ ->
|
||||
Txt = <<"Too many <list/> elements">>,
|
||||
{error, xmpp:err_bad_request(Txt, Lang)}
|
||||
end.
|
||||
|
||||
-spec process_lists_get(binary(), binary(), binary(), undefined | binary()) ->
|
||||
{error, error()} | {result, privacy_query()}.
|
||||
process_lists_get(LUser, LServer, Active, Lang) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
case Mod:process_lists_get(LUser, LServer) of
|
||||
error -> {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)};
|
||||
{_Default, []} ->
|
||||
{result,
|
||||
[#xmlel{name = <<"query">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_PRIVACY}], children = []}]};
|
||||
{Default, LItems} ->
|
||||
DItems = case Default of
|
||||
none -> LItems;
|
||||
_ ->
|
||||
[#xmlel{name = <<"default">>,
|
||||
attrs = [{<<"name">>, Default}], children = []}
|
||||
| LItems]
|
||||
end,
|
||||
ADItems = case Active of
|
||||
none -> DItems;
|
||||
_ ->
|
||||
[#xmlel{name = <<"active">>,
|
||||
attrs = [{<<"name">>, Active}], children = []}
|
||||
| DItems]
|
||||
end,
|
||||
{result,
|
||||
[#xmlel{name = <<"query">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_PRIVACY}],
|
||||
children = ADItems}]}
|
||||
error ->
|
||||
Txt = <<"Database failure">>,
|
||||
{error, xmpp:err_internal_server_error(Txt, Lang)};
|
||||
{_Default, []} ->
|
||||
{result, #privacy_query{}};
|
||||
{Default, ListNames} ->
|
||||
{result,
|
||||
#privacy_query{active = Active,
|
||||
default = Default,
|
||||
lists = [#privacy_list{name = ListName}
|
||||
|| ListName <- ListNames]}}
|
||||
end.
|
||||
|
||||
process_list_get(LUser, LServer, {value, Name}, Lang) ->
|
||||
-spec process_list_get(binary(), binary(), binary(), undefined | binary()) ->
|
||||
{error, error()} | {result, privacy_query()}.
|
||||
process_list_get(LUser, LServer, Name, Lang) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
case Mod:process_list_get(LUser, LServer, Name) of
|
||||
error -> {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)};
|
||||
not_found -> {error, ?ERR_ITEM_NOT_FOUND};
|
||||
Items ->
|
||||
LItems = lists:map(fun item_to_xml/1, Items),
|
||||
{result,
|
||||
[#xmlel{name = <<"query">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_PRIVACY}],
|
||||
children =
|
||||
[#xmlel{name = <<"list">>, attrs = [{<<"name">>, Name}],
|
||||
children = LItems}]}]}
|
||||
end;
|
||||
process_list_get(_LUser, _LServer, false, _Lang) ->
|
||||
{error, ?ERR_BAD_REQUEST}.
|
||||
|
||||
item_to_xml(Item) ->
|
||||
Attrs1 = [{<<"action">>,
|
||||
action_to_list(Item#listitem.action)},
|
||||
{<<"order">>, order_to_list(Item#listitem.order)}],
|
||||
Attrs2 = case Item#listitem.type of
|
||||
none -> Attrs1;
|
||||
Type ->
|
||||
[{<<"type">>, type_to_list(Item#listitem.type)},
|
||||
{<<"value">>, value_to_list(Type, Item#listitem.value)}
|
||||
| Attrs1]
|
||||
end,
|
||||
SubEls = case Item#listitem.match_all of
|
||||
true -> [];
|
||||
false ->
|
||||
SE1 = case Item#listitem.match_iq of
|
||||
true ->
|
||||
[#xmlel{name = <<"iq">>, attrs = [],
|
||||
children = []}];
|
||||
false -> []
|
||||
end,
|
||||
SE2 = case Item#listitem.match_message of
|
||||
true ->
|
||||
[#xmlel{name = <<"message">>, attrs = [],
|
||||
children = []}
|
||||
| SE1];
|
||||
false -> SE1
|
||||
end,
|
||||
SE3 = case Item#listitem.match_presence_in of
|
||||
true ->
|
||||
[#xmlel{name = <<"presence-in">>, attrs = [],
|
||||
children = []}
|
||||
| SE2];
|
||||
false -> SE2
|
||||
end,
|
||||
SE4 = case Item#listitem.match_presence_out of
|
||||
true ->
|
||||
[#xmlel{name = <<"presence-out">>, attrs = [],
|
||||
children = []}
|
||||
| SE3];
|
||||
false -> SE3
|
||||
end,
|
||||
SE4
|
||||
end,
|
||||
#xmlel{name = <<"item">>, attrs = Attrs2,
|
||||
children = SubEls}.
|
||||
|
||||
action_to_list(Action) ->
|
||||
case Action of
|
||||
allow -> <<"allow">>;
|
||||
deny -> <<"deny">>
|
||||
error ->
|
||||
Txt = <<"Database failure">>,
|
||||
{error, xmpp:err_internal_server_error(Txt, Lang)};
|
||||
not_found ->
|
||||
Txt = <<"No privacy list with this name found">>,
|
||||
{error, xmpp:err_item_not_found(Txt, Lang)};
|
||||
Items ->
|
||||
LItems = lists:map(fun encode_list_item/1, Items),
|
||||
{result,
|
||||
#privacy_query{
|
||||
lists = [#privacy_list{name = Name, items = LItems}]}}
|
||||
end.
|
||||
|
||||
order_to_list(Order) ->
|
||||
iolist_to_binary(integer_to_list(Order)).
|
||||
-spec item_to_xml(listitem()) -> xmlel().
|
||||
item_to_xml(ListItem) ->
|
||||
xmpp:encode(encode_list_item(ListItem)).
|
||||
|
||||
type_to_list(Type) ->
|
||||
-spec encode_list_item(listitem()) -> privacy_item().
|
||||
encode_list_item(#listitem{action = Action,
|
||||
order = Order,
|
||||
type = Type,
|
||||
match_all = MatchAll,
|
||||
match_iq = MatchIQ,
|
||||
match_message = MatchMessage,
|
||||
match_presence_in = MatchPresenceIn,
|
||||
match_presence_out = MatchPresenceOut,
|
||||
value = Value}) ->
|
||||
Item = #privacy_item{action = Action,
|
||||
order = Order,
|
||||
type = case Type of
|
||||
none -> undefined;
|
||||
Type -> Type
|
||||
end,
|
||||
value = encode_value(Type, Value)},
|
||||
case MatchAll of
|
||||
true ->
|
||||
Item;
|
||||
false ->
|
||||
Item#privacy_item{message = MatchMessage,
|
||||
iq = MatchIQ,
|
||||
presence_in = MatchPresenceIn,
|
||||
presence_out = MatchPresenceOut}
|
||||
end.
|
||||
|
||||
-spec encode_value(listitem_type(), listitem_value()) -> undefined | binary().
|
||||
encode_value(Type, Val) ->
|
||||
case Type of
|
||||
jid -> <<"jid">>;
|
||||
group -> <<"group">>;
|
||||
subscription -> <<"subscription">>
|
||||
jid -> jid:to_string(Val);
|
||||
group -> Val;
|
||||
subscription ->
|
||||
case Val of
|
||||
both -> <<"both">>;
|
||||
to -> <<"to">>;
|
||||
from -> <<"from">>;
|
||||
none -> <<"none">>
|
||||
end;
|
||||
none -> undefined
|
||||
end.
|
||||
|
||||
value_to_list(Type, Val) ->
|
||||
-spec decode_value(jid | subscription | group | undefined, binary()) ->
|
||||
listitem_value().
|
||||
decode_value(Type, Value) ->
|
||||
case Type of
|
||||
jid -> jid:to_string(Val);
|
||||
group -> Val;
|
||||
subscription ->
|
||||
case Val of
|
||||
both -> <<"both">>;
|
||||
to -> <<"to">>;
|
||||
from -> <<"from">>;
|
||||
none -> <<"none">>
|
||||
end
|
||||
jid -> jid:from_string(Value);
|
||||
subscription ->
|
||||
case Value of
|
||||
<<"from">> -> from;
|
||||
<<"to">> -> to;
|
||||
<<"both">> -> both;
|
||||
<<"none">> -> none
|
||||
end;
|
||||
group -> Value;
|
||||
undefined -> none
|
||||
end.
|
||||
|
||||
list_to_action(S) ->
|
||||
case S of
|
||||
<<"allow">> -> allow;
|
||||
<<"deny">> -> deny
|
||||
end.
|
||||
|
||||
process_iq_set(_, From, _To, #iq{lang = Lang, sub_el = SubEl}) ->
|
||||
-spec process_iq_set({error, error()} |
|
||||
{result, privacy_query() | undefined, userlist()},
|
||||
iq()) -> {error, error()} |
|
||||
{result, undefined, userlist()}.
|
||||
process_iq_set(_, #iq{from = From, lang = Lang,
|
||||
sub_els = [#privacy_query{default = Default,
|
||||
active = Active,
|
||||
lists = Lists}]}) ->
|
||||
#jid{luser = LUser, lserver = LServer} = From,
|
||||
#xmlel{children = Els} = SubEl,
|
||||
case fxml:remove_cdata(Els) of
|
||||
[#xmlel{name = Name, attrs = Attrs,
|
||||
children = SubEls}] ->
|
||||
ListName = fxml:get_attr(<<"name">>, Attrs),
|
||||
case Name of
|
||||
<<"list">> ->
|
||||
process_list_set(LUser, LServer, ListName,
|
||||
fxml:remove_cdata(SubEls), Lang);
|
||||
<<"active">> ->
|
||||
process_active_set(LUser, LServer, ListName);
|
||||
<<"default">> ->
|
||||
process_default_set(LUser, LServer, ListName, Lang);
|
||||
_ ->
|
||||
Txt = <<"Unsupported tag name">>,
|
||||
{error, ?ERRT_BAD_REQUEST(Lang, Txt)}
|
||||
end;
|
||||
_ -> {error, ?ERR_BAD_REQUEST}
|
||||
case Lists of
|
||||
[#privacy_list{items = Items, name = ListName}]
|
||||
when Default == undefined, Active == undefined ->
|
||||
process_lists_set(LUser, LServer, ListName, Items, Lang);
|
||||
[] when Default == undefined, Active /= undefined ->
|
||||
process_active_set(LUser, LServer, Active, Lang);
|
||||
[] when Active == undefined, Default /= undefined ->
|
||||
process_default_set(LUser, LServer, Default, Lang);
|
||||
_ ->
|
||||
Txt = <<"There should be exactly one element in this query: "
|
||||
"<list/>, <active/> or <default/>">>,
|
||||
{error, xmpp:err_bad_request(Txt, Lang)}
|
||||
end.
|
||||
|
||||
-spec process_default_set(binary(), binary(), none | binary(),
|
||||
undefined | binary()) -> {error, error()} |
|
||||
{result, undefined}.
|
||||
process_default_set(LUser, LServer, Value, Lang) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
case Mod:process_default_set(LUser, LServer, Value) of
|
||||
{atomic, error} ->
|
||||
{error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)};
|
||||
{atomic, not_found} -> {error, ?ERR_ITEM_NOT_FOUND};
|
||||
{atomic, ok} -> {result, []};
|
||||
_ -> {error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||
{atomic, error} ->
|
||||
Txt = <<"Database failure">>,
|
||||
{error, xmpp:err_internal_server_error(Txt, Lang)};
|
||||
{atomic, not_found} ->
|
||||
Txt = <<"No privacy list with this name found">>,
|
||||
{error, xmpp:err_item_not_found(Txt, Lang)};
|
||||
{atomic, ok} ->
|
||||
{result, undefined};
|
||||
Err ->
|
||||
?ERROR_MSG("failed to set default list '~s' for user ~s@~s: ~p",
|
||||
[Value, LUser, LServer, Err]),
|
||||
{error, xmpp:err_internal_server_error()}
|
||||
end.
|
||||
|
||||
process_active_set(LUser, LServer, {value, Name}) ->
|
||||
-spec process_active_set(binary(), binary(), none | binary(),
|
||||
undefined | binary()) ->
|
||||
{error, error()} |
|
||||
{result, undefined, userlist()}.
|
||||
process_active_set(_LUser, _LServer, none, _Lang) ->
|
||||
{result, undefined, #userlist{}};
|
||||
process_active_set(LUser, LServer, Name, Lang) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
case Mod:process_active_set(LUser, LServer, Name) of
|
||||
error -> {error, ?ERR_ITEM_NOT_FOUND};
|
||||
Items ->
|
||||
NeedDb = is_list_needdb(Items),
|
||||
{result, [],
|
||||
#userlist{name = Name, list = Items, needdb = NeedDb}}
|
||||
end;
|
||||
process_active_set(_LUser, _LServer, false) ->
|
||||
{result, [], #userlist{}}.
|
||||
error ->
|
||||
Txt = <<"No privacy list with this name found">>,
|
||||
{error, xmpp:err_item_not_found(Txt, Lang)};
|
||||
Items ->
|
||||
NeedDb = is_list_needdb(Items),
|
||||
{result, undefined,
|
||||
#userlist{name = Name, list = Items, needdb = NeedDb}}
|
||||
end.
|
||||
|
||||
-spec set_privacy_list(privacy()) -> any().
|
||||
set_privacy_list(#privacy{us = {_, LServer}} = Privacy) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:set_privacy_list(Privacy).
|
||||
|
||||
process_list_set(LUser, LServer, {value, Name}, Els, Lang) ->
|
||||
case parse_items(Els) of
|
||||
false -> {error, ?ERR_BAD_REQUEST};
|
||||
remove ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
case Mod:remove_privacy_list(LUser, LServer, Name) of
|
||||
{atomic, conflict} ->
|
||||
Txt = <<"Cannot remove default list">>,
|
||||
{error, ?ERRT_CONFLICT(Lang, Txt)};
|
||||
{atomic, ok} ->
|
||||
ejabberd_sm:route(jid:make(LUser, LServer,
|
||||
<<"">>),
|
||||
jid:make(LUser, LServer, <<"">>),
|
||||
{broadcast, {privacy_list,
|
||||
#userlist{name = Name,
|
||||
list = []},
|
||||
Name}}),
|
||||
{result, []};
|
||||
_ -> {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)}
|
||||
end;
|
||||
List ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
case Mod:set_privacy_list(LUser, LServer, Name, List) of
|
||||
{atomic, ok} ->
|
||||
NeedDb = is_list_needdb(List),
|
||||
ejabberd_sm:route(jid:make(LUser, LServer,
|
||||
<<"">>),
|
||||
jid:make(LUser, LServer, <<"">>),
|
||||
{broadcast, {privacy_list,
|
||||
#userlist{name = Name,
|
||||
list = List,
|
||||
needdb = NeedDb},
|
||||
Name}}),
|
||||
{result, []};
|
||||
_ -> {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)}
|
||||
end
|
||||
-spec process_lists_set(binary(), binary(), binary(), [privacy_item()],
|
||||
undefined | binary()) -> {error, error()} |
|
||||
{result, undefined}.
|
||||
process_lists_set(LUser, LServer, Name, [], Lang) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
case Mod:remove_privacy_list(LUser, LServer, Name) of
|
||||
{atomic, conflict} ->
|
||||
Txt = <<"Cannot remove default list">>,
|
||||
{error, xmpp:err_conflict(Txt, Lang)};
|
||||
{atomic, ok} ->
|
||||
ejabberd_sm:route(jid:make(LUser, LServer,
|
||||
<<"">>),
|
||||
jid:make(LUser, LServer, <<"">>),
|
||||
{broadcast, {privacy_list,
|
||||
#userlist{name = Name,
|
||||
list = []},
|
||||
Name}}),
|
||||
{result, undefined};
|
||||
Err ->
|
||||
?ERROR_MSG("failed to remove privacy list '~s' for user ~s@~s: ~p",
|
||||
[Name, LUser, LServer, Err]),
|
||||
Txt = <<"Database failure">>,
|
||||
{error, xmpp:err_internal_server_error(Txt, Lang)}
|
||||
end;
|
||||
process_list_set(_LUser, _LServer, false, _Els, _Lang) ->
|
||||
{error, ?ERR_BAD_REQUEST}.
|
||||
process_lists_set(LUser, LServer, Name, Items, Lang) ->
|
||||
case catch lists:map(fun decode_item/1, Items) of
|
||||
{error, Why} ->
|
||||
Txt = xmpp:format_error(Why),
|
||||
{error, xmpp:err_bad_request(Txt, Lang)};
|
||||
List ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
case Mod:set_privacy_list(LUser, LServer, Name, List) of
|
||||
{atomic, ok} ->
|
||||
NeedDb = is_list_needdb(List),
|
||||
ejabberd_sm:route(jid:make(LUser, LServer,
|
||||
<<"">>),
|
||||
jid:make(LUser, LServer, <<"">>),
|
||||
{broadcast, {privacy_list,
|
||||
#userlist{name = Name,
|
||||
list = List,
|
||||
needdb = NeedDb},
|
||||
Name}}),
|
||||
{result, undefined};
|
||||
Err ->
|
||||
?ERROR_MSG("failed to set privacy list '~s' "
|
||||
"for user ~s@~s: ~p",
|
||||
[Name, LUser, LServer, Err]),
|
||||
Txt = <<"Database failure">>,
|
||||
{error, xmpp:err_internal_server_error(Txt, Lang)}
|
||||
end
|
||||
end.
|
||||
|
||||
parse_items([]) -> remove;
|
||||
parse_items(Els) -> parse_items(Els, []).
|
||||
|
||||
parse_items([], Res) ->
|
||||
lists:keysort(#listitem.order, Res);
|
||||
parse_items([#xmlel{name = <<"item">>, attrs = Attrs,
|
||||
children = SubEls}
|
||||
| Els],
|
||||
Res) ->
|
||||
Type = fxml:get_attr(<<"type">>, Attrs),
|
||||
Value = fxml:get_attr(<<"value">>, Attrs),
|
||||
SAction = fxml:get_attr(<<"action">>, Attrs),
|
||||
SOrder = fxml:get_attr(<<"order">>, Attrs),
|
||||
Action = case catch list_to_action(element(2, SAction))
|
||||
of
|
||||
{'EXIT', _} -> false;
|
||||
Val -> Val
|
||||
end,
|
||||
Order = case catch jlib:binary_to_integer(element(2,
|
||||
SOrder))
|
||||
of
|
||||
{'EXIT', _} -> false;
|
||||
IntVal ->
|
||||
if IntVal >= 0 -> IntVal;
|
||||
true -> false
|
||||
end
|
||||
-spec decode_item(privacy_item()) -> listitem().
|
||||
decode_item(#privacy_item{order = Order,
|
||||
action = Action,
|
||||
type = T,
|
||||
value = V,
|
||||
message = MatchMessage,
|
||||
iq = MatchIQ,
|
||||
presence_in = MatchPresenceIn,
|
||||
presence_out = MatchPresenceOut}) ->
|
||||
Value = try decode_value(T, V)
|
||||
catch _:_ ->
|
||||
throw({error, {bad_attr_value, <<"value">>,
|
||||
<<"item">>, ?NS_PRIVACY}})
|
||||
end,
|
||||
if (Action /= false) and (Order /= false) ->
|
||||
I1 = #listitem{action = Action, order = Order},
|
||||
I2 = case {Type, Value} of
|
||||
{{value, T}, {value, V}} ->
|
||||
case T of
|
||||
<<"jid">> ->
|
||||
case jid:from_string(V) of
|
||||
error -> false;
|
||||
JID ->
|
||||
I1#listitem{type = jid,
|
||||
value = jid:tolower(JID)}
|
||||
end;
|
||||
<<"group">> -> I1#listitem{type = group, value = V};
|
||||
<<"subscription">> ->
|
||||
case V of
|
||||
<<"none">> ->
|
||||
I1#listitem{type = subscription,
|
||||
value = none};
|
||||
<<"both">> ->
|
||||
I1#listitem{type = subscription,
|
||||
value = both};
|
||||
<<"from">> ->
|
||||
I1#listitem{type = subscription,
|
||||
value = from};
|
||||
<<"to">> ->
|
||||
I1#listitem{type = subscription, value = to};
|
||||
_ -> false
|
||||
end
|
||||
end;
|
||||
{{value, _}, false} -> false;
|
||||
_ -> I1
|
||||
end,
|
||||
case I2 of
|
||||
false -> false;
|
||||
_ ->
|
||||
case parse_matches(I2, fxml:remove_cdata(SubEls)) of
|
||||
false -> false;
|
||||
I3 -> parse_items(Els, [I3 | Res])
|
||||
end
|
||||
end;
|
||||
true -> false
|
||||
end;
|
||||
parse_items(_, _Res) -> false.
|
||||
|
||||
parse_matches(Item, []) ->
|
||||
Item#listitem{match_all = true};
|
||||
parse_matches(Item, Els) -> parse_matches1(Item, Els).
|
||||
|
||||
parse_matches1(Item, []) -> Item;
|
||||
parse_matches1(Item,
|
||||
[#xmlel{name = <<"message">>} | Els]) ->
|
||||
parse_matches1(Item#listitem{match_message = true},
|
||||
Els);
|
||||
parse_matches1(Item, [#xmlel{name = <<"iq">>} | Els]) ->
|
||||
parse_matches1(Item#listitem{match_iq = true}, Els);
|
||||
parse_matches1(Item,
|
||||
[#xmlel{name = <<"presence-in">>} | Els]) ->
|
||||
parse_matches1(Item#listitem{match_presence_in = true},
|
||||
Els);
|
||||
parse_matches1(Item,
|
||||
[#xmlel{name = <<"presence-out">>} | Els]) ->
|
||||
parse_matches1(Item#listitem{match_presence_out = true},
|
||||
Els);
|
||||
parse_matches1(_Item, [#xmlel{} | _Els]) -> false.
|
||||
Type = case T of
|
||||
undefined -> none;
|
||||
_ -> T
|
||||
end,
|
||||
ListItem = #listitem{order = Order,
|
||||
action = Action,
|
||||
type = Type,
|
||||
value = Value},
|
||||
if MatchMessage and MatchIQ and MatchPresenceIn and MatchPresenceOut ->
|
||||
ListItem#listitem{match_all = true};
|
||||
not (MatchMessage or MatchIQ or MatchPresenceIn or MatchPresenceOut) ->
|
||||
ListItem#listitem{match_all = true};
|
||||
true ->
|
||||
ListItem#listitem{match_iq = MatchIQ,
|
||||
match_message = MatchMessage,
|
||||
match_presence_in = MatchPresenceIn,
|
||||
match_presence_out = MatchPresenceOut}
|
||||
end.
|
||||
|
||||
-spec is_list_needdb([listitem()]) -> boolean().
|
||||
is_list_needdb(Items) ->
|
||||
lists:any(fun (X) ->
|
||||
case X#listitem.type of
|
||||
@ -435,6 +376,7 @@ is_list_needdb(Items) ->
|
||||
end,
|
||||
Items).
|
||||
|
||||
-spec get_user_list(userlist(), binary(), binary()) -> userlist().
|
||||
get_user_list(_Acc, User, Server) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
@ -444,6 +386,7 @@ get_user_list(_Acc, User, Server) ->
|
||||
#userlist{name = Default, list = Items,
|
||||
needdb = NeedDb}.
|
||||
|
||||
-spec get_user_lists(binary(), binary()) -> {ok, privacy()} | error.
|
||||
get_user_lists(User, Server) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
@ -453,6 +396,8 @@ get_user_lists(User, Server) ->
|
||||
%% From is the sender, To is the destination.
|
||||
%% If Dir = out, User@Server is the sender account (From).
|
||||
%% If Dir = in, User@Server is the destination account (To).
|
||||
-spec check_packet(allow | deny, binary(), binary(), userlist(),
|
||||
{jid(), jid(), stanza()}, in | out) -> allow | deny.
|
||||
check_packet(_, _User, _Server, _UserList,
|
||||
{#jid{luser = <<"">>, lserver = Server} = _From,
|
||||
#jid{lserver = Server} = _To, _},
|
||||
@ -470,22 +415,16 @@ check_packet(_, _User, _Server, _UserList,
|
||||
allow;
|
||||
check_packet(_, User, Server,
|
||||
#userlist{list = List, needdb = NeedDb},
|
||||
{From, To, #xmlel{name = PName, attrs = Attrs}}, Dir) ->
|
||||
{From, To, Packet}, Dir) ->
|
||||
case List of
|
||||
[] -> allow;
|
||||
_ ->
|
||||
PType = case PName of
|
||||
<<"message">> -> message;
|
||||
<<"iq">> -> iq;
|
||||
<<"presence">> ->
|
||||
case fxml:get_attr_s(<<"type">>, Attrs) of
|
||||
%% notification
|
||||
<<"">> -> presence;
|
||||
<<"unavailable">> -> presence;
|
||||
%% subscribe, subscribed, unsubscribe,
|
||||
%% unsubscribed, error, probe, or other
|
||||
_ -> other
|
||||
end
|
||||
PType = case Packet of
|
||||
#message{} -> message;
|
||||
#iq{} -> iq;
|
||||
#presence{type = available} -> presence;
|
||||
#presence{type = unavailable} -> presence;
|
||||
_ -> other
|
||||
end,
|
||||
PType2 = case {PType, Dir} of
|
||||
{message, in} -> message;
|
||||
@ -511,6 +450,10 @@ check_packet(_, User, Server,
|
||||
Groups)
|
||||
end.
|
||||
|
||||
-spec check_packet_aux([listitem()],
|
||||
message | iq | presence_in | presence_out | other,
|
||||
ljid(), none | both | from | to, [binary()]) ->
|
||||
allow | deny.
|
||||
%% Ptype = mesage | iq | presence_in | presence_out | other
|
||||
check_packet_aux([], _PType, _JID, _Subscription,
|
||||
_Groups) ->
|
||||
@ -536,6 +479,9 @@ check_packet_aux([Item | List], PType, JID,
|
||||
check_packet_aux(List, PType, JID, Subscription, Groups)
|
||||
end.
|
||||
|
||||
-spec is_ptype_match(listitem(),
|
||||
message | iq | presence_in | presence_out | other) ->
|
||||
boolean().
|
||||
is_ptype_match(Item, PType) ->
|
||||
case Item#listitem.match_all of
|
||||
true -> true;
|
||||
@ -549,6 +495,8 @@ is_ptype_match(Item, PType) ->
|
||||
end
|
||||
end.
|
||||
|
||||
-spec is_type_match(jid | subscription | group, listitem_value(),
|
||||
ljid(), none | both | from | to, [binary()]) -> boolean().
|
||||
is_type_match(Type, Value, JID, Subscription, Groups) ->
|
||||
case Type of
|
||||
jid ->
|
||||
@ -575,6 +523,7 @@ remove_user(User, Server) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:remove_user(LUser, LServer).
|
||||
|
||||
-spec updated_list(userlist(), userlist(), userlist()) -> userlist().
|
||||
updated_list(_, #userlist{name = OldName} = Old,
|
||||
#userlist{name = NewName} = New) ->
|
||||
if OldName == NewName -> New;
|
||||
|
@ -35,11 +35,7 @@ process_lists_get(LUser, LServer) ->
|
||||
{'EXIT', _Reason} -> error;
|
||||
[] -> {none, []};
|
||||
[#privacy{default = Default, lists = Lists}] ->
|
||||
LItems = lists:map(fun ({N, _}) ->
|
||||
#xmlel{name = <<"list">>,
|
||||
attrs = [{<<"name">>, N}],
|
||||
children = []}
|
||||
end, Lists),
|
||||
LItems = lists:map(fun ({N, _}) -> N end, Lists),
|
||||
{Default, LItems}
|
||||
end.
|
||||
|
||||
@ -54,7 +50,15 @@ process_list_get(LUser, LServer, Name) ->
|
||||
end
|
||||
end.
|
||||
|
||||
process_default_set(LUser, LServer, {value, Name}) ->
|
||||
process_default_set(LUser, LServer, none) ->
|
||||
F = fun () ->
|
||||
case mnesia:read({privacy, {LUser, LServer}}) of
|
||||
[] -> ok;
|
||||
[R] -> mnesia:write(R#privacy{default = none})
|
||||
end
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
process_default_set(LUser, LServer, Name) ->
|
||||
F = fun () ->
|
||||
case mnesia:read({privacy, {LUser, LServer}}) of
|
||||
[] -> not_found;
|
||||
@ -68,14 +72,6 @@ process_default_set(LUser, LServer, {value, Name}) ->
|
||||
end
|
||||
end
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
process_default_set(LUser, LServer, false) ->
|
||||
F = fun () ->
|
||||
case mnesia:read({privacy, {LUser, LServer}}) of
|
||||
[] -> ok;
|
||||
[R] -> mnesia:write(R#privacy{default = none})
|
||||
end
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
|
||||
process_active_set(LUser, LServer, Name) ->
|
||||
|
@ -31,12 +31,7 @@ init(_Host, _Opts) ->
|
||||
process_lists_get(LUser, LServer) ->
|
||||
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
|
||||
{ok, #privacy{default = Default, lists = Lists}} ->
|
||||
LItems = lists:map(fun ({N, _}) ->
|
||||
#xmlel{name = <<"list">>,
|
||||
attrs = [{<<"name">>, N}],
|
||||
children = []}
|
||||
end,
|
||||
Lists),
|
||||
LItems = lists:map(fun ({N, _}) -> N end, Lists),
|
||||
{Default, LItems};
|
||||
{error, notfound} ->
|
||||
{none, []};
|
||||
@ -57,7 +52,15 @@ process_list_get(LUser, LServer, Name) ->
|
||||
error
|
||||
end.
|
||||
|
||||
process_default_set(LUser, LServer, {value, Name}) ->
|
||||
process_default_set(LUser, LServer, none) ->
|
||||
{atomic,
|
||||
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
|
||||
{ok, R} ->
|
||||
ejabberd_riak:put(R#privacy{default = none}, privacy_schema());
|
||||
{error, _} ->
|
||||
ok
|
||||
end};
|
||||
process_default_set(LUser, LServer, Name) ->
|
||||
{atomic,
|
||||
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
|
||||
{ok, #privacy{lists = Lists} = P} ->
|
||||
@ -71,14 +74,6 @@ process_default_set(LUser, LServer, {value, Name}) ->
|
||||
end;
|
||||
{error, _} ->
|
||||
not_found
|
||||
end};
|
||||
process_default_set(LUser, LServer, false) ->
|
||||
{atomic,
|
||||
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
|
||||
{ok, R} ->
|
||||
ejabberd_riak:put(R#privacy{default = none}, privacy_schema());
|
||||
{error, _} ->
|
||||
ok
|
||||
end}.
|
||||
|
||||
process_active_set(LUser, LServer, Name) ->
|
||||
|
@ -47,12 +47,7 @@ process_lists_get(LUser, LServer) ->
|
||||
end,
|
||||
case catch sql_get_privacy_list_names(LUser, LServer) of
|
||||
{selected, Names} ->
|
||||
LItems = lists:map(fun ({N}) ->
|
||||
#xmlel{name = <<"list">>,
|
||||
attrs = [{<<"name">>, N}],
|
||||
children = []}
|
||||
end,
|
||||
Names),
|
||||
LItems = lists:map(fun ({N}) -> N end, Names),
|
||||
{Default, LItems};
|
||||
_ -> error
|
||||
end.
|
||||
@ -69,7 +64,15 @@ process_list_get(LUser, LServer, Name) ->
|
||||
_ -> error
|
||||
end.
|
||||
|
||||
process_default_set(LUser, LServer, {value, Name}) ->
|
||||
process_default_set(LUser, LServer, none) ->
|
||||
case catch sql_unset_default_privacy_list(LUser,
|
||||
LServer)
|
||||
of
|
||||
{'EXIT', _Reason} -> {atomic, error};
|
||||
{error, _Reason} -> {atomic, error};
|
||||
_ -> {atomic, ok}
|
||||
end;
|
||||
process_default_set(LUser, LServer, Name) ->
|
||||
F = fun () ->
|
||||
case sql_get_privacy_list_names_t(LUser) of
|
||||
{selected, []} -> not_found;
|
||||
@ -80,15 +83,7 @@ process_default_set(LUser, LServer, {value, Name}) ->
|
||||
end
|
||||
end
|
||||
end,
|
||||
sql_queries:sql_transaction(LServer, F);
|
||||
process_default_set(LUser, LServer, false) ->
|
||||
case catch sql_unset_default_privacy_list(LUser,
|
||||
LServer)
|
||||
of
|
||||
{'EXIT', _Reason} -> {atomic, error};
|
||||
{error, _Reason} -> {atomic, error};
|
||||
_ -> {atomic, ok}
|
||||
end.
|
||||
sql_queries:sql_transaction(LServer, F).
|
||||
|
||||
process_active_set(LUser, LServer, Name) ->
|
||||
case catch sql_get_privacy_list_id(LUser, LServer, Name) of
|
||||
|
@ -31,14 +31,14 @@
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([start/2, stop/1, process_sm_iq/3, import/3,
|
||||
-export([start/2, stop/1, process_sm_iq/1, import/3,
|
||||
remove_user/2, get_data/2, export/1, import/1,
|
||||
mod_opt_type/1, set_data/3, depends/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("xmpp.hrl").
|
||||
-include("mod_private.hrl").
|
||||
|
||||
-callback init(binary(), gen_mod:opts()) -> any().
|
||||
@ -46,10 +46,7 @@
|
||||
-callback set_data(binary(), binary(), [{binary(), xmlel()}]) -> {atomic, any()}.
|
||||
-callback get_data(binary(), binary(), binary()) -> {ok, xmlel()} | error.
|
||||
-callback get_all_data(binary(), binary()) -> [xmlel()].
|
||||
|
||||
-define(Xmlel_Query(Attrs, Children),
|
||||
#xmlel{name = <<"query">>, attrs = Attrs,
|
||||
children = Children}).
|
||||
-callback remove_user(binary(), binary()) -> {atomic, any()}.
|
||||
|
||||
start(Host, Opts) ->
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
|
||||
@ -67,90 +64,55 @@ stop(Host) ->
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
|
||||
?NS_PRIVATE).
|
||||
|
||||
process_sm_iq(#jid{luser = LUser, lserver = LServer},
|
||||
#jid{luser = LUser, lserver = LServer}, #iq{lang = Lang} = IQ)
|
||||
when IQ#iq.type == set ->
|
||||
case IQ#iq.sub_el of
|
||||
#xmlel{name = <<"query">>, children = Xmlels} ->
|
||||
case filter_xmlels(Xmlels) of
|
||||
[] ->
|
||||
Txt = <<"No private data found in this query">>,
|
||||
IQ#iq{type = error,
|
||||
sub_el = [IQ#iq.sub_el, ?ERRT_NOT_ACCEPTABLE(Lang, Txt)]};
|
||||
Data ->
|
||||
set_data(LUser, LServer, Data),
|
||||
IQ#iq{type = result, sub_el = []}
|
||||
end;
|
||||
_ ->
|
||||
Txt = <<"No query found">>,
|
||||
IQ#iq{type = error,
|
||||
sub_el = [IQ#iq.sub_el, ?ERRT_NOT_ACCEPTABLE(Lang, Txt)]}
|
||||
-spec process_sm_iq(iq()) -> iq().
|
||||
process_sm_iq(#iq{type = Type, lang = Lang,
|
||||
from = #jid{luser = LUser, lserver = LServer},
|
||||
to = #jid{luser = LUser, lserver = LServer},
|
||||
sub_els = [#private{xml_els = Els0}]} = IQ) ->
|
||||
case filter_xmlels(Els0) of
|
||||
[] ->
|
||||
Txt = <<"No private data found in this query">>,
|
||||
xmpp:make_error(IQ, xmpp:err_bad_format(Txt, Lang));
|
||||
Data when Type == set ->
|
||||
set_data(LUser, LServer, Data),
|
||||
xmpp:make_iq_result(IQ);
|
||||
Data when Type == get ->
|
||||
StorageEls = get_data(LUser, LServer, Data),
|
||||
xmpp:make_iq_result(IQ, #private{xml_els = StorageEls})
|
||||
end;
|
||||
%%
|
||||
process_sm_iq(#jid{luser = LUser, lserver = LServer},
|
||||
#jid{luser = LUser, lserver = LServer}, #iq{lang = Lang} = IQ)
|
||||
when IQ#iq.type == get ->
|
||||
case IQ#iq.sub_el of
|
||||
#xmlel{name = <<"query">>, attrs = Attrs,
|
||||
children = Xmlels} ->
|
||||
case filter_xmlels(Xmlels) of
|
||||
[] ->
|
||||
Txt = <<"No private data found in this query">>,
|
||||
IQ#iq{type = error,
|
||||
sub_el = [IQ#iq.sub_el, ?ERRT_BAD_FORMAT(Lang, Txt)]};
|
||||
Data ->
|
||||
case catch get_data(LUser, LServer, Data) of
|
||||
{'EXIT', _Reason} ->
|
||||
Txt = <<"Database failure">>,
|
||||
IQ#iq{type = error,
|
||||
sub_el =
|
||||
[IQ#iq.sub_el, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)]};
|
||||
Storage_Xmlels ->
|
||||
IQ#iq{type = result,
|
||||
sub_el = [?Xmlel_Query(Attrs, Storage_Xmlels)]}
|
||||
end
|
||||
end;
|
||||
_ ->
|
||||
Txt = <<"No query found">>,
|
||||
IQ#iq{type = error,
|
||||
sub_el = [IQ#iq.sub_el, ?ERRT_BAD_FORMAT(Lang, Txt)]}
|
||||
end;
|
||||
%%
|
||||
process_sm_iq(_From, _To, #iq{lang = Lang} = IQ) ->
|
||||
process_sm_iq(#iq{lang = Lang} = IQ) ->
|
||||
Txt = <<"Query to another users is forbidden">>,
|
||||
IQ#iq{type = error,
|
||||
sub_el = [IQ#iq.sub_el, ?ERRT_FORBIDDEN(Lang, Txt)]}.
|
||||
xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang)).
|
||||
|
||||
filter_xmlels(Xmlels) -> filter_xmlels(Xmlels, []).
|
||||
|
||||
filter_xmlels([], Data) -> lists:reverse(Data);
|
||||
filter_xmlels([#xmlel{attrs = Attrs} = Xmlel | Xmlels],
|
||||
Data) ->
|
||||
case fxml:get_attr_s(<<"xmlns">>, Attrs) of
|
||||
<<"">> -> [];
|
||||
XmlNS -> filter_xmlels(Xmlels, [{XmlNS, Xmlel} | Data])
|
||||
end;
|
||||
filter_xmlels([_ | Xmlels], Data) ->
|
||||
filter_xmlels(Xmlels, Data).
|
||||
-spec filter_xmlels([xmlel()]) -> [{binary(), xmlel()}].
|
||||
filter_xmlels(Els) ->
|
||||
lists:flatmap(
|
||||
fun(#xmlel{} = El) ->
|
||||
case fxml:get_tag_attr_s(<<"xmlns">>, El) of
|
||||
<<"">> -> [];
|
||||
NS -> [{NS, El}]
|
||||
end
|
||||
end, Els).
|
||||
|
||||
-spec set_data(binary(), binary(), [{binary(), xmlel()}]) -> {atomic, any()}.
|
||||
set_data(LUser, LServer, Data) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:set_data(LUser, LServer, Data).
|
||||
|
||||
-spec get_data(binary(), binary(), [{binary(), xmlel()}]) -> [xmlel()].
|
||||
get_data(LUser, LServer, Data) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
get_data(LUser, LServer, Data, Mod, []).
|
||||
|
||||
get_data(_LUser, _LServer, [], _Mod, Storage_Xmlels) ->
|
||||
lists:reverse(Storage_Xmlels);
|
||||
get_data(LUser, LServer, [{XmlNS, Xmlel} | Data], Mod, Storage_Xmlels) ->
|
||||
case Mod:get_data(LUser, LServer, XmlNS) of
|
||||
{ok, Storage_Xmlel} ->
|
||||
get_data(LUser, LServer, Data, Mod, [Storage_Xmlel | Storage_Xmlels]);
|
||||
error ->
|
||||
get_data(LUser, LServer, Data, Mod, [Xmlel | Storage_Xmlels])
|
||||
end.
|
||||
lists:map(
|
||||
fun({NS, El}) ->
|
||||
case Mod:get_data(LUser, LServer, NS) of
|
||||
{ok, StorageEl} ->
|
||||
StorageEl;
|
||||
error ->
|
||||
El
|
||||
end
|
||||
end, Data).
|
||||
|
||||
-spec get_data(binary(), binary()) -> [xmlel()].
|
||||
get_data(LUser, LServer) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:get_all_data(LUser, LServer).
|
||||
|
@ -41,12 +41,12 @@
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([start/2, stop/1, process_iq/3, export/1,
|
||||
import/1, process_local_iq/3, get_user_roster/2,
|
||||
-export([start/2, stop/1, process_iq/1, export/1,
|
||||
import/1, process_local_iq/1, get_user_roster/2,
|
||||
import/3, get_subscription_lists/3, get_roster/2,
|
||||
get_in_pending_subscriptions/3, in_subscription/6,
|
||||
out_subscription/4, set_items/3, remove_user/2,
|
||||
get_jid_info/4, item_to_xml/1, webadmin_page/3,
|
||||
get_jid_info/4, encode_item/1, webadmin_page/3,
|
||||
webadmin_user/4, get_versioning_feature/2,
|
||||
roster_versioning_enabled/1, roster_version/2,
|
||||
mod_opt_type/1, set_roster/1, depends/2]).
|
||||
@ -54,7 +54,7 @@
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("xmpp.hrl").
|
||||
|
||||
-include("mod_roster.hrl").
|
||||
|
||||
@ -139,24 +139,23 @@ stop(Host) ->
|
||||
depends(_Host, _Opts) ->
|
||||
[].
|
||||
|
||||
process_iq(From, To, IQ) when ((From#jid.luser == <<"">>) andalso (From#jid.resource == <<"">>)) ->
|
||||
process_iq_manager(From, To, IQ);
|
||||
process_iq(#iq{from = #jid{luser = <<"">>},
|
||||
to = #jid{resource = <<"">>}} = IQ) ->
|
||||
process_iq_manager(IQ);
|
||||
|
||||
process_iq(From, To, IQ) ->
|
||||
#iq{sub_el = SubEl, lang = Lang} = IQ,
|
||||
process_iq(#iq{from = From, lang = Lang} = IQ) ->
|
||||
#jid{lserver = LServer} = From,
|
||||
case lists:member(LServer, ?MYHOSTS) of
|
||||
true -> process_local_iq(From, To, IQ);
|
||||
true -> process_local_iq(IQ);
|
||||
_ ->
|
||||
Txt = <<"The query is only allowed from local users">>,
|
||||
IQ#iq{type = error,
|
||||
sub_el = [SubEl, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)]}
|
||||
xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang))
|
||||
end.
|
||||
|
||||
process_local_iq(From, To, #iq{type = Type} = IQ) ->
|
||||
process_local_iq(#iq{type = Type} = IQ) ->
|
||||
case Type of
|
||||
set -> try_process_iq_set(From, To, IQ);
|
||||
get -> process_iq_get(From, To, IQ)
|
||||
set -> try_process_iq_set(IQ);
|
||||
get -> process_iq_get(IQ)
|
||||
end.
|
||||
|
||||
roster_hash(Items) ->
|
||||
@ -179,10 +178,7 @@ roster_version_on_db(Host) ->
|
||||
get_versioning_feature(Acc, Host) ->
|
||||
case roster_versioning_enabled(Host) of
|
||||
true ->
|
||||
Feature = #xmlel{name = <<"ver">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_ROSTER_VER}],
|
||||
children = []},
|
||||
[Feature | Acc];
|
||||
[#rosterver_feature{}|Acc];
|
||||
false -> []
|
||||
end.
|
||||
|
||||
@ -221,82 +217,61 @@ write_roster_version(LUser, LServer, InTransaction) ->
|
||||
%% - roster versioning is not used by the client OR
|
||||
%% - roster versioning is used by server and client, BUT the server isn't storing versions on db OR
|
||||
%% - the roster version from client don't match current version.
|
||||
process_iq_get(From, To, #iq{sub_el = SubEl} = IQ) ->
|
||||
process_iq_get(#iq{from = From, to = To, lang = Lang,
|
||||
sub_els = [#roster_query{ver = RequestedVersion}]} = IQ) ->
|
||||
LUser = From#jid.luser,
|
||||
LServer = From#jid.lserver,
|
||||
US = {LUser, LServer},
|
||||
try {ItemsToSend, VersionToSend} = case
|
||||
{fxml:get_tag_attr(<<"ver">>, SubEl),
|
||||
roster_versioning_enabled(LServer),
|
||||
roster_version_on_db(LServer)}
|
||||
of
|
||||
{{value, RequestedVersion}, true,
|
||||
true} ->
|
||||
case read_roster_version(LUser,
|
||||
LServer)
|
||||
of
|
||||
error ->
|
||||
RosterVersion =
|
||||
write_roster_version(LUser,
|
||||
LServer),
|
||||
{lists:map(fun item_to_xml/1,
|
||||
ejabberd_hooks:run_fold(roster_get,
|
||||
To#jid.lserver,
|
||||
[],
|
||||
[US])),
|
||||
RosterVersion};
|
||||
RequestedVersion ->
|
||||
{false, false};
|
||||
NewVersion ->
|
||||
{lists:map(fun item_to_xml/1,
|
||||
ejabberd_hooks:run_fold(roster_get,
|
||||
To#jid.lserver,
|
||||
[],
|
||||
[US])),
|
||||
NewVersion}
|
||||
end;
|
||||
{{value, RequestedVersion}, true,
|
||||
false} ->
|
||||
RosterItems =
|
||||
ejabberd_hooks:run_fold(roster_get,
|
||||
To#jid.lserver,
|
||||
[],
|
||||
[US]),
|
||||
case roster_hash(RosterItems) of
|
||||
RequestedVersion ->
|
||||
{false, false};
|
||||
New ->
|
||||
{lists:map(fun item_to_xml/1,
|
||||
RosterItems),
|
||||
New}
|
||||
end;
|
||||
_ ->
|
||||
{lists:map(fun item_to_xml/1,
|
||||
ejabberd_hooks:run_fold(roster_get,
|
||||
To#jid.lserver,
|
||||
[],
|
||||
[US])),
|
||||
false}
|
||||
end,
|
||||
IQ#iq{type = result,
|
||||
sub_el =
|
||||
case {ItemsToSend, VersionToSend} of
|
||||
{false, false} -> [];
|
||||
{Items, false} ->
|
||||
[#xmlel{name = <<"query">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_ROSTER}],
|
||||
children = Items}];
|
||||
{Items, Version} ->
|
||||
[#xmlel{name = <<"query">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>, ?NS_ROSTER},
|
||||
{<<"ver">>, Version}],
|
||||
children = Items}]
|
||||
end}
|
||||
catch
|
||||
_:_ ->
|
||||
IQ#iq{type = error,
|
||||
sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
|
||||
try {ItemsToSend, VersionToSend} =
|
||||
case {roster_versioning_enabled(LServer),
|
||||
roster_version_on_db(LServer)} of
|
||||
{true, true} when RequestedVersion /= undefined ->
|
||||
case read_roster_version(LUser, LServer) of
|
||||
error ->
|
||||
RosterVersion = write_roster_version(LUser, LServer),
|
||||
{lists:map(fun encode_item/1,
|
||||
ejabberd_hooks:run_fold(
|
||||
roster_get, To#jid.lserver, [], [US])),
|
||||
RosterVersion};
|
||||
RequestedVersion ->
|
||||
{false, false};
|
||||
NewVersion ->
|
||||
{lists:map(fun encode_item/1,
|
||||
ejabberd_hooks:run_fold(
|
||||
roster_get, To#jid.lserver, [], [US])),
|
||||
NewVersion}
|
||||
end;
|
||||
{true, false} when RequestedVersion /= undefined ->
|
||||
RosterItems = ejabberd_hooks:run_fold(
|
||||
roster_get, To#jid.lserver, [], [US]),
|
||||
case roster_hash(RosterItems) of
|
||||
RequestedVersion ->
|
||||
{false, false};
|
||||
New ->
|
||||
{lists:map(fun encode_item/1, RosterItems), New}
|
||||
end;
|
||||
_ ->
|
||||
{lists:map(fun encode_item/1,
|
||||
ejabberd_hooks:run_fold(
|
||||
roster_get, To#jid.lserver, [], [US])),
|
||||
false}
|
||||
end,
|
||||
xmpp:make_iq_result(
|
||||
IQ,
|
||||
case {ItemsToSend, VersionToSend} of
|
||||
{false, false} ->
|
||||
undefined;
|
||||
{Items, false} ->
|
||||
#roster_query{items = Items};
|
||||
{Items, Version} ->
|
||||
#roster_query{items = Items,
|
||||
ver = Version}
|
||||
end)
|
||||
catch E:R ->
|
||||
?ERROR_MSG("failed to process roster get for ~s: ~p",
|
||||
[jid:to_string(From), {E, {R, erlang:get_stacktrace()}}]),
|
||||
Txt = <<"Roster module has failed">>,
|
||||
xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
|
||||
end.
|
||||
|
||||
get_user_roster(Acc, {LUser, LServer}) ->
|
||||
@ -320,144 +295,83 @@ set_roster(#roster{us = {LUser, LServer}, jid = LJID} = Item) ->
|
||||
roster_subscribe_t(LUser, LServer, LJID, Item)
|
||||
end).
|
||||
|
||||
item_to_xml(Item) ->
|
||||
Attrs1 = [{<<"jid">>,
|
||||
jid:to_string(Item#roster.jid)}],
|
||||
Attrs2 = case Item#roster.name of
|
||||
<<"">> -> Attrs1;
|
||||
Name -> [{<<"name">>, Name} | Attrs1]
|
||||
end,
|
||||
Attrs3 = case Item#roster.subscription of
|
||||
none -> [{<<"subscription">>, <<"none">>} | Attrs2];
|
||||
from -> [{<<"subscription">>, <<"from">>} | Attrs2];
|
||||
to -> [{<<"subscription">>, <<"to">>} | Attrs2];
|
||||
both -> [{<<"subscription">>, <<"both">>} | Attrs2];
|
||||
remove -> [{<<"subscription">>, <<"remove">>} | Attrs2]
|
||||
end,
|
||||
Attrs4 = case ask_to_pending(Item#roster.ask) of
|
||||
out -> [{<<"ask">>, <<"subscribe">>} | Attrs3];
|
||||
both -> [{<<"ask">>, <<"subscribe">>} | Attrs3];
|
||||
_ -> Attrs3
|
||||
end,
|
||||
SubEls1 = lists:map(fun (G) ->
|
||||
#xmlel{name = <<"group">>, attrs = [],
|
||||
children = [{xmlcdata, G}]}
|
||||
end,
|
||||
Item#roster.groups),
|
||||
SubEls = SubEls1 ++ Item#roster.xs,
|
||||
#xmlel{name = <<"item">>, attrs = Attrs4,
|
||||
children = SubEls}.
|
||||
encode_item(Item) ->
|
||||
#roster_item{jid = jid:make(Item#roster.jid),
|
||||
name = Item#roster.name,
|
||||
subscription = Item#roster.subscription,
|
||||
ask = case ask_to_pending(Item#roster.ask) of
|
||||
out -> subscribe;
|
||||
both -> subscribe;
|
||||
_ -> undefined
|
||||
end,
|
||||
groups = Item#roster.groups}.
|
||||
|
||||
decode_item(#roster_item{} = Item, R, Managed) ->
|
||||
R#roster{jid = jid:tolower(Item#roster_item.jid),
|
||||
name = Item#roster_item.name,
|
||||
subscription = case Item#roster_item.subscription of
|
||||
remove -> remove;
|
||||
Sub when Managed -> Sub;
|
||||
_ -> undefined
|
||||
end,
|
||||
groups = Item#roster_item.groups}.
|
||||
|
||||
get_roster_by_jid_t(LUser, LServer, LJID) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:get_roster_by_jid(LUser, LServer, LJID).
|
||||
|
||||
try_process_iq_set(From, To, #iq{sub_el = SubEl, lang = Lang} = IQ) ->
|
||||
try_process_iq_set(#iq{from = From, lang = Lang} = IQ) ->
|
||||
#jid{server = Server} = From,
|
||||
Access = gen_mod:get_module_opt(Server, ?MODULE, access, fun(A) -> A end, all),
|
||||
case acl:match_rule(Server, Access, From) of
|
||||
deny ->
|
||||
Txt = <<"Denied by ACL">>,
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
|
||||
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
|
||||
allow ->
|
||||
process_iq_set(From, To, IQ)
|
||||
process_iq_set(IQ)
|
||||
end.
|
||||
|
||||
process_iq_set(From, To, #iq{sub_el = SubEl, id = Id} = IQ) ->
|
||||
#xmlel{children = Els} = SubEl,
|
||||
process_iq_set(#iq{from = From, to = To, id = Id,
|
||||
sub_els = [#roster_query{items = Items}]} = IQ) ->
|
||||
Managed = is_managed_from_id(Id),
|
||||
lists:foreach(fun (El) -> process_item_set(From, To, El, Managed)
|
||||
lists:foreach(fun (Item) -> process_item_set(From, To, Item, Managed)
|
||||
end,
|
||||
Els),
|
||||
IQ#iq{type = result, sub_el = []}.
|
||||
Items),
|
||||
xmpp:make_iq_result(IQ).
|
||||
|
||||
process_item_set(From, To,
|
||||
#xmlel{attrs = Attrs, children = Els}, Managed) ->
|
||||
JID1 = jid:from_string(fxml:get_attr_s(<<"jid">>,
|
||||
Attrs)),
|
||||
#jid{user = User, luser = LUser, lserver = LServer} =
|
||||
From,
|
||||
case JID1 of
|
||||
error -> ok;
|
||||
_ ->
|
||||
LJID = jid:tolower(JID1),
|
||||
F = fun () ->
|
||||
Item = get_roster_by_jid_t(LUser, LServer, LJID),
|
||||
Item1 = process_item_attrs_managed(Item, Attrs, Managed),
|
||||
Item2 = process_item_els(Item1, Els),
|
||||
Item3 = ejabberd_hooks:run_fold(roster_process_item,
|
||||
LServer, Item2,
|
||||
[LServer]),
|
||||
case Item3#roster.subscription of
|
||||
remove -> del_roster_t(LUser, LServer, LJID);
|
||||
_ -> update_roster_t(LUser, LServer, LJID, Item3)
|
||||
end,
|
||||
send_itemset_to_managers(From, Item3, Managed),
|
||||
case roster_version_on_db(LServer) of
|
||||
true -> write_roster_version_t(LUser, LServer);
|
||||
false -> ok
|
||||
end,
|
||||
{Item, Item3}
|
||||
end,
|
||||
case transaction(LServer, F) of
|
||||
{atomic, {OldItem, Item}} ->
|
||||
push_item(User, LServer, To, Item),
|
||||
case Item#roster.subscription of
|
||||
remove ->
|
||||
send_unsubscribing_presence(From, OldItem), ok;
|
||||
_ -> ok
|
||||
end;
|
||||
E ->
|
||||
?DEBUG("ROSTER: roster item set error: ~p~n", [E]), ok
|
||||
end
|
||||
process_item_set(From, To, #roster_item{jid = JID1} = QueryItem, Managed) ->
|
||||
#jid{user = User, luser = LUser, lserver = LServer} = From,
|
||||
LJID = jid:tolower(JID1),
|
||||
F = fun () ->
|
||||
Item = get_roster_by_jid_t(LUser, LServer, LJID),
|
||||
Item2 = decode_item(QueryItem, Item, Managed),
|
||||
Item3 = ejabberd_hooks:run_fold(roster_process_item,
|
||||
LServer, Item2,
|
||||
[LServer]),
|
||||
case Item3#roster.subscription of
|
||||
remove -> del_roster_t(LUser, LServer, LJID);
|
||||
_ -> update_roster_t(LUser, LServer, LJID, Item3)
|
||||
end,
|
||||
send_itemset_to_managers(From, Item3, Managed),
|
||||
case roster_version_on_db(LServer) of
|
||||
true -> write_roster_version_t(LUser, LServer);
|
||||
false -> ok
|
||||
end,
|
||||
{Item, Item3}
|
||||
end,
|
||||
case transaction(LServer, F) of
|
||||
{atomic, {OldItem, Item}} ->
|
||||
push_item(User, LServer, To, Item),
|
||||
case Item#roster.subscription of
|
||||
remove ->
|
||||
send_unsubscribing_presence(From, OldItem), ok;
|
||||
_ -> ok
|
||||
end;
|
||||
E ->
|
||||
?DEBUG("ROSTER: roster item set error: ~p~n", [E]), ok
|
||||
end;
|
||||
process_item_set(_From, _To, _, _Managed) -> ok.
|
||||
|
||||
process_item_attrs(Item, [{Attr, Val} | Attrs]) ->
|
||||
case Attr of
|
||||
<<"jid">> ->
|
||||
case jid:from_string(Val) of
|
||||
error -> process_item_attrs(Item, Attrs);
|
||||
JID1 ->
|
||||
JID = {JID1#jid.luser, JID1#jid.lserver,
|
||||
JID1#jid.lresource},
|
||||
process_item_attrs(Item#roster{jid = JID}, Attrs)
|
||||
end;
|
||||
<<"name">> ->
|
||||
process_item_attrs(Item#roster{name = Val}, Attrs);
|
||||
<<"subscription">> ->
|
||||
case Val of
|
||||
<<"remove">> ->
|
||||
process_item_attrs(Item#roster{subscription = remove},
|
||||
Attrs);
|
||||
_ -> process_item_attrs(Item, Attrs)
|
||||
end;
|
||||
<<"ask">> -> process_item_attrs(Item, Attrs);
|
||||
_ -> process_item_attrs(Item, Attrs)
|
||||
end;
|
||||
process_item_attrs(Item, []) -> Item.
|
||||
|
||||
process_item_els(Item,
|
||||
[#xmlel{name = Name, attrs = Attrs, children = SEls}
|
||||
| Els]) ->
|
||||
case Name of
|
||||
<<"group">> ->
|
||||
Groups = [fxml:get_cdata(SEls) | Item#roster.groups],
|
||||
process_item_els(Item#roster{groups = Groups}, Els);
|
||||
_ ->
|
||||
case fxml:get_attr_s(<<"xmlns">>, Attrs) of
|
||||
<<"">> -> process_item_els(Item, Els);
|
||||
_ ->
|
||||
XEls = [#xmlel{name = Name, attrs = Attrs,
|
||||
children = SEls}
|
||||
| Item#roster.xs],
|
||||
process_item_els(Item#roster{xs = XEls}, Els)
|
||||
end
|
||||
end;
|
||||
process_item_els(Item, [{xmlcdata, _} | Els]) ->
|
||||
process_item_els(Item, Els);
|
||||
process_item_els(Item, []) -> Item.
|
||||
|
||||
push_item(User, Server, From, Item) ->
|
||||
ejabberd_sm:route(jid:make(<<"">>, <<"">>, <<"">>),
|
||||
jid:make(User, Server, <<"">>),
|
||||
@ -480,21 +394,19 @@ push_item(User, Server, Resource, From, Item) ->
|
||||
|
||||
push_item(User, Server, Resource, From, Item,
|
||||
RosterVersion) ->
|
||||
ExtraAttrs = case RosterVersion of
|
||||
not_found -> [];
|
||||
_ -> [{<<"ver">>, RosterVersion}]
|
||||
end,
|
||||
ResIQ = #iq{type = set, xmlns = ?NS_ROSTER,
|
||||
Ver = case RosterVersion of
|
||||
not_found -> undefined;
|
||||
_ -> RosterVersion
|
||||
end,
|
||||
ResIQ = #iq{type = set,
|
||||
%% @doc Roster push, calculate and include the version attribute.
|
||||
%% TODO: don't push to those who didn't load roster
|
||||
id = <<"push", (randoms:get_string())/binary>>,
|
||||
sub_el =
|
||||
[#xmlel{name = <<"query">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_ROSTER} | ExtraAttrs],
|
||||
children = [item_to_xml(Item)]}]},
|
||||
sub_els = [#roster_query{ver = Ver,
|
||||
items = [encode_item(Item)]}]},
|
||||
ejabberd_router:route(From,
|
||||
jid:make(User, Server, Resource),
|
||||
jlib:iq_to_xml(ResIQ)).
|
||||
ResIQ).
|
||||
|
||||
push_item_version(Server, User, From, Item,
|
||||
RosterVersion) ->
|
||||
@ -598,16 +510,8 @@ process_subscription(Direction, User, Server, JID1,
|
||||
case AutoReply of
|
||||
none -> ok;
|
||||
_ ->
|
||||
T = case AutoReply of
|
||||
subscribed -> <<"subscribed">>;
|
||||
unsubscribed -> <<"unsubscribed">>
|
||||
end,
|
||||
ejabberd_router:route(jid:make(User, Server,
|
||||
<<"">>),
|
||||
JID1,
|
||||
#xmlel{name = <<"presence">>,
|
||||
attrs = [{<<"type">>, T}],
|
||||
children = []})
|
||||
ejabberd_router:route(jid:make(User, Server, <<"">>),
|
||||
JID1, #presence{type = AutoReply})
|
||||
end,
|
||||
case Push of
|
||||
{push, Item} ->
|
||||
@ -769,24 +673,19 @@ send_unsubscribing_presence(From, Item) ->
|
||||
_ -> false
|
||||
end,
|
||||
if IsTo ->
|
||||
send_presence_type(jid:remove_resource(From),
|
||||
jid:make(Item#roster.jid),
|
||||
<<"unsubscribe">>);
|
||||
ejabberd_router:route(jid:remove_resource(From),
|
||||
jid:make(Item#roster.jid),
|
||||
#presence{type = unsubscribe});
|
||||
true -> ok
|
||||
end,
|
||||
if IsFrom ->
|
||||
send_presence_type(jid:remove_resource(From),
|
||||
jid:make(Item#roster.jid),
|
||||
<<"unsubscribed">>);
|
||||
ejabberd_router:route(jid:remove_resource(From),
|
||||
jid:make(Item#roster.jid),
|
||||
#presence{type = unsubscribed});
|
||||
true -> ok
|
||||
end,
|
||||
ok.
|
||||
|
||||
send_presence_type(From, To, Type) ->
|
||||
ejabberd_router:route(From, To,
|
||||
#xmlel{name = <<"presence">>,
|
||||
attrs = [{<<"type">>, Type}], children = []}).
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
set_items(User, Server, SubEl) ->
|
||||
@ -809,65 +708,20 @@ del_roster_t(LUser, LServer, LJID) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:del_roster(LUser, LServer, LJID).
|
||||
|
||||
process_item_set_t(LUser, LServer,
|
||||
#xmlel{attrs = Attrs, children = Els}) ->
|
||||
JID1 = jid:from_string(fxml:get_attr_s(<<"jid">>,
|
||||
Attrs)),
|
||||
case JID1 of
|
||||
error -> ok;
|
||||
_ ->
|
||||
JID = {JID1#jid.user, JID1#jid.server,
|
||||
JID1#jid.resource},
|
||||
LJID = {JID1#jid.luser, JID1#jid.lserver,
|
||||
JID1#jid.lresource},
|
||||
Item = #roster{usj = {LUser, LServer, LJID},
|
||||
us = {LUser, LServer}, jid = JID},
|
||||
Item1 = process_item_attrs_ws(Item, Attrs),
|
||||
Item2 = process_item_els(Item1, Els),
|
||||
case Item2#roster.subscription of
|
||||
remove -> del_roster_t(LUser, LServer, LJID);
|
||||
_ -> update_roster_t(LUser, LServer, LJID, Item2)
|
||||
end
|
||||
process_item_set_t(LUser, LServer, #roster_item{jid = JID1} = QueryItem) ->
|
||||
JID = {JID1#jid.user, JID1#jid.server,
|
||||
JID1#jid.resource},
|
||||
LJID = {JID1#jid.luser, JID1#jid.lserver,
|
||||
JID1#jid.lresource},
|
||||
Item = #roster{usj = {LUser, LServer, LJID},
|
||||
us = {LUser, LServer}, jid = JID},
|
||||
Item2 = decode_item(QueryItem, Item, _Managed = true),
|
||||
case Item2#roster.subscription of
|
||||
remove -> del_roster_t(LUser, LServer, LJID);
|
||||
_ -> update_roster_t(LUser, LServer, LJID, Item2)
|
||||
end;
|
||||
process_item_set_t(_LUser, _LServer, _) -> ok.
|
||||
|
||||
process_item_attrs_ws(Item, [{Attr, Val} | Attrs]) ->
|
||||
case Attr of
|
||||
<<"jid">> ->
|
||||
case jid:from_string(Val) of
|
||||
error -> process_item_attrs_ws(Item, Attrs);
|
||||
JID1 ->
|
||||
JID = {JID1#jid.luser, JID1#jid.lserver,
|
||||
JID1#jid.lresource},
|
||||
process_item_attrs_ws(Item#roster{jid = JID}, Attrs)
|
||||
end;
|
||||
<<"name">> ->
|
||||
process_item_attrs_ws(Item#roster{name = Val}, Attrs);
|
||||
<<"subscription">> ->
|
||||
case Val of
|
||||
<<"remove">> ->
|
||||
process_item_attrs_ws(Item#roster{subscription =
|
||||
remove},
|
||||
Attrs);
|
||||
<<"none">> ->
|
||||
process_item_attrs_ws(Item#roster{subscription = none},
|
||||
Attrs);
|
||||
<<"both">> ->
|
||||
process_item_attrs_ws(Item#roster{subscription = both},
|
||||
Attrs);
|
||||
<<"from">> ->
|
||||
process_item_attrs_ws(Item#roster{subscription = from},
|
||||
Attrs);
|
||||
<<"to">> ->
|
||||
process_item_attrs_ws(Item#roster{subscription = to},
|
||||
Attrs);
|
||||
_ -> process_item_attrs_ws(Item, Attrs)
|
||||
end;
|
||||
<<"ask">> -> process_item_attrs_ws(Item, Attrs);
|
||||
_ -> process_item_attrs_ws(Item, Attrs)
|
||||
end;
|
||||
process_item_attrs_ws(Item, []) -> Item.
|
||||
|
||||
get_in_pending_subscriptions(Ls, User, Server) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
@ -876,31 +730,18 @@ get_in_pending_subscriptions(Ls, User, Server) ->
|
||||
get_in_pending_subscriptions(Ls, User, Server, Mod) ->
|
||||
JID = jid:make(User, Server, <<"">>),
|
||||
Result = Mod:get_only_items(JID#jid.luser, JID#jid.lserver),
|
||||
Ls ++ lists:map(fun (R) ->
|
||||
Message = R#roster.askmessage,
|
||||
Status = if is_binary(Message) -> (Message);
|
||||
true -> <<"">>
|
||||
end,
|
||||
#xmlel{name = <<"presence">>,
|
||||
attrs =
|
||||
[{<<"from">>,
|
||||
jid:to_string(R#roster.jid)},
|
||||
{<<"to">>, jid:to_string(JID)},
|
||||
{<<"type">>, <<"subscribe">>}],
|
||||
children =
|
||||
[#xmlel{name = <<"status">>,
|
||||
attrs = [],
|
||||
children =
|
||||
[{xmlcdata, Status}]}]}
|
||||
end,
|
||||
lists:filter(fun (R) ->
|
||||
case R#roster.ask of
|
||||
in -> true;
|
||||
both -> true;
|
||||
_ -> false
|
||||
end
|
||||
end,
|
||||
Result)).
|
||||
Ls ++ lists:flatmap(
|
||||
fun(#roster{ask = Ask} = R) when Ask == in; Ask == both ->
|
||||
Message = R#roster.askmessage,
|
||||
Status = if is_binary(Message) -> (Message);
|
||||
true -> <<"">>
|
||||
end,
|
||||
[#presence{from = R#roster.jid, to = JID,
|
||||
type = subscribe,
|
||||
status = xmpp:mk_text(Status)}];
|
||||
(_) ->
|
||||
[]
|
||||
end, Result).
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
@ -1070,10 +911,7 @@ user_roster_parse_query(User, Server, Items, Query) ->
|
||||
user_roster_subscribe_jid(User, Server, JID) ->
|
||||
out_subscription(User, Server, JID, subscribe),
|
||||
UJID = jid:make(User, Server, <<"">>),
|
||||
ejabberd_router:route(UJID, JID,
|
||||
#xmlel{name = <<"presence">>,
|
||||
attrs = [{<<"type">>, <<"subscribe">>}],
|
||||
children = []}).
|
||||
ejabberd_router:route(UJID, JID, #presence{type = subscribe}).
|
||||
|
||||
user_roster_item_parse_query(User, Server, Items,
|
||||
Query) ->
|
||||
@ -1089,12 +927,7 @@ user_roster_item_parse_query(User, Server, Items,
|
||||
subscribed),
|
||||
UJID = jid:make(User, Server, <<"">>),
|
||||
ejabberd_router:route(UJID, JID1,
|
||||
#xmlel{name =
|
||||
<<"presence">>,
|
||||
attrs =
|
||||
[{<<"type">>,
|
||||
<<"subscribed">>}],
|
||||
children = []}),
|
||||
#presence{type = subscribed}),
|
||||
throw(submitted);
|
||||
false ->
|
||||
case lists:keysearch(<<"remove",
|
||||
@ -1102,29 +935,17 @@ user_roster_item_parse_query(User, Server, Items,
|
||||
1, Query)
|
||||
of
|
||||
{value, _} ->
|
||||
UJID = jid:make(User, Server,
|
||||
<<"">>),
|
||||
process_iq_set(UJID, UJID,
|
||||
#iq{type = set,
|
||||
sub_el =
|
||||
#xmlel{name =
|
||||
<<"query">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>,
|
||||
?NS_ROSTER}],
|
||||
children =
|
||||
[#xmlel{name
|
||||
=
|
||||
<<"item">>,
|
||||
attrs
|
||||
=
|
||||
[{<<"jid">>,
|
||||
jid:to_string(JID)},
|
||||
{<<"subscription">>,
|
||||
<<"remove">>}],
|
||||
children
|
||||
=
|
||||
[]}]}}),
|
||||
UJID = jid:make(User, Server),
|
||||
RosterItem = #roster_item{
|
||||
jid = jid:make(JID),
|
||||
subscription = remove},
|
||||
process_iq_set(
|
||||
#iq{type = set,
|
||||
from = UJID,
|
||||
to = UJID,
|
||||
id = randoms:get_string(),
|
||||
sub_els = [#roster_query{
|
||||
items = [RosterItem]}]}),
|
||||
throw(submitted);
|
||||
false -> ok
|
||||
end
|
||||
@ -1144,24 +965,24 @@ webadmin_user(Acc, _User, _Server, Lang) ->
|
||||
|
||||
%% Implement XEP-0321 Remote Roster Management
|
||||
|
||||
process_iq_manager(From, To, IQ) ->
|
||||
process_iq_manager(#iq{from = From, to = To, lang = Lang} = IQ) ->
|
||||
%% Check what access is allowed for From to To
|
||||
MatchDomain = From#jid.lserver,
|
||||
case is_domain_managed(MatchDomain, To#jid.lserver) of
|
||||
true ->
|
||||
process_iq_manager2(MatchDomain, To, IQ);
|
||||
process_iq_manager2(MatchDomain, IQ);
|
||||
false ->
|
||||
#iq{sub_el = SubEl, lang = Lang} = IQ,
|
||||
Txt = <<"Roster management is not allowed from this domain">>,
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt)]}
|
||||
xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang))
|
||||
end.
|
||||
|
||||
process_iq_manager2(MatchDomain, To, IQ) ->
|
||||
process_iq_manager2(MatchDomain, #iq{to = To} = IQ) ->
|
||||
%% If IQ is SET, filter the input IQ
|
||||
IQFiltered = maybe_filter_request(MatchDomain, IQ),
|
||||
%% Call the standard function with reversed JIDs
|
||||
IdInitial = IQFiltered#iq.id,
|
||||
ResIQ = process_iq(To, To, IQFiltered#iq{id = <<"roster-remotely-managed">>}),
|
||||
ResIQ = process_iq(IQFiltered#iq{from = To, to = To,
|
||||
id = <<"roster-remotely-managed">>}),
|
||||
%% Filter the output IQ
|
||||
filter_stanza(MatchDomain, ResIQ#iq{id = IdInitial}).
|
||||
|
||||
@ -1176,37 +997,13 @@ maybe_filter_request(MatchDomain, IQ) when IQ#iq.type == set ->
|
||||
maybe_filter_request(_MatchDomain, IQ) ->
|
||||
IQ.
|
||||
|
||||
filter_stanza(_MatchDomain, #iq{sub_el = []} = IQ) ->
|
||||
IQ;
|
||||
filter_stanza(MatchDomain, #iq{sub_el = [SubEl | _]} = IQ) ->
|
||||
#iq{sub_el = SubElFiltered} = IQRes =
|
||||
filter_stanza(MatchDomain, IQ#iq{sub_el = SubEl}),
|
||||
IQRes#iq{sub_el = [SubElFiltered]};
|
||||
filter_stanza(MatchDomain, #iq{sub_el = SubEl} = IQ) ->
|
||||
#xmlel{name = Type, attrs = Attrs, children = Items} = SubEl,
|
||||
filter_stanza(MatchDomain,
|
||||
#iq{sub_els = [#roster_query{items = Items} = R]} = IQ) ->
|
||||
ItemsFiltered = lists:filter(
|
||||
fun(Item) ->
|
||||
is_item_of_domain(MatchDomain, Item) end, Items),
|
||||
SubElFiltered = #xmlel{name=Type, attrs = Attrs, children = ItemsFiltered},
|
||||
IQ#iq{sub_el = SubElFiltered}.
|
||||
|
||||
is_item_of_domain(MatchDomain, #xmlel{} = El) ->
|
||||
lists:any(fun(Attr) -> is_jid_of_domain(MatchDomain, Attr) end, El#xmlel.attrs);
|
||||
is_item_of_domain(_MatchDomain, {xmlcdata, _}) ->
|
||||
false.
|
||||
|
||||
is_jid_of_domain(MatchDomain, {<<"jid">>, JIDString}) ->
|
||||
case jid:from_string(JIDString) of
|
||||
JID when JID#jid.lserver == MatchDomain -> true;
|
||||
_ -> false
|
||||
end;
|
||||
is_jid_of_domain(_, _) ->
|
||||
false.
|
||||
|
||||
process_item_attrs_managed(Item, Attrs, true) ->
|
||||
process_item_attrs_ws(Item, Attrs);
|
||||
process_item_attrs_managed(Item, _Attrs, false) ->
|
||||
process_item_attrs(Item, _Attrs).
|
||||
fun(#roster_item{jid = #jid{lserver = S}}) ->
|
||||
S == MatchDomain
|
||||
end, Items),
|
||||
IQ#iq{sub_els = [R#roster_query{items = ItemsFiltered}]}.
|
||||
|
||||
send_itemset_to_managers(_From, _Item, true) ->
|
||||
ok;
|
||||
|
@ -32,13 +32,13 @@
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([start/2, stop/1, process_local_iq/3,
|
||||
-export([start/2, stop/1, process_local_iq/1,
|
||||
mod_opt_type/1, depends/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("xmpp.hrl").
|
||||
|
||||
start(Host, Opts) ->
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
|
||||
@ -50,41 +50,18 @@ stop(Host) ->
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
|
||||
?NS_TIME).
|
||||
|
||||
process_local_iq(_From, _To,
|
||||
#iq{type = Type, sub_el = SubEl, lang = Lang} = IQ) ->
|
||||
case Type of
|
||||
set ->
|
||||
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
|
||||
get ->
|
||||
Now_universal = calendar:universal_time(),
|
||||
Now_local = calendar:universal_time_to_local_time(Now_universal),
|
||||
{UTC, UTC_diff} = jlib:timestamp_to_iso(Now_universal,
|
||||
utc),
|
||||
Seconds_diff =
|
||||
calendar:datetime_to_gregorian_seconds(Now_local) -
|
||||
calendar:datetime_to_gregorian_seconds(Now_universal),
|
||||
{Hd, Md, _} =
|
||||
calendar:seconds_to_time(abs(Seconds_diff)),
|
||||
{_, TZO_diff} = jlib:timestamp_to_iso({{0, 1, 1},
|
||||
{0, 0, 0}},
|
||||
{sign(Seconds_diff), {Hd, Md}}),
|
||||
IQ#iq{type = result,
|
||||
sub_el =
|
||||
[#xmlel{name = <<"time">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_TIME}],
|
||||
children =
|
||||
[#xmlel{name = <<"tzo">>, attrs = [],
|
||||
children = [{xmlcdata, TZO_diff}]},
|
||||
#xmlel{name = <<"utc">>, attrs = [],
|
||||
children =
|
||||
[{xmlcdata,
|
||||
<<UTC/binary,
|
||||
UTC_diff/binary>>}]}]}]}
|
||||
end.
|
||||
|
||||
sign(N) when N < 0 -> <<"-">>;
|
||||
sign(_) -> <<"+">>.
|
||||
process_local_iq(#iq{type = set, lang = Lang} = IQ) ->
|
||||
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
|
||||
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
|
||||
process_local_iq(#iq{type = get} = IQ) ->
|
||||
Now = p1_time_compat:timestamp(),
|
||||
Now_universal = calendar:now_to_universal_time(Now),
|
||||
Now_local = calendar:universal_time_to_local_time(Now_universal),
|
||||
Seconds_diff =
|
||||
calendar:datetime_to_gregorian_seconds(Now_local) -
|
||||
calendar:datetime_to_gregorian_seconds(Now_universal),
|
||||
{Hd, Md, _} = calendar:seconds_to_time(abs(Seconds_diff)),
|
||||
xmpp:make_iq_result(IQ, #time{tzo = {Hd, Md}, utc = Now}).
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[].
|
||||
|
@ -33,13 +33,15 @@
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([start/2, init/3, stop/1, get_sm_features/5,
|
||||
process_local_iq/3, process_sm_iq/3, string2lower/1,
|
||||
process_local_iq/1, process_sm_iq/1, string2lower/1,
|
||||
remove_user/2, export/1, import/1, import/3, depends/2,
|
||||
process_search/1, process_vcard/1,
|
||||
disco_items/5, disco_features/5, disco_identity/5,
|
||||
mod_opt_type/1, set_vcard/3, make_vcard_search/4]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("xmpp.hrl").
|
||||
-include("mod_vcard.hrl").
|
||||
|
||||
-define(JUD_MATCHES, 30).
|
||||
@ -68,11 +70,30 @@ start(Host, Opts) ->
|
||||
?NS_VCARD, ?MODULE, process_sm_iq, IQDisc),
|
||||
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
|
||||
get_sm_features, 50),
|
||||
MyHost = gen_mod:get_opt_host(Host, Opts,
|
||||
<<"vjud.@HOST@">>),
|
||||
MyHost = gen_mod:get_opt_host(Host, Opts, <<"vjud.@HOST@">>),
|
||||
Search = gen_mod:get_opt(search, Opts,
|
||||
fun(B) when is_boolean(B) -> B end,
|
||||
false),
|
||||
if Search ->
|
||||
ejabberd_hooks:add(
|
||||
disco_local_items, MyHost, ?MODULE, disco_items, 100),
|
||||
ejabberd_hooks:add(
|
||||
disco_local_features, MyHost, ?MODULE, disco_features, 100),
|
||||
ejabberd_hooks:add(
|
||||
disco_local_identity, MyHost, ?MODULE, disco_identity, 100),
|
||||
gen_iq_handler:add_iq_handler(
|
||||
ejabberd_local, MyHost, ?NS_SEARCH, ?MODULE, process_search, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(
|
||||
ejabberd_local, MyHost, ?NS_VCARD, ?MODULE, process_vcard, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(
|
||||
ejabberd_local, MyHost, ?NS_DISCO_ITEMS, mod_disco,
|
||||
process_local_iq_items, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(
|
||||
ejabberd_local, MyHost, ?NS_DISCO_INFO, mod_disco,
|
||||
process_local_iq_info, IQDisc);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
register(gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
spawn(?MODULE, init, [MyHost, Host, Search])).
|
||||
|
||||
@ -87,12 +108,20 @@ init(Host, ServerHost, Search) ->
|
||||
loop(Host, ServerHost) ->
|
||||
receive
|
||||
{route, From, To, Packet} ->
|
||||
case catch do_route(ServerHost, From, To, Packet) of
|
||||
case catch do_route(From, To, Packet) of
|
||||
{'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]);
|
||||
_ -> ok
|
||||
end,
|
||||
loop(Host, ServerHost);
|
||||
stop -> ejabberd_router:unregister_route(Host), ok;
|
||||
stop ->
|
||||
ejabberd_router:unregister_route(Host),
|
||||
ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, disco_items, 50),
|
||||
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, disco_features, 50),
|
||||
ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, disco_identity, 50),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_SEARCH),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO);
|
||||
_ -> loop(Host, ServerHost)
|
||||
end.
|
||||
|
||||
@ -109,12 +138,23 @@ stop(Host) ->
|
||||
Proc ! stop,
|
||||
{wait, Proc}.
|
||||
|
||||
do_route(From, To, #xmlel{name = <<"iq">>} = El) ->
|
||||
ejabberd_router:process_iq(From, To, El);
|
||||
do_route(From, To, #iq{} = IQ) ->
|
||||
ejabberd_router:process_iq(From, To, IQ);
|
||||
do_route(_, _, _) ->
|
||||
ok.
|
||||
|
||||
-spec get_sm_features({error, error()} | empty | {result, [binary()]},
|
||||
jid(), jid(),
|
||||
undefined | binary(), undefined | binary()) ->
|
||||
{error, error()} | empty | {result, [binary()]}.
|
||||
get_sm_features({error, _Error} = Acc, _From, _To,
|
||||
_Node, _Lang) ->
|
||||
Acc;
|
||||
get_sm_features(Acc, _From, _To, Node, _Lang) ->
|
||||
case Node of
|
||||
<<"">> ->
|
||||
undefined ->
|
||||
case Acc of
|
||||
{result, Features} ->
|
||||
{result, [?NS_DISCO_INFO, ?NS_VCARD | Features]};
|
||||
@ -123,67 +163,113 @@ get_sm_features(Acc, _From, _To, Node, _Lang) ->
|
||||
_ -> Acc
|
||||
end.
|
||||
|
||||
process_local_iq(_From, _To,
|
||||
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
|
||||
case Type of
|
||||
set ->
|
||||
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
|
||||
get ->
|
||||
IQ#iq{type = result,
|
||||
sub_el =
|
||||
[#xmlel{name = <<"vCard">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_VCARD}],
|
||||
children =
|
||||
[#xmlel{name = <<"FN">>, attrs = [],
|
||||
children =
|
||||
[{xmlcdata, <<"ejabberd">>}]},
|
||||
#xmlel{name = <<"URL">>, attrs = [],
|
||||
children = [{xmlcdata, ?EJABBERD_URI}]},
|
||||
#xmlel{name = <<"DESC">>, attrs = [],
|
||||
children =
|
||||
[{xmlcdata,
|
||||
<<(translate:translate(Lang,
|
||||
<<"Erlang Jabber Server">>))/binary,
|
||||
"\nCopyright (c) 2002-2016 ProcessOne">>}]},
|
||||
#xmlel{name = <<"BDAY">>, attrs = [],
|
||||
children =
|
||||
[{xmlcdata, <<"2002-11-16">>}]}]}]}
|
||||
-spec process_local_iq(iq()) -> iq().
|
||||
process_local_iq(#iq{type = set, lang = Lang} = IQ) ->
|
||||
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
|
||||
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
|
||||
process_local_iq(#iq{type = get, lang = Lang} = IQ) ->
|
||||
Desc = translate:translate(Lang, <<"Erlang Jabber Server">>),
|
||||
Copyright = <<"Copyright (c) 2002-2016 ProcessOne">>,
|
||||
xmpp:make_iq_result(
|
||||
IQ, #vcard_temp{fn = <<"ejabberd">>,
|
||||
url = ?EJABBERD_URI,
|
||||
desc = <<Desc/binary, $\n, Copyright/binary>>,
|
||||
bday = <<"2002-11-16">>}).
|
||||
|
||||
-spec process_sm_iq(iq()) -> iq().
|
||||
process_sm_iq(#iq{type = set, lang = Lang, from = From,
|
||||
sub_els = [SubEl]} = IQ) ->
|
||||
#jid{user = User, lserver = LServer} = From,
|
||||
case lists:member(LServer, ?MYHOSTS) of
|
||||
true ->
|
||||
set_vcard(User, LServer, SubEl),
|
||||
xmpp:make_iq_result(IQ);
|
||||
false ->
|
||||
Txt = <<"The query is only allowed from local users">>,
|
||||
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang))
|
||||
end;
|
||||
process_sm_iq(#iq{type = get, from = From, to = To, lang = Lang} = IQ) ->
|
||||
#jid{luser = LUser, lserver = LServer} = To,
|
||||
case get_vcard(LUser, LServer) of
|
||||
error ->
|
||||
Txt = <<"Database failure">>,
|
||||
xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang));
|
||||
[] ->
|
||||
xmpp:make_iq_result(IQ, #vcard_temp{});
|
||||
Els ->
|
||||
IQ#iq{type = result, to = From, from = To, sub_els = Els}
|
||||
end.
|
||||
|
||||
process_sm_iq(From, To,
|
||||
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
|
||||
case Type of
|
||||
set ->
|
||||
#jid{user = User, lserver = LServer} = From,
|
||||
case lists:member(LServer, ?MYHOSTS) of
|
||||
true ->
|
||||
set_vcard(User, LServer, SubEl),
|
||||
IQ#iq{type = result, sub_el = []};
|
||||
false ->
|
||||
Txt = <<"The query is only allowed from local users">>,
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}
|
||||
end;
|
||||
get ->
|
||||
#jid{luser = LUser, lserver = LServer} = To,
|
||||
case get_vcard(LUser, LServer) of
|
||||
error ->
|
||||
Txt = <<"Database failure">>,
|
||||
IQ#iq{type = error,
|
||||
sub_el = [SubEl, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)]};
|
||||
[] ->
|
||||
IQ#iq{type = result,
|
||||
sub_el = [#xmlel{name = <<"vCard">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_VCARD}],
|
||||
children = []}]};
|
||||
Els -> IQ#iq{type = result, sub_el = Els}
|
||||
end
|
||||
end.
|
||||
-spec process_vcard(iq()) -> iq().
|
||||
process_vcard(#iq{type = set, lang = Lang} = IQ) ->
|
||||
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
|
||||
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
|
||||
process_vcard(#iq{type = get, lang = Lang} = IQ) ->
|
||||
Desc = translate:translate(Lang, <<"ejabberd vCard module">>),
|
||||
Copyright = <<"Copyright (c) 2003-2016 ProcessOne">>,
|
||||
xmpp:make_iq_result(
|
||||
IQ, #vcard_temp{fn = <<"ejabberd/mod_vcard">>,
|
||||
url = ?EJABBERD_URI,
|
||||
desc = <<Desc/binary, $\n, Copyright/binary>>}).
|
||||
|
||||
-spec process_search(iq()) -> iq().
|
||||
process_search(#iq{type = get, to = To, lang = Lang} = IQ) ->
|
||||
xmpp:make_iq_result(IQ, mk_search_form(To, Lang));
|
||||
process_search(#iq{type = set, to = To, lang = Lang,
|
||||
sub_els = [#search{xdata = #xdata{type = submit,
|
||||
fields = Fs}}]} = IQ) ->
|
||||
ServerHost = ejabberd_router:host_of_route(To#jid.lserver),
|
||||
ResultXData = search_result(Lang, To, ServerHost, Fs),
|
||||
xmpp:make_iq_result(IQ, #search{xdata = ResultXData});
|
||||
process_search(#iq{type = set, lang = Lang} = IQ) ->
|
||||
Txt = <<"Incorrect data form">>,
|
||||
xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)).
|
||||
|
||||
-spec disco_items({error, error()} | {result, [disco_item()]} | empty,
|
||||
jid(), jid(),
|
||||
undefined | binary(), undefined | binary()) ->
|
||||
{error, error()} | {result, [disco_item()]}.
|
||||
disco_items(empty, _From, _To, undefined, _Lang) ->
|
||||
{result, []};
|
||||
disco_items(empty, _From, _To, _Node, Lang) ->
|
||||
{error, xmpp:err_item_not_found(<<"No services available">>, Lang)};
|
||||
disco_items(Acc, _From, _To, _Node, _Lang) ->
|
||||
Acc.
|
||||
|
||||
-spec disco_features({error, error()} | {result, [binary()]} | empty,
|
||||
jid(), jid(),
|
||||
undefined | binary(), undefined | binary()) ->
|
||||
{error, error()} | {result, [binary()]}.
|
||||
disco_features({error, _Error} = Acc, _From, _To, _Node, _Lang) ->
|
||||
Acc;
|
||||
disco_features(Acc, _From, _To, undefined, _Lang) ->
|
||||
Features = case Acc of
|
||||
{result, Fs} -> Fs;
|
||||
empty -> []
|
||||
end,
|
||||
{result, [?NS_DISCO_INFO, ?NS_DISCO_ITEMS,
|
||||
?NS_VCARD, ?NS_SEARCH | Features]};
|
||||
disco_features(empty, _From, _To, _Node, Lang) ->
|
||||
Txt = <<"No features available">>,
|
||||
{error, xmpp:err_item_not_found(Txt, Lang)};
|
||||
disco_features(Acc, _From, _To, _Node, _Lang) ->
|
||||
Acc.
|
||||
|
||||
-spec disco_identity([identity()], jid(), jid(), undefined | binary(),
|
||||
undefined | binary()) -> [identity()].
|
||||
disco_identity(Acc, _From, _To, undefined, Lang) ->
|
||||
[#identity{category = <<"directory">>,
|
||||
type = <<"user">>,
|
||||
name = translate:translate(Lang, <<"vCard User Search">>)}|Acc];
|
||||
disco_identity(Acc, _From, _To, _Node, _Lang) ->
|
||||
Acc.
|
||||
|
||||
-spec get_vcard(binary(), binary()) -> [xmlel()] | error.
|
||||
get_vcard(LUser, LServer) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:get_vcard(LUser, LServer).
|
||||
|
||||
-spec make_vcard_search(binary(), binary(), binary(), xmlel()) -> #vcard_search{}.
|
||||
make_vcard_search(User, LUser, LServer, VCARD) ->
|
||||
FN = fxml:get_path_s(VCARD, [{elem, <<"FN">>}, cdata]),
|
||||
Family = fxml:get_path_s(VCARD,
|
||||
@ -250,6 +336,7 @@ make_vcard_search(User, LUser, LServer, VCARD) ->
|
||||
orgunit = OrgUnit,
|
||||
lorgunit = LOrgUnit}.
|
||||
|
||||
-spec set_vcard(binary(), binary(), xmlel()) -> any().
|
||||
set_vcard(User, LServer, VCARD) ->
|
||||
case jid:nodeprep(User) of
|
||||
error ->
|
||||
@ -262,307 +349,108 @@ set_vcard(User, LServer, VCARD) ->
|
||||
[LUser, LServer, VCARD])
|
||||
end.
|
||||
|
||||
-spec string2lower(binary()) -> binary().
|
||||
string2lower(String) ->
|
||||
case stringprep:tolower(String) of
|
||||
Lower when is_binary(Lower) -> Lower;
|
||||
error -> str:to_lower(String)
|
||||
end.
|
||||
|
||||
-define(TLFIELD(Type, Label, Var),
|
||||
#xmlel{name = <<"field">>,
|
||||
attrs =
|
||||
[{<<"type">>, Type},
|
||||
{<<"label">>, translate:translate(Lang, Label)},
|
||||
{<<"var">>, Var}],
|
||||
children = []}).
|
||||
-spec mk_tfield(binary(), binary(), undefined | binary()) -> xdata_field().
|
||||
mk_tfield(Label, Var, Lang) ->
|
||||
#xdata_field{type = 'text-single',
|
||||
label = translate:translate(Lang, Label),
|
||||
var = Var}.
|
||||
|
||||
-define(FORM(JID),
|
||||
[#xmlel{name = <<"instructions">>, attrs = [],
|
||||
children =
|
||||
[{xmlcdata,
|
||||
translate:translate(Lang,
|
||||
<<"You need an x:data capable client to "
|
||||
"search">>)}]},
|
||||
#xmlel{name = <<"x">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}],
|
||||
children =
|
||||
[#xmlel{name = <<"title">>, attrs = [],
|
||||
children =
|
||||
[{xmlcdata,
|
||||
<<(translate:translate(Lang,
|
||||
<<"Search users in ">>))/binary,
|
||||
(jid:to_string(JID))/binary>>}]},
|
||||
#xmlel{name = <<"instructions">>, attrs = [],
|
||||
children =
|
||||
[{xmlcdata,
|
||||
translate:translate(Lang,
|
||||
<<"Fill in the form to search for any matching "
|
||||
"Jabber User (Add * to the end of field "
|
||||
"to match substring)">>)}]},
|
||||
?TLFIELD(<<"text-single">>, <<"User">>, <<"user">>),
|
||||
?TLFIELD(<<"text-single">>, <<"Full Name">>, <<"fn">>),
|
||||
?TLFIELD(<<"text-single">>, <<"Name">>, <<"first">>),
|
||||
?TLFIELD(<<"text-single">>, <<"Middle Name">>,
|
||||
<<"middle">>),
|
||||
?TLFIELD(<<"text-single">>, <<"Family Name">>,
|
||||
<<"last">>),
|
||||
?TLFIELD(<<"text-single">>, <<"Nickname">>, <<"nick">>),
|
||||
?TLFIELD(<<"text-single">>, <<"Birthday">>, <<"bday">>),
|
||||
?TLFIELD(<<"text-single">>, <<"Country">>, <<"ctry">>),
|
||||
?TLFIELD(<<"text-single">>, <<"City">>, <<"locality">>),
|
||||
?TLFIELD(<<"text-single">>, <<"Email">>, <<"email">>),
|
||||
?TLFIELD(<<"text-single">>, <<"Organization Name">>,
|
||||
<<"orgname">>),
|
||||
?TLFIELD(<<"text-single">>, <<"Organization Unit">>,
|
||||
<<"orgunit">>)]}]).
|
||||
-spec mk_field(binary(), binary()) -> xdata_field().
|
||||
mk_field(Var, Val) ->
|
||||
#xdata_field{var = Var, values = [Val]}.
|
||||
|
||||
do_route(ServerHost, From, To, Packet) ->
|
||||
#jid{user = User, resource = Resource} = To,
|
||||
if (User /= <<"">>) or (Resource /= <<"">>) ->
|
||||
Err = jlib:make_error_reply(Packet,
|
||||
?ERR_SERVICE_UNAVAILABLE),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
true ->
|
||||
IQ = jlib:iq_query_info(Packet),
|
||||
case IQ of
|
||||
#iq{type = Type, xmlns = ?NS_SEARCH, lang = Lang,
|
||||
sub_el = SubEl} ->
|
||||
case Type of
|
||||
set ->
|
||||
XDataEl = find_xdata_el(SubEl),
|
||||
case XDataEl of
|
||||
false ->
|
||||
Txt = <<"Data form not found">>,
|
||||
Err = jlib:make_error_reply(
|
||||
Packet, ?ERRT_BAD_REQUEST(Lang, Txt)),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
_ ->
|
||||
XData = jlib:parse_xdata_submit(XDataEl),
|
||||
case XData of
|
||||
invalid ->
|
||||
Txt = <<"Incorrect data form">>,
|
||||
Err = jlib:make_error_reply(
|
||||
Packet, ?ERRT_BAD_REQUEST(Lang, Txt)),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
_ ->
|
||||
ResIQ = IQ#iq{type = result,
|
||||
sub_el =
|
||||
[#xmlel{name = <<"query">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>,
|
||||
?NS_SEARCH}],
|
||||
children =
|
||||
[#xmlel{name =
|
||||
<<"x">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>,
|
||||
?NS_XDATA},
|
||||
{<<"type">>,
|
||||
<<"result">>}],
|
||||
children
|
||||
=
|
||||
search_result(Lang,
|
||||
To,
|
||||
ServerHost,
|
||||
XData)}]}]},
|
||||
ejabberd_router:route(To, From,
|
||||
jlib:iq_to_xml(ResIQ))
|
||||
end
|
||||
end;
|
||||
get ->
|
||||
ResIQ = IQ#iq{type = result,
|
||||
sub_el =
|
||||
[#xmlel{name = <<"query">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>,
|
||||
?NS_SEARCH}],
|
||||
children = ?FORM(To)}]},
|
||||
ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ))
|
||||
end;
|
||||
#iq{type = Type, xmlns = ?NS_DISCO_INFO, lang = Lang} ->
|
||||
case Type of
|
||||
set ->
|
||||
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
|
||||
Err = jlib:make_error_reply(Packet, ?ERRT_NOT_ALLOWED(Lang, Txt)),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
get ->
|
||||
Info = ejabberd_hooks:run_fold(disco_info, ServerHost,
|
||||
[],
|
||||
[ServerHost, ?MODULE,
|
||||
<<"">>, <<"">>]),
|
||||
ResIQ = IQ#iq{type = result,
|
||||
sub_el =
|
||||
[#xmlel{name = <<"query">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>,
|
||||
?NS_DISCO_INFO}],
|
||||
children =
|
||||
[#xmlel{name =
|
||||
<<"identity">>,
|
||||
attrs =
|
||||
[{<<"category">>,
|
||||
<<"directory">>},
|
||||
{<<"type">>,
|
||||
<<"user">>},
|
||||
{<<"name">>,
|
||||
translate:translate(Lang,
|
||||
<<"vCard User Search">>)}],
|
||||
children = []},
|
||||
#xmlel{name =
|
||||
<<"feature">>,
|
||||
attrs =
|
||||
[{<<"var">>,
|
||||
?NS_DISCO_INFO}],
|
||||
children = []},
|
||||
#xmlel{name =
|
||||
<<"feature">>,
|
||||
attrs =
|
||||
[{<<"var">>,
|
||||
?NS_SEARCH}],
|
||||
children = []},
|
||||
#xmlel{name =
|
||||
<<"feature">>,
|
||||
attrs =
|
||||
[{<<"var">>,
|
||||
?NS_VCARD}],
|
||||
children = []}]
|
||||
++ Info}]},
|
||||
ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ))
|
||||
end;
|
||||
#iq{type = Type, lang = Lang, xmlns = ?NS_DISCO_ITEMS} ->
|
||||
case Type of
|
||||
set ->
|
||||
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
|
||||
Err = jlib:make_error_reply(Packet, ?ERRT_NOT_ALLOWED(Lang, Txt)),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
get ->
|
||||
ResIQ = IQ#iq{type = result,
|
||||
sub_el =
|
||||
[#xmlel{name = <<"query">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>,
|
||||
?NS_DISCO_ITEMS}],
|
||||
children = []}]},
|
||||
ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ))
|
||||
end;
|
||||
#iq{type = get, xmlns = ?NS_VCARD, lang = Lang} ->
|
||||
ResIQ = IQ#iq{type = result,
|
||||
sub_el =
|
||||
[#xmlel{name = <<"vCard">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_VCARD}],
|
||||
children = iq_get_vcard(Lang)}]},
|
||||
ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ));
|
||||
_ ->
|
||||
Err = jlib:make_error_reply(Packet,
|
||||
?ERR_SERVICE_UNAVAILABLE),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end
|
||||
end.
|
||||
-spec mk_search_form(jid(), undefined | binary()) -> search().
|
||||
mk_search_form(JID, Lang) ->
|
||||
Title = <<(translate:translate(Lang, <<"Search users in ">>))/binary,
|
||||
(jid:to_string(JID))/binary>>,
|
||||
Fs = [mk_tfield(<<"User">>, <<"user">>, Lang),
|
||||
mk_tfield(<<"Full Name">>, <<"fn">>, Lang),
|
||||
mk_tfield(<<"Name">>, <<"first">>, Lang),
|
||||
mk_tfield(<<"Middle Name">>, <<"middle">>, Lang),
|
||||
mk_tfield(<<"Family Name">>, <<"last">>, Lang),
|
||||
mk_tfield(<<"Nickname">>, <<"nick">>, Lang),
|
||||
mk_tfield(<<"Birthday">>, <<"bday">>, Lang),
|
||||
mk_tfield(<<"Country">>, <<"ctry">>, Lang),
|
||||
mk_tfield(<<"City">>, <<"locality">>, Lang),
|
||||
mk_tfield(<<"Email">>, <<"email">>, Lang),
|
||||
mk_tfield(<<"Organization Name">>, <<"orgname">>, Lang),
|
||||
mk_tfield(<<"Organization Unit">>, <<"orgunit">>, Lang)],
|
||||
X = #xdata{type = form,
|
||||
title = Title,
|
||||
instructions =
|
||||
[translate:translate(
|
||||
Lang,
|
||||
<<"Fill in the form to search for any matching "
|
||||
"Jabber User (Add * to the end of field "
|
||||
"to match substring)">>)],
|
||||
fields = Fs},
|
||||
#search{instructions =
|
||||
translate:translate(
|
||||
Lang, <<"You need an x:data capable client to search">>),
|
||||
xdata = X}.
|
||||
|
||||
iq_get_vcard(Lang) ->
|
||||
[#xmlel{name = <<"FN">>, attrs = [],
|
||||
children = [{xmlcdata, <<"ejabberd/mod_vcard">>}]},
|
||||
#xmlel{name = <<"URL">>, attrs = [],
|
||||
children = [{xmlcdata, ?EJABBERD_URI}]},
|
||||
#xmlel{name = <<"DESC">>, attrs = [],
|
||||
children =
|
||||
[{xmlcdata,
|
||||
<<(translate:translate(Lang,
|
||||
<<"ejabberd vCard module">>))/binary,
|
||||
"\nCopyright (c) 2003-2016 ProcessOne">>}]}].
|
||||
|
||||
find_xdata_el(#xmlel{children = SubEls}) ->
|
||||
find_xdata_el1(SubEls).
|
||||
|
||||
find_xdata_el1([]) -> false;
|
||||
find_xdata_el1([#xmlel{name = Name, attrs = Attrs,
|
||||
children = SubEls}
|
||||
| Els]) ->
|
||||
case fxml:get_attr_s(<<"xmlns">>, Attrs) of
|
||||
?NS_XDATA ->
|
||||
#xmlel{name = Name, attrs = Attrs, children = SubEls};
|
||||
_ -> find_xdata_el1(Els)
|
||||
end;
|
||||
find_xdata_el1([_ | Els]) -> find_xdata_el1(Els).
|
||||
|
||||
-define(LFIELD(Label, Var),
|
||||
#xmlel{name = <<"field">>,
|
||||
attrs =
|
||||
[{<<"label">>, translate:translate(Lang, Label)},
|
||||
{<<"var">>, Var}],
|
||||
children = []}).
|
||||
|
||||
search_result(Lang, JID, ServerHost, Data) ->
|
||||
[#xmlel{name = <<"title">>, attrs = [],
|
||||
children =
|
||||
[{xmlcdata,
|
||||
<<(translate:translate(Lang,
|
||||
<<"Search Results for ">>))/binary,
|
||||
(jid:to_string(JID))/binary>>}]},
|
||||
#xmlel{name = <<"reported">>, attrs = [],
|
||||
children =
|
||||
[?TLFIELD(<<"text-single">>, <<"Jabber ID">>,
|
||||
<<"jid">>),
|
||||
?TLFIELD(<<"text-single">>, <<"Full Name">>, <<"fn">>),
|
||||
?TLFIELD(<<"text-single">>, <<"Name">>, <<"first">>),
|
||||
?TLFIELD(<<"text-single">>, <<"Middle Name">>,
|
||||
<<"middle">>),
|
||||
?TLFIELD(<<"text-single">>, <<"Family Name">>,
|
||||
<<"last">>),
|
||||
?TLFIELD(<<"text-single">>, <<"Nickname">>, <<"nick">>),
|
||||
?TLFIELD(<<"text-single">>, <<"Birthday">>, <<"bday">>),
|
||||
?TLFIELD(<<"text-single">>, <<"Country">>, <<"ctry">>),
|
||||
?TLFIELD(<<"text-single">>, <<"City">>, <<"locality">>),
|
||||
?TLFIELD(<<"text-single">>, <<"Email">>, <<"email">>),
|
||||
?TLFIELD(<<"text-single">>, <<"Organization Name">>,
|
||||
<<"orgname">>),
|
||||
?TLFIELD(<<"text-single">>, <<"Organization Unit">>,
|
||||
<<"orgunit">>)]}]
|
||||
++
|
||||
lists:map(fun (R) -> record_to_item(ServerHost, R) end,
|
||||
search(ServerHost, Data)).
|
||||
|
||||
-define(FIELD(Var, Val),
|
||||
#xmlel{name = <<"field">>, attrs = [{<<"var">>, Var}],
|
||||
children =
|
||||
[#xmlel{name = <<"value">>, attrs = [],
|
||||
children = [{xmlcdata, Val}]}]}).
|
||||
-spec search_result(undefined | binary(), jid(), binary(), [xdata_field()]) -> xdata().
|
||||
search_result(Lang, JID, ServerHost, XFields) ->
|
||||
#xdata{type = result,
|
||||
title = <<(translate:translate(Lang,
|
||||
<<"Search Results for ">>))/binary,
|
||||
(jid:to_string(JID))/binary>>,
|
||||
reported = [mk_tfield(<<"Jabber ID">>, <<"jid">>, Lang),
|
||||
mk_tfield(<<"Full Name">>, <<"fn">>, Lang),
|
||||
mk_tfield(<<"Name">>, <<"first">>, Lang),
|
||||
mk_tfield(<<"Middle Name">>, <<"middle">>, Lang),
|
||||
mk_tfield(<<"Family Name">>, <<"last">>, Lang),
|
||||
mk_tfield(<<"Nickname">>, <<"nick">>, Lang),
|
||||
mk_tfield(<<"Birthday">>, <<"bday">>, Lang),
|
||||
mk_tfield(<<"Country">>, <<"ctry">>, Lang),
|
||||
mk_tfield(<<"City">>, <<"locality">>, Lang),
|
||||
mk_tfield(<<"Email">>, <<"email">>, Lang),
|
||||
mk_tfield(<<"Organization Name">>, <<"orgname">>, Lang),
|
||||
mk_tfield(<<"Organization Unit">>, <<"orgunit">>, Lang)],
|
||||
items = lists:map(fun (R) -> record_to_item(ServerHost, R) end,
|
||||
search(ServerHost, XFields))}.
|
||||
|
||||
-spec record_to_item(binary(), [binary()] | #vcard_search{}) -> [xdata_field()].
|
||||
record_to_item(LServer,
|
||||
[Username, FN, Family, Given, Middle, Nickname, BDay,
|
||||
CTRY, Locality, EMail, OrgName, OrgUnit]) ->
|
||||
#xmlel{name = <<"item">>, attrs = [],
|
||||
children =
|
||||
[?FIELD(<<"jid">>,
|
||||
<<Username/binary, "@", LServer/binary>>),
|
||||
?FIELD(<<"fn">>, FN), ?FIELD(<<"last">>, Family),
|
||||
?FIELD(<<"first">>, Given),
|
||||
?FIELD(<<"middle">>, Middle),
|
||||
?FIELD(<<"nick">>, Nickname), ?FIELD(<<"bday">>, BDay),
|
||||
?FIELD(<<"ctry">>, CTRY),
|
||||
?FIELD(<<"locality">>, Locality),
|
||||
?FIELD(<<"email">>, EMail),
|
||||
?FIELD(<<"orgname">>, OrgName),
|
||||
?FIELD(<<"orgunit">>, OrgUnit)]};
|
||||
[mk_field(<<"jid">>, <<Username/binary, "@", LServer/binary>>),
|
||||
mk_field(<<"fn">>, FN),
|
||||
mk_field(<<"last">>, Family),
|
||||
mk_field(<<"first">>, Given),
|
||||
mk_field(<<"middle">>, Middle),
|
||||
mk_field(<<"nick">>, Nickname),
|
||||
mk_field(<<"bday">>, BDay),
|
||||
mk_field(<<"ctry">>, CTRY),
|
||||
mk_field(<<"locality">>, Locality),
|
||||
mk_field(<<"email">>, EMail),
|
||||
mk_field(<<"orgname">>, OrgName),
|
||||
mk_field(<<"orgunit">>, OrgUnit)];
|
||||
record_to_item(_LServer, #vcard_search{} = R) ->
|
||||
{User, Server} = R#vcard_search.user,
|
||||
#xmlel{name = <<"item">>, attrs = [],
|
||||
children =
|
||||
[?FIELD(<<"jid">>, <<User/binary, "@", Server/binary>>),
|
||||
?FIELD(<<"fn">>, (R#vcard_search.fn)),
|
||||
?FIELD(<<"last">>, (R#vcard_search.family)),
|
||||
?FIELD(<<"first">>, (R#vcard_search.given)),
|
||||
?FIELD(<<"middle">>, (R#vcard_search.middle)),
|
||||
?FIELD(<<"nick">>, (R#vcard_search.nickname)),
|
||||
?FIELD(<<"bday">>, (R#vcard_search.bday)),
|
||||
?FIELD(<<"ctry">>, (R#vcard_search.ctry)),
|
||||
?FIELD(<<"locality">>, (R#vcard_search.locality)),
|
||||
?FIELD(<<"email">>, (R#vcard_search.email)),
|
||||
?FIELD(<<"orgname">>, (R#vcard_search.orgname)),
|
||||
?FIELD(<<"orgunit">>, (R#vcard_search.orgunit))]}.
|
||||
[mk_field(<<"jid">>, <<User/binary, "@", Server/binary>>),
|
||||
mk_field(<<"fn">>, (R#vcard_search.fn)),
|
||||
mk_field(<<"last">>, (R#vcard_search.family)),
|
||||
mk_field(<<"first">>, (R#vcard_search.given)),
|
||||
mk_field(<<"middle">>, (R#vcard_search.middle)),
|
||||
mk_field(<<"nick">>, (R#vcard_search.nickname)),
|
||||
mk_field(<<"bday">>, (R#vcard_search.bday)),
|
||||
mk_field(<<"ctry">>, (R#vcard_search.ctry)),
|
||||
mk_field(<<"locality">>, (R#vcard_search.locality)),
|
||||
mk_field(<<"email">>, (R#vcard_search.email)),
|
||||
mk_field(<<"orgname">>, (R#vcard_search.orgname)),
|
||||
mk_field(<<"orgunit">>, (R#vcard_search.orgunit))].
|
||||
|
||||
search(LServer, Data) ->
|
||||
-spec search(binary(), [xdata_field()]) -> [binary()].
|
||||
search(LServer, XFields) ->
|
||||
Data = [{Var, Vals} || #xdata_field{var = Var, values = Vals} <- XFields],
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
AllowReturnAll = gen_mod:get_module_opt(LServer, ?MODULE, allow_return_all,
|
||||
fun(B) when is_boolean(B) -> B end,
|
||||
|
@ -17,8 +17,7 @@
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("mod_vcard_xupdate.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("xmpp.hrl").
|
||||
|
||||
-callback init(binary(), gen_mod:opts()) -> any().
|
||||
-callback import(binary(), #vcard_xupdate{}) -> ok | pass.
|
||||
@ -53,12 +52,8 @@ depends(_Host, _Opts) ->
|
||||
%% Hooks
|
||||
%%====================================================================
|
||||
|
||||
update_presence(#xmlel{name = <<"presence">>, attrs = Attrs} = Packet,
|
||||
User, Host) ->
|
||||
case fxml:get_attr_s(<<"type">>, Attrs) of
|
||||
<<>> -> presence_with_xupdate(Packet, User, Host);
|
||||
_ -> Packet
|
||||
end;
|
||||
update_presence(#presence{type = undefined} = Packet, User, Host) ->
|
||||
presence_with_xupdate(Packet, User, Host);
|
||||
update_presence(Packet, _User, _Host) -> Packet.
|
||||
|
||||
vcard_set(LUser, LServer, VCARD) ->
|
||||
@ -93,36 +88,10 @@ remove_xupdate(LUser, LServer) ->
|
||||
%%% Presence stanza rebuilding
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
presence_with_xupdate(#xmlel{name = <<"presence">>,
|
||||
attrs = Attrs, children = Els},
|
||||
User, Host) ->
|
||||
XPhotoEl = build_xphotoel(User, Host),
|
||||
Els2 = presence_with_xupdate2(Els, [], XPhotoEl),
|
||||
#xmlel{name = <<"presence">>, attrs = Attrs,
|
||||
children = Els2}.
|
||||
|
||||
presence_with_xupdate2([], Els2, XPhotoEl) ->
|
||||
lists:reverse([XPhotoEl | Els2]);
|
||||
%% This clause assumes that the x element contains only the XMLNS attribute:
|
||||
presence_with_xupdate2([#xmlel{name = <<"x">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_VCARD_UPDATE}]}
|
||||
| Els],
|
||||
Els2, XPhotoEl) ->
|
||||
presence_with_xupdate2(Els, Els2, XPhotoEl);
|
||||
presence_with_xupdate2([El | Els], Els2, XPhotoEl) ->
|
||||
presence_with_xupdate2(Els, [El | Els2], XPhotoEl).
|
||||
|
||||
build_xphotoel(User, Host) ->
|
||||
presence_with_xupdate(Presence, User, Host) ->
|
||||
Hash = get_xupdate(User, Host),
|
||||
PhotoSubEls = case Hash of
|
||||
Hash when is_binary(Hash) -> [{xmlcdata, Hash}];
|
||||
_ -> []
|
||||
end,
|
||||
PhotoEl = [#xmlel{name = <<"photo">>, attrs = [],
|
||||
children = PhotoSubEls}],
|
||||
#xmlel{name = <<"x">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_VCARD_UPDATE}],
|
||||
children = PhotoEl}.
|
||||
Presence1 = xmpp:remove_subtag(Presence, #vcard_xupdate{}),
|
||||
xmpp:set_subtag(Presence1, #vcard_xupdate{hash = Hash}).
|
||||
|
||||
export(LServer) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
|
@ -31,13 +31,13 @@
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([start/2, stop/1, process_local_iq/3,
|
||||
-export([start/2, stop/1, process_local_iq/1,
|
||||
mod_opt_type/1, depends/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("xmpp.hrl").
|
||||
|
||||
start(Host, Opts) ->
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
|
||||
@ -50,35 +50,20 @@ stop(Host) ->
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
|
||||
?NS_VERSION).
|
||||
|
||||
process_local_iq(_From, To,
|
||||
#iq{id = _ID, type = Type, xmlns = _XMLNS,
|
||||
sub_el = SubEl, lang = Lang} =
|
||||
IQ) ->
|
||||
case Type of
|
||||
set ->
|
||||
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
|
||||
get ->
|
||||
Host = To#jid.lserver,
|
||||
OS = case gen_mod:get_module_opt(Host, ?MODULE, show_os,
|
||||
fun(B) when is_boolean(B) -> B end,
|
||||
true)
|
||||
of
|
||||
true -> [get_os()];
|
||||
false -> []
|
||||
end,
|
||||
IQ#iq{type = result,
|
||||
sub_el =
|
||||
[#xmlel{name = <<"query">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_VERSION}],
|
||||
children =
|
||||
[#xmlel{name = <<"name">>, attrs = [],
|
||||
children =
|
||||
[{xmlcdata, <<"ejabberd">>}]},
|
||||
#xmlel{name = <<"version">>, attrs = [],
|
||||
children = [{xmlcdata, ?VERSION}]}]
|
||||
++ OS}]}
|
||||
end.
|
||||
process_local_iq(#iq{type = set, lang = Lang} = IQ) ->
|
||||
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
|
||||
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
|
||||
process_local_iq(#iq{type = get, to = To} = IQ) ->
|
||||
Host = To#jid.lserver,
|
||||
OS = case gen_mod:get_module_opt(Host, ?MODULE, show_os,
|
||||
fun(B) when is_boolean(B) -> B end,
|
||||
true) of
|
||||
true -> get_os();
|
||||
false -> undefined
|
||||
end,
|
||||
xmpp:make_iq_result(IQ, #version{name = <<"ejabberd">>,
|
||||
ver = ?VERSION,
|
||||
os = OS}).
|
||||
|
||||
get_os() ->
|
||||
{Osfamily, Osname} = os:type(),
|
||||
@ -89,9 +74,7 @@ get_os() ->
|
||||
[Major, Minor, Release]));
|
||||
VersionString -> VersionString
|
||||
end,
|
||||
OS = <<OSType/binary, " ", OSVersion/binary>>,
|
||||
#xmlel{name = <<"os">>, attrs = [],
|
||||
children = [{xmlcdata, OS}]}.
|
||||
<<OSType/binary, " ", OSVersion/binary>>.
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[].
|
||||
|
@ -126,8 +126,10 @@ load_file_loop(Fd, Line, File, Lang) ->
|
||||
ok
|
||||
end.
|
||||
|
||||
-spec translate(binary(), binary()) -> binary().
|
||||
-spec translate(binary() | undefined, binary()) -> binary().
|
||||
|
||||
translate(undefined, Msg) ->
|
||||
translate(?MYLANG, Msg);
|
||||
translate(Lang, Msg) ->
|
||||
LLang = ascii_tolower(Lang),
|
||||
case ets:lookup(translations, {LLang, Msg}) of
|
||||
|
712
src/xmpp.erl
Normal file
712
src/xmpp.erl
Normal file
@ -0,0 +1,712 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% @copyright (C) 2015, Evgeny Khramtsov
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 9 Dec 2015 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(xmpp).
|
||||
|
||||
%% API
|
||||
-export([make_iq_result/1, make_iq_result/2, make_error/2,
|
||||
decode/1, decode/2, decode_tags_by_ns/2, encode/1,
|
||||
get_type/1, get_to/1, get_from/1, get_id/1,
|
||||
get_lang/1, get_error/1, get_els/1, get_ns/1,
|
||||
set_type/2, set_to/2, set_from/2, set_id/2,
|
||||
set_lang/2, set_error/2, set_els/2, set_from_to/3,
|
||||
format_error/1, is_stanza/1, set_subtag/2, get_subtag/2,
|
||||
remove_subtag/2, has_subtag/2, decode_els/1, pp/1,
|
||||
get_name/1, get_text/1, mk_text/1, mk_text/2]).
|
||||
|
||||
%% XMPP errors
|
||||
-export([err_bad_request/0, err_bad_request/2,
|
||||
err_bad_format/0, err_bad_format/2,
|
||||
err_not_allowed/0, err_not_allowed/2,
|
||||
err_conflict/0, err_conflict/2,
|
||||
err_forbidden/0, err_forbidden/2,
|
||||
err_not_acceptable/0, err_not_acceptable/2,
|
||||
err_internal_server_error/0, err_internal_server_error/2,
|
||||
err_service_unavailable/0, err_service_unavailable/2,
|
||||
err_item_not_found/0, err_item_not_found/2,
|
||||
err_jid_malformed/0, err_jid_malformed/2,
|
||||
err_not_authorized/0, err_not_authorized/2,
|
||||
err_feature_not_implemented/0, err_feature_not_implemented/2]).
|
||||
|
||||
%% XMPP stream errors
|
||||
-export([serr_bad_format/0, serr_bad_format/2,
|
||||
serr_bad_namespace_prefix/0, serr_bad_namespace_prefix/2,
|
||||
serr_conflict/0, serr_conflict/2,
|
||||
serr_connection_timeout/0, serr_connection_timeout/2,
|
||||
serr_host_gone/0, serr_host_gone/2,
|
||||
serr_host_unknown/0, serr_host_unknown/2,
|
||||
serr_improper_addressing/0, serr_improper_addressing/2,
|
||||
serr_internal_server_error/0, serr_internal_server_error/2,
|
||||
serr_invalid_from/0, serr_invalid_from/2,
|
||||
serr_invalid_id/0, serr_invalid_id/2,
|
||||
serr_invalid_namespace/0, serr_invalid_namespace/2,
|
||||
serr_invalid_xml/0, serr_invalid_xml/2,
|
||||
serr_not_authorized/0, serr_not_authorized/2,
|
||||
serr_not_well_formed/0, serr_not_well_formed/2,
|
||||
serr_policy_violation/0, serr_policy_violation/2,
|
||||
serr_remote_connection_failed/0, serr_remote_connection_failed/2,
|
||||
serr_reset/0, serr_reset/2,
|
||||
serr_resource_constraint/0, serr_resource_constraint/2,
|
||||
serr_restricted_xml/0, serr_restricted_xml/2,
|
||||
serr_see_other_host/0, serr_see_other_host/2,
|
||||
serr_system_shutdown/0, serr_system_shutdown/2,
|
||||
serr_undefined_condition/0, serr_undefined_condition/2,
|
||||
serr_unsupported_encoding/0, serr_unsupported_encoding/2,
|
||||
serr_unsupported_stanza_type/0, serr_unsupported_stanza_type/2,
|
||||
serr_unsupported_version/0, serr_unsupported_version/2]).
|
||||
|
||||
-ifndef(NS_CLIENT).
|
||||
-define(NS_CLIENT, <<"jabber:client">>).
|
||||
-endif.
|
||||
|
||||
-include("xmpp.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
-spec make_iq_result(iq()) -> iq().
|
||||
make_iq_result(IQ) ->
|
||||
make_iq_result(IQ, undefined).
|
||||
|
||||
-spec make_iq_result(iq(), xmpp_element() | xmlel() | undefined) -> iq().
|
||||
make_iq_result(#iq{type = Type, from = From, to = To} = IQ, El)
|
||||
when Type == get; Type == set ->
|
||||
SubEls = if El == undefined -> [];
|
||||
true -> [El]
|
||||
end,
|
||||
IQ#iq{type = result, to = From, from = To, sub_els = SubEls}.
|
||||
|
||||
-spec make_error(message(), error()) -> message();
|
||||
(presence(), error()) -> presence();
|
||||
(iq(), error()) -> iq();
|
||||
(xmlel(), error()) -> xmlel().
|
||||
make_error(#message{type = Type, from = From, to = To, sub_els = Els} = Msg,
|
||||
Err) when Type /= error ->
|
||||
Msg#message{type = error, from = To, to = From, sub_els = Els ++ [Err]};
|
||||
make_error(#presence{type = Type, from = From, to = To, sub_els = Els} = Pres,
|
||||
Err) when Type /= error ->
|
||||
Pres#presence{type = error, from = To, to = From, sub_els = Els ++ [Err]};
|
||||
make_error(#iq{type = Type, from = From, to = To, sub_els = Els} = IQ,
|
||||
Err) when Type /= result, Type /= error ->
|
||||
IQ#iq{type = error, from = To, to = From, sub_els = Els ++ [Err]};
|
||||
make_error(#xmlel{attrs = Attrs, children = Els} = El, Err) ->
|
||||
To = fxml:get_attr(<<"to">>, Attrs),
|
||||
From = fxml:get_attr(<<"from">>, Attrs),
|
||||
Attrs1 = case To of
|
||||
{value, T} ->
|
||||
lists:keystore(<<"from">>, 1, Attrs, {<<"from">>, T});
|
||||
_ ->
|
||||
Attrs
|
||||
end,
|
||||
Attrs2 = case From of
|
||||
{value, F} ->
|
||||
lists:keystore(<<"to">>, 1, Attrs1, {<<"to">>, F});
|
||||
_ ->
|
||||
Attrs
|
||||
end,
|
||||
Attrs3 = lists:keystore(<<"type">>, 1, Attrs2, {<<"type">>, <<"error">>}),
|
||||
El#xmlel{attrs = Attrs3, children = Els ++ [encode(Err)]}.
|
||||
|
||||
-spec get_id(iq() | message() | presence() | xmlel()) -> undefined | binary().
|
||||
get_id(#iq{id = ID}) -> ID;
|
||||
get_id(#message{id = ID}) -> ID;
|
||||
get_id(#presence{id = ID}) -> ID;
|
||||
get_id(#xmlel{attrs = Attrs}) ->
|
||||
case fxml:get_attr(<<"id">>, Attrs) of
|
||||
{value, ID} -> ID;
|
||||
false -> undefined
|
||||
end.
|
||||
|
||||
-spec get_type(iq()) -> iq_type();
|
||||
(message()) -> message_type();
|
||||
(presence()) -> presence_type();
|
||||
(xmlel()) -> binary().
|
||||
get_type(#iq{type = T}) -> T;
|
||||
get_type(#message{type = T}) -> T;
|
||||
get_type(#presence{type = T}) -> T;
|
||||
get_type(#xmlel{attrs = Attrs}) -> fxml:get_attr_s(<<"type">>, Attrs).
|
||||
|
||||
-spec get_lang(iq() | message() | presence()) -> undefined | binary().
|
||||
get_lang(#iq{lang = L}) -> L;
|
||||
get_lang(#message{lang = L}) -> L;
|
||||
get_lang(#presence{lang = L}) -> L;
|
||||
get_lang(#xmlel{attrs = Attrs}) ->
|
||||
case fxml:get_attr(<<"xml:lang">>, Attrs) of
|
||||
{value, L} -> L;
|
||||
false -> undefined
|
||||
end.
|
||||
|
||||
-spec get_from(iq() | message() | presence()) -> undefined | jid:jid().
|
||||
get_from(#iq{from = J}) -> J;
|
||||
get_from(#message{from = J}) -> J;
|
||||
get_from(#presence{from = J}) -> J.
|
||||
|
||||
-spec get_to(iq() | message() | presence()) -> undefined | jid:jid().
|
||||
get_to(#iq{to = J}) -> J;
|
||||
get_to(#message{to = J}) -> J;
|
||||
get_to(#presence{to = J}) -> J.
|
||||
|
||||
-spec get_error(iq() | message() | presence()) -> undefined | error().
|
||||
get_error(#iq{error = E}) -> E;
|
||||
get_error(#message{error = E}) -> E;
|
||||
get_error(#presence{error = E}) -> E.
|
||||
|
||||
-spec get_els(iq() | message() | presence()) -> [xmpp_element() | xmlel()].
|
||||
get_els(#iq{sub_els = Els}) -> Els;
|
||||
get_els(#message{sub_els = Els}) -> Els;
|
||||
get_els(#presence{sub_els = Els}) -> Els.
|
||||
|
||||
-spec set_id(iq(), binary()) -> iq();
|
||||
(message(), binary()) -> message();
|
||||
(presence(), binary()) -> presence().
|
||||
set_id(#iq{} = IQ, I) -> IQ#iq{id = I};
|
||||
set_id(#message{} = Msg, I) -> Msg#message{id = I};
|
||||
set_id(#presence{} = Pres, I) -> Pres#presence{id = I}.
|
||||
|
||||
-spec set_type(iq(), iq_type()) -> iq();
|
||||
(message(), message_type()) -> message();
|
||||
(presence(), presence_type()) -> presence().
|
||||
set_type(#iq{} = IQ, T) -> IQ#iq{type = T};
|
||||
set_type(#message{} = Msg, T) -> Msg#message{type = T};
|
||||
set_type(#presence{} = Pres, T) -> Pres#presence{type = T}.
|
||||
|
||||
-spec set_lang(iq(), binary()) -> iq();
|
||||
(message(), binary()) -> message();
|
||||
(presence(), binary()) -> presence().
|
||||
set_lang(#iq{} = IQ, L) -> IQ#iq{lang = L};
|
||||
set_lang(#message{} = Msg, L) -> Msg#message{lang = L};
|
||||
set_lang(#presence{} = Pres, L) -> Pres#presence{lang = L}.
|
||||
|
||||
-spec set_from(iq(), jid:jid()) -> iq();
|
||||
(message(), jid:jid()) -> message();
|
||||
(presence(), jid:jid()) -> presence().
|
||||
set_from(#iq{} = IQ, J) -> IQ#iq{from = J};
|
||||
set_from(#message{} = Msg, J) -> Msg#message{from = J};
|
||||
set_from(#presence{} = Pres, J) -> Pres#presence{from = J}.
|
||||
|
||||
-spec set_to(iq(), jid:jid()) -> iq();
|
||||
(message(), jid:jid()) -> message();
|
||||
(presence(), jid:jid()) -> presence().
|
||||
set_to(#iq{} = IQ, J) -> IQ#iq{to = J};
|
||||
set_to(#message{} = Msg, J) -> Msg#message{to = J};
|
||||
set_to(#presence{} = Pres, J) -> Pres#presence{to = J}.
|
||||
|
||||
-spec set_from_to(iq(), jid:jid(), jid:jid()) -> iq();
|
||||
(message(), jid:jid(), jid:jid()) -> message();
|
||||
(presence(), jid:jid(), jid:jid()) -> presence().
|
||||
set_from_to(#iq{} = IQ, F, T) -> IQ#iq{from = F, to = T};
|
||||
set_from_to(#message{} = Msg, F, T) -> Msg#message{from = F, to = T};
|
||||
set_from_to(#presence{} = Pres, F, T) -> Pres#presence{from = F, to = T}.
|
||||
|
||||
-spec set_error(iq(), error()) -> iq();
|
||||
(message(), error()) -> message();
|
||||
(presence(), error()) -> presence().
|
||||
set_error(#iq{} = IQ, E) -> IQ#iq{error = E};
|
||||
set_error(#message{} = Msg, E) -> Msg#message{error = E};
|
||||
set_error(#presence{} = Pres, E) -> Pres#presence{error = E}.
|
||||
|
||||
-spec set_els(iq(), [xmpp_element() | xmlel()]) -> iq();
|
||||
(message(), [xmpp_element() | xmlel()]) -> message();
|
||||
(presence(), [xmpp_element() | xmlel()]) -> presence().
|
||||
set_els(#iq{} = IQ, Els) -> IQ#iq{sub_els = Els};
|
||||
set_els(#message{} = Msg, Els) -> Msg#message{sub_els = Els};
|
||||
set_els(#presence{} = Pres, Els) -> Pres#presence{sub_els = Els}.
|
||||
|
||||
-spec get_ns(xmpp_element() | xmlel()) -> binary().
|
||||
get_ns(#xmlel{attrs = Attrs}) ->
|
||||
fxml:get_attr_s(<<"xmlns">>, Attrs);
|
||||
get_ns(Pkt) ->
|
||||
xmpp_codec:get_ns(Pkt).
|
||||
|
||||
-spec get_name(xmpp_element() | xmlel()) -> binary().
|
||||
get_name(#xmlel{name = Name}) ->
|
||||
Name;
|
||||
get_name(Pkt) ->
|
||||
xmpp_codec:get_name(Pkt).
|
||||
|
||||
-spec decode(xmlel() | xmpp_element()) -> {ok, xmpp_element()} | {error, any()}.
|
||||
decode(El) ->
|
||||
decode(El, []).
|
||||
|
||||
-spec decode(xmlel() | xmpp_element(), [proplists:property()]) ->
|
||||
{ok, xmpp_element()} | {error, any()}.
|
||||
decode(#xmlel{} = El, Opts) ->
|
||||
xmpp_codec:decode(add_ns(El), Opts);
|
||||
decode(Pkt, _Opts) ->
|
||||
Pkt.
|
||||
|
||||
-spec decode_els(iq()) -> iq();
|
||||
(message()) -> message();
|
||||
(presence()) -> presence().
|
||||
decode_els(Stanza) ->
|
||||
Els = lists:map(
|
||||
fun(#xmlel{} = El) ->
|
||||
case xmpp_codec:is_known_tag(El) of
|
||||
true -> decode(El);
|
||||
false -> El
|
||||
end;
|
||||
(Pkt) ->
|
||||
Pkt
|
||||
end, get_els(Stanza)),
|
||||
set_els(Stanza, Els).
|
||||
|
||||
-spec encode(xmpp_element() | xmlel()) -> xmlel().
|
||||
encode(Pkt) ->
|
||||
xmpp_codec:encode(Pkt).
|
||||
|
||||
-spec decode_tags_by_ns([xmpp_element() | xmlel()], [binary()]) -> [xmpp_element()].
|
||||
decode_tags_by_ns(Els, NSList) ->
|
||||
[xmpp_codec:decode(El) || El <- Els, lists:member(get_ns(El), NSList)].
|
||||
|
||||
format_error(Reason) ->
|
||||
xmpp_codec:format_error(Reason).
|
||||
|
||||
-spec is_stanza(any()) -> boolean().
|
||||
is_stanza(#message{}) -> true;
|
||||
is_stanza(#iq{}) -> true;
|
||||
is_stanza(#presence{}) -> true;
|
||||
is_stanza(#xmlel{name = Name}) ->
|
||||
(Name == <<"iq">>) or (Name == <<"message">>) or (Name == <<"presence">>);
|
||||
is_stanza(_) -> false.
|
||||
|
||||
-spec set_subtag(iq(), xmpp_element()) -> iq();
|
||||
(message(), xmpp_element()) -> message();
|
||||
(presence(), xmpp_element()) -> presence().
|
||||
set_subtag(Stanza, Tag) ->
|
||||
TagName = xmpp_codec:get_name(Tag),
|
||||
XMLNS = xmpp_codec:get_ns(Tag),
|
||||
Els = get_els(Stanza),
|
||||
NewEls = set_subtag(Els, Tag, TagName, XMLNS),
|
||||
set_els(Stanza, NewEls).
|
||||
|
||||
set_subtag([El|Els], Tag, TagName, XMLNS) ->
|
||||
case {get_name(El), get_ns(El)} of
|
||||
{TagName, XMLNS} ->
|
||||
[Tag|Els];
|
||||
_ ->
|
||||
[El|set_subtag(Els, Tag, TagName, XMLNS)]
|
||||
end;
|
||||
set_subtag([], Tag, _, _) ->
|
||||
[Tag].
|
||||
|
||||
-spec get_subtag(stanza(), xmpp_element()) -> xmpp_element() | false.
|
||||
get_subtag(Stanza, Tag) ->
|
||||
Els = get_els(Stanza),
|
||||
TagName = xmpp_codec:get_name(Tag),
|
||||
XMLNS = xmpp_codec:get_ns(Tag),
|
||||
get_subtag(Els, TagName, XMLNS).
|
||||
|
||||
get_subtag([El|Els], TagName, XMLNS) ->
|
||||
case {get_name(El), get_ns(El)} of
|
||||
{TagName, XMLNS} ->
|
||||
try
|
||||
decode(El)
|
||||
catch _:{xmpp_codec, _Why} ->
|
||||
get_subtag(Els, TagName, XMLNS)
|
||||
end;
|
||||
_ ->
|
||||
get_subtag(Els, TagName, XMLNS)
|
||||
end;
|
||||
get_subtag([], _, _) ->
|
||||
false.
|
||||
|
||||
-spec remove_subtag(iq(), xmpp_element()) -> iq();
|
||||
(message(), xmpp_element()) -> message();
|
||||
(presence(), xmpp_element()) -> presence().
|
||||
remove_subtag(Stanza, Tag) ->
|
||||
Els = get_els(Stanza),
|
||||
TagName = xmpp_codec:get_name(Tag),
|
||||
XMLNS = xmpp_codec:get_ns(Tag),
|
||||
NewEls = remove_subtag(Els, TagName, XMLNS),
|
||||
set_els(Stanza, NewEls).
|
||||
|
||||
remove_subtag([El|Els], TagName, XMLNS) ->
|
||||
case {get_name(El), get_ns(El)} of
|
||||
{TagName, XMLNS} ->
|
||||
remove_subtag(Els, TagName, XMLNS);
|
||||
_ ->
|
||||
[El|remove_subtag(Els, TagName, XMLNS)]
|
||||
end;
|
||||
remove_subtag([], _, _) ->
|
||||
[].
|
||||
|
||||
-spec has_subtag(stanza(), xmpp_element()) -> boolean().
|
||||
has_subtag(Stanza, Tag) ->
|
||||
Els = get_els(Stanza),
|
||||
TagName = xmpp_codec:get_name(Tag),
|
||||
XMLNS = xmpp_codec:get_ns(Tag),
|
||||
has_subtag(Els, TagName, XMLNS).
|
||||
|
||||
has_subtag([El|Els], TagName, XMLNS) ->
|
||||
case {get_name(El), get_ns(El)} of
|
||||
{TagName, XMLNS} ->
|
||||
true;
|
||||
_ ->
|
||||
has_subtag(Els, TagName, XMLNS)
|
||||
end;
|
||||
has_subtag([], _, _) ->
|
||||
false.
|
||||
|
||||
-spec get_text([text()]) -> binary().
|
||||
get_text([]) -> <<"">>;
|
||||
get_text([#text{data = undefined}|_]) -> <<"">>;
|
||||
get_text([#text{data = Data}|_]) -> Data.
|
||||
|
||||
-spec mk_text(binary()) -> [text()].
|
||||
mk_text(Text) ->
|
||||
mk_text(Text, undefined).
|
||||
|
||||
-spec mk_text(binary(), binary() | undefined) -> [text()].
|
||||
mk_text(<<"">>, _) ->
|
||||
[];
|
||||
mk_text(Text, Lang) ->
|
||||
[#text{lang = Lang,
|
||||
data = translate:translate(Lang, Text)}].
|
||||
|
||||
-spec pp(any()) -> iodata().
|
||||
pp(Term) ->
|
||||
xmpp_codec:pp(Term).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Functions to construct general XMPP errors
|
||||
%%%===================================================================
|
||||
-spec err_bad_request() -> error().
|
||||
err_bad_request() ->
|
||||
err(modify, 'bad-request', 400).
|
||||
|
||||
-spec err_bad_request(binary(), binary() | undefined) -> error().
|
||||
err_bad_request(Text, Lang) ->
|
||||
err(modify, 'bad-request', 400, Text, Lang).
|
||||
|
||||
-spec err_bad_format() -> error().
|
||||
err_bad_format() ->
|
||||
err(modify, 'bad-format', 406).
|
||||
|
||||
-spec err_bad_format(binary(), binary() | undefined) -> error().
|
||||
err_bad_format(Text, Lang) ->
|
||||
err(modify, 'bad-format', 406, Text, Lang).
|
||||
|
||||
-spec err_conflict() -> error().
|
||||
err_conflict() ->
|
||||
err(cancel, 'conflict', 409).
|
||||
|
||||
-spec err_conflict(binary(), binary() | undefined) -> error().
|
||||
err_conflict(Text, Lang) ->
|
||||
err(cancel, 'conflict', 409, Text, Lang).
|
||||
|
||||
-spec err_not_allowed() -> error().
|
||||
err_not_allowed() ->
|
||||
err(cancel, 'not-allowed', 405).
|
||||
|
||||
-spec err_not_allowed(binary(), binary() | undefined) -> error().
|
||||
err_not_allowed(Text, Lang) ->
|
||||
err(cancel, 'not-allowed', 405, Text, Lang).
|
||||
|
||||
-spec err_feature_not_implemented() -> error().
|
||||
err_feature_not_implemented() ->
|
||||
err(cancel, 'feature-not-implemented', 501).
|
||||
|
||||
-spec err_feature_not_implemented(binary(), binary() | undefined) -> error().
|
||||
err_feature_not_implemented(Text, Lang) ->
|
||||
err(cancel, 'feature-not-implemented', 501, Text, Lang).
|
||||
|
||||
-spec err_item_not_found() -> error().
|
||||
err_item_not_found() ->
|
||||
err(cancel, 'item-not-found', 404).
|
||||
|
||||
-spec err_item_not_found(binary(), binary() | undefined) -> error().
|
||||
err_item_not_found(Text, Lang) ->
|
||||
err(cancel, 'item-not-found', 404, Text, Lang).
|
||||
|
||||
-spec err_forbidden() -> error().
|
||||
err_forbidden() ->
|
||||
err(auth, 'forbidden', 403).
|
||||
|
||||
-spec err_forbidden(binary(), binary() | undefined) -> error().
|
||||
err_forbidden(Text, Lang) ->
|
||||
err(auth, 'forbidden', 403, Text, Lang).
|
||||
|
||||
-spec err_not_acceptable() -> error().
|
||||
err_not_acceptable() ->
|
||||
err(modify, 'not-acceptable', 406).
|
||||
|
||||
-spec err_not_acceptable(binary(), binary() | undefined) -> error().
|
||||
err_not_acceptable(Text, Lang) ->
|
||||
err(modify, 'not-acceptable', 406, Text, Lang).
|
||||
|
||||
-spec err_internal_server_error() -> error().
|
||||
err_internal_server_error() ->
|
||||
err(wait, 'internal-server-error', 500).
|
||||
|
||||
-spec err_internal_server_error(binary(), binary() | undefined) -> error().
|
||||
err_internal_server_error(Text, Lang) ->
|
||||
err(wait, 'internal-server-error', 500, Text, Lang).
|
||||
|
||||
-spec err_service_unavailable() -> error().
|
||||
err_service_unavailable() ->
|
||||
err(cancel, 'service-unavailable', 503).
|
||||
|
||||
-spec err_service_unavailable(binary(), binary() | undefined) -> error().
|
||||
err_service_unavailable(Text, Lang) ->
|
||||
err(cancel, 'service-unavailable', 503, Text, Lang).
|
||||
|
||||
-spec err_jid_malformed() -> error().
|
||||
err_jid_malformed() ->
|
||||
err(modify, 'jid-malformed', 400).
|
||||
|
||||
-spec err_jid_malformed(binary(), binary() | undefined) -> error().
|
||||
err_jid_malformed(Text, Lang) ->
|
||||
err(modify, 'jid-malformed', 400, Text, Lang).
|
||||
|
||||
-spec err_not_authorized() -> error().
|
||||
err_not_authorized() ->
|
||||
err(auth, 'not-authorized', 401).
|
||||
|
||||
-spec err_not_authorized(binary(), binary() | undefined) -> error().
|
||||
err_not_authorized(Text, Lang) ->
|
||||
err(auth, 'not-authorized', 401, Text, Lang).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Functions to construct stream errors
|
||||
%%%===================================================================
|
||||
-spec serr_bad_format() -> stream_error().
|
||||
serr_bad_format() ->
|
||||
serr('bad-format').
|
||||
|
||||
-spec serr_bad_format(binary(), binary() | undefined) -> stream_error().
|
||||
serr_bad_format(Text, Lang) ->
|
||||
serr('bad-format', Text, Lang).
|
||||
|
||||
-spec serr_bad_namespace_prefix() -> stream_error().
|
||||
serr_bad_namespace_prefix() ->
|
||||
serr('bad-namespace-prefix').
|
||||
|
||||
-spec serr_bad_namespace_prefix(binary(), binary() | undefined) -> stream_error().
|
||||
serr_bad_namespace_prefix(Text, Lang) ->
|
||||
serr('bad-namespace-prefix', Text, Lang).
|
||||
|
||||
-spec serr_conflict() -> stream_error().
|
||||
serr_conflict() ->
|
||||
serr('conflict').
|
||||
|
||||
-spec serr_conflict(binary(), binary() | undefined) -> stream_error().
|
||||
serr_conflict(Text, Lang) ->
|
||||
serr('conflict', Text, Lang).
|
||||
|
||||
-spec serr_connection_timeout() -> stream_error().
|
||||
serr_connection_timeout() ->
|
||||
serr('connection-timeout').
|
||||
|
||||
-spec serr_connection_timeout(binary(), binary() | undefined) -> stream_error().
|
||||
serr_connection_timeout(Text, Lang) ->
|
||||
serr('connection-timeout', Text, Lang).
|
||||
|
||||
-spec serr_host_gone() -> stream_error().
|
||||
serr_host_gone() ->
|
||||
serr('host-gone').
|
||||
|
||||
-spec serr_host_gone(binary(), binary() | undefined) -> stream_error().
|
||||
serr_host_gone(Text, Lang) ->
|
||||
serr('host-gone', Text, Lang).
|
||||
|
||||
-spec serr_host_unknown() -> stream_error().
|
||||
serr_host_unknown() ->
|
||||
serr('host-unknown').
|
||||
|
||||
-spec serr_host_unknown(binary(), binary() | undefined) -> stream_error().
|
||||
serr_host_unknown(Text, Lang) ->
|
||||
serr('host-unknown', Text, Lang).
|
||||
|
||||
-spec serr_improper_addressing() -> stream_error().
|
||||
serr_improper_addressing() ->
|
||||
serr('improper-addressing').
|
||||
|
||||
-spec serr_improper_addressing(binary(), binary() | undefined) -> stream_error().
|
||||
serr_improper_addressing(Text, Lang) ->
|
||||
serr('improper-addressing', Text, Lang).
|
||||
|
||||
-spec serr_internal_server_error() -> stream_error().
|
||||
serr_internal_server_error() ->
|
||||
serr('internal-server-error').
|
||||
|
||||
-spec serr_internal_server_error(binary(), binary() | undefined) -> stream_error().
|
||||
serr_internal_server_error(Text, Lang) ->
|
||||
serr('internal-server-error', Text, Lang).
|
||||
|
||||
-spec serr_invalid_from() -> stream_error().
|
||||
serr_invalid_from() ->
|
||||
serr('invalid-from').
|
||||
|
||||
-spec serr_invalid_from(binary(), binary() | undefined) -> stream_error().
|
||||
serr_invalid_from(Text, Lang) ->
|
||||
serr('invalid-from', Text, Lang).
|
||||
|
||||
-spec serr_invalid_id() -> stream_error().
|
||||
serr_invalid_id() ->
|
||||
serr('invalid-id').
|
||||
|
||||
-spec serr_invalid_id(binary(), binary() | undefined) -> stream_error().
|
||||
serr_invalid_id(Text, Lang) ->
|
||||
serr('invalid-id', Text, Lang).
|
||||
|
||||
-spec serr_invalid_namespace() -> stream_error().
|
||||
serr_invalid_namespace() ->
|
||||
serr('invalid-namespace').
|
||||
|
||||
-spec serr_invalid_namespace(binary(), binary() | undefined) -> stream_error().
|
||||
serr_invalid_namespace(Text, Lang) ->
|
||||
serr('invalid-namespace', Text, Lang).
|
||||
|
||||
-spec serr_invalid_xml() -> stream_error().
|
||||
serr_invalid_xml() ->
|
||||
serr('invalid-xml').
|
||||
|
||||
-spec serr_invalid_xml(binary(), binary() | undefined) -> stream_error().
|
||||
serr_invalid_xml(Text, Lang) ->
|
||||
serr('invalid-xml', Text, Lang).
|
||||
|
||||
-spec serr_not_authorized() -> stream_error().
|
||||
serr_not_authorized() ->
|
||||
serr('not-authorized').
|
||||
|
||||
-spec serr_not_authorized(binary(), binary() | undefined) -> stream_error().
|
||||
serr_not_authorized(Text, Lang) ->
|
||||
serr('not-authorized', Text, Lang).
|
||||
|
||||
-spec serr_not_well_formed() -> stream_error().
|
||||
serr_not_well_formed() ->
|
||||
serr('not-well-formed').
|
||||
|
||||
-spec serr_not_well_formed(binary(), binary() | undefined) -> stream_error().
|
||||
serr_not_well_formed(Text, Lang) ->
|
||||
serr('not-well-formed', Text, Lang).
|
||||
|
||||
-spec serr_policy_violation() -> stream_error().
|
||||
serr_policy_violation() ->
|
||||
serr('policy-violation').
|
||||
|
||||
-spec serr_policy_violation(binary(), binary() | undefined) -> stream_error().
|
||||
serr_policy_violation(Text, Lang) ->
|
||||
serr('policy-violation', Text, Lang).
|
||||
|
||||
-spec serr_remote_connection_failed() -> stream_error().
|
||||
serr_remote_connection_failed() ->
|
||||
serr('remote-connection-failed').
|
||||
|
||||
-spec serr_remote_connection_failed(binary(), binary() | undefined) -> stream_error().
|
||||
serr_remote_connection_failed(Text, Lang) ->
|
||||
serr('remote-connection-failed', Text, Lang).
|
||||
|
||||
-spec serr_reset() -> stream_error().
|
||||
serr_reset() ->
|
||||
serr('reset').
|
||||
|
||||
-spec serr_reset(binary(), binary() | undefined) -> stream_error().
|
||||
serr_reset(Text, Lang) ->
|
||||
serr('reset', Text, Lang).
|
||||
|
||||
-spec serr_resource_constraint() -> stream_error().
|
||||
serr_resource_constraint() ->
|
||||
serr('resource-constraint').
|
||||
|
||||
-spec serr_resource_constraint(binary(), binary() | undefined) -> stream_error().
|
||||
serr_resource_constraint(Text, Lang) ->
|
||||
serr('resource-constraint', Text, Lang).
|
||||
|
||||
-spec serr_restricted_xml() -> stream_error().
|
||||
serr_restricted_xml() ->
|
||||
serr('restricted-xml').
|
||||
|
||||
-spec serr_restricted_xml(binary(), binary() | undefined) -> stream_error().
|
||||
serr_restricted_xml(Text, Lang) ->
|
||||
serr('restricted-xml', Text, Lang).
|
||||
|
||||
-spec serr_see_other_host() -> stream_error().
|
||||
serr_see_other_host() ->
|
||||
serr('see-other-host').
|
||||
|
||||
-spec serr_see_other_host(binary(), binary() | undefined) -> stream_error().
|
||||
serr_see_other_host(Text, Lang) ->
|
||||
serr('see-other-host', Text, Lang).
|
||||
|
||||
-spec serr_system_shutdown() -> stream_error().
|
||||
serr_system_shutdown() ->
|
||||
serr('system-shutdown').
|
||||
|
||||
-spec serr_system_shutdown(binary(), binary() | undefined) -> stream_error().
|
||||
serr_system_shutdown(Text, Lang) ->
|
||||
serr('system-shutdown', Text, Lang).
|
||||
|
||||
-spec serr_undefined_condition() -> stream_error().
|
||||
serr_undefined_condition() ->
|
||||
serr('undefined-condition').
|
||||
|
||||
-spec serr_undefined_condition(binary(), binary() | undefined) -> stream_error().
|
||||
serr_undefined_condition(Text, Lang) ->
|
||||
serr('undefined-condition', Text, Lang).
|
||||
|
||||
-spec serr_unsupported_encoding() -> stream_error().
|
||||
serr_unsupported_encoding() ->
|
||||
serr('unsupported-encoding').
|
||||
|
||||
-spec serr_unsupported_encoding(binary(), binary() | undefined) -> stream_error().
|
||||
serr_unsupported_encoding(Text, Lang) ->
|
||||
serr('unsupported-encoding', Text, Lang).
|
||||
|
||||
-spec serr_unsupported_stanza_type() -> stream_error().
|
||||
serr_unsupported_stanza_type() ->
|
||||
serr('unsupported-stanza-type').
|
||||
|
||||
-spec serr_unsupported_stanza_type(binary(), binary() | undefined) -> stream_error().
|
||||
serr_unsupported_stanza_type(Text, Lang) ->
|
||||
serr('unsupported-stanza-type', Text, Lang).
|
||||
|
||||
-spec serr_unsupported_version() -> stream_error().
|
||||
serr_unsupported_version() ->
|
||||
serr('unsupported-version').
|
||||
|
||||
-spec serr_unsupported_version(binary(), binary() | undefined) -> stream_error().
|
||||
serr_unsupported_version(Text, Lang) ->
|
||||
serr('unsupported-version', Text, Lang).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
-spec err('auth' | 'cancel' | 'continue' | 'modify' | 'wait',
|
||||
atom() | gone() | redirect(), non_neg_integer()) -> error().
|
||||
err(Type, Reason, Code) ->
|
||||
#error{type = Type, reason = Reason, code = Code}.
|
||||
|
||||
-spec err('auth' | 'cancel' | 'continue' | 'modify' | 'wait',
|
||||
atom() | gone() | redirect(), non_neg_integer(),
|
||||
binary(), binary() | undefined) -> error().
|
||||
err(Type, Reason, Code, Text, Lang) ->
|
||||
#error{type = Type, reason = Reason, code = Code,
|
||||
text = #text{lang = Lang,
|
||||
data = translate:translate(Lang, Text)}}.
|
||||
|
||||
-spec serr(atom() | 'see-other-host'()) -> stream_error().
|
||||
serr(Reason) ->
|
||||
#stream_error{reason = Reason}.
|
||||
|
||||
-spec serr(atom() | 'see-other-host'(), binary(),
|
||||
binary() | undefined) -> stream_error().
|
||||
serr(Reason, Text, Lang) ->
|
||||
#stream_error{reason = Reason,
|
||||
text = #text{lang = Lang,
|
||||
data = translate:translate(Lang, Text)}}.
|
||||
|
||||
-spec add_ns(xmlel()) -> xmlel().
|
||||
add_ns(#xmlel{name = Name} = El) when Name == <<"message">>;
|
||||
Name == <<"presence">>;
|
||||
Name == <<"iq">> ->
|
||||
Attrs = lists:keystore(<<"xmlns">>, 1, El#xmlel.attrs,
|
||||
{<<"xmlns">>, ?NS_CLIENT}),
|
||||
El#xmlel{attrs = Attrs};
|
||||
add_ns(El) ->
|
||||
El.
|
File diff suppressed because it is too large
Load Diff
81
src/xmpp_util.erl
Normal file
81
src/xmpp_util.erl
Normal file
@ -0,0 +1,81 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% @copyright (C) 2016, Evgeny Khramtsov
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 12 Jul 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(xmpp_util).
|
||||
|
||||
%% API
|
||||
-export([add_delay_info/3, add_delay_info/4, unwrap_carbon/1,
|
||||
is_standalone_chat_state/1]).
|
||||
|
||||
-include("xmpp.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
-spec add_delay_info(stanza(), jid(), erlang:timestamp()) -> stanza().
|
||||
add_delay_info(Stz, From, Time) ->
|
||||
add_delay_info(Stz, From, Time, <<"">>).
|
||||
|
||||
-spec add_delay_info(stanza(), jid(),
|
||||
erlang:timestamp(), binary()) -> stanza().
|
||||
|
||||
add_delay_info(Stz, From, Time, Desc) ->
|
||||
case xmpp:get_subtag(Stz, #delay{}) of
|
||||
#delay{from = OldFrom, desc = OldDesc} = Delay ->
|
||||
case jid:tolower(From) == jid:tolower(OldFrom) of
|
||||
true when Desc == <<"">> ->
|
||||
Stz;
|
||||
true when OldDesc == <<"">> ->
|
||||
xmpp:set_subtag(Stz, Delay#delay{desc = Desc});
|
||||
true ->
|
||||
case binary:match(OldDesc, Desc) of
|
||||
nomatch ->
|
||||
NewDesc = <<OldDesc/binary, ", ", Desc/binary>>,
|
||||
xmpp:set_subtag(Stz, Delay#delay{desc = NewDesc});
|
||||
_ ->
|
||||
Stz
|
||||
end;
|
||||
false ->
|
||||
NewDelay = #delay{stamp = Time, from = From, desc = Desc},
|
||||
xmpp:set_subtag(Stz, NewDelay)
|
||||
end;
|
||||
false ->
|
||||
Delay = #delay{stamp = Time, from = From, desc = Desc},
|
||||
xmpp:set_subtag(Stz, Delay)
|
||||
end.
|
||||
|
||||
-spec unwrap_carbon(stanza()) -> xmpp_element().
|
||||
unwrap_carbon(#message{} = Msg) ->
|
||||
case xmpp:get_subtag(Msg, #carbons_sent{}) of
|
||||
#carbons_sent{forwarded = #forwarded{sub_els = [El]}} ->
|
||||
El;
|
||||
_ ->
|
||||
case xmpp:get_subtag(Msg, #carbons_received{}) of
|
||||
#carbons_received{forwarded = #forwarded{sub_els = [El]}} ->
|
||||
El;
|
||||
_ ->
|
||||
Msg
|
||||
end
|
||||
end;
|
||||
unwrap_carbon(Stanza) -> Stanza.
|
||||
|
||||
-spec is_standalone_chat_state(stanza()) -> boolean().
|
||||
is_standalone_chat_state(Stanza) ->
|
||||
case unwrap_carbon(Stanza) of
|
||||
#message{sub_els = Els} ->
|
||||
IgnoreNS = [?NS_CHATSTATES, ?NS_DELAY],
|
||||
Stripped = [El || El <- Els,
|
||||
not lists:member(xmpp:get_ns(El), IgnoreNS)],
|
||||
Stripped == [];
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
@ -1,11 +1,11 @@
|
||||
-xml(last,
|
||||
#elem{name = <<"query">>,
|
||||
xmlns = <<"jabber:iq:last">>,
|
||||
result = {last, '$seconds', '$text'},
|
||||
result = {last, '$seconds', '$status'},
|
||||
attrs = [#attr{name = <<"seconds">>,
|
||||
enc = {enc_int, []},
|
||||
dec = {dec_int, [0, infinity]}}],
|
||||
cdata = #cdata{label = '$text'}}).
|
||||
cdata = #cdata{default = <<"">>, label = '$status'}}).
|
||||
|
||||
-xml(version_name,
|
||||
#elem{name = <<"name">>,
|
||||
@ -54,7 +54,8 @@
|
||||
required = true,
|
||||
dec = {dec_jid, []},
|
||||
enc = {enc_jid, []}},
|
||||
#attr{name = <<"name">>},
|
||||
#attr{name = <<"name">>,
|
||||
default = <<"">>},
|
||||
#attr{name = <<"subscription">>,
|
||||
default = none,
|
||||
enc = {enc_enum, []},
|
||||
@ -64,29 +65,34 @@
|
||||
dec = {dec_enum, [[subscribe]]}}],
|
||||
refs = [#ref{name = roster_group, label = '$groups'}]}).
|
||||
|
||||
-xml(roster,
|
||||
-xml(roster_query,
|
||||
#elem{name = <<"query">>,
|
||||
xmlns = <<"jabber:iq:roster">>,
|
||||
result = {roster, '$items', '$ver'},
|
||||
result = {roster_query, '$items', '$ver'},
|
||||
attrs = [#attr{name = <<"ver">>}],
|
||||
refs = [#ref{name = roster_item, label = '$items'}]}).
|
||||
|
||||
-xml(rosterver_feature,
|
||||
#elem{name = <<"ver">>,
|
||||
xmlns = <<"urn:xmpp:features:rosterver">>,
|
||||
result = {rosterver_feature}}).
|
||||
|
||||
-xml(privacy_message, #elem{name = <<"message">>, xmlns = <<"jabber:iq:privacy">>,
|
||||
result = message}).
|
||||
result = true}).
|
||||
-xml(privacy_iq, #elem{name = <<"iq">>, xmlns = <<"jabber:iq:privacy">>,
|
||||
result = iq}).
|
||||
result = true}).
|
||||
-xml(privacy_presence_in, #elem{name = <<"presence-in">>,
|
||||
xmlns = <<"jabber:iq:privacy">>,
|
||||
result = 'presence-in'}).
|
||||
result = true}).
|
||||
-xml(privacy_presence_out, #elem{name = <<"presence-out">>,
|
||||
xmlns = <<"jabber:iq:privacy">>,
|
||||
result = 'presence-out'}).
|
||||
result = true}).
|
||||
|
||||
-xml(privacy_item,
|
||||
#elem{name = <<"item">>,
|
||||
xmlns = <<"jabber:iq:privacy">>,
|
||||
result = {privacy_item, '$order', '$action', '$type',
|
||||
'$value', '$kinds'},
|
||||
result = {privacy_item, '$order', '$action', '$type', '$value',
|
||||
'$message', '$iq', '$presence_in', '$presence_out'},
|
||||
attrs = [#attr{name = <<"action">>,
|
||||
required = true,
|
||||
dec = {dec_enum, [[allow, deny]]},
|
||||
@ -99,14 +105,14 @@
|
||||
dec = {dec_enum, [[group, jid, subscription]]},
|
||||
enc = {enc_enum, []}},
|
||||
#attr{name = <<"value">>}],
|
||||
refs = [#ref{name = privacy_message,
|
||||
label = '$kinds'},
|
||||
#ref{name = privacy_iq,
|
||||
label = '$kinds'},
|
||||
#ref{name = privacy_presence_in,
|
||||
label = '$kinds'},
|
||||
#ref{name = privacy_presence_out,
|
||||
label = '$kinds'}]}).
|
||||
refs = [#ref{name = privacy_message, default = false,
|
||||
min = 0, max = 1, label = '$message'},
|
||||
#ref{name = privacy_iq, default = false,
|
||||
min = 0, max = 1, label = '$iq'},
|
||||
#ref{name = privacy_presence_in, default = false,
|
||||
min = 0, max = 1, label = '$presence_in'},
|
||||
#ref{name = privacy_presence_out, default = false,
|
||||
min = 0, max = 1, label = '$presence_out'}]}).
|
||||
|
||||
-xml(privacy_list,
|
||||
#elem{name = <<"list">>,
|
||||
@ -134,7 +140,7 @@
|
||||
-xml(privacy,
|
||||
#elem{name = <<"query">>,
|
||||
xmlns = <<"jabber:iq:privacy">>,
|
||||
result = {privacy, '$lists', '$default', '$active'},
|
||||
result = {privacy_query, '$lists', '$default', '$active'},
|
||||
refs = [#ref{name = privacy_list,
|
||||
label = '$lists'},
|
||||
#ref{name = privacy_default_list,
|
||||
@ -396,10 +402,11 @@
|
||||
'$show', '$status', '$priority', '$error', '$_els'},
|
||||
attrs = [#attr{name = <<"id">>},
|
||||
#attr{name = <<"type">>,
|
||||
default = available,
|
||||
enc = {enc_enum, []},
|
||||
dec = {dec_enum, [[unavailable, subscribe, subscribed,
|
||||
unsubscribe, unsubscribed,
|
||||
probe, error]]}},
|
||||
available, probe, error]]}},
|
||||
#attr{name = <<"from">>,
|
||||
dec = {dec_jid, []},
|
||||
enc = {enc_jid, []}},
|
||||
@ -516,13 +523,17 @@
|
||||
-xml(error,
|
||||
#elem{name = <<"error">>,
|
||||
xmlns = <<"jabber:client">>,
|
||||
result = {error, '$type', '$by', '$reason', '$text'},
|
||||
result = {error, '$type', '$code', '$by', '$reason', '$text'},
|
||||
attrs = [#attr{name = <<"type">>,
|
||||
label = '$type',
|
||||
required = true,
|
||||
dec = {dec_enum, [[auth, cancel, continue,
|
||||
modify, wait]]},
|
||||
enc = {enc_enum, []}},
|
||||
#attr{name = <<"code">>,
|
||||
label = '$code',
|
||||
enc = {enc_int, []},
|
||||
dec = {dec_int, [0, infinity]}},
|
||||
#attr{name = <<"by">>}],
|
||||
refs = [#ref{name = error_text,
|
||||
min = 0, max = 1, label = '$text'},
|
||||
@ -595,6 +606,41 @@
|
||||
min = 0, max = 1,
|
||||
label = '$resource'}]}).
|
||||
|
||||
-xml(legacy_auth_username,
|
||||
#elem{name = <<"username">>,
|
||||
xmlns = <<"jabber:iq:auth">>,
|
||||
cdata = #cdata{default = none},
|
||||
result = '$cdata'}).
|
||||
-xml(legacy_auth_password,
|
||||
#elem{name = <<"password">>,
|
||||
xmlns = <<"jabber:iq:auth">>,
|
||||
cdata = #cdata{default = none},
|
||||
result = '$cdata'}).
|
||||
-xml(legacy_auth_digest,
|
||||
#elem{name = <<"digest">>,
|
||||
xmlns = <<"jabber:iq:auth">>,
|
||||
cdata = #cdata{default = none},
|
||||
result = '$cdata'}).
|
||||
-xml(legacy_auth_resource,
|
||||
#elem{name = <<"resource">>,
|
||||
xmlns = <<"jabber:iq:auth">>,
|
||||
cdata = #cdata{default = none},
|
||||
result = '$cdata'}).
|
||||
|
||||
-xml(legacy_auth,
|
||||
#elem{name = <<"query">>,
|
||||
xmlns = <<"jabber:iq:auth">>,
|
||||
result = {legacy_auth, '$username', '$password',
|
||||
'$digest', '$resource'},
|
||||
refs = [#ref{name = legacy_auth_username, min = 0, max = 1,
|
||||
label = '$username'},
|
||||
#ref{name = legacy_auth_password, min = 0, max = 1,
|
||||
label = '$password'},
|
||||
#ref{name = legacy_auth_digest, min = 0, max = 1,
|
||||
label = '$digest'},
|
||||
#ref{name = legacy_auth_resource, min = 0, max = 1,
|
||||
label = '$resource'}]}).
|
||||
|
||||
-xml(sasl_auth,
|
||||
#elem{name = <<"auth">>,
|
||||
xmlns = <<"urn:ietf:params:xml:ns:xmpp-sasl">>,
|
||||
@ -682,6 +728,10 @@
|
||||
#elem{name = <<"not-authorized">>,
|
||||
result = 'not-authorized',
|
||||
xmlns = <<"urn:ietf:params:xml:ns:xmpp-sasl">>}).
|
||||
-xml(sasl_failure_bad_protocol,
|
||||
#elem{name = <<"bad-protocol">>,
|
||||
result = 'bad-protocol',
|
||||
xmlns = <<"urn:ietf:params:xml:ns:xmpp-sasl">>}).
|
||||
-xml(sasl_failure_temporary_auth_failure,
|
||||
#elem{name = <<"temporary-auth-failure">>,
|
||||
result = 'temporary-auth-failure',
|
||||
@ -713,6 +763,8 @@
|
||||
min = 0, max = 1, label = '$reason'},
|
||||
#ref{name = sasl_failure_not_authorized,
|
||||
min = 0, max = 1, label = '$reason'},
|
||||
#ref{name = sasl_failure_bad_protocol,
|
||||
min = 0, max = 1, label = '$reason'},
|
||||
#ref{name = sasl_failure_temporary_auth_failure,
|
||||
min = 0, max = 1, label = '$reason'}]}).
|
||||
|
||||
@ -827,12 +879,16 @@
|
||||
-xml(caps,
|
||||
#elem{name = <<"c">>,
|
||||
xmlns = <<"http://jabber.org/protocol/caps">>,
|
||||
result = {caps, '$hash', '$node', '$ver'},
|
||||
result = {caps, '$node', '$version', '$hash', '$exts'},
|
||||
attrs = [#attr{name = <<"hash">>},
|
||||
#attr{name = <<"node">>},
|
||||
#attr{name = <<"ext">>,
|
||||
label = '$exts',
|
||||
default = [],
|
||||
dec = {re, split, ["\\h+"]},
|
||||
enc = {join, [$ ]}},
|
||||
#attr{name = <<"ver">>,
|
||||
enc = {base64, encode, []},
|
||||
dec = {base64, decode, []}}]}).
|
||||
label = '$version'}]}).
|
||||
|
||||
-xml(feature_register,
|
||||
#elem{name = <<"register">>,
|
||||
@ -988,10 +1044,18 @@
|
||||
#ref{name = register_key, min = 0, max = 1,
|
||||
label = '$key'}]}).
|
||||
|
||||
-xml(session_optional,
|
||||
#elem{name = <<"optional">>,
|
||||
xmlns = <<"urn:ietf:params:xml:ns:xmpp-session">>,
|
||||
result = true}).
|
||||
|
||||
-xml(session,
|
||||
#elem{name = <<"session">>,
|
||||
xmlns = <<"urn:ietf:params:xml:ns:xmpp-session">>,
|
||||
result = {session}}).
|
||||
result = {xmpp_session, '$optional'},
|
||||
refs = [#ref{name = session_optional,
|
||||
min = 0, max = 1, default = false,
|
||||
label = '$optional'}]}).
|
||||
|
||||
-xml(ping,
|
||||
#elem{name = <<"ping">>,
|
||||
@ -1444,10 +1508,10 @@
|
||||
%% refs = [#ref{name = vcard, min = 0, max = 1, label = '$vcard'},
|
||||
%% #ref{name = vcard_EXTVAL, min = 0, max = 1, label = '$extval'}]}).
|
||||
|
||||
-xml(vcard,
|
||||
-xml(vcard_temp,
|
||||
#elem{name = <<"vCard">>,
|
||||
xmlns = <<"vcard-temp">>,
|
||||
result = {vcard, '$version', '$fn', '$n', '$nickname', '$photo',
|
||||
result = {vcard_temp, '$version', '$fn', '$n', '$nickname', '$photo',
|
||||
'$bday', '$adr', '$label', '$tel', '$email', '$jabberid',
|
||||
'$mailer', '$tz', '$geo', '$title', '$role', '$logo',
|
||||
'$org', '$categories', '$note', '$prodid', %% '$agent',
|
||||
@ -1491,12 +1555,16 @@
|
||||
xmlns = <<"vcard-temp:x:update">>,
|
||||
result = '$cdata'}).
|
||||
|
||||
-record(vcard_xupdate, {us :: {binary(), binary()},
|
||||
hash :: binary()}).
|
||||
-type vcard_xupdate() :: #vcard_xupdate{}.
|
||||
|
||||
-xml(vcard_xupdate,
|
||||
#elem{name = <<"x">>,
|
||||
xmlns = <<"vcard-temp:x:update">>,
|
||||
result = {vcard_xupdate, '$photo'},
|
||||
result = {vcard_xupdate, undefined, '$hash'},
|
||||
refs = [#ref{name = vcard_xupdate_photo, min = 0, max = 1,
|
||||
label = '$photo'}]}).
|
||||
label = '$hash'}]}).
|
||||
|
||||
-xml(xdata_field_required,
|
||||
#elem{name = <<"required">>,
|
||||
@ -1770,6 +1838,7 @@
|
||||
refs = [#ref{name = shim_header, label = '$headers'}]}).
|
||||
|
||||
-record(chatstate, {type :: active | composing | gone | inactive | paused}).
|
||||
-type chatstate() :: #chatstate{}.
|
||||
|
||||
-xml(chatstate_active,
|
||||
#elem{name = <<"active">>,
|
||||
@ -1799,7 +1868,8 @@
|
||||
-xml(delay,
|
||||
#elem{name = <<"delay">>,
|
||||
xmlns = <<"urn:xmpp:delay">>,
|
||||
result = {delay, '$stamp', '$from'},
|
||||
result = {delay, '$stamp', '$from', '$desc'},
|
||||
cdata = #cdata{label = '$desc', default = <<"">>},
|
||||
attrs = [#attr{name = <<"stamp">>,
|
||||
required = true,
|
||||
dec = {dec_utc, []},
|
||||
@ -2263,6 +2333,7 @@
|
||||
attrs = [#attr{name = <<"xmlns">>}]}).
|
||||
|
||||
-record(csi, {type :: active | inactive}).
|
||||
-type csi() :: #csi{}.
|
||||
|
||||
-xml(csi_active,
|
||||
#elem{name = <<"active">>,
|
||||
@ -2432,38 +2503,123 @@
|
||||
|
||||
-xml(mix_subscribe,
|
||||
#elem{name = <<"subscribe">>,
|
||||
xmlns = <<"urn:xmpp:mix:0">>,
|
||||
result = '$node',
|
||||
attrs = [#attr{name = <<"node">>,
|
||||
required = true,
|
||||
label = '$node'}]}).
|
||||
xmlns = <<"urn:xmpp:mix:0">>,
|
||||
result = '$node',
|
||||
attrs = [#attr{name = <<"node">>,
|
||||
required = true,
|
||||
label = '$node'}]}).
|
||||
|
||||
-xml(mix_join,
|
||||
#elem{name = <<"join">>,
|
||||
xmlns = <<"urn:xmpp:mix:0">>,
|
||||
result = {mix_join, '$jid', '$subscribe'},
|
||||
attrs = [#attr{name = <<"jid">>,
|
||||
label = '$jid',
|
||||
dec = {dec_jid, []},
|
||||
xmlns = <<"urn:xmpp:mix:0">>,
|
||||
result = {mix_join, '$jid', '$subscribe'},
|
||||
attrs = [#attr{name = <<"jid">>,
|
||||
label = '$jid',
|
||||
dec = {dec_jid, []},
|
||||
enc = {enc_jid, []}}],
|
||||
refs = [#ref{name = mix_subscribe, min = 0, label = '$subscribe'}]}).
|
||||
refs = [#ref{name = mix_subscribe, min = 0, label = '$subscribe'}]}).
|
||||
|
||||
-xml(mix_leave,
|
||||
#elem{name = <<"leave">>,
|
||||
xmlns = <<"urn:xmpp:mix:0">>,
|
||||
result = {mix_leave}}).
|
||||
xmlns = <<"urn:xmpp:mix:0">>,
|
||||
result = {mix_leave}}).
|
||||
|
||||
-xml(mix_participant,
|
||||
#elem{name = <<"participant">>,
|
||||
xmlns = <<"urn:xmpp:mix:0">>,
|
||||
result = {mix_participant, '$jid', '$nick'},
|
||||
attrs = [#attr{name = <<"jid">>,
|
||||
required = true,
|
||||
label = '$jid',
|
||||
dec = {dec_jid, []},
|
||||
xmlns = <<"urn:xmpp:mix:0">>,
|
||||
result = {mix_participant, '$jid', '$nick'},
|
||||
attrs = [#attr{name = <<"jid">>,
|
||||
required = true,
|
||||
label = '$jid',
|
||||
dec = {dec_jid, []},
|
||||
enc = {enc_jid, []}},
|
||||
#attr{name = <<"nick">>,
|
||||
label = '$nick'}]}).
|
||||
#attr{name = <<"nick">>,
|
||||
label = '$nick'}]}).
|
||||
|
||||
-record(hint, {type :: 'no-copy' | 'no-store' | 'store' | 'no-permanent-store'}).
|
||||
-type hint() :: #hint{}.
|
||||
|
||||
-xml(hint_no_copy,
|
||||
#elem{name = <<"no-copy">>,
|
||||
xmlns = <<"urn:xmpp:hints">>,
|
||||
result = {hint, 'no-copy'}}).
|
||||
|
||||
-xml(hint_no_store,
|
||||
#elem{name = <<"no-store">>,
|
||||
xmlns = <<"urn:xmpp:hints">>,
|
||||
result = {hint, 'no-store'}}).
|
||||
|
||||
-xml(hint_store,
|
||||
#elem{name = <<"store">>,
|
||||
xmlns = <<"urn:xmpp:hints">>,
|
||||
result = {hint, 'store'}}).
|
||||
|
||||
-xml(hint_no_permanent_store,
|
||||
#elem{name = <<"no-permanent-store">>,
|
||||
xmlns = <<"urn:xmpp:hints">>,
|
||||
result = {hint, 'no-permanent-store'}}).
|
||||
|
||||
-xml(search_instructions,
|
||||
#elem{name = <<"instructions">>,
|
||||
xmlns = <<"jabber:iq:search">>,
|
||||
result = '$cdata'}).
|
||||
|
||||
-xml(search_first,
|
||||
#elem{name = <<"first">>,
|
||||
xmlns = <<"jabber:iq:search">>,
|
||||
cdata = #cdata{default = <<"">>},
|
||||
result = '$cdata'}).
|
||||
-xml(search_last,
|
||||
#elem{name = <<"last">>,
|
||||
xmlns = <<"jabber:iq:search">>,
|
||||
cdata = #cdata{default = <<"">>},
|
||||
result = '$cdata'}).
|
||||
-xml(search_nick,
|
||||
#elem{name = <<"nick">>,
|
||||
xmlns = <<"jabber:iq:search">>,
|
||||
cdata = #cdata{default = <<"">>},
|
||||
result = '$cdata'}).
|
||||
-xml(search_email,
|
||||
#elem{name = <<"email">>,
|
||||
xmlns = <<"jabber:iq:search">>,
|
||||
cdata = #cdata{default = <<"">>},
|
||||
result = '$cdata'}).
|
||||
|
||||
-xml(search_item,
|
||||
#elem{name = <<"item">>,
|
||||
xmlns = <<"jabber:iq:search">>,
|
||||
result = {search_item, '$jid', '$first', '$last', '$nick', '$email'},
|
||||
attrs = [#attr{name = <<"jid">>,
|
||||
required = true,
|
||||
enc = {enc_jid, []},
|
||||
dec = {dec_jid, []}}],
|
||||
refs = [#ref{name = search_first, min = 0, max = 1,
|
||||
label = '$first'},
|
||||
#ref{name = search_last, min = 0, max = 1,
|
||||
label = '$last'},
|
||||
#ref{name = search_nick, min = 0, max = 1,
|
||||
label = '$nick'},
|
||||
#ref{name = search_email, min = 0, max = 1,
|
||||
label = '$email'}]}).
|
||||
|
||||
-xml(search,
|
||||
#elem{name = <<"query">>,
|
||||
xmlns = <<"jabber:iq:search">>,
|
||||
result = {search, '$instructions', '$first', '$last',
|
||||
'$nick', '$email', '$items', '$xdata'},
|
||||
refs = [#ref{name = search_instructions, min = 0, max = 1,
|
||||
label = '$instructions'},
|
||||
#ref{name = search_first, min = 0, max = 1,
|
||||
label = '$first'},
|
||||
#ref{name = search_last, min = 0, max = 1,
|
||||
label = '$last'},
|
||||
#ref{name = search_nick, min = 0, max = 1,
|
||||
label = '$nick'},
|
||||
#ref{name = search_email, min = 0, max = 1,
|
||||
label = '$email'},
|
||||
#ref{name = search_item, label = '$items'},
|
||||
#ref{name = xdata, min = 0, max = 1,
|
||||
label = '$xdata'}]}).
|
||||
|
||||
dec_tzo(Val) ->
|
||||
[H1, M1] = str:tokens(Val, <<":">>),
|
||||
@ -2514,6 +2670,10 @@ dec_bool(<<"1">>) -> true.
|
||||
enc_bool(false) -> <<"false">>;
|
||||
enc_bool(true) -> <<"true">>.
|
||||
|
||||
join([], _Sep) -> <<>>;
|
||||
join([H | T], Sep) ->
|
||||
<<H/binary, (<< <<Sep, X/binary>> || X <- T >>)/binary>>.
|
||||
|
||||
%% Local Variables:
|
||||
%% mode: erlang
|
||||
%% End:
|
||||
|
Loading…
Reference in New Issue
Block a user