Skip to content

Commit e156407

Browse files
committed
Add: Add dependency filter functionality
This restructures the original feed-filter into separate files. Additionally a dependency filter is added. It does not filter based on builtin functions but based on given scipts and their dependencies.
1 parent 3314cb6 commit e156407

8 files changed

Lines changed: 871 additions & 553 deletions

File tree

rust/src/feed_filter/builtins.rs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// SPDX-FileCopyrightText: 2026 Greenbone AG
2+
//
3+
// SPDX-License-Identifier: GPL-2.0-or-later WITH x11vnc-openssl-exception
4+
5+
use scannerlib::nasl::nasl_std_functions;
6+
use scannerlib::nasl::syntax::grammar::Ast;
7+
use std::collections::{HashMap, HashSet};
8+
use std::fs;
9+
use std::path::PathBuf;
10+
11+
use crate::utils::iter_fn_calls;
12+
13+
#[derive(Eq, Hash, PartialEq, Clone)]
14+
pub enum BuiltinStatus {
15+
Used,
16+
Unused,
17+
Deprecated,
18+
}
19+
20+
/// This struct holds information about implemented and unimplemented built-in functions
21+
/// based on the documentation files and rust implementation of NASL. It also tracks
22+
/// functions that are present in the rust implementation but not documented.
23+
pub struct BuiltinFunctions {
24+
/// HashMap<function_name, (category, deprecated)>
25+
implemented: HashMap<String, (String, bool)>,
26+
/// HashMap<function_name, (category, deprecated)>
27+
unimplemented: HashMap<String, (String, bool)>,
28+
/// Set of undocumented functions
29+
pub undocumented: HashSet<String>,
30+
}
31+
32+
impl BuiltinFunctions {
33+
pub fn new(doc_path: PathBuf) -> Self {
34+
let mut implemented = HashMap::new();
35+
let mut unimplemented = HashMap::new();
36+
37+
let exec = nasl_std_functions();
38+
let mut implemented_funcs = exec.iter().map(|f| f.to_string()).collect::<HashSet<_>>();
39+
40+
for entry in fs::read_dir(doc_path).unwrap() {
41+
let entry = entry.unwrap();
42+
let category_path = entry.path();
43+
let category = category_path
44+
.file_name()
45+
.unwrap()
46+
.to_string_lossy()
47+
.into_owned();
48+
49+
if category_path.is_dir() {
50+
for func_entry in fs::read_dir(category_path).unwrap() {
51+
let func_path = func_entry.unwrap().path();
52+
let function = func_path
53+
.file_stem()
54+
.unwrap()
55+
.to_string_lossy()
56+
.into_owned();
57+
58+
if function == "index" {
59+
continue;
60+
}
61+
62+
let content = fs::read_to_string(func_path.clone())
63+
.unwrap()
64+
.to_ascii_lowercase();
65+
66+
let deprecated = content.contains("## deprecated");
67+
68+
if implemented_funcs.take(&function).is_some() {
69+
implemented.insert(function, (category.clone(), deprecated));
70+
} else {
71+
unimplemented.insert(function, (category.clone(), deprecated));
72+
}
73+
}
74+
}
75+
}
76+
Self {
77+
implemented,
78+
unimplemented,
79+
undocumented: implemented_funcs,
80+
}
81+
}
82+
83+
pub fn implemented(&self) -> &HashMap<String, (String, bool)> {
84+
&self.implemented
85+
}
86+
87+
pub fn unimplemented(&self) -> &HashMap<String, (String, bool)> {
88+
&self.unimplemented
89+
}
90+
91+
pub fn script_is_runnable(&self, ast: &Ast) -> bool {
92+
for call in iter_fn_calls(ast) {
93+
let function = call.fn_name.to_string();
94+
if let Some((_, deprecated)) = self.unimplemented().get(&function)
95+
&& !deprecated
96+
{
97+
return false;
98+
}
99+
}
100+
true
101+
}
102+
}

