1// Copyright (c) 2017, 2018 lumi <lumi@pew.im>
2// Copyright (c) 2017, 2018, 2019 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
3// Copyright (c) 2017, 2018, 2019 Maxime “pep” Buquet <pep@bouah.net>
4// Copyright (c) 2017, 2018 Astro <astro@spaceboyz.net>
5// Copyright (c) 2017 Bastien Orivel <eijebong@bananium.fr>
6//
7// This Source Code Form is subject to the terms of the Mozilla Public
8// License, v. 2.0. If a copy of the MPL was not distributed with this
9// file, You can obtain one at http://mozilla.org/MPL/2.0/.
10
11#![no_std]
12#![deny(missing_docs)]
13
14//! Represents XMPP addresses, also known as JabberIDs (JIDs) for the [XMPP](https://xmpp.org/)
15//! protocol. A [`Jid`] can have between one and three parts in the form `node@domain/resource`:
16//! - the (optional) node part designates a specific account/service on a server, for example
17//! `username@server.com`
18//! - the domain part designates a server, for example `irc.jabberfr.org`
19//! - the (optional) resource part designates a more specific client, such as a participant in a
20//! groupchat (`jabberfr@chat.jabberfr.org/user`) or a specific client device associated with an
21//! account (`user@example.com/dino`)
22//!
23//! The [`Jid`] enum can be one of two variants, containing a more specific type:
24//! - [`BareJid`] (`Jid::Bare` variant): a JID without a resource
25//! - [`FullJid`] (`Jid::Full` variant): a JID with a resource
26//!
27//! Jids as per the XMPP protocol only ever contain valid UTF-8. However, creating any form of Jid
28//! can fail in one of the following cases:
29//! - wrong syntax: creating a Jid with an empty (yet declared) node or resource part, such as
30//! `@example.com` or `user@example.com/`
31//! - stringprep error: some characters were invalid according to the stringprep algorithm, such as
32//! mixing left-to-write and right-to-left characters
33
34extern crate alloc;
35
36#[cfg(any(feature = "std", test))]
37extern crate std;
38
39use alloc::borrow::Cow;
40use alloc::format;
41use alloc::string::{String, ToString};
42use core::borrow::Borrow;
43use core::cmp::Ordering;
44use core::fmt;
45use core::hash::{Hash, Hasher};
46use core::mem;
47use core::num::NonZeroU16;
48use core::ops::Deref;
49use core::str::FromStr;
50
51use memchr::memchr;
52
53use stringprep::{nameprep, nodeprep, resourceprep};
54
55#[cfg(feature = "serde")]
56use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
57
58#[cfg(feature = "quote")]
59use proc_macro2::TokenStream;
60#[cfg(feature = "quote")]
61use quote::{quote, ToTokens};
62
63#[cfg(feature = "minidom")]
64use minidom::{IntoAttributeValue, Node};
65
66mod error;
67pub use crate::error::Error;
68
69mod parts;
70pub use parts::{DomainPart, DomainRef, NodePart, NodeRef, ResourcePart, ResourceRef};
71
72fn length_check(len: usize, error_empty: Error, error_too_long: Error) -> Result<(), Error> {
73 if len == 0 {
74 Err(error_empty)
75 } else if len > 1023 {
76 Err(error_too_long)
77 } else {
78 Ok(())
79 }
80}
81
82/// A struct representing a Jabber ID (JID).
83///
84/// This JID can either be "bare" (without a `/resource` suffix) or full (with
85/// a resource suffix).
86///
87/// In many APIs, it is appropriate to use the more specific types
88/// ([`BareJid`] or [`FullJid`]) instead, as these two JID types are generally
89/// used in different contexts within XMPP.
90///
91/// This dynamic type on the other hand can be used in contexts where it is
92/// not known, at compile-time, whether a JID is full or bare.
93#[derive(Debug, Clone, Eq)]
94pub struct Jid {
95 normalized: String,
96 at: Option<NonZeroU16>,
97 slash: Option<NonZeroU16>,
98}
99
100impl PartialEq for Jid {
101 fn eq(&self, other: &Jid) -> bool {
102 self.normalized == other.normalized
103 }
104}
105
106impl PartialOrd for Jid {
107 fn partial_cmp(&self, other: &Jid) -> Option<Ordering> {
108 self.normalized.partial_cmp(&other.normalized)
109 }
110}
111
112impl Ord for Jid {
113 fn cmp(&self, other: &Jid) -> Ordering {
114 self.normalized.cmp(&other.normalized)
115 }
116}
117
118impl Hash for Jid {
119 fn hash<H: Hasher>(&self, state: &mut H) {
120 self.normalized.hash(state)
121 }
122}
123
124impl FromStr for Jid {
125 type Err = Error;
126
127 fn from_str(s: &str) -> Result<Self, Self::Err> {
128 Self::new(s)
129 }
130}
131
132impl From<BareJid> for Jid {
133 fn from(other: BareJid) -> Self {
134 other.inner
135 }
136}
137
138impl From<FullJid> for Jid {
139 fn from(other: FullJid) -> Self {
140 other.inner
141 }
142}
143
144impl fmt::Display for Jid {
145 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
146 fmt.write_str(&self.normalized)
147 }
148}
149
150impl Jid {
151 /// Constructs a Jabber ID from a string. This is of the form
152 /// `node`@`domain`/`resource`, where node and resource parts are optional.
153 /// If you want a non-fallible version, use [`Jid::from_parts`] instead.
154 ///
155 /// # Examples
156 ///
157 /// ```
158 /// use jid::Jid;
159 /// # use jid::Error;
160 ///
161 /// # fn main() -> Result<(), Error> {
162 /// let jid = Jid::new("node@domain/resource")?;
163 ///
164 /// assert_eq!(jid.node().map(|x| x.as_str()), Some("node"));
165 /// assert_eq!(jid.domain().as_str(), "domain");
166 /// assert_eq!(jid.resource().map(|x| x.as_str()), Some("resource"));
167 /// # Ok(())
168 /// # }
169 /// ```
170 pub fn new(unnormalized: &str) -> Result<Jid, Error> {
171 let bytes = unnormalized.as_bytes();
172 let mut orig_at = memchr(b'@', bytes);
173 let mut orig_slash = memchr(b'/', bytes);
174 if orig_at.is_some() && orig_slash.is_some() && orig_at > orig_slash {
175 // This is part of the resource, not a node@domain separator.
176 orig_at = None;
177 }
178
179 let normalized = match (orig_at, orig_slash) {
180 (Some(at), Some(slash)) => {
181 let node = nodeprep(&unnormalized[..at]).map_err(|_| Error::NodePrep)?;
182 length_check(node.len(), Error::NodeEmpty, Error::NodeTooLong)?;
183
184 let domain = nameprep(&unnormalized[at + 1..slash]).map_err(|_| Error::NamePrep)?;
185 length_check(domain.len(), Error::DomainEmpty, Error::DomainTooLong)?;
186
187 let resource =
188 resourceprep(&unnormalized[slash + 1..]).map_err(|_| Error::ResourcePrep)?;
189 length_check(resource.len(), Error::ResourceEmpty, Error::ResourceTooLong)?;
190
191 orig_at = Some(node.len());
192 orig_slash = Some(node.len() + domain.len() + 1);
193 match (node, domain, resource) {
194 (Cow::Borrowed(_), Cow::Borrowed(_), Cow::Borrowed(_)) => {
195 unnormalized.to_string()
196 }
197 (node, domain, resource) => format!("{node}@{domain}/{resource}"),
198 }
199 }
200 (Some(at), None) => {
201 let node = nodeprep(&unnormalized[..at]).map_err(|_| Error::NodePrep)?;
202 length_check(node.len(), Error::NodeEmpty, Error::NodeTooLong)?;
203
204 let domain = nameprep(&unnormalized[at + 1..]).map_err(|_| Error::NamePrep)?;
205 length_check(domain.len(), Error::DomainEmpty, Error::DomainTooLong)?;
206
207 orig_at = Some(node.len());
208 match (node, domain) {
209 (Cow::Borrowed(_), Cow::Borrowed(_)) => unnormalized.to_string(),
210 (node, domain) => format!("{node}@{domain}"),
211 }
212 }
213 (None, Some(slash)) => {
214 let domain = nameprep(&unnormalized[..slash]).map_err(|_| Error::NamePrep)?;
215 length_check(domain.len(), Error::DomainEmpty, Error::DomainTooLong)?;
216
217 let resource =
218 resourceprep(&unnormalized[slash + 1..]).map_err(|_| Error::ResourcePrep)?;
219 length_check(resource.len(), Error::ResourceEmpty, Error::ResourceTooLong)?;
220
221 orig_slash = Some(domain.len());
222 match (domain, resource) {
223 (Cow::Borrowed(_), Cow::Borrowed(_)) => unnormalized.to_string(),
224 (domain, resource) => format!("{domain}/{resource}"),
225 }
226 }
227 (None, None) => {
228 let domain = nameprep(unnormalized).map_err(|_| Error::NamePrep)?;
229 length_check(domain.len(), Error::DomainEmpty, Error::DomainTooLong)?;
230
231 domain.into_owned()
232 }
233 };
234
235 Ok(Self {
236 normalized,
237 at: orig_at.and_then(|x| NonZeroU16::new(x as u16)),
238 slash: orig_slash.and_then(|x| NonZeroU16::new(x as u16)),
239 })
240 }
241
242 /// Returns the inner String of this JID.
243 pub fn into_inner(self) -> String {
244 self.normalized
245 }
246
247 /// Build a [`Jid`] from typed parts. This method cannot fail because it uses parts that have
248 /// already been parsed and stringprepped into [`NodePart`], [`DomainPart`], and [`ResourcePart`].
249 ///
250 /// This method allocates and does not consume the typed parts. To avoid
251 /// allocation if both `node` and `resource` are known to be `None` and
252 /// `domain` is owned, you can use `domain.into()`.
253 pub fn from_parts(
254 node: Option<&NodeRef>,
255 domain: &DomainRef,
256 resource: Option<&ResourceRef>,
257 ) -> Self {
258 match resource {
259 Some(resource) => FullJid::from_parts(node, domain, resource).into(),
260 None => BareJid::from_parts(node, domain).into(),
261 }
262 }
263
264 /// The optional node part of the JID as reference.
265 pub fn node(&self) -> Option<&NodeRef> {
266 self.at.map(|at| {
267 let at = u16::from(at) as usize;
268 NodeRef::from_str_unchecked(&self.normalized[..at])
269 })
270 }
271
272 /// The domain part of the JID as reference
273 pub fn domain(&self) -> &DomainRef {
274 match (self.at, self.slash) {
275 (Some(at), Some(slash)) => {
276 let at = u16::from(at) as usize;
277 let slash = u16::from(slash) as usize;
278 DomainRef::from_str_unchecked(&self.normalized[at + 1..slash])
279 }
280 (Some(at), None) => {
281 let at = u16::from(at) as usize;
282 DomainRef::from_str_unchecked(&self.normalized[at + 1..])
283 }
284 (None, Some(slash)) => {
285 let slash = u16::from(slash) as usize;
286 DomainRef::from_str_unchecked(&self.normalized[..slash])
287 }
288 (None, None) => DomainRef::from_str_unchecked(&self.normalized),
289 }
290 }
291
292 /// The optional resource of the Jabber ID. It is guaranteed to be present when the JID is
293 /// a Full variant, which you can check with [`Jid::is_full`].
294 pub fn resource(&self) -> Option<&ResourceRef> {
295 self.slash.map(|slash| {
296 let slash = u16::from(slash) as usize;
297 ResourceRef::from_str_unchecked(&self.normalized[slash + 1..])
298 })
299 }
300
301 /// Allocate a new [`BareJid`] from this JID, discarding the resource.
302 pub fn to_bare(&self) -> BareJid {
303 BareJid::from_parts(self.node(), self.domain())
304 }
305
306 /// Transforms this JID into a [`BareJid`], throwing away the resource.
307 ///
308 /// ```
309 /// # use jid::{BareJid, Jid};
310 /// let jid: Jid = "foo@bar/baz".parse().unwrap();
311 /// let bare = jid.into_bare();
312 /// assert_eq!(bare.to_string(), "foo@bar");
313 /// ```
314 pub fn into_bare(mut self) -> BareJid {
315 if let Some(slash) = self.slash {
316 // truncate the string
317 self.normalized.truncate(slash.get() as usize);
318 self.slash = None;
319 }
320 BareJid { inner: self }
321 }
322
323 /// Checks if the JID is a full JID.
324 pub fn is_full(&self) -> bool {
325 self.slash.is_some()
326 }
327
328 /// Checks if the JID is a bare JID.
329 pub fn is_bare(&self) -> bool {
330 self.slash.is_none()
331 }
332
333 /// Return a reference to the canonical string representation of the JID.
334 pub fn as_str(&self) -> &str {
335 &self.normalized
336 }
337
338 /// Try to convert this Jid to a [`FullJid`] if it contains a resource
339 /// and return a [`BareJid`] otherwise.
340 ///
341 /// This is useful for match blocks:
342 ///
343 /// ```
344 /// # use jid::Jid;
345 /// let jid: Jid = "foo@bar".parse().unwrap();
346 /// match jid.try_into_full() {
347 /// Ok(full) => println!("it is full: {:?}", full),
348 /// Err(bare) => println!("it is bare: {:?}", bare),
349 /// }
350 /// ```
351 pub fn try_into_full(self) -> Result<FullJid, BareJid> {
352 if self.slash.is_some() {
353 Ok(FullJid { inner: self })
354 } else {
355 Err(BareJid { inner: self })
356 }
357 }
358
359 /// Try to convert this Jid reference to a [`&FullJid`][`FullJid`] if it
360 /// contains a resource and return a [`&BareJid`][`BareJid`] otherwise.
361 ///
362 /// This is useful for match blocks:
363 ///
364 /// ```
365 /// # use jid::Jid;
366 /// let jid: Jid = "foo@bar".parse().unwrap();
367 /// match jid.try_as_full() {
368 /// Ok(full) => println!("it is full: {:?}", full),
369 /// Err(bare) => println!("it is bare: {:?}", bare),
370 /// }
371 /// ```
372 pub fn try_as_full(&self) -> Result<&FullJid, &BareJid> {
373 if self.slash.is_some() {
374 Ok(unsafe {
375 // SAFETY: FullJid is #[repr(transparent)] of Jid
376 // SOUNDNESS: we asserted that self.slash is set above
377 mem::transmute::<&Jid, &FullJid>(self)
378 })
379 } else {
380 Err(unsafe {
381 // SAFETY: BareJid is #[repr(transparent)] of Jid
382 // SOUNDNESS: we asserted that self.slash is unset above
383 mem::transmute::<&Jid, &BareJid>(self)
384 })
385 }
386 }
387
388 /// Try to convert this mutable Jid reference to a
389 /// [`&mut FullJid`][`FullJid`] if it contains a resource and return a
390 /// [`&mut BareJid`][`BareJid`] otherwise.
391 pub fn try_as_full_mut(&mut self) -> Result<&mut FullJid, &mut BareJid> {
392 if self.slash.is_some() {
393 Ok(unsafe {
394 // SAFETY: FullJid is #[repr(transparent)] of Jid
395 // SOUNDNESS: we asserted that self.slash is set above
396 mem::transmute::<&mut Jid, &mut FullJid>(self)
397 })
398 } else {
399 Err(unsafe {
400 // SAFETY: BareJid is #[repr(transparent)] of Jid
401 // SOUNDNESS: we asserted that self.slash is unset above
402 mem::transmute::<&mut Jid, &mut BareJid>(self)
403 })
404 }
405 }
406
407 #[doc(hidden)]
408 #[allow(non_snake_case)]
409 #[deprecated(
410 since = "0.11.0",
411 note = "use Jid::from (for construction of Jid values) or Jid::try_into_full/Jid::try_as_full (for match blocks) instead"
412 )]
413 pub fn Bare(other: BareJid) -> Self {
414 Self::from(other)
415 }
416
417 #[doc(hidden)]
418 #[allow(non_snake_case)]
419 #[deprecated(
420 since = "0.11.0",
421 note = "use Jid::from (for construction of Jid values) or Jid::try_into_full/Jid::try_as_full (for match blocks) instead"
422 )]
423 pub fn Full(other: FullJid) -> Self {
424 Self::from(other)
425 }
426}
427
428impl TryFrom<Jid> for FullJid {
429 type Error = Error;
430
431 fn try_from(inner: Jid) -> Result<Self, Self::Error> {
432 if inner.slash.is_none() {
433 return Err(Error::ResourceMissingInFullJid);
434 }
435 Ok(Self { inner })
436 }
437}
438
439impl TryFrom<Jid> for BareJid {
440 type Error = Error;
441
442 fn try_from(inner: Jid) -> Result<Self, Self::Error> {
443 if inner.slash.is_some() {
444 return Err(Error::ResourceInBareJid);
445 }
446 Ok(Self { inner })
447 }
448}
449
450impl PartialEq<Jid> for FullJid {
451 fn eq(&self, other: &Jid) -> bool {
452 &self.inner == other
453 }
454}
455
456impl PartialEq<Jid> for BareJid {
457 fn eq(&self, other: &Jid) -> bool {
458 &self.inner == other
459 }
460}
461
462impl PartialEq<FullJid> for Jid {
463 fn eq(&self, other: &FullJid) -> bool {
464 self == &other.inner
465 }
466}
467
468impl PartialEq<BareJid> for Jid {
469 fn eq(&self, other: &BareJid) -> bool {
470 self == &other.inner
471 }
472}
473
474/// A struct representing a full Jabber ID, with a resource part.
475///
476/// A full JID is composed of 3 components, of which only the node is optional:
477///
478/// - the (optional) node part is the part before the (optional) `@`.
479/// - the domain part is the mandatory part between the (optional) `@` and before the `/`.
480/// - the resource part after the `/`.
481///
482/// Unlike a [`BareJid`], it always contains a resource, and should only be used when you are
483/// certain there is no case where a resource can be missing. Otherwise, use a [`Jid`] or
484/// [`BareJid`].
485#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
486#[repr(transparent)] // WARNING: Jid::try_as_* relies on this for safety!
487pub struct FullJid {
488 inner: Jid,
489}
490
491/// A struct representing a bare Jabber ID, without a resource part.
492///
493/// A bare JID is composed of 2 components, of which only the node is optional:
494/// - the (optional) node part is the part before the (optional) `@`.
495/// - the domain part is the mandatory part between the (optional) `@` and before the `/`.
496///
497/// Unlike a [`FullJid`], it can’t contain a resource, and should only be used when you are certain
498/// there is no case where a resource can be set. Otherwise, use a [`Jid`] or [`FullJid`].
499#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
500#[repr(transparent)] // WARNING: Jid::try_as_* relies on this for safety!
501pub struct BareJid {
502 inner: Jid,
503}
504
505impl Deref for FullJid {
506 type Target = Jid;
507
508 fn deref(&self) -> &Self::Target {
509 &self.inner
510 }
511}
512
513impl Deref for BareJid {
514 type Target = Jid;
515
516 fn deref(&self) -> &Self::Target {
517 &self.inner
518 }
519}
520
521impl Borrow<Jid> for FullJid {
522 fn borrow(&self) -> &Jid {
523 &self.inner
524 }
525}
526
527impl Borrow<Jid> for BareJid {
528 fn borrow(&self) -> &Jid {
529 &self.inner
530 }
531}
532
533impl fmt::Debug for FullJid {
534 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
535 fmt.debug_tuple("FullJid").field(&self.inner).finish()
536 }
537}
538
539impl fmt::Debug for BareJid {
540 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
541 fmt.debug_tuple("BareJid").field(&self.inner).finish()
542 }
543}
544
545impl fmt::Display for FullJid {
546 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
547 fmt::Display::fmt(&self.inner, fmt)
548 }
549}
550
551impl fmt::Display for BareJid {
552 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
553 fmt::Display::fmt(&self.inner, fmt)
554 }
555}
556
557#[cfg(feature = "serde")]
558impl Serialize for Jid {
559 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
560 where
561 S: Serializer,
562 {
563 serializer.serialize_str(&self.normalized)
564 }
565}
566
567#[cfg(feature = "serde")]
568impl Serialize for FullJid {
569 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
570 where
571 S: Serializer,
572 {
573 self.inner.serialize(serializer)
574 }
575}
576
577#[cfg(feature = "serde")]
578impl Serialize for BareJid {
579 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
580 where
581 S: Serializer,
582 {
583 self.inner.serialize(serializer)
584 }
585}
586
587impl FromStr for FullJid {
588 type Err = Error;
589
590 fn from_str(s: &str) -> Result<Self, Self::Err> {
591 Self::new(s)
592 }
593}
594
595#[cfg(feature = "serde")]
596impl<'de> Deserialize<'de> for Jid {
597 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
598 where
599 D: Deserializer<'de>,
600 {
601 let s = String::deserialize(deserializer)?;
602 Jid::new(&s).map_err(de::Error::custom)
603 }
604}
605
606#[cfg(feature = "serde")]
607impl<'de> Deserialize<'de> for FullJid {
608 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
609 where
610 D: Deserializer<'de>,
611 {
612 let jid = Jid::deserialize(deserializer)?;
613 jid.try_into().map_err(de::Error::custom)
614 }
615}
616
617#[cfg(feature = "serde")]
618impl<'de> Deserialize<'de> for BareJid {
619 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
620 where
621 D: Deserializer<'de>,
622 {
623 let jid = Jid::deserialize(deserializer)?;
624 jid.try_into().map_err(de::Error::custom)
625 }
626}
627
628#[cfg(feature = "quote")]
629impl ToTokens for Jid {
630 fn to_tokens(&self, tokens: &mut TokenStream) {
631 let s = &self.normalized;
632 tokens.extend(quote! {
633 ::jid::Jid::new(#s).unwrap()
634 });
635 }
636}
637
638#[cfg(feature = "quote")]
639impl ToTokens for FullJid {
640 fn to_tokens(&self, tokens: &mut TokenStream) {
641 let s = &self.inner.normalized;
642 tokens.extend(quote! {
643 ::jid::FullJid::new(#s).unwrap()
644 });
645 }
646}
647
648#[cfg(feature = "quote")]
649impl ToTokens for BareJid {
650 fn to_tokens(&self, tokens: &mut TokenStream) {
651 let s = &self.inner.normalized;
652 tokens.extend(quote! {
653 ::jid::BareJid::new(#s).unwrap()
654 });
655 }
656}
657
658impl FullJid {
659 /// Constructs a full Jabber ID containing all three components. This is of the form
660 /// `node@domain/resource`, where node part is optional.
661 /// If you want a non-fallible version, use [`FullJid::from_parts`] instead.
662 ///
663 /// # Examples
664 ///
665 /// ```
666 /// use jid::FullJid;
667 /// # use jid::Error;
668 ///
669 /// # fn main() -> Result<(), Error> {
670 /// let jid = FullJid::new("node@domain/resource")?;
671 ///
672 /// assert_eq!(jid.node().map(|x| x.as_str()), Some("node"));
673 /// assert_eq!(jid.domain().as_str(), "domain");
674 /// assert_eq!(jid.resource().as_str(), "resource");
675 /// # Ok(())
676 /// # }
677 /// ```
678 pub fn new(unnormalized: &str) -> Result<Self, Error> {
679 Jid::new(unnormalized)?.try_into()
680 }
681
682 /// Build a [`FullJid`] from typed parts. This method cannot fail because it uses parts that have
683 /// already been parsed and stringprepped into [`NodePart`], [`DomainPart`], and [`ResourcePart`].
684 /// This method allocates and does not consume the typed parts.
685 pub fn from_parts(
686 node: Option<&NodeRef>,
687 domain: &DomainRef,
688 resource: &ResourceRef,
689 ) -> FullJid {
690 let (at, slash, normalized) = if let Some(node) = node {
691 // Parts are never empty so len > 0 for NonZeroU16::new is always Some
692 (
693 NonZeroU16::new(node.len() as u16),
694 NonZeroU16::new((node.len() + 1 + domain.len()) as u16),
695 format!(
696 "{}@{}/{}",
697 node.as_str(),
698 domain.as_str(),
699 resource.as_str()
700 ),
701 )
702 } else {
703 (
704 None,
705 NonZeroU16::new(domain.len() as u16),
706 format!("{}/{}", domain.as_str(), resource.as_str()),
707 )
708 };
709
710 let inner = Jid {
711 normalized,
712 at,
713 slash,
714 };
715
716 Self { inner }
717 }
718
719 /// The optional resource of the Jabber ID. Since this is a full JID it is always present.
720 pub fn resource(&self) -> &ResourceRef {
721 self.inner.resource().unwrap()
722 }
723
724 /// Return the inner String of this full JID.
725 pub fn into_inner(self) -> String {
726 self.inner.into_inner()
727 }
728
729 /// Transforms this full JID into a [`BareJid`], throwing away the
730 /// resource.
731 ///
732 /// ```
733 /// # use jid::{BareJid, FullJid};
734 /// let jid: FullJid = "foo@bar/baz".parse().unwrap();
735 /// let bare = jid.into_bare();
736 /// assert_eq!(bare.to_string(), "foo@bar");
737 /// ```
738 pub fn into_bare(self) -> BareJid {
739 self.inner.into_bare()
740 }
741}
742
743impl FromStr for BareJid {
744 type Err = Error;
745
746 fn from_str(s: &str) -> Result<Self, Self::Err> {
747 Self::new(s)
748 }
749}
750
751impl BareJid {
752 /// Constructs a bare Jabber ID, containing two components. This is of the form
753 /// `node`@`domain`, where node part is optional.
754 /// If you want a non-fallible version, use [`BareJid::from_parts`] instead.
755 ///
756 /// # Examples
757 ///
758 /// ```
759 /// use jid::BareJid;
760 /// # use jid::Error;
761 ///
762 /// # fn main() -> Result<(), Error> {
763 /// let jid = BareJid::new("node@domain")?;
764 ///
765 /// assert_eq!(jid.node().map(|x| x.as_str()), Some("node"));
766 /// assert_eq!(jid.domain().as_str(), "domain");
767 /// # Ok(())
768 /// # }
769 /// ```
770 pub fn new(unnormalized: &str) -> Result<Self, Error> {
771 Jid::new(unnormalized)?.try_into()
772 }
773
774 /// Build a [`BareJid`] from typed parts. This method cannot fail because it uses parts that have
775 /// already been parsed and stringprepped into [`NodePart`] and [`DomainPart`].
776 ///
777 /// This method allocates and does not consume the typed parts. To avoid
778 /// allocation if `node` is known to be `None` and `domain` is owned, you
779 /// can use `domain.into()`.
780 pub fn from_parts(node: Option<&NodeRef>, domain: &DomainRef) -> Self {
781 let (at, normalized) = if let Some(node) = node {
782 // Parts are never empty so len > 0 for NonZeroU16::new is always Some
783 (
784 NonZeroU16::new(node.len() as u16),
785 format!("{}@{}", node.as_str(), domain.as_str()),
786 )
787 } else {
788 (None, domain.to_string())
789 };
790
791 let inner = Jid {
792 normalized,
793 at,
794 slash: None,
795 };
796
797 Self { inner }
798 }
799
800 /// Constructs a [`BareJid`] from the bare JID, by specifying a [`ResourcePart`].
801 /// If you'd like to specify a stringy resource, use [`BareJid::with_resource_str`] instead.
802 ///
803 /// # Examples
804 ///
805 /// ```
806 /// use jid::{BareJid, ResourcePart};
807 ///
808 /// let resource = ResourcePart::new("resource").unwrap();
809 /// let bare = BareJid::new("node@domain").unwrap();
810 /// let full = bare.with_resource(&resource);
811 ///
812 /// assert_eq!(full.node().map(|x| x.as_str()), Some("node"));
813 /// assert_eq!(full.domain().as_str(), "domain");
814 /// assert_eq!(full.resource().as_str(), "resource");
815 /// ```
816 pub fn with_resource(&self, resource: &ResourceRef) -> FullJid {
817 let slash = NonZeroU16::new(self.inner.normalized.len() as u16);
818 let normalized = format!("{}/{resource}", self.inner.normalized);
819 let inner = Jid {
820 normalized,
821 at: self.inner.at,
822 slash,
823 };
824
825 FullJid { inner }
826 }
827
828 /// Constructs a [`FullJid`] from the bare JID, by specifying a stringy `resource`.
829 /// If your resource has already been parsed into a [`ResourcePart`], use [`BareJid::with_resource`].
830 ///
831 /// # Examples
832 ///
833 /// ```
834 /// use jid::BareJid;
835 ///
836 /// let bare = BareJid::new("node@domain").unwrap();
837 /// let full = bare.with_resource_str("resource").unwrap();
838 ///
839 /// assert_eq!(full.node().map(|x| x.as_str()), Some("node"));
840 /// assert_eq!(full.domain().as_str(), "domain");
841 /// assert_eq!(full.resource().as_str(), "resource");
842 /// ```
843 pub fn with_resource_str(&self, resource: &str) -> Result<FullJid, Error> {
844 let resource = ResourcePart::new(resource)?;
845 Ok(self.with_resource(&resource))
846 }
847
848 /// Return the inner String of this bare JID.
849 pub fn into_inner(self) -> String {
850 self.inner.into_inner()
851 }
852}
853
854#[cfg(feature = "minidom")]
855impl IntoAttributeValue for Jid {
856 fn into_attribute_value(self) -> Option<String> {
857 Some(self.to_string())
858 }
859}
860
861#[cfg(feature = "minidom")]
862impl From<Jid> for Node {
863 fn from(jid: Jid) -> Self {
864 Node::Text(jid.to_string())
865 }
866}
867
868#[cfg(feature = "minidom")]
869impl IntoAttributeValue for FullJid {
870 fn into_attribute_value(self) -> Option<String> {
871 self.inner.into_attribute_value()
872 }
873}
874
875#[cfg(feature = "minidom")]
876impl From<FullJid> for Node {
877 fn from(jid: FullJid) -> Self {
878 jid.inner.into()
879 }
880}
881
882#[cfg(feature = "minidom")]
883impl IntoAttributeValue for BareJid {
884 fn into_attribute_value(self) -> Option<String> {
885 self.inner.into_attribute_value()
886 }
887}
888
889#[cfg(feature = "minidom")]
890impl From<BareJid> for Node {
891 fn from(other: BareJid) -> Self {
892 other.inner.into()
893 }
894}
895
896#[cfg(test)]
897mod tests {
898 use super::*;
899
900 use std::collections::{HashMap, HashSet};
901 use std::vec::Vec;
902
903 macro_rules! assert_size (
904 ($t:ty, $sz:expr) => (
905 assert_eq!(::core::mem::size_of::<$t>(), $sz);
906 );
907 );
908
909 #[cfg(target_pointer_width = "32")]
910 #[test]
911 fn test_size() {
912 assert_size!(BareJid, 16);
913 assert_size!(FullJid, 16);
914 assert_size!(Jid, 16);
915 }
916
917 #[cfg(target_pointer_width = "64")]
918 #[test]
919 fn test_size() {
920 assert_size!(BareJid, 32);
921 assert_size!(FullJid, 32);
922 assert_size!(Jid, 32);
923 }
924
925 #[test]
926 fn can_parse_full_jids() {
927 assert_eq!(
928 FullJid::from_str("a@b.c/d"),
929 Ok(FullJid::new("a@b.c/d").unwrap())
930 );
931 assert_eq!(
932 FullJid::from_str("b.c/d"),
933 Ok(FullJid::new("b.c/d").unwrap())
934 );
935
936 assert_eq!(
937 FullJid::from_str("a@b.c"),
938 Err(Error::ResourceMissingInFullJid)
939 );
940 assert_eq!(
941 FullJid::from_str("b.c"),
942 Err(Error::ResourceMissingInFullJid)
943 );
944 }
945
946 #[test]
947 fn can_parse_bare_jids() {
948 assert_eq!(
949 BareJid::from_str("a@b.c"),
950 Ok(BareJid::new("a@b.c").unwrap())
951 );
952 assert_eq!(BareJid::from_str("b.c"), Ok(BareJid::new("b.c").unwrap()));
953 }
954
955 #[test]
956 fn can_parse_jids() {
957 let full = FullJid::from_str("a@b.c/d").unwrap();
958 let bare = BareJid::from_str("e@f.g").unwrap();
959
960 assert_eq!(Jid::from_str("a@b.c/d").unwrap(), full);
961 assert_eq!(Jid::from_str("e@f.g").unwrap(), bare);
962 }
963
964 #[test]
965 fn full_to_bare_jid() {
966 let bare: BareJid = FullJid::new("a@b.c/d").unwrap().to_bare();
967 assert_eq!(bare, BareJid::new("a@b.c").unwrap());
968 }
969
970 #[test]
971 fn bare_to_full_jid_str() {
972 assert_eq!(
973 BareJid::new("a@b.c")
974 .unwrap()
975 .with_resource_str("d")
976 .unwrap(),
977 FullJid::new("a@b.c/d").unwrap()
978 );
979 }
980
981 #[test]
982 fn bare_to_full_jid() {
983 assert_eq!(
984 BareJid::new("a@b.c")
985 .unwrap()
986 .with_resource(&ResourcePart::new("d").unwrap()),
987 FullJid::new("a@b.c/d").unwrap()
988 )
989 }
990
991 #[test]
992 fn node_from_jid() {
993 let jid = Jid::new("a@b.c/d").unwrap();
994
995 assert_eq!(jid.node().map(|x| x.as_str()), Some("a"),);
996 }
997
998 #[test]
999 fn domain_from_jid() {
1000 let jid = Jid::new("a@b.c").unwrap();
1001
1002 assert_eq!(jid.domain().as_str(), "b.c");
1003 }
1004
1005 #[test]
1006 fn resource_from_jid() {
1007 let jid = Jid::new("a@b.c/d").unwrap();
1008
1009 assert_eq!(jid.resource().map(|x| x.as_str()), Some("d"),);
1010 }
1011
1012 #[test]
1013 fn jid_to_full_bare() {
1014 let full = FullJid::new("a@b.c/d").unwrap();
1015 let bare = BareJid::new("a@b.c").unwrap();
1016
1017 assert_eq!(FullJid::try_from(Jid::from(full.clone())), Ok(full.clone()));
1018 assert_eq!(
1019 FullJid::try_from(Jid::from(bare.clone())),
1020 Err(Error::ResourceMissingInFullJid),
1021 );
1022 assert_eq!(Jid::from(full.clone().to_bare()), bare.clone());
1023 assert_eq!(Jid::from(bare.clone()), bare);
1024 }
1025
1026 #[test]
1027 fn serialise() {
1028 assert_eq!(FullJid::new("a@b/c").unwrap().to_string(), "a@b/c");
1029 assert_eq!(BareJid::new("a@b").unwrap().to_string(), "a@b");
1030 }
1031
1032 #[test]
1033 fn hash() {
1034 let _map: HashMap<Jid, String> = HashMap::new();
1035 }
1036
1037 #[test]
1038 fn invalid_jids() {
1039 assert_eq!(BareJid::from_str(""), Err(Error::DomainEmpty));
1040 assert_eq!(BareJid::from_str("/c"), Err(Error::DomainEmpty));
1041 assert_eq!(BareJid::from_str("a@/c"), Err(Error::DomainEmpty));
1042 assert_eq!(BareJid::from_str("@b"), Err(Error::NodeEmpty));
1043 assert_eq!(BareJid::from_str("b/"), Err(Error::ResourceEmpty));
1044
1045 assert_eq!(FullJid::from_str(""), Err(Error::DomainEmpty));
1046 assert_eq!(FullJid::from_str("/c"), Err(Error::DomainEmpty));
1047 assert_eq!(FullJid::from_str("a@/c"), Err(Error::DomainEmpty));
1048 assert_eq!(FullJid::from_str("@b"), Err(Error::NodeEmpty));
1049 assert_eq!(FullJid::from_str("b/"), Err(Error::ResourceEmpty));
1050 assert_eq!(
1051 FullJid::from_str("a@b"),
1052 Err(Error::ResourceMissingInFullJid)
1053 );
1054 assert_eq!(BareJid::from_str("a@b/c"), Err(Error::ResourceInBareJid));
1055 }
1056
1057 #[test]
1058 fn display_jids() {
1059 assert_eq!(FullJid::new("a@b/c").unwrap().to_string(), "a@b/c");
1060 assert_eq!(BareJid::new("a@b").unwrap().to_string(), "a@b");
1061 assert_eq!(
1062 Jid::from(FullJid::new("a@b/c").unwrap()).to_string(),
1063 "a@b/c"
1064 );
1065 assert_eq!(Jid::from(BareJid::new("a@b").unwrap()).to_string(), "a@b");
1066 }
1067
1068 #[cfg(feature = "minidom")]
1069 #[test]
1070 fn minidom() {
1071 let elem: minidom::Element = "<message xmlns='ns1' from='a@b/c'/>".parse().unwrap();
1072 let to: Jid = elem.attr("from").unwrap().parse().unwrap();
1073 assert_eq!(to, Jid::from(FullJid::new("a@b/c").unwrap()));
1074
1075 let elem: minidom::Element = "<message xmlns='ns1' from='a@b'/>".parse().unwrap();
1076 let to: Jid = elem.attr("from").unwrap().parse().unwrap();
1077 assert_eq!(to, Jid::from(BareJid::new("a@b").unwrap()));
1078
1079 let elem: minidom::Element = "<message xmlns='ns1' from='a@b/c'/>".parse().unwrap();
1080 let to: FullJid = elem.attr("from").unwrap().parse().unwrap();
1081 assert_eq!(to, FullJid::new("a@b/c").unwrap());
1082
1083 let elem: minidom::Element = "<message xmlns='ns1' from='a@b'/>".parse().unwrap();
1084 let to: BareJid = elem.attr("from").unwrap().parse().unwrap();
1085 assert_eq!(to, BareJid::new("a@b").unwrap());
1086 }
1087
1088 #[cfg(feature = "minidom")]
1089 #[test]
1090 fn minidom_into_attr() {
1091 let full = FullJid::new("a@b/c").unwrap();
1092 let elem = minidom::Element::builder("message", "jabber:client")
1093 .attr("from", full.clone())
1094 .build();
1095 assert_eq!(elem.attr("from"), Some(full.to_string().as_str()));
1096
1097 let bare = BareJid::new("a@b").unwrap();
1098 let elem = minidom::Element::builder("message", "jabber:client")
1099 .attr("from", bare.clone())
1100 .build();
1101 assert_eq!(elem.attr("from"), Some(bare.to_string().as_str()));
1102
1103 let jid = Jid::from(bare.clone());
1104 let _elem = minidom::Element::builder("message", "jabber:client")
1105 .attr("from", jid)
1106 .build();
1107 assert_eq!(elem.attr("from"), Some(bare.to_string().as_str()));
1108 }
1109
1110 #[test]
1111 fn stringprep() {
1112 let full = FullJid::from_str("Test@☃.coM/Test™").unwrap();
1113 let equiv = FullJid::new("test@☃.com/TestTM").unwrap();
1114 assert_eq!(full, equiv);
1115 }
1116
1117 #[test]
1118 fn invalid_stringprep() {
1119 FullJid::from_str("a@b/🎉").unwrap_err();
1120 }
1121
1122 #[test]
1123 fn jid_from_parts() {
1124 let node = NodePart::new("node").unwrap();
1125 let domain = DomainPart::new("domain").unwrap();
1126 let resource = ResourcePart::new("resource").unwrap();
1127
1128 let jid = Jid::from_parts(Some(&node), &domain, Some(&resource));
1129 assert_eq!(jid, Jid::new("node@domain/resource").unwrap());
1130
1131 let barejid = BareJid::from_parts(Some(&node), &domain);
1132 assert_eq!(barejid, BareJid::new("node@domain").unwrap());
1133
1134 let fulljid = FullJid::from_parts(Some(&node), &domain, &resource);
1135 assert_eq!(fulljid, FullJid::new("node@domain/resource").unwrap());
1136 }
1137
1138 #[test]
1139 #[cfg(feature = "serde")]
1140 fn jid_ser_de() {
1141 let jid: Jid = Jid::new("node@domain").unwrap();
1142 serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain")]);
1143
1144 let jid: Jid = Jid::new("node@domain/resource").unwrap();
1145 serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain/resource")]);
1146
1147 let jid: BareJid = BareJid::new("node@domain").unwrap();
1148 serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain")]);
1149
1150 let jid: FullJid = FullJid::new("node@domain/resource").unwrap();
1151 serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain/resource")]);
1152 }
1153
1154 #[test]
1155 fn jid_into_parts_and_from_parts() {
1156 let node = NodePart::new("node").unwrap();
1157 let domain = DomainPart::new("domain").unwrap();
1158
1159 let jid1 = domain.with_node(&node);
1160 let jid2 = node.with_domain(&domain);
1161 let jid3 = BareJid::new("node@domain").unwrap();
1162 assert_eq!(jid1, jid2);
1163 assert_eq!(jid2, jid3);
1164 }
1165
1166 #[test]
1167 fn jid_match_replacement_try_as() {
1168 let jid1 = Jid::new("foo@bar").unwrap();
1169 let jid2 = Jid::new("foo@bar/baz").unwrap();
1170
1171 match jid1.try_as_full() {
1172 Err(_) => (),
1173 other => panic!("unexpected result: {:?}", other),
1174 };
1175
1176 match jid2.try_as_full() {
1177 Ok(_) => (),
1178 other => panic!("unexpected result: {:?}", other),
1179 };
1180 }
1181
1182 #[test]
1183 fn jid_match_replacement_try_as_mut() {
1184 let mut jid1 = Jid::new("foo@bar").unwrap();
1185 let mut jid2 = Jid::new("foo@bar/baz").unwrap();
1186
1187 match jid1.try_as_full_mut() {
1188 Err(_) => (),
1189 other => panic!("unexpected result: {:?}", other),
1190 };
1191
1192 match jid2.try_as_full_mut() {
1193 Ok(_) => (),
1194 other => panic!("unexpected result: {:?}", other),
1195 };
1196 }
1197
1198 #[test]
1199 fn jid_match_replacement_try_into() {
1200 let jid1 = Jid::new("foo@bar").unwrap();
1201 let jid2 = Jid::new("foo@bar/baz").unwrap();
1202
1203 match jid1.try_as_full() {
1204 Err(_) => (),
1205 other => panic!("unexpected result: {:?}", other),
1206 };
1207
1208 match jid2.try_as_full() {
1209 Ok(_) => (),
1210 other => panic!("unexpected result: {:?}", other),
1211 };
1212 }
1213
1214 #[test]
1215 fn lookup_jid_by_full_jid() {
1216 let mut map: HashSet<Jid> = HashSet::new();
1217 let jid1 = Jid::new("foo@bar").unwrap();
1218 let jid2 = Jid::new("foo@bar/baz").unwrap();
1219 let jid3 = FullJid::new("foo@bar/baz").unwrap();
1220
1221 map.insert(jid1);
1222 assert!(!map.contains(&jid2));
1223 assert!(!map.contains(&jid3));
1224 map.insert(jid2);
1225 assert!(map.contains(&jid3));
1226 }
1227
1228 #[test]
1229 fn lookup_full_jid_by_jid() {
1230 let mut map: HashSet<FullJid> = HashSet::new();
1231 let jid1 = FullJid::new("foo@bar/baz").unwrap();
1232 let jid2 = FullJid::new("foo@bar/fnord").unwrap();
1233 let jid3 = Jid::new("foo@bar/fnord").unwrap();
1234
1235 map.insert(jid1);
1236 assert!(!map.contains(&jid2));
1237 assert!(!map.contains(&jid3));
1238 map.insert(jid2);
1239 assert!(map.contains(&jid3));
1240 }
1241
1242 #[test]
1243 fn lookup_bare_jid_by_jid() {
1244 let mut map: HashSet<BareJid> = HashSet::new();
1245 let jid1 = BareJid::new("foo@bar").unwrap();
1246 let jid2 = BareJid::new("foo@baz").unwrap();
1247 let jid3 = Jid::new("foo@baz").unwrap();
1248
1249 map.insert(jid1);
1250 assert!(!map.contains(&jid2));
1251 assert!(!map.contains(&jid3));
1252 map.insert(jid2);
1253 assert!(map.contains(&jid3));
1254 }
1255
1256 #[test]
1257 fn normalizes_all_parts() {
1258 assert_eq!(
1259 Jid::new("ßA@IX.test/\u{2168}").unwrap().as_str(),
1260 "ssa@ix.test/IX"
1261 );
1262 }
1263
1264 #[test]
1265 fn rejects_unassigned_codepoints() {
1266 match Jid::new("\u{01f601}@example.com") {
1267 Err(Error::NodePrep) => (),
1268 other => panic!("unexpected result: {:?}", other),
1269 };
1270
1271 match Jid::new("foo@\u{01f601}.example.com") {
1272 Err(Error::NamePrep) => (),
1273 other => panic!("unexpected result: {:?}", other),
1274 };
1275
1276 match Jid::new("foo@example.com/\u{01f601}") {
1277 Err(Error::ResourcePrep) => (),
1278 other => panic!("unexpected result: {:?}", other),
1279 };
1280 }
1281
1282 #[test]
1283 fn accepts_domain_only_jid() {
1284 match Jid::new("example.com") {
1285 Ok(_) => (),
1286 other => panic!("unexpected result: {:?}", other),
1287 };
1288
1289 match BareJid::new("example.com") {
1290 Ok(_) => (),
1291 other => panic!("unexpected result: {:?}", other),
1292 };
1293
1294 match FullJid::new("example.com/x") {
1295 Ok(_) => (),
1296 other => panic!("unexpected result: {:?}", other),
1297 };
1298 }
1299
1300 #[test]
1301 fn is_bare_returns_true_iff_bare() {
1302 let bare = Jid::new("foo@bar").unwrap();
1303 let full = Jid::new("foo@bar/baz").unwrap();
1304
1305 assert!(bare.is_bare());
1306 assert!(!full.is_bare());
1307 }
1308
1309 #[test]
1310 fn is_full_returns_true_iff_full() {
1311 let bare = Jid::new("foo@bar").unwrap();
1312 let full = Jid::new("foo@bar/baz").unwrap();
1313
1314 assert!(!bare.is_full());
1315 assert!(full.is_full());
1316 }
1317
1318 #[test]
1319 fn reject_long_localpart() {
1320 let mut long = Vec::with_capacity(1028);
1321 long.resize(1024, b'a');
1322 let mut long = String::from_utf8(long).unwrap();
1323 long.push_str("@foo");
1324
1325 match Jid::new(&long) {
1326 Err(Error::NodeTooLong) => (),
1327 other => panic!("unexpected result: {:?}", other),
1328 }
1329
1330 match BareJid::new(&long) {
1331 Err(Error::NodeTooLong) => (),
1332 other => panic!("unexpected result: {:?}", other),
1333 }
1334 }
1335
1336 #[test]
1337 fn reject_long_domainpart() {
1338 let mut long = Vec::with_capacity(1028);
1339 long.push(b'x');
1340 long.push(b'@');
1341 long.resize(1026, b'a');
1342 let long = String::from_utf8(long).unwrap();
1343
1344 match Jid::new(&long) {
1345 Err(Error::DomainTooLong) => (),
1346 other => panic!("unexpected result: {:?}", other),
1347 }
1348
1349 match BareJid::new(&long) {
1350 Err(Error::DomainTooLong) => (),
1351 other => panic!("unexpected result: {:?}", other),
1352 }
1353 }
1354
1355 #[test]
1356 fn reject_long_resourcepart() {
1357 let mut long = Vec::with_capacity(1028);
1358 long.push(b'x');
1359 long.push(b'@');
1360 long.push(b'y');
1361 long.push(b'/');
1362 long.resize(1028, b'a');
1363 let long = String::from_utf8(long).unwrap();
1364
1365 match Jid::new(&long) {
1366 Err(Error::ResourceTooLong) => (),
1367 other => panic!("unexpected result: {:?}", other),
1368 }
1369
1370 match FullJid::new(&long) {
1371 Err(Error::ResourceTooLong) => (),
1372 other => panic!("unexpected result: {:?}", other),
1373 }
1374 }
1375}