1use crate::{
2 analyzer::{tool_installer::ToolInstaller, dependency_parser::Language},
3 cli::{ToolsCommand, OutputFormat},
4};
5use std::collections::HashMap;
6use termcolor::{ColorChoice, StandardStream, WriteColor, ColorSpec, Color};
7
8pub async fn handle_tools(command: ToolsCommand) -> crate::Result<()> {
9 match command {
10 ToolsCommand::Status { format, languages } => handle_tools_status(format, languages),
11 ToolsCommand::Install { languages, include_owasp, dry_run, yes } => {
12 handle_tools_install(languages, include_owasp, dry_run, yes)
13 }
14 ToolsCommand::Verify { languages, verbose } => handle_tools_verify(languages, verbose),
15 ToolsCommand::Guide { languages, platform } => handle_tools_guide(languages, platform),
16 }
17}
18
19fn handle_tools_status(format: OutputFormat, languages: Option<Vec<String>>) -> crate::Result<()> {
20 let installer = ToolInstaller::new();
21
22 let langs_to_check = get_languages_to_check(languages);
24
25 println!("š§ Checking vulnerability scanning tools status...\n");
26
27 match format {
28 OutputFormat::Table => display_status_table(&installer, &langs_to_check)?,
29 OutputFormat::Json => display_status_json(&installer, &langs_to_check),
30 }
31
32 Ok(())
33}
34
35fn handle_tools_install(
36 languages: Option<Vec<String>>,
37 include_owasp: bool,
38 dry_run: bool,
39 yes: bool,
40) -> crate::Result<()> {
41 let mut installer = ToolInstaller::new();
42
43 let langs_to_install = get_languages_to_install(languages);
45
46 if dry_run {
47 return handle_dry_run(&installer, &langs_to_install, include_owasp);
48 }
49
50 if !yes && !confirm_installation()? {
51 println!("Installation cancelled.");
52 return Ok(());
53 }
54
55 println!("š ļø Installing vulnerability scanning tools...");
56
57 match installer.ensure_tools_for_languages(&langs_to_install) {
58 Ok(()) => {
59 println!("ā
Tool installation completed!");
60 installer.print_tool_status(&langs_to_install);
61 print_setup_instructions();
62 }
63 Err(e) => {
64 eprintln!("ā Tool installation failed: {}", e);
65 eprintln!("\nš§ Manual installation may be required for some tools.");
66 eprintln!(" Run 'sync-ctl tools guide' for manual installation instructions.");
67 return Err(e);
68 }
69 }
70
71 Ok(())
72}
73
74fn handle_tools_verify(languages: Option<Vec<String>>, verbose: bool) -> crate::Result<()> {
75 let installer = ToolInstaller::new();
76
77 let langs_to_verify = get_languages_to_verify(languages);
79
80 println!("š Verifying vulnerability scanning tools...\n");
81
82 let mut all_working = true;
83
84 for language in &langs_to_verify {
85 let (tool_name, is_working) = get_tool_for_language(&installer, language);
86
87 print!(" {} {:?}: {}",
88 if is_working { "ā
" } else { "ā" },
89 language,
90 tool_name);
91
92 if is_working {
93 println!(" - working correctly");
94
95 if verbose {
96 print_version_info(tool_name);
97 }
98 } else {
99 println!(" - not working or missing");
100 all_working = false;
101 }
102 }
103
104 if all_working {
105 println!("\nā
All tools are working correctly!");
106 } else {
107 println!("\nā Some tools are missing or not working.");
108 println!(" Run 'sync-ctl tools install' to install missing tools.");
109 }
110
111 Ok(())
112}
113
114fn handle_tools_guide(languages: Option<Vec<String>>, platform: Option<String>) -> crate::Result<()> {
115 let target_platform = platform.unwrap_or_else(|| {
116 match std::env::consts::OS {
117 "macos" => "macOS".to_string(),
118 "linux" => "Linux".to_string(),
119 "windows" => "Windows".to_string(),
120 other => other.to_string(),
121 }
122 });
123
124 println!("š Vulnerability Scanning Tools Installation Guide");
125 println!("Platform: {}", target_platform);
126 println!("{}", "=".repeat(60));
127
128 let langs_to_show = get_languages_to_show(languages);
129
130 for language in &langs_to_show {
131 print_language_guide(language, &target_platform);
132 }
133
134 print_universal_scanners_info();
135 print_general_tips();
136
137 Ok(())
138}
139
140fn get_languages_to_check(languages: Option<Vec<String>>) -> Vec<Language> {
143 if let Some(lang_names) = languages {
144 lang_names.iter()
145 .filter_map(|name| Language::from_string(name))
146 .collect()
147 } else {
148 vec![
149 Language::Rust,
150 Language::JavaScript,
151 Language::TypeScript,
152 Language::Python,
153 Language::Go,
154 Language::Java,
155 Language::Kotlin,
156 ]
157 }
158}
159
160fn get_languages_to_install(languages: Option<Vec<String>>) -> Vec<Language> {
161 if let Some(lang_names) = languages {
162 lang_names.iter()
163 .filter_map(|name| Language::from_string(name))
164 .collect()
165 } else {
166 vec![
167 Language::Rust,
168 Language::JavaScript,
169 Language::TypeScript,
170 Language::Python,
171 Language::Go,
172 Language::Java,
173 ]
174 }
175}
176
177fn get_languages_to_verify(languages: Option<Vec<String>>) -> Vec<Language> {
178 if let Some(lang_names) = languages {
179 lang_names.iter()
180 .filter_map(|name| Language::from_string(name))
181 .collect()
182 } else {
183 vec![
184 Language::Rust,
185 Language::JavaScript,
186 Language::TypeScript,
187 Language::Python,
188 Language::Go,
189 Language::Java,
190 ]
191 }
192}
193
194fn get_languages_to_show(languages: Option<Vec<String>>) -> Vec<Language> {
195 if let Some(lang_names) = languages {
196 lang_names.iter()
197 .filter_map(|name| Language::from_string(name))
198 .collect()
199 } else {
200 vec![
201 Language::Rust,
202 Language::JavaScript,
203 Language::TypeScript,
204 Language::Python,
205 Language::Go,
206 Language::Java,
207 ]
208 }
209}
210
211fn get_tool_for_language<'a>(installer: &ToolInstaller, language: &Language) -> (&'a str, bool) {
212 match language {
213 Language::Rust => ("cargo-audit", installer.test_tool_availability("cargo-audit")),
214 Language::JavaScript | Language::TypeScript => ("npm", installer.test_tool_availability("npm")),
215 Language::Python => ("pip-audit", installer.test_tool_availability("pip-audit")),
216 Language::Go => ("govulncheck", installer.test_tool_availability("govulncheck")),
217 Language::Java | Language::Kotlin => ("grype", installer.test_tool_availability("grype")),
218 _ => ("unknown", false),
219 }
220}
221
222fn display_status_table(installer: &ToolInstaller, langs_to_check: &[Language]) -> crate::Result<()> {
223 let mut stdout = StandardStream::stdout(ColorChoice::Always);
224
225 println!("š Vulnerability Scanning Tools Status");
226 println!("{}", "=".repeat(50));
227
228 for language in langs_to_check {
229 let (tool_name, is_available) = get_tool_for_language(installer, language);
230
231 if tool_name == "unknown" {
232 continue;
233 }
234
235 print!(" {} {:?}: ",
236 if is_available { "ā
" } else { "ā" },
237 language);
238
239 if is_available {
240 stdout.set_color(ColorSpec::new().set_fg(Some(Color::Green)))?;
241 print!("{} installed", tool_name);
242 } else {
243 stdout.set_color(ColorSpec::new().set_fg(Some(Color::Red)))?;
244 print!("{} missing", tool_name);
245 }
246
247 stdout.reset()?;
248 println!();
249 }
250
251 println!("\nš Universal Scanners:");
253 let grype_available = installer.test_tool_availability("grype");
254 print!(" {} Grype: ", if grype_available { "ā
" } else { "ā" });
255 if grype_available {
256 stdout.set_color(ColorSpec::new().set_fg(Some(Color::Green)))?;
257 println!("installed");
258 } else {
259 stdout.set_color(ColorSpec::new().set_fg(Some(Color::Red)))?;
260 println!("missing");
261 }
262 stdout.reset()?;
263
264 Ok(())
265}
266
267fn display_status_json(installer: &ToolInstaller, langs_to_check: &[Language]) {
268 let mut status = HashMap::new();
269
270 for language in langs_to_check {
271 let (tool_name, is_available) = get_tool_for_language(installer, language);
272
273 if tool_name == "unknown" {
274 continue;
275 }
276
277 status.insert(format!("{:?}", language), serde_json::json!({
278 "tool": tool_name,
279 "available": is_available
280 }));
281 }
282
283 println!("{}", serde_json::to_string_pretty(&status).unwrap());
284}
285
286fn handle_dry_run(installer: &ToolInstaller, langs_to_install: &[Language], include_owasp: bool) -> crate::Result<()> {
287 println!("š Dry run: Tools that would be installed:");
288 println!("{}", "=".repeat(50));
289
290 for language in langs_to_install {
291 let (tool_name, is_available) = get_tool_for_language(installer, language);
292
293 if tool_name == "unknown" {
294 continue;
295 }
296
297 if !is_available {
298 println!(" š¦ Would install {} for {:?}", tool_name, language);
299 } else {
300 println!(" ā
{} already installed for {:?}", tool_name, language);
301 }
302 }
303
304 if include_owasp && !installer.test_tool_availability("dependency-check") {
305 println!(" š¦ Would install OWASP Dependency Check (large download)");
306 }
307
308 Ok(())
309}
310
311fn confirm_installation() -> crate::Result<bool> {
312 use std::io::{self, Write};
313 print!("š§ Install missing vulnerability scanning tools? [y/N]: ");
314 io::stdout().flush()?;
315
316 let mut input = String::new();
317 io::stdin().read_line(&mut input)?;
318
319 Ok(input.trim().to_lowercase().starts_with('y'))
320}
321
322fn print_setup_instructions() {
323 println!("\nš” Setup Instructions:");
324 println!(" ⢠Add ~/.local/bin to your PATH for manually installed tools");
325 println!(" ⢠Add ~/go/bin to your PATH for Go tools");
326 println!(" ⢠Add to your shell profile (~/.bashrc, ~/.zshrc, etc.):");
327 println!(" export PATH=\"$HOME/.local/bin:$HOME/go/bin:$PATH\"");
328}
329
330fn print_version_info(tool_name: &str) {
331 use std::process::Command;
332 let version_result = match tool_name {
333 "cargo-audit" => Command::new("cargo").args(&["audit", "--version"]).output(),
334 "npm" => Command::new("npm").arg("--version").output(),
335 "pip-audit" => Command::new("pip-audit").arg("--version").output(),
336 "govulncheck" => Command::new("govulncheck").arg("-version").output(),
337 "grype" => Command::new("grype").arg("version").output(),
338 _ => return,
339 };
340
341 if let Ok(output) = version_result {
342 if output.status.success() {
343 let version = String::from_utf8_lossy(&output.stdout);
344 println!(" Version: {}", version.trim());
345 }
346 }
347}
348
349fn print_language_guide(language: &Language, target_platform: &str) {
350 match language {
351 Language::Rust => {
352 println!("\nš¦ Rust - cargo-audit");
353 println!(" Install: cargo install cargo-audit");
354 println!(" Usage: cargo audit");
355 }
356 Language::JavaScript | Language::TypeScript => {
357 println!("\nš JavaScript/TypeScript - npm audit");
358 println!(" Install: Download Node.js from https://nodejs.org/");
359 match target_platform {
360 "macOS" => println!(" Package manager: brew install node"),
361 "Linux" => println!(" Package manager: sudo apt install nodejs npm (Ubuntu/Debian)"),
362 _ => {}
363 }
364 println!(" Usage: npm audit");
365 }
366 Language::Python => {
367 println!("\nš Python - pip-audit");
368 println!(" Install: pipx install pip-audit (recommended)");
369 println!(" Alternative: pip3 install --user pip-audit");
370 println!(" Also available: safety (pip install safety)");
371 println!(" Usage: pip-audit");
372 }
373 Language::Go => {
374 println!("\nš¹ Go - govulncheck");
375 println!(" Install: go install golang.org/x/vuln/cmd/govulncheck@latest");
376 println!(" Note: Make sure ~/go/bin is in your PATH");
377 println!(" Usage: govulncheck ./...");
378 }
379 Language::Java => {
380 println!("\nā Java - Multiple options");
381 println!(" Grype (recommended):");
382 match target_platform {
383 "macOS" => println!(" Install: brew install anchore/grype/grype"),
384 "Linux" => println!(" Install: Download from https://github.com/anchore/grype/releases"),
385 _ => println!(" Install: Download from https://github.com/anchore/grype/releases"),
386 }
387 println!(" Usage: grype .");
388 println!(" OWASP Dependency Check:");
389 match target_platform {
390 "macOS" => println!(" Install: brew install dependency-check"),
391 _ => println!(" Install: Download from https://github.com/jeremylong/DependencyCheck/releases"),
392 }
393 println!(" Usage: dependency-check --project myproject --scan .");
394 }
395 _ => {}
396 }
397}
398
399fn print_universal_scanners_info() {
400 println!("\nš Universal Scanners:");
401 println!(" Grype: Works with multiple ecosystems");
402 println!(" Trivy: Container and filesystem scanning");
403 println!(" Snyk: Commercial solution with free tier");
404}
405
406fn print_general_tips() {
407 println!("\nš” Tips:");
408 println!(" ⢠Run 'sync-ctl tools status' to check current installation");
409 println!(" ⢠Run 'sync-ctl tools install' for automatic installation");
410 println!(" ⢠Add tool directories to your PATH for easier access");
411}