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 impl Borrow<String> for $name {
158 fn borrow(&self) -> &String {
159 &self.0
160 }
161 }
162
163 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 unsafe { mem::transmute(s) }
200 }
201
202 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 #[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 #[cfg_attr(feature = "serde", derive(Serialize))]
252 pub struct ref NodeRef(str);
253}
254
255def_part_types! {
256 #[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 #[cfg_attr(feature = "serde", derive(Serialize))]
267 pub struct ref DomainRef(str);
268}
269
270def_part_types! {
271 #[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 #[cfg_attr(feature = "serde", derive(Serialize))]
282 pub struct ref ResourceRef(str);
283}
284
285impl DomainRef {
286 pub fn with_node(&self, node: &NodeRef) -> BareJid {
289 BareJid::from_parts(Some(node), self)
290 }
291
292 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 pub fn with_domain(&self, domain: &DomainRef) -> BareJid {
329 BareJid::from_parts(Some(self), domain)
330 }
331
332 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 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 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 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}