Skip to main content

strypt_core/
registry.rs

1//! Dispatch from a detected format to its handler.
2//!
3//! The registry is a static table, not a plugin system. Nothing is loaded at runtime, ever
4//! (ADR-0011): a metadata scrubber that can be taught new behaviour by a file on disk has
5//! handed an attacker the tool's own privileges over the user's most sensitive documents.
6//!
7//! Adding a format means adding a line here and implementing the trait. Core dispatch does
8//! not otherwise change, which is the point of the design.
9
10use crate::detect::Format;
11use crate::formats::MetadataHandler;
12use crate::formats::jpeg::JpegHandler;
13use crate::formats::pdf::PdfHandler;
14use crate::formats::png::PngHandler;
15use crate::formats::webp::WebpHandler;
16
17/// The handler for `format`, or [`None`] if this release has none.
18///
19/// [`None`] is a real answer that the pipeline turns into a reported refusal. It must never
20/// become "pass the file through unchanged" — that is the silent failure in
21/// `docs/THREAT_MODEL.md` §5.4, and it is the one bug in this project that gets a user hurt
22/// while the tool prints success.
23#[must_use]
24pub fn handler_for(format: Format) -> Option<&'static dyn MetadataHandler> {
25    match format {
26        Format::Jpeg => Some(&JpegHandler),
27        Format::Pdf => Some(&PdfHandler),
28        Format::Png => Some(&PngHandler),
29        Format::Webp => Some(&WebpHandler),
30    }
31}
32
33/// Every format this release can actually process.
34#[must_use]
35pub fn supported_formats() -> Vec<Format> {
36    [Format::Jpeg, Format::Png, Format::Webp, Format::Pdf]
37        .into_iter()
38        .filter(|f| handler_for(*f).is_some())
39        .collect()
40}
41
42#[cfg(test)]
43mod tests {
44    #![allow(clippy::expect_used)]
45
46    use super::*;
47
48    #[test]
49    fn a_handler_is_registered_for_its_own_format() {
50        for format in supported_formats() {
51            let handler = handler_for(format).expect("listed as supported");
52            assert_eq!(handler.format(), format);
53            assert_eq!(
54                handler.name(),
55                format.id(),
56                "the handler name is the format id, because both appear in JSON output"
57            );
58        }
59    }
60
61    #[test]
62    fn every_phase_one_format_has_a_handler() {
63        // All four are implemented as of this release. The assertion that matters is not this
64        // one but its absent counterpart: there is deliberately no fallback handler, so a
65        // format added to `Format` without a line in `handler_for` fails to compile rather
66        // than silently "succeeding" by being passed through (`docs/THREAT_MODEL.md` §5.4).
67        for format in [Format::Jpeg, Format::Png, Format::Webp, Format::Pdf] {
68            assert!(handler_for(format).is_some(), "{format} has no handler");
69        }
70    }
71}