const SNIFF_WINDOW: usize = 1024;
pub fn sniff_content_type(bytes: &[u8]) -> Option<&'static str> {
if bytes.is_empty() {
return None;
}
if let Some(image) = sniff_image(bytes) {
return Some(image);
}
if let Some(structured) = sniff_structured(bytes) {
return Some(structured);
}
sniff_text(bytes)
}
fn sniff_image(bytes: &[u8]) -> Option<&'static str> {
if bytes.starts_with(b"\x89PNG\r\n\x1a\n") {
return Some("image/png");
}
if bytes.starts_with(b"\xFF\xD8\xFF") {
return Some("image/jpeg");
}
if bytes.starts_with(b"GIF87a") || bytes.starts_with(b"GIF89a") {
return Some("image/gif");
}
if bytes.len() >= 12 && &bytes[..4] == b"RIFF" && &bytes[8..12] == b"WEBP" {
return Some("image/webp");
}
if bytes.starts_with(b"<svg") || starts_with_after_ws(bytes, b"<svg") {
return Some("image/svg+xml");
}
None
}
fn sniff_structured(bytes: &[u8]) -> Option<&'static str> {
let head = head_window(bytes);
if starts_with_after_bom_and_ws(head, b"<?xml") {
return Some("application/xml");
}
let trimmed = trim_leading_ws(strip_bom(head));
if trimmed.starts_with(b"<!doctype html") || trimmed.starts_with(b"<!DOCTYPE html") {
return Some("text/html");
}
if trimmed.starts_with(b"<html")
|| trimmed.starts_with(b"<HTML")
|| trimmed.starts_with(b"<head")
|| trimmed.starts_with(b"<body")
{
return Some("text/html");
}
if has_atom_or_rss_root(trimmed) {
return Some("application/atom+xml");
}
if trimmed.starts_with(b"---\n") || trimmed.starts_with(b"---\r\n") {
if looks_like_knot_frontmatter(trimmed) {
return Some("text/x-knot");
}
}
None
}
fn sniff_text(bytes: &[u8]) -> Option<&'static str> {
let head = head_window(bytes);
let trimmed = trim_leading_ws(strip_bom(head));
if trimmed.starts_with(b"=> ") || trimmed.starts_with(b"=>\t") {
return Some("text/gemini");
}
if has_markdown_marker(trimmed) {
return Some("text/markdown");
}
if !head.contains(&0) {
Some("text/plain")
} else {
None
}
}
fn head_window(bytes: &[u8]) -> &[u8] {
&bytes[..bytes.len().min(SNIFF_WINDOW)]
}
fn strip_bom(bytes: &[u8]) -> &[u8] {
bytes.strip_prefix(b"\xEF\xBB\xBF").unwrap_or(bytes)
}
fn trim_leading_ws(bytes: &[u8]) -> &[u8] {
let mut i = 0;
while i < bytes.len() && matches!(bytes[i], b' ' | b'\t' | b'\r' | b'\n') {
i += 1;
}
&bytes[i..]
}
fn starts_with_after_ws(bytes: &[u8], needle: &[u8]) -> bool {
trim_leading_ws(bytes).starts_with(needle)
}
fn starts_with_after_bom_and_ws(bytes: &[u8], needle: &[u8]) -> bool {
trim_leading_ws(strip_bom(bytes)).starts_with(needle)
}
fn has_atom_or_rss_root(trimmed: &[u8]) -> bool {
let s = std::str::from_utf8(trimmed).unwrap_or("");
s.contains("<feed") || s.contains("<rss") || s.contains("<rdf:RDF")
}
fn looks_like_knot_frontmatter(trimmed: &[u8]) -> bool {
let s = match std::str::from_utf8(trimmed) {
Ok(s) => s,
Err(_) => return false,
};
let mut lines = s.lines();
let _opener = lines.next();
let mut saw_kv = false;
for line in lines {
if line == "---" {
return saw_kv;
}
if !saw_kv && line.contains(':') {
saw_kv = true;
}
}
false
}
fn has_markdown_marker(trimmed: &[u8]) -> bool {
let s = match std::str::from_utf8(trimmed) {
Ok(s) => s,
Err(_) => return false,
};
for (i, line) in s.lines().take(8).enumerate() {
let l = line.trim_start();
if l.starts_with("# ")
|| l.starts_with("## ")
|| l.starts_with("### ")
|| l.starts_with("```")
{
return true;
}
if i > 0
&& (l.starts_with("===") || l.starts_with("---"))
&& l.chars().all(|c| c == l.chars().next().unwrap())
{
return true;
}
}
false
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_input_returns_none() {
assert_eq!(sniff_content_type(b""), None);
}
#[test]
fn png_signature_is_detected() {
let png = b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR";
assert_eq!(sniff_content_type(png), Some("image/png"));
}
#[test]
fn jpeg_signature_is_detected() {
let jpeg = b"\xFF\xD8\xFF\xE0\x00\x10JFIF";
assert_eq!(sniff_content_type(jpeg), Some("image/jpeg"));
}
#[test]
fn gif_signatures_are_detected() {
assert_eq!(sniff_content_type(b"GIF89a..."), Some("image/gif"));
assert_eq!(sniff_content_type(b"GIF87a..."), Some("image/gif"));
}
#[test]
fn webp_signature_is_detected() {
let webp = b"RIFF\x00\x00\x00\x00WEBPVP8 ";
assert_eq!(sniff_content_type(webp), Some("image/webp"));
}
#[test]
fn svg_with_leading_whitespace_is_detected() {
let svg = b" <svg xmlns=\"http://www.w3.org/2000/svg\"/>";
assert_eq!(sniff_content_type(svg), Some("image/svg+xml"));
}
#[test]
fn html_doctype_is_detected_case_insensitive() {
let html = b"<!DOCTYPE html>\n<html>";
assert_eq!(sniff_content_type(html), Some("text/html"));
let html_lower = b"<!doctype html>\n<html>";
assert_eq!(sniff_content_type(html_lower), Some("text/html"));
}
#[test]
fn html_root_without_doctype_is_detected() {
assert_eq!(
sniff_content_type(b"<html><body/></html>"),
Some("text/html")
);
}
#[test]
fn xml_declaration_is_detected_as_xml() {
let xml = b"<?xml version=\"1.0\"?><root/>";
assert_eq!(sniff_content_type(xml), Some("application/xml"));
}
#[test]
fn atom_feed_is_detected_inside_xml() {
let atom = b"<?xml version=\"1.0\"?>\n<feed xmlns=\"http://www.w3.org/2005/Atom\"/>";
let got = sniff_content_type(atom).unwrap();
assert!(
got == "application/atom+xml" || got == "application/xml",
"got {got}"
);
}
#[test]
fn rss_root_is_detected() {
let rss = b"<rss version=\"2.0\"><channel/></rss>";
assert_eq!(sniff_content_type(rss), Some("application/atom+xml"));
}
#[test]
fn knot_frontmatter_is_detected() {
let knot = b"---\ntitle: Hello\nsource: file:///x\n---\n\n# Body\n";
assert_eq!(sniff_content_type(knot), Some("text/x-knot"));
}
#[test]
fn frontmatter_without_closing_does_not_claim_knot() {
let md = b"---\n# Just a heading after a rule\n";
assert_ne!(sniff_content_type(md), Some("text/x-knot"));
}
#[test]
fn gemtext_link_line_is_detected() {
let gmi = b"=> gemini://example.org/ Example\n";
assert_eq!(sniff_content_type(gmi), Some("text/gemini"));
}
#[test]
fn markdown_heading_is_detected() {
let md = b"# Title\n\nBody paragraph here.\n";
assert_eq!(sniff_content_type(md), Some("text/markdown"));
}
#[test]
fn markdown_fenced_code_is_detected() {
let md = b"```rust\nfn main() {}\n```\n";
assert_eq!(sniff_content_type(md), Some("text/markdown"));
}
#[test]
fn plain_text_falls_through_to_text_plain() {
let txt = b"Just some unstructured prose without any markers.\n";
assert_eq!(sniff_content_type(txt), Some("text/plain"));
}
#[test]
fn binary_with_nul_returns_none() {
let bin = b"\x00\x01\x02\x03random binary garbage\x00\xFF";
assert_eq!(sniff_content_type(bin), None);
}
#[test]
fn utf8_bom_does_not_block_detection() {
let html = b"\xEF\xBB\xBF<!DOCTYPE html><html/>";
assert_eq!(sniff_content_type(html), Some("text/html"));
}
#[test]
fn sniff_window_does_not_read_past_bound() {
let mut buf = vec![b' '; SNIFF_WINDOW + 64];
buf.extend_from_slice(b"<html/>");
assert_eq!(sniff_content_type(&buf), Some("text/plain"));
}
}