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