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