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}