///|
/// True when the string contains a CR, LF or bare control newline, which must
/// never appear inside a header field value.
pub fn contains_crlf(s : String) -> Bool {
for b in string_to_bytes(s) {
if b == b'\r' || b == b'\n' {
return true
}
}
false
}
///|
/// Reject any CR, LF or NUL byte in `value`. This is the core header-injection
/// guard (MM_INJ_001 / MM_INJ_002): user-controlled input must not introduce
/// new header fields or terminate the header block.
pub fn reject_crlf(value : String, context : String) -> Unit raise MailFailure {
for b in string_to_bytes(value) {
if b == b'\r' || b == b'\n' {
raise MailFailure::of(HeaderInjection, MM_INJ_001, "CR/LF in \{context}")
}
if b == b'\x00' {
raise MailFailure::of(
HeaderInjection,
MM_INJ_002,
"NUL byte in \{context}",
)
}
}
}
///|
/// Sanitize a filename for use inside `Content-Disposition`: strips path
/// separators, CR/LF, NUL and other control characters (MM_INJ_003).
pub fn sanitize_filename(name : String) -> String {
let buf = Buffer::Buffer(size_hint=name.length())
for ch in name {
let c = ch.to_int()
if c == 0 || c == 0x0A || c == 0x0D || c == 0x2F || c == 0x5C {
continue
}
if c < 0x20 || c == 0x7F {
buf.write_char_utf16le('_')
} else {
buf.write_char_utf16le(ch)
}
}
let s = buf.to_string().trim().to_owned()
if s.is_empty() {
"attachment"
} else {
s
}
}