zoi-rs 1.14.0

Universal Package Manager & Environment Setup Tool
Documentation
use anyhow::{Result, anyhow};
use sha2::{Digest, Sha256, Sha512};
use std::fs::File;
use std::io::Read;

pub enum HashType {
    Sha512,
    Sha256,
}

fn update_digest_from_reader<R: Read, D: Digest>(reader: &mut R, hasher: &mut D) -> Result<()> {
    let mut buffer = [0; 8192];
    loop {
        let bytes_read = reader.read(&mut buffer)?;
        if bytes_read == 0 {
            break;
        }
        hasher.update(&buffer[..bytes_read]);
    }
    Ok(())
}

pub fn get_hash(source: &str, hash_type: HashType) -> Result<String> {
    let mut hasher_sha512 = Sha512::new();
    let mut hasher_sha256 = Sha256::new();

    if source.starts_with("http://") || source.starts_with("https://") {
        let client = crate::utils::get_http_client()?;
        let mut response = client.get(source).send()?;
        if !response.status().is_success() {
            return Err(anyhow!(
                "Failed to download file from URL: {}",
                response.status()
            ));
        }
        match hash_type {
            HashType::Sha512 => {
                update_digest_from_reader(&mut response, &mut hasher_sha512)?;
            }
            HashType::Sha256 => {
                update_digest_from_reader(&mut response, &mut hasher_sha256)?;
            }
        }
    } else {
        let mut file = File::open(source)?;
        match hash_type {
            HashType::Sha512 => {
                update_digest_from_reader(&mut file, &mut hasher_sha512)?;
            }
            HashType::Sha256 => {
                update_digest_from_reader(&mut file, &mut hasher_sha256)?;
            }
        }
    };

    let hash = match hash_type {
        HashType::Sha512 => hex::encode(hasher_sha512.finalize()),
        HashType::Sha256 => hex::encode(hasher_sha256.finalize()),
    };

    Ok(hash)
}

pub mod validate {
    use anyhow::{Result, anyhow};
    use colored::Colorize;
    use std::path::Path;

    pub fn run(file: &Path) -> Result<()> {
        if !file.exists() {
            return Err(anyhow!("File does not exist: {}", file.display()));
        }

        let content = std::fs::read_to_string(file)?;
        let file_name = file.file_name().and_then(|n| n.to_str()).unwrap_or("");

        println!("{} Validating {}...", "::".bold().blue(), file.display());

        if file_name == "registries.json" {
            let _: crate::pkg::purl::CentralDbSpec = serde_json::from_str(&content)
                .map_err(|e| anyhow!("Invalid registries.json spec: {}", e))?;
            println!(
                "{} file is a valid registries.json spec.",
                "OK".bold().green()
            );
        } else if file_name == "repo.yaml" || file_name == "repo.yml" {
            let _: crate::pkg::types::RepoConfig = serde_yaml::from_str(&content)
                .map_err(|e| anyhow!("Invalid repo.yaml spec: {}", e))?;
            println!("{} file is a valid repo.yaml spec.", "OK".bold().green());
        } else if file_name == "advisories.json" {
            let _: crate::pkg::types::AdvisoryRegistry = serde_json::from_str(&content)
                .map_err(|e| anyhow!("Invalid advisories.json spec: {}", e))?;
            println!(
                "{} file is a valid advisories.json spec.",
                "OK".bold().green()
            );
        } else if file_name == "packages.json" {
            let _: crate::pkg::purl::RegistryIndex = serde_json::from_str(&content)
                .map_err(|e| anyhow!("Invalid packages.json spec: {}", e))?;
            println!(
                "{} file is a valid packages.json spec.",
                "OK".bold().green()
            );
        } else if file_name.ends_with(".sec.yaml") || file_name.ends_with(".sec.yml") {
            let _: crate::pkg::types::Advisory = serde_yaml::from_str(&content)
                .map_err(|e| anyhow!("Invalid security advisory (.sec.yaml) spec: {}", e))?;
            println!("{} file is a valid .sec.yaml spec.", "OK".bold().green());
        } else {
            if file.extension().and_then(|e| e.to_str()) == Some("json") {
                if serde_json::from_str::<crate::pkg::purl::CentralDbSpec>(&content).is_ok() {
                    println!("{} file matches registries.json spec.", "OK".bold().green());
                } else if serde_json::from_str::<crate::pkg::types::AdvisoryRegistry>(&content)
                    .is_ok()
                {
                    println!("{} file matches advisories.json spec.", "OK".bold().green());
                } else if serde_json::from_str::<crate::pkg::purl::RegistryIndex>(&content).is_ok()
                {
                    println!("{} file matches packages.json spec.", "OK".bold().green());
                } else {
                    return Err(anyhow!(
                        "File does not match any known Zoi JSON spec (registries.json, advisories.json, or packages.json)"
                    ));
                }
            } else if file.extension().and_then(|e| e.to_str()) == Some("yaml")
                || file.extension().and_then(|e| e.to_str()) == Some("yml")
            {
                if serde_yaml::from_str::<crate::pkg::types::RepoConfig>(&content).is_ok() {
                    println!("{} file matches repo.yaml spec.", "OK".bold().green());
                } else if serde_yaml::from_str::<crate::pkg::types::Advisory>(&content).is_ok() {
                    println!("{} file matches .sec.yaml spec.", "OK".bold().green());
                } else {
                    return Err(anyhow!(
                        "File does not match any known Zoi YAML spec (repo.yaml or .sec.yaml)"
                    ));
                }
            } else {
                return Err(anyhow!(
                    "Unsupported file extension. Please provide a .json or .yaml file"
                ));
            }
        }

        Ok(())
    }
}