use std::collections::HashMap;
use anyhow::{Result, anyhow};
use colored::Colorize;
use serde::{Deserialize, Serialize};
pub use zoi_core::types::MiniVulnerability;
fn default_revision() -> String {
"1".to_string()
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct MiniPackageIndex {
pub repo: String,
pub repo_type: String,
pub version: String,
#[serde(default = "default_revision")]
pub revision: String,
pub description: String,
#[serde(default, deserialize_with = "deserialize_sub_packages")]
pub sub_packages: Option<Vec<String>>,
pub vuln: Option<Vec<MiniVulnerability>>
}
fn deserialize_sub_packages<'de, D>(
deserializer: D
) -> Result<Option<Vec<String>>, D::Error>
where
D: serde::Deserializer<'de>
{
let v: serde_json::Value = serde::Deserialize::deserialize(deserializer)?;
if v.is_array() {
Ok(serde_json::from_value(v).ok())
} else {
Ok(None)
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct MiniRegistryIndex {
pub packages: HashMap<String, MiniPackageIndex>
}
fn set_registry_info() -> Result<(String, String)> {
let reg = zoi_core::builtin::registry::get_set()?.ok_or_else(|| {
anyhow!(
"No set registry is defined in the built-in registries. Please \
reinstall Zoi."
)
})?;
Ok((reg.git, reg.branch))
}
pub fn fetch_registry_index() -> Result<MiniRegistryIndex> {
let (git, branch) = set_registry_info()?;
let url = zoi_purl::construct_raw_url(&git, &branch, "packages.json")?;
let client = zoi_core::utils::get_http_client()?;
let response = client.get(url).send()?;
if !response.status().is_success() {
return Err(anyhow!(
"Failed to fetch packages.json from registry: {}",
response.status()
));
}
let index: MiniRegistryIndex = response.json()?;
Ok(index)
}
pub fn fetch_registry_config() -> Result<zoi_core::types::RepoConfig> {
let (git, branch) = set_registry_info()?;
let url = zoi_purl::construct_raw_url(&git, &branch, "repo.yaml")?;
let client = zoi_core::utils::get_http_client()?;
let response = client.get(url).send()?;
if !response.status().is_success() {
return Err(anyhow!(
"Failed to fetch repo.yaml from registry: {}",
response.status()
));
}
let content = response.text()?;
let config: zoi_core::types::RepoConfig = serde_yaml::from_str(&content)?;
Ok(config)
}
pub fn get_package_lua_url(repo: &str, name: &str) -> String {
let file_path = if repo.is_empty() {
format!("{name}/{name}.pkg.lua")
} else {
format!("{repo}/{name}/{name}.pkg.lua")
};
let (git, branch) = set_registry_info().unwrap_or_else(|_| {
(
"https://gitlab.com/zillowe/zillwen/zusty/zoidberg".to_string(),
"main".to_string()
)
});
zoi_purl::construct_raw_url(&git, &branch, &file_path).unwrap_or(file_path)
}
pub fn check_vulnerabilities(
pkg_name: &str,
pkg_index: &MiniPackageIndex,
version: &str
) -> Result<bool> {
let Some(vulns) = &pkg_index.vuln else {
return Ok(true);
};
let target_version =
semver::Version::parse(version.trim_start_matches('v'))
.map_err(|e| anyhow!("Failed to parse version {version}: {e}"))?;
let mut affected = Vec::new();
for vuln in vulns {
if let Ok(req) = semver::VersionReq::parse(&vuln.affected_range)
&& req.matches(&target_version)
{
affected.push(vuln);
}
}
if affected.is_empty() {
return Ok(true);
}
println!("\n{}", "SECURITY WARNING".red().bold());
for vuln in affected {
println!(
"Package {} v{} is known to be vulnerable:",
pkg_name.cyan().bold(),
version.red()
);
println!(
"[{}] {} (Severity: {})",
vuln.id.dimmed(),
vuln.summary,
vuln.severity.to_uppercase()
);
if let Some(fixed) = &vuln.fixed_in {
println!("Fixed in version: {}", fixed.green());
}
println!();
}
Ok(zoi_core::utils::ask_for_confirmation(
"Do you want to continue with the installation anyway?",
false
))
}