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::convert::TryFrom;
35use std::fmt;
36use std::str::FromStr;
37use stringprep::resourceprep;
38
39#[cfg(feature = "serde")]
40use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
41
42mod error;
43pub use crate::error::Error;
44
45mod inner;
46use inner::InnerJid;
47
48/// An enum representing a Jabber ID. It can be either a `FullJid` or a `BareJid`.
49#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
50#[cfg_attr(feature = "serde", serde(untagged))]
51#[derive(Debug, Clone, PartialEq, Eq, Hash)]
52pub enum Jid {
53 /// Contains a [`BareJid`], without a resource part
54 Bare(BareJid),
55
56 /// Contains a [`FullJid`], with a resource part
57 Full(FullJid),
58}
59
60impl FromStr for Jid {
61 type Err = Error;
62
63 fn from_str(s: &str) -> Result<Jid, Error> {
64 Jid::new(s)
65 }
66}
67
68impl From<BareJid> for Jid {
69 fn from(bare_jid: BareJid) -> Jid {
70 Jid::Bare(bare_jid)
71 }
72}
73
74impl From<FullJid> for Jid {
75 fn from(full_jid: FullJid) -> Jid {
76 Jid::Full(full_jid)
77 }
78}
79
80impl fmt::Display for Jid {
81 fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
82 match self {
83 Jid::Bare(bare) => bare.fmt(fmt),
84 Jid::Full(full) => full.fmt(fmt),
85 }
86 }
87}
88
89impl Jid {
90 /// Constructs a Jabber ID from a string. This is of the form
91 /// `node`@`domain`/`resource`, where node and resource parts are optional.
92 ///
93 /// # Examples
94 ///
95 /// ```
96 /// use jid::Jid;
97 /// # use jid::Error;
98 ///
99 /// # fn main() -> Result<(), Error> {
100 /// let jid = Jid::new("node@domain/resource")?;
101 ///
102 /// assert_eq!(jid.node(), Some("node"));
103 /// assert_eq!(jid.domain(), "domain");
104 /// assert_eq!(jid.resource(), Some("resource"));
105 /// # Ok(())
106 /// # }
107 /// ```
108 pub fn new(s: &str) -> Result<Jid, Error> {
109 let inner = InnerJid::new(s)?;
110 if inner.slash.is_some() {
111 Ok(Jid::Full(FullJid { inner }))
112 } else {
113 Ok(Jid::Bare(BareJid { inner }))
114 }
115 }
116
117 /// The optional node part of the JID.
118 pub fn node(&self) -> Option<&str> {
119 match self {
120 Jid::Bare(BareJid { inner }) | Jid::Full(FullJid { inner }) => inner.node(),
121 }
122 }
123
124 /// The domain part of the JID.
125 pub fn domain(&self) -> &str {
126 match self {
127 Jid::Bare(BareJid { inner }) | Jid::Full(FullJid { inner }) => inner.domain(),
128 }
129 }
130
131 /// The optional resource part of the JID.
132 pub fn resource(&self) -> Option<&str> {
133 match self {
134 Jid::Bare(BareJid { inner }) | Jid::Full(FullJid { inner }) => inner.resource(),
135 }
136 }
137
138 /// Allocate a new [`BareJid`] from this JID, discarding the resource.
139 pub fn to_bare(&self) -> BareJid {
140 match self {
141 Jid::Full(jid) => jid.to_bare(),
142 Jid::Bare(jid) => jid.clone(),
143 }
144 }
145
146 /// Transforms this JID into a [`BareJid`], throwing away the resource.
147 pub fn into_bare(self) -> BareJid {
148 match self {
149 Jid::Full(jid) => jid.into_bare(),
150 Jid::Bare(jid) => jid,
151 }
152 }
153}
154
155impl TryFrom<Jid> for FullJid {
156 type Error = Error;
157
158 fn try_from(jid: Jid) -> Result<Self, Self::Error> {
159 match jid {
160 Jid::Full(full) => Ok(full),
161 Jid::Bare(_) => Err(Error::ResourceMissingInFullJid),
162 }
163 }
164}
165
166impl PartialEq<Jid> for FullJid {
167 fn eq(&self, other: &Jid) -> bool {
168 match other {
169 Jid::Full(full) => self == full,
170 Jid::Bare(_) => false,
171 }
172 }
173}
174
175impl PartialEq<Jid> for BareJid {
176 fn eq(&self, other: &Jid) -> bool {
177 match other {
178 Jid::Full(_) => false,
179 Jid::Bare(bare) => self == bare,
180 }
181 }
182}
183
184impl PartialEq<FullJid> for Jid {
185 fn eq(&self, other: &FullJid) -> bool {
186 match self {
187 Jid::Full(full) => full == other,
188 Jid::Bare(_) => false,
189 }
190 }
191}
192
193impl PartialEq<BareJid> for Jid {
194 fn eq(&self, other: &BareJid) -> bool {
195 match self {
196 Jid::Full(_) => false,
197 Jid::Bare(bare) => bare == other,
198 }
199 }
200}
201
202/// A struct representing a full Jabber ID, with a resource part.
203///
204/// A full JID is composed of 3 components, of which only the node is optional:
205///
206/// - the (optional) node part is the part before the (optional) `@`.
207/// - the domain part is the mandatory part between the (optional) `@` and before the `/`.
208/// - the resource part after the `/`.
209///
210/// Unlike a [`BareJid`], it always contains a resource, and should only be used when you are
211/// certain there is no case where a resource can be missing. Otherwise, use a [`Jid`] or
212/// [`BareJid`].
213#[derive(Clone, PartialEq, Eq, Hash)]
214pub struct FullJid {
215 inner: InnerJid,
216}
217
218/// A struct representing a bare Jabber ID, without a resource part.
219///
220/// A bare JID is composed of 2 components, of which only the node is optional:
221/// - the (optional) node part is the part before the (optional) `@`.
222/// - the domain part is the mandatory part between the (optional) `@` and before the `/`.
223///
224/// Unlike a [`FullJid`], it can’t contain a resource, and should only be used when you are certain
225/// there is no case where a resource can be set. Otherwise, use a [`Jid`] or [`FullJid`].
226#[derive(Clone, PartialEq, Eq, Hash)]
227pub struct BareJid {
228 inner: InnerJid,
229}
230
231impl fmt::Debug for FullJid {
232 fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
233 write!(fmt, "FullJID({})", self)
234 }
235}
236
237impl fmt::Debug for BareJid {
238 fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
239 write!(fmt, "BareJID({})", self)
240 }
241}
242
243impl fmt::Display for FullJid {
244 fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
245 fmt.write_str(&self.inner.normalized)
246 }
247}
248
249impl fmt::Display for BareJid {
250 fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
251 fmt.write_str(&self.inner.normalized)
252 }
253}
254
255#[cfg(feature = "serde")]
256impl Serialize for FullJid {
257 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
258 where
259 S: Serializer,
260 {
261 serializer.serialize_str(String::from(self).as_str())
262 }
263}
264
265#[cfg(feature = "serde")]
266impl Serialize for BareJid {
267 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
268 where
269 S: Serializer,
270 {
271 serializer.serialize_str(String::from(self).as_str())
272 }
273}
274
275impl FromStr for FullJid {
276 type Err = Error;
277
278 fn from_str(s: &str) -> Result<FullJid, Error> {
279 FullJid::new(s)
280 }
281}
282
283#[cfg(feature = "serde")]
284impl<'de> Deserialize<'de> for FullJid {
285 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
286 where
287 D: Deserializer<'de>,
288 {
289 let s = String::deserialize(deserializer)?;
290 FullJid::from_str(&s).map_err(de::Error::custom)
291 }
292}
293
294#[cfg(feature = "serde")]
295impl<'de> Deserialize<'de> for BareJid {
296 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
297 where
298 D: Deserializer<'de>,
299 {
300 let s = String::deserialize(deserializer)?;
301 BareJid::from_str(&s).map_err(de::Error::custom)
302 }
303}
304
305impl FullJid {
306 /// Constructs a full Jabber ID containing all three components. This is of the form
307 /// `node@domain/resource`, where node part is optional.
308 ///
309 /// # Examples
310 ///
311 /// ```
312 /// use jid::FullJid;
313 /// # use jid::Error;
314 ///
315 /// # fn main() -> Result<(), Error> {
316 /// let jid = FullJid::new("node@domain/resource")?;
317 ///
318 /// assert_eq!(jid.node(), Some("node"));
319 /// assert_eq!(jid.domain(), "domain");
320 /// assert_eq!(jid.resource(), "resource");
321 /// # Ok(())
322 /// # }
323 /// ```
324 pub fn new(s: &str) -> Result<FullJid, Error> {
325 let inner = InnerJid::new(s)?;
326 if inner.slash.is_some() {
327 Ok(FullJid { inner })
328 } else {
329 Err(Error::ResourceMissingInFullJid)
330 }
331 }
332
333 /// The optional node part of the JID.
334 pub fn node(&self) -> Option<&str> {
335 self.inner.node()
336 }
337
338 /// The domain part of the JID.
339 pub fn domain(&self) -> &str {
340 self.inner.domain()
341 }
342
343 /// The optional resource of the Jabber ID. Since this is a full JID it is always present.
344 pub fn resource(&self) -> &str {
345 self.inner.resource().unwrap()
346 }
347
348 /// Allocate a new [`BareJid`] from this full JID, discarding the resource.
349 pub fn to_bare(&self) -> BareJid {
350 let slash = self.inner.slash.unwrap().get() as usize;
351 let normalized = self.inner.normalized[..slash].to_string();
352 let inner = InnerJid {
353 normalized,
354 at: self.inner.at,
355 slash: None,
356 };
357 BareJid { inner }
358 }
359
360 /// Transforms this full JID into a [`BareJid`], discarding the resource.
361 pub fn into_bare(mut self) -> BareJid {
362 let slash = self.inner.slash.unwrap().get() as usize;
363 self.inner.normalized.truncate(slash);
364 self.inner.normalized.shrink_to_fit();
365 self.inner.slash = None;
366 BareJid { inner: self.inner }
367 }
368}
369
370impl FromStr for BareJid {
371 type Err = Error;
372
373 fn from_str(s: &str) -> Result<BareJid, Error> {
374 BareJid::new(s)
375 }
376}
377
378impl BareJid {
379 /// Constructs a bare Jabber ID, containing two components. This is of the form
380 /// `node`@`domain`, where node part is optional.
381 ///
382 /// # Examples
383 ///
384 /// ```
385 /// use jid::BareJid;
386 /// # use jid::Error;
387 ///
388 /// # fn main() -> Result<(), Error> {
389 /// let jid = BareJid::new("node@domain")?;
390 ///
391 /// assert_eq!(jid.node(), Some("node"));
392 /// assert_eq!(jid.domain(), "domain");
393 /// # Ok(())
394 /// # }
395 /// ```
396 pub fn new(s: &str) -> Result<BareJid, Error> {
397 let inner = InnerJid::new(s)?;
398 if inner.slash.is_none() {
399 Ok(BareJid { inner })
400 } else {
401 Err(Error::ResourceInBareJid)
402 }
403 }
404
405 /// The optional node part of the JID.
406 pub fn node(&self) -> Option<&str> {
407 self.inner.node()
408 }
409
410 /// The domain part of the JID.
411 pub fn domain(&self) -> &str {
412 self.inner.domain()
413 }
414
415 /// Constructs a [`FullJid`] from the bare JID, by specifying a `resource`.
416 ///
417 /// # Examples
418 ///
419 /// ```
420 /// use jid::BareJid;
421 ///
422 /// let bare = BareJid::new("node@domain").unwrap();
423 /// let full = bare.with_resource("resource").unwrap();
424 ///
425 /// assert_eq!(full.node(), Some("node"));
426 /// assert_eq!(full.domain(), "domain");
427 /// assert_eq!(full.resource(), "resource");
428 /// ```
429 pub fn with_resource(&self, resource: &str) -> Result<FullJid, Error> {
430 let resource = resourceprep(resource).map_err(|_| Error::ResourcePrep)?;
431 let slash = NonZeroU16::new(self.inner.normalized.len() as u16);
432 let normalized = format!("{}/{resource}", self.inner.normalized);
433 let inner = InnerJid {
434 normalized,
435 at: self.inner.at,
436 slash,
437 };
438 Ok(FullJid { inner })
439 }
440}
441
442#[cfg(feature = "minidom")]
443use minidom::{IntoAttributeValue, Node};
444
445#[cfg(feature = "minidom")]
446impl IntoAttributeValue for Jid {
447 fn into_attribute_value(self) -> Option<String> {
448 Some(format!("{}", self))
449 }
450}
451
452#[cfg(feature = "minidom")]
453impl From<Jid> for Node {
454 fn from(jid: Jid) -> Node {
455 Node::Text(format!("{}", jid))
456 }
457}
458
459#[cfg(feature = "minidom")]
460impl IntoAttributeValue for FullJid {
461 fn into_attribute_value(self) -> Option<String> {
462 Some(format!("{}", self))
463 }
464}
465
466#[cfg(feature = "minidom")]
467impl From<FullJid> for Node {
468 fn from(jid: FullJid) -> Node {
469 Node::Text(format!("{}", jid))
470 }
471}
472
473#[cfg(feature = "minidom")]
474impl IntoAttributeValue for BareJid {
475 fn into_attribute_value(self) -> Option<String> {
476 Some(format!("{}", self))
477 }
478}
479
480#[cfg(feature = "minidom")]
481impl From<BareJid> for Node {
482 fn from(jid: BareJid) -> Node {
483 Node::Text(format!("{}", jid))
484 }
485}
486
487#[cfg(test)]
488mod tests {
489 use super::*;
490
491 use std::collections::HashMap;
492
493 macro_rules! assert_size (
494 ($t:ty, $sz:expr) => (
495 assert_eq!(::std::mem::size_of::<$t>(), $sz);
496 );
497 );
498
499 #[cfg(target_pointer_width = "32")]
500 #[test]
501 fn test_size() {
502 assert_size!(BareJid, 16);
503 assert_size!(FullJid, 16);
504 assert_size!(Jid, 20);
505 }
506
507 #[cfg(target_pointer_width = "64")]
508 #[test]
509 fn test_size() {
510 assert_size!(BareJid, 32);
511 assert_size!(FullJid, 32);
512 assert_size!(Jid, 40);
513 }
514
515 #[test]
516 fn can_parse_full_jids() {
517 assert_eq!(
518 FullJid::from_str("a@b.c/d"),
519 Ok(FullJid::new("a@b.c/d").unwrap())
520 );
521 assert_eq!(
522 FullJid::from_str("b.c/d"),
523 Ok(FullJid::new("b.c/d").unwrap())
524 );
525
526 assert_eq!(
527 FullJid::from_str("a@b.c"),
528 Err(Error::ResourceMissingInFullJid)
529 );
530 assert_eq!(
531 FullJid::from_str("b.c"),
532 Err(Error::ResourceMissingInFullJid)
533 );
534 }
535
536 #[test]
537 fn can_parse_bare_jids() {
538 assert_eq!(
539 BareJid::from_str("a@b.c"),
540 Ok(BareJid::new("a@b.c").unwrap())
541 );
542 assert_eq!(BareJid::from_str("b.c"), Ok(BareJid::new("b.c").unwrap()));
543 }
544
545 #[test]
546 fn can_parse_jids() {
547 let full = FullJid::from_str("a@b.c/d").unwrap();
548 let bare = BareJid::from_str("e@f.g").unwrap();
549
550 assert_eq!(Jid::from_str("a@b.c/d"), Ok(Jid::Full(full)));
551 assert_eq!(Jid::from_str("e@f.g"), Ok(Jid::Bare(bare)));
552 }
553
554 #[test]
555 fn full_to_bare_jid() {
556 let bare: BareJid = FullJid::new("a@b.c/d").unwrap().to_bare();
557 assert_eq!(bare, BareJid::new("a@b.c").unwrap());
558 }
559
560 #[test]
561 fn bare_to_full_jid() {
562 assert_eq!(
563 BareJid::new("a@b.c").unwrap().with_resource("d").unwrap(),
564 FullJid::new("a@b.c/d").unwrap()
565 );
566 }
567
568 #[test]
569 fn node_from_jid() {
570 assert_eq!(
571 Jid::Full(FullJid::new("a@b.c/d").unwrap()).node(),
572 Some("a"),
573 );
574 }
575
576 #[test]
577 fn domain_from_jid() {
578 assert_eq!(Jid::Bare(BareJid::new("a@b.c").unwrap()).domain(), "b.c");
579 }
580
581 #[test]
582 fn jid_to_full_bare() {
583 let full = FullJid::new("a@b.c/d").unwrap();
584 let bare = BareJid::new("a@b.c").unwrap();
585
586 assert_eq!(FullJid::try_from(Jid::Full(full.clone())), Ok(full.clone()));
587 assert_eq!(
588 FullJid::try_from(Jid::Bare(bare.clone())),
589 Err(Error::ResourceMissingInFullJid),
590 );
591 assert_eq!(Jid::Bare(full.clone().to_bare()), bare.clone());
592 assert_eq!(Jid::Bare(bare.clone()), bare);
593 }
594
595 #[test]
596 fn serialise() {
597 assert_eq!(
598 format!("{}", FullJid::new("a@b/c").unwrap()),
599 String::from("a@b/c")
600 );
601 assert_eq!(
602 format!("{}", BareJid::new("a@b").unwrap()),
603 String::from("a@b")
604 );
605 }
606
607 #[test]
608 fn hash() {
609 let _map: HashMap<Jid, String> = HashMap::new();
610 }
611
612 #[test]
613 fn invalid_jids() {
614 assert_eq!(BareJid::from_str(""), Err(Error::DomainEmpty));
615 assert_eq!(BareJid::from_str("/c"), Err(Error::DomainEmpty));
616 assert_eq!(BareJid::from_str("a@/c"), Err(Error::DomainEmpty));
617 assert_eq!(BareJid::from_str("@b"), Err(Error::NodeEmpty));
618 assert_eq!(BareJid::from_str("b/"), Err(Error::ResourceEmpty));
619
620 assert_eq!(FullJid::from_str(""), Err(Error::DomainEmpty));
621 assert_eq!(FullJid::from_str("/c"), Err(Error::DomainEmpty));
622 assert_eq!(FullJid::from_str("a@/c"), Err(Error::DomainEmpty));
623 assert_eq!(FullJid::from_str("@b"), Err(Error::NodeEmpty));
624 assert_eq!(FullJid::from_str("b/"), Err(Error::ResourceEmpty));
625 assert_eq!(
626 FullJid::from_str("a@b"),
627 Err(Error::ResourceMissingInFullJid)
628 );
629 }
630
631 #[test]
632 fn display_jids() {
633 assert_eq!(
634 format!("{}", FullJid::new("a@b/c").unwrap()),
635 String::from("a@b/c")
636 );
637 assert_eq!(
638 format!("{}", BareJid::new("a@b").unwrap()),
639 String::from("a@b")
640 );
641 assert_eq!(
642 format!("{}", Jid::Full(FullJid::new("a@b/c").unwrap())),
643 String::from("a@b/c")
644 );
645 assert_eq!(
646 format!("{}", Jid::Bare(BareJid::new("a@b").unwrap())),
647 String::from("a@b")
648 );
649 }
650
651 #[cfg(feature = "minidom")]
652 #[test]
653 fn minidom() {
654 let elem: minidom::Element = "<message xmlns='ns1' from='a@b/c'/>".parse().unwrap();
655 let to: Jid = elem.attr("from").unwrap().parse().unwrap();
656 assert_eq!(to, Jid::Full(FullJid::new("a@b/c").unwrap()));
657
658 let elem: minidom::Element = "<message xmlns='ns1' from='a@b'/>".parse().unwrap();
659 let to: Jid = elem.attr("from").unwrap().parse().unwrap();
660 assert_eq!(to, Jid::Bare(BareJid::new("a@b").unwrap()));
661
662 let elem: minidom::Element = "<message xmlns='ns1' from='a@b/c'/>".parse().unwrap();
663 let to: FullJid = elem.attr("from").unwrap().parse().unwrap();
664 assert_eq!(to, FullJid::new("a@b/c").unwrap());
665
666 let elem: minidom::Element = "<message xmlns='ns1' from='a@b'/>".parse().unwrap();
667 let to: BareJid = elem.attr("from").unwrap().parse().unwrap();
668 assert_eq!(to, BareJid::new("a@b").unwrap());
669 }
670
671 #[cfg(feature = "minidom")]
672 #[test]
673 fn minidom_into_attr() {
674 let full = FullJid::new("a@b/c").unwrap();
675 let elem = minidom::Element::builder("message", "jabber:client")
676 .attr("from", full.clone())
677 .build();
678 assert_eq!(elem.attr("from"), Some(format!("{}", full).as_str()));
679
680 let bare = BareJid::new("a@b").unwrap();
681 let elem = minidom::Element::builder("message", "jabber:client")
682 .attr("from", bare.clone())
683 .build();
684 assert_eq!(elem.attr("from"), Some(format!("{}", bare).as_str()));
685
686 let jid = Jid::Bare(bare.clone());
687 let _elem = minidom::Element::builder("message", "jabber:client")
688 .attr("from", jid)
689 .build();
690 assert_eq!(elem.attr("from"), Some(format!("{}", bare).as_str()));
691 }
692
693 #[test]
694 fn stringprep() {
695 let full = FullJid::from_str("Test@☃.coM/Test™").unwrap();
696 let equiv = FullJid::new("test@☃.com/TestTM").unwrap();
697 assert_eq!(full, equiv);
698 }
699}