lib.rs

  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}