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