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::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}