use crate::{engine::ScanEvent, layer_attr::LayerAttributes, ContentHandle, EngineError};
use clamav_sys::cl_error_t;
use std::{
ffi::CStr,
io::Cursor,
os::raw::{c_char, c_int, c_uchar, c_void},
pin::Pin,
};
use tokio::io::AsyncRead;
type ShouldCopyFileBuffer = Box<dyn for<'a> Fn(u32, &'a str, Option<&'a str>, usize) -> bool>;
pub(crate) struct ScanCbContext {
pub(crate) sender: tokio::sync::mpsc::Sender<ScanEvent>,
pub(crate) should_copy_file_buffer: Option<ShouldCopyFileBuffer>,
}
impl ScanCbContext {
unsafe fn scanned_content(
&self,
file_buffer: *const c_char,
recursion_level: u32,
file_type: &str,
file_name: Option<&str>,
file_size: usize,
) -> Option<Pin<Box<dyn AsyncRead + Send>>> {
let Some(buffer) = file_buffer
.cast::<c_uchar>()
.as_ref()
.map(|buf| core::slice::from_raw_parts(buf, file_size))
else {
return None;
};
let Some(cb) = &self.should_copy_file_buffer else {
return None;
};
if cb(recursion_level, file_type, file_name, file_size) {
Some(Box::pin(Cursor::new(buffer.to_vec())) as ContentHandle)
} else {
None
}
}
}
#[derive(Debug)]
pub enum Progress<T, E> {
Update {
now_completed: usize,
total_items: usize,
},
Complete(Result<T, E>),
}
pub(crate) unsafe extern "C" fn progress(
total_items: usize,
now_completed: usize,
context: *mut c_void,
) -> cl_error_t {
if let Some(sender) = context
.cast::<tokio::sync::mpsc::Sender<Progress<(), EngineError>>>()
.as_ref()
{
let _ = sender.blocking_send(Progress::Update {
total_items,
now_completed,
});
}
cl_error_t::CL_SUCCESS
}
pub(crate) unsafe extern "C" fn engine_pre_scan(
fd: c_int,
type_: *const c_char,
context: *mut c_void,
) -> cl_error_t {
if let Some(cxt) = context.cast::<ScanCbContext>().as_ref() {
let file_type = CStr::from_ptr(type_).to_string_lossy();
let _ = cxt.sender.blocking_send(ScanEvent::PreScan {
file: dup_fd_to_file(fd),
file_type: file_type.into(),
});
}
cl_error_t::CL_CLEAN
}
pub(crate) unsafe extern "C" fn engine_post_scan(
fd: c_int,
result: c_int,
virname: *const c_char,
context: *mut c_void,
) -> cl_error_t {
if let Some(cxt) = context.cast::<ScanCbContext>().as_ref() {
let result = result as isize;
let match_name = if virname.is_null() {
String::from("<NULL>")
} else {
CStr::from_ptr(virname).to_string_lossy().into()
};
let _ = cxt.sender.blocking_send(ScanEvent::PostScan {
file: dup_fd_to_file(fd),
result,
match_name,
});
}
cl_error_t::CL_CLEAN
}
pub(crate) unsafe extern "C" fn engine_virus_found(
fd: c_int,
virname: *const c_char,
context: *mut c_void,
) {
if let Some(cxt) = context.cast::<ScanCbContext>().as_ref() {
let name = CStr::from_ptr(virname).to_string_lossy().into();
let _ = cxt.sender.blocking_send(ScanEvent::MatchFound {
file: dup_fd_to_file(fd),
name,
});
}
}
pub(crate) unsafe extern "C" fn engine_file_inspection(
_fd: c_int,
file_type: *const c_char,
c_ancestors: *mut *const c_char,
parent_file_size: usize,
file_name: *const c_char,
file_size: usize,
file_buffer: *const c_char,
recursion_level: u32,
layer_attributes: u32,
context: *mut c_void,
) -> cl_error_t {
let Some(cxt) = context.cast::<ScanCbContext>().as_ref() else {
return cl_error_t::CL_CLEAN;
};
let Some(file_type) = file_type
.as_ref()
.map(|p| CStr::from_ptr(p))
.map(CStr::to_string_lossy)
.map(|s| s.to_string())
else {
return cl_error_t::CL_CLEAN;
};
let file_name = file_name
.as_ref()
.map(|ptr| CStr::from_ptr(ptr))
.map(CStr::to_string_lossy)
.map(|s| s.to_string());
let scanned_content = cxt.scanned_content(
file_buffer,
recursion_level,
&file_type,
file_name.as_deref(),
file_size,
);
let _ = cxt.sender.blocking_send(ScanEvent::FileInspect {
content: scanned_content,
ancestors: build_ancestors(recursion_level, c_ancestors),
file_name,
file_size,
file_type,
layer_attrs: LayerAttributes::from_bits(layer_attributes).unwrap_or_default(),
parent_file_size,
recursion_level,
});
cl_error_t::CL_CLEAN
}
unsafe fn build_ancestors(
recursion_level: u32,
c_ancestors: *mut *const c_char,
) -> Vec<Option<String>> {
let mut ancestors = vec![];
if let Ok(recursion_level) = isize::try_from(recursion_level) {
if !c_ancestors.is_null() {
for i in 0..recursion_level {
let ancestor = *(c_ancestors.offset(i));
if ancestor.is_null() {
ancestors.push(None);
} else {
let ancestor = CStr::from_ptr(ancestor).to_string_lossy();
ancestors.push(Some(ancestor.into()));
}
}
}
}
ancestors
}
#[cfg(unix)]
fn dup_fd_to_file(fd: c_int) -> Option<std::fs::File> {
use std::os::unix::prelude::FromRawFd;
if fd == -1 {
None
} else {
let new_fd = unsafe { libc::dup(fd) };
if new_fd == -1 {
None
} else {
Some(unsafe { std::fs::File::from_raw_fd(new_fd) })
}
}
}
#[cfg(windows)]
fn dup_fd_to_file(fd: c_int) -> Option<File> {
None
}