Skip to main content

jid/
parts.rs

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