#![no_std]
static RAW_DATA: &'static str = include_str!("raw_data");
#[derive(Copy, Clone, PartialEq, Debug)]
struct Entry(u16, u8, u8);
impl Entry {
fn subtype(self) -> &'static [u8] {
let loc = self.0 as usize;
let len = self.1 as usize;
&RAW_DATA.as_bytes()[loc..loc + len]
}
fn extension(self) -> &'static str {
let loc = self.0 as usize + self.1 as usize;
let len = self.2 as usize;
&RAW_DATA[loc..loc + len]
}
}
type Table = &'static [Entry];
type Tables = &'static [(&'static str, Table)];
static LOOKUP: Tables = include!("lookup");
fn find_entry(table: Table, subtype: &str) -> Option<Entry> {
let subtype = subtype.as_bytes();
match table.binary_search_by(|entry| entry.subtype().cmp(subtype)) {
Ok(idx) => Some(table[idx]),
Err(_) => None,
}
}
fn find_table(type_: &str) -> Option<Table> {
match LOOKUP.iter().find(|item| item.0 == type_) {
Some(item) => Some(item.1),
None => None,
}
}
fn parse_mimetype(mimetype: &str) -> Option<(&str, &str)> {
let idx = match mimetype.find('/') {
Some(idx) => idx,
None => return None,
};
let (type_, mut subtype) = mimetype.split_at(idx);
subtype = &subtype[1..];
if let Some(idx) = subtype.find(';') {
subtype = &subtype[..idx];
}
Some((type_, subtype))
}
pub fn mime2ext<S: AsRef<str>>(mimetype: S) -> Option<&'static str> {
match parse_mimetype(mimetype.as_ref()) {
Some((type_, subtype)) => match find_table(type_) {
Some(table) => find_entry(table, subtype).map(Entry::extension),
None => None,
},
None => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
extern crate core;
extern crate std;
static NOT_FOUND: &'static [&'static str] = &[
"notareal/mimetype",
"noslash",
"application/",
"application/jpeg",
"application////",
"application/octet-stream/",
"/application/octet-stream",
"application/aaaaaaa",
"application/zzzzzzz",
"aaaaaaaa/jpeg",
"zzzzzzzz/jpeg",
"",
"/",
"//",
"/;",
"a/;",
"/a;",
"a/a;",
";;",
";",
"\0",
"\u{00B5}",
"\u{00B5}\u{00B5}/\u{00B5}\u{00B5}",
"\u{00B5}\u{00B5}/\u{00B5}\u{00B5}",
"a\u{00B5}\u{00B5}//\u{00B5}\u{00B5}",
"application/clr", "x-conference/nonexistent",
"text/html ;", "application/xcap-error+xml", "image/hsj2", ];
#[test]
fn not_found() {
for mimetype in NOT_FOUND {
assert_eq!(mime2ext(*mimetype), None);
}
}
static FOUND: &'static [(&'static str, &'static str)] = &[
("application/octet-stream", "bin"),
("image/png", "png"),
("application/davmount+xml", "davmount"),
("application/andrew-inset", "ez"),
("x-conference/x-cooltalk", "ice"),
("text/html; charset=UTF-8", "html"),
("text/xml;", "xml"),
("audio/amr", "amr"), ("model/vnd.sap.vds", "vds"), ("application/ecmascript", "ecma"), ("application/vnd.mapbox-vector-tile", "mvt"), ("model/step-xml+zip", "stpxz"), ("application/express", "exp"), ("text/vnd.familysearch.gedcom", "ged"), ("image/avci", "avci"), ("image/jxl", "jxl"), ("text/markdown", "md"), ("application/x-blender", "blend"), ("image/jpeg", "jpg"), ];
#[test]
fn found() {
for &(mimetype, ext) in FOUND {
assert_eq!(mime2ext(mimetype), Some(ext));
}
}
#[test]
fn check_entries() {
for &(type_, entries) in super::LOOKUP {
assert!(!type_.is_empty());
assert!(!type_.contains('/'));
let mut sorted = entries.to_vec();
sorted.sort_by(|a, b| a.subtype().cmp(b.subtype()));
let sorted: &[super::Entry] = &sorted;
assert_eq!(entries, sorted);
for entry in entries {
let subtype = core::str::from_utf8(entry.subtype()).unwrap();
let ext = entry.extension();
assert!(!subtype.is_empty());
assert!(!subtype.contains('/'));
assert!(!ext.is_empty());
assert!(!ext.contains('.'));
assert!(!ext.contains('/'));
let mimetype = std::string::String::from(type_) + "/" + subtype;
assert_eq!(mime2ext(&mimetype), Some(ext));
}
}
}
#[test]
fn check_sizes() {
assert_eq!(std::mem::size_of::<super::Entry>(), 4);
assert!(super::RAW_DATA.len() < std::u16::MAX as usize);
}
}