diff options
| author | Marvin W <git@larma.de> | 2026-03-14 13:42:58 +0800 |
|---|---|---|
| committer | Marvin W <git@larma.de> | 2026-04-08 19:02:44 +0200 |
| commit | 64757cbe2572259ad201f4c691aeb545088ae42a (patch) | |
| tree | 6c3de38252417500ab1234687bd413e29dcc4d1b /xmpp-vala | |
| parent | 82da21c1a67d5a50d0a4940b5e3c687929b041c2 (diff) | |
Add implementation for XEP-0202 Entity Time
Diffstat (limited to 'xmpp-vala')
| -rw-r--r-- | xmpp-vala/meson.build | 1 | ||||
| -rw-r--r-- | xmpp-vala/src/module/iq/module.vala | 13 | ||||
| -rw-r--r-- | xmpp-vala/src/module/stanza_error.vala | 3 | ||||
| -rw-r--r-- | xmpp-vala/src/module/xep/0045_muc/module.vala | 2 | ||||
| -rw-r--r-- | xmpp-vala/src/module/xep/0082_date_time_profiles.vala | 33 | ||||
| -rw-r--r-- | xmpp-vala/src/module/xep/0202_entity_time.vala | 75 | ||||
| -rw-r--r-- | xmpp-vala/src/module/xep/0203_delayed_delivery.vala | 4 | ||||
| -rw-r--r-- | xmpp-vala/src/module/xep/0215_external_service_discovery.vala | 2 | ||||
| -rw-r--r-- | xmpp-vala/src/module/xep/0313_2_message_archive_management.vala | 4 | ||||
| -rw-r--r-- | xmpp-vala/src/module/xep/0446_file_metadata_element.vala | 4 |
10 files changed, 129 insertions, 12 deletions
diff --git a/xmpp-vala/meson.build b/xmpp-vala/meson.build index 4769a8a6..b035e367 100644 --- a/xmpp-vala/meson.build +++ b/xmpp-vala/meson.build @@ -93,6 +93,7 @@ sources = files( 'src/module/xep/0191_blocking_command.vala', 'src/module/xep/0198_stream_management.vala', 'src/module/xep/0199_ping.vala', + 'src/module/xep/0202_entity_time.vala', 'src/module/xep/0203_delayed_delivery.vala', 'src/module/xep/0215_external_service_discovery.vala', 'src/module/xep/0231_bits_of_binary.vala', diff --git a/xmpp-vala/src/module/iq/module.vala b/xmpp-vala/src/module/iq/module.vala index 172aa9b1..bb9260c9 100644 --- a/xmpp-vala/src/module/iq/module.vala +++ b/xmpp-vala/src/module/iq/module.vala @@ -19,11 +19,20 @@ namespace Xmpp.Iq { Iq.Stanza? return_stanza = null; responseListeners[iq.id] = new ResponseListener((_, result_iq) => { return_stanza = result_iq; - Idle.add(send_iq_async.callback); + if (cancellable == null || !cancellable.is_cancelled()) { + Idle.add(send_iq_async.callback); + } }); stream.write_async(iq.stanza, io_priority, cancellable); + ulong cancelled_handler_id = 0; + if (cancellable != null) { + cancelled_handler_id = cancellable.cancelled.connect(() => Idle.add(send_iq_async.callback)); + } yield; - cancellable.set_error_if_cancelled(); + if (cancellable != null) { + cancellable.disconnect(cancelled_handler_id); + cancellable.set_error_if_cancelled(); + } return return_stanza; } diff --git a/xmpp-vala/src/module/stanza_error.vala b/xmpp-vala/src/module/stanza_error.vala index 5e872291..1a69fd30 100644 --- a/xmpp-vala/src/module/stanza_error.vala +++ b/xmpp-vala/src/module/stanza_error.vala @@ -88,6 +88,9 @@ namespace Xmpp { public ErrorStanza.feature_not_implemented(string? human_readable = null) { this.build(TYPE_MODIFY, CONDITION_FEATURE_NOT_IMPLEMENTED, human_readable, null); } + public ErrorStanza.forbidden(string? human_readable = null) { + this.build(TYPE_AUTH, CONDITION_FORBIDDEN, human_readable, null); + } public ErrorStanza.item_not_found(StanzaNode? application_condition = null) { this.build(TYPE_CANCEL, CONDITION_ITEM_NOT_FOUND, null, application_condition); } diff --git a/xmpp-vala/src/module/xep/0045_muc/module.vala b/xmpp-vala/src/module/xep/0045_muc/module.vala index f660a1cc..88ff819f 100644 --- a/xmpp-vala/src/module/xep/0045_muc/module.vala +++ b/xmpp-vala/src/module/xep/0045_muc/module.vala @@ -100,7 +100,7 @@ public class Module : XmppStreamModule { x_node.put_node(history_node); if (history_since != null) { - history_node.set_attribute("since", DateTimeProfiles.to_datetime(history_since)); + history_node.set_attribute("since", DateTimeProfiles.format_time(history_since)); } else if (!receive_history) { history_node.set_attribute("maxchars", "0"); } diff --git a/xmpp-vala/src/module/xep/0082_date_time_profiles.vala b/xmpp-vala/src/module/xep/0082_date_time_profiles.vala index 8b40d3ac..3237208e 100644 --- a/xmpp-vala/src/module/xep/0082_date_time_profiles.vala +++ b/xmpp-vala/src/module/xep/0082_date_time_profiles.vala @@ -1,11 +1,40 @@ namespace Xmpp.Xep.DateTimeProfiles { -public DateTime? parse_string(string time_string) { +public DateTime? parse_time(string time_string) { return new DateTime.from_iso8601(time_string, null); } -public string to_datetime(DateTime time) { +public TimeZone? parse_tzd(string tzd) { + if (tzd == "Z") return new TimeZone.utc(); + // We support parsing TZD without positive or negative sign prefix for compatibility + string unprefixed; + if (tzd.length == 5) { + unprefixed = tzd; + } else if (tzd.length == 6 && (tzd.has_prefix("-") || tzd.has_prefix("+"))) { + unprefixed = tzd.substring(1); + } else { + return null; + } + int h_offset = unprefixed.substring(0, 2).to_int(); + int m_offset = unprefixed.substring(3, 2).to_int(); + int offset = h_offset * 60 * 60 + m_offset * 60; + if (tzd.has_prefix("-")) { + offset = -offset; + } + return new TimeZone.offset(offset); +} + +public string format_time(DateTime time) { return time.to_utc().format_iso8601().to_string(); } +public string format_tzd(DateTime time) { + TimeSpan utc_offset = time.get_utc_offset(); + if (utc_offset == 0) return "Z"; + int m_offset = (int) (utc_offset / TimeSpan.MINUTE).abs() % 60; + int h_offset = (int) (utc_offset / TimeSpan.HOUR).abs(); + string prefix = utc_offset < 0 ? "-" : "+"; + return "%s%02d:%02d".printf(prefix, h_offset, m_offset); +} + } diff --git a/xmpp-vala/src/module/xep/0202_entity_time.vala b/xmpp-vala/src/module/xep/0202_entity_time.vala new file mode 100644 index 00000000..9421d921 --- /dev/null +++ b/xmpp-vala/src/module/xep/0202_entity_time.vala @@ -0,0 +1,75 @@ +using Gee; + +namespace Xmpp.Xep.EntityTime { + public const string NS_URI = "urn:xmpp:time"; + + public class Module : XmppStreamModule, Iq.Handler { + public static ModuleIdentity<Module> IDENTITY = new ModuleIdentity<Module>(NS_URI, "0202_entity_time"); + public bool enabled { get; set; default = false; } + + private HashMap<Jid, Future<DateTime?>> active_time_requests = new HashMap<Jid, Future<DateTime?>>(Jid.hash_func, Jid.equals_func); + + public async DateTime? query_time(XmppStream stream, Jid jid) { + if (!enabled) return null; + + var future = active_time_requests[jid]; + if (future != null) { + try { + return yield future.wait_async(); + } catch (FutureError error) { + return null; + } + } + + var promise = new Promise<DateTime?>(); + active_time_requests[jid] = promise.future; + + DateTime? res = null; + try { + Iq.Stanza iq = new Iq.Stanza.get(new StanzaNode.build("time", NS_URI).add_self_xmlns()) { to=jid }; + var cancellable = new Cancellable(); + var timeout_id = Timeout.add_seconds_once(10, () => cancellable.cancel()); + Iq.Stanza result = yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq, Priority.HIGH, cancellable); + Source.remove(timeout_id); + StanzaNode time_node = result.stanza.get_subnode("time", NS_URI); + if (time_node == null) return null; + StanzaNode tzo_node = time_node.get_subnode("tzo", NS_URI); + StanzaNode utc_node = time_node.get_subnode("utc", NS_URI); + TimeZone? tzo = null; + if (utc_node != null) res = DateTimeProfiles.parse_time(utc_node.get_string_content()); + if (tzo_node != null) tzo = DateTimeProfiles.parse_tzd(tzo_node.get_string_content()); + if (res != null && tzo != null) res = res.to_timezone(tzo); + promise.set_value(res); + } catch (IOError e) { + debug("Error while fetching time: %s", e.message); + } + return res; + } + + public override void attach(XmppStream stream) { + stream.get_module(Iq.Module.IDENTITY).register_for_namespace(NS_URI, this); + stream.get_module(ServiceDiscovery.Module.IDENTITY).add_feature(stream, NS_URI); + } + + public override void detach(XmppStream stream) { + stream.get_module(Iq.Module.IDENTITY).unregister_from_namespace(NS_URI, this); + stream.get_module(ServiceDiscovery.Module.IDENTITY).remove_feature(stream, NS_URI); + } + + public async void on_iq_get(XmppStream stream, Iq.Stanza iq) { + var roster_flag = stream.get_flag(Roster.Flag.IDENTITY); + if (!enabled || roster_flag == null || roster_flag.get_item(iq.from.bare_jid) == null) { + stream.get_module(Iq.Module.IDENTITY).send_iq(stream, new Iq.Stanza.error(iq, new ErrorStanza.forbidden()), null, Priority.LOW); + } else { + DateTime time = new DateTime.now_local(); + StanzaNode time_node = new StanzaNode.build("time", NS_URI).add_self_xmlns(); + time_node.put_node(new StanzaNode.build("utc", NS_URI).put_node(new StanzaNode.text(DateTimeProfiles.format_time(time)))); + time_node.put_node(new StanzaNode.build("tzo", NS_URI).put_node(new StanzaNode.text(DateTimeProfiles.format_tzd(time)))); + stream.get_module(Iq.Module.IDENTITY).send_iq(stream, new Iq.Stanza.result(iq, time_node), null, Priority.LOW); + } + } + + public override string get_ns() { return NS_URI; } + public override string get_id() { return IDENTITY.id; } + } +} diff --git a/xmpp-vala/src/module/xep/0203_delayed_delivery.vala b/xmpp-vala/src/module/xep/0203_delayed_delivery.vala index ae344d2e..c4be0ad3 100644 --- a/xmpp-vala/src/module/xep/0203_delayed_delivery.vala +++ b/xmpp-vala/src/module/xep/0203_delayed_delivery.vala @@ -4,7 +4,7 @@ public const string NS_URI = "urn:xmpp:delay"; public static DateTime? get_time_for_node(StanzaNode node) { string? time = node.get_attribute("stamp"); - if (time != null) return DateTimeProfiles.parse_string(time); + if (time != null) return DateTimeProfiles.parse_time(time); return null; } @@ -24,7 +24,7 @@ public class Module : XmppStreamModule { public static void set_message_delay(MessageStanza message, DateTime datetime) { StanzaNode delay_node = (new StanzaNode.build("delay", NS_URI)).add_self_xmlns(); - delay_node.put_attribute("stamp", DateTimeProfiles.to_datetime(datetime)); + delay_node.put_attribute("stamp", DateTimeProfiles.format_time(datetime)); message.stanza.put_node(delay_node); } diff --git a/xmpp-vala/src/module/xep/0215_external_service_discovery.vala b/xmpp-vala/src/module/xep/0215_external_service_discovery.vala index 6d41a288..5bad71cd 100644 --- a/xmpp-vala/src/module/xep/0215_external_service_discovery.vala +++ b/xmpp-vala/src/module/xep/0215_external_service_discovery.vala @@ -26,7 +26,7 @@ namespace Xmpp.Xep.ExternalServiceDiscovery { service.username = service_node.get_attribute("username", NS_URI); service.password = service_node.get_attribute("password", NS_URI); string? expires_str = service_node.get_attribute("expires", NS_URI); - if (expires_str != null) service.expires = DateTimeProfiles.parse_string(expires_str); + if (expires_str != null) service.expires = DateTimeProfiles.parse_time(expires_str); service.transport = service_node.get_attribute("transport", NS_URI); service.name = service_node.get_attribute("name", NS_URI); diff --git a/xmpp-vala/src/module/xep/0313_2_message_archive_management.vala b/xmpp-vala/src/module/xep/0313_2_message_archive_management.vala index b25f6106..298a1c61 100644 --- a/xmpp-vala/src/module/xep/0313_2_message_archive_management.vala +++ b/xmpp-vala/src/module/xep/0313_2_message_archive_management.vala @@ -49,12 +49,12 @@ namespace Xmpp.MessageArchiveManagement.V2 { } if (mam_params.start != null) { DataForms.DataForm.Field field = new DataForms.DataForm.Field() { var="start" }; - field.set_value_string(DateTimeProfiles.to_datetime(mam_params.start)); + field.set_value_string(DateTimeProfiles.format_time(mam_params.start)); fields.add(field); } if (mam_params.end != null && mam_params.end_id == null && !mam_params.use_ns2_extended) { DataForms.DataForm.Field field = new DataForms.DataForm.Field() { var="end" }; - field.set_value_string(DateTimeProfiles.to_datetime(mam_params.end)); + field.set_value_string(DateTimeProfiles.format_time(mam_params.end)); fields.add(field); } diff --git a/xmpp-vala/src/module/xep/0446_file_metadata_element.vala b/xmpp-vala/src/module/xep/0446_file_metadata_element.vala index ebdcbcfa..b4c4367c 100644 --- a/xmpp-vala/src/module/xep/0446_file_metadata_element.vala +++ b/xmpp-vala/src/module/xep/0446_file_metadata_element.vala @@ -29,7 +29,7 @@ namespace Xmpp.Xep.FileMetadataElement { } if (this.date != null) { node.put_node(new StanzaNode.build("date", NS_URI).put_node( - new StanzaNode.text(DateTimeProfiles.to_datetime(this.date)) + new StanzaNode.text(DateTimeProfiles.format_time(this.date)) )); } if (this.desc != null) { @@ -84,7 +84,7 @@ namespace Xmpp.Xep.FileMetadataElement { } StanzaNode? date_node = file_node.get_subnode("date"); if (date_node != null && date_node.get_string_content() != null) { - metadata.date = DateTimeProfiles.parse_string(date_node.get_string_content()); + metadata.date = DateTimeProfiles.parse_time(date_node.get_string_content()); } StanzaNode? width_node = file_node.get_subnode("width"); if (width_node != null && width_node.get_string_content() != null) { |