rust/src/feed_filter/dep_filter.rs

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// SPDX-FileCopyrightText: 2026 Greenbone AG
2+
//
3+
// SPDX-License-Identifier: GPL-2.0-or-later WITH x11vnc-openssl-exception
4+
5+
use std::collections::{HashMap, VecDeque};
6+
use std::path::{Path, PathBuf};
7+
8+
use scannerlib::nasl::Code;
9+
use scannerlib::nasl::syntax::Loader;
10+
11+
use crate::script::ScriptPath;
12+
use crate::utils::oid_from_ast;
13+
14+
/// Dependencies of a script.
15+
pub struct ScriptDepsInfo {
16+
/// include deps
17+
pub includes: Vec<ScriptPath>,
18+
/// script_dependencies deps
19+
pub script_dependencies: Vec<ScriptPath>,
20+
/// The OID of this script, if present.
21+
pub oid: Option<String>,
22+
}
23+
24+
impl ScriptDepsInfo {
25+
pub fn all_deps(&self) -> impl Iterator<Item = &ScriptPath> {
26+
self.includes.iter().chain(self.script_dependencies.iter())
27+
}
28+
}
29+
30+
/// Map from every script in the feed to its dependency info.
31+
pub type DepMap = HashMap<ScriptPath, ScriptDepsInfo>;
32+
33+
/// Reads the feed and builds the full dependency map.
34+
pub struct DepReader {
35+
feed_path: PathBuf,
36+
loader: Loader,
37+
}
38+
39+
impl DepReader {
40+
pub fn new(feed_path: PathBuf) -> Self {
41+
let loader = Loader::from_feed_path(&feed_path);
42+
Self { feed_path, loader }
43+
}
44+
45+
/// Read the given root scripts and all their dependencies recursively.
46+
///
47+
/// Only the files that are actually reachable from `roots` are read from
48+
/// disk; the rest of the feed is never touched.
49+
pub fn read_from_roots(&mut self, roots: &[ScriptPath]) -> DepMap {
50+
let mut map: DepMap = HashMap::default();
51+
let mut queue: VecDeque<ScriptPath> = roots.iter().cloned().collect();
52+
53+
while let Some(path) = queue.pop_front() {
54+
if map.contains_key(&path) {
55+
continue;
56+
}
57+
let full_path = self.feed_path.join(&path.0);
58+
match self.read_one(&full_path) {
59+
Some(info) => {
60+
for dep in info.all_deps() {
61+
if !map.contains_key(dep) {
62+
queue.push_back(dep.clone());
63+
}
64+
}
65+
map.insert(path, info);
66+
}
67+
None => {
68+
eprintln!("Warning: could not read {}", path.0);
69+
}
70+
}
71+
}
72+
73+
map
74+
}
75+
76+
fn read_one(&mut self, path: &Path) -> Option<ScriptDepsInfo> {
77+
let code = Code::load(&self.loader, path).ok()?;
78+
let ast = code.parse().emit_errors().ok()?;
79+
Some(extract_deps(&ast, &self.feed_path))
80+
}
81+
}
82+
83+
fn extract_deps(ast: &scannerlib::nasl::syntax::grammar::Ast, feed_path: &Path) -> ScriptDepsInfo {
84+
let sp = search_path::SearchPath::from(feed_path.to_path_buf());
85+
86+
let includes = ast
87+
.iter_includes()
88+
.filter_map(|inc| {
89+
sp.find_file(&PathBuf::from(&inc.path))
90+
.map(|p| ScriptPath::new(feed_path, &p))
91+
})
92+
.collect();
93+
94+
let script_dependencies = ast
95+
.iter_fn_calls()
96+
.filter(|call| call.fn_name.to_string() == "script_dependencies")
97+
.flat_map(|call| {
98+
call.args.items.iter().filter_map(|arg| {
99+
let s = arg.to_string();
100+
// Args are double-quoted string literals: `"filename.nasl"`
101+
let name = if s.starts_with('"') && s.ends_with('"') && s.len() >= 2 {
102+
s[1..s.len() - 1].to_string()
103+
} else {
104+
return None;
105+
};
106+
sp.find_file(&PathBuf::from(&name))
107+
.map(|p| ScriptPath::new(feed_path, &p))
108+
})
109+
})
110+
.collect();
111+
112+
let oid = oid_from_ast(ast);
113+
114+
ScriptDepsInfo {
115+
includes,
116+
script_dependencies,
117+
oid,
118+
}
119+
}
120+
121+
pub fn resolve_script(feed_path: &Path, script: &Path) -> Option<ScriptPath> {
122+
if script.is_absolute() {
123+
pathdiff::diff_paths(script, feed_path)
124+
.map(|rel| ScriptPath(rel.to_string_lossy().into_owned()))
125+
} else {
126+
let full = feed_path.join(script);
127+
if full.exists() {
128+
Some(ScriptPath(script.to_string_lossy().into_owned()))
129+
} else {
130+
None
131+
}
132+
}
133+
}

rust/src/feed_filter/error.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@ use std::fmt;
77
#[derive(Debug)]
88
pub(crate) enum CliError {
99
Io(std::io::Error),
10+
Message(String),
1011
}
1112

1213
impl fmt::Display for CliError {
1314
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1415
match self {
1516
CliError::Io(error) => write!(f, "{error}"),
17+
CliError::Message(msg) => write!(f, "{msg}"),
1618
}
1719
}
1820
}
@@ -23,4 +25,10 @@ impl From<std::io::Error> for CliError {
2325
}
2426
}
2527

28+
impl From<&str> for CliError {
29+
fn from(value: &str) -> Self {
30+
Self::Message(value.to_string())
31+
}
32+
}
33+
2634
impl std::error::Error for CliError {}

0 commit comments

Comments
 (0)