blob: f8a82629c07111948a7d695533b0dda9b63b904b [file] [log] [blame]
//! Automatically attempt to fix vulnerable dependencies
//!
//! This module is **experimental**, and its behavior may change in the future.
use crate::vulnerability::Vulnerability;
use cargo_lock::{Lockfile, Package};
use std::path::{Path, PathBuf};
use std::process::Command;
/// Auto-fixer for vulnerable dependencies
#[cfg_attr(docsrs, doc(cfg(feature = "fix")))]
pub struct Fixer {
lockfile: Lockfile,
manifest_path: Option<PathBuf>,
path_to_cargo: Option<PathBuf>,
}
impl Fixer {
/// Create a new [`Fixer`] for the given `Cargo.lock` file
///
/// `path_to_cargo` defaults to `cargo`, resolved in your `$PATH`.
///
/// If the path to `Cargo.toml` is not specified, the `cargo update` command
/// will be run in the directory with the `Cargo.lock` file.
/// Leaving it blank will fix the entire workspace.
pub fn new(
cargo_lock: Lockfile,
cargo_toml: Option<PathBuf>,
path_to_cargo: Option<PathBuf>,
) -> Self {
Self {
lockfile: cargo_lock,
manifest_path: cargo_toml,
path_to_cargo,
}
}
/// Returns a command that calls `cargo update` with the right arguments
/// to attempt to fix this vulnerability.
///
/// Note that the success of the command does not mean
/// the vulnerability was actually fixed!
/// It may remain if no semver-compatible fix was available.
pub fn get_fix_command(&self, vulnerability: &Vulnerability, dry_run: bool) -> Command {
let cargo_path: &Path = self.path_to_cargo.as_deref().unwrap_or(Path::new("cargo"));
let pkg_name = &vulnerability.package.name;
let mut command = Command::new(cargo_path);
command.arg("update");
if let Some(path) = self.manifest_path.as_ref() {
command.arg("--manifest-path").arg(path);
}
if dry_run {
command.arg("--dry-run");
}
// there can be more than one version of a given package in the lockfile, so we need to iterate over all of them
for pkg in self.lockfile.packages.iter().filter(|pkg| {
&pkg.name == pkg_name && vulnerability.versions.is_vulnerable(&pkg.version)
}) {
let pkgid = pkgid(pkg);
command.arg(&pkgid);
}
command
}
}
/// Returns a Cargo unique identifier for a package.
/// See `cargo help pkgid` for more info.
///
/// We need to pass these to `cargo update` because otherwise
/// the package specification will be ambiguous, and it will refuse to do anything.
fn pkgid(pkg: &Package) -> String {
match pkg.source.as_ref() {
Some(source) => format!("{}#{}@{}", source, pkg.name, pkg.version),
None => format!("{}@{}", pkg.name, pkg.version),
}
}