diff --git a/libdino/src/application.vala b/libdino/src/application.vala index 99cef929..b28bb69b 100644 --- a/libdino/src/application.vala +++ b/libdino/src/application.vala @@ -8,16 +8,18 @@ public class Dino.Application : Gtk.Application { public StreamInteractor stream_interaction; public Plugins.Registry plugin_registry = new Plugins.Registry(); + static string print_xmpp; + + private const OptionEntry[] options = { + { "print-xmpp", 0, 0, OptionArg.STRING, ref print_xmpp, "Print XMPP stanzas identified by DESC to stderr", "DESC" }, + { null } + }; + public Application() throws Error { if (DirUtils.create_with_parents(get_storage_dir(), 0700) == -1) { throw new Error(-1, 0, "Could not create storage dir \"%s\": %s", get_storage_dir(), FileUtils.error_from_errno(errno).to_string()); } - // FIXME: Legacy import - if (FileUtils.test("store.sqlite3", FileTest.IS_REGULAR)) { - FileUtils.rename("store.sqlite3", Path.build_filename(get_storage_dir(), "dino.db")); - } - this.db = new Database(Path.build_filename(get_storage_dir(), "dino.db")); this.stream_interaction = new StreamInteractor(db); @@ -31,8 +33,10 @@ public class Dino.Application : Gtk.Application { ChatInteraction.start(stream_interaction); activate.connect(() => { + stream_interaction.connection_manager.log_options = print_xmpp; restore(); }); + add_main_option_entries(options); } public static string get_storage_dir() { diff --git a/libdino/src/service/connection_manager.vala b/libdino/src/service/connection_manager.vala index 17e31c01..9d069efa 100644 --- a/libdino/src/service/connection_manager.vala +++ b/libdino/src/service/connection_manager.vala @@ -21,6 +21,7 @@ public class ConnectionManager { private NetworkManager? network_manager; private Login1Manager? login1; private ModuleManager module_manager; + public string? log_options; private class Connection { public Core.XmppStream stream { get; set; } @@ -92,7 +93,7 @@ public class ConnectionManager { foreach (Core.XmppStreamModule module in module_manager.get_modules(account, resource)) { stream.add_module(module); } - stream.debug = false; + stream.log = new Core.XmppLog(account.bare_jid.to_string(), log_options); Connection connection = new Connection(stream, new DateTime.now_local()); stream_states[account] = connection; diff --git a/plugins/omemo/src/plugin.vala b/plugins/omemo/src/plugin.vala index f98b03d9..811ecc32 100644 --- a/plugins/omemo/src/plugin.vala +++ b/plugins/omemo/src/plugin.vala @@ -10,11 +10,6 @@ public class Plugin : RootInterface, Object { public AccountSettingsEntry settings_entry; public void registered(Dino.Application app) { - // FIXME: Legacy import - if (FileUtils.test("omemo.db", FileTest.IS_REGULAR)) { - FileUtils.rename("omemo.db", Path.build_filename(Application.get_storage_dir(), "omemo.db")); - } - try { context = new Signal.Context(DEBUG); this.app = app; diff --git a/xmpp-vala/src/core/stanza_attribute.vala b/xmpp-vala/src/core/stanza_attribute.vala index 3169e90e..e6887f33 100644 --- a/xmpp-vala/src/core/stanza_attribute.vala +++ b/xmpp-vala/src/core/stanza_attribute.vala @@ -17,6 +17,14 @@ public class StanzaAttribute : StanzaEntry { } } + public string to_ansi_string(bool hide_ns = false) { + if (ns_uri == null || hide_ns) { + return @"$name=$ANSI_COLOR_GREEN'$val'$ANSI_COLOR_END"; + } else { + return @"$ANSI_COLOR_GRAY{$ns_uri}:$ANSI_COLOR_END$name=$ANSI_COLOR_GREEN'$val'$ANSI_COLOR_END"; + } + } + 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")) { diff --git a/xmpp-vala/src/core/stanza_node.vala b/xmpp-vala/src/core/stanza_node.vala index aff1770d..d9398a39 100644 --- a/xmpp-vala/src/core/stanza_node.vala +++ b/xmpp-vala/src/core/stanza_node.vala @@ -3,6 +3,11 @@ using Gee; namespace Xmpp.Core { public abstract class StanzaEntry { + protected const string ANSI_COLOR_END = "\x1b[0m"; + protected const string ANSI_COLOR_GREEN = "\x1b[32m"; + protected const string ANSI_COLOR_YELLOW = "\x1b[33m"; + protected const string ANSI_COLOR_GRAY = "\x1b[37m"; + public string? ns_uri; public string name; public string? val; @@ -46,7 +51,8 @@ public class StanzaNode : StanzaEntry { public bool has_nodes = false; public bool pseudo = false; - public StanzaNode () {} + public StanzaNode() { + } public StanzaNode.build(string name, string ns_uri = "jabber:client", ArrayList? nodes = null, ArrayList? attrs = null) { this.ns_uri = ns_uri; @@ -81,7 +87,7 @@ public class StanzaNode : StanzaEntry { _ns_uri = this.ns_uri; } } - foreach(var attr in attributes) { + foreach (var attr in attributes) { if (attr.ns_uri == _ns_uri && attr.name == _name) return attr.val; } return null; @@ -93,6 +99,12 @@ public class StanzaNode : StanzaEntry { return int.parse(res); } + public uint get_attribute_uint(string name, uint def = 0, string? ns_uri = null) { + string? res = get_attribute(name, ns_uri); + if (res == null) return def; + return (uint) long.parse(res); + } + public bool get_attribute_bool(string name, bool def = false, string? ns_uri = null) { string? res = get_attribute(name, ns_uri); if (res == null) return def; @@ -111,7 +123,7 @@ public class StanzaNode : StanzaEntry { _ns_uri = this.ns_uri; } } - foreach(var attr in attributes) { + foreach (var attr in attributes) { if (attr.ns_uri == _ns_uri && attr.name == _name) return attr; } return null; @@ -119,7 +131,7 @@ public class StanzaNode : StanzaEntry { public ArrayList get_attributes_by_ns_uri(string ns_uri) { ArrayList ret = new ArrayList (); - foreach(var attr in attributes) { + foreach (var attr in attributes) { if (attr.ns_uri == ns_uri) ret.add(attr); } return ret; @@ -145,7 +157,7 @@ public class StanzaNode : StanzaEntry { StanzaNode? node = this; string? attribute_name = l.arg(); if (attribute_name == null) return null; - while(true) { + while (true) { string? s = l.arg(); if (s == null) break; node = node.get_subnode(attribute_name); @@ -167,7 +179,7 @@ public class StanzaNode : StanzaEntry { _ns_uri = this.ns_uri; } } - foreach(var node in sub_nodes) { + 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); @@ -190,7 +202,7 @@ public class StanzaNode : StanzaEntry { _ns_uri = this.ns_uri; } } - foreach(var node in sub_nodes) { + 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)); @@ -206,7 +218,7 @@ public class StanzaNode : StanzaEntry { public StanzaNode? get_deep_subnode_(va_list l) { StanzaNode? node = this; - while(true) { + while (true) { string? s = l.arg(); if (s == null) break; node = node.get_subnode(s); @@ -226,7 +238,7 @@ public class StanzaNode : StanzaEntry { StanzaNode? node = this; string? subnode_name = l.arg(); if (subnode_name == null) return new ArrayList(); - while(true) { + while (true) { string? s = l.arg(); if (s == null) break; node = node.get_subnode(subnode_name); @@ -276,7 +288,7 @@ public class StanzaNode : StanzaEntry { **/ 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) { + foreach (var attr in attributes) { if (attr.ns_uri == ns_uri && attr.name == name) { attr.val = val; return; @@ -314,10 +326,38 @@ public class StanzaNode : StanzaEntry { return sb.str; } - public string to_xml (NamespaceState? state = null) throws XmlError { + public string to_ansi_string(bool hide_ns = false, int i = 0) { + string indent = string.nfill (i * 2, ' '); + if (name == "#text") { + return @"$indent$val\n"; + } + var sb = new StringBuilder(); + sb.append(@"$indent$ANSI_COLOR_YELLOW<"); + if (!hide_ns) sb.append(@"$ANSI_COLOR_GRAY{$ns_uri}:$ANSI_COLOR_YELLOW"); + sb.append(@"$name$ANSI_COLOR_END"); + foreach (StanzaAttribute attr in attributes) { + sb.append_printf(" %s", attr.to_ansi_string(hide_ns)); + } + if (!has_nodes && sub_nodes.size == 0) { + sb.append(@" $ANSI_COLOR_YELLOW/>$ANSI_COLOR_END\n"); + } else { + sb.append(@"$ANSI_COLOR_YELLOW>$ANSI_COLOR_END\n"); + if (sub_nodes.size != 0) { + foreach (StanzaNode subnode in sub_nodes) { + sb.append(subnode.to_ansi_string(hide_ns, i + 1)); + } + sb.append(@"$indent$ANSI_COLOR_YELLOW$ANSI_COLOR_END\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)) { + 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 { @@ -352,4 +392,5 @@ public class StanzaNode : StanzaEntry { return sb.str; } } + } diff --git a/xmpp-vala/src/core/xmpp_stream.vala b/xmpp-vala/src/core/xmpp_stream.vala index 0283920b..684c7ed0 100644 --- a/xmpp-vala/src/core/xmpp_stream.vala +++ b/xmpp-vala/src/core/xmpp_stream.vala @@ -10,11 +10,133 @@ public errordomain IOStreamError { } +public class XmppLog { + protected const string ANSI_COLOR_END = "\x1b[0m"; + protected const string ANSI_COLOR_WHITE = "\x1b[37;1m"; + + class NodeLogDesc { + public string? name; + private string? ns_uri; + private string? val; + private Map attrs = new HashMap(); + private NodeLogDesc? inner; + + public NodeLogDesc(string desc) { + string d = desc; + + if (d.contains("[")) { + int start = d.index_of("["); + int end = d.index_of("]"); + string attrs = d.substring(start + 1, end - start - 1); + d = d.substring(0, start) + d.substring(end + 1); + foreach (string attr in attrs.split(",")) { + if (attr.contains("=")) { + string key = attr.substring(0, attr.index_of("=")); + string val = attr.substring(attr.index_of("=") + 1); + this.attrs[key] = val; + } else { + this.attrs[attr] = null; + } + } + } + if (d.contains(":") && d.index_of("{") == 0 && d.index_of("}") != -1) { + int end = d.index_of("}"); + this.ns_uri = d.substring(1, end - 2); + d = d.substring(end + 2); + } + if (d.contains(".")) { + inner = new NodeLogDesc(d.substring(d.index_of(".") + 1)); + d = d.substring(0, d.index_of(".")); + } else if (d.contains("=")) { + this.val = d.substring(d.index_of("=")); + d = d.substring(0, d.index_of("=")); + } + + if (d != "") this.name = d; + } + + public bool matches(StanzaNode node) { + if (name != null && node.name != name) return false; + if (ns_uri != null && node.ns_uri != ns_uri) return false; + if (val != null && node.val != val) return false; + foreach (var pair in attrs.entries) { + if (pair.value == null && node.get_attribute(pair.key) == null) return false; + else if (pair.value != null && pair.value != node.get_attribute(pair.key)) return false; + } + if (inner == null) return true; + foreach (StanzaNode snode in node.get_all_subnodes()) { + if (inner.matches(snode)) return true; + } + return false; + } + } + + private bool use_ansi; + private bool hide_ns = true; + private string ident; + private string desc; + private ArrayList descs = new ArrayList(); + + public XmppLog(string? ident = null, string? desc = null) { + this.ident = ident; + this.desc = desc; + this.use_ansi = is_atty(stderr.fileno()); + while (this.desc != null && this.desc.contains(";")) { + string opt = this.desc.substring(0, this.desc.index_of(";")); + this.desc = this.desc.substring(opt.length + 1); + switch (opt) { + case "ansi": use_ansi = true; break; + case "no-ansi": use_ansi = false; break; + case "hide-ns": hide_ns = true; break; + case "show-ns": hide_ns = false; break; + } + } + if (desc != null) { + foreach (string d in this.desc.split("|")) { + descs.add(new NodeLogDesc(d)); + } + } + } + + public virtual bool should_log_node(StanzaNode node) { + if (ident == null || desc == null) return false; + if (desc == "all") return true; + foreach (var desc in descs) { + if (desc.matches(node)) return true; + } + return false; + } + + public virtual bool should_log_str(string str) { + if (ident == null || desc == null) return false; + if (desc == "all") return true; + foreach (var desc in descs) { + if (desc.name == "#text") return true; + } + return false; + } + + public void node(string what, StanzaNode node) { + if (should_log_node(node)) { + stderr.printf("%sXMPP %s [%s]%s\n%s\n", ANSI_COLOR_WHITE, what, ident, ANSI_COLOR_END, use_ansi ? node.to_ansi_string(hide_ns) : node.to_string()); + } + } + + public void str(string what, string str) { + if (should_log_str(str)) { + stderr.printf("%sXMPP %s [%s]%s\n%s\n", ANSI_COLOR_WHITE, what, ident, ANSI_COLOR_END, str); + } + } + + [CCode (cname = "isatty")] + private static extern bool is_atty(int fd); +} + public class XmppStream { private static string NS_URI = "http://etherx.jabber.org/streams"; public string remote_name; - public bool debug = false; + public XmppLog log = new XmppLog(); public StanzaNode? features { get; private set; default = new StanzaNode.build("features", NS_URI); } private IOStream? stream; @@ -51,7 +173,7 @@ public class XmppStream { 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\n"); + log.str("OUT", ""); writer.write.begin(""); reader.cancel(); stream.close_async.begin(); @@ -75,8 +197,8 @@ public class XmppStream { 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()); + StanzaNode node = reader.read_node(); + log.node("IN", node); return node; } catch (XmlError e) { throw new IOStreamError.READ(e.message); @@ -86,7 +208,7 @@ public class XmppStream { 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()); + log.node("OUT", node); writer.write_node(node); } catch (XmlError e) { throw new IOStreamError.WRITE(e.message); @@ -138,18 +260,19 @@ public class XmppStream { } private void setup() throws IOStreamError { - var outs = new StanzaNode.build("stream", "http://etherx.jabber.org/streams") + StanzaNode 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; + log.node("OUT ROOT", outs); write(outs); received_root_node(this, read_root()); } private void loop() throws IOStreamError { - while(true) { + while (true) { if (setup_needed) { setup(); setup_needed = false; @@ -219,8 +342,8 @@ public class XmppStream { private StanzaNode read_root() throws IOStreamError { try { - var node = reader.read_root_node(); - if (debug) stderr.printf("IN\n%s\n", node.to_string()); + StanzaNode node = reader.read_root_node(); + log.node("IN ROOT", node); return node; } catch (XmlError e) { throw new IOStreamError.READ(e.message); @@ -248,6 +371,7 @@ public class FlagIdentity : Object { public abstract class XmppStreamFlag : Object { public abstract string get_ns(); + public abstract string get_id(); } @@ -271,13 +395,18 @@ public class ModuleIdentity : Object { public abstract class XmppStreamModule : Object { public abstract void attach(XmppStream stream); + public abstract void detach(XmppStream stream); + public abstract string get_ns(); + public abstract string get_id(); } public abstract class XmppStreamNegotiationModule : XmppStreamModule { public abstract bool mandatory_outstanding(XmppStream stream); + public abstract bool negotiation_active(XmppStream stream); } + } diff --git a/xmpp-vala/src/module/xep/0184_message_delivery_receipts.vala b/xmpp-vala/src/module/xep/0184_message_delivery_receipts.vala index ed19f9a4..80eea852 100644 --- a/xmpp-vala/src/module/xep/0184_message_delivery_receipts.vala +++ b/xmpp-vala/src/module/xep/0184_message_delivery_receipts.vala @@ -1,5 +1,3 @@ -using Gdk; - using Xmpp.Core; namespace Xmpp.Xep.MessageDeliveryReceipts {