lib.rs

  1//! Crate wrapping what we need from ICU’s C API for JIDs.
  2//!
  3//! See <http://site.icu-project.org/>
  4
  5#![deny(missing_docs)]
  6
  7mod bindings;
  8mod error;
  9mod idna2008;
 10mod spoof;
 11mod stringprep;
 12
 13use crate::bindings::{
 14    icu_trace_set_level, UIDNA_DEFAULT, UIDNA_USE_STD3_RULES, USPOOF_CONFUSABLE,
 15    USPREP_RFC3491_NAMEPREP, USPREP_RFC3920_NODEPREP, USPREP_RFC3920_RESOURCEPREP,
 16    USPREP_RFC4013_SASLPREP, UTRACE_VERBOSE,
 17};
 18pub use crate::error::Error;
 19pub use crate::idna2008::Idna;
 20pub use crate::spoof::SpoofChecker;
 21use crate::stringprep::Stringprep;
 22
 23/// How unassigned codepoints should be handled.
 24pub enum Strict {
 25    /// All codepoints should be assigned, otherwise an error will be emitted.
 26    True,
 27
 28    /// Codepoints can be unassigned.
 29    AllowUnassigned,
 30}
 31
 32/// Main struct of this module, exposing the needed ICU functions to JID.
 33pub struct Icu {
 34    nameprep: Stringprep,
 35    nodeprep: Stringprep,
 36    resourceprep: Stringprep,
 37    saslprep: Stringprep,
 38
 39    /// IDNA2008 support.
 40    ///
 41    /// See [RFC5891](https://tools.ietf.org/html/rfc5891).
 42    pub idna2008: Idna,
 43
 44    /// Spoof checker TODO: better doc.
 45    pub spoofchecker: SpoofChecker,
 46}
 47
 48impl Icu {
 49    /// Create a new ICU struct, initialising stringprep profiles, IDNA2008, as well as a spoof
 50    /// checker.
 51    pub fn new() -> Result<Icu, Error> {
 52        unsafe { icu_trace_set_level(UTRACE_VERBOSE) };
 53
 54        let nameprep = Stringprep::new(USPREP_RFC3491_NAMEPREP)?;
 55        let nodeprep = Stringprep::new(USPREP_RFC3920_NODEPREP)?;
 56        let resourceprep = Stringprep::new(USPREP_RFC3920_RESOURCEPREP)?;
 57        let saslprep = Stringprep::new(USPREP_RFC4013_SASLPREP)?;
 58
 59        let mut options = UIDNA_DEFAULT;
 60        options |= UIDNA_USE_STD3_RULES;
 61        let idna2008 = Idna::new(options)?;
 62
 63        let spoofchecker = SpoofChecker::new(USPOOF_CONFUSABLE)?;
 64
 65        Ok(Icu {
 66            nameprep,
 67            nodeprep,
 68            resourceprep,
 69            saslprep,
 70            idna2008,
 71            spoofchecker,
 72        })
 73    }
 74
 75    /// Perform stringprep using the Nameprep profile.
 76    ///
 77    /// See [RFC3491](https://tools.ietf.org/html/rfc3491).
 78    pub fn nameprep(&self, string: &str, strict: Strict) -> Result<String, Error> {
 79        self.nameprep.stringprep(string, strict)
 80    }
 81
 82    /// Perform stringprep using the Nodeprep profile.
 83    ///
 84    /// See [RFC6122 appendix A](https://tools.ietf.org/html/rfc6122#appendix-A).
 85    pub fn nodeprep(&self, string: &str, strict: Strict) -> Result<String, Error> {
 86        self.nodeprep.stringprep(string, strict)
 87    }
 88
 89    /// Perform stringprep using the Resourceprep profile.
 90    ///
 91    /// See [RFC6122 appendix A](https://tools.ietf.org/html/rfc6122#appendix-A).
 92    pub fn resourceprep(&self, string: &str, strict: Strict) -> Result<String, Error> {
 93        self.resourceprep.stringprep(string, strict)
 94    }
 95
 96    /// Perform stringprep using the Saslprep profile.
 97    ///
 98    /// See [RFC4013](https://tools.ietf.org/html/rfc4013).
 99    pub fn saslprep(&self, string: &str, strict: Strict) -> Result<String, Error> {
100        self.saslprep.stringprep(string, strict)
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn nameprep() {
110        let name = "Link";
111        let icu = Icu::new().unwrap();
112        let name = icu.nodeprep.stringprep(name, Strict::True).unwrap();
113        assert_eq!(name, "link");
114    }
115
116    #[test]
117    fn resourceprep() {
118        let name = "Testā„¢";
119        let icu = Icu::new().unwrap();
120        let name = icu
121            .resourceprep
122            .stringprep(name, Strict::AllowUnassigned)
123            .unwrap();
124        assert_eq!(name, "TestTM");
125    }
126
127    #[test]
128    fn idna() {
129        let name = "☃.coM";
130        let icu = Icu::new().unwrap();
131        let name = icu.idna2008.to_ascii(name).unwrap();
132        assert_eq!(name, "xn--n3h.com");
133
134        let name = "xn--N3H.com";
135        let icu = Icu::new().unwrap();
136        let name = icu.idna2008.to_unicode(name).unwrap();
137        assert_eq!(name, "☃.com");
138    }
139
140    #[test]
141    fn spoof() {
142        // Non-breakable and narrow non-breakable spaces spoofing.
143        let name = "fooĀ bar baz";
144        let icu = Icu::new().unwrap();
145        let name = icu.spoofchecker.get_skeleton(name).unwrap();
146        assert_eq!(name, "foo bar baz");
147
148        // Cyrillic spoofing.
149        let name = "ŠŠµllо wоrld";
150        let icu = Icu::new().unwrap();
151        let name = icu.spoofchecker.get_skeleton(name).unwrap();
152        assert_eq!(name, "Hello world");
153    }
154}