Skip to content

Commit 061df0b

Browse files
author
Eric Sessoms
committed
Add saslprep integration tests
Added some comments and did some minor reformatting to make it easier to compare the saslprep implementation point-by-point with the RFC. Reviewed the implementation against the RFC and found no errors or omissions. Reviewed `tables.rs` against the stringprep RFC and found no error or omissions.
1 parent e3352d3 commit 061df0b

File tree

2 files changed

+131
-11
lines changed

2 files changed

+131
-11
lines changed

src/lib.rs

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,18 @@ use unicode_normalization::UnicodeNormalization;
1414

1515
pub mod tables;
1616

17+
/// Describes why a string failed stringprep normalization.
1718
#[derive(Debug)]
18-
enum ErrorCause {
19+
pub enum ErrorCause {
20+
/// Contains stringprep prohibited characters.
1921
ProhibitedCharacter(char),
22+
/// Violates stringprep rules for bidirectional text.
2023
ProhibitedBidirectionalText,
2124
}
2225

2326
/// An error performing the stringprep algorithm.
2427
#[derive(Debug)]
25-
pub struct Error(ErrorCause);
28+
pub struct Error(pub ErrorCause);
2629

2730
impl fmt::Display for Error {
2831
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
@@ -51,6 +54,7 @@ pub fn saslprep<'a>(s: &'a str) -> Result<Cow<'a, str>, Error> {
5154
return Ok(Cow::Borrowed(s));
5255
}
5356

57+
// 2.1 Mapping
5458
let mapped = s.chars()
5559
.map(|c| if tables::non_ascii_space_character(c) {
5660
' '
@@ -59,50 +63,79 @@ pub fn saslprep<'a>(s: &'a str) -> Result<Cow<'a, str>, Error> {
5963
})
6064
.filter(|&c| !tables::commonly_mapped_to_nothing(c));
6165

66+
// 2.2 Normalization
6267
let normalized = mapped.nfkc().collect::<String>();
6368

69+
// 2.3 Prohibited Output
6470
let prohibited = normalized
6571
.chars()
6672
.filter(|&c| {
67-
tables::non_ascii_space_character(c) || tables::ascii_control_character(c) ||
68-
tables::non_ascii_control_character(c) || tables::private_use(c) ||
69-
tables::non_character_code_point(c) ||
70-
tables::surrogate_code(c) || tables::inappropriate_for_plain_text(c) ||
71-
tables::inappropriate_for_canonical_representation(c) ||
72-
tables::change_display_properties_or_deprecated(c) ||
73-
tables::tagging_character(c)
73+
tables::non_ascii_space_character(c) /* C.1.2 */ ||
74+
tables::ascii_control_character(c) /* C.2.1 */ ||
75+
tables::non_ascii_control_character(c) /* C.2.2 */ ||
76+
tables::private_use(c) /* C.3 */ ||
77+
tables::non_character_code_point(c) /* C.4 */ ||
78+
tables::surrogate_code(c) /* C.5 */ ||
79+
tables::inappropriate_for_plain_text(c) /* C.6 */ ||
80+
tables::inappropriate_for_canonical_representation(c) /* C.7 */ ||
81+
tables::change_display_properties_or_deprecated(c) /* C.8 */ ||
82+
tables::tagging_character(c) /* C.9 */
7483
})
7584
.next();
7685
if let Some(c) = prohibited {
7786
return Err(Error(ErrorCause::ProhibitedCharacter(c)));
7887
}
7988

89+
// RFC3454, 6. Bidirectional Characters
8090
if normalized.contains(tables::bidi_r_or_al) {
91+
// 2) If a string contains any RandALCat character, the string
92+
// MUST NOT contain any LCat character.
8193
if normalized.contains(tables::bidi_l) {
8294
return Err(Error(ErrorCause::ProhibitedBidirectionalText));
8395
}
8496

97+
// 3) If a string contains any RandALCat character, a RandALCat
98+
// character MUST be the first character of the string, and a
99+
// RandALCat character MUST be the last character of the string.
85100
if !tables::bidi_r_or_al(normalized.chars().next().unwrap()) ||
86101
!tables::bidi_r_or_al(normalized.chars().next_back().unwrap()) {
87102
return Err(Error(ErrorCause::ProhibitedBidirectionalText));
88103
}
89104
}
90105

106+
// 2.5 Unassigned Code Points
107+
// TODO: Reject unassigned code points.
108+
91109
Ok(Cow::Owned(normalized))
92110
}
93111

