inner.rs

  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}