const SUPPORTED: [&str; 5] = ["png", "jpg", "jpeg", "gif", "webp"];
const FNV_OFFSET_BASIS: u64 = 0xcbf2_9ce4_8422_2325;
const FNV_PRIME: u64 = 0x0000_0100_0000_01b3;
pub fn content_hash(bytes: &[u8]) -> u64 {
let mut hash = FNV_OFFSET_BASIS;
for byte in bytes {
hash ^= *byte as u64;
hash = hash.wrapping_mul(FNV_PRIME);
}
hash
}
pub fn name_with_ext(bytes: &[u8], ext: &str) -> Option<String> {
let ext = ext.trim_start_matches('.').to_ascii_lowercase();
if !SUPPORTED.contains(&ext.as_str()) {
return None;
}
Some(format!("{:016x}.{ext}", content_hash(bytes)))
}
pub fn name_for_path(bytes: &[u8], path: &str) -> Option<String> {
let lower = path.to_ascii_lowercase();
let ext = SUPPORTED.iter().find(|e| lower.ends_with(&format!(".{e}")))?;
name_with_ext(bytes, ext)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fnv1a_matches_the_published_vectors() {
assert_eq!(content_hash(b""), 0xcbf2_9ce4_8422_2325);
assert_eq!(content_hash(b"a"), 0xaf63_dc4c_8601_ec8c);
assert_eq!(content_hash(b"foobar"), 0x8594_4171_f739_67e8);
}
#[test]
fn the_hash_does_not_depend_on_a_length_prefix() {
assert_ne!(content_hash(b"ab"), content_hash(b"abc"));
assert_eq!(content_hash(b"foobar"), content_hash(b"foobar"));
}
#[test]
fn names_are_sixteen_hex_digits_plus_the_extension() {
let name = name_with_ext(b"payload", "png").unwrap();
assert_eq!(name.len(), 16 + ".png".len());
assert!(name.ends_with(".png"));
assert!(name[..16].chars().all(|c| c.is_ascii_hexdigit()));
}
#[test]
fn identical_bytes_get_identical_names() {
assert_eq!(
name_with_ext(b"same picture", "jpg"),
name_with_ext(b"same picture", "jpg")
);
assert_ne!(
name_with_ext(b"one", "png"),
name_with_ext(b"another", "png")
);
}
#[test]
fn a_leading_dot_and_upper_case_are_both_accepted() {
let a = name_with_ext(b"x", "PNG").unwrap();
let b = name_with_ext(b"x", ".png").unwrap();
assert_eq!(a, b);
assert!(a.ends_with(".png"), "the extension is normalised to lower case");
}
#[test]
fn an_unsupported_extension_is_not_named() {
assert!(name_with_ext(b"x", "emf").is_none());
assert!(name_with_ext(b"x", "tiff").is_none());
assert!(name_for_path(b"x", "media/diagram.wmf").is_none());
}
#[test]
fn the_extension_is_read_off_a_zip_entry_path() {
assert_eq!(
name_for_path(b"x", "word/media/image1.JPEG"),
name_with_ext(b"x", "jpeg")
);
assert_eq!(
name_for_path(b"x", "Pictures/1000000100000.png"),
name_with_ext(b"x", "png")
);
}
#[test]
fn jpg_and_jpeg_are_not_collapsed() {
assert_ne!(
name_with_ext(b"x", "jpg").unwrap(),
name_with_ext(b"x", "jpeg").unwrap()
);
}
}