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::flac::FlacHandler;
13use crate::formats::gif::GifHandler;
14use crate::formats::heif::HeifHandler;
15use crate::formats::jpeg::JpegHandler;
16use crate::formats::jxl::JxlHandler;
17use crate::formats::mp3::Mp3Handler;
18use crate::formats::mp4::Mp4Handler;
19use crate::formats::odf::OdfHandler;
20use crate::formats::ogg::OggHandler;
21use crate::formats::ooxml::OoxmlHandler;
22use crate::formats::pdf::PdfHandler;
23use crate::formats::png::PngHandler;
24use crate::formats::svg::SvgHandler;
25use crate::formats::tiff::TiffHandler;
26use crate::formats::wav::WavHandler;
27use crate::formats::webp::WebpHandler;
28
29/// The handler for `format`, or [`None`] if this release has none.
30///
31/// [`None`] is a real answer that the pipeline turns into a reported refusal. It must never
32/// become "pass the file through unchanged" — that is the silent failure in
33/// `docs/THREAT_MODEL.md` §5.4, and it is the one bug in this project that gets a user hurt
34/// while the tool prints success.
35#[must_use]
36pub fn handler_for(format: Format) -> Option<&'static dyn MetadataHandler> {
37    match format {
38        Format::Jpeg => Some(&JpegHandler),
39        Format::Pdf => Some(&PdfHandler),
40        Format::Png => Some(&PngHandler),
41        Format::Tiff => Some(&TiffHandler),
42        Format::Gif => Some(&GifHandler),
43        Format::Svg => Some(&SvgHandler),
44        Format::Jxl => Some(&JxlHandler),
45        Format::Flac => Some(&FlacHandler),
46        Format::Wav => Some(&WavHandler),
47        Format::Mp3 => Some(&Mp3Handler),
48        // As the Office and OpenDocument handlers below: one type, one instance per format, so
49        // that `handler.format()` answers with what dispatch chose.
50        Format::Heif => Some(&HEIF),
51        // One handler, two formats: the container is the same and only the name a user has for it
52        // differs (ADR-0042).
53        Format::Mp4 => Some(&MP4),
54        Format::M4a => Some(&M4A),
55        // One handler, three mappings: the container is shared and the header packets are not.
56        Format::Ogg => Some(&OGG_VORBIS),
57        Format::Opus => Some(&OPUS),
58        Format::OggFlac => Some(&OGG_FLAC),
59        Format::Avif => Some(&AVIF),
60        Format::Webp => Some(&WebpHandler),
61        // One handler type serving three formats, instantiated once per format rather than
62        // branching inside itself, so `handler.format()` still answers with the format the
63        // registry dispatched on.
64        Format::Docx => Some(&DOCX),
65        Format::Xlsx => Some(&XLSX),
66        Format::Pptx => Some(&PPTX),
67        Format::Odt => Some(&ODT),
68        Format::Ods => Some(&ODS),
69        Format::Odp => Some(&ODP),
70    }
71}
72
73static OGG_VORBIS: OggHandler = OggHandler::VORBIS;
74static OPUS: OggHandler = OggHandler::OPUS;
75static OGG_FLAC: OggHandler = OggHandler::FLAC;
76static MP4: Mp4Handler = Mp4Handler::MP4;
77static M4A: Mp4Handler = Mp4Handler::M4A;
78static HEIF: HeifHandler = HeifHandler::HEIF;
79static AVIF: HeifHandler = HeifHandler::AVIF;
80static DOCX: OoxmlHandler = OoxmlHandler::DOCX;
81static XLSX: OoxmlHandler = OoxmlHandler::XLSX;
82static PPTX: OoxmlHandler = OoxmlHandler::PPTX;
83static ODT: OdfHandler = OdfHandler::ODT;
84static ODS: OdfHandler = OdfHandler::ODS;
85static ODP: OdfHandler = OdfHandler::ODP;
86
87/// Every format this release can actually process.
88#[must_use]
89pub fn supported_formats() -> Vec<Format> {
90    [
91        Format::Jpeg,
92        Format::Png,
93        Format::Webp,
94        Format::Pdf,
95        Format::Tiff,
96        Format::Gif,
97        Format::Svg,
98        Format::Jxl,
99        Format::Flac,
100        Format::Wav,
101        Format::Mp3,
102        Format::Ogg,
103        Format::Opus,
104        Format::OggFlac,
105        Format::Mp4,
106        Format::M4a,
107        Format::Heif,
108        Format::Avif,
109        Format::Docx,
110        Format::Xlsx,
111        Format::Pptx,
112        Format::Odt,
113        Format::Ods,
114        Format::Odp,
115    ]
116    .into_iter()
117    .filter(|f| handler_for(*f).is_some())
118    .collect()
119}
120
121#[cfg(test)]
122mod tests {
123    #![allow(clippy::expect_used)]
124
125    use super::*;
126
127    #[test]
128    fn a_handler_is_registered_for_its_own_format() {
129        for format in supported_formats() {
130            let handler = handler_for(format).expect("listed as supported");
131            assert_eq!(handler.format(), format);
132            assert_eq!(
133                handler.name(),
134                format.id(),
135                "the handler name is the format id, because both appear in JSON output"
136            );
137        }
138    }
139
140    #[test]
141    fn every_shipped_format_has_a_handler() {
142        // The assertion that matters is not this one but its absent counterpart: there is
143        // deliberately no fallback handler, so a format added to `Format` without a line in
144        // `handler_for` fails to compile rather than silently "succeeding" by being passed
145        // through (`docs/THREAT_MODEL.md` §5.4).
146        for format in [
147            Format::Jpeg,
148            Format::Png,
149            Format::Webp,
150            Format::Pdf,
151            Format::Tiff,
152            Format::Gif,
153            Format::Svg,
154            Format::Jxl,
155            Format::Flac,
156            Format::Wav,
157            Format::Mp3,
158            Format::Ogg,
159            Format::Opus,
160            Format::OggFlac,
161            Format::Mp4,
162            Format::M4a,
163            Format::Docx,
164            Format::Xlsx,
165            Format::Pptx,
166            Format::Odt,
167            Format::Ods,
168            Format::Odp,
169        ] {
170            assert!(handler_for(format).is_some(), "{format} has no handler");
171        }
172    }
173}