fn is_text_safe_byte(b: u8) -> bool {
matches!(
b,
0x20..=0x7E | b'\t' | b'\n' | b'\r' | 0x0B | 0x0C | 0x07 | 0x08 | 0x1B
)
}
#[must_use]
pub fn classify_fallback(buffer: &[u8]) -> &'static str {
if buffer.is_empty() {
return "empty";
}
if buffer.iter().all(|&b| is_text_safe_byte(b)) {
return "ASCII text";
}
let has_non_ascii_byte = buffer.iter().any(|&b| b >= 0x80);
let no_binary_control_byte = buffer.iter().all(|&b| b >= 0x80 || is_text_safe_byte(b));
if has_non_ascii_byte && no_binary_control_byte && std::str::from_utf8(buffer).is_ok() {
return "UTF-8 Unicode text";
}
"data"
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn classifies_empty_buffer_as_empty() {
assert_eq!(classify_fallback(b""), "empty");
}
#[test]
fn classifies_plain_ascii_as_ascii_text() {
let cases: &[(&str, &[u8])] = &[
("simple sentence", b"hello world this is plain text\n"),
("tabs and newlines", b"a\tb\nc\r\nd\n"),
("vertical tab and form feed", b"a\x0bb\x0cc\n"),
("bell, backspace, escape", b"a\x07b\x08c\x1bd\n"),
];
for (label, input) in cases {
assert_eq!(
classify_fallback(input),
"ASCII text",
"case {label:?} should classify as ASCII text"
);
}
}
#[test]
fn classifies_valid_non_ascii_utf8_as_utf8_unicode_text() {
let cases: &[(&str, &[u8])] = &[
("accented latin", "caf\u{e9} r\u{e9}sum\u{e9}\n".as_bytes()),
("bom prefix", &[0xEF, 0xBB, 0xBF, b'h', b'i']),
("multi-byte cjk", "\u{4f60}\u{597d}\n".as_bytes()),
];
for (label, input) in cases {
assert_eq!(
classify_fallback(input),
"UTF-8 Unicode text",
"case {label:?} should classify as UTF-8 Unicode text"
);
}
}
#[test]
fn classifies_binary_content_as_data() {
let cases: &[(&str, &[u8])] = &[
("null byte in otherwise-ascii text", b"hello\x00world\n"),
("random high bytes", &[0x80, 0x81, 0x82, 0x83]),
("invalid utf8 continuation-only", &[0xC0, 0x80]),
("elf magic", &[0x7f, b'E', b'L', b'F']),
];
for (label, input) in cases {
assert_eq!(
classify_fallback(input),
"data",
"case {label:?} should classify as data"
);
}
}
}