keyhog-sources 0.5.39

keyhog-sources: pluggable input backends for KeyHog (git, S3, Docker, Web)
Documentation
//! Pluggable input sources for KeyHog.
//!
//! Each source implements the [`keyhog_core::Source`] trait and yields [`keyhog_core::Chunk`]
//! values for the scanner. Sources are gated behind cargo features so only the
//! transitive dependencies you actually need are compiled.

#![allow(clippy::too_many_arguments)]

pub mod timeouts;

use std::sync::atomic::AtomicUsize;

/// How many files the filesystem walker skipped because they exceeded
/// the active `--max-file-size` cap. Bumped once per skipped entry
/// inside `FilesystemSource::process_entry`; the orchestrator reads
/// it at end-of-scan to emit a single summary line so users see what
/// the previously-silent walker filter dropped (kimi-1 dogfood #130).
/// Counter is process-global; reset between scans by the test harness
/// via `reset_skipped_over_max_size()`.
pub static SKIPPED_OVER_MAX_SIZE: AtomicUsize = AtomicUsize::new(0);

/// Reset the over-max-size counter. Public so test fixtures that run
/// multiple scans in one process can baseline between runs.
pub fn reset_skipped_over_max_size() {
    SKIPPED_OVER_MAX_SIZE.store(0, std::sync::atomic::Ordering::Relaxed);
}

/// Local HTTP compatibility shim backed by reqwest. Only present when
/// at least one feature that pulls in `reqwest` is enabled -
/// otherwise this module would `pub use reqwest::*` against a crate
/// that wasn't compiled in, which fails resolution on stable rustc
/// (especially on Windows where `--no-default-features` is the
/// release profile we ship for the no-Hyperscan build).
#[cfg(any(feature = "web", feature = "github", feature = "slack", feature = "s3"))]
pub mod reqwest {
    pub use ::reqwest::*;
}

/// Shared HTTP-client policy (proxy, TLS, UA) used by every source
/// + verifier site that talks to the network. Always compiled - the
/// `HttpClientConfig` type is the thread-through even when the
/// reqwest-backed builders are feature-gated out - so the CLI can
/// construct one without caring about which feature set is active.
pub mod http;

#[cfg(feature = "binary")]
mod binary;
#[cfg(feature = "docker")]
mod docker;
mod filesystem;
#[cfg(feature = "git")]
mod git;
#[cfg(feature = "github")]
mod github_org;
mod har;
#[cfg(feature = "s3")]
pub mod s3;
#[cfg(feature = "slack")]
mod slack;
mod stdin;
pub mod strings;
#[cfg(feature = "web")]
mod web;

#[cfg(feature = "binary")]
pub use binary::BinarySource;
#[cfg(feature = "docker")]
pub use docker::DockerImageSource;
#[doc(hidden)]
pub use filesystem::reader_pool_thread_count_for_test;
pub use filesystem::FilesystemSource;
#[cfg(feature = "git")]
pub use git::GitDiffSource;
#[cfg(feature = "git")]
pub use git::GitHistorySource;
#[cfg(feature = "git")]
pub use git::GitSource;
#[cfg(feature = "github")]
pub use github_org::GitHubOrgSource;
#[cfg(feature = "s3")]
pub use s3::S3Source;
#[cfg(feature = "slack")]
pub use slack::SlackSource;
pub use stdin::StdinSource;
#[cfg(feature = "web")]
pub use web::WebSource;

use keyhog_core::registry::get_source_registry;
// Arc is used by the registry-registration plugins below. The cfg
// match has to track the *actual* `Arc::new(...)` call sites, not
// every feature flag the file references - gating broader than this
// triggers `unused_imports` on builds that include only Docker
// (which doesn't go through the registry).
#[cfg(any(feature = "slack", feature = "s3"))]
use std::sync::Arc;

