Magic Byte File Sniffer
Identify PNG, JPEG, GIF, PDF, ZIP, and gzip files from their leading bytes, then check whether the extension agrees.
Code
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
enum FileKind {
Png,
Jpeg,
Gif,
Pdf,
Zip,
Gzip,
Unknown,
}
fn sniff(bytes: &[u8]) -> FileKind {
const PNG: &[u8] = &[0x89, b'P', b'N', b'G', 0x0d, 0x0a, 0x1a, 0x0a];
const JPEG: &[u8] = &[0xff, 0xd8, 0xff];
const GIF87: &[u8] = b"GIF87a";
const GIF89: &[u8] = b"GIF89a";
const PDF: &[u8] = b"%PDF-";
const ZIP: &[u8] = &[0x50, 0x4b, 0x03, 0x04];
const GZIP: &[u8] = &[0x1f, 0x8b];
if bytes.starts_with(PNG) {
return FileKind::Png;
}
if bytes.starts_with(JPEG) {
return FileKind::Jpeg;
}
if bytes.starts_with(GIF87) || bytes.starts_with(GIF89) {
return FileKind::Gif;
}
if bytes.starts_with(PDF) {
return FileKind::Pdf;
}
if bytes.starts_with(ZIP) {
return FileKind::Zip;
}
if bytes.starts_with(GZIP) {
return FileKind::Gzip;
}
FileKind::Unknown
}
fn extension_matches(kind: FileKind, extension: &str) -> bool {
let normalized = extension.to_ascii_lowercase();
matches!(
(kind, normalized.as_str()),
(FileKind::Png, "png")
| (FileKind::Jpeg, "jpg" | "jpeg")
| (FileKind::Gif, "gif")
| (FileKind::Pdf, "pdf")
| (FileKind::Zip, "zip")
| (FileKind::Gzip, "gz" | "tgz")
)
}
fn main() {
let samples: [(&[u8], &str); 4] = [
(&[0x89, b'P', b'N', b'G', 0x0d, 0x0a, 0x1a, 0x0a, 0x00][..], "logo.png"),
(&b"%PDF-1.7"[..], "invoice.pdf"),
(&[0xff, 0xd8, 0xff, 0xe0][..], "photo.jpeg"),
(&[0x50, 0x4b, 0x03, 0x04, 0x14][..], "archive.zip"),
];
for (bytes, name) in samples {
let kind = sniff(bytes);
let extension = name.rsplit('.').next().unwrap_or_default();
println!(
"{name}: {:?} extension_ok={}",
kind,
extension_matches(kind, extension)
);
}
println!("unknown: {:?}", sniff(b"plain text"));
}Report a problem with this exampleOpens a pre-filled issue on GitHub.