dd307557e6 2026-07-31 1: use smtp2tg::utils::{
dd307557e6 2026-07-31 2: validate,
dd307557e6 2026-07-31 3: RE_CLOSING,
dd307557e6 2026-07-31 4: RE_DOMAIN,
dd307557e6 2026-07-31 5: };
6e7d1e877b 2026-07-31 6:
6e7d1e877b 2026-07-31 7: use std::{
6e7d1e877b 2026-07-31 8: borrow::Cow,
6e7d1e877b 2026-07-31 9: mem::discriminant,
6e7d1e877b 2026-07-31 10: };
6e7d1e877b 2026-07-31 11:
6e7d1e877b 2026-07-31 12: use stacked_errors::Result;
6e7d1e877b 2026-07-31 13:
6e7d1e877b 2026-07-31 14: #[test]
6e7d1e877b 2026-07-31 15: fn test_validate_escaping_behavior () -> Result<()> {
6e7d1e877b 2026-07-31 16: let cases: &[(&str, Cow<str>)] = &[
6e7d1e877b 2026-07-31 17: // `validate` escapes HTML special characters.
6e7d1e877b 2026-07-31 18: ("<p>Some <b>valid</b> HTML</p>", Cow::Owned("<p>Some <b>valid</b> HTML</p>".into())),
6e7d1e877b 2026-07-31 19: // Empty input is returned unchanged.
6e7d1e877b 2026-07-31 20: ("", Cow::Borrowed("")),
6e7d1e877b 2026-07-31 21: // Whitespace-only input needs no escaping.
6e7d1e877b 2026-07-31 22: (" \t\n", Cow::Borrowed(" \t\n")),
6e7d1e877b 2026-07-31 23: // `validate` returns `Cow<'a, str>` borrowed from its input lifetime `'a`.
6e7d1e877b 2026-07-31 24: // These two cases exercise both branches of that `Cow` to make sure the
6e7d1e877b 2026-07-31 25: // explicit lifetime introduced on `validate` still lets callers observe a
6e7d1e877b 2026-07-31 26: // zero-copy borrow when no escaping is required.
6e7d1e877b 2026-07-31 27: ("plain text without special html characters", Cow::Borrowed("plain text without special html characters")),
6e7d1e877b 2026-07-31 28: ("5 > 3 & 2 < 4", Cow::Owned("5 > 3 & 2 < 4".into())),
6e7d1e877b 2026-07-31 29: ];
6e7d1e877b 2026-07-31 30: for (input, expected) in cases {
6e7d1e877b 2026-07-31 31: let result = validate(input)?;
6e7d1e877b 2026-07-31 32: assert_eq!(&result, expected, "unexpected output for input {input:?}");
6e7d1e877b 2026-07-31 33: assert_eq!(discriminant(&result), discriminant(expected), "wrong Cow variant for input {input:?}");
6e7d1e877b 2026-07-31 34: }
6e7d1e877b 2026-07-31 35: Ok(())
6e7d1e877b 2026-07-31 36: }
6e7d1e877b 2026-07-31 37:
6e7d1e877b 2026-07-31 38: #[test]
6e7d1e877b 2026-07-31 39: fn test_validate_closing_tag_behavior () {
6e7d1e877b 2026-07-31 40: let cases = [
6e7d1e877b 2026-07-31 41: ("</ pre >", true),
6e7d1e877b 2026-07-31 42: ("</\tcode\t>", true),
6e7d1e877b 2026-07-31 43: ("</b>", false),
6e7d1e877b 2026-07-31 44: ("</Code>", true),
6e7d1e877b 2026-07-31 45: ("</code>", true),
6e7d1e877b 2026-07-31 46: ("</code>\t", true),
6e7d1e877b 2026-07-31 47: ("</code>\t>", true),
6e7d1e877b 2026-07-31 48: ("</div>", false), // Not a pre/code tag
6e7d1e877b 2026-07-31 49: ("</PRE>", true),
6e7d1e877b 2026-07-31 50: ("</pre>", true),
6e7d1e877b 2026-07-31 51: ("</pre>\n", true),
6e7d1e877b 2026-07-31 52: ("<p>Some <b>valid</b> HTML</p></code><a href='http://somewere.com'>Link injection!</a>", true),
6e7d1e877b 2026-07-31 53: ("<pre>", false),
6e7d1e877b 2026-07-31 54: ];
6e7d1e877b 2026-07-31 55: for (input, expected) in cases {
6e7d1e877b 2026-07-31 56: assert_eq!(RE_CLOSING.is_match(input), expected, "unexpected match result for {input:?}");
6e7d1e877b 2026-07-31 57: }
6e7d1e877b 2026-07-31 58: }
6e7d1e877b 2026-07-31 59:
6e7d1e877b 2026-07-31 60: #[test]
6e7d1e877b 2026-07-31 61: fn test_regex_domain_behavior() {
6e7d1e877b 2026-07-31 62: let cases = [
6e7d1e877b 2026-07-31 63: ("", false),
6e7d1e877b 2026-07-31 64: ("-example.com", false),
6e7d1e877b 2026-07-31 65: (".example.com", false),
6e7d1e877b 2026-07-31 66: ("123.456", true),
6e7d1e877b 2026-07-31 67: ("EXAMPLE.COM", false),
6e7d1e877b 2026-07-31 68: ("a", true),
6e7d1e877b 2026-07-31 69: ("a.b", true),
6e7d1e877b 2026-07-31 70: ("example-.com", false),
6e7d1e877b 2026-07-31 71: ("example..com", false),
6e7d1e877b 2026-07-31 72: ("example.com", true),
6e7d1e877b 2026-07-31 73: ("example.com.", false),
6e7d1e877b 2026-07-31 74: ("invalid@domain.com", false),
6e7d1e877b 2026-07-31 75: ("my-host.example.com", true),
6e7d1e877b 2026-07-31 76: ("sub.example.co.uk", true),
6e7d1e877b 2026-07-31 77: ];
6e7d1e877b 2026-07-31 78: for (input, expected) in cases {
6e7d1e877b 2026-07-31 79: assert_eq!(RE_DOMAIN.is_match(input), expected, "unexpected match result for {input:?}");
6e7d1e877b 2026-07-31 80: }
dd307557e6 2026-07-31 81: }