const QUICKTIME_BOXES: [&[u8; 4]; 7] =
[b"ftyp", b"wide", b"mdat", b"moov", b"free", b"skip", b"pnot"];
pub fn sniff(head: &[u8]) -> Option<&'static str> {
if head.len() < 12 {
return None;
}
if head.starts_with(b"\xff\xd8\xff") {
return Some("image/jpeg");
}
if head.starts_with(b"\x89PNG\r\n\x1a\n") {
return Some("image/png");
}
if head.starts_with(b"GIF87a") || head.starts_with(b"GIF89a") {
return Some("image/gif");
}
if head.starts_with(b"BM") {
return Some("image/bmp");
}
if head.starts_with(b"II*\x00") || head.starts_with(b"MM\x00*") {
return Some("image/tiff");
}
if head.starts_with(b"RIFF") && &head[8..12] == b"WEBP" {
return Some("image/webp");
}
let box_type = &head[4..8];
if box_type == b"ftyp" {
let brand = &head[8..12];
if matches!(brand, b"heic" | b"heix" | b"hevc" | b"hevx" | b"mif1" | b"msf1") {
return Some("image/heic");
}
if &brand[..2] == b"qt" {
return Some("video/quicktime");
}
return Some("video/mp4");
}
if QUICKTIME_BOXES.iter().any(|b| box_type == *b) {
return Some("video/quicktime");
}
None
}
pub const EMBEDDABLE_MIMES: &[&str] = &[
"image/jpeg",
"image/png",
"image/gif",
"image/webp",
"image/bmp",
"image/tiff",
"image/heic",
"video/quicktime",
"video/mp4",
];
pub const EXIF_MIMES: &[&str] = &["image/jpeg", "image/tiff", "image/heic"];
pub const PHASH_MIMES: &[&str] = &[
"image/jpeg",
"image/png",
"image/gif",
"image/webp",
"image/bmp",
"image/tiff",
"video/quicktime",
"video/mp4",
];
pub const PHOTO_MIMES: &[&str] = &[
"image/jpeg",
"image/png",
"image/gif",
"image/webp",
"image/bmp",
"image/tiff",
"image/heic",
];
pub const VIDEO_MIMES: &[&str] = &["video/quicktime", "video/mp4"];
pub const UNKNOWN_MIME: &str = "application/octet-stream";
fn mime_for_ext(ext: &str) -> Option<&'static str> {
Some(match ext.to_lowercase().as_str() {
"jpg" | "jpeg" => "image/jpeg",
"png" => "image/png",
"gif" => "image/gif",
"webp" => "image/webp",
"bmp" => "image/bmp",
"tiff" | "tif" | "dng" => "image/tiff",
"heic" => "image/heic",
"mov" => "video/quicktime",
"mp4" | "m4v" => "video/mp4",
_ => return None,
})
}
pub fn effective_mime(mime: Option<&str>, ext: &str) -> Option<&'static str> {
let mime = mime.filter(|m| *m != UNKNOWN_MIME);
if let Some(m) = mime {
if let Some(known) = EMBEDDABLE_MIMES
.iter()
.chain(PHOTO_MIMES)
.chain(VIDEO_MIMES)
.find(|k| **k == m)
{
return Some(known);
}
}
mime_for_ext(ext)
}
pub fn is_embeddable(mime: Option<&str>, ext: &str) -> bool {
if ext.eq_ignore_ascii_case("dng") {
return false;
}
effective_mime(mime, ext).is_some_and(|m| EMBEDDABLE_MIMES.contains(&m))
}
pub fn is_video_mime(mime: &str) -> bool {
VIDEO_MIMES.contains(&mime)
}
#[cfg(test)]
mod tests {
use super::*;
fn iso(box_type: &[u8; 4], brand: &[u8; 4]) -> Vec<u8> {
let mut v = vec![0, 0, 0, 0x20];
v.extend_from_slice(box_type);
v.extend_from_slice(brand);
v
}
#[test]
fn jpeg_is_detected() {
assert_eq!(
sniff(b"\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01"),
Some("image/jpeg")
);
}
#[test]
fn png_is_detected() {
assert_eq!(sniff(b"\x89PNG\r\n\x1a\n\x00\x00\x00\x0d"), Some("image/png"));
}
#[test]
fn gif_both_versions_are_detected() {
assert_eq!(sniff(b"GIF87a\x00\x00\x00\x00\x00\x00"), Some("image/gif"));
assert_eq!(sniff(b"GIF89a\x00\x00\x00\x00\x00\x00"), Some("image/gif"));
}
#[test]
fn bmp_is_detected() {
assert_eq!(
sniff(b"BM\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"),
Some("image/bmp")
);
}
#[test]
fn tiff_both_endiannesses_are_detected() {
assert_eq!(
sniff(b"II*\x00\x08\x00\x00\x00\x00\x00\x00\x00"),
Some("image/tiff")
);
assert_eq!(
sniff(b"MM\x00*\x00\x00\x00\x08\x00\x00\x00\x00"),
Some("image/tiff")
);
}
#[test]
fn webp_needs_both_riff_and_webp() {
assert_eq!(sniff(b"RIFF\x00\x00\x00\x00WEBP"), Some("image/webp"));
assert_eq!(sniff(b"RIFF\x00\x00\x00\x00WAVE"), None);
}
#[test]
fn heic_brands_are_images_not_video() {
for brand in [b"heic", b"heix", b"hevc", b"hevx", b"mif1", b"msf1"] {
assert_eq!(sniff(&iso(b"ftyp", brand)), Some("image/heic"), "{brand:?}");
}
}
#[test]
fn mp4_and_quicktime_brands_are_distinguished() {
assert_eq!(sniff(&iso(b"ftyp", b"isom")), Some("video/mp4"));
assert_eq!(sniff(&iso(b"ftyp", b"mp42")), Some("video/mp4"));
assert_eq!(sniff(&iso(b"ftyp", b"qt ")), Some("video/quicktime"));
}
#[test]
fn classic_quicktime_without_ftyp_is_detected() {
for b in [b"wide", b"mdat", b"moov", b"free", b"skip", b"pnot"] {
let mut v = vec![0, 0, 0, 8];
v.extend_from_slice(b);
v.extend_from_slice(&[0u8; 8]);
assert_eq!(sniff(&v), Some("video/quicktime"), "{b:?}");
}
}
#[test]
fn short_input_is_none() {
assert_eq!(sniff(b""), None);
assert_eq!(
sniff(b"\xff\xd8\xff"),
None,
"under 12 bytes is never enough"
);
}
#[test]
fn unrecognised_bytes_are_none() {
assert_eq!(sniff(b"not a real file header at all"), None);
}
#[test]
fn effective_mime_prefers_the_detected_value() {
assert_eq!(effective_mime(Some("image/jpeg"), "png"), Some("image/jpeg"));
}
#[test]
fn effective_mime_falls_back_to_the_extension_when_null() {
assert_eq!(effective_mime(None, "jpg"), Some("image/jpeg"));
assert_eq!(effective_mime(None, "MOV"), Some("video/quicktime"));
assert_eq!(effective_mime(None, "xyz"), None);
}
#[test]
fn a_misnamed_jpeg_is_embeddable() {
assert!(is_embeddable(Some("image/jpeg"), "png"));
}
#[test]
fn dng_is_never_embeddable_even_though_it_reports_tiff() {
assert!(!is_embeddable(Some("image/tiff"), "dng"));
assert!(!is_embeddable(None, "dng"));
}
#[test]
fn a_real_tiff_is_still_embeddable_so_the_veto_stays_narrow() {
assert!(is_embeddable(Some("image/tiff"), "tiff"));
}
#[test]
fn videos_are_embeddable_and_identified_as_video() {
assert!(is_embeddable(Some("video/quicktime"), "mov"));
assert!(is_video_mime("video/quicktime"));
assert!(is_video_mime("video/mp4"));
assert!(!is_video_mime("image/jpeg"));
}
#[test]
fn an_unknown_type_is_not_embeddable() {
assert!(!is_embeddable(None, "xyz"));
}
#[test]
fn the_sentinel_is_treated_as_unknown_not_as_a_type() {
assert_eq!(effective_mime(Some(UNKNOWN_MIME), "jpg"), Some("image/jpeg"));
assert_eq!(effective_mime(Some(UNKNOWN_MIME), "mov"), Some("video/quicktime"));
}
#[test]
fn a_file_with_the_sentinel_is_still_embeddable() {
assert!(is_embeddable(Some(UNKNOWN_MIME), "jpg"));
}
#[test]
fn the_sentinel_with_an_unknown_extension_stays_unknown() {
assert_eq!(effective_mime(Some(UNKNOWN_MIME), "xyz"), None);
assert!(!is_embeddable(Some(UNKNOWN_MIME), "xyz"));
}
}