/// Create a source instance from a name and optional parameters.
/// This allows the CLI to remain agnostic of specific source implementations.
pub fn create_source(
    name: &str,
    params: Option<&str>,
) -> Result<Box<dyn keyhog_core::Source>, keyhog_core::SourceError> {
    match name {
        "slack" => {
            if let Some(token) = params {
                #[cfg(feature = "slack")]
                return Ok(Box::new(SlackSource::new(token)));
                #[cfg(not(feature = "slack"))]
                {
                    let _ = token;
                    return Err(keyhog_core::SourceError::Other(
                        "slack feature not enabled".into(),
                    ));
                }
            }
            Err(keyhog_core::SourceError::Other(
                "slack source requires a token: slack:TOKEN".into(),
            ))
        }
        "docker" => {
            if let Some(image) = params {
                #[cfg(feature = "docker")]
                return Ok(Box::new(DockerImageSource::new(image)));
                #[cfg(not(feature = "docker"))]
                {
                    let _ = image;
                    return Err(keyhog_core::SourceError::Other(
                        "docker feature not enabled".into(),
                    ));
                }
            }
            Err(keyhog_core::SourceError::Other(
                "docker source requires an image name: docker:IMAGE".into(),
            ))
        }
        "s3" => {
            if let Some(bucket) = params {
                #[cfg(feature = "s3")]
                return Ok(Box::new(S3Source::new(bucket)));
                #[cfg(not(feature = "s3"))]
                {
                    let _ = bucket;
                    return Err(keyhog_core::SourceError::Other(
                        "s3 feature not enabled".into(),
                    ));
                }
            }
            Err(keyhog_core::SourceError::Other(
                "s3 source requires a bucket name: s3:BUCKET".into(),
            ))
        }
        _ => Err(keyhog_core::SourceError::Other(format!(
            "unknown source plugin: {}",
            name
        ))),
    }
}

/// Register all compiled-in source plugins into the global registry.
/// This allows the CLI to discover sources like `slack` or `s3` via the
/// generic `--source` flag without hardcoded logic in the main crate.
pub fn register_plugins() {
    #[allow(unused_variables)]
    let registry = get_source_registry();

    #[cfg(feature = "slack")]
    if let Ok(token) = std::env::var("SLACK_TOKEN") {
        registry.register(Arc::new(SlackSource::new(token)));
    }

    #[cfg(feature = "s3")]
    if let Ok(bucket) = std::env::var("S3_BUCKET") {
        registry.register(Arc::new(S3Source::new(bucket)));
    }
}

#[doc(hidden)]
pub mod testing {
    pub fn strip_unc_prefix(s: &str) -> &str {
        crate::filesystem::strip_unc_prefix(s)
    }

    pub fn user_agent(suffix: Option<&str>) -> String {
        crate::http::user_agent(suffix)
    }

    #[cfg(feature = "binary")]
    pub fn extract_string_literals(line: &str) -> Vec<String> {
        let mut out = Vec::new();
        crate::binary::literals::extract_string_literals(line, &mut out);
        out
    }

    #[cfg(feature = "binary")]
    pub fn extract_sections(bytes: &[u8], path: &str) -> Option<Vec<keyhog_core::Chunk>> {
        crate::binary::sections::extract_sections(bytes, path)
    }

    #[cfg(feature = "github")]
    pub fn validate_repo_name(name: &str) -> Result<(), keyhog_core::SourceError> {
        crate::github_org::validate_repo_name(name)
    }

    #[cfg(feature = "github")]
    pub fn validate_clone_url(url: &str) -> Result<(), keyhog_core::SourceError> {
        crate::github_org::validate_clone_url(url)
    }

    #[cfg(feature = "web")]
    pub fn redact_url(url: &str) -> String {
        crate::web::redact_url(url).into_owned()
    }

    #[cfg(feature = "web")]
    pub fn is_disallowed_web_host(url: &str) -> bool {
        crate::web::is_disallowed_web_host(url)
    }

    #[cfg(feature = "web")]
    pub fn is_disallowed_ip(ip: std::net::IpAddr) -> bool {
        crate::web::is_disallowed_ip(ip)
    }

    #[cfg(feature = "web")]
    pub fn resolve_and_screen(
        host: &str,
        port: u16,
    ) -> Result<Vec<std::net::SocketAddr>, keyhog_core::SourceError> {
        crate::web::resolve_and_screen(host, port)
    }

    #[cfg(feature = "web")]
    pub fn build_web_client(
        http: &crate::http::HttpClientConfig,
        original_url: &str,
        use_proxy: bool,
    ) -> Result<crate::reqwest::blocking::Client, keyhog_core::SourceError> {
        crate::web::build_web_client(http, original_url, use_proxy)
    }
}