94112
#[cfg(test)]
95113
mod test {
96114
use super::*;
97115

116+
// RFC4013, 3. Examples
98117
#[test]
99118
fn saslprep_examples() {
100119
assert_eq!(saslprep("I\u{00AD}X").unwrap(), "IX");
101120
assert_eq!(saslprep("user").unwrap(), "user");
102121
assert_eq!(saslprep("USER").unwrap(), "USER");
103122
assert_eq!(saslprep("\u{00AA}").unwrap(), "a");
104123
assert_eq!(saslprep("\u{2168}").unwrap(), "IX");
105-
assert!(saslprep("\u{0007}").is_err());
106-
assert!(saslprep("\u{0627}\u{0031}").is_err());
124+
assert_prohibited_character(saslprep("\u{0007}"));
125+
assert_prohibited_bidirectional_text(saslprep("\u{0627}\u{0031}"));
126+
}
127+
128+
fn assert_prohibited_character<T>(result: Result<T, Error>) {
129+
match result {
130+
Err(Error(ErrorCause::ProhibitedCharacter(_))) => (),
131+
_ => assert!(false)
132+
}
133+
}
134+
135+
fn assert_prohibited_bidirectional_text<T>(result: Result<T, Error>) {
136+
match result {
137+
Err(Error(ErrorCause::ProhibitedBidirectionalText)) => (),
138+
_ => assert!(false)
139+
}
107140
}
108141
}

tests/saslprep_tests.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Integration tests from https://github.com/reklatsmasters/saslprep (MIT License)
2+
extern crate stringprep;
3+
4+
use stringprep::{Error, ErrorCause, saslprep};
5+
6+
fn assert_prohibited_character<T>(result: Result<T, Error>) {
7+
match result {
8+
Err(Error(ErrorCause::ProhibitedCharacter(_))) => (),
9+
_ => assert!(false)
10+
}
11+
}
12+
13+
fn assert_prohibited_bidirectional_text<T>(result: Result<T, Error>) {
14+
match result {
15+
Err(Error(ErrorCause::ProhibitedBidirectionalText)) => (),
16+
_ => assert!(false)
17+
}
18+
}
19+
20+
#[test]
21+
fn should_work_with_latin_letters() {
22+
assert_eq!(saslprep("user").unwrap(), "user");
23+
}
24+
25+
#[test]
26+
fn should_preserve_case() {
27+
assert_eq!(saslprep("USER").unwrap(), "USER");
28+
}
29+
30+
#[test]
31+
fn should_remove_mapped_to_nothing() {
32+
assert_eq!(saslprep("I\u{00AD}X").unwrap(), "IX");
33+
}
34+
35+
#[test]
36+
fn should_replace_non_ascii_space() {
37+
assert_eq!(saslprep("a\u{00A0}b").unwrap(), "a\u{0020}b");
38+
}
39+
40+
#[test]
41+
fn should_normalize_as_nfkc() {
42+
assert_eq!(saslprep("\u{00AA}").unwrap(), "a");
43+
assert_eq!(saslprep("\u{2168}").unwrap(), "IX");
44+
}
45+
46+
#[test]
47+
fn should_not_allow_prohibited_characters() {
48+
// C.2.1 ASCII control characters
49+
assert_prohibited_character(saslprep("a\u{007F}b"));
50+
51+
// C.2.2 Non-ASCII control characters
52+
assert_prohibited_character(saslprep("a\u{06DD}b"));
53+
54+
// C.3 Private use
55+
assert_prohibited_character(saslprep("a\u{E000}b"));
56+
57+
// C.4 Non-character code points
58+
assert_prohibited_character(saslprep("a\u{1FFFE}b"));
59+
60+
// C.5 Surrogate codes
61+
// forbidden by rust
62+
63+
// C.6 Inappropriate for plain text
64+
assert_prohibited_character(saslprep("a\u{FFF9}b"));
65+
66+
// C.7 Inappropriate for canonical representation
67+
assert_prohibited_character(saslprep("a\u{2FF0}b"));
68+
69+
// C.8 Change display properties or are deprecated
70+
assert_prohibited_character(saslprep("a\u{200E}b"));
71+
72+
// C.9 Tagging characters
73+
assert_prohibited_character(saslprep("a\u{E0001}b"));
74+
}
75+
76+
#[test]
77+
fn randalcat_should_be_first_and_last() {
78+
assert_eq!(saslprep("\u{0627}\u{0031}\u{0628}").unwrap(), "\u{0627}\u{0031}\u{0628}");
79+
assert_prohibited_bidirectional_text(saslprep("\u{0627}\u{0031}"));
80+
}
81+
82+
#[ignore]
83+
#[should_panic]
84+
#[test]
85+
fn should_handle_unassigned_code_points() {
86+
saslprep("a\u{0487}").unwrap();
87+
}

0 commit comments

Comments
 (0)