diff options
Diffstat (limited to 'vala-xmpp/src/core')
-rw-r--r-- | vala-xmpp/src/core/namespace_state.vala | 80 | ||||
-rw-r--r-- | vala-xmpp/src/core/stanza_attribute.vala | 29 | ||||
-rw-r--r-- | vala-xmpp/src/core/stanza_node.vala | 297 | ||||
-rw-r--r-- | vala-xmpp/src/core/stanza_reader.vala | 260 | ||||
-rw-r--r-- | vala-xmpp/src/core/stanza_writer.vala | 29 | ||||
-rw-r--r-- | vala-xmpp/src/core/xmpp_stream.vala | 245 |
6 files changed, 940 insertions, 0 deletions
diff --git a/vala-xmpp/src/core/namespace_state.vala b/vala-xmpp/src/core/namespace_state.vala new file mode 100644 index 00000000..e71607fa --- /dev/null +++ b/vala-xmpp/src/core/namespace_state.vala @@ -0,0 +1,80 @@ +using Gee; + +namespace Xmpp.Core { +public class NamespaceState { + private HashMap<string, string> uri_to_name = new HashMap<string, string> (); + private HashMap<string, string> name_to_uri = new HashMap<string, string> (); + public string current_ns_uri; + + public NamespaceState () { + add_assoc(XMLNS_URI, "xmlns"); + add_assoc("http://www.w3.org/XML/1998/namespace", "xml"); + current_ns_uri = "http://www.w3.org/XML/1998/namespace"; + } + + public NamespaceState.for_stanza () { + this(); + add_assoc("http://etherx.jabber.org/streams", "stream"); + current_ns_uri = "jabber:client"; + } + + public NamespaceState.copy (NamespaceState old) { + foreach (string key in old.uri_to_name.keys) { + add_assoc(key, old.uri_to_name[key]); + } + set_current(old.current_ns_uri); + } + + public NamespaceState.with_assoc (NamespaceState old, string ns_uri, string name) { + this.copy(old); + add_assoc(ns_uri, name); + } + + public NamespaceState.with_current (NamespaceState old, string current_ns_uri) { + this.copy(old); + set_current(current_ns_uri); + } + + public void add_assoc (string ns_uri, string name) { + name_to_uri[name] = ns_uri; + uri_to_name[ns_uri] = name; + } + + public void set_current (string current_ns_uri) { + this.current_ns_uri = current_ns_uri; + } + + public string find_name (string ns_uri) throws XmlError { + if (uri_to_name.has_key(ns_uri)) { + return uri_to_name[ns_uri]; + } + throw new XmlError.NS_DICT_ERROR(@"NS URI $ns_uri not found."); + } + + public string find_uri (string name) throws XmlError { + if (name_to_uri.has_key(name)) { + return name_to_uri[name]; + } + throw new XmlError.NS_DICT_ERROR(@"NS name $name not found."); + } + + public NamespaceState clone() { + return new NamespaceState.copy(this); + } + + public string to_string () { + StringBuilder sb = new StringBuilder (); + sb.append ("NamespaceState{"); + foreach (string key in uri_to_name.keys) { + sb.append(key); + sb.append_c('='); + sb.append(uri_to_name[key]); + sb.append_c(','); + } + sb.append("current="); + sb.append(current_ns_uri); + sb.append_c('}'); + return sb.str; + } +} +}
\ No newline at end of file diff --git a/vala-xmpp/src/core/stanza_attribute.vala b/vala-xmpp/src/core/stanza_attribute.vala new file mode 100644 index 00000000..3169e90e --- /dev/null +++ b/vala-xmpp/src/core/stanza_attribute.vala @@ -0,0 +1,29 @@ +namespace Xmpp.Core { +public class StanzaAttribute : StanzaEntry { + + public StanzaAttribute() {} + + public StanzaAttribute.build(string ns_uri, string name, string val) { + this.ns_uri = ns_uri; + this.name = name; + this.val = val; + } + + public string to_string() { + if (ns_uri == null) { + return @"$name='$val'"; + } else { + return @"{$ns_uri}:$name='$val'"; + } + } + + public string to_xml(NamespaceState? state_) throws XmlError { + NamespaceState state = state_ ?? new NamespaceState(); + if (ns_uri == state.current_ns_uri || (ns_uri == XMLNS_URI && name == "xmlns")) { + return @"$name='$val'"; + } else { + return "%s:%s='%s'".printf (state.find_name (ns_uri), name, val); + } + } +} +} diff --git a/vala-xmpp/src/core/stanza_node.vala b/vala-xmpp/src/core/stanza_node.vala new file mode 100644 index 00000000..1dfacfdd --- /dev/null +++ b/vala-xmpp/src/core/stanza_node.vala @@ -0,0 +1,297 @@ +using Gee; + +namespace Xmpp.Core { + +public abstract class StanzaEntry { + public string? ns_uri; + public string name; + public string? val; + + public string encoded_val { + owned get { + return val.replace("&", "&").replace("\"", """).replace("'", "'").replace("<", "<").replace(">", ">"); + } + set { + string tmp = value.replace(">", ">").replace("<", "<").replace("'","'").replace(""","\""); + while (tmp.contains("&#")) { + int start = tmp.index_of("&#"); + int end = tmp.index_of(";", start); + if (end < start) break; + unichar num = -1; + if (tmp[start+2]=='x') { + tmp.substring(start+3, start-end-3).scanf("%x", &num); + } else { + num = int.parse(tmp.substring(start+2, start-end-2)); + } + tmp = tmp.splice(start, end, num.to_string()); + } + val = tmp.replace("&", "&"); + } + } + + public virtual unowned string? get_string_content() { + return val; + } +} + +public class NoStanza : StanzaEntry { + public NoStanza(string? name) { + this.name = name; + } +} + +public class StanzaNode : StanzaEntry { + public ArrayList<StanzaNode> sub_nodes = new ArrayList<StanzaNode>(); + public ArrayList<StanzaAttribute> attributes = new ArrayList<StanzaAttribute>(); + public bool has_nodes = false; + public bool pseudo = false; + + public StanzaNode () {} + + public StanzaNode.build(string name, string ns_uri = "jabber:client", ArrayList<StanzaNode>? nodes = null, ArrayList<StanzaAttribute>? attrs = null) { + this.ns_uri = ns_uri; + this.name = name; + if (nodes != null) this.sub_nodes.add_all(nodes); + if (attrs != null) this.attributes.add_all(attrs); + } + + public StanzaNode.text(string text) { + this.name = "#text"; + this.val = text; + } + + public StanzaNode.encoded_text(string text) { + this.name = "#text"; + this.encoded_val = text; + } + + public StanzaNode add_self_xmlns() { + return put_attribute("xmlns", ns_uri); + } + + public unowned string? get_attribute(string name, string? ns_uri = null) { + string _name = name; + string? _ns_uri = ns_uri; + if (_ns_uri == null) { + if (_name.contains(":")) { + var lastIndex = _name.last_index_of_char(':'); + _ns_uri = _name.substring(0, lastIndex); + _name = _name.substring(lastIndex + 1); + } else { + _ns_uri = this.ns_uri; + } + } + foreach(var attr in attributes) { + if (attr.ns_uri == _ns_uri && attr.name == _name) return attr.val; + } + return null; + } + + public StanzaAttribute get_attribute_raw(string name, string? ns_uri = null) { + string _name = name; + string? _ns_uri = ns_uri; + if (_ns_uri == null) { + if (_name.contains(":")) { + var lastIndex = _name.last_index_of_char(':'); + _ns_uri = _name.substring(0, lastIndex); + _name = _name.substring(lastIndex + 1); + } else { + _ns_uri = this.ns_uri; + } + } + foreach(var attr in attributes) { + if (attr.ns_uri == _ns_uri && attr.name == _name) return attr; + } + return null; + } + + public ArrayList<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); + } + return ret; + } + + public StanzaEntry get(...) { + va_list l = va_list(); + StanzaEntry? res = get_deep_attribute_(va_list.copy(l)); + if (res != null) return res; + res = get_deep_subnode_(va_list.copy(l)); + if (res != null) return res; + return new NoStanza("-"); + } + + public unowned string? get_deep_attribute(...) { + va_list l = va_list(); + var res = get_deep_attribute_(va_list.copy(l)); + if (res == null) return null; + return res.val; + } + + public StanzaAttribute? get_deep_attribute_(va_list l) { + StanzaNode? node = this; + string? attribute_name = l.arg(); + if (attribute_name == null) return null; + while(true) { + string? s = l.arg(); + if (s == null) break; + node = get_subnode(attribute_name); + if (node == null) return null; + attribute_name = s; + } + return node.get_attribute_raw(attribute_name); + } + + public StanzaNode? get_subnode(string name, string? ns_uri = null, bool recurse = false) { + string _name = name; + string? _ns_uri = ns_uri; + if (ns_uri == null) { + if (_name.contains(":")) { + var lastIndex = _name.last_index_of_char(':'); + _ns_uri = _name.substring(0, lastIndex); + _name = _name.substring(lastIndex + 1); + } else { + _ns_uri = this.ns_uri; + } + } + foreach(var node in sub_nodes) { + if (node.ns_uri == _ns_uri && node.name == _name) return node; + if (recurse) { + var x = node.get_subnode(_name, _ns_uri, recurse); + if (x != null) return x; + } + } + return null; + } + + public ArrayList<StanzaNode> get_subnodes(string name, string? ns_uri = null, bool recurse = false) { + ArrayList<StanzaNode> ret = new ArrayList<StanzaNode>(); + if (ns_uri == null) ns_uri = this.ns_uri; + foreach(var node in sub_nodes) { + if (node.ns_uri == ns_uri && node.name == name) ret.add(node); + if (recurse) { + ret.add_all(node.get_subnodes(name, ns_uri, recurse)); + } + } + return ret; + } + + public StanzaNode? get_deep_subnode(...) { + va_list l = va_list(); + return get_deep_subnode_(va_list.copy(l)); + } + + public StanzaNode? get_deep_subnode_(va_list l) { + StanzaNode? node = this; + while(true) { + string? s = l.arg(); + if (s == null) break; + node = get_subnode(s); + } + return node; + } + + public ArrayList<StanzaNode> get_all_subnodes() { + return sub_nodes; + } + + public void add_attribute(StanzaAttribute attr) { + attributes.add(attr); + } + + public override unowned string? get_string_content() { + if (val != null) return val; + if (sub_nodes.size == 1) return sub_nodes[0].get_string_content(); + return null; + } + + public StanzaNode put_attribute(string name, string val, string? ns_uri = null) { + if (name == "xmlns") ns_uri = XMLNS_URI; + if (ns_uri == null) ns_uri = this.ns_uri; + attributes.add(new StanzaAttribute.build(ns_uri, name, val)); + return this; + } + + /** + * Set only occurence + **/ + public void set_attribute(string name, string val, string? ns_uri = null) { + if (ns_uri == null) ns_uri = this.ns_uri; + foreach(var attr in attributes) { + if (attr.ns_uri == ns_uri && attr.name == name) { + attr.val = val; + return; + } + } + put_attribute(name, val, ns_uri); + } + + public StanzaNode put_node(StanzaNode node) { + sub_nodes.add(node); + return this; + } + + public string to_string(int i = 0) { + string indent = string.nfill (i * 2, ' '); + if (name == "#text") { + return @"$indent$val\n"; + } + var sb = new StringBuilder(); + sb.append(@"$indent<{$ns_uri}:$name"); + foreach (StanzaAttribute attr in attributes) { + sb.append_printf(" %s", attr.to_string()); + } + if (!has_nodes && sub_nodes.size == 0) { + sb.append(" />\n"); + } else { + sb.append(">\n"); + if (sub_nodes.size != 0) { + foreach (StanzaNode subnode in sub_nodes) { + sb.append(subnode.to_string(i+1)); + } + sb.append(@"$indent</{$ns_uri}:$name>\n"); + } + } + return sb.str; + } + + public string to_xml (NamespaceState? state = null) throws XmlError { + NamespaceState my_state = state ?? new NamespaceState.for_stanza(); + if (name == "#text") return @"$encoded_val"; + foreach(var xmlns in get_attributes_by_ns_uri (XMLNS_URI)) { + if (xmlns.name == "xmlns") { + my_state = new NamespaceState.with_current(my_state, xmlns.val); + } else { + my_state = new NamespaceState.with_assoc(my_state, xmlns.val, xmlns.name); + } + } + var sb = new StringBuilder(); + if (ns_uri == my_state.current_ns_uri) { + sb.append(@"<$name"); + } else { + sb.append_printf("<%s:%s", my_state.find_name (ns_uri), name); + } + var attr_ns_state = new NamespaceState.with_current(my_state, ns_uri); + foreach (StanzaAttribute attr in attributes) { + sb.append_printf(" %s", attr.to_xml(attr_ns_state)); + } + if (!has_nodes && sub_nodes.size == 0) { + sb.append("/>"); + } else { + sb.append(">"); + if (sub_nodes.size != 0) { + foreach (StanzaNode subnode in sub_nodes) { + sb.append(subnode.to_xml(my_state)); + } + if (ns_uri == my_state.current_ns_uri) { + sb.append(@"</$name>"); + } else { + sb.append_printf("</%s:%s>", my_state.find_name (ns_uri), name); + } + } + } + return sb.str; + } +} +} diff --git a/vala-xmpp/src/core/stanza_reader.vala b/vala-xmpp/src/core/stanza_reader.vala new file mode 100644 index 00000000..0a90f855 --- /dev/null +++ b/vala-xmpp/src/core/stanza_reader.vala @@ -0,0 +1,260 @@ +using Gee; + +namespace Xmpp.Core { +public const string XMLNS_URI = "http://www.w3.org/2000/xmlns/"; +public const string JABBER_URI = "jabber:client"; + +public errordomain XmlError { + XML_ERROR, + NS_DICT_ERROR, + UNSUPPORTED, + EOF, + BAD_XML, + IO_ERROR +} + +public class StanzaReader { + private static int BUFFER_MAX = 4096; + + private InputStream? input; + private uint8[] buffer; + private int buffer_fill = 0; + private int buffer_pos = 0; + private Cancellable cancellable = new Cancellable(); + + private NamespaceState ns_state = new NamespaceState(); + + public StanzaReader.for_buffer(uint8[] buffer) { + this.buffer = buffer; + this.buffer_fill = buffer.length; + } + + public StanzaReader.for_string(string s) { + this.for_buffer(s.data); + } + + public StanzaReader.for_stream(InputStream input) { + this.input = input; + buffer = new uint8[BUFFER_MAX]; + } + + public void cancel() { + cancellable.cancel(); + } + + private void update_buffer() throws XmlError { + try { + if (input == null) throw new XmlError.EOF("No input stream specified and end of buffer reached."); + if (cancellable.is_cancelled()) throw new XmlError.EOF("Input stream is canceled."); + buffer_fill = (int) input.read(buffer, cancellable); + if (buffer_fill == 0) throw new XmlError.EOF("End of input stream reached."); + buffer_pos = 0; + } catch (GLib.IOError e) { + throw new XmlError.IO_ERROR("IOError in GLib: %s".printf(e.message)); + } + } + + private char read_single() throws XmlError { + if (buffer_pos >= buffer_fill) { + update_buffer(); + } + return (char) buffer[buffer_pos++]; + } + + private char peek_single() throws XmlError { + var res = read_single(); + buffer_pos--; + return res; + } + + private bool is_ws(uint8 what) { + return what == ' ' || what == '\t' || what == '\r' || what == '\n'; + } + + private void skip_single() { + buffer_pos++; + } + + private void skip_until_non_ws() throws XmlError { + while (is_ws(peek_single())) { + skip_single(); + } + } + + private string read_until_ws() throws XmlError { + var res = new StringBuilder(); + var what = peek_single(); + while(!is_ws(what)) { + res.append_c(read_single()); + what = peek_single(); + } + return res.str; + } + + private string read_until_char_or_ws(char x, char y = 0) throws XmlError { + var res = new StringBuilder(); + var what = peek_single(); + while(what != x && what != y && !is_ws(what)) { + res.append_c(read_single()); + what = peek_single(); + } + return res.str; + } + + private string read_until_char(char x) throws XmlError { + var res = new StringBuilder(); + var what = peek_single(); + while(what != x) { + res.append_c(read_single()); + what = peek_single(); + } + return res.str; + } + + private StanzaAttribute read_attribute() throws XmlError { + var res = new StanzaAttribute(); + res.name = read_until_char_or_ws('='); + if (read_single() == '=') { + var quot = peek_single(); + if (quot == '\'' || quot == '"') { + skip_single(); + res.encoded_val = read_until_char(quot); + skip_single(); + } else { + res.encoded_val = read_until_ws(); + } + } + return res; + } + + private void handle_entry_ns(StanzaEntry entry, string default_uri = ns_state.current_ns_uri) throws XmlError { + if (entry.ns_uri != null) return; + if (entry.name.contains(":")) { + var split = entry.name.split(":"); + entry.ns_uri = ns_state.find_uri(split[0]); + entry.name = split[1]; + } else { + entry.ns_uri = default_uri; + } + } + + private void handle_stanza_ns(StanzaNode res) throws XmlError { + foreach (StanzaAttribute attr in res.attributes) { + if (attr.name == "xmlns") { + attr.ns_uri = XMLNS_URI; + ns_state.set_current(attr.val); + } else if (attr.name.contains(":")) { + var split = attr.name.split(":"); + if (split[0] == "xmlns") { + attr.ns_uri = XMLNS_URI; + attr.name = split[1]; + ns_state.add_assoc(attr.val, attr.name); + } + } + } + handle_entry_ns(res); + foreach (StanzaAttribute attr in res.attributes) { + handle_entry_ns(attr, res.ns_uri); + } + } + + public StanzaNode read_node_start() throws XmlError { + var res = new StanzaNode(); + res.attributes = new ArrayList<StanzaAttribute>(); + var eof = false; + if (peek_single() == '<') skip_single(); + if (peek_single() == '?') res.pseudo = true; + if (peek_single() == '/') { + eof = true; + skip_single(); + res.name = read_until_char_or_ws('>'); + while(peek_single() != '>') { + skip_single(); + } + skip_single(); + res.has_nodes = false; + res.pseudo = false; + handle_stanza_ns(res); + return res; + } + res.name = read_until_char_or_ws('>', '/'); + skip_until_non_ws(); + while (peek_single() != '/' && peek_single() != '>' && peek_single() != '?') { + res.attributes.add(read_attribute()); + skip_until_non_ws(); + } + if (read_single() == '/' || res.pseudo ) { + res.has_nodes = false; + skip_single(); + } else { + res.has_nodes = true; + } + handle_stanza_ns(res); + return res; + } + + public StanzaNode read_text_node() throws XmlError { + var res = new StanzaNode(); + res.name = "#text"; + res.ns_uri = ns_state.current_ns_uri; + res.encoded_val = read_until_char('<').strip(); + return res; + } + + public StanzaNode read_root_node() throws XmlError { + skip_until_non_ws(); + if (peek_single() == '<') { + var res = read_node_start(); + if (res.pseudo) { + return read_root_node(); + } + return res; + } else { + throw new XmlError.BAD_XML("Content before root node"); + } + } + + public StanzaNode read_stanza_node(NamespaceState? baseNs = null) throws XmlError { + ns_state = baseNs ?? new NamespaceState.for_stanza(); + var res = read_node_start(); + if (res.has_nodes) { + bool finishNodeSeen = false; + do { + skip_until_non_ws(); + if (peek_single() == '<') { + skip_single(); + if (peek_single() == '/') { + skip_single(); + string desc = read_until_char('>'); + skip_single(); + if (desc.contains(":")) { + var split = desc.split(":"); + assert(split[0] == ns_state.find_name(res.ns_uri)); + assert(split[1] == res.name); + } else { + assert(ns_state.current_ns_uri == res.ns_uri); + assert(desc == res.name); + } + return res; + } else { + res.sub_nodes.add(read_stanza_node(ns_state.clone())); + ns_state = baseNs ?? new NamespaceState.for_stanza(); + } + } else { + res.sub_nodes.add(read_text_node()); + } + } while (!finishNodeSeen); + } + return res; + } + + public StanzaNode read_node(NamespaceState? baseNs = null) throws XmlError { + skip_until_non_ws(); + if (peek_single() == '<') { + return read_stanza_node(baseNs ?? new NamespaceState.for_stanza()); + } else { + return read_text_node(); + } + } +} +} diff --git a/vala-xmpp/src/core/stanza_writer.vala b/vala-xmpp/src/core/stanza_writer.vala new file mode 100644 index 00000000..625f42e2 --- /dev/null +++ b/vala-xmpp/src/core/stanza_writer.vala @@ -0,0 +1,29 @@ +namespace Xmpp.Core { +public class StanzaWriter { + private OutputStream output; + + private NamespaceState ns_state = new NamespaceState(); + + public StanzaWriter.for_stream(OutputStream output) { + this.output = output; + } + + public void write_node(StanzaNode node) throws XmlError { + try { + lock(output) { + output.write_all(node.to_xml().data, null); + } + } catch (GLib.IOError e) { + throw new XmlError.IO_ERROR(@"IOError in GLib: $(e.message)"); + } + } + + public async void write(string s) throws XmlError { + try { + output.write_all(s.data, null); + } catch (GLib.IOError e) { + throw new XmlError.IO_ERROR(@"IOError in GLib: $(e.message)"); + } + } +} +}
\ No newline at end of file diff --git a/vala-xmpp/src/core/xmpp_stream.vala b/vala-xmpp/src/core/xmpp_stream.vala new file mode 100644 index 00000000..e30a1c9b --- /dev/null +++ b/vala-xmpp/src/core/xmpp_stream.vala @@ -0,0 +1,245 @@ +using Gee; + +namespace Xmpp.Core { + +public errordomain IOStreamError { + READ, + WRITE, + CONNECT, + DISCONNECT + +} + +public class XmppStream { + private static string NS_URI = "http://etherx.jabber.org/streams"; + + public string remote_name; + public bool debug = false; + public StanzaNode? features { get; private set; default = new StanzaNode.build("features", NS_URI); } + + private IOStream? stream; + private StanzaReader? reader; + private StanzaWriter? writer; + + private ArrayList<XmppStreamFlag> flags = new ArrayList<XmppStreamFlag>(); + private ArrayList<XmppStreamModule> modules = new ArrayList<XmppStreamModule>(); + private bool setup_needed = false; + private bool negotiation_complete = false; + + public signal void received_node(XmppStream stream, StanzaNode node); + public signal void received_root_node(XmppStream stream, StanzaNode node); + public signal void received_features_node(XmppStream stream); + public signal void received_message_stanza(XmppStream stream, StanzaNode node); + public signal void received_presence_stanza(XmppStream stream, StanzaNode node); + public signal void received_iq_stanza(XmppStream stream, StanzaNode node); + public signal void received_nonza(XmppStream stream, StanzaNode node); + public signal void stream_negotiated(XmppStream stream); + + public void connect(string? remote_name = null) throws IOStreamError { + if (remote_name != null) this.remote_name = remote_name; + SocketClient client = new SocketClient(); + try { + SocketConnection? stream = client.connect(new NetworkService("xmpp-client", "tcp", this.remote_name)); + if (stream == null) throw new IOStreamError.CONNECT("client.connect() returned null"); + reset_stream(stream); + } catch (Error e) { + stderr.printf("CONNECTION LOST?\n"); + throw new IOStreamError.CONNECT(e.message); + } + loop(); + } + + public void disconnect() throws IOStreamError { + if (writer == null) throw new IOStreamError.DISCONNECT("trying to disconnect, but no stream open"); + if (debug) stderr.puts("OUT\n</stream:stream>\n"); + writer.write.begin("</stream:stream>"); + reader.cancel(); + stream.close_async.begin(); + } + + public void reset_stream(IOStream stream) { + this.stream = stream; + reader = new StanzaReader.for_stream(stream.input_stream); + writer = new StanzaWriter.for_stream(stream.output_stream); + require_setup(); + } + + public void require_setup() { + setup_needed = true; + } + + public bool is_setup_needed() { + return setup_needed; + } + + public StanzaNode read() throws IOStreamError { + if (reader == null) throw new IOStreamError.READ("trying to read, but no stream open"); + try { + var node = reader.read_node(); + if (debug) stderr.printf("IN\n%s\n", node.to_string()); + return node; + } catch (XmlError e) { + throw new IOStreamError.READ(e.message); + } + } + + public void write(StanzaNode node) throws IOStreamError { + if (writer == null) throw new IOStreamError.WRITE("trying to write, but no stream open"); + try { + if (debug) stderr.printf("OUT\n%s\n", node.to_string()); + writer.write_node(node); + } catch (XmlError e) { + throw new IOStreamError.WRITE(e.message); + } + } + + public IOStream get_stream() { + return stream; + } + + public void add_flag(XmppStreamFlag flag) { + flags.add(flag); + } + + public XmppStreamFlag? get_flag(string ns, string id) { + foreach (var flag in flags) { + if (flag.get_ns() == ns && flag.get_id() == id) { + return flag; + } + } + return null; + } + + public void remove_flag(XmppStreamFlag flag) { + flags.remove(flag); + } + + public XmppStream add_module(XmppStreamModule module) { + modules.add(module); + if (negotiation_complete || module as XmppStreamNegotiationModule != null) { + module.attach(this); + } + return this; + } + + public void remove_modules() { + foreach (XmppStreamModule module in modules) module.detach(this); + } + + public XmppStreamModule? get_module(string ns, string id) { + foreach (var module in modules) { + if (module.get_ns() == ns && module.get_id() == id) { + return module; + } + } + return null; + } + + private void setup() throws IOStreamError { + var outs = new StanzaNode.build("stream", "http://etherx.jabber.org/streams") + .put_attribute("to", remote_name) + .put_attribute("version", "1.0") + .put_attribute("xmlns", "jabber:client") + .put_attribute("stream", "http://etherx.jabber.org/streams", XMLNS_URI); + outs.has_nodes = true; + write(outs); + received_root_node(this, read_root()); + } + + private void loop() throws IOStreamError { + while(true) { + if (setup_needed) { + setup(); + setup_needed = false; + } + + StanzaNode node = read(); + received_node(this, node); + + if (node.ns_uri == NS_URI && node.name == "features") { + features = node; + received_features_node(this); + } else if (node.ns_uri == NS_URI && node.name == "stream" && node.pseudo) { + print("disconnect\n"); + disconnect(); + return; + } else if (node.ns_uri == JABBER_URI) { + if (node.name == "message") { + received_message_stanza(this, node); + } else if (node.name == "presence") { + received_presence_stanza(this, node); + } else if (node.name == "iq") { + received_iq_stanza(this, node); + } else { + received_nonza(this, node); + } + } else { + received_nonza(this, node); + } + + if (!negotiation_complete && negotiation_modules_done()) { + negotiation_complete = true; + attach_non_negotation_modules(); + stream_negotiated(this); + } + } + } + + private bool negotiation_modules_done() throws IOStreamError { + if (!setup_needed) { + bool mandatory_outstanding = false; + bool negotiation_active = false; + foreach (XmppStreamModule module in modules) { + XmppStreamNegotiationModule negotiation_module = module as XmppStreamNegotiationModule; + if (negotiation_module != null) { + if (negotiation_module.negotiation_active(this)) negotiation_active = true; + if (negotiation_module.mandatory_outstanding(this)) mandatory_outstanding = true; + } + } + if (!negotiation_active) { + if (mandatory_outstanding) { + throw new IOStreamError.CONNECT("mandatory-to-negotiate feature not negotiated"); + } else { + return true; + } + } + } + return false; + } + + private void attach_non_negotation_modules() { + foreach (XmppStreamModule module in modules) { + if (module as XmppStreamNegotiationModule == null) { + module.attach(this); + } + } + } + + private StanzaNode read_root() throws IOStreamError { + try { + var node = reader.read_root_node(); + if (debug) stderr.printf("IN\n%s\n", node.to_string()); + return node; + } catch (XmlError e) { + throw new IOStreamError.READ(e.message); + } + } +} + +public abstract class XmppStreamFlag { + internal abstract string get_ns(); + internal abstract string get_id(); +} + +public abstract class XmppStreamModule : Object { + internal abstract void attach(XmppStream stream); + internal abstract void detach(XmppStream stream); + internal abstract string get_ns(); + internal abstract string get_id(); +} + +public abstract class XmppStreamNegotiationModule : XmppStreamModule { + internal abstract bool mandatory_outstanding(XmppStream stream); + internal abstract bool negotiation_active(XmppStream stream); +} +} |