Skip to main content

jid/
parts.rs

1use alloc::borrow::{Cow, ToOwned};
2use alloc::string::{String, ToString};
3use alloc::vec::Vec;
4use core::borrow::Borrow;
5use core::fmt;
6use core::mem;
7use core::ops::Deref;
8use core::str::FromStr;
9
10#[cfg(feature = "serde")]
11use serde::{Deserialize, Serialize};
12
13use crate::{domain_check, node_check, resource_check};
14use crate::{BareJid, Error, Jid};
15
16macro_rules! def_part_parse_doc {
17    ($name:ident, $other:ident, $more:expr) => {
18        concat!(
19            "Parse a [`",
20            stringify!($name),
21            "`] from a `",
22            stringify!($other),
23            "`, copying its contents.\n",
24            "\n",
25            "If the given `",
26            stringify!($other),
27            "` does not conform to the restrictions imposed by `",
28            stringify!($name),
29            "`, an error is returned.\n",
30            $more,
31        )
32    };
33}
34
35macro_rules! def_part_into_inner_doc {
36    ($name:ident, $other:ident, $more:expr) => {
37        concat!(
38            "Consume the `",
39            stringify!($name),
40            "` and return the inner `",
41            stringify!($other),
42            "`.\n",
43            $more,
44        )
45    };
46}
47
48#[cfg(feature = "serde")]
49#[derive(Deserialize)]
50struct NodeDeserializer<'a>(Cow<'a, str>);
51
52#[cfg(feature = "serde")]
53impl TryFrom<NodeDeserializer<'_>> for NodePart {
54    type Error = Error;
55
56    fn try_from(deserializer: NodeDeserializer) -> Result<NodePart, Self::Error> {
57        Ok(NodePart::new(&deserializer.0)?.into_owned())
58    }
59}
60
61#[cfg(feature = "serde")]
62#[derive(Deserialize)]
63struct DomainDeserializer<'a>(Cow<'a, str>);
64
65#[cfg(feature = "serde")]
66impl TryFrom<DomainDeserializer<'_>> for DomainPart {
67    type Error = Error;
68
69    fn try_from(deserializer: DomainDeserializer) -> Result<DomainPart, Self::Error> {
70        Ok(DomainPart::new(&deserializer.0)?.into_owned())
71    }
72}
73
74#[cfg(feature = "serde")]
75#[derive(Deserialize)]
76struct ResourceDeserializer<'a>(Cow<'a, str>);
77
78#[cfg(feature = "serde")]
79impl TryFrom<ResourceDeserializer<'_>> for ResourcePart {
80    type Error = Error;
81
82    fn try_from(deserializer: ResourceDeserializer) -> Result<ResourcePart, Self::Error> {
83        Ok(ResourcePart::new(&deserializer.0)?.into_owned())
84    }
85}
86
87macro_rules! def_part_types {
88    (
89        $(#[$mainmeta:meta])*
90        pub struct $name:ident(String) use $check_fn:ident();
91
92        $(#[$refmeta:meta])*
93        pub struct ref $borrowed:ident(str);
94    ) => {
95        $(#[$mainmeta])*
96        #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
97        #[repr(transparent)]
98        pub struct $name(pub(crate) String);
99
100        impl $name {
101            #[doc = def_part_parse_doc!($name, str, "Depending on whether the contents are changed by normalisation operations, this function either returns a copy or a reference to the original data.")]
102            #[allow(clippy::new_ret_no_self)]
103            pub fn new(s: &str) -> Result<Cow<'_, $borrowed>, Error> {
104                let part = $check_fn(s)?;
105                match part {
106                    Cow::Borrowed(v) => Ok(Cow::Borrowed($borrowed::from_str_unchecked(v))),
107                    Cow::Owned(v) => Ok(Cow::Owned(Self(v))),
108                }
109            }
110
111            #[doc = def_part_into_inner_doc!($name, String, "")]
112            pub fn into_inner(self) -> String {
113                self.0
114            }
115        }
116
117        impl FromStr for $name {
118            type Err = Error;
119
120            fn from_str(s: &str) -> Result<Self, Error> {
121                Ok(Self::new(s)?.into_owned())
122            }
123        }
124
125        impl fmt::Display for $name {
126            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127                <$borrowed as fmt::Display>::fmt(Borrow::<$borrowed>::borrow(self), f)
128            }
129        }
130
131        impl Deref for $name {
132            type Target = $borrowed;
133
134            fn deref(&self) -> &Self::Target {
135                Borrow::<$borrowed>::borrow(self)
136            }
137        }
138
139        impl AsRef<$borrowed> for $name {
140            fn as_ref(&self) -> &$borrowed {
141                Borrow::<$borrowed>::borrow(self)
142            }
143        }
144
145        impl AsRef<String> for $name {
146            fn as_ref(&self) -> &String {
147                &self.0
148            }
149        }
150
151        impl Borrow<$borrowed> for $name {
152            fn borrow(&self) -> &$borrowed {
153                $borrowed::from_str_unchecked(self.0.as_str())
154            }
155        }
156
157        // useful for use in hashmaps
158        impl Borrow<String> for $name {
159            fn borrow(&self) -> &String {
160                &self.0
161            }
162        }
163
164        // useful for use in hashmaps
165        impl Borrow<str> for $name {
166            fn borrow(&self) -> &str {
167                self.0.as_str()
168            }
169        }
170
171        impl<'x> TryFrom<&'x str> for $name {
172            type Error = Error;
173
174            fn try_from(s: &str) -> Result<Self, Error> {
175                Self::from_str(s)
176            }
177        }
178
179        impl From<&$borrowed> for $name {
180            fn from(other: &$borrowed) -> Self {
181                other.to_owned()
182            }
183        }
184
185        impl<'x> From<Cow<'x, $borrowed>> for $name {
186            fn from(other: Cow<'x, $borrowed>) -> Self {
187                other.into_owned()
188            }
189        }
190
191        $(#[$refmeta])*
192        #[repr(transparent)]
193        #[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
194        pub struct $borrowed(pub(crate) str);
195
196        impl $borrowed {
197            pub(crate) fn from_str_unchecked(s: &str) -> &Self {
198                // SAFETY: repr(transparent) thing can be transmuted to/from
199                // its inner.
200                unsafe { mem::transmute(s) }
201            }
202
203            /// Access the contents as [`str`] slice.
204            pub fn as_str(&self) -> &str {
205                &self.0
206            }
207        }
208
209        impl Deref for $borrowed {
210            type Target = str;
211
212            fn deref(&self) -> &Self::Target {
213                &self.0
214            }
215        }
216
217        impl ToOwned for $borrowed {
218            type Owned = $name;
219
220            fn to_owned(&self) -> Self::Owned {
221                $name(self.0.to_string())
222            }
223        }
224
225        impl AsRef<str> for $borrowed {
226            fn as_ref(&self) -> &str {
227                &self.0
228            }
229        }
230
231        impl fmt::Display for $borrowed {
232            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233                write!(f, "{}", &self.0)
234            }
235        }
236    }
237}
238
239def_part_types! {
240    /// The [`NodePart`] is the optional part before the (optional) `@` in any
241    /// [`Jid`][crate::Jid], whether [`BareJid`][crate::BareJid] or
242    /// [`FullJid`][crate::FullJid].
243    ///
244    /// The corresponding slice type is [`NodeRef`].
245    #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
246    #[cfg_attr(feature = "serde", serde(try_from = "NodeDeserializer"))]
247    pub struct NodePart(String) use node_check();
248
249    /// `str`-like type which conforms to the requirements of [`NodePart`].
250    ///
251    /// See [`NodePart`] for details.
252    #[cfg_attr(feature = "serde", derive(Serialize))]
253    pub struct ref NodeRef(str);
254}
255
256def_part_types! {
257    /// The [`DomainPart`] is the part between the (optional) `@` and the
258    /// (optional) `/` in any [`Jid`][crate::Jid], whether
259    /// [`BareJid`][crate::BareJid] or [`FullJid`][crate::FullJid].
260    #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
261    #[cfg_attr(feature = "serde", serde(try_from = "DomainDeserializer"))]
262    pub struct DomainPart(String) use domain_check();
263
264    /// `str`-like type which conforms to the requirements of [`DomainPart`].
265    ///
266    /// See [`DomainPart`] for details.
267    #[cfg_attr(feature = "serde", derive(Serialize))]
268    pub struct ref DomainRef(str);
269}
270
271def_part_types! {
272    /// The [`ResourcePart`] is the optional part after the `/` in a
273    /// [`Jid`][crate::Jid]. It is mandatory in [`FullJid`][crate::FullJid].
274    #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
275    #[cfg_attr(feature = "serde", serde(try_from = "ResourceDeserializer"))]
276    pub struct ResourcePart(String) use resource_check();
277
278    /// `str`-like type which conforms to the requirements of
279    /// [`ResourcePart`].
280    ///
281    /// See [`ResourcePart`] for details.
282    #[cfg_attr(feature = "serde", derive(Serialize))]
283    pub struct ref ResourceRef(str);
284}
285
286impl DomainRef {
287    /// Construct a bare JID (a JID without a resource) from this domain and
288    /// the given node (local part).
289    pub fn with_node(&self, node: &NodeRef) -> BareJid {
290        BareJid::from_parts(Some(node), self)
291    }
292}
293
294impl From<DomainPart> for BareJid {
295    fn from(other: DomainPart) -> Self {
296        BareJid {
297            inner: other.into(),
298        }
299    }
300}
301
302impl From<DomainPart> for Jid {
303    fn from(other: DomainPart) -> Self {
304        Jid {
305            normalized: other.0,
306            at: None,
307            slash: None,
308        }
309    }
310}
311
312impl<'x> From<&'x DomainRef> for BareJid {
313    fn from(other: &'x DomainRef) -> Self {
314        Self::from_parts(None, other)
315    }
316}
317
318impl NodeRef {
319    /// Construct a bare JID (a JID without a resource) from this node (the
320    /// local part) and the given domain.
321    pub fn with_domain(&self, domain: &DomainRef) -> BareJid {
322        BareJid::from_parts(Some(self), domain)
323    }
324
325    /// Implements XEP-0106 unescape algorithm.
326    pub fn unescape(&self) -> Result<Cow<'_, str>, Error> {
327        fn hex_to_char(bytes: [u8; 2]) -> Result<u8, ()> {
328            Ok(match &[bytes[0], bytes[1]] {
329                b"20" => b' ',
330                b"22" => b'"',
331                b"26" => b'&',
332                b"27" => b'\'',
333                b"2f" => b'/',
334                b"3a" => b':',
335                b"3c" => b'<',
336                b"3e" => b'>',
337                b"40" => b'@',
338                b"5c" => b'\\',
339                _ => return Err(()),
340            })
341        }
342
343        let bytes = self.0.as_bytes();
344        let mut iter = memchr::memchr_iter(b'\\', bytes);
345        Ok(match iter.next() {
346            // Fast path, there is no escape code in the nodepart.
347            None => Cow::Borrowed(self),
348
349            Some(mut index) => {
350                let mut acc = Vec::from(&bytes[..index]);
351                match hex_to_char([bytes[index + 1], bytes[index + 2]]) {
352                    Ok(char) => acc.push(char),
353                    // If the escape code wasn’t valid, we want to copy it as is.
354                    Err(()) => index -= 3,
355                }
356                loop {
357                    match iter.next() {
358                        None => {
359                            acc.extend_from_slice(&bytes[index + 3..]);
360                            break;
361                        }
362                        Some(index2) => {
363                            acc.extend_from_slice(&bytes[index + 3..index2]);
364                            index = index2;
365                            match hex_to_char([bytes[index + 1], bytes[index + 2]]) {
366                                Ok(char) => acc.push(char),
367                                // If the escape code wasn’t valid, we want to copy it as is.
368                                Err(()) => index -= 3,
369                            }
370                        }
371                    }
372                }
373                if acc.starts_with(b" ") || acc.ends_with(b" ") {
374                    return Err(Error::NodePrep);
375                }
376
377                // This unwrap() should never panic, since we started from a valid NodeRef.
378                Cow::Owned(String::try_from(acc).unwrap())
379            }
380        })
381    }
382}
383
384#[cfg(test)]
385mod tests {
386    use super::*;
387
388    #[test]
389    fn nodepart_comparison() {
390        let n1 = NodePart::new("foo").unwrap();
391        let n2 = NodePart::new("bar").unwrap();
392        let n3 = NodePart::new("foo").unwrap();
393        assert_eq!(n1, n3);
394        assert_ne!(n1, n2);
395    }
396
397    #[cfg(feature = "serde")]
398    #[test]
399    fn nodepart_serde() {
400        serde_test::assert_de_tokens(
401            &NodePart(String::from("test")),
402            &[
403                serde_test::Token::TupleStruct {
404                    name: "NodePart",
405                    len: 1,
406                },
407                serde_test::Token::BorrowedStr("test"),
408                serde_test::Token::TupleStructEnd,
409            ],
410        );
411
412        serde_test::assert_de_tokens(
413            &NodePart(String::from("test")),
414            &[
415                serde_test::Token::TupleStruct {
416                    name: "NodePart",
417                    len: 1,
418                },
419                serde_test::Token::String("test"),
420                serde_test::Token::TupleStructEnd,
421            ],
422        );
423
424        serde_test::assert_de_tokens_error::<NodePart>(
425            &[
426                serde_test::Token::TupleStruct {
427                    name: "NodePart",
428                    len: 1,
429                },
430                serde_test::Token::BorrowedStr("invalid@domain"),
431                serde_test::Token::TupleStructEnd,
432            ],
433            "localpart doesn’t pass nodeprep validation",
434        );
435    }
436
437    #[cfg(feature = "serde")]
438    #[test]
439    fn domainpart_serde() {
440        serde_test::assert_de_tokens(
441            &DomainPart(String::from("[::1]")),
442            &[
443                serde_test::Token::TupleStruct {
444                    name: "DomainPart",
445                    len: 1,
446                },
447                serde_test::Token::BorrowedStr("[::1]"),
448                serde_test::Token::TupleStructEnd,
449            ],
450        );
451
452        serde_test::assert_de_tokens(
453            &DomainPart(String::from("[::1]")),
454            &[
455                serde_test::Token::TupleStruct {
456                    name: "DomainPart",
457                    len: 1,
458                },
459                serde_test::Token::String("[::1]"),
460                serde_test::Token::TupleStructEnd,
461            ],
462        );
463
464        serde_test::assert_de_tokens(
465            &DomainPart(String::from("domain.example")),
466            &[
467                serde_test::Token::TupleStruct {
468                    name: "DomainPart",
469                    len: 1,
470                },
471                serde_test::Token::BorrowedStr("domain.example"),
472                serde_test::Token::TupleStructEnd,
473            ],
474        );
475
476        serde_test::assert_de_tokens_error::<DomainPart>(
477            &[
478                serde_test::Token::TupleStruct {
479                    name: "DomainPart",
480                    len: 1,
481                },
482                serde_test::Token::BorrowedStr("invalid@domain"),
483                serde_test::Token::TupleStructEnd,
484            ],
485            "domain doesn’t pass idna validation",
486        );
487    }
488
489    #[cfg(feature = "serde")]
490    #[test]
491    fn resourcepart_serde() {
492        serde_test::assert_de_tokens(
493            &ResourcePart(String::from("test")),
494            &[
495                serde_test::Token::TupleStruct {
496                    name: "ResourcePart",
497                    len: 1,
498                },
499                serde_test::Token::BorrowedStr("test"),
500                serde_test::Token::TupleStructEnd,
501            ],
502        );
503
504        serde_test::assert_de_tokens(
505            &ResourcePart(String::from("test")),
506            &[
507                serde_test::Token::TupleStruct {
508                    name: "ResourcePart",
509                    len: 1,
510                },
511                serde_test::Token::String("test"),
512                serde_test::Token::TupleStructEnd,
513            ],
514        );
515
516        serde_test::assert_de_tokens_error::<ResourcePart>(
517            &[
518                serde_test::Token::TupleStruct {
519                    name: "ResourcePart",
520                    len: 1,
521                },
522                serde_test::Token::BorrowedStr("🤖"),
523                serde_test::Token::TupleStructEnd,
524            ],
525            "resource doesn’t pass resourceprep validation",
526        );
527    }
528
529    #[test]
530    fn unescape() {
531        let node = NodePart::new("foo\\40bar").unwrap();
532        assert_eq!(node.unescape().unwrap(), "foo@bar");
533
534        let node = NodePart::new("\\22\\26\\27\\2f\\20\\3a\\3c\\3e\\40\\5c").unwrap();
535        assert_eq!(node.unescape().unwrap(), "\"&'/ :<>@\\");
536
537        let node = NodePart::new("\\20foo").unwrap();
538        node.unescape().unwrap_err();
539
540        let node = NodePart::new("foo\\20").unwrap();
541        node.unescape().unwrap_err();
542
543        let jid = BareJid::new("tréville\\40musketeers.lit@smtp.gascon.fr").unwrap();
544        let node = jid.node().unwrap();
545        assert_eq!(node.unescape().unwrap(), "tréville@musketeers.lit");
546
547        // Those come from section 5.1 from the XEP.
548        let data = [
549            ("space cadet@example.com", "space\\20cadet@example.com"),
550            (
551                "call me \"ishmael\"@example.com",
552                "call\\20me\\20\\22ishmael\\22@example.com",
553            ),
554            ("at&t guy@example.com", "at\\26t\\20guy@example.com"),
555            ("d'artagnan@example.com", "d\\27artagnan@example.com"),
556            ("/.fanboy@example.com", "\\2f.fanboy@example.com"),
557            ("::foo::@example.com", "\\3a\\3afoo\\3a\\3a@example.com"),
558            ("<foo>@example.com", "\\3cfoo\\3e@example.com"),
559            ("user@host@example.com", "user\\40host@example.com"),
560            ("c:\\net@example.com", "c\\3a\\net@example.com"),
561            ("c:\\\\net@example.com", "c\\3a\\\\net@example.com"),
562            (
563                "c:\\cool stuff@example.com",
564                "c\\3a\\cool\\20stuff@example.com",
565            ),
566            ("c:\\5commas@example.com", "c\\3a\\5c5commas@example.com"),
567        ];
568        for (unescaped, escaped) in data {
569            let jid = BareJid::new(escaped).unwrap();
570            let node = jid.node().unwrap();
571            assert_eq!(
572                alloc::format!("{}@example.com", node.unescape().unwrap()),
573                unescaped
574            );
575        }
576    }
577}