1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#![doc(html_root_url="https://docs.rs/stringprep/0.1.0")]
#![warn(missing_docs)]
extern crate unicode_bidi;
extern crate unicode_normalization;
use std::ascii::AsciiExt;
use std::borrow::Cow;
use std::error;
use std::fmt;
use unicode_normalization::UnicodeNormalization;
pub mod tables;
#[derive(Debug)]
enum ErrorCause {
ProhibitedCharacter(char),
ProhibitedBidirectionalText,
}
#[derive(Debug)]
pub struct Error(ErrorCause);
impl fmt::Display for Error {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self.0 {
ErrorCause::ProhibitedCharacter(c) => write!(fmt, "prohibited character `{}`", c),
ErrorCause::ProhibitedBidirectionalText => write!(fmt, "prohibited bidirectional text"),
}
}
}
impl error::Error for Error {
fn description(&self) -> &str {
"error performing stringprep algorithm"
}
}
pub fn saslprep<'a>(s: &'a str) -> Result<Cow<'a, str>, Error> {
if s.chars()
.all(|c| c.is_ascii() && !tables::ascii_control_character(c)) {
return Ok(Cow::Borrowed(s));
}
let mapped = s.chars()
.map(|c| if tables::non_ascii_space_character(c) {
' '
} else {
c
})
.filter(|&c| !tables::commonly_mapped_to_nothing(c));
let normalized = mapped.nfkc().collect::<String>();
let prohibited = normalized
.chars()
.filter(|&c| {
tables::non_ascii_space_character(c) || tables::ascii_control_character(c) ||
tables::non_ascii_control_character(c) || tables::private_use(c) ||
tables::non_character_code_point(c) ||
tables::surrogate_code(c) || tables::inappropriate_for_plain_text(c) ||
tables::inappropriate_for_canonical_representation(c) ||
tables::change_display_properties_or_deprecated(c) ||
tables::tagging_character(c)
})
.next();
if let Some(c) = prohibited {
return Err(Error(ErrorCause::ProhibitedCharacter(c)));
}
if normalized.contains(tables::bidi_r_or_al) {
if normalized.contains(tables::bidi_l) {
return Err(Error(ErrorCause::ProhibitedBidirectionalText));
}
if !tables::bidi_r_or_al(normalized.chars().next().unwrap()) ||
!tables::bidi_r_or_al(normalized.chars().next_back().unwrap()) {
return Err(Error(ErrorCause::ProhibitedBidirectionalText));
}
}
Ok(Cow::Owned(normalized))
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn saslprep_examples() {
assert_eq!(saslprep("I\u{00AD}X").unwrap(), "IX");
assert_eq!(saslprep("user").unwrap(), "user");
assert_eq!(saslprep("USER").unwrap(), "USER");
assert_eq!(saslprep("\u{00AA}").unwrap(), "a");
assert_eq!(saslprep("\u{2168}").unwrap(), "IX");
assert!(saslprep("\u{0007}").is_err());
assert!(saslprep("\u{0627}\u{0031}").is_err());
}
}