Rust ClamAV Client
A simple ClamAV client for sending files, in-memory data, and data streams to clamd for antivirus scanning. It provides both a synchronous API and asynchronous functions compatible with async-std, smol, and Tokio.
Check out the examples below, the integration tests, or the API documentation to learn how to use this library.
The integration tests run against a lightweight ClamAV daemon that's available as a separate GitHub Action.

Installation
Add this to your Cargo.toml:
[dependencies]
clamav-client = "2.2.0"
To use the async functions in clamav_client::tokio, add this to your Cargo.toml:
[dependencies]
clamav-client = { version = "2.2.0", features = ["tokio"] }
To scan Tokio streams, enable the tokio-stream feature and add this to your Cargo.toml:
[dependencies]
clamav-client = { version = "2.2.0", features = ["tokio-stream"] }
Support for smol is available since version 2.1.0 by enabling the smol feature:
[dependencies]
clamav-client = { version = "2.2.0", features = ["smol"] }
Support for async-std is still available, but async-std itself has been discontinued in favor of smol. You can enable it with the async-std feature:
[dependencies]
clamav-client = { version = "2.2.0", features = ["async-std"] }
Migrations
Migrate to 1.x
The *_socket and *_tcp functions were deprecated in version 0.5.0 and have been removed in version 1.0.0.
Migrate to 0.5.x
The *_socket and *_tcp functions have been deprecated in favor of more general functions with the same name, but without the suffixes. These updated functions, such as ping, scan_buffer, and scan_file, now have the connection type (TCP or Unix socket) as a parameter, effectively replacing the host_address and socket_path parameters.
For example,
let clamd_host_address = "localhost:3310";
let result = clamav_client::scan_file_tcp("README.md", clamd_host_address, None);
assert!(result.is_ok());
becomes:
let clamd_tcp = clamav_client::Tcp{ host_address: "localhost:3310" };
let result = clamav_client::scan_file("README.md", clamd_tcp, None);
assert!(result.is_ok());
Examples
Usage
let clamd_tcp = clamav_client::Tcp{ host_address: "localhost:3310" };
let clamd_available = match clamav_client::ping(clamd_tcp) {
Ok(ping_response) => ping_response == clamav_client::PONG,
Err(_) => false,
};
if !clamd_available {
println!("Cannot ping clamd at {}", clamd_tcp.host_address);
return;
}
assert!(clamd_available);
let file_path = "tests/data/eicar.txt";
let scan_file_response = clamav_client::scan_file(file_path, clamd_tcp, None).unwrap();
let file_clean = clamav_client::clean(&scan_file_response).unwrap();
if file_clean {
println!("No virus found in {}", file_path);
} else {
println!("The file {} is infected!", file_path);
}
assert!(!file_clean);
let buffer = br#"X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*"#;
let scan_buffer_response = clamav_client::scan_buffer(buffer, clamd_tcp, None).unwrap();
let data_clean = clamav_client::clean(&scan_buffer_response).unwrap();
if data_clean {
println!("No virus found");
} else {
println!("The data is infected!");
}
assert!(!data_clean);
Usage - Async with tokio
#[cfg(feature = "tokio-stream")]
tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
let clamd_tcp = clamav_client::tokio::Tcp{ host_address: "localhost:3310" };
let clamd_available = match clamav_client::tokio::ping(clamd_tcp).await {
Ok(ping_response) => ping_response == clamav_client::PONG,
Err(_) => false,
};
if !clamd_available {
println!("Cannot ping clamd at {}", clamd_tcp.host_address);
return;
}
assert!(clamd_available);
let file_path = "tests/data/eicar.txt";
let buffer = br#"X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*"#;
let file = tokio::fs::File::open(file_path).await.unwrap();
let stream = tokio_util::io::ReaderStream::new(file);
let (scan_file_result, scan_buffer_result, scan_stream_result) = tokio::join!(
clamav_client::tokio::scan_file(file_path, clamd_tcp, None),
clamav_client::tokio::scan_buffer(buffer, clamd_tcp, None),
clamav_client::tokio::scan_stream(stream, clamd_tcp, None)
);
let scan_file_response = scan_file_result.unwrap();
let file_clean = clamav_client::clean(&scan_file_response).unwrap();
if file_clean {
println!("No virus found in {}", file_path);
} else {
println!("The file {} is infected!", file_path);
}
assert!(!file_clean);
let scan_buffer_response = scan_buffer_result.unwrap();
let data_clean = clamav_client::clean(&scan_buffer_response).unwrap();
if data_clean {
println!("No virus found");
} else {
println!("The data buffer is infected!");
}
assert!(!data_clean);
let scan_stream_response = scan_stream_result.unwrap();
let stream_clean = clamav_client::clean(&scan_stream_response).unwrap();
if stream_clean {
println!("No virus found");
} else {
println!("The file stream is infected!");
}
assert!(!stream_clean);
})
Usage - Async with smol
#[cfg(feature = "smol")]
smol::block_on(async {
let clamd_tcp = clamav_client::smol::Tcp{ host_address: "localhost:3310" };
let clamd_available = match clamav_client::smol::ping(clamd_tcp).await {
Ok(ping_response) => ping_response == clamav_client::PONG,
Err(_) => false,
};
if !clamd_available {
println!("Cannot ping clamd at {}", clamd_tcp.host_address);
return;
}
assert!(clamd_available);
let file_path = "tests/data/eicar.txt";
let scan_file_result = clamav_client::smol::scan_file(file_path, clamd_tcp, None).await;
let scan_file_response = scan_file_result.unwrap();
let file_clean = clamav_client::clean(&scan_file_response).unwrap();
if file_clean {
println!("No virus found in {}", file_path);
} else {
println!("The file {} is infected!", file_path);
}
assert!(!file_clean);
})
Usage - Async with async-std
#[cfg(feature = "async-std")]
async_std::task::block_on(async {
let clamd_tcp = clamav_client::async_std::Tcp{ host_address: "localhost:3310" };
let clamd_available = match clamav_client::async_std::ping(clamd_tcp).await {
Ok(ping_response) => ping_response == clamav_client::PONG,
Err(_) => false,
};
if !clamd_available {
println!("Cannot ping clamd at {}", clamd_tcp.host_address);
return;
}
assert!(clamd_available);
let file_path = "tests/data/eicar.txt";
let scan_file_result = clamav_client::async_std::scan_file(file_path, clamd_tcp, None).await;
let scan_file_response = scan_file_result.unwrap();
let file_clean = clamav_client::clean(&scan_file_response).unwrap();
if file_clean {
println!("No virus found in {}", file_path);
} else {
println!("The file {} is infected!", file_path);
}
assert!(!file_clean);
})
More examples can be found in the tests.
Links
Development
Testing locally
For the tests to pass, you should start clamd as follows:
clamd -F --config-file=clamd/clamd.conf
and then run cargo test --all-features to cover all tests.
It doesn't really matter how you start clamd, as long as the options from clamd.conf are the same for your configuration.
Contributing
Contributions are welcome!
Contributors