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}