-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathproject.rs
More file actions
101 lines (87 loc) · 2.77 KB
/
project.rs
File metadata and controls
101 lines (87 loc) · 2.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
//! # `lsec`
//!
//! Laravel Security Audit CLI for scanning Laravel applications for
//! common security issues, insecure patterns, and risky configuration.
//!
//! (c) 2026 Afaan Bilal <https://afaan.dev>
//!
use std::fs;
use std::path::{Path, PathBuf};
use walkdir::WalkDir;
use crate::config::Config;
#[derive(Debug, Clone)]
pub struct ProjectFile {
pub path: PathBuf,
pub relative_path: String,
pub content: String,
}
#[derive(Debug, Clone)]
pub struct Project {
pub files: Vec<ProjectFile>,
}
impl Project {
pub fn load(root: &Path, config: &Config) -> Result<Self, Box<dyn std::error::Error>> {
let mut files = Vec::new();
for entry in WalkDir::new(root).into_iter().filter_map(Result::ok) {
let path = entry.path();
if !entry.file_type().is_file() || is_excluded(root, path, &config.scan.exclude_paths) {
continue;
}
let Ok(bytes) = fs::read(path) else {
continue;
};
if bytes.contains(&0) {
continue;
}
let Ok(content) = String::from_utf8(bytes) else {
continue;
};
files.push(ProjectFile {
path: path.to_path_buf(),
relative_path: relative(root, path),
content,
});
}
Ok(Self { files })
}
pub fn find_file(&self, relative_path: &str) -> Option<&ProjectFile> {
self.files
.iter()
.find(|file| file.relative_path == relative_path)
}
pub fn files_with_extension(&self, extension: &str) -> Vec<&ProjectFile> {
self.files
.iter()
.filter(|file| file.path.extension().and_then(|ext| ext.to_str()) == Some(extension))
.collect()
}
pub fn files_under(&self, prefix: &str) -> Vec<&ProjectFile> {
self.files
.iter()
.filter(|file| file.relative_path.starts_with(prefix))
.collect()
}
#[cfg(test)]
pub fn from_test_files(files: &[(&str, &str)]) -> Self {
Self {
files: files
.iter()
.map(|(relative_path, content)| ProjectFile {
path: PathBuf::from(relative_path.replace('/', "\\")),
relative_path: (*relative_path).to_string(),
content: (*content).to_string(),
})
.collect(),
}
}
}
fn is_excluded(root: &Path, path: &Path, excludes: &[String]) -> bool {
let rel = relative(root, path);
excludes.iter().any(|prefix| rel.starts_with(prefix.trim()))
}
fn relative(root: &Path, path: &Path) -> String {
path.strip_prefix(root)
.unwrap_or(path)
.to_string_lossy()
.replace('\\', "/")
}