#clam-av #async-await #data-stream #tokio #async #client-send #clamd

clamav-client

ClamAV client library with optional Tokio and async-std support

23 releases (3 stable)

new 2.0.0 Dec 7, 2024
1.0.1 Dec 6, 2024
1.0.0 Nov 19, 2024
0.5.2 Oct 19, 2024
0.1.3 May 16, 2021

#1163 in Network programming

Download history 18/week @ 2024-08-26 58/week @ 2024-09-02 51/week @ 2024-09-09 24/week @ 2024-09-16 50/week @ 2024-09-23 67/week @ 2024-09-30 77/week @ 2024-10-07 180/week @ 2024-10-14 38/week @ 2024-10-21 106/week @ 2024-10-28 130/week @ 2024-11-04 153/week @ 2024-11-11 303/week @ 2024-11-18 71/week @ 2024-11-25 272/week @ 2024-12-02

806 downloads per month

MIT license

36KB
415 lines

Rust ClamAV Client

A simple ClamAV client to send files, in-memory data, and data streams to clamd for antivirus scanning.

It provides a synchronous API and asynchronous functions for both Tokio and async-std.

Check out the examples below, the integration tests, or the API docs for more information on how to use this library.

Build status Crates.io

Installation

Add this to your Cargo.toml:

[dependencies]
clamav-client = "2.0.0"

To use the async functions in clamav_client::tokio, add this to your Cargo.toml:

[dependencies]
clamav-client = { version = "2.0.0", features = ["tokio"] }

To scan Tokio streams, enable the tokio-stream feature instead and add this to your Cargo.toml:

[dependencies]
clamav-client = { version = "2.0.0", features = ["tokio-stream"] }

Support for async-std is also available by enabling the async-std feature:

[dependencies]
clamav-client = { version = "2.0.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" };

// Ping clamd to make sure the server is available and accepting TCP connections
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);

// Scan file for viruses
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);

// Scan in-memory data for viruses
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" };

    // Ping clamd asynchronously and await the result
    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);

    // Concurrently scan a file, a data buffer, and a file stream for viruses
    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 async-std

#[cfg(feature = "async-std")]
async_std::task::block_on(async {
    let clamd_tcp = clamav_client::async_std::Tcp{ host_address: "localhost:3310" };

    // Ping clamd asynchronously and await the result
    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);

    // Scan a file for viruses
    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.

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

Dependencies

~0–10MB
~115K SLoC