mirror of https://gitlab.com/xmpp-rs/xmpp-rs.git
Compare commits
16 Commits
e7cac4b68b
...
e5dfc0cd62
Author | SHA1 | Date |
---|---|---|
Werner Kroneman | e5dfc0cd62 | |
Jonas Schäfer | 1293e9a3eb | |
Jonas Schäfer | 054447d147 | |
Jonas Schäfer | c08946aa7c | |
Jonas Schäfer | c895cb1009 | |
Jonas Schäfer | 2e9c9411a3 | |
Werner Kroneman | e1677e7612 | |
Werner Kroneman | ffff9ae618 | |
Werner Kroneman | 857115d0de | |
Werner Kroneman | e5d8d2d561 | |
Werner Kroneman | ccdf800d12 | |
Werner Kroneman | b630ae44c5 | |
Werner Kroneman | fde92f2aee | |
Werner Kroneman | 6f635bba17 | |
Werner Kroneman | 3e7f240122 | |
Werner Kroneman | 1868afeda3 |
180
jid/src/inner.rs
180
jid/src/inner.rs
|
@ -1,180 +0,0 @@
|
|||
// Copyright (c) 2023 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
|
||||
//
|
||||
// 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/.
|
||||
|
||||
#![deny(missing_docs)]
|
||||
|
||||
//! Provides a type for Jabber IDs.
|
||||
//!
|
||||
//! For usage, check the documentation on the `Jid` struct.
|
||||
|
||||
use crate::Error;
|
||||
use core::num::NonZeroU16;
|
||||
use memchr::memchr;
|
||||
use std::borrow::Cow;
|
||||
use std::str::FromStr;
|
||||
use stringprep::{nameprep, nodeprep, resourceprep};
|
||||
|
||||
use crate::parts::{DomainRef, NodeRef, ResourceRef};
|
||||
|
||||
fn length_check(len: usize, error_empty: Error, error_too_long: Error) -> Result<(), Error> {
|
||||
if len == 0 {
|
||||
Err(error_empty)
|
||||
} else if len > 1023 {
|
||||
Err(error_too_long)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub(crate) struct InnerJid {
|
||||
pub(crate) normalized: String,
|
||||
pub(crate) at: Option<NonZeroU16>,
|
||||
pub(crate) slash: Option<NonZeroU16>,
|
||||
}
|
||||
|
||||
impl InnerJid {
|
||||
pub(crate) fn new(unnormalized: &str) -> Result<InnerJid, Error> {
|
||||
let bytes = unnormalized.as_bytes();
|
||||
let mut orig_at = memchr(b'@', bytes);
|
||||
let mut orig_slash = memchr(b'/', bytes);
|
||||
if orig_at.is_some() && orig_slash.is_some() && orig_at > orig_slash {
|
||||
// This is part of the resource, not a node@domain separator.
|
||||
orig_at = None;
|
||||
}
|
||||
|
||||
let normalized = match (orig_at, orig_slash) {
|
||||
(Some(at), Some(slash)) => {
|
||||
let node = nodeprep(&unnormalized[..at]).map_err(|_| Error::NodePrep)?;
|
||||
length_check(node.len(), Error::NodeEmpty, Error::NodeTooLong)?;
|
||||
|
||||
let domain = nameprep(&unnormalized[at + 1..slash]).map_err(|_| Error::NamePrep)?;
|
||||
length_check(domain.len(), Error::DomainEmpty, Error::DomainTooLong)?;
|
||||
|
||||
let resource =
|
||||
resourceprep(&unnormalized[slash + 1..]).map_err(|_| Error::ResourcePrep)?;
|
||||
length_check(resource.len(), Error::ResourceEmpty, Error::ResourceTooLong)?;
|
||||
|
||||
orig_at = Some(node.len());
|
||||
orig_slash = Some(node.len() + domain.len() + 1);
|
||||
match (node, domain, resource) {
|
||||
(Cow::Borrowed(_), Cow::Borrowed(_), Cow::Borrowed(_)) => {
|
||||
unnormalized.to_string()
|
||||
}
|
||||
(node, domain, resource) => format!("{node}@{domain}/{resource}"),
|
||||
}
|
||||
}
|
||||
(Some(at), None) => {
|
||||
let node = nodeprep(&unnormalized[..at]).map_err(|_| Error::NodePrep)?;
|
||||
length_check(node.len(), Error::NodeEmpty, Error::NodeTooLong)?;
|
||||
|
||||
let domain = nameprep(&unnormalized[at + 1..]).map_err(|_| Error::NamePrep)?;
|
||||
length_check(domain.len(), Error::DomainEmpty, Error::DomainTooLong)?;
|
||||
|
||||
orig_at = Some(node.len());
|
||||
match (node, domain) {
|
||||
(Cow::Borrowed(_), Cow::Borrowed(_)) => unnormalized.to_string(),
|
||||
(node, domain) => format!("{node}@{domain}"),
|
||||
}
|
||||
}
|
||||
(None, Some(slash)) => {
|
||||
let domain = nameprep(&unnormalized[..slash]).map_err(|_| Error::NamePrep)?;
|
||||
length_check(domain.len(), Error::DomainEmpty, Error::DomainTooLong)?;
|
||||
|
||||
let resource =
|
||||
resourceprep(&unnormalized[slash + 1..]).map_err(|_| Error::ResourcePrep)?;
|
||||
length_check(resource.len(), Error::ResourceEmpty, Error::ResourceTooLong)?;
|
||||
|
||||
orig_slash = Some(domain.len());
|
||||
match (domain, resource) {
|
||||
(Cow::Borrowed(_), Cow::Borrowed(_)) => unnormalized.to_string(),
|
||||
(domain, resource) => format!("{domain}/{resource}"),
|
||||
}
|
||||
}
|
||||
(None, None) => {
|
||||
let domain = nameprep(unnormalized).map_err(|_| Error::NamePrep)?;
|
||||
length_check(domain.len(), Error::DomainEmpty, Error::DomainTooLong)?;
|
||||
|
||||
domain.into_owned()
|
||||
}
|
||||
};
|
||||
|
||||
Ok(InnerJid {
|
||||
normalized,
|
||||
at: orig_at.and_then(|x| NonZeroU16::new(x as u16)),
|
||||
slash: orig_slash.and_then(|x| NonZeroU16::new(x as u16)),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn node(&self) -> Option<&NodeRef> {
|
||||
self.at.map(|at| {
|
||||
let at = u16::from(at) as usize;
|
||||
NodeRef::from_str_unchecked(&self.normalized[..at])
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn domain(&self) -> &DomainRef {
|
||||
match (self.at, self.slash) {
|
||||
(Some(at), Some(slash)) => {
|
||||
let at = u16::from(at) as usize;
|
||||
let slash = u16::from(slash) as usize;
|
||||
DomainRef::from_str_unchecked(&self.normalized[at + 1..slash])
|
||||
}
|
||||
(Some(at), None) => {
|
||||
let at = u16::from(at) as usize;
|
||||
DomainRef::from_str_unchecked(&self.normalized[at + 1..])
|
||||
}
|
||||
(None, Some(slash)) => {
|
||||
let slash = u16::from(slash) as usize;
|
||||
DomainRef::from_str_unchecked(&self.normalized[..slash])
|
||||
}
|
||||
(None, None) => DomainRef::from_str_unchecked(&self.normalized),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn resource(&self) -> Option<&ResourceRef> {
|
||||
self.slash.map(|slash| {
|
||||
let slash = u16::from(slash) as usize;
|
||||
ResourceRef::from_str_unchecked(&self.normalized[slash + 1..])
|
||||
})
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn as_str(&self) -> &str {
|
||||
self.normalized.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for InnerJid {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
InnerJid::new(s)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
macro_rules! assert_size (
|
||||
($t:ty, $sz:expr) => (
|
||||
assert_eq!(::std::mem::size_of::<$t>(), $sz);
|
||||
);
|
||||
);
|
||||
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
#[test]
|
||||
fn test_size() {
|
||||
assert_size!(InnerJid, 16);
|
||||
}
|
||||
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
#[test]
|
||||
fn test_size() {
|
||||
assert_size!(InnerJid, 32);
|
||||
}
|
||||
}
|
831
jid/src/lib.rs
831
jid/src/lib.rs
File diff suppressed because it is too large
Load Diff
|
@ -5,7 +5,7 @@ use std::str::FromStr;
|
|||
|
||||
use stringprep::{nameprep, nodeprep, resourceprep};
|
||||
|
||||
use crate::{BareJid, Error, InnerJid, Jid};
|
||||
use crate::{BareJid, Error, Jid};
|
||||
|
||||
fn length_check(len: usize, error_empty: Error, error_too_long: Error) -> Result<(), Error> {
|
||||
if len == 0 {
|
||||
|
@ -250,18 +250,18 @@ impl DomainRef {
|
|||
impl From<DomainPart> for BareJid {
|
||||
fn from(other: DomainPart) -> Self {
|
||||
BareJid {
|
||||
inner: InnerJid {
|
||||
normalized: other.0,
|
||||
at: None,
|
||||
slash: None,
|
||||
},
|
||||
inner: other.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DomainPart> for Jid {
|
||||
fn from(other: DomainPart) -> Self {
|
||||
Jid::Bare(other.into())
|
||||
Jid {
|
||||
normalized: other.0,
|
||||
at: None,
|
||||
slash: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -88,7 +88,7 @@ impl From<BindResponse> for FullJid {
|
|||
|
||||
impl From<BindResponse> for Jid {
|
||||
fn from(bind: BindResponse) -> Jid {
|
||||
Jid::Full(bind.jid)
|
||||
Jid::from(bind.jid)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -71,8 +71,8 @@ mod tests {
|
|||
assert_size!(Enable, 0);
|
||||
assert_size!(Disable, 0);
|
||||
assert_size!(Private, 0);
|
||||
assert_size!(Received, 152);
|
||||
assert_size!(Sent, 152);
|
||||
assert_size!(Received, 140);
|
||||
assert_size!(Sent, 140);
|
||||
}
|
||||
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
|
@ -81,8 +81,8 @@ mod tests {
|
|||
assert_size!(Enable, 0);
|
||||
assert_size!(Disable, 0);
|
||||
assert_size!(Private, 0);
|
||||
assert_size!(Received, 288);
|
||||
assert_size!(Sent, 288);
|
||||
assert_size!(Received, 264);
|
||||
assert_size!(Sent, 264);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -40,13 +40,13 @@ mod tests {
|
|||
#[cfg(target_pointer_width = "32")]
|
||||
#[test]
|
||||
fn test_size() {
|
||||
assert_size!(Delay, 48);
|
||||
assert_size!(Delay, 44);
|
||||
}
|
||||
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
#[test]
|
||||
fn test_size() {
|
||||
assert_size!(Delay, 80);
|
||||
assert_size!(Delay, 72);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -248,7 +248,7 @@ mod tests {
|
|||
assert_size!(DiscoInfoQuery, 12);
|
||||
assert_size!(DiscoInfoResult, 48);
|
||||
|
||||
assert_size!(Item, 44);
|
||||
assert_size!(Item, 40);
|
||||
assert_size!(DiscoItemsQuery, 52);
|
||||
assert_size!(DiscoItemsResult, 64);
|
||||
}
|
||||
|
@ -261,7 +261,7 @@ mod tests {
|
|||
assert_size!(DiscoInfoQuery, 24);
|
||||
assert_size!(DiscoInfoResult, 96);
|
||||
|
||||
assert_size!(Item, 88);
|
||||
assert_size!(Item, 80);
|
||||
assert_size!(DiscoItemsQuery, 104);
|
||||
assert_size!(DiscoItemsResult, 128);
|
||||
}
|
||||
|
|
|
@ -32,13 +32,13 @@ mod tests {
|
|||
#[cfg(target_pointer_width = "32")]
|
||||
#[test]
|
||||
fn test_size() {
|
||||
assert_size!(Forwarded, 152);
|
||||
assert_size!(Forwarded, 140);
|
||||
}
|
||||
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
#[test]
|
||||
fn test_size() {
|
||||
assert_size!(Forwarded, 288);
|
||||
assert_size!(Forwarded, 264);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -232,15 +232,15 @@ mod tests {
|
|||
#[cfg(target_pointer_width = "32")]
|
||||
#[test]
|
||||
fn test_size() {
|
||||
assert_size!(IqType, 96);
|
||||
assert_size!(Iq, 148);
|
||||
assert_size!(IqType, 92);
|
||||
assert_size!(Iq, 156);
|
||||
}
|
||||
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
#[test]
|
||||
fn test_size() {
|
||||
assert_size!(IqType, 192);
|
||||
assert_size!(Iq, 296);
|
||||
assert_size!(IqType, 184);
|
||||
assert_size!(Iq, 272);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -46,14 +46,14 @@ mod tests {
|
|||
#[test]
|
||||
fn test_size() {
|
||||
assert_size!(JidPrepQuery, 12);
|
||||
assert_size!(JidPrepResponse, 20);
|
||||
assert_size!(JidPrepResponse, 16);
|
||||
}
|
||||
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
#[test]
|
||||
fn test_size() {
|
||||
assert_size!(JidPrepQuery, 24);
|
||||
assert_size!(JidPrepResponse, 40);
|
||||
assert_size!(JidPrepResponse, 32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -692,7 +692,7 @@ mod tests {
|
|||
assert_size!(Reason, 1);
|
||||
assert_size!(ReasonElement, 16);
|
||||
assert_size!(SessionId, 12);
|
||||
assert_size!(Jingle, 112);
|
||||
assert_size!(Jingle, 104);
|
||||
}
|
||||
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
|
@ -711,7 +711,7 @@ mod tests {
|
|||
assert_size!(Reason, 1);
|
||||
assert_size!(ReasonElement, 32);
|
||||
assert_size!(SessionId, 24);
|
||||
assert_size!(Jingle, 224);
|
||||
assert_size!(Jingle, 208);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -284,7 +284,7 @@ mod tests {
|
|||
assert_size!(Mode, 1);
|
||||
assert_size!(CandidateId, 12);
|
||||
assert_size!(StreamId, 12);
|
||||
assert_size!(Candidate, 60);
|
||||
assert_size!(Candidate, 56);
|
||||
assert_size!(TransportPayload, 16);
|
||||
assert_size!(Transport, 44);
|
||||
}
|
||||
|
@ -296,7 +296,7 @@ mod tests {
|
|||
assert_size!(Mode, 1);
|
||||
assert_size!(CandidateId, 24);
|
||||
assert_size!(StreamId, 24);
|
||||
assert_size!(Candidate, 96);
|
||||
assert_size!(Candidate, 88);
|
||||
assert_size!(TransportPayload, 32);
|
||||
assert_size!(Transport, 88);
|
||||
}
|
||||
|
|
|
@ -166,7 +166,7 @@ mod tests {
|
|||
fn test_size() {
|
||||
assert_size!(QueryId, 12);
|
||||
assert_size!(Query, 120);
|
||||
assert_size!(Result_, 176);
|
||||
assert_size!(Result_, 164);
|
||||
assert_size!(Complete, 1);
|
||||
assert_size!(Fin, 44);
|
||||
}
|
||||
|
@ -176,7 +176,7 @@ mod tests {
|
|||
fn test_size() {
|
||||
assert_size!(QueryId, 24);
|
||||
assert_size!(Query, 240);
|
||||
assert_size!(Result_, 336);
|
||||
assert_size!(Result_, 312);
|
||||
assert_size!(Complete, 1);
|
||||
assert_size!(Fin, 88);
|
||||
}
|
||||
|
|
|
@ -307,7 +307,7 @@ mod tests {
|
|||
assert_size!(Body, 12);
|
||||
assert_size!(Subject, 12);
|
||||
assert_size!(Thread, 12);
|
||||
assert_size!(Message, 104);
|
||||
assert_size!(Message, 96);
|
||||
}
|
||||
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
|
@ -317,7 +317,7 @@ mod tests {
|
|||
assert_size!(Body, 24);
|
||||
assert_size!(Subject, 24);
|
||||
assert_size!(Thread, 24);
|
||||
assert_size!(Message, 208);
|
||||
assert_size!(Message, 192);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -379,7 +379,7 @@ mod tests {
|
|||
fn test_size() {
|
||||
assert_size!(Show, 1);
|
||||
assert_size!(Type, 1);
|
||||
assert_size!(Presence, 80);
|
||||
assert_size!(Presence, 72);
|
||||
}
|
||||
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
|
@ -387,7 +387,7 @@ mod tests {
|
|||
fn test_size() {
|
||||
assert_size!(Show, 1);
|
||||
assert_size!(Type, 1);
|
||||
assert_size!(Presence, 160);
|
||||
assert_size!(Presence, 144);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -200,11 +200,11 @@ mod tests {
|
|||
node: NodeName(String::from("foo")),
|
||||
affiliations: vec![
|
||||
Affiliation {
|
||||
jid: Jid::Bare(BareJid::from_str("hamlet@denmark.lit").unwrap()),
|
||||
jid: Jid::from(BareJid::from_str("hamlet@denmark.lit").unwrap()),
|
||||
affiliation: AffiliationAttribute::Owner,
|
||||
},
|
||||
Affiliation {
|
||||
jid: Jid::Bare(BareJid::from_str("polonius@denmark.lit").unwrap()),
|
||||
jid: Jid::from(BareJid::from_str("polonius@denmark.lit").unwrap()),
|
||||
affiliation: AffiliationAttribute::Outcast,
|
||||
},
|
||||
],
|
||||
|
@ -335,22 +335,22 @@ mod tests {
|
|||
node: NodeName(String::from("foo")),
|
||||
subscriptions: vec![
|
||||
SubscriptionElem {
|
||||
jid: Jid::Bare(BareJid::from_str("hamlet@denmark.lit").unwrap()),
|
||||
jid: Jid::from(BareJid::from_str("hamlet@denmark.lit").unwrap()),
|
||||
subscription: Subscription::Subscribed,
|
||||
subid: None,
|
||||
},
|
||||
SubscriptionElem {
|
||||
jid: Jid::Bare(BareJid::from_str("polonius@denmark.lit").unwrap()),
|
||||
jid: Jid::from(BareJid::from_str("polonius@denmark.lit").unwrap()),
|
||||
subscription: Subscription::Unconfigured,
|
||||
subid: None,
|
||||
},
|
||||
SubscriptionElem {
|
||||
jid: Jid::Bare(BareJid::from_str("bernardo@denmark.lit").unwrap()),
|
||||
jid: Jid::from(BareJid::from_str("bernardo@denmark.lit").unwrap()),
|
||||
subscription: Subscription::Subscribed,
|
||||
subid: Some(String::from("123-abc")),
|
||||
},
|
||||
SubscriptionElem {
|
||||
jid: Jid::Bare(BareJid::from_str("bernardo@denmark.lit").unwrap()),
|
||||
jid: Jid::from(BareJid::from_str("bernardo@denmark.lit").unwrap()),
|
||||
subscription: Subscription::Subscribed,
|
||||
subid: Some(String::from("004-yyy")),
|
||||
},
|
||||
|
|
|
@ -319,7 +319,7 @@ mod tests {
|
|||
fn test_size() {
|
||||
assert_size!(ErrorType, 1);
|
||||
assert_size!(DefinedCondition, 1);
|
||||
assert_size!(StanzaError, 96);
|
||||
assert_size!(StanzaError, 92);
|
||||
}
|
||||
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
|
@ -327,7 +327,7 @@ mod tests {
|
|||
fn test_size() {
|
||||
assert_size!(ErrorType, 1);
|
||||
assert_size!(DefinedCondition, 1);
|
||||
assert_size!(StanzaError, 192);
|
||||
assert_size!(StanzaError, 184);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -44,14 +44,14 @@ mod tests {
|
|||
#[cfg(target_pointer_width = "32")]
|
||||
#[test]
|
||||
fn test_size() {
|
||||
assert_size!(StanzaId, 32);
|
||||
assert_size!(StanzaId, 24);
|
||||
assert_size!(OriginId, 12);
|
||||
}
|
||||
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
#[test]
|
||||
fn test_size() {
|
||||
assert_size!(StanzaId, 64);
|
||||
assert_size!(StanzaId, 56);
|
||||
assert_size!(OriginId, 24);
|
||||
}
|
||||
|
||||
|
|
|
@ -75,7 +75,7 @@ async fn main() -> Result<(), Option<()>> {
|
|||
Event::RoomJoined(jid) => {
|
||||
println!("Joined room {}.", jid);
|
||||
client
|
||||
.send_message(Jid::Bare(jid), MessageType::Groupchat, "en", "Hello world!")
|
||||
.send_message(Jid::from(jid), MessageType::Groupchat, "en", "Hello world!")
|
||||
.await;
|
||||
}
|
||||
Event::RoomLeft(jid) => {
|
||||
|
|
|
@ -36,7 +36,9 @@ impl<C: ServerConnector> Agent<C> {
|
|||
lang: &str,
|
||||
status: &str,
|
||||
) {
|
||||
muc::room::join_room(self, room, nick, password, lang, status).await
|
||||
// Use the provided nickname, or the default if none.
|
||||
let nick = nick.unwrap_or_else(|| self.default_nick.read().unwrap().to_string());
|
||||
muc::room::join_room(&mut self.client, room, &nick, password, lang, status).await
|
||||
}
|
||||
|
||||
/// Request to leave a chatroom.
|
||||
|
@ -57,7 +59,7 @@ impl<C: ServerConnector> Agent<C> {
|
|||
lang: impl Into<String>,
|
||||
status: impl Into<String>,
|
||||
) {
|
||||
muc::room::leave_room(self, room_jid, nickname, lang, status).await
|
||||
muc::room::leave_room(&mut self.client, room_jid, nickname, lang, status).await
|
||||
}
|
||||
|
||||
pub async fn send_message(
|
||||
|
@ -67,7 +69,7 @@ impl<C: ServerConnector> Agent<C> {
|
|||
lang: &str,
|
||||
text: &str,
|
||||
) {
|
||||
message::send::send_message(self, recipient, type_, lang, text).await
|
||||
message::send::send_message(&mut self.client, recipient, type_, lang, text).await
|
||||
}
|
||||
|
||||
pub async fn send_room_private_message(
|
||||
|
@ -90,7 +92,7 @@ impl<C: ServerConnector> Agent<C> {
|
|||
event_loop::wait_for_events(self).await
|
||||
}
|
||||
|
||||
pub async fn upload_file_with(&mut self, service: &str, path: &Path) {
|
||||
pub async fn upload_file_with(&mut self, service: Jid, path: &Path) {
|
||||
upload::send::upload_file_with(self, service, path).await
|
||||
}
|
||||
|
||||
|
|
|
@ -25,8 +25,8 @@ pub async fn handle_message_chat<C: ServerConnector>(
|
|||
|
||||
for payload in &message.payloads {
|
||||
if let Ok(_) = MucUser::try_from(payload.clone()) {
|
||||
let event = match from.clone() {
|
||||
Jid::Bare(bare) => {
|
||||
let event = match from.clone().try_into_full() {
|
||||
Err(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(
|
||||
|
@ -36,7 +36,7 @@ pub async fn handle_message_chat<C: ServerConnector>(
|
|||
time_info.clone(),
|
||||
)
|
||||
}
|
||||
Jid::Full(full) => Event::RoomPrivateMessage(
|
||||
Ok(full) => Event::RoomPrivateMessage(
|
||||
message.id.clone(),
|
||||
full.to_bare(),
|
||||
full.resource().to_string(),
|
||||
|
|
|
@ -28,17 +28,15 @@ pub async fn handle_message_group_chat<C: ServerConnector>(
|
|||
}
|
||||
|
||||
if let Some((_lang, body)) = message.get_best_body(langs) {
|
||||
let event = match from.clone() {
|
||||
Jid::Full(full) => Event::RoomMessage(
|
||||
let event = match from.clone().try_into_full() {
|
||||
Ok(full) => Event::RoomMessage(
|
||||
message.id.clone(),
|
||||
from.to_bare(),
|
||||
full.resource().to_string(),
|
||||
body.clone(),
|
||||
time_info,
|
||||
),
|
||||
Jid::Bare(bare) => {
|
||||
Event::ServiceMessage(message.id.clone(), bare, body.clone(), time_info)
|
||||
}
|
||||
Err(bare) => Event::ServiceMessage(message.id.clone(), bare, body.clone(), time_info),
|
||||
};
|
||||
events.push(event)
|
||||
}
|
||||
|
|
|
@ -10,10 +10,17 @@ use tokio_xmpp::{
|
|||
Jid,
|
||||
};
|
||||
|
||||
use crate::Agent;
|
||||
use crate::agent::TokioXmppClient;
|
||||
|
||||
/// Send a text message to a recipient.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `client`: The XMPP client to use to send the message.
|
||||
/// - `recipient`: The JID of the recipient.
|
||||
/// - `type_`: The type of the message.
|
||||
/// - `lang`: The language of the message.
|
||||
pub async fn send_message<C: ServerConnector>(
|
||||
agent: &mut Agent<C>,
|
||||
client: &mut TokioXmppClient<C>,
|
||||
recipient: Jid,
|
||||
type_: MessageType,
|
||||
lang: &str,
|
||||
|
@ -24,5 +31,5 @@ pub async fn send_message<C: ServerConnector>(
|
|||
message
|
||||
.bodies
|
||||
.insert(String::from(lang), Body(String::from(text)));
|
||||
let _ = agent.client.send_stanza(message.into()).await;
|
||||
let _ = client.send_stanza(message.into()).await;
|
||||
}
|
||||
|
|
|
@ -13,27 +13,58 @@ use tokio_xmpp::{
|
|||
BareJid,
|
||||
};
|
||||
|
||||
use crate::{Agent, RoomNick};
|
||||
use crate::agent::TokioXmppClient;
|
||||
use crate::RoomNick;
|
||||
|
||||
pub async fn join_room<C: ServerConnector>(
|
||||
agent: &mut Agent<C>,
|
||||
client: &mut TokioXmppClient<C>,
|
||||
room: BareJid,
|
||||
nick: Option<String>,
|
||||
nick: &String,
|
||||
password: Option<String>,
|
||||
lang: &str,
|
||||
status: &str,
|
||||
) {
|
||||
let presence = create_muc_join_presence_stanza(room, password, lang, status, nick);
|
||||
let _ = client.send_stanza(presence.into()).await;
|
||||
}
|
||||
|
||||
/// Create a Presence stanza for joining a MUC room with a specific nickname.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `room`: The JID of the room to join.
|
||||
/// - `password`: The password to use to join the room, if any.
|
||||
/// - `lang`: The language of the status message.
|
||||
/// - `status`: The status message to send.
|
||||
/// - `nick`: The nickname to use in the room.
|
||||
pub fn create_muc_join_presence_stanza(
|
||||
room: BareJid,
|
||||
password: Option<String>,
|
||||
lang: &str,
|
||||
status: &str,
|
||||
nick: &String,
|
||||
) -> Presence {
|
||||
// Combine the room JID with the nickname to create the JID of the room occupant.
|
||||
let room_jid = room.with_resource_str(&nick).unwrap();
|
||||
|
||||
// Create a presence stanza.
|
||||
let mut presence = Presence::new(PresenceType::None).with_to(room_jid);
|
||||
|
||||
// MUC presence stanza's must have a Muc payload.
|
||||
let mut muc = Muc::new();
|
||||
|
||||
// If a password is provided, include it in the Muc payload.
|
||||
if let Some(password) = password {
|
||||
muc = muc.with_password(password);
|
||||
}
|
||||
|
||||
let nick = nick.unwrap_or_else(|| agent.default_nick.read().unwrap().clone());
|
||||
let room_jid = room.with_resource_str(&nick).unwrap();
|
||||
let mut presence = Presence::new(PresenceType::None).with_to(room_jid);
|
||||
// Add the Muc payload to the presence stanza.
|
||||
presence.add_payload(muc);
|
||||
|
||||
// Set the user-readable status message.
|
||||
presence.set_status(String::from(lang), String::from(status));
|
||||
let _ = agent.client.send_stanza(presence.into()).await;
|
||||
|
||||
// Return the presence stanza.
|
||||
presence
|
||||
}
|
||||
|
||||
/// Send a "leave room" request to the server (specifically, an "unavailable" presence stanza).
|
||||
|
@ -51,17 +82,40 @@ pub async fn join_room<C: ServerConnector>(
|
|||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `client`: The XMPP client to use to send the status message.
|
||||
/// * `room_jid`: The JID of the room to leave.
|
||||
/// * `nickname`: The nickname to use in the room.
|
||||
/// * `lang`: The language of the status message.
|
||||
/// * `status`: The status message to send.
|
||||
pub async fn leave_room<C: ServerConnector>(
|
||||
agent: &mut Agent<C>,
|
||||
client: &mut TokioXmppClient<C>,
|
||||
room_jid: BareJid,
|
||||
nickname: RoomNick,
|
||||
lang: impl Into<String>,
|
||||
status: impl Into<String>,
|
||||
) {
|
||||
let presence = create_muc_leave_room_status(room_jid, nickname, lang, status);
|
||||
|
||||
// Send the presence stanza.
|
||||
if let Err(e) = client.send_stanza(presence.into()).await {
|
||||
// Report any errors to the log.
|
||||
error!("Failed to send leave room presence: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a Presence stanza for leaving a MUC room that we're in with a specific nickname.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `room_jid`: The JID of the room to leave.
|
||||
/// - `nickname`: The nickname we are present in the room with.
|
||||
/// - `lang`: The language of the status message.
|
||||
/// - `status`: The status message to send.
|
||||
pub fn create_muc_leave_room_status(
|
||||
room_jid: BareJid,
|
||||
nickname: RoomNick,
|
||||
lang: impl Into<String> + Sized,
|
||||
status: impl Into<String> + Sized,
|
||||
) -> Presence {
|
||||
// XEP-0045 specifies that, to leave a room, the client must send a presence stanza
|
||||
// with type="unavailable".
|
||||
let mut presence = Presence::new(PresenceType::Unavailable).with_to(
|
||||
|
@ -74,10 +128,5 @@ pub async fn leave_room<C: ServerConnector>(
|
|||
// TODO: Should this be optional? The XEP says "MAY", but the method signature requires the arguments.
|
||||
// XEP-0045: "The occupant MAY include normal <status/> information in the unavailable presence stanzas"
|
||||
presence.set_status(lang, status);
|
||||
|
||||
// Send the presence stanza.
|
||||
if let Err(e) = agent.client.send_stanza(presence.into()).await {
|
||||
// Report any errors to the log.
|
||||
error!("Failed to send leave room presence: {}", e);
|
||||
}
|
||||
presence
|
||||
}
|
||||
|
|
|
@ -14,23 +14,44 @@ use tokio_xmpp::{
|
|||
|
||||
use crate::Agent;
|
||||
|
||||
pub async fn upload_file_with<C: ServerConnector>(
|
||||
agent: &mut Agent<C>,
|
||||
service: &str,
|
||||
path: &Path,
|
||||
) {
|
||||
/// Upload a file to the HTTP server associated with a given Jid.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `agent`: The XMPP agent through which to negociate the request.
|
||||
/// - `service`: The Jid of the HTTP server to upload the file to.
|
||||
/// - `path`: The path to the file to upload.
|
||||
pub async fn upload_file_with<C: ServerConnector>(agent: &mut Agent<C>, service: Jid, path: &Path) {
|
||||
// Create the IQ request to upload the file.
|
||||
let request =
|
||||
Iq::from_get("upload1", slotslot_request_for_file(path).await).with_to(service.clone());
|
||||
|
||||
// Record the upload request so we can handle the response later.
|
||||
agent
|
||||
.uploads
|
||||
.push((String::from("upload1"), service, path.to_path_buf()));
|
||||
|
||||
// Send the request to the server.
|
||||
agent.client.send_stanza(request.into()).await.unwrap();
|
||||
}
|
||||
|
||||
/// Create a SlotRequest for a file, representing a request for a URL to upload the file to.
|
||||
///
|
||||
/// Note: this function is async because it accesses the file system to read the file's metadata.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `path`: The path to the file to upload.
|
||||
async fn slotslot_request_for_file(path: &Path) -> SlotRequest {
|
||||
// Extract the file's name.
|
||||
let name = path.file_name().unwrap().to_str().unwrap().to_string();
|
||||
|
||||
// Open the file and read its size.
|
||||
let file = File::open(path).await.unwrap();
|
||||
let size = file.metadata().await.unwrap().len();
|
||||
let slot_request = SlotRequest {
|
||||
|
||||
// Create a SlotRequest for the file.
|
||||
SlotRequest {
|
||||
filename: name,
|
||||
size: size,
|
||||
content_type: None,
|
||||
};
|
||||
let to = service.parse::<Jid>().unwrap();
|
||||
let request = Iq::from_get("upload1", slot_request).with_to(to.clone());
|
||||
agent
|
||||
.uploads
|
||||
.push((String::from("upload1"), to, path.to_path_buf()));
|
||||
agent.client.send_stanza(request.into()).await.unwrap();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue