1use crate::{
4 error::{Error, ErrorKind},
5 lock::acquire_cargo_package_lock,
6 prelude::*,
7};
8use std::{
9 fs,
10 path::{Path, PathBuf},
11};
12use tame_index::index::RemoteGitIndex;
13
14pub const COLLECTIONS: &[rustsec::Collection] =
17 &[rustsec::Collection::Crates, rustsec::Collection::Rust];
18
19pub struct Linter {
21 repo_path: PathBuf,
23
24 crates_index: RemoteGitIndex,
26
27 advisory_db: rustsec::Database,
29
30 invalid_advisories: usize,
32
33 skip_namecheck: Option<String>,
35}
36
37impl Linter {
38 pub fn new(
40 repo_path: impl Into<PathBuf>,
41 skip_namecheck: Option<String>,
42 ) -> Result<Self, Error> {
43 let repo_path = repo_path.into();
44 let cargo_package_lock = acquire_cargo_package_lock()?;
45 let mut crates_index = RemoteGitIndex::new(
46 tame_index::GitIndex::new(tame_index::IndexLocation::new(
47 tame_index::IndexUrl::CratesIoGit,
48 ))?,
49 &cargo_package_lock,
50 )?;
51 crates_index.fetch(&cargo_package_lock)?;
52 let advisory_db = rustsec::Database::open(&repo_path)?;
53
54 Ok(Self {
55 repo_path,
56 crates_index,
57 advisory_db,
58 invalid_advisories: 0,
59 skip_namecheck,
60 })
61 }
62
63 pub fn advisory_db(&self) -> &rustsec::Database {
65 &self.advisory_db
66 }
67
68 pub fn lint(mut self) -> Result<usize, Error> {
70 for collection in COLLECTIONS {
71 for crate_entry in fs::read_dir(self.repo_path.join(collection.as_str())).unwrap() {
72 let crate_dir = crate_entry.unwrap().path();
73
74 if !crate_dir.is_dir() {
75 fail!(
76 ErrorKind::RustSec,
77 "unexpected file in `{}`: {}",
78 collection,
79 crate_dir.display()
80 );
81 }
82
83 for advisory_entry in crate_dir.read_dir().unwrap() {
84 let advisory_path = advisory_entry.unwrap().path();
85 self.lint_advisory(*collection, &advisory_path)?;
86 }
87 }
88 }
89
90 Ok(self.invalid_advisories)
91 }
92
93 fn lint_advisory(
96 &mut self,
97 collection: rustsec::Collection,
98 advisory_path: &Path,
99 ) -> Result<(), Error> {
100 if !advisory_path.is_file() {
101 fail!(
102 ErrorKind::RustSec,
103 "unexpected entry in `{}`: {}",
104 collection,
105 advisory_path.display()
106 );
107 }
108
109 let advisory = rustsec::Advisory::load_file(advisory_path)?;
110
111 if collection == rustsec::Collection::Crates {
112 self.crates_io_lints(&advisory)?;
113 }
114
115 let lint_result = rustsec::advisory::Linter::lint_file(advisory_path)?;
116
117 if lint_result.errors().is_empty() {
118 status_ok!("Linted", "ok: {}", advisory_path.display());
119 } else {
120 self.invalid_advisories += 1;
121
122 status_err!(
123 "{} contained the following lint errors:",
124 advisory_path.display()
125 );
126
127 for error in lint_result.errors() {
128 println!(" - {}", error);
129 }
130 }
131
132 Ok(())
133 }
134
135 fn crates_io_lints(&mut self, advisory: &rustsec::Advisory) -> Result<(), Error> {
137 if !self.name_is_skipped(advisory.metadata.package.as_str())
138 && !self.name_exists_on_crates_io(advisory.metadata.package.as_str())
139 {
140 self.invalid_advisories += 1;
141
142 fail!(
143 ErrorKind::CratesIo,
144 "crates.io package name does not match package name in advisory for {} in {}",
145 advisory.metadata.package.as_str(),
146 advisory.metadata.id
147 );
148 }
149
150 Ok(())
151 }
152
153 fn name_is_skipped(&self, package_name: &str) -> bool {
155 match &self.skip_namecheck {
156 Some(skips) => skips.split(',').any(|a| a == package_name),
157 None => false,
158 }
159 }
160
161 fn name_exists_on_crates_io(&self, name: &str) -> bool {
163 if let Ok(Some(crate_)) = self.crates_index.krate(
164 name.try_into().unwrap(),
165 true,
166 &acquire_cargo_package_lock().unwrap(),
167 ) {
168 crate_.name() == name
172 } else {
173 false
174 }
175 }
176}