#![allow(clippy::unwrap_used, clippy::expect_used)]
use super::*;
#[test]
fn split_header_body_crlf() {
let input = b"From: a@b.com\r\nSubject: test\r\n\r\nBody";
let (h, b) = split_header_body(input);
assert_eq!(h, b"From: a@b.com\r\nSubject: test");
assert_eq!(b, b"Body");
}
#[test]
fn split_header_body_lf() {
let input = b"From: a@b.com\nSubject: test\n\nBody";
let (h, b) = split_header_body(input);
assert_eq!(h, b"From: a@b.com\nSubject: test");
assert_eq!(b, b"Body");
}
#[test]
fn split_header_body_cr() {
let input = b"From: a@b.com\rSubject: test\r\rBody";
let (h, b) = split_header_body(input);
assert_eq!(h, b"From: a@b.com\rSubject: test");
assert_eq!(b, b"Body");
}
#[test]
fn split_header_body_empty_headers() {
let input = b"\r\nBody text";
let (h, b) = split_header_body(input);
assert!(h.is_empty());
assert_eq!(b, b"Body text");
}
#[test]
fn split_header_body_no_separator() {
let input = b"From: a@b.com\r\nSubject: test";
let (h, b) = split_header_body(input);
assert_eq!(h, input.as_slice());
assert!(b.is_empty());
}
#[test]
fn parse_headers_basic() {
let raw = b"From: a@b.com\r\nSubject: Test\r\n";
let headers = parse_headers(raw);
assert_eq!(headers.len(), 2);
assert_eq!(headers[0], ("from".to_string(), "a@b.com".to_string()));
assert_eq!(headers[1], ("subject".to_string(), "Test".to_string()));
}
#[test]
fn parse_headers_bare_cr_line_endings() {
let raw = b"From: a@b.com\rSubject: Test\r";
let headers = parse_headers(raw);
assert_eq!(headers.len(), 2);
assert_eq!(headers[0], ("from".to_string(), "a@b.com".to_string()));
assert_eq!(headers[1], ("subject".to_string(), "Test".to_string()));
}
#[test]
fn parse_headers_continuation_line() {
let raw = b"Subject: This is a very long\r\n subject line that wraps\r\n";
let headers = parse_headers(raw);
assert_eq!(headers.len(), 1);
assert_eq!(headers[0].1, "This is a very long subject line that wraps");
}
#[test]
fn parse_headers_first_continuation_line_preserves_leading_wsp() {
let raw = b"Subject:\r\n\tWorld\r\n";
let headers = parse_headers(raw);
assert_eq!(headers.len(), 1);
assert_eq!(
headers[0].1, "\tWorld",
"RFC 5322 Section 2.2.3: unfolding must preserve the continuation line's leading WSP"
);
}
#[test]
fn parse_headers_preserves_initial_wsp_before_first_continuation_content() {
let raw = b"Subject: \r\n\tWorld\r\n";
let headers = parse_headers(raw);
assert_eq!(headers.len(), 1);
assert_eq!(
headers[0].1, " \tWorld",
"RFC 5322 Section 2.2.3: unfolding must preserve both the first line's WSP and the continuation line's WSP"
);
}
#[test]
fn parse_headers_invalid_name_skipped() {
let raw = b"Valid: yes\r\n: no-name\r\nAlso-Valid: yes\r\n";
let headers = parse_headers(raw);
assert_eq!(headers.len(), 2);
assert_eq!(headers[0].0, "valid");
assert_eq!(headers[1].0, "also-valid");
}
#[test]
fn is_valid_header_name_rejects_controls() {
assert!(!is_valid_header_name(""));
assert!(!is_valid_header_name("foo:bar"));
assert!(!is_valid_header_name("foo bar"));
assert!(!is_valid_header_name("\x00bad"));
}
#[test]
fn is_valid_header_name_accepts_ascii() {
assert!(is_valid_header_name("From"));
assert!(is_valid_header_name("X-Custom-Header"));
}
#[test]
fn looks_like_headerless_body_text() {
assert!(looks_like_headerless_body(b"Hello from the body"));
assert!(!looks_like_headerless_body(b"\x00binary"));
assert!(!looks_like_headerless_body(b""));
}
#[test]
fn split_mime_parts_basic() {
let body = b"--boundary\r\nContent-Type: text/plain\r\n\r\nHello\r\n--boundary--";
let parts = split_mime_parts(body, "boundary");
assert_eq!(parts.len(), 1);
assert_eq!(parts[0], b"Content-Type: text/plain\r\n\r\nHello");
}
#[test]
fn split_mime_parts_truncated() {
let body = b"--boundary\r\nContent-Type: text/plain\r\n\r\nHello";
let parts = split_mime_parts(body, "boundary");
assert_eq!(parts.len(), 1);
assert_eq!(parts[0], b"Content-Type: text/plain\r\n\r\nHello");
}
#[test]
fn find_subsequence_found() {
assert_eq!(find_subsequence(b"hello world", b"world"), Some(6));
assert_eq!(find_subsequence(b"hello world", b"hello"), Some(0));
}
#[test]
fn find_subsequence_not_found() {
assert_eq!(find_subsequence(b"hello", b"world"), None);
}
#[test]
fn parse_wire_empty_input() {
let result = parse_wire(b"");
assert!(matches!(result, Err(Error::EmptyInput)));
}
#[test]
fn parse_wire_basic_message() {
let raw = b"From: a@b.com\r\nSubject: Test\r\n\r\nBody";
let wire = parse_wire(raw).unwrap();
assert_eq!(wire.headers.len(), 2);
assert_eq!(wire.body, b"Body");
assert_eq!(wire.size, raw.len() as u64);
assert!(!wire.headerless);
assert!(wire.raw_headers.contains("From: a@b.com"));
}
#[test]
fn parse_wire_bare_cr_line_endings() {
let raw = b"From: a@b.com\rSubject: Test\r\rBody";
let wire = parse_wire(raw).unwrap();
assert_eq!(wire.headers.len(), 2);
assert_eq!(wire.body, b"Body");
assert!(!wire.headerless);
}
#[test]
fn parse_wire_headerless_body() {
let raw = b"Hello from body only";
let wire = parse_wire(raw).unwrap();
assert!(wire.headers.is_empty());
assert_eq!(wire.body, raw);
assert!(wire.headerless);
assert!(wire.raw_headers.is_empty());
}
#[test]
fn parse_headers_non_ascii_name_skipped() {
let input = "X-\u{0130}: value\r\n\r\n";
let headers = parse_headers(input.as_bytes());
assert!(headers.is_empty(), "non-ASCII header names must be skipped");
}
#[test]
fn regression_msg009_leading_bare_cr_does_not_eat_headers() {
let input = b"\rFrom: user@example.com\r\nSubject: Test\r\n\r\nBody";
let (headers, body) = split_header_body(input);
assert!(
!headers.is_empty(),
"Leading bare CR followed by header field name must not produce empty headers"
);
let parsed_headers = parse_headers(headers);
assert!(
parsed_headers.iter().any(|(k, _)| k == "from"),
"From header must be parsed; got headers: {parsed_headers:?}",
);
assert_eq!(body, b"Body");
}