2019-06-10 23:09:20 +02:00
|
|
|
|
// Copyright (c) 2017, 2018 lumi <lumi@pew.im>
|
|
|
|
|
// Copyright (c) 2017, 2018, 2019 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
|
|
|
|
|
// Copyright (c) 2017, 2018, 2019 Maxime “pep” Buquet <pep@bouah.net>
|
|
|
|
|
// Copyright (c) 2017, 2018 Astro <astro@spaceboyz.net>
|
|
|
|
|
// Copyright (c) 2017 Bastien Orivel <eijebong@bananium.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/.
|
|
|
|
|
|
2017-02-28 12:38:00 +01:00
|
|
|
|
#![deny(missing_docs)]
|
|
|
|
|
|
2023-06-20 20:07:26 +02:00
|
|
|
|
//! Represents XMPP addresses, also known as JabberIDs (JIDs) for the [XMPP](https://xmpp.org/)
|
|
|
|
|
//! protocol. A [`Jid`] can have between one and three parts in the form `node@domain/resource`:
|
|
|
|
|
//! - the (optional) node part designates a specific account/service on a server, for example
|
|
|
|
|
//! `username@server.com`
|
|
|
|
|
//! - the domain part designates a server, for example `irc.jabberfr.org`
|
|
|
|
|
//! - the (optional) resource part designates a more specific client, such as a participant in a
|
|
|
|
|
//! groupchat (`jabberfr@chat.jabberfr.org/user`) or a specific client device associated with an
|
|
|
|
|
//! account (`user@example.com/dino`)
|
2017-02-27 16:42:09 +01:00
|
|
|
|
//!
|
2023-06-20 20:07:26 +02:00
|
|
|
|
//! The [`Jid`] enum can be one of two variants, containing a more specific type:
|
|
|
|
|
//! - [`BareJid`] (`Jid::Bare` variant): a JID without a resource
|
|
|
|
|
//! - [`FullJid`] (`Jid::Full` variant): a JID with a resource
|
|
|
|
|
//!
|
|
|
|
|
//! Jids as per the XMPP protocol only ever contain valid UTF-8. However, creating any form of Jid
|
|
|
|
|
//! can fail in one of the following cases:
|
|
|
|
|
//! - wrong syntax: creating a Jid with an empty (yet declared) node or resource part, such as
|
|
|
|
|
//! `@example.com` or `user@example.com/`
|
|
|
|
|
//! - stringprep error: some characters were invalid according to the stringprep algorithm, such as
|
|
|
|
|
//! mixing left-to-write and right-to-left characters
|
2017-02-27 15:35:57 +01:00
|
|
|
|
|
2023-06-20 12:49:20 +02:00
|
|
|
|
use core::num::NonZeroU16;
|
2024-04-15 17:09:49 +02:00
|
|
|
|
use std::borrow::{Borrow, Cow};
|
2024-04-15 18:58:02 +02:00
|
|
|
|
use std::cmp::Ordering;
|
2018-12-18 16:40:44 +01:00
|
|
|
|
use std::fmt;
|
2024-04-15 18:58:02 +02:00
|
|
|
|
use std::hash::{Hash, Hasher};
|
2024-04-15 17:03:57 +02:00
|
|
|
|
use std::ops::Deref;
|
2017-02-27 15:35:57 +01:00
|
|
|
|
use std::str::FromStr;
|
|
|
|
|
|
2024-04-15 17:03:57 +02:00
|
|
|
|
use memchr::memchr;
|
|
|
|
|
|
|
|
|
|
use stringprep::{nameprep, nodeprep, resourceprep};
|
|
|
|
|
|
2020-04-30 23:19:06 +02:00
|
|
|
|
#[cfg(feature = "serde")]
|
2020-05-01 02:26:58 +02:00
|
|
|
|
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
2020-04-30 23:19:06 +02:00
|
|
|
|
|
2023-10-29 13:51:23 +01:00
|
|
|
|
#[cfg(feature = "quote")]
|
|
|
|
|
use proc_macro2::TokenStream;
|
|
|
|
|
#[cfg(feature = "quote")]
|
|
|
|
|
use quote::{quote, ToTokens};
|
|
|
|
|
|
2024-04-15 17:03:57 +02:00
|
|
|
|
#[cfg(feature = "minidom")]
|
|
|
|
|
use minidom::{IntoAttributeValue, Node};
|
|
|
|
|
|
2023-06-19 23:54:08 +02:00
|
|
|
|
mod error;
|
2023-06-20 12:51:37 +02:00
|
|
|
|
pub use crate::error::Error;
|
2019-09-05 18:42:22 +02:00
|
|
|
|
|
2023-06-21 13:52:31 +02:00
|
|
|
|
mod parts;
|
2024-03-03 15:53:09 +01:00
|
|
|
|
pub use parts::{DomainPart, DomainRef, NodePart, NodeRef, ResourcePart, ResourceRef};
|
2023-06-21 13:52:31 +02:00
|
|
|
|
|
2024-04-15 17:03:57 +02:00
|
|
|
|
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(())
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-22 00:52:02 +02:00
|
|
|
|
|
2024-04-15 17:03:57 +02:00
|
|
|
|
/// A struct representing a Jabber ID (JID).
|
|
|
|
|
///
|
|
|
|
|
/// This JID can either be "bare" (without a `/resource` suffix) or full (with
|
|
|
|
|
/// a resource suffix).
|
|
|
|
|
///
|
|
|
|
|
/// In many APIs, it is appropriate to use the more specific types
|
|
|
|
|
/// ([`BareJid`] or [`FullJid`]) instead, as these two JID types are generally
|
|
|
|
|
/// used in different contexts within XMPP.
|
|
|
|
|
///
|
|
|
|
|
/// This dynamic type on the other hand can be used in contexts where it is
|
|
|
|
|
/// not known, at compile-time, whether a JID is full or bare.
|
2024-04-15 18:58:02 +02:00
|
|
|
|
#[derive(Debug, Clone, Eq)]
|
2024-04-15 17:03:57 +02:00
|
|
|
|
pub struct Jid {
|
|
|
|
|
normalized: String,
|
|
|
|
|
at: Option<NonZeroU16>,
|
|
|
|
|
slash: Option<NonZeroU16>,
|
2019-04-22 00:52:02 +02:00
|
|
|
|
}
|
|
|
|
|
|
2024-04-15 18:58:02 +02:00
|
|
|
|
impl PartialEq for Jid {
|
|
|
|
|
fn eq(&self, other: &Jid) -> bool {
|
|
|
|
|
self.normalized == other.normalized
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PartialOrd for Jid {
|
|
|
|
|
fn partial_cmp(&self, other: &Jid) -> Option<Ordering> {
|
|
|
|
|
self.normalized.partial_cmp(&other.normalized)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Ord for Jid {
|
|
|
|
|
fn cmp(&self, other: &Jid) -> Ordering {
|
|
|
|
|
self.normalized.cmp(&other.normalized)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Hash for Jid {
|
|
|
|
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
|
|
|
self.normalized.hash(state)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-22 00:52:02 +02:00
|
|
|
|
impl FromStr for Jid {
|
2023-06-20 12:51:37 +02:00
|
|
|
|
type Err = Error;
|
2019-04-22 00:52:02 +02:00
|
|
|
|
|
2024-04-15 17:03:57 +02:00
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
|
|
|
Self::new(s)
|
2019-04-22 00:52:02 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-06 14:55:19 +02:00
|
|
|
|
impl From<BareJid> for Jid {
|
2024-04-15 17:03:57 +02:00
|
|
|
|
fn from(other: BareJid) -> Self {
|
|
|
|
|
other.inner
|
2019-07-06 14:55:19 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<FullJid> for Jid {
|
2024-04-15 17:03:57 +02:00
|
|
|
|
fn from(other: FullJid) -> Self {
|
|
|
|
|
other.inner
|
2019-07-06 14:55:19 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-31 15:17:51 +02:00
|
|
|
|
impl fmt::Display for Jid {
|
2024-04-15 17:03:57 +02:00
|
|
|
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
|
fmt.write_str(&self.normalized)
|
2019-08-31 15:17:51 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-20 19:01:25 +02:00
|
|
|
|
impl Jid {
|
2023-06-20 20:07:26 +02:00
|
|
|
|
/// Constructs a Jabber ID from a string. This is of the form
|
|
|
|
|
/// `node`@`domain`/`resource`, where node and resource parts are optional.
|
2023-06-21 13:52:31 +02:00
|
|
|
|
/// If you want a non-fallible version, use [`Jid::from_parts`] instead.
|
2023-06-20 12:49:20 +02:00
|
|
|
|
///
|
|
|
|
|
/// # Examples
|
|
|
|
|
///
|
|
|
|
|
/// ```
|
|
|
|
|
/// use jid::Jid;
|
2023-06-20 12:51:37 +02:00
|
|
|
|
/// # use jid::Error;
|
2023-06-20 12:49:20 +02:00
|
|
|
|
///
|
2023-06-20 12:51:37 +02:00
|
|
|
|
/// # fn main() -> Result<(), Error> {
|
2023-06-20 12:49:20 +02:00
|
|
|
|
/// let jid = Jid::new("node@domain/resource")?;
|
|
|
|
|
///
|
2024-03-03 16:15:04 +01:00
|
|
|
|
/// assert_eq!(jid.node().map(|x| x.as_str()), Some("node"));
|
|
|
|
|
/// assert_eq!(jid.domain().as_str(), "domain");
|
|
|
|
|
/// assert_eq!(jid.resource().map(|x| x.as_str()), Some("resource"));
|
2023-06-20 12:49:20 +02:00
|
|
|
|
/// # Ok(())
|
|
|
|
|
/// # }
|
|
|
|
|
/// ```
|
2024-04-15 17:03:57 +02:00
|
|
|
|
pub fn new(unnormalized: &str) -> Result<Jid, 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;
|
2023-06-20 12:49:20 +02:00
|
|
|
|
}
|
2024-04-15 17:03:57 +02:00
|
|
|
|
|
|
|
|
|
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(Self {
|
|
|
|
|
normalized,
|
|
|
|
|
at: orig_at.and_then(|x| NonZeroU16::new(x as u16)),
|
|
|
|
|
slash: orig_slash.and_then(|x| NonZeroU16::new(x as u16)),
|
|
|
|
|
})
|
2023-06-20 12:49:20 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-07-30 14:48:32 +02:00
|
|
|
|
/// Returns the inner String of this JID.
|
|
|
|
|
pub fn into_inner(self) -> String {
|
2024-04-15 17:03:57 +02:00
|
|
|
|
self.normalized
|
2023-07-30 14:48:32 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-06-21 13:52:31 +02:00
|
|
|
|
/// Build a [`Jid`] from typed parts. This method cannot fail because it uses parts that have
|
|
|
|
|
/// already been parsed and stringprepped into [`NodePart`], [`DomainPart`], and [`ResourcePart`].
|
2024-03-08 13:31:26 +01:00
|
|
|
|
///
|
|
|
|
|
/// This method allocates and does not consume the typed parts. To avoid
|
|
|
|
|
/// allocation if both `node` and `resource` are known to be `None` and
|
|
|
|
|
/// `domain` is owned, you can use `domain.into()`.
|
2023-06-21 13:52:31 +02:00
|
|
|
|
pub fn from_parts(
|
2024-03-03 16:15:04 +01:00
|
|
|
|
node: Option<&NodeRef>,
|
|
|
|
|
domain: &DomainRef,
|
|
|
|
|
resource: Option<&ResourceRef>,
|
2024-04-15 17:03:57 +02:00
|
|
|
|
) -> Self {
|
|
|
|
|
match resource {
|
|
|
|
|
Some(resource) => FullJid::from_parts(node, domain, resource).into(),
|
|
|
|
|
None => BareJid::from_parts(node, domain).into(),
|
2023-06-21 13:52:31 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-03 16:15:04 +01:00
|
|
|
|
/// The optional node part of the JID as reference.
|
|
|
|
|
pub fn node(&self) -> Option<&NodeRef> {
|
2024-04-15 17:03:57 +02:00
|
|
|
|
self.at.map(|at| {
|
|
|
|
|
let at = u16::from(at) as usize;
|
|
|
|
|
NodeRef::from_str_unchecked(&self.normalized[..at])
|
|
|
|
|
})
|
2019-07-20 19:01:25 +02:00
|
|
|
|
}
|
|
|
|
|
|
2024-03-03 16:15:04 +01:00
|
|
|
|
/// The domain part of the JID as reference
|
|
|
|
|
pub fn domain(&self) -> &DomainRef {
|
2024-04-15 17:03:57 +02:00
|
|
|
|
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),
|
2019-07-20 19:01:25 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-21 18:30:25 +02:00
|
|
|
|
/// The optional resource of the Jabber ID. It is guaranteed to be present when the JID is
|
|
|
|
|
/// a Full variant, which you can check with [`Jid::is_full`].
|
2024-03-03 16:15:04 +01:00
|
|
|
|
pub fn resource(&self) -> Option<&ResourceRef> {
|
2024-04-15 17:03:57 +02:00
|
|
|
|
self.slash.map(|slash| {
|
|
|
|
|
let slash = u16::from(slash) as usize;
|
|
|
|
|
ResourceRef::from_str_unchecked(&self.normalized[slash + 1..])
|
|
|
|
|
})
|
2023-06-20 12:49:20 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-06-20 20:07:26 +02:00
|
|
|
|
/// Allocate a new [`BareJid`] from this JID, discarding the resource.
|
2023-06-20 12:49:20 +02:00
|
|
|
|
pub fn to_bare(&self) -> BareJid {
|
2024-04-15 17:03:57 +02:00
|
|
|
|
BareJid::from_parts(self.node(), self.domain())
|
2023-06-20 12:49:20 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-06-20 20:07:26 +02:00
|
|
|
|
/// Transforms this JID into a [`BareJid`], throwing away the resource.
|
2024-04-15 17:03:57 +02:00
|
|
|
|
///
|
|
|
|
|
/// ```
|
|
|
|
|
/// # use jid::{BareJid, Jid};
|
|
|
|
|
/// let jid: Jid = "foo@bar/baz".parse().unwrap();
|
|
|
|
|
/// let bare = jid.into_bare();
|
|
|
|
|
/// assert_eq!(bare.to_string(), "foo@bar");
|
|
|
|
|
/// ```
|
|
|
|
|
pub fn into_bare(mut self) -> BareJid {
|
|
|
|
|
if let Some(slash) = self.slash {
|
|
|
|
|
// truncate the string
|
|
|
|
|
self.normalized.truncate(slash.get() as usize);
|
|
|
|
|
self.slash = None;
|
2019-09-08 22:09:25 +02:00
|
|
|
|
}
|
2024-04-15 17:03:57 +02:00
|
|
|
|
BareJid { inner: self }
|
2019-09-08 22:09:25 +02:00
|
|
|
|
}
|
2023-06-21 18:30:25 +02:00
|
|
|
|
|
2024-04-15 17:03:57 +02:00
|
|
|
|
/// Checks if the JID is a full JID.
|
2023-06-21 18:30:25 +02:00
|
|
|
|
pub fn is_full(&self) -> bool {
|
2024-04-15 17:03:57 +02:00
|
|
|
|
self.slash.is_some()
|
2023-06-21 18:30:25 +02:00
|
|
|
|
}
|
|
|
|
|
|
2024-04-15 17:03:57 +02:00
|
|
|
|
/// Checks if the JID is a bare JID.
|
2023-06-21 18:30:25 +02:00
|
|
|
|
pub fn is_bare(&self) -> bool {
|
2024-04-15 17:03:57 +02:00
|
|
|
|
self.slash.is_none()
|
2023-06-21 18:30:25 +02:00
|
|
|
|
}
|
2024-03-09 09:00:36 +01:00
|
|
|
|
|
|
|
|
|
/// Return a reference to the canonical string representation of the JID.
|
|
|
|
|
pub fn as_str(&self) -> &str {
|
2024-04-15 17:03:57 +02:00
|
|
|
|
&self.normalized
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Try to convert this Jid to a [`FullJid`] if it contains a resource
|
|
|
|
|
/// and return a [`BareJid`] otherwise.
|
|
|
|
|
///
|
|
|
|
|
/// This is useful for match blocks:
|
|
|
|
|
///
|
|
|
|
|
/// ```
|
|
|
|
|
/// # use jid::Jid;
|
|
|
|
|
/// let jid: Jid = "foo@bar".parse().unwrap();
|
|
|
|
|
/// match jid.try_into_full() {
|
|
|
|
|
/// Ok(full) => println!("it is full: {:?}", full),
|
|
|
|
|
/// Err(bare) => println!("it is bare: {:?}", bare),
|
|
|
|
|
/// }
|
|
|
|
|
/// ```
|
|
|
|
|
pub fn try_into_full(self) -> Result<FullJid, BareJid> {
|
|
|
|
|
if self.slash.is_some() {
|
|
|
|
|
Ok(FullJid { inner: self })
|
|
|
|
|
} else {
|
|
|
|
|
Err(BareJid { inner: self })
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Try to convert this Jid reference to a [`&FullJid`][`FullJid`] if it
|
|
|
|
|
/// contains a resource and return a [`&BareJid`][`BareJid`] otherwise.
|
|
|
|
|
///
|
|
|
|
|
/// This is useful for match blocks:
|
|
|
|
|
///
|
|
|
|
|
/// ```
|
|
|
|
|
/// # use jid::Jid;
|
|
|
|
|
/// let jid: Jid = "foo@bar".parse().unwrap();
|
|
|
|
|
/// match jid.try_as_full() {
|
|
|
|
|
/// Ok(full) => println!("it is full: {:?}", full),
|
|
|
|
|
/// Err(bare) => println!("it is bare: {:?}", bare),
|
|
|
|
|
/// }
|
|
|
|
|
/// ```
|
|
|
|
|
pub fn try_as_full(&self) -> Result<&FullJid, &BareJid> {
|
|
|
|
|
if self.slash.is_some() {
|
|
|
|
|
Ok(unsafe {
|
|
|
|
|
// SAFETY: FullJid is #[repr(transparent)] of Jid
|
|
|
|
|
// SOUNDNESS: we asserted that self.slash is set above
|
|
|
|
|
std::mem::transmute::<&Jid, &FullJid>(self)
|
|
|
|
|
})
|
|
|
|
|
} else {
|
|
|
|
|
Err(unsafe {
|
|
|
|
|
// SAFETY: BareJid is #[repr(transparent)] of Jid
|
|
|
|
|
// SOUNDNESS: we asserted that self.slash is unset above
|
|
|
|
|
std::mem::transmute::<&Jid, &BareJid>(self)
|
|
|
|
|
})
|
2024-03-09 09:00:36 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
2024-04-15 17:03:57 +02:00
|
|
|
|
|
|
|
|
|
/// Try to convert this mutable Jid reference to a
|
|
|
|
|
/// [`&mut FullJid`][`FullJid`] if it contains a resource and return a
|
|
|
|
|
/// [`&mut BareJid`][`BareJid`] otherwise.
|
|
|
|
|
pub fn try_as_full_mut(&mut self) -> Result<&mut FullJid, &mut BareJid> {
|
|
|
|
|
if self.slash.is_some() {
|
|
|
|
|
Ok(unsafe {
|
|
|
|
|
// SAFETY: FullJid is #[repr(transparent)] of Jid
|
|
|
|
|
// SOUNDNESS: we asserted that self.slash is set above
|
|
|
|
|
std::mem::transmute::<&mut Jid, &mut FullJid>(self)
|
|
|
|
|
})
|
|
|
|
|
} else {
|
|
|
|
|
Err(unsafe {
|
|
|
|
|
// SAFETY: BareJid is #[repr(transparent)] of Jid
|
|
|
|
|
// SOUNDNESS: we asserted that self.slash is unset above
|
|
|
|
|
std::mem::transmute::<&mut Jid, &mut BareJid>(self)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[doc(hidden)]
|
|
|
|
|
#[allow(non_snake_case)]
|
|
|
|
|
#[deprecated(
|
|
|
|
|
since = "0.11.0",
|
|
|
|
|
note = "use Jid::from (for construction of Jid values) or Jid::try_into_full/Jid::try_as_full (for match blocks) instead"
|
|
|
|
|
)]
|
|
|
|
|
pub fn Bare(other: BareJid) -> Self {
|
|
|
|
|
Self::from(other)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[doc(hidden)]
|
|
|
|
|
#[allow(non_snake_case)]
|
|
|
|
|
#[deprecated(
|
|
|
|
|
since = "0.11.0",
|
|
|
|
|
note = "use Jid::from (for construction of Jid values) or Jid::try_into_full/Jid::try_as_full (for match blocks) instead"
|
|
|
|
|
)]
|
2024-04-16 18:19:47 +02:00
|
|
|
|
pub fn Full(other: FullJid) -> Self {
|
2024-04-15 17:03:57 +02:00
|
|
|
|
Self::from(other)
|
|
|
|
|
}
|
2019-09-08 22:09:25 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl TryFrom<Jid> for FullJid {
|
2023-06-20 12:51:37 +02:00
|
|
|
|
type Error = Error;
|
2019-09-08 22:09:25 +02:00
|
|
|
|
|
2024-04-15 17:03:57 +02:00
|
|
|
|
fn try_from(inner: Jid) -> Result<Self, Self::Error> {
|
|
|
|
|
if inner.slash.is_none() {
|
|
|
|
|
return Err(Error::ResourceMissingInFullJid);
|
2019-09-08 22:09:25 +02:00
|
|
|
|
}
|
2024-04-15 17:03:57 +02:00
|
|
|
|
Ok(Self { inner })
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl TryFrom<Jid> for BareJid {
|
|
|
|
|
type Error = Error;
|
|
|
|
|
|
|
|
|
|
fn try_from(inner: Jid) -> Result<Self, Self::Error> {
|
|
|
|
|
if inner.slash.is_some() {
|
|
|
|
|
return Err(Error::ResourceInBareJid);
|
|
|
|
|
}
|
|
|
|
|
Ok(Self { inner })
|
2019-09-08 22:09:25 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-10 20:28:32 +01:00
|
|
|
|
impl PartialEq<Jid> for FullJid {
|
|
|
|
|
fn eq(&self, other: &Jid) -> bool {
|
2024-04-15 17:03:57 +02:00
|
|
|
|
&self.inner == other
|
2020-12-10 20:28:32 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PartialEq<Jid> for BareJid {
|
|
|
|
|
fn eq(&self, other: &Jid) -> bool {
|
2024-04-15 17:03:57 +02:00
|
|
|
|
&self.inner == other
|
2020-12-10 20:28:32 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PartialEq<FullJid> for Jid {
|
|
|
|
|
fn eq(&self, other: &FullJid) -> bool {
|
2024-04-15 17:03:57 +02:00
|
|
|
|
self == &other.inner
|
2020-12-10 20:28:32 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PartialEq<BareJid> for Jid {
|
|
|
|
|
fn eq(&self, other: &BareJid) -> bool {
|
2024-04-15 17:03:57 +02:00
|
|
|
|
self == &other.inner
|
2020-12-10 20:28:32 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-20 20:07:26 +02:00
|
|
|
|
/// A struct representing a full Jabber ID, with a resource part.
|
2017-02-27 15:35:57 +01:00
|
|
|
|
///
|
2023-06-20 20:07:26 +02:00
|
|
|
|
/// A full JID is composed of 3 components, of which only the node is optional:
|
2017-02-27 15:35:57 +01:00
|
|
|
|
///
|
2023-06-20 20:07:26 +02:00
|
|
|
|
/// - the (optional) node part is the part before the (optional) `@`.
|
|
|
|
|
/// - the domain part is the mandatory part between the (optional) `@` and before the `/`.
|
|
|
|
|
/// - the resource part after the `/`.
|
2019-06-10 22:55:15 +02:00
|
|
|
|
///
|
2023-06-20 20:07:26 +02:00
|
|
|
|
/// Unlike a [`BareJid`], it always contains a resource, and should only be used when you are
|
|
|
|
|
/// certain there is no case where a resource can be missing. Otherwise, use a [`Jid`] or
|
|
|
|
|
/// [`BareJid`].
|
2024-04-15 17:03:57 +02:00
|
|
|
|
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
|
|
|
#[repr(transparent)] // WARNING: Jid::try_as_* relies on this for safety!
|
2019-04-22 00:52:02 +02:00
|
|
|
|
pub struct FullJid {
|
2024-04-15 17:03:57 +02:00
|
|
|
|
inner: Jid,
|
2017-02-27 15:35:57 +01:00
|
|
|
|
}
|
|
|
|
|
|
2023-06-20 20:07:26 +02:00
|
|
|
|
/// A struct representing a bare Jabber ID, without a resource part.
|
2019-04-22 00:52:02 +02:00
|
|
|
|
///
|
2023-06-20 20:07:26 +02:00
|
|
|
|
/// A bare JID is composed of 2 components, of which only the node is optional:
|
|
|
|
|
/// - the (optional) node part is the part before the (optional) `@`.
|
|
|
|
|
/// - the domain part is the mandatory part between the (optional) `@` and before the `/`.
|
2019-06-10 22:55:15 +02:00
|
|
|
|
///
|
2023-06-20 20:07:26 +02:00
|
|
|
|
/// Unlike a [`FullJid`], it can’t contain a resource, and should only be used when you are certain
|
|
|
|
|
/// there is no case where a resource can be set. Otherwise, use a [`Jid`] or [`FullJid`].
|
2024-04-15 17:03:57 +02:00
|
|
|
|
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
|
|
|
#[repr(transparent)] // WARNING: Jid::try_as_* relies on this for safety!
|
2019-04-22 00:52:02 +02:00
|
|
|
|
pub struct BareJid {
|
2024-04-15 17:03:57 +02:00
|
|
|
|
inner: Jid,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Deref for FullJid {
|
|
|
|
|
type Target = Jid;
|
|
|
|
|
|
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
|
|
|
&self.inner
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Deref for BareJid {
|
|
|
|
|
type Target = Jid;
|
|
|
|
|
|
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
|
|
|
&self.inner
|
|
|
|
|
}
|
2019-04-22 00:52:02 +02:00
|
|
|
|
}
|
|
|
|
|
|
2024-04-15 17:09:49 +02:00
|
|
|
|
impl Borrow<Jid> for FullJid {
|
|
|
|
|
fn borrow(&self) -> &Jid {
|
|
|
|
|
&self.inner
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Borrow<Jid> for BareJid {
|
|
|
|
|
fn borrow(&self) -> &Jid {
|
|
|
|
|
&self.inner
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-22 00:52:02 +02:00
|
|
|
|
impl fmt::Debug for FullJid {
|
2024-04-15 17:03:57 +02:00
|
|
|
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
2024-03-03 15:55:11 +01:00
|
|
|
|
fmt.debug_tuple("FullJid").field(&self.inner).finish()
|
2017-04-30 22:44:17 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-22 00:52:02 +02:00
|
|
|
|
impl fmt::Debug for BareJid {
|
2024-04-15 17:03:57 +02:00
|
|
|
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
2024-03-03 15:55:11 +01:00
|
|
|
|
fmt.debug_tuple("BareJid").field(&self.inner).finish()
|
2019-04-22 00:52:02 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl fmt::Display for FullJid {
|
2024-04-15 17:03:57 +02:00
|
|
|
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
|
fmt::Display::fmt(&self.inner, fmt)
|
2019-04-22 00:52:02 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl fmt::Display for BareJid {
|
2024-04-15 17:03:57 +02:00
|
|
|
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
|
fmt::Display::fmt(&self.inner, fmt)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "serde")]
|
|
|
|
|
impl Serialize for Jid {
|
|
|
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
|
|
|
where
|
|
|
|
|
S: Serializer,
|
|
|
|
|
{
|
|
|
|
|
serializer.serialize_str(&self.normalized)
|
2017-02-27 15:35:57 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-01 02:26:58 +02:00
|
|
|
|
#[cfg(feature = "serde")]
|
|
|
|
|
impl Serialize for FullJid {
|
|
|
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
|
|
|
where
|
|
|
|
|
S: Serializer,
|
|
|
|
|
{
|
2024-04-15 17:03:57 +02:00
|
|
|
|
self.inner.serialize(serializer)
|
2020-05-01 02:26:58 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "serde")]
|
|
|
|
|
impl Serialize for BareJid {
|
|
|
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
|
|
|
where
|
|
|
|
|
S: Serializer,
|
|
|
|
|
{
|
2024-04-15 17:03:57 +02:00
|
|
|
|
self.inner.serialize(serializer)
|
2020-05-01 02:26:58 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-22 00:52:02 +02:00
|
|
|
|
impl FromStr for FullJid {
|
2023-06-20 12:51:37 +02:00
|
|
|
|
type Err = Error;
|
2019-04-22 00:52:02 +02:00
|
|
|
|
|
2024-04-15 17:03:57 +02:00
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
|
|
|
Self::new(s)
|
2017-02-27 15:35:57 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-01 02:26:58 +02:00
|
|
|
|
#[cfg(feature = "serde")]
|
2024-04-15 17:03:57 +02:00
|
|
|
|
impl<'de> Deserialize<'de> for Jid {
|
2020-05-01 02:26:58 +02:00
|
|
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
|
|
|
where
|
|
|
|
|
D: Deserializer<'de>,
|
|
|
|
|
{
|
|
|
|
|
let s = String::deserialize(deserializer)?;
|
2024-04-15 17:03:57 +02:00
|
|
|
|
Jid::new(&s).map_err(de::Error::custom)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "serde")]
|
|
|
|
|
impl<'de> Deserialize<'de> for FullJid {
|
|
|
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
|
|
|
where
|
|
|
|
|
D: Deserializer<'de>,
|
|
|
|
|
{
|
|
|
|
|
let jid = Jid::deserialize(deserializer)?;
|
|
|
|
|
jid.try_into().map_err(de::Error::custom)
|
2020-05-01 02:26:58 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "serde")]
|
|
|
|
|
impl<'de> Deserialize<'de> for BareJid {
|
|
|
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
|
|
|
where
|
|
|
|
|
D: Deserializer<'de>,
|
|
|
|
|
{
|
2024-04-15 17:03:57 +02:00
|
|
|
|
let jid = Jid::deserialize(deserializer)?;
|
|
|
|
|
jid.try_into().map_err(de::Error::custom)
|
2020-05-01 02:26:58 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-29 13:51:23 +01:00
|
|
|
|
#[cfg(feature = "quote")]
|
|
|
|
|
impl ToTokens for Jid {
|
|
|
|
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
2024-04-15 17:03:57 +02:00
|
|
|
|
let s = &self.normalized;
|
|
|
|
|
tokens.extend(quote! {
|
|
|
|
|
::jid::Jid::new(#s).unwrap()
|
2023-10-29 13:51:23 +01:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "quote")]
|
|
|
|
|
impl ToTokens for FullJid {
|
|
|
|
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
2024-04-15 17:03:57 +02:00
|
|
|
|
let s = &self.inner.normalized;
|
|
|
|
|
tokens.extend(quote! {
|
|
|
|
|
::jid::FullJid::new(#s).unwrap()
|
|
|
|
|
});
|
2023-10-29 13:51:23 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "quote")]
|
|
|
|
|
impl ToTokens for BareJid {
|
|
|
|
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
2024-04-15 17:03:57 +02:00
|
|
|
|
let s = &self.inner.normalized;
|
|
|
|
|
tokens.extend(quote! {
|
|
|
|
|
::jid::BareJid::new(#s).unwrap()
|
|
|
|
|
});
|
2023-10-29 13:51:23 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-22 00:52:02 +02:00
|
|
|
|
impl FullJid {
|
2023-06-20 20:07:26 +02:00
|
|
|
|
/// Constructs a full Jabber ID containing all three components. This is of the form
|
|
|
|
|
/// `node@domain/resource`, where node part is optional.
|
2023-06-21 13:52:31 +02:00
|
|
|
|
/// If you want a non-fallible version, use [`FullJid::from_parts`] instead.
|
2017-02-27 15:35:57 +01:00
|
|
|
|
///
|
|
|
|
|
/// # Examples
|
|
|
|
|
///
|
|
|
|
|
/// ```
|
2019-04-22 00:52:02 +02:00
|
|
|
|
/// use jid::FullJid;
|
2023-06-20 12:51:37 +02:00
|
|
|
|
/// # use jid::Error;
|
2017-02-27 15:35:57 +01:00
|
|
|
|
///
|
2023-06-20 12:51:37 +02:00
|
|
|
|
/// # fn main() -> Result<(), Error> {
|
2023-06-20 12:49:20 +02:00
|
|
|
|
/// let jid = FullJid::new("node@domain/resource")?;
|
2017-02-27 15:35:57 +01:00
|
|
|
|
///
|
2024-03-03 16:15:04 +01:00
|
|
|
|
/// assert_eq!(jid.node().map(|x| x.as_str()), Some("node"));
|
|
|
|
|
/// assert_eq!(jid.domain().as_str(), "domain");
|
|
|
|
|
/// assert_eq!(jid.resource().as_str(), "resource");
|
2023-06-20 12:49:20 +02:00
|
|
|
|
/// # Ok(())
|
|
|
|
|
/// # }
|
2017-02-27 15:35:57 +01:00
|
|
|
|
/// ```
|
2024-04-15 17:03:57 +02:00
|
|
|
|
pub fn new(unnormalized: &str) -> Result<Self, Error> {
|
|
|
|
|
Jid::new(unnormalized)?.try_into()
|
2023-07-30 14:48:32 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-06-21 13:52:31 +02:00
|
|
|
|
/// Build a [`FullJid`] from typed parts. This method cannot fail because it uses parts that have
|
|
|
|
|
/// already been parsed and stringprepped into [`NodePart`], [`DomainPart`], and [`ResourcePart`].
|
|
|
|
|
/// This method allocates and does not consume the typed parts.
|
|
|
|
|
pub fn from_parts(
|
2024-03-03 16:15:04 +01:00
|
|
|
|
node: Option<&NodeRef>,
|
|
|
|
|
domain: &DomainRef,
|
|
|
|
|
resource: &ResourceRef,
|
2023-06-21 13:52:31 +02:00
|
|
|
|
) -> FullJid {
|
|
|
|
|
let (at, slash, normalized) = if let Some(node) = node {
|
|
|
|
|
// Parts are never empty so len > 0 for NonZeroU16::new is always Some
|
|
|
|
|
(
|
2024-03-03 16:15:04 +01:00
|
|
|
|
NonZeroU16::new(node.len() as u16),
|
|
|
|
|
NonZeroU16::new((node.len() + 1 + domain.len()) as u16),
|
|
|
|
|
format!(
|
|
|
|
|
"{}@{}/{}",
|
|
|
|
|
node.as_str(),
|
|
|
|
|
domain.as_str(),
|
|
|
|
|
resource.as_str()
|
|
|
|
|
),
|
2023-06-21 13:52:31 +02:00
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
(
|
|
|
|
|
None,
|
2024-03-03 16:15:04 +01:00
|
|
|
|
NonZeroU16::new(domain.len() as u16),
|
|
|
|
|
format!("{}/{}", domain.as_str(), resource.as_str()),
|
2023-06-21 13:52:31 +02:00
|
|
|
|
)
|
|
|
|
|
};
|
|
|
|
|
|
2024-04-15 17:03:57 +02:00
|
|
|
|
let inner = Jid {
|
2023-06-21 13:52:31 +02:00
|
|
|
|
normalized,
|
|
|
|
|
at,
|
|
|
|
|
slash,
|
|
|
|
|
};
|
|
|
|
|
|
2024-04-15 17:03:57 +02:00
|
|
|
|
Self { inner }
|
2017-06-12 18:40:39 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-06-20 20:07:26 +02:00
|
|
|
|
/// The optional resource of the Jabber ID. Since this is a full JID it is always present.
|
2024-03-03 16:15:04 +01:00
|
|
|
|
pub fn resource(&self) -> &ResourceRef {
|
2023-06-20 12:49:20 +02:00
|
|
|
|
self.inner.resource().unwrap()
|
|
|
|
|
}
|
2019-04-22 00:52:02 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl FromStr for BareJid {
|
2023-06-20 12:51:37 +02:00
|
|
|
|
type Err = Error;
|
2019-04-22 00:52:02 +02:00
|
|
|
|
|
2024-04-15 17:03:57 +02:00
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
|
|
|
Self::new(s)
|
2019-04-22 00:52:02 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2017-02-27 15:35:57 +01:00
|
|
|
|
|
2019-04-22 00:52:02 +02:00
|
|
|
|
impl BareJid {
|
2023-06-20 20:07:26 +02:00
|
|
|
|
/// Constructs a bare Jabber ID, containing two components. This is of the form
|
|
|
|
|
/// `node`@`domain`, where node part is optional.
|
2023-06-21 13:52:31 +02:00
|
|
|
|
/// If you want a non-fallible version, use [`BareJid::from_parts`] instead.
|
2017-06-12 18:40:39 +02:00
|
|
|
|
///
|
|
|
|
|
/// # Examples
|
|
|
|
|
///
|
|
|
|
|
/// ```
|
2019-04-22 00:52:02 +02:00
|
|
|
|
/// use jid::BareJid;
|
2023-06-20 12:51:37 +02:00
|
|
|
|
/// # use jid::Error;
|
2017-06-12 18:40:39 +02:00
|
|
|
|
///
|
2023-06-20 12:51:37 +02:00
|
|
|
|
/// # fn main() -> Result<(), Error> {
|
2023-06-20 12:49:20 +02:00
|
|
|
|
/// let jid = BareJid::new("node@domain")?;
|
2017-06-12 18:40:39 +02:00
|
|
|
|
///
|
2024-03-03 16:15:04 +01:00
|
|
|
|
/// assert_eq!(jid.node().map(|x| x.as_str()), Some("node"));
|
|
|
|
|
/// assert_eq!(jid.domain().as_str(), "domain");
|
2023-06-20 12:49:20 +02:00
|
|
|
|
/// # Ok(())
|
|
|
|
|
/// # }
|
2017-06-12 19:20:42 +02:00
|
|
|
|
/// ```
|
2024-04-15 17:03:57 +02:00
|
|
|
|
pub fn new(unnormalized: &str) -> Result<Self, Error> {
|
|
|
|
|
Jid::new(unnormalized)?.try_into()
|
2023-07-30 14:48:32 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-06-21 13:52:31 +02:00
|
|
|
|
/// Build a [`BareJid`] from typed parts. This method cannot fail because it uses parts that have
|
2024-03-08 13:31:26 +01:00
|
|
|
|
/// already been parsed and stringprepped into [`NodePart`] and [`DomainPart`].
|
|
|
|
|
///
|
|
|
|
|
/// This method allocates and does not consume the typed parts. To avoid
|
|
|
|
|
/// allocation if `node` is known to be `None` and `domain` is owned, you
|
|
|
|
|
/// can use `domain.into()`.
|
2024-04-15 17:03:57 +02:00
|
|
|
|
pub fn from_parts(node: Option<&NodeRef>, domain: &DomainRef) -> Self {
|
2023-06-21 13:52:31 +02:00
|
|
|
|
let (at, normalized) = if let Some(node) = node {
|
|
|
|
|
// Parts are never empty so len > 0 for NonZeroU16::new is always Some
|
|
|
|
|
(
|
2024-03-03 16:15:04 +01:00
|
|
|
|
NonZeroU16::new(node.len() as u16),
|
|
|
|
|
format!("{}@{}", node.as_str(), domain.as_str()),
|
2023-06-21 13:52:31 +02:00
|
|
|
|
)
|
|
|
|
|
} else {
|
2024-03-03 16:15:04 +01:00
|
|
|
|
(None, domain.to_string())
|
2023-06-21 13:52:31 +02:00
|
|
|
|
};
|
|
|
|
|
|
2024-04-15 17:03:57 +02:00
|
|
|
|
let inner = Jid {
|
2023-06-21 13:52:31 +02:00
|
|
|
|
normalized,
|
|
|
|
|
at,
|
|
|
|
|
slash: None,
|
|
|
|
|
};
|
|
|
|
|
|
2024-04-15 17:03:57 +02:00
|
|
|
|
Self { inner }
|
2017-02-27 15:35:57 +01:00
|
|
|
|
}
|
|
|
|
|
|
2023-06-21 18:30:25 +02:00
|
|
|
|
/// Constructs a [`BareJid`] from the bare JID, by specifying a [`ResourcePart`].
|
|
|
|
|
/// If you'd like to specify a stringy resource, use [`BareJid::with_resource_str`] instead.
|
2017-02-27 15:35:57 +01:00
|
|
|
|
///
|
|
|
|
|
/// # Examples
|
|
|
|
|
///
|
|
|
|
|
/// ```
|
2023-06-21 18:30:25 +02:00
|
|
|
|
/// use jid::{BareJid, ResourcePart};
|
2017-02-27 15:35:57 +01:00
|
|
|
|
///
|
2023-06-21 18:30:25 +02:00
|
|
|
|
/// let resource = ResourcePart::new("resource").unwrap();
|
2023-06-20 12:49:20 +02:00
|
|
|
|
/// let bare = BareJid::new("node@domain").unwrap();
|
2023-06-21 18:30:25 +02:00
|
|
|
|
/// let full = bare.with_resource(&resource);
|
2017-02-27 15:35:57 +01:00
|
|
|
|
///
|
2024-03-03 16:15:04 +01:00
|
|
|
|
/// assert_eq!(full.node().map(|x| x.as_str()), Some("node"));
|
|
|
|
|
/// assert_eq!(full.domain().as_str(), "domain");
|
|
|
|
|
/// assert_eq!(full.resource().as_str(), "resource");
|
2017-02-27 15:35:57 +01:00
|
|
|
|
/// ```
|
2024-03-03 16:15:04 +01:00
|
|
|
|
pub fn with_resource(&self, resource: &ResourceRef) -> FullJid {
|
2023-06-20 12:49:20 +02:00
|
|
|
|
let slash = NonZeroU16::new(self.inner.normalized.len() as u16);
|
|
|
|
|
let normalized = format!("{}/{resource}", self.inner.normalized);
|
2024-04-15 17:03:57 +02:00
|
|
|
|
let inner = Jid {
|
2023-06-20 12:49:20 +02:00
|
|
|
|
normalized,
|
|
|
|
|
at: self.inner.at,
|
|
|
|
|
slash,
|
|
|
|
|
};
|
2023-06-21 18:30:25 +02:00
|
|
|
|
|
|
|
|
|
FullJid { inner }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Constructs a [`FullJid`] from the bare JID, by specifying a stringy `resource`.
|
|
|
|
|
/// If your resource has already been parsed into a [`ResourcePart`], use [`BareJid::with_resource`].
|
|
|
|
|
///
|
|
|
|
|
/// # Examples
|
|
|
|
|
///
|
|
|
|
|
/// ```
|
|
|
|
|
/// use jid::BareJid;
|
|
|
|
|
///
|
|
|
|
|
/// let bare = BareJid::new("node@domain").unwrap();
|
|
|
|
|
/// let full = bare.with_resource_str("resource").unwrap();
|
|
|
|
|
///
|
2024-03-03 16:15:04 +01:00
|
|
|
|
/// assert_eq!(full.node().map(|x| x.as_str()), Some("node"));
|
|
|
|
|
/// assert_eq!(full.domain().as_str(), "domain");
|
|
|
|
|
/// assert_eq!(full.resource().as_str(), "resource");
|
2023-06-21 18:30:25 +02:00
|
|
|
|
/// ```
|
|
|
|
|
pub fn with_resource_str(&self, resource: &str) -> Result<FullJid, Error> {
|
|
|
|
|
let resource = ResourcePart::new(resource)?;
|
|
|
|
|
Ok(self.with_resource(&resource))
|
2017-02-27 15:35:57 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-29 06:11:30 +02:00
|
|
|
|
#[cfg(feature = "minidom")]
|
|
|
|
|
impl IntoAttributeValue for Jid {
|
|
|
|
|
fn into_attribute_value(self) -> Option<String> {
|
2023-08-10 20:16:41 +02:00
|
|
|
|
Some(self.to_string())
|
2017-07-29 06:11:30 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-31 21:24:41 +01:00
|
|
|
|
#[cfg(feature = "minidom")]
|
2021-10-11 15:35:27 +02:00
|
|
|
|
impl From<Jid> for Node {
|
2024-04-15 17:03:57 +02:00
|
|
|
|
fn from(jid: Jid) -> Self {
|
2023-08-10 20:16:41 +02:00
|
|
|
|
Node::Text(jid.to_string())
|
2017-10-31 21:24:41 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-22 00:52:02 +02:00
|
|
|
|
#[cfg(feature = "minidom")]
|
|
|
|
|
impl IntoAttributeValue for FullJid {
|
|
|
|
|
fn into_attribute_value(self) -> Option<String> {
|
2024-04-15 17:03:57 +02:00
|
|
|
|
self.inner.into_attribute_value()
|
2019-04-22 00:52:02 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "minidom")]
|
2021-10-11 15:35:27 +02:00
|
|
|
|
impl From<FullJid> for Node {
|
2024-04-15 17:03:57 +02:00
|
|
|
|
fn from(jid: FullJid) -> Self {
|
|
|
|
|
jid.inner.into()
|
2019-04-22 00:52:02 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "minidom")]
|
|
|
|
|
impl IntoAttributeValue for BareJid {
|
|
|
|
|
fn into_attribute_value(self) -> Option<String> {
|
2024-04-15 17:03:57 +02:00
|
|
|
|
self.inner.into_attribute_value()
|
2019-04-22 00:52:02 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "minidom")]
|
2021-10-11 15:35:27 +02:00
|
|
|
|
impl From<BareJid> for Node {
|
2024-04-15 17:03:57 +02:00
|
|
|
|
fn from(other: BareJid) -> Self {
|
|
|
|
|
other.inner.into()
|
2019-04-22 00:52:02 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-27 15:35:57 +01:00
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
2024-04-15 17:09:49 +02:00
|
|
|
|
use std::collections::{HashMap, HashSet};
|
2017-02-27 15:35:57 +01:00
|
|
|
|
|
2023-06-19 23:25:48 +02:00
|
|
|
|
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() {
|
2023-06-20 12:49:20 +02:00
|
|
|
|
assert_size!(BareJid, 16);
|
|
|
|
|
assert_size!(FullJid, 16);
|
2024-04-15 17:03:57 +02:00
|
|
|
|
assert_size!(Jid, 16);
|
2023-06-19 23:25:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(target_pointer_width = "64")]
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_size() {
|
2023-06-20 12:49:20 +02:00
|
|
|
|
assert_size!(BareJid, 32);
|
|
|
|
|
assert_size!(FullJid, 32);
|
2024-04-15 17:03:57 +02:00
|
|
|
|
assert_size!(Jid, 32);
|
2023-06-19 23:25:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
2017-02-27 15:35:57 +01:00
|
|
|
|
#[test]
|
2019-04-22 00:52:02 +02:00
|
|
|
|
fn can_parse_full_jids() {
|
2019-06-10 22:04:03 +02:00
|
|
|
|
assert_eq!(
|
|
|
|
|
FullJid::from_str("a@b.c/d"),
|
2023-06-20 12:49:20 +02:00
|
|
|
|
Ok(FullJid::new("a@b.c/d").unwrap())
|
2019-06-10 22:04:03 +02:00
|
|
|
|
);
|
2019-04-22 00:52:02 +02:00
|
|
|
|
assert_eq!(
|
|
|
|
|
FullJid::from_str("b.c/d"),
|
2023-06-20 12:49:20 +02:00
|
|
|
|
Ok(FullJid::new("b.c/d").unwrap())
|
2019-04-22 00:52:02 +02:00
|
|
|
|
);
|
|
|
|
|
|
2023-06-20 17:58:16 +02:00
|
|
|
|
assert_eq!(
|
|
|
|
|
FullJid::from_str("a@b.c"),
|
|
|
|
|
Err(Error::ResourceMissingInFullJid)
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
FullJid::from_str("b.c"),
|
|
|
|
|
Err(Error::ResourceMissingInFullJid)
|
|
|
|
|
);
|
2019-04-22 00:52:02 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn can_parse_bare_jids() {
|
|
|
|
|
assert_eq!(
|
2023-06-20 12:49:20 +02:00
|
|
|
|
BareJid::from_str("a@b.c"),
|
|
|
|
|
Ok(BareJid::new("a@b.c").unwrap())
|
2018-12-18 16:40:44 +01:00
|
|
|
|
);
|
2023-06-20 12:49:20 +02:00
|
|
|
|
assert_eq!(BareJid::from_str("b.c"), Ok(BareJid::new("b.c").unwrap()));
|
2017-02-27 15:35:57 +01:00
|
|
|
|
}
|
2017-04-23 15:49:00 +02:00
|
|
|
|
|
2019-04-22 00:52:02 +02:00
|
|
|
|
#[test]
|
|
|
|
|
fn can_parse_jids() {
|
|
|
|
|
let full = FullJid::from_str("a@b.c/d").unwrap();
|
|
|
|
|
let bare = BareJid::from_str("e@f.g").unwrap();
|
|
|
|
|
|
2024-04-15 17:03:57 +02:00
|
|
|
|
assert_eq!(Jid::from_str("a@b.c/d").unwrap(), full);
|
|
|
|
|
assert_eq!(Jid::from_str("e@f.g").unwrap(), bare);
|
2019-04-22 00:52:02 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn full_to_bare_jid() {
|
2023-06-20 12:49:20 +02:00
|
|
|
|
let bare: BareJid = FullJid::new("a@b.c/d").unwrap().to_bare();
|
|
|
|
|
assert_eq!(bare, BareJid::new("a@b.c").unwrap());
|
2019-04-22 00:52:02 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
2023-06-21 18:30:25 +02:00
|
|
|
|
fn bare_to_full_jid_str() {
|
2019-06-10 22:04:03 +02:00
|
|
|
|
assert_eq!(
|
2023-06-21 18:30:25 +02:00
|
|
|
|
BareJid::new("a@b.c")
|
|
|
|
|
.unwrap()
|
|
|
|
|
.with_resource_str("d")
|
|
|
|
|
.unwrap(),
|
2023-06-20 12:49:20 +02:00
|
|
|
|
FullJid::new("a@b.c/d").unwrap()
|
2019-06-10 22:04:03 +02:00
|
|
|
|
);
|
2019-04-22 00:52:02 +02:00
|
|
|
|
}
|
|
|
|
|
|
2019-07-20 19:01:25 +02:00
|
|
|
|
#[test]
|
2023-06-21 18:30:25 +02:00
|
|
|
|
fn bare_to_full_jid() {
|
2019-07-20 19:01:25 +02:00
|
|
|
|
assert_eq!(
|
2023-06-21 18:30:25 +02:00
|
|
|
|
BareJid::new("a@b.c")
|
|
|
|
|
.unwrap()
|
|
|
|
|
.with_resource(&ResourcePart::new("d").unwrap()),
|
|
|
|
|
FullJid::new("a@b.c/d").unwrap()
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn node_from_jid() {
|
|
|
|
|
let jid = Jid::new("a@b.c/d").unwrap();
|
|
|
|
|
|
2024-03-03 16:15:04 +01:00
|
|
|
|
assert_eq!(jid.node().map(|x| x.as_str()), Some("a"),);
|
2019-07-20 19:01:25 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn domain_from_jid() {
|
2023-06-21 18:30:25 +02:00
|
|
|
|
let jid = Jid::new("a@b.c").unwrap();
|
|
|
|
|
|
2024-03-03 16:15:04 +01:00
|
|
|
|
assert_eq!(jid.domain().as_str(), "b.c");
|
2023-06-21 18:30:25 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn resource_from_jid() {
|
|
|
|
|
let jid = Jid::new("a@b.c/d").unwrap();
|
|
|
|
|
|
2024-03-03 16:15:04 +01:00
|
|
|
|
assert_eq!(jid.resource().map(|x| x.as_str()), Some("d"),);
|
2019-07-20 19:01:25 +02:00
|
|
|
|
}
|
|
|
|
|
|
2019-09-08 22:09:25 +02:00
|
|
|
|
#[test]
|
|
|
|
|
fn jid_to_full_bare() {
|
2023-06-20 12:49:20 +02:00
|
|
|
|
let full = FullJid::new("a@b.c/d").unwrap();
|
|
|
|
|
let bare = BareJid::new("a@b.c").unwrap();
|
2019-09-08 22:09:25 +02:00
|
|
|
|
|
2024-04-15 17:03:57 +02:00
|
|
|
|
assert_eq!(FullJid::try_from(Jid::from(full.clone())), Ok(full.clone()));
|
2019-09-08 22:09:25 +02:00
|
|
|
|
assert_eq!(
|
2024-04-15 17:03:57 +02:00
|
|
|
|
FullJid::try_from(Jid::from(bare.clone())),
|
2023-06-20 17:58:16 +02:00
|
|
|
|
Err(Error::ResourceMissingInFullJid),
|
2019-09-08 22:09:25 +02:00
|
|
|
|
);
|
2024-04-15 17:03:57 +02:00
|
|
|
|
assert_eq!(Jid::from(full.clone().to_bare()), bare.clone());
|
|
|
|
|
assert_eq!(Jid::from(bare.clone()), bare);
|
2019-09-08 22:09:25 +02:00
|
|
|
|
}
|
|
|
|
|
|
2017-04-23 15:49:00 +02:00
|
|
|
|
#[test]
|
|
|
|
|
fn serialise() {
|
2023-08-10 20:16:41 +02:00
|
|
|
|
assert_eq!(FullJid::new("a@b/c").unwrap().to_string(), "a@b/c");
|
|
|
|
|
assert_eq!(BareJid::new("a@b").unwrap().to_string(), "a@b");
|
2017-04-23 15:49:00 +02:00
|
|
|
|
}
|
2017-07-29 06:11:30 +02:00
|
|
|
|
|
2019-10-16 01:23:21 +02:00
|
|
|
|
#[test]
|
|
|
|
|
fn hash() {
|
|
|
|
|
let _map: HashMap<Jid, String> = HashMap::new();
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-18 21:36:36 +01:00
|
|
|
|
#[test]
|
2018-03-01 16:26:44 +01:00
|
|
|
|
fn invalid_jids() {
|
2023-06-20 17:58:16 +02:00
|
|
|
|
assert_eq!(BareJid::from_str(""), Err(Error::DomainEmpty));
|
|
|
|
|
assert_eq!(BareJid::from_str("/c"), Err(Error::DomainEmpty));
|
|
|
|
|
assert_eq!(BareJid::from_str("a@/c"), Err(Error::DomainEmpty));
|
|
|
|
|
assert_eq!(BareJid::from_str("@b"), Err(Error::NodeEmpty));
|
|
|
|
|
assert_eq!(BareJid::from_str("b/"), Err(Error::ResourceEmpty));
|
|
|
|
|
|
|
|
|
|
assert_eq!(FullJid::from_str(""), Err(Error::DomainEmpty));
|
|
|
|
|
assert_eq!(FullJid::from_str("/c"), Err(Error::DomainEmpty));
|
|
|
|
|
assert_eq!(FullJid::from_str("a@/c"), Err(Error::DomainEmpty));
|
|
|
|
|
assert_eq!(FullJid::from_str("@b"), Err(Error::NodeEmpty));
|
|
|
|
|
assert_eq!(FullJid::from_str("b/"), Err(Error::ResourceEmpty));
|
|
|
|
|
assert_eq!(
|
|
|
|
|
FullJid::from_str("a@b"),
|
|
|
|
|
Err(Error::ResourceMissingInFullJid)
|
|
|
|
|
);
|
2024-04-15 19:54:59 +02:00
|
|
|
|
assert_eq!(BareJid::from_str("a@b/c"), Err(Error::ResourceInBareJid));
|
2018-02-18 21:36:36 +01:00
|
|
|
|
}
|
|
|
|
|
|
2019-08-31 15:17:51 +02:00
|
|
|
|
#[test]
|
|
|
|
|
fn display_jids() {
|
2023-08-10 20:16:41 +02:00
|
|
|
|
assert_eq!(FullJid::new("a@b/c").unwrap().to_string(), "a@b/c");
|
|
|
|
|
assert_eq!(BareJid::new("a@b").unwrap().to_string(), "a@b");
|
2019-10-23 01:32:41 +02:00
|
|
|
|
assert_eq!(
|
2024-04-15 17:03:57 +02:00
|
|
|
|
Jid::from(FullJid::new("a@b/c").unwrap()).to_string(),
|
2023-08-10 20:16:41 +02:00
|
|
|
|
"a@b/c"
|
2019-10-23 01:32:41 +02:00
|
|
|
|
);
|
2024-04-15 17:03:57 +02:00
|
|
|
|
assert_eq!(Jid::from(BareJid::new("a@b").unwrap()).to_string(), "a@b");
|
2019-08-31 15:17:51 +02:00
|
|
|
|
}
|
|
|
|
|
|
2017-07-29 06:11:30 +02:00
|
|
|
|
#[cfg(feature = "minidom")]
|
|
|
|
|
#[test]
|
|
|
|
|
fn minidom() {
|
2020-04-02 23:11:58 +02:00
|
|
|
|
let elem: minidom::Element = "<message xmlns='ns1' from='a@b/c'/>".parse().unwrap();
|
2017-07-29 06:11:30 +02:00
|
|
|
|
let to: Jid = elem.attr("from").unwrap().parse().unwrap();
|
2024-04-15 17:03:57 +02:00
|
|
|
|
assert_eq!(to, Jid::from(FullJid::new("a@b/c").unwrap()));
|
2019-04-22 00:52:02 +02:00
|
|
|
|
|
2020-04-02 23:11:58 +02:00
|
|
|
|
let elem: minidom::Element = "<message xmlns='ns1' from='a@b'/>".parse().unwrap();
|
2019-04-22 00:52:02 +02:00
|
|
|
|
let to: Jid = elem.attr("from").unwrap().parse().unwrap();
|
2024-04-15 17:03:57 +02:00
|
|
|
|
assert_eq!(to, Jid::from(BareJid::new("a@b").unwrap()));
|
2019-04-22 00:52:02 +02:00
|
|
|
|
|
2020-04-02 23:11:58 +02:00
|
|
|
|
let elem: minidom::Element = "<message xmlns='ns1' from='a@b/c'/>".parse().unwrap();
|
2019-04-22 00:52:02 +02:00
|
|
|
|
let to: FullJid = elem.attr("from").unwrap().parse().unwrap();
|
2023-06-20 12:49:20 +02:00
|
|
|
|
assert_eq!(to, FullJid::new("a@b/c").unwrap());
|
2019-04-22 00:52:02 +02:00
|
|
|
|
|
2020-04-02 23:11:58 +02:00
|
|
|
|
let elem: minidom::Element = "<message xmlns='ns1' from='a@b'/>".parse().unwrap();
|
2019-04-22 00:52:02 +02:00
|
|
|
|
let to: BareJid = elem.attr("from").unwrap().parse().unwrap();
|
2023-06-20 12:49:20 +02:00
|
|
|
|
assert_eq!(to, BareJid::new("a@b").unwrap());
|
2019-04-22 00:52:02 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "minidom")]
|
|
|
|
|
#[test]
|
|
|
|
|
fn minidom_into_attr() {
|
2023-06-20 12:49:20 +02:00
|
|
|
|
let full = FullJid::new("a@b/c").unwrap();
|
2020-04-02 23:11:58 +02:00
|
|
|
|
let elem = minidom::Element::builder("message", "jabber:client")
|
2019-04-22 00:52:02 +02:00
|
|
|
|
.attr("from", full.clone())
|
|
|
|
|
.build();
|
2023-08-10 20:16:41 +02:00
|
|
|
|
assert_eq!(elem.attr("from"), Some(full.to_string().as_str()));
|
2019-04-22 00:52:02 +02:00
|
|
|
|
|
2023-06-20 12:49:20 +02:00
|
|
|
|
let bare = BareJid::new("a@b").unwrap();
|
2020-04-02 23:11:58 +02:00
|
|
|
|
let elem = minidom::Element::builder("message", "jabber:client")
|
2019-04-22 00:52:02 +02:00
|
|
|
|
.attr("from", bare.clone())
|
|
|
|
|
.build();
|
2023-08-10 20:16:41 +02:00
|
|
|
|
assert_eq!(elem.attr("from"), Some(bare.to_string().as_str()));
|
2019-04-22 00:52:02 +02:00
|
|
|
|
|
2024-04-15 17:03:57 +02:00
|
|
|
|
let jid = Jid::from(bare.clone());
|
2020-04-02 23:11:58 +02:00
|
|
|
|
let _elem = minidom::Element::builder("message", "jabber:client")
|
2019-04-22 00:52:02 +02:00
|
|
|
|
.attr("from", jid)
|
|
|
|
|
.build();
|
2023-08-10 20:16:41 +02:00
|
|
|
|
assert_eq!(elem.attr("from"), Some(bare.to_string().as_str()));
|
2017-07-29 06:11:30 +02:00
|
|
|
|
}
|
2019-12-02 02:57:20 +01:00
|
|
|
|
|
|
|
|
|
#[test]
|
2023-06-19 23:39:21 +02:00
|
|
|
|
fn stringprep() {
|
2019-12-02 02:57:20 +01:00
|
|
|
|
let full = FullJid::from_str("Test@☃.coM/Test™").unwrap();
|
2023-06-20 12:49:20 +02:00
|
|
|
|
let equiv = FullJid::new("test@☃.com/TestTM").unwrap();
|
2019-12-02 02:57:20 +01:00
|
|
|
|
assert_eq!(full, equiv);
|
|
|
|
|
}
|
2023-06-21 18:30:25 +02:00
|
|
|
|
|
2023-08-05 16:53:22 +02:00
|
|
|
|
#[test]
|
|
|
|
|
fn invalid_stringprep() {
|
|
|
|
|
FullJid::from_str("a@b/🎉").unwrap_err();
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-21 18:30:25 +02:00
|
|
|
|
#[test]
|
|
|
|
|
fn jid_from_parts() {
|
|
|
|
|
let node = NodePart::new("node").unwrap();
|
|
|
|
|
let domain = DomainPart::new("domain").unwrap();
|
|
|
|
|
let resource = ResourcePart::new("resource").unwrap();
|
|
|
|
|
|
|
|
|
|
let jid = Jid::from_parts(Some(&node), &domain, Some(&resource));
|
|
|
|
|
assert_eq!(jid, Jid::new("node@domain/resource").unwrap());
|
|
|
|
|
|
|
|
|
|
let barejid = BareJid::from_parts(Some(&node), &domain);
|
|
|
|
|
assert_eq!(barejid, BareJid::new("node@domain").unwrap());
|
|
|
|
|
|
|
|
|
|
let fulljid = FullJid::from_parts(Some(&node), &domain, &resource);
|
|
|
|
|
assert_eq!(fulljid, FullJid::new("node@domain/resource").unwrap());
|
|
|
|
|
}
|
2023-08-03 23:13:34 +02:00
|
|
|
|
|
|
|
|
|
#[test]
|
2023-08-04 16:38:41 +02:00
|
|
|
|
#[cfg(feature = "serde")]
|
2023-08-14 19:59:28 +02:00
|
|
|
|
fn jid_ser_de() {
|
|
|
|
|
let jid: Jid = Jid::new("node@domain").unwrap();
|
|
|
|
|
serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain")]);
|
2023-08-14 21:59:27 +02:00
|
|
|
|
|
|
|
|
|
let jid: Jid = Jid::new("node@domain/resource").unwrap();
|
|
|
|
|
serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain/resource")]);
|
|
|
|
|
|
2023-08-14 19:59:28 +02:00
|
|
|
|
let jid: BareJid = BareJid::new("node@domain").unwrap();
|
|
|
|
|
serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain")]);
|
|
|
|
|
|
|
|
|
|
let jid: FullJid = FullJid::new("node@domain/resource").unwrap();
|
|
|
|
|
serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain/resource")]);
|
2023-08-03 23:13:34 +02:00
|
|
|
|
}
|
2024-03-08 13:31:26 +01:00
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn jid_into_parts_and_from_parts() {
|
|
|
|
|
let node = NodePart::new("node").unwrap();
|
|
|
|
|
let domain = DomainPart::new("domain").unwrap();
|
|
|
|
|
|
|
|
|
|
let jid1 = domain.with_node(&node);
|
|
|
|
|
let jid2 = node.with_domain(&domain);
|
|
|
|
|
let jid3 = BareJid::new("node@domain").unwrap();
|
|
|
|
|
assert_eq!(jid1, jid2);
|
|
|
|
|
assert_eq!(jid2, jid3);
|
|
|
|
|
}
|
2024-04-15 17:03:57 +02:00
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn jid_match_replacement_try_as() {
|
|
|
|
|
let jid1 = Jid::new("foo@bar").unwrap();
|
|
|
|
|
let jid2 = Jid::new("foo@bar/baz").unwrap();
|
|
|
|
|
|
|
|
|
|
match jid1.try_as_full() {
|
|
|
|
|
Err(_) => (),
|
|
|
|
|
other => panic!("unexpected result: {:?}", other),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
match jid2.try_as_full() {
|
|
|
|
|
Ok(_) => (),
|
|
|
|
|
other => panic!("unexpected result: {:?}", other),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn jid_match_replacement_try_as_mut() {
|
|
|
|
|
let mut jid1 = Jid::new("foo@bar").unwrap();
|
|
|
|
|
let mut jid2 = Jid::new("foo@bar/baz").unwrap();
|
|
|
|
|
|
|
|
|
|
match jid1.try_as_full_mut() {
|
|
|
|
|
Err(_) => (),
|
|
|
|
|
other => panic!("unexpected result: {:?}", other),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
match jid2.try_as_full_mut() {
|
|
|
|
|
Ok(_) => (),
|
|
|
|
|
other => panic!("unexpected result: {:?}", other),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn jid_match_replacement_try_into() {
|
|
|
|
|
let jid1 = Jid::new("foo@bar").unwrap();
|
|
|
|
|
let jid2 = Jid::new("foo@bar/baz").unwrap();
|
|
|
|
|
|
|
|
|
|
match jid1.try_as_full() {
|
|
|
|
|
Err(_) => (),
|
|
|
|
|
other => panic!("unexpected result: {:?}", other),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
match jid2.try_as_full() {
|
|
|
|
|
Ok(_) => (),
|
|
|
|
|
other => panic!("unexpected result: {:?}", other),
|
|
|
|
|
};
|
|
|
|
|
}
|
2024-04-15 17:09:49 +02:00
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn lookup_jid_by_full_jid() {
|
|
|
|
|
let mut map: HashSet<Jid> = HashSet::new();
|
|
|
|
|
let jid1 = Jid::new("foo@bar").unwrap();
|
|
|
|
|
let jid2 = Jid::new("foo@bar/baz").unwrap();
|
|
|
|
|
let jid3 = FullJid::new("foo@bar/baz").unwrap();
|
|
|
|
|
|
|
|
|
|
map.insert(jid1);
|
|
|
|
|
assert!(!map.contains(&jid2));
|
|
|
|
|
assert!(!map.contains(&jid3));
|
|
|
|
|
map.insert(jid2);
|
|
|
|
|
assert!(map.contains(&jid3));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn lookup_full_jid_by_jid() {
|
|
|
|
|
let mut map: HashSet<FullJid> = HashSet::new();
|
|
|
|
|
let jid1 = FullJid::new("foo@bar/baz").unwrap();
|
|
|
|
|
let jid2 = FullJid::new("foo@bar/fnord").unwrap();
|
|
|
|
|
let jid3 = Jid::new("foo@bar/fnord").unwrap();
|
|
|
|
|
|
|
|
|
|
map.insert(jid1);
|
|
|
|
|
assert!(!map.contains(&jid2));
|
|
|
|
|
assert!(!map.contains(&jid3));
|
|
|
|
|
map.insert(jid2);
|
|
|
|
|
assert!(map.contains(&jid3));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn lookup_bare_jid_by_jid() {
|
|
|
|
|
let mut map: HashSet<BareJid> = HashSet::new();
|
|
|
|
|
let jid1 = BareJid::new("foo@bar").unwrap();
|
|
|
|
|
let jid2 = BareJid::new("foo@baz").unwrap();
|
|
|
|
|
let jid3 = Jid::new("foo@baz").unwrap();
|
|
|
|
|
|
|
|
|
|
map.insert(jid1);
|
|
|
|
|
assert!(!map.contains(&jid2));
|
|
|
|
|
assert!(!map.contains(&jid3));
|
|
|
|
|
map.insert(jid2);
|
|
|
|
|
assert!(map.contains(&jid3));
|
|
|
|
|
}
|
2024-04-15 19:54:59 +02:00
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn normalizes_all_parts() {
|
|
|
|
|
assert_eq!(
|
|
|
|
|
Jid::new("ßA@IX.test/\u{2168}").unwrap().as_str(),
|
|
|
|
|
"ssa@ix.test/IX"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn rejects_unassigned_codepoints() {
|
|
|
|
|
match Jid::new("\u{01f601}@example.com") {
|
|
|
|
|
Err(Error::NodePrep) => (),
|
|
|
|
|
other => panic!("unexpected result: {:?}", other),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
match Jid::new("foo@\u{01f601}.example.com") {
|
|
|
|
|
Err(Error::NamePrep) => (),
|
|
|
|
|
other => panic!("unexpected result: {:?}", other),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
match Jid::new("foo@example.com/\u{01f601}") {
|
|
|
|
|
Err(Error::ResourcePrep) => (),
|
|
|
|
|
other => panic!("unexpected result: {:?}", other),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn accepts_domain_only_jid() {
|
|
|
|
|
match Jid::new("example.com") {
|
|
|
|
|
Ok(_) => (),
|
|
|
|
|
other => panic!("unexpected result: {:?}", other),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
match BareJid::new("example.com") {
|
|
|
|
|
Ok(_) => (),
|
|
|
|
|
other => panic!("unexpected result: {:?}", other),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
match FullJid::new("example.com/x") {
|
|
|
|
|
Ok(_) => (),
|
|
|
|
|
other => panic!("unexpected result: {:?}", other),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn is_bare_returns_true_iff_bare() {
|
|
|
|
|
let bare = Jid::new("foo@bar").unwrap();
|
|
|
|
|
let full = Jid::new("foo@bar/baz").unwrap();
|
|
|
|
|
|
|
|
|
|
assert!(bare.is_bare());
|
|
|
|
|
assert!(!full.is_bare());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn is_full_returns_true_iff_full() {
|
|
|
|
|
let bare = Jid::new("foo@bar").unwrap();
|
|
|
|
|
let full = Jid::new("foo@bar/baz").unwrap();
|
|
|
|
|
|
|
|
|
|
assert!(!bare.is_full());
|
|
|
|
|
assert!(full.is_full());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn reject_long_localpart() {
|
|
|
|
|
let mut long = Vec::with_capacity(1028);
|
|
|
|
|
long.resize(1024, b'a');
|
|
|
|
|
let mut long = String::from_utf8(long).unwrap();
|
|
|
|
|
long.push_str("@foo");
|
|
|
|
|
|
|
|
|
|
match Jid::new(&long) {
|
|
|
|
|
Err(Error::NodeTooLong) => (),
|
|
|
|
|
other => panic!("unexpected result: {:?}", other),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
match BareJid::new(&long) {
|
|
|
|
|
Err(Error::NodeTooLong) => (),
|
|
|
|
|
other => panic!("unexpected result: {:?}", other),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn reject_long_domainpart() {
|
|
|
|
|
let mut long = Vec::with_capacity(1028);
|
|
|
|
|
long.push(b'x');
|
|
|
|
|
long.push(b'@');
|
|
|
|
|
long.resize(1026, b'a');
|
|
|
|
|
let long = String::from_utf8(long).unwrap();
|
|
|
|
|
|
|
|
|
|
match Jid::new(&long) {
|
|
|
|
|
Err(Error::DomainTooLong) => (),
|
|
|
|
|
other => panic!("unexpected result: {:?}", other),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
match BareJid::new(&long) {
|
|
|
|
|
Err(Error::DomainTooLong) => (),
|
|
|
|
|
other => panic!("unexpected result: {:?}", other),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn reject_long_resourcepart() {
|
|
|
|
|
let mut long = Vec::with_capacity(1028);
|
|
|
|
|
long.push(b'x');
|
|
|
|
|
long.push(b'@');
|
|
|
|
|
long.push(b'y');
|
|
|
|
|
long.push(b'/');
|
|
|
|
|
long.resize(1028, b'a');
|
|
|
|
|
let long = String::from_utf8(long).unwrap();
|
|
|
|
|
|
|
|
|
|
match Jid::new(&long) {
|
|
|
|
|
Err(Error::ResourceTooLong) => (),
|
|
|
|
|
other => panic!("unexpected result: {:?}", other),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
match FullJid::new(&long) {
|
|
|
|
|
Err(Error::ResourceTooLong) => (),
|
|
|
|
|
other => panic!("unexpected result: {:?}", other),
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-02-27 15:35:57 +01:00
|
|
|
|
}
|