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
|
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
|
+
+
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
-
-
+
+
-
-
-
+
+
-
-
-
-
-
-
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
|
use smtp2tg::utils::{
validate,
RE_CLOSING,
RE_DOMAIN,
};
use std::{
borrow::Cow,
mem::discriminant,
};
use stacked_errors::Result;
#[test]
fn test_validate_valid_html() -> Result<()> {
let html = "<p>Some <b>valid</b> HTML</p>";
let escaped = validate(html)?;
assert_eq!(escaped, "<p>Some <b>valid</b> HTML</p>");
fn test_validate_escaping_behavior () -> Result<()> {
let cases: &[(&str, Cow<str>)] = &[
// `validate` escapes HTML special characters.
("<p>Some <b>valid</b> HTML</p>", Cow::Owned("<p>Some <b>valid</b> HTML</p>".into())),
// Empty input is returned unchanged.
("", Cow::Borrowed("")),
// Whitespace-only input needs no escaping.
(" \t\n", Cow::Borrowed(" \t\n")),
// `validate` returns `Cow<'a, str>` borrowed from its input lifetime `'a`.
// These two cases exercise both branches of that `Cow` to make sure the
// explicit lifetime introduced on `validate` still lets callers observe a
// zero-copy borrow when no escaping is required.
("plain text without special html characters", Cow::Borrowed("plain text without special html characters")),
("5 > 3 & 2 < 4", Cow::Owned("5 > 3 & 2 < 4".into())),
];
for (input, expected) in cases {
let result = validate(input)?;
assert_eq!(&result, expected, "unexpected output for input {input:?}");
assert_eq!(discriminant(&result), discriminant(expected), "wrong Cow variant for input {input:?}");
}
Ok(())
}
#[test]
fn test_validate_closing_tag() -> Result<()> {
fn test_validate_closing_tag_behavior () {
let html = "<p>Some <b>valid</b> HTML</p></code><a href='http://somewere.com'>Link injection!</a>";
assert!(validate(html).is_err());
assert!(validate("</pre>").is_err());
assert!(validate("</code>").is_err());
let cases = [
("</ pre >", true),
("</\tcode\t>", true),
assert!(validate("</pre>\n").is_err());
assert!(validate("</code>\t").is_err());
Ok(())
}
#[test]
fn test_validate_empty_string() -> Result<()> {
assert_eq!(validate("").unwrap(), "");
Ok(())
("</b>", false),
("</Code>", true),
("</code>", true),
("</code>\t", true),
("</code>\t>", true),
}
#[test]
("</div>", false), // Not a pre/code tag
("</PRE>", true),
("</pre>", true),
("</pre>\n", true),
("<p>Some <b>valid</b> HTML</p></code><a href='http://somewere.com'>Link injection!</a>", true),
("<pre>", false),
];
fn test_validate_whitespace() -> Result<()> {
assert_eq!(validate(" \t\n").unwrap(), " \t\n"); // no escaping for whitespace
for (input, expected) in cases {
assert_eq!(RE_CLOSING.is_match(input), expected, "unexpected match result for {input:?}");
Ok(())
}
}
}
#[test]
fn test_regex_closing_tag_matches() {
assert!(RE_CLOSING.is_match("</pre>"));
assert!(RE_CLOSING.is_match("</code>\t>"));
assert!(!RE_CLOSING.is_match("</div>")); // Not a pre/code tag
}
#[test]
fn test_regex_domain_matches() {
assert!(RE_DOMAIN.is_match("example.com"));
assert!(RE_DOMAIN.is_match("sub.example.co.uk"));
assert!(!RE_DOMAIN.is_match("invalid@domain.com"));
fn test_regex_domain_behavior() {
let cases = [
("", false),
("-example.com", false),
(".example.com", false),
("123.456", true),
("EXAMPLE.COM", false),
("a", true),
("a.b", true),
("example-.com", false),
("example..com", false),
("example.com", true),
("example.com.", false),
("invalid@domain.com", false),
("my-host.example.com", true),
("sub.example.co.uk", true),
];
for (input, expected) in cases {
assert_eq!(RE_DOMAIN.is_match(input), expected, "unexpected match result for {input:?}");
}
}
|