1// Copyright (c) 2023 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7#![deny(missing_docs)]
8
9//! Provides a type for Jabber IDs.
10//!
11//! For usage, check the documentation on the `Jid` struct.
12
13use crate::Error;
14use core::num::NonZeroU16;
15use memchr::memchr;
16use std::borrow::Cow;
17use std::str::FromStr;
18use stringprep::{nameprep, nodeprep, resourceprep};
19
20fn length_check(len: usize, error_empty: Error, error_too_long: Error) -> Result<(), Error> {
21 if len == 0 {
22 Err(error_empty)
23 } else if len > 1023 {
24 Err(error_too_long)
25 } else {
26 Ok(())
27 }
28}
29
30#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
31pub(crate) struct InnerJid {
32 pub(crate) normalized: String,
33 pub(crate) at: Option<NonZeroU16>,
34 pub(crate) slash: Option<NonZeroU16>,
35}
36
37impl InnerJid {
38 pub(crate) fn new(unnormalized: &str) -> Result<InnerJid, Error> {
39 let bytes = unnormalized.as_bytes();
40 let mut orig_at = memchr(b'@', bytes);
41 let mut orig_slash = memchr(b'/', bytes);
42 if orig_at.is_some() && orig_slash.is_some() && orig_at > orig_slash {
43 // This is part of the resource, not a node@domain separator.
44 orig_at = None;
45 }
46
47 let normalized = match (orig_at, orig_slash) {
48 (Some(at), Some(slash)) => {
49 let node = nodeprep(&unnormalized[..at]).map_err(|_| Error::NodePrep)?;
50 length_check(node.len(), Error::NodeEmpty, Error::NodeTooLong)?;
51
52 let domain = nameprep(&unnormalized[at + 1..slash]).map_err(|_| Error::NamePrep)?;
53 length_check(domain.len(), Error::DomainEmpty, Error::DomainTooLong)?;
54
55 let resource =
56 resourceprep(&unnormalized[slash + 1..]).map_err(|_| Error::ResourcePrep)?;
57 length_check(resource.len(), Error::ResourceEmpty, Error::ResourceTooLong)?;
58
59 orig_at = Some(node.len());
60 orig_slash = Some(node.len() + domain.len() + 1);
61 match (node, domain, resource) {
62 (Cow::Borrowed(_), Cow::Borrowed(_), Cow::Borrowed(_)) => {
63 unnormalized.to_string()
64 }
65 (node, domain, resource) => format!("{node}@{domain}/{resource}"),
66 }
67 }
68 (Some(at), None) => {
69 let node = nodeprep(&unnormalized[..at]).map_err(|_| Error::NodePrep)?;
70 length_check(node.len(), Error::NodeEmpty, Error::NodeTooLong)?;
71
72 let domain = nameprep(&unnormalized[at + 1..]).map_err(|_| Error::NamePrep)?;
73 length_check(domain.len(), Error::DomainEmpty, Error::DomainTooLong)?;
74
75 orig_at = Some(node.len());
76 match (node, domain) {
77 (Cow::Borrowed(_), Cow::Borrowed(_)) => unnormalized.to_string(),
78 (node, domain) => format!("{node}@{domain}"),
79 }
80 }
81 (None, Some(slash)) => {
82 let domain = nameprep(&unnormalized[..slash]).map_err(|_| Error::NamePrep)?;
83 length_check(domain.len(), Error::DomainEmpty, Error::DomainTooLong)?;
84
85 let resource =
86 resourceprep(&unnormalized[slash + 1..]).map_err(|_| Error::ResourcePrep)?;
87 length_check(resource.len(), Error::ResourceEmpty, Error::ResourceTooLong)?;
88
89 orig_slash = Some(domain.len());
90 match (domain, resource) {
91 (Cow::Borrowed(_), Cow::Borrowed(_)) => unnormalized.to_string(),
92 (domain, resource) => format!("{domain}/{resource}"),
93 }
94 }
95 (None, None) => {
96 let domain = nameprep(unnormalized).map_err(|_| Error::NamePrep)?;
97 length_check(domain.len(), Error::DomainEmpty, Error::DomainTooLong)?;
98
99 domain.into_owned()
100 }
101 };
102
103 Ok(InnerJid {
104 normalized,
105 at: orig_at.and_then(|x| NonZeroU16::new(x as u16)),
106 slash: orig_slash.and_then(|x| NonZeroU16::new(x as u16)),
107 })
108 }
109
110 pub(crate) fn node(&self) -> Option<&str> {
111 self.at.map(|at| {
112 let at = u16::from(at) as usize;
113 &self.normalized[..at]
114 })
115 }
116
117 pub(crate) fn domain(&self) -> &str {
118 match (self.at, self.slash) {
119 (Some(at), Some(slash)) => {
120 let at = u16::from(at) as usize;
121 let slash = u16::from(slash) as usize;
122 &self.normalized[at + 1..slash]
123 }
124 (Some(at), None) => {
125 let at = u16::from(at) as usize;
126 &self.normalized[at + 1..]
127 }
128 (None, Some(slash)) => {
129 let slash = u16::from(slash) as usize;
130 &self.normalized[..slash]
131 }
132 (None, None) => &self.normalized,
133 }
134 }
135
136 pub(crate) fn resource(&self) -> Option<&str> {
137 self.slash.map(|slash| {
138 let slash = u16::from(slash) as usize;
139 &self.normalized[slash + 1..]
140 })
141 }
142}
143
144impl FromStr for InnerJid {
145 type Err = Error;
146
147 fn from_str(s: &str) -> Result<Self, Self::Err> {
148 InnerJid::new(s)
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 macro_rules! assert_size (
157 ($t:ty, $sz:expr) => (
158 assert_eq!(::std::mem::size_of::<$t>(), $sz);
159 );
160 );
161
162 #[cfg(target_pointer_width = "32")]
163 #[test]
164 fn test_size() {
165 assert_size!(InnerJid, 16);
166 }
167
168 #[cfg(target_pointer_width = "64")]
169 #[test]
170 fn test_size() {
171 assert_size!(InnerJid, 32);
172 }
173}