Skip to main content

tiny_tree_magic/
lib.rs

1use std::collections::{HashMap, VecDeque, hash_map::Entry};
2use std::error::Error as StdError;
3use std::fmt;
4use std::hint::cold_path;
5use std::ops::Range;
6use std::str::from_utf8;
7
8use memchr::{memchr, memmem::find};
9
10pub struct Database<C> {
11    cache: C,
12    types: Box<[FrozenType]>,
13    matches: FrozenMatches,
14    children: FrozenChildren,
15    text_idx: u32,
16    binary_idx: u32,
17}
18
19#[derive(Debug, Default)]
20struct Type {
21    matches: Vec<Match>,
22    children: Vec<u32>,
23}
24
25#[derive(Debug)]
26struct Match {
27    cnt: u32,
28    off: u32,
29}
30
31#[derive(Debug)]
32struct FrozenType {
33    mime_type_off: u32,
34    matches: Range<u32>,
35    children: Range<u32>,
36}
37
38type FrozenMatches = Box<[Match]>;
39
40type FrozenChildren = Box<[u32]>;
41
42impl<C> Database<C>
43where
44    C: AsRef<[u8]>,
45{
46    /// Open the given MIME-info `cache`
47    ///
48    /// This can be an in-memory buffer or a memory map of a [mime.cache file](https://specifications.freedesktop.org/shared-mime-info/latest/ar01s02.html#id-1.3.12).
49    ///
50    /// ```no_run
51    /// # use std::error::Error;
52    /// use std::fs::File;
53    ///
54    /// use memmap2::Mmap;
55    /// use tiny_tree_magic::Database;
56    ///
57    /// let cache = unsafe { Mmap::map(&File::open("/usr/share/mime/mime.cache")?)? };
58    ///
59    /// let database = Database::open(cache)?;
60    /// # Ok::<_, Box<dyn Error>>(())
61    /// ```
62    #[cold]
63    pub fn open(cache: C) -> Result<Self, Error> {
64        let (parents, types) = parse_offsets(cache.as_ref()).bail(Error::MalformedOffsets)?;
65
66        let mut parents = parse_parents(cache.as_ref(), parents).bail(Error::MalformedParents)?;
67
68        let mut types = parse_types(cache.as_ref(), types).bail(Error::MalformedTypes)?;
69
70        let (text_off, binary_off) = parents_to_children(cache.as_ref(), &mut parents, &mut types)?;
71
72        let types = sort(types, parents)?;
73
74        Ok(Self::freeze(cache, types, text_off, binary_off))
75    }
76
77    fn freeze(cache: C, types: Vec<(u32, Type)>, text_off: u32, binary_off: u32) -> Self {
78        let mut matches =
79            Vec::with_capacity(types.iter().map(|(_, r#type)| r#type.matches.len()).sum());
80        let mut children =
81            Vec::with_capacity(types.iter().map(|(_, r#type)| r#type.children.len()).sum());
82
83        let types = types
84            .into_iter()
85            .map(|(mime_type_off, r#type)| {
86                let matches_start = u32::try_from(matches.len()).unwrap();
87                let matches_len = u32::try_from(r#type.matches.len()).unwrap();
88                let matches_end = matches_start.checked_add(matches_len).unwrap();
89                matches.extend(r#type.matches);
90
91                let children_start = u32::try_from(children.len()).unwrap();
92                let children_len = u32::try_from(r#type.children.len()).unwrap();
93                let children_end = children_start.checked_add(children_len).unwrap();
94                children.extend(r#type.children);
95
96                FrozenType {
97                    mime_type_off,
98                    matches: matches_start..matches_end,
99                    children: children_start..children_end,
100                }
101            })
102            .collect::<Box<[_]>>();
103
104        let mime_type_off_to_type_idx = types
105            .iter()
106            .enumerate()
107            .map(|(idx, r#type)| (r#type.mime_type_off, u32::try_from(idx).unwrap()))
108            .collect::<HashMap<_, _>>();
109
110        for type_idx in &mut children {
111            *type_idx = mime_type_off_to_type_idx[type_idx];
112        }
113
114        Self {
115            cache,
116            types,
117            matches: matches.into(),
118            children: children.into(),
119            text_idx: mime_type_off_to_type_idx[&text_off],
120            binary_idx: mime_type_off_to_type_idx[&binary_off],
121        }
122    }
123
124    pub fn r#match<'a>(&'a self, bytes: &[u8]) -> Result<&'a str, Error> {
125        let is_text = memchr(0, bytes).is_none();
126
127        let text_type = &self.types[self.text_idx as usize];
128        let binary_type = &self.types[self.binary_idx as usize];
129
130        let mime_type_off = if is_text
131            && let Some(mime_type_off) = self.match_children(None, &text_type.children, bytes)?
132        {
133            mime_type_off
134        } else if let Some(mime_type_off) =
135            self.match_children(None, &binary_type.children, bytes)?
136        {
137            mime_type_off
138        } else if is_text {
139            text_type.mime_type_off
140        } else {
141            binary_type.mime_type_off
142        };
143
144        resolve_mime_type(self.cache.as_ref(), mime_type_off).bail(Error::InvalidMimeType)
145    }
146
147    fn match_children(
148        &self,
149        default: Option<u32>,
150        children: &Range<u32>,
151        bytes: &[u8],
152    ) -> Result<Option<u32>, Error> {
153        let children = &self.children[children.start as usize..children.end as usize];
154
155        for type_idx in children {
156            let r#type = &self.types[*type_idx as usize];
157
158            let matches = &self.matches[r#type.matches.start as usize..r#type.matches.end as usize];
159
160            let mut args = MatchArgs {
161                cache: self.cache.as_ref(),
162                bytes,
163                depth: 0,
164            };
165
166            for r#match in matches {
167                if args
168                    .try_match(r#match.cnt, r#match.off)
169                    .bail(Error::MalformedMatch)?
170                {
171                    return self.match_children(
172                        Some(r#type.mime_type_off),
173                        &r#type.children,
174                        bytes,
175                    );
176                }
177            }
178        }
179
180        Ok(default)
181    }
182}
183
184struct MatchArgs<'a> {
185    cache: &'a [u8],
186    bytes: &'a [u8],
187    depth: u8,
188}
189
190impl MatchArgs<'_> {
191    fn try_match(&mut self, cnt: u32, off: u32) -> Option<bool> {
192        let cnt = cnt as usize;
193        let off = off as usize;
194
195        let matchlets = self.cache.get(off..)?.get(..32 * cnt)?;
196
197        for idx in 0..cnt {
198            let matchlet = &matchlets[32 * idx..][..32];
199
200            if self.try_matchlet(matchlet)? {
201                let cnt = parse_u32(matchlet, 24);
202                let off = parse_u32(matchlet, 28);
203
204                if cnt == 0 {
205                    return Some(true);
206                }
207
208                self.depth += 1;
209                if self.depth == 128 {
210                    return None;
211                }
212
213                if self.try_match(cnt, off)? {
214                    return Some(true);
215                }
216
217                self.depth -= 1;
218            }
219        }
220
221        Some(false)
222    }
223
224    #[inline]
225    fn try_matchlet(&self, matchlet: &[u8]) -> Option<bool> {
226        let range_start = parse_u32(matchlet, 0) as usize;
227        let range_length = parse_u32(matchlet, 4) as usize;
228        let data_length = parse_u32(matchlet, 12) as usize;
229        let data_offset = parse_u32(matchlet, 16) as usize;
230        let mask_offset = parse_u32(matchlet, 20) as usize;
231
232        let data = self.cache.get(data_offset..)?.get(..data_length)?;
233
234        let mask = if mask_offset != 0 {
235            Some(self.cache.get(mask_offset..)?.get(..data_length)?)
236        } else {
237            None
238        };
239
240        for pos in range_start..range_start + range_length {
241            if let Some(bytes) = self.bytes.get(pos..pos + data_length) {
242                if let Some(mask) = mask {
243                    if data
244                        .iter()
245                        .zip(mask)
246                        .zip(bytes)
247                        .all(|((data, mask), byte)| data & mask == byte & mask)
248                    {
249                        return Some(true);
250                    }
251                } else {
252                    if compare(data, bytes) {
253                        return Some(true);
254                    }
255                }
256            } else {
257                break;
258            }
259        }
260
261        Some(false)
262    }
263}
264
265#[inline]
266fn compare(lhs: &[u8], rhs: &[u8]) -> bool {
267    let len = lhs.len();
268    debug_assert_eq!(len, rhs.len());
269
270    if len >= 8 {
271        let lhs_lo = u64::from_le_bytes(lhs[..8].try_into().unwrap());
272        let rhs_lo = u64::from_le_bytes(rhs[..8].try_into().unwrap());
273        if lhs_lo != rhs_lo {
274            return false;
275        } else if len == 8 {
276            return true;
277        }
278
279        let lhs_hi = u64::from_le_bytes(lhs[len - 8..].try_into().unwrap());
280        let rhs_hi = u64::from_le_bytes(rhs[len - 8..].try_into().unwrap());
281        if lhs_hi != rhs_hi {
282            return false;
283        } else if len <= 16 {
284            return true;
285        }
286    } else if len >= 4 {
287        let lhs_lo = u32::from_le_bytes(lhs[..4].try_into().unwrap());
288        let rhs_lo = u32::from_le_bytes(rhs[..4].try_into().unwrap());
289        if lhs_lo != rhs_lo {
290            return false;
291        }
292
293        let lhs_hi = u32::from_le_bytes(lhs[len - 4..].try_into().unwrap());
294        let rhs_hi = u32::from_le_bytes(rhs[len - 4..].try_into().unwrap());
295        return lhs_hi == rhs_hi;
296    } else if len > 0 {
297        let lhs_lo = lhs[0];
298        let rhs_lo = rhs[0];
299        if lhs_lo != rhs_lo {
300            return false;
301        }
302
303        let lhs_mid = lhs[len / 2];
304        let rhs_mid = rhs[len / 2];
305        if lhs_mid != rhs_mid {
306            return false;
307        }
308
309        let lhs_hi = lhs[len - 1];
310        let rhs_hi = rhs[len - 1];
311        return lhs_hi == rhs_hi;
312    } else {
313        return true;
314    }
315
316    lhs == rhs
317}
318
319fn sort(
320    mut types: HashMap<u32, Type>,
321    mut parents: HashMap<u32, Vec<u32>>,
322) -> Result<Vec<(u32, Type)>, Error> {
323    let mut sorted = Vec::with_capacity(types.len());
324
325    let mut orphans = types
326        .keys()
327        .copied()
328        .filter(|mime_type_off| !parents.contains_key(mime_type_off))
329        .collect::<VecDeque<_>>();
330
331    while let Some(orphan_mime_type_off) = orphans.pop_back() {
332        let orphan_type = types.remove(&orphan_mime_type_off).unwrap();
333
334        for child_mime_type_off in &orphan_type.children {
335            match parents.entry(*child_mime_type_off) {
336                Entry::Occupied(mut entry) => {
337                    let parents = entry.get_mut();
338
339                    parents.retain(|parent_mime_type_off| {
340                        *parent_mime_type_off != orphan_mime_type_off
341                    });
342
343                    if parents.is_empty() {
344                        entry.remove();
345
346                        orphans.push_front(*child_mime_type_off);
347                    }
348                }
349                Entry::Vacant(_) => unreachable!(),
350            }
351        }
352
353        sorted.push((orphan_mime_type_off, orphan_type));
354    }
355
356    if parents.is_empty() {
357        Ok(sorted)
358    } else {
359        Err(Error::MalformedParents)
360    }
361}
362
363fn parents_to_children(
364    cache: &[u8],
365    parents: &mut HashMap<u32, Vec<u32>>,
366    types: &mut HashMap<u32, Type>,
367) -> Result<(u32, u32), Error> {
368    types.retain(|mime_type_off, _type| resolve_mime_type(cache, *mime_type_off).is_some());
369
370    parents.retain(|mime_type_off, parents| {
371        if types.contains_key(mime_type_off) {
372            parents.retain(|parent_mime_type_off| types.contains_key(parent_mime_type_off));
373
374            !parents.is_empty()
375        } else {
376            false
377        }
378    });
379
380    let text_off =
381        find_or_insert_mime_type(cache, types, "text/plain\0").bail(Error::MissingTextPlain)?;
382
383    let binary_off = find_or_insert_mime_type(cache, types, "application/octet-stream\0")
384        .bail(Error::MissingApplicationOctetStream)?;
385
386    for mime_type_off in types.keys() {
387        if *mime_type_off != text_off
388            && *mime_type_off != binary_off
389            && let Entry::Vacant(entry) = parents.entry(*mime_type_off)
390        {
391            let mime_type = resolve_mime_type(cache, *mime_type_off).unwrap();
392
393            let parent_mime_type_off = if mime_type.starts_with("text/") {
394                text_off
395            } else {
396                binary_off
397            };
398
399            entry.insert(vec![parent_mime_type_off]);
400        }
401    }
402
403    for (mime_type_off, parents) in parents {
404        for parent_mime_type_off in parents {
405            types
406                .get_mut(parent_mime_type_off)
407                .unwrap()
408                .children
409                .push(*mime_type_off);
410        }
411    }
412
413    for r#type in types.values_mut() {
414        r#type.children.sort_unstable();
415        r#type.children.dedup();
416    }
417
418    Ok((text_off, binary_off))
419}
420
421fn find_or_insert_mime_type(
422    cache: &[u8],
423    types: &mut HashMap<u32, Type>,
424    mime_type0: &str,
425) -> Option<u32> {
426    let mime_type = &mime_type0[..mime_type0.len() - 1];
427
428    let mime_type_off = types
429        .keys()
430        .copied()
431        .find(|mime_type_off| resolve_mime_type(cache, *mime_type_off) == Some(mime_type));
432
433    mime_type_off.or_else(|| {
434        let mime_type_off = find(cache, mime_type0.as_bytes())?.try_into().ok()?;
435
436        types.insert(mime_type_off, Default::default());
437
438        Some(mime_type_off)
439    })
440}
441
442fn resolve_mime_type(cache: &[u8], mime_type_off: u32) -> Option<&str> {
443    let off = mime_type_off as usize;
444    let bytes = cache.get(off..)?;
445
446    let pos = memchr(0, bytes)?;
447    let mime_type = &bytes[..pos];
448
449    from_utf8(mime_type).ok()
450}
451
452fn parse_offsets(cache: &[u8]) -> Option<(&[u8], &[u8])> {
453    if cache.len() < 2 * 2 + 9 * 4 {
454        return None;
455    }
456
457    let parents_off = parse_u32(cache, 2 * 2 + 4) as usize;
458    let types_off = parse_u32(cache, 2 * 2 + 5 * 4) as usize;
459
460    let parents = cache.get(parents_off..)?;
461    let types = cache.get(types_off..)?;
462
463    Some((parents, types))
464}
465
466fn parse_parents<'a>(cache: &'a [u8], bytes: &'a [u8]) -> Option<HashMap<u32, Vec<u32>>> {
467    if bytes.len() < 4 {
468        return None;
469    }
470
471    let cnt = parse_u32(bytes, 0) as usize;
472
473    let bytes = bytes.get(4..)?.get(..8 * cnt)?;
474
475    let mut parents = HashMap::<_, Vec<_>>::with_capacity(cnt);
476
477    for idx in 0..cnt {
478        let bytes = &bytes[8 * idx..][..8];
479
480        let mime_type_off = parse_u32(bytes, 0);
481        let parents_off = parse_u32(bytes, 4) as usize;
482
483        let bytes = cache.get(parents_off..)?;
484
485        if bytes.len() < 4 {
486            return None;
487        }
488
489        let cnt = parse_u32(bytes, 0) as usize;
490
491        let bytes = bytes.get(4..)?.get(..4 * cnt)?;
492
493        let parents = parents.entry(mime_type_off).or_default();
494        parents.reserve(cnt);
495
496        for idx in 0..cnt {
497            let parent_mime_type_off = parse_u32(bytes, 4 * idx);
498
499            parents.push(parent_mime_type_off);
500        }
501    }
502
503    Some(parents)
504}
505
506fn parse_types(cache: &[u8], bytes: &[u8]) -> Option<HashMap<u32, Type>> {
507    if bytes.len() < 3 * 4 {
508        return None;
509    }
510
511    let cnt = parse_u32(bytes, 0) as usize;
512    let off = parse_u32(bytes, 8) as usize;
513
514    let bytes = cache.get(off..)?.get(..16 * cnt)?;
515
516    let mut types = HashMap::<_, Type>::with_capacity(cnt);
517
518    for idx in 0..cnt {
519        let bytes = &bytes[16 * idx..];
520
521        let mime_type_off = parse_u32(bytes, 4);
522
523        let cnt = parse_u32(bytes, 8);
524        let off = parse_u32(bytes, 12);
525
526        types
527            .entry(mime_type_off)
528            .or_default()
529            .matches
530            .push(Match { cnt, off });
531    }
532
533    Some(types)
534}
535
536fn parse_u32(bytes: &[u8], off: usize) -> u32 {
537    u32::from_be_bytes(bytes[off..][..4].try_into().unwrap())
538}
539
540#[derive(Debug, Clone, Copy)]
541pub enum Error {
542    MalformedOffsets,
543    MalformedParents,
544    MalformedTypes,
545    MalformedMatch,
546    MissingTextPlain,
547    MissingApplicationOctetStream,
548    InvalidMimeType,
549}
550
551trait BailExt<T> {
552    fn bail(self, err: Error) -> Result<T, Error>;
553}
554
555impl<T> BailExt<T> for Option<T> {
556    #[inline(always)]
557    fn bail(self, err: Error) -> Result<T, Error> {
558        self.ok_or_else(|| {
559            cold_path();
560            err
561        })
562    }
563}
564
565impl fmt::Display for Error {
566    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
567        let msg = match self {
568            Self::MalformedOffsets => "Malformed offsets",
569            Self::MalformedParents => "Malformed parents",
570            Self::MalformedTypes => "Malformed types",
571            Self::MalformedMatch => "Malformed match",
572            Self::MissingTextPlain => "Missing text/plain MIME type",
573            Self::MissingApplicationOctetStream => "Missing application/octet-stream MIME type",
574            Self::InvalidMimeType => "Invalid MIME type",
575        };
576
577        fmt.write_str(msg)
578    }
579}
580
581impl StdError for Error {}