strypt-core 0.0.1

Detection and removal of hidden identifying metadata from files
Documentation
//! Dispatch from a detected format to its handler.
//!
//! The registry is a static table, not a plugin system. Nothing is loaded at runtime, ever
//! (ADR-0011): a metadata scrubber that can be taught new behaviour by a file on disk has
//! handed an attacker the tool's own privileges over the user's most sensitive documents.
//!
//! Adding a format means adding a line here and implementing the trait. Core dispatch does
//! not otherwise change, which is the point of the design.

use crate::detect::Format;
use crate::formats::MetadataHandler;
use crate::formats::jpeg::JpegHandler;
use crate::formats::pdf::PdfHandler;
use crate::formats::png::PngHandler;
use crate::formats::webp::WebpHandler;

/// The handler for `format`, or [`None`] if this release has none.
///
/// [`None`] is a real answer that the pipeline turns into a reported refusal. It must never
/// become "pass the file through unchanged" — that is the silent failure in
/// `docs/THREAT_MODEL.md` §5.4, and it is the one bug in this project that gets a user hurt
/// while the tool prints success.
#[must_use]
pub fn handler_for(format: Format) -> Option<&'static dyn MetadataHandler> {
    match format {
        Format::Jpeg => Some(&JpegHandler),
        Format::Pdf => Some(&PdfHandler),
        Format::Png => Some(&PngHandler),
        Format::Webp => Some(&WebpHandler),
    }
}

/// Every format this release can actually process.
#[must_use]
pub fn supported_formats() -> Vec<Format> {
    [Format::Jpeg, Format::Png, Format::Webp, Format::Pdf]
        .into_iter()
        .filter(|f| handler_for(*f).is_some())
        .collect()
}

#[cfg(test)]
mod tests {
    #![allow(clippy::expect_used)]

    use super::*;

    #[test]
    fn a_handler_is_registered_for_its_own_format() {
        for format in supported_formats() {
            let handler = handler_for(format).expect("listed as supported");
            assert_eq!(handler.format(), format);
            assert_eq!(
                handler.name(),
                format.id(),
                "the handler name is the format id, because both appear in JSON output"
            );
        }
    }

    #[test]
    fn every_phase_one_format_has_a_handler() {
        // All four are implemented as of this release. The assertion that matters is not this
        // one but its absent counterpart: there is deliberately no fallback handler, so a
        // format added to `Format` without a line in `handler_for` fails to compile rather
        // than silently "succeeding" by being passed through (`docs/THREAT_MODEL.md` §5.4).
        for format in [Format::Jpeg, Format::Png, Format::Webp, Format::Pdf] {
            assert!(handler_for(format).is_some(), "{format} has no handler");
        }
    }
}