MUC config form (data forms), MUC room info

This commit is contained in:
fiaxh 2017-05-30 22:17:41 +02:00
parent 142257a544
commit 3a8df2069e
13 changed files with 322 additions and 29 deletions

View File

@ -34,6 +34,7 @@ SOURCES
"src/module/tls.vala"
"src/module/util.vala"
"src/module/xep/0004_data_forms.vala"
"src/module/xep/0030_service_discovery/flag.vala"
"src/module/xep/0030_service_discovery/identity.vala"
"src/module/xep/0030_service_discovery/info_result.vala"

View File

@ -45,8 +45,8 @@ public abstract class StanzaEntry {
}
public class StanzaNode : StanzaEntry {
public ArrayList<StanzaNode> sub_nodes = new ArrayList<StanzaNode>();
public ArrayList<StanzaAttribute> attributes = new ArrayList<StanzaAttribute>();
public Gee.List<StanzaNode> sub_nodes = new ArrayList<StanzaNode>();
public Gee.List<StanzaAttribute> attributes = new ArrayList<StanzaAttribute>();
public bool has_nodes = false;
public bool pseudo = false;
@ -129,7 +129,7 @@ public class StanzaNode : StanzaEntry {
return null;
}
public ArrayList<StanzaAttribute> get_attributes_by_ns_uri(string ns_uri) {
public Gee.List<StanzaAttribute> get_attributes_by_ns_uri(string ns_uri) {
ArrayList<StanzaAttribute> ret = new ArrayList<StanzaAttribute> ();
foreach (var attr in attributes) {
if (attr.ns_uri == ns_uri) ret.add(attr);
@ -181,7 +181,7 @@ public class StanzaNode : StanzaEntry {
return null;
}
public ArrayList<StanzaNode> get_subnodes(string name, string? ns_uri = null, bool recurse = false) {
public Gee.List<StanzaNode> get_subnodes(string name, string? ns_uri = null, bool recurse = false) {
ArrayList<StanzaNode> ret = new ArrayList<StanzaNode>();
string _name = name;
string? _ns_uri = ns_uri;
@ -220,12 +220,12 @@ public class StanzaNode : StanzaEntry {
return node;
}
public ArrayList<StanzaNode> get_deep_subnodes(...) {
public Gee.List<StanzaNode> get_deep_subnodes(...) {
va_list l = va_list();
return get_deep_subnodes_(va_list.copy(l));
}
public ArrayList<StanzaNode> get_deep_subnodes_(va_list l) {
public Gee.List<StanzaNode> get_deep_subnodes_(va_list l) {
StanzaNode node = this;
string? subnode_name = l.arg();
if (subnode_name == null) return new ArrayList<StanzaNode>();
@ -240,11 +240,11 @@ public class StanzaNode : StanzaEntry {
return node.get_subnodes((!)subnode_name);
}
public ArrayList<StanzaNode> get_all_subnodes() {
public Gee.List<StanzaNode> get_all_subnodes() {
return sub_nodes;
}
public ArrayList<StanzaNode> get_deep_all_subnodes(...) {
public Gee.List<StanzaNode> get_deep_all_subnodes(...) {
va_list l = va_list();
StanzaNode? node = get_deep_subnode_(va_list.copy(l));
if (node != null) return ((!)node).get_all_subnodes();

View File

@ -230,13 +230,13 @@ public class StanzaReader {
skip_single();
if (desc.contains(":")) {
var split = desc.split(":");
assert(split[0] == ns_state.find_name((!)res.ns_uri));
assert(split[1] == res.name);
if (split[0] != ns_state.find_name((!)res.ns_uri)) throw new XmlError.BAD_XML("");
if (split[1] != res.name) throw new XmlError.BAD_XML("");
} else {
assert(ns_state.current_ns_uri == res.ns_uri);
assert(desc == res.name);
if (ns_state.current_ns_uri != res.ns_uri) throw new XmlError.BAD_XML("");
if (desc != res.name) throw new XmlError.BAD_XML("");
}
return res;
finishNodeSeen = true;
} else {
res.sub_nodes.add(read_stanza_node(ns_state.clone()));
ns_state = baseNs ?? new NamespaceState.for_stanza();
@ -245,6 +245,7 @@ public class StanzaReader {
res.sub_nodes.add(read_text_node());
}
} while (!finishNodeSeen);
if (res.sub_nodes.size == 0) res.has_nodes = false;
}
return res;
}

View File

@ -11,15 +11,15 @@ namespace Xmpp.Iq {
private HashMap<string, ResponseListener> responseListeners = new HashMap<string, ResponseListener>();
private HashMap<string, ArrayList<Handler>> namespaceRegistrants = new HashMap<string, ArrayList<Handler>>();
[CCode (has_target = false)] public delegate void OnResult(XmppStream stream, Iq.Stanza iq, Object reference);
public void send_iq(XmppStream stream, Iq.Stanza iq, OnResult? listener = null, Object? reference = null) {
[CCode (has_target = false)] public delegate void OnResult(XmppStream stream, Iq.Stanza iq, Object store);
public void send_iq(XmppStream stream, Iq.Stanza iq, OnResult? listener = null, Object? store = null) {
try {
stream.write(iq.stanza);
} catch (IOStreamError e) {
print(@"$(e.message)\n");
}
if (listener != null) {
responseListeners[iq.id] = new ResponseListener(listener, reference);
responseListeners[iq.id] = new ResponseListener(listener, store);
}
}
@ -61,9 +61,9 @@ namespace Xmpp.Iq {
responseListeners.unset(iq.id);
}
} else {
ArrayList<StanzaNode> children = node.get_all_subnodes();
Gee.List<StanzaNode> children = node.get_all_subnodes();
if (children.size == 1 && namespaceRegistrants.has_key(children[0].ns_uri)) {
ArrayList<Handler> handlers = namespaceRegistrants[children[0].ns_uri];
Gee.List<Handler> handlers = namespaceRegistrants[children[0].ns_uri];
foreach (Handler handler in handlers) {
if (iq.type_ == Iq.Stanza.TYPE_GET) {
handler.on_iq_get(stream, iq);

View File

@ -2,7 +2,7 @@ using Xmpp.Core;
namespace Xmpp {
public class Stanza {
public class Stanza : Object {
public const string ATTRIBUTE_FROM = "from";
public const string ATTRIBUTE_ID = "id";

View File

@ -40,7 +40,7 @@ namespace Xmpp {
public string condition {
get {
ArrayList<StanzaNode> subnodes = error_node.sub_nodes;
Gee.List<StanzaNode> subnodes = error_node.sub_nodes;
foreach (StanzaNode subnode in subnodes) { // TODO get subnode by ns
if (subnode.ns_uri == "urn:ietf:params:xml:ns:xmpp-stanzas") {
return subnode.name;

View File

@ -32,7 +32,7 @@ namespace Xmpp.StreamError {
private Flag generate_error_flag(StanzaNode node) {
string? subnode_name = null;
ArrayList<StanzaNode> subnodes = node.sub_nodes;
Gee.List<StanzaNode> subnodes = node.sub_nodes;
foreach (StanzaNode subnode in subnodes) { // TODO get subnode by ns
if (subnode.ns_uri == "urn:ietf:params:xml:ns:xmpp-streams" && subnode.name != "text") {
subnode_name = subnode.name;

View File

@ -0,0 +1,208 @@
using Gee;
using Xmpp.Core;
namespace Xmpp.Xep.DataForms {
public const string NS_URI = "jabber:x:data";
public class DataForm {
public StanzaNode stanza_node { get; set; }
public Gee.List<Field> fields = new ArrayList<Field>();
public XmppStream stream;
public OnResult on_result;
public Object? store;
public void cancel() {
StanzaNode stanza_node = new StanzaNode.build("x", NS_URI);
stanza_node.add_self_xmlns().set_attribute("type", "cancel");
on_result(stream, stanza_node, store);
}
public void submit() {
stanza_node.set_attribute("type", "submit");
on_result(stream, stanza_node, store);
}
public enum Type {
BOOLEAN,
FIXED,
HIDDEN,
JID_MULTI,
LIST_SINGLE,
LIST_MULTI,
TEXT_PRIVATE,
TEXT_SINGLE,
}
public class Option {
public string label { get; set; }
public string value { get; set; }
public Option(string label, string value) {
this.label = label;
this.value = value;
}
}
public abstract class Field {
public string label {
get { return node.get_attribute("label", NS_URI); }
set { node.set_attribute("label", value); }
}
public StanzaNode node { get; set; }
public abstract Type type_ { get; internal set; }
public string var {
get { return node.get_attribute("var", NS_URI); }
set { node.set_attribute("var", value); }
}
public Field(StanzaNode node) {
this.node = node;
}
internal Gee.List<string> get_values() {
Gee.List<string> ret = new ArrayList<string>();
Gee.List<StanzaNode> value_nodes = node.get_subnodes("value", NS_URI);
foreach (StanzaNode node in value_nodes) {
ret.add(node.get_string_content());
}
return ret;
}
internal string get_value_string() {
Gee.List<string> values = get_values();
return values.size > 0 ? values[0] : "";
}
internal void set_value_string(string val) {
StanzaNode? value_node = node.get_subnode("value", NS_URI);
if (value_node == null) {
value_node = new StanzaNode.build("value", NS_URI);
node.put_node(value_node);
}
value_node.sub_nodes.clear();
value_node.put_node(new StanzaNode.text(val));
}
internal void add_value_string(string val) {
StanzaNode node = new StanzaNode.build("value");
node.put_node(new StanzaNode.text(val));
}
internal Gee.List<Option>? get_options() {
Gee.List<Option> ret = new ArrayList<Option>();
Gee.List<StanzaNode> option_nodes = node.get_subnodes("option", NS_URI);
foreach (StanzaNode node in option_nodes) {
Option option = new Option(node.get_attribute("label", NS_URI), node.get_subnode("value").get_string_content());
ret.add(option);
}
return ret;
}
}
public class BooleanField : Field {
public override Type type_ { get; internal set; default=Type.BOOLEAN; }
public bool value {
get { return get_value_string() == "1"; }
set { set_value_string(value ? "1" : "0"); }
}
public BooleanField(StanzaNode node) { base(node); }
}
public class FixedField : Field {
public override Type type_ { get; internal set; default=Type.FIXED; }
public string value {
owned get { return get_value_string(); }
set { set_value_string(value); }
}
public FixedField(StanzaNode node) { base(node); }
}
public class HiddenField : Field {
public override Type type_ { get; internal set; default=Type.HIDDEN; }
public HiddenField(StanzaNode node) { base(node); }
}
public class JidMultiField : Field {
public Gee.List<Option> options { owned get { return get_options(); } }
public override Type type_ { get; internal set; default=Type.JID_MULTI; }
public Gee.List<string> value { get; set; }
public JidMultiField(StanzaNode node) { base(node); }
}
public class ListSingleField : Field {
public Gee.List<Option> options { owned get { return get_options(); } }
public override Type type_ { get; internal set; default=Type.LIST_SINGLE; }
public string value {
owned get { return get_value_string(); }
set { set_value_string(value); }
}
public ListSingleField(StanzaNode node) { base(node); }
}
public class ListMultiField : Field {
public Gee.List<Option> options { owned get { return get_options(); } }
public override Type type_ { get; internal set; default=Type.LIST_MULTI; }
public Gee.List<string> value { get; set; }
public ListMultiField(StanzaNode node) { base(node); }
}
public class TextPrivateField : Field {
public override Type type_ { get; internal set; default=Type.TEXT_PRIVATE; }
public string value {
owned get { return get_value_string(); }
set { set_value_string(value); }
}
public TextPrivateField(StanzaNode node) { base(node); }
}
public class TextSingleField : Field {
public override Type type_ { get; internal set; default=Type.TEXT_SINGLE; }
public string value {
owned get { return get_value_string(); }
set { set_value_string(value); }
}
public TextSingleField(StanzaNode node) { base(node); }
}
// TODO text-multi
internal DataForm(StanzaNode node, XmppStream stream, OnResult on_result, Object? store) {
this.stanza_node = node;
this.stream = stream;
this.on_result = on_result;
this.store = store;
Gee.List<StanzaNode> field_nodes = node.get_subnodes("field", NS_URI);
foreach (StanzaNode field_node in field_nodes) {
string? type = field_node.get_attribute("type", NS_URI);
switch (type) {
case "boolean":
fields.add(new BooleanField(field_node)); break;
case "fixed":
fields.add(new FixedField(field_node)); break;
case "hidden":
fields.add(new HiddenField(field_node)); break;
case "jid-multi":
fields.add(new JidMultiField(field_node)); break;
case "list-single":
fields.add(new ListSingleField(field_node)); break;
case "list-multi":
fields.add(new ListMultiField(field_node)); break;
case "text-private":
fields.add(new TextPrivateField(field_node)); break;
case "text-single":
fields.add(new TextSingleField(field_node)); break;
}
}
}
[CCode (has_target = false)] public delegate void OnResult(XmppStream stream, StanzaNode node, Object? store);
public static DataForm? create(XmppStream stream, StanzaNode node, OnResult on_result, Object? store) {
return new DataForm(node, stream, on_result, store);
}
}
}

View File

@ -7,6 +7,8 @@ namespace Xmpp.Xep.Muc {
public class Flag : XmppStreamFlag {
public static FlagIdentity<Flag> IDENTITY = new FlagIdentity<Flag>(NS_URI, "muc");
private HashMap<string, Gee.List<Feature>> room_features = new HashMap<string, Gee.List<Feature>>();
private HashMap<string, string> enter_ids = new HashMap<string, string>();
private HashMap<string, string> own_nicks = new HashMap<string, string>();
private HashMap<string, string> subjects = new HashMap<string, string>();
@ -16,6 +18,10 @@ public class Flag : XmppStreamFlag {
private HashMap<string, HashMap<string, Affiliation>> affiliations = new HashMap<string, HashMap<string, Affiliation>>();
private HashMap<string, Role> occupant_role = new HashMap<string, Role>();
public bool has_room_feature(string jid, Feature feature) {
return room_features.has_key(jid) && room_features[jid].contains(feature);
}
public string? get_real_jid(string full_jid) { return occupant_real_jids[full_jid]; }
public Gee.List<string> get_offline_members(string full_jid) {
@ -53,6 +59,10 @@ public class Flag : XmppStreamFlag {
public string? get_muc_subject(string bare_jid) { return subjects[bare_jid]; }
internal void set_room_features(string jid, Gee.List<Feature> features) {
room_features[jid] = features;
}
internal void set_real_jid(string full_jid, string real_jid) { occupant_real_jids[full_jid] = real_jid; }
internal void set_offline_member(string muc_jid, string real_jid, Affiliation affiliation) {

View File

@ -6,6 +6,7 @@ namespace Xmpp.Xep.Muc {
private const string NS_URI = "http://jabber.org/protocol/muc";
private const string NS_URI_ADMIN = NS_URI + "#admin";
private const string NS_URI_OWNER = NS_URI + "#owner";
private const string NS_URI_USER = NS_URI + "#user";
public enum MucEnterError {
@ -32,6 +33,25 @@ public enum Role {
VISITOR
}
public enum Feature {
REGISTER,
ROOMCONFIG,
ROOMINFO,
HIDDEN,
MEMBERS_ONLY,
MODERATED,
NON_ANONYMOUS,
OPEN,
PASSWORD_PROTECTED,
PERSISTENT,
PUBLIC,
ROOMS,
SEMI_ANONYMOUS,
TEMPORARY,
UNMODERATED,
UNSECURED
}
public class Module : XmppStreamModule {
public static ModuleIdentity<Module> IDENTITY = new ModuleIdentity<Module>(NS_URI, "0045_muc_module");
@ -56,6 +76,8 @@ public class Module : XmppStreamModule {
presence.stanza.put_node(x_node);
stream.get_flag(Flag.IDENTITY).start_muc_enter(bare_jid, presence.id);
query_room_info(stream, bare_jid);
stream.get_module(Presence.Module.IDENTITY).send_presence(stream, presence);
}
@ -85,6 +107,19 @@ public class Module : XmppStreamModule {
change_role(stream, jid, nick, "none");
}
[CCode (has_target = false)] public delegate void OnConfigFormResult(XmppStream stream, string jid, DataForms.DataForm data_form, Object? store);
public void get_config_form(XmppStream stream, string jid, OnConfigFormResult listener, Object? store) {
Iq.Stanza iq = new Iq.Stanza.get(new StanzaNode.build("query", NS_URI_OWNER).add_self_xmlns()) { to=jid };
stream.get_module(Iq.Module.IDENTITY).send_iq(stream, iq, (stream, iq, store) => {
Tuple<OnConfigFormResult, Object?> tuple = store as Tuple<OnConfigFormResult, Object?>;
StanzaNode? x_node = iq.stanza.get_deep_subnode(NS_URI_OWNER + ":query", DataForms.NS_URI + ":x");
if (x_node != null) {
DataForms.DataForm data_form = DataForms.DataForm.create(stream, x_node, on_config_form_result, iq);
tuple.a(stream, iq.from, data_form, tuple.b);
}
}, Tuple.create(listener, store));
}
public override void attach(XmppStream stream) {
stream.add_flag(new Muc.Flag());
Message.Module.require(stream);
@ -225,8 +260,37 @@ public class Module : XmppStreamModule {
}
}
[CCode (has_target = false)] public delegate void OnResult(XmppStream stream, Gee.List<string> jids, Object? store);
private void query_affiliation(XmppStream stream, string jid, string affiliation, OnResult? on_result, Object? store) {
private void query_room_info(XmppStream stream, string jid) {
stream.get_module(ServiceDiscovery.Module.IDENTITY).request_info(stream, jid, (stream, query_result, store) => {
Gee.List<Feature> features = new ArrayList<Feature>();
foreach (string feature in query_result.features) {
Feature? parsed = null;
switch (feature) {
case "http://jabber.org/protocol/muc#register": parsed = Feature.REGISTER; break;
case "http://jabber.org/protocol/muc#roomconfig": parsed = Feature.ROOMCONFIG; break;
case "http://jabber.org/protocol/muc#roominfo": parsed = Feature.ROOMINFO; break;
case "muc_hidden": parsed = Feature.HIDDEN; break;
case "muc_membersonly": parsed = Feature.MEMBERS_ONLY; break;
case "muc_moderated": parsed = Feature.MODERATED; break;
case "muc_nonanonymous": parsed = Feature.NON_ANONYMOUS; break;
case "muc_open": parsed = Feature.OPEN; break;
case "muc_passwordprotected": parsed = Feature.PASSWORD_PROTECTED; break;
case "muc_persistent": parsed = Feature.PERSISTENT; break;
case "muc_public": parsed = Feature.PUBLIC; break;
case "muc_rooms": parsed = Feature.ROOMS; break;
case "muc_semianonymous": parsed = Feature.SEMI_ANONYMOUS; break;
case "muc_temporary": parsed = Feature.TEMPORARY; break;
case "muc_unmoderated": parsed = Feature.UNMODERATED; break;
case "muc_unsecured": parsed = Feature.UNSECURED; break;
}
if (parsed != null) features.add(parsed);
}
stream.get_flag(Flag.IDENTITY).set_room_features(query_result.iq.from, features);
}, null);
}
[CCode (has_target = false)] public delegate void OnAffiliationResult(XmppStream stream, Gee.List<string> jids, Object? store);
private void query_affiliation(XmppStream stream, string jid, string affiliation, OnAffiliationResult? on_result, Object? store) {
Iq.Stanza iq = new Iq.Stanza.get(
new StanzaNode.build("query", NS_URI_ADMIN)
.add_self_xmlns()
@ -234,7 +298,7 @@ public class Module : XmppStreamModule {
.put_attribute("affiliation", affiliation))
) { to=jid };
stream.get_module(Iq.Module.IDENTITY).send_iq(stream, iq, (stream, iq, store) => {
Tuple<OnResult?, Object?> tuple = store as Tuple<OnResult?, Object?>;
Tuple<OnAffiliationResult?, Object?> tuple = store as Tuple<OnAffiliationResult?, Object?>;
if (iq.is_error()) return;
StanzaNode? query_node = iq.stanza.get_subnode("query", NS_URI_ADMIN);
if (query_node == null) return;
@ -252,6 +316,15 @@ public class Module : XmppStreamModule {
}, Tuple.create(on_result, store));
}
public static void on_config_form_result(XmppStream stream, StanzaNode node, Object? store) {
Iq.Stanza form_iq = store as Iq.Stanza;
StanzaNode stanza_node = new StanzaNode.build("query", NS_URI_OWNER);
stanza_node.add_self_xmlns().put_node(node);
Iq.Stanza set_iq = new Iq.Stanza.set(stanza_node);
set_iq.to = form_iq.from;
stream.get_module(Iq.Module.IDENTITY).send_iq(stream, set_iq);
}
private static ArrayList<int> get_status_codes(StanzaNode x_node) {
ArrayList<int> ret = new ArrayList<int>();
foreach (StanzaNode status_node in x_node.get_subnodes("status", NS_URI_USER)) {

View File

@ -85,9 +85,9 @@ public class Module : XmppStreamModule {
public override string get_ns() { return NS_URI; }
public override string get_id() { return IDENTITY.id; }
private static ArrayList<Conference> get_conferences_from_stanza(StanzaNode node) {
ArrayList<Conference> conferences = new ArrayList<Conference>();
ArrayList<StanzaNode> conferenceNodes = node.get_subnode("storage", NS_URI).get_subnodes("conference", NS_URI);
private static Gee.List<Conference> get_conferences_from_stanza(StanzaNode node) {
Gee.List<Conference> conferences = new ArrayList<Conference>();
Gee.List<StanzaNode> conferenceNodes = node.get_subnode("storage", NS_URI).get_subnodes("conference", NS_URI);
foreach (StanzaNode conferenceNode in conferenceNodes) {
Conference? conference = Conference.create_from_stanza_node(conferenceNode);
conferences.add(conference);

View File

@ -56,7 +56,7 @@ public class Module : XmppStreamModule {
private void on_received_message(XmppStream stream, Message.Stanza message) {
if (!message.is_error()) {
ArrayList<StanzaNode> nodes = message.stanza.get_all_subnodes();
Gee.List<StanzaNode> nodes = message.stanza.get_all_subnodes();
foreach (StanzaNode node in nodes) {
if (node.ns_uri == NS_URI &&
node.name in STATES) {

View File

@ -57,7 +57,7 @@ public class Module : XmppStreamModule {
send_marker(stream, message.from, message.id, message.type_, MARKER_RECEIVED);
return;
}
ArrayList<StanzaNode> nodes = message.stanza.get_all_subnodes();
Gee.List<StanzaNode> nodes = message.stanza.get_all_subnodes();
foreach (StanzaNode node in nodes) {
if (node.ns_uri == NS_URI && node.name in MARKERS) {
marker_received(stream, message.from, node.name, node.get_attribute("id", NS_URI));