diff --git a/xmpp/Cargo.toml b/xmpp/Cargo.toml index 1a24e795..79cb3d1f 100644 --- a/xmpp/Cargo.toml +++ b/xmpp/Cargo.toml @@ -14,6 +14,7 @@ license = "MPL-2.0" edition = "2021" [dependencies] +chrono = "0.4" futures = "0.3" tokio = { version = "1", features = ["fs"] } log = "0.4" diff --git a/xmpp/examples/hello_bot.rs b/xmpp/examples/hello_bot.rs index cd96d24c..26422778 100644 --- a/xmpp/examples/hello_bot.rs +++ b/xmpp/examples/hello_bot.rs @@ -52,8 +52,8 @@ async fn main() -> Result<(), Option<()>> { Event::ContactChanged(contact) => { println!("Contact {} changed.", contact.jid); } - Event::ChatMessage(_id, jid, body) => { - println!("Message from {}: {}", jid, body.0); + Event::ChatMessage(_id, jid, body, time_info) => { + println!("Message from {} at {}: {}", jid, time_info.received, body.0); } Event::JoinRoom(jid, conference) => { println!("Joining room {} ({:?})…", jid, conference.name); @@ -82,8 +82,11 @@ async fn main() -> Result<(), Option<()>> { Event::RoomLeft(jid) => { println!("Left room {}.", jid); } - Event::RoomMessage(_id, jid, nick, body) => { - println!("Message in room {} from {}: {}", jid, nick, body.0); + Event::RoomMessage(_id, jid, nick, body, time_info) => { + println!( + "Message in room {} from {} at {}: {}", + jid, nick, time_info.received, body.0 + ); } Event::AvatarRetrieved(jid, path) => { println!("Received avatar for {} in {}.", jid, path); diff --git a/xmpp/src/delay.rs b/xmpp/src/delay.rs new file mode 100644 index 00000000..326d62a3 --- /dev/null +++ b/xmpp/src/delay.rs @@ -0,0 +1,65 @@ +// Copyright (c) 2023 xmpp-rs contributors. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use chrono::{DateTime, Utc}; +use tokio_xmpp::{ + parsers::{delay::Delay, message::Message, ns}, + Jid, +}; + +/// Time information associated with a stanza. +/// +/// Contains information about when the message was received, and any claim about when it was sent. +#[derive(Debug, Clone)] +pub struct StanzaTimeInfo { + /// Time information when the message was received by the library + pub received: DateTime, + + /// Time information claimed by the sender or an intermediary. + /// + /// **Warning**: this has security implications. See [XEP-0203 security section](https://xmpp.org/extensions/xep-0203.html#security). + pub delays: Vec, +} + +impl StanzaTimeInfo { + pub fn delay_from(&self, jid: &Jid) -> Option<&Delay> { + self.delays.iter().find(|delay| { + if let Some(from) = &delay.from { + return from == jid; + } + return false; + }) + } +} + +/// Parsing a [`Message`], store the current time it was processed, as well [XEP-0203](https://xmpp.org/extensions/xep-0203.html#protocol) +/// [`Delay`] contained in the message's payloads. +/// +/// Specifically, this method will look for any element in the message's payloads. If they were found, +/// they will be added to the [`StanzaTimeInfo`] result. +pub fn message_time_info(message: &Message) -> StanzaTimeInfo { + let mut delays = vec![]; + + // Scan the message payloads for XEP-0203 delays. + for payload in &message.payloads { + if payload.is("delay", ns::DELAY) { + match Delay::try_from(payload.clone()) { + Ok(delay) => delays.push(delay), + Err(e) => { + error!("Wrong format in payload from {}:{}\n{:?}\nUsing received time only.", + message.from.as_ref().unwrap().to_owned(), + e, + payload); + } + } + } + } + + StanzaTimeInfo { + received: Utc::now(), + delays, + } +} diff --git a/xmpp/src/event.rs b/xmpp/src/event.rs index 2eea5c24..46156dca 100644 --- a/xmpp/src/event.rs +++ b/xmpp/src/event.rs @@ -6,7 +6,7 @@ use tokio_xmpp::parsers::{bookmarks2, message::Body, roster::Item as RosterItem, BareJid, Jid}; -use crate::{Error, Id, RoomNick}; +use crate::{delay::StanzaTimeInfo, Error, Id, RoomNick}; #[derive(Debug)] pub enum Event { @@ -17,21 +17,26 @@ pub enum Event { ContactChanged(RosterItem), #[cfg(feature = "avatars")] AvatarRetrieved(Jid, String), - ChatMessage(Id, BareJid, Body), + /// A chat message was received. It may have been delayed on the network. + /// - The [`Id`] is a unique identifier for this message. + /// - The [`BareJid`] is the sender's JID. + /// - The [`Body`] is the message body. + /// - The [`StanzaTimeInfo`] about when message was received, and when the message was claimed sent. + ChatMessage(Id, BareJid, Body, StanzaTimeInfo), JoinRoom(BareJid, bookmarks2::Conference), LeaveRoom(BareJid), LeaveAllRooms, RoomJoined(BareJid), RoomLeft(BareJid), - RoomMessage(Id, BareJid, RoomNick, Body), + RoomMessage(Id, BareJid, RoomNick, Body, StanzaTimeInfo), /// The subject of a room was received. /// - The BareJid is the room's address. /// - The RoomNick is the nickname of the room member who set the subject. /// - The String is the new subject. - RoomSubject(BareJid, Option, String), + RoomSubject(BareJid, Option, String, StanzaTimeInfo), /// A private message received from a room, containing the message ID, the room's BareJid, /// the sender's nickname, and the message body. - RoomPrivateMessage(Id, BareJid, RoomNick, Body), - ServiceMessage(Id, BareJid, Body), + RoomPrivateMessage(Id, BareJid, RoomNick, Body, StanzaTimeInfo), + ServiceMessage(Id, BareJid, Body, StanzaTimeInfo), HttpUploadedFile(String), } diff --git a/xmpp/src/lib.rs b/xmpp/src/lib.rs index 7b85c8f4..5e3f7ce7 100644 --- a/xmpp/src/lib.rs +++ b/xmpp/src/lib.rs @@ -13,6 +13,7 @@ extern crate log; pub mod agent; pub mod builder; +pub mod delay; pub mod disco; pub mod event; pub mod event_loop; diff --git a/xmpp/src/message/receive/chat.rs b/xmpp/src/message/receive/chat.rs index 7191ba88..685eea57 100644 --- a/xmpp/src/message/receive/chat.rs +++ b/xmpp/src/message/receive/chat.rs @@ -9,13 +9,14 @@ use tokio_xmpp::{ Jid, }; -use crate::{Agent, Event}; +use crate::{delay::StanzaTimeInfo, Agent, Event}; pub async fn handle_message_chat( agent: &mut Agent, events: &mut Vec, from: Jid, message: &Message, + time_info: StanzaTimeInfo, ) { let langs: Vec<&str> = agent.lang.iter().map(String::as_str).collect(); if let Some((_lang, body)) = message.get_best_body(langs) { @@ -27,13 +28,19 @@ pub async fn handle_message_chat( Jid::Bare(bare) => { // TODO: Can a service message be of type Chat/Normal and not Groupchat? warn!("Received misformed MessageType::Chat in muc#user namespace from a bare JID."); - Event::ServiceMessage(message.id.clone(), bare, body.clone()) + Event::ServiceMessage( + message.id.clone(), + bare, + body.clone(), + time_info.clone(), + ) } Jid::Full(full) => Event::RoomPrivateMessage( message.id.clone(), full.to_bare(), full.resource_str().to_owned(), body.clone(), + time_info.clone(), ), }; @@ -43,7 +50,8 @@ pub async fn handle_message_chat( } if !found_special_message { - let event = Event::ChatMessage(message.id.clone(), from.to_bare(), body.clone()); + let event = + Event::ChatMessage(message.id.clone(), from.to_bare(), body.clone(), time_info); events.push(event); } } diff --git a/xmpp/src/message/receive/group_chat.rs b/xmpp/src/message/receive/group_chat.rs index 9d29814d..417794f3 100644 --- a/xmpp/src/message/receive/group_chat.rs +++ b/xmpp/src/message/receive/group_chat.rs @@ -6,13 +6,14 @@ use tokio_xmpp::{parsers::message::Message, Jid}; -use crate::{Agent, Event}; +use crate::{delay::StanzaTimeInfo, Agent, Event}; pub async fn handle_message_group_chat( agent: &mut Agent, events: &mut Vec, from: Jid, message: &Message, + time_info: StanzaTimeInfo, ) { let langs: Vec<&str> = agent.lang.iter().map(String::as_str).collect(); @@ -21,6 +22,7 @@ pub async fn handle_message_group_chat( from.to_bare(), from.resource_str().map(String::from), subject.0.clone(), + time_info.clone(), )); } @@ -31,8 +33,11 @@ pub async fn handle_message_group_chat( from.to_bare(), full.resource_str().to_owned(), body.clone(), + time_info, ), - Jid::Bare(bare) => Event::ServiceMessage(message.id.clone(), bare, body.clone()), + Jid::Bare(bare) => { + Event::ServiceMessage(message.id.clone(), bare, body.clone(), time_info) + } }; events.push(event) } diff --git a/xmpp/src/message/receive/mod.rs b/xmpp/src/message/receive/mod.rs index 9f2a4b36..0b03112d 100644 --- a/xmpp/src/message/receive/mod.rs +++ b/xmpp/src/message/receive/mod.rs @@ -9,7 +9,7 @@ use tokio_xmpp::parsers::{ ns, }; -use crate::{pubsub, Agent, Event}; +use crate::{delay::message_time_info, pubsub, Agent, Event}; pub mod chat; pub mod group_chat; @@ -17,13 +17,21 @@ pub mod group_chat; pub async fn handle_message(agent: &mut Agent, message: Message) -> Vec { let mut events = vec![]; let from = message.from.clone().unwrap(); + let time_info = message_time_info(&message); match message.type_ { MessageType::Groupchat => { - group_chat::handle_message_group_chat(agent, &mut events, from.clone(), &message).await; + group_chat::handle_message_group_chat( + agent, + &mut events, + from.clone(), + &message, + time_info, + ) + .await; } MessageType::Chat | MessageType::Normal => { - chat::handle_message_chat(agent, &mut events, from.clone(), &message).await; + chat::handle_message_chat(agent, &mut events, from.clone(), &message, time_info).await; } _ => {} }