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//! 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}