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
// for nicer doc links
#[allow(unused_imports)]
use crate::ClamdClient;
use std::num::TryFromIntError;

use tracing::trace;

pub type Result<T> = std::result::Result<T, ClamdError>;

/// Errors that can occur when using [`ClamdClient`].
#[derive(Debug, thiserror::Error)]
pub enum ClamdError {
    /// Occurs when the custom set chunk size is larger than
    /// [`std::u32::MAX`].
    #[error("could not send chunk: too large: {0}")]
    ChunkSizeError(#[source] TryFromIntError),
    /// Occurs when the library cannot connect to the tcp or unix
    /// socket. Contains underlying [`std::io::Error`].
    #[error("could not connect to socket: {0}")]
    ConnectError(#[source] std::io::Error),
    /// Occurs when the clamd response is not valid Utf8.
    #[error("could not decode clamav response: {0}")]
    DecodingUtf8Error(#[source] std::string::FromUtf8Error),
    /// Occurs when there was an [`std::io::Error`] while transfering
    /// commands or data to/from clamd.
    #[error("could not decode / encode clamav response: {0}")]
    DecodingIoError(
        #[from]
        #[source]
        std::io::Error,
    ),

    /// Occurs when the response from clamd is not what the library
    /// expects. Contains the invalid response.
    #[error("invalid response from clamd: {0}")]
    InvalidResponse(String),
    /// Occurs when there should be a response from clamd but it just
    /// closed the connection without sending a response.
    #[error("no response from clamd")]
    NoResponse,
    /// Occurs when we expect a longer response from clamd, but it
    /// is somehow malformed. Contains the invalid response.
    #[error("incomplete response from clamd: {0}")]
    IncompleteResponse(String),
    /// Occurs when everything between this library and clamd went
    /// well but clamd seems to have found a virus signature. See also
    /// [`ClamdError::scan_error`].
    #[error("clamd returned error on scan, possible virus: {0}")]
    ScanError(String),
}

impl ClamdError {
    /// If you want to ignore any error but an actual malignent scan
    /// result from clamd. I do not recommend using this without careful thought, as any other error
    /// could hide that uploaded bytes are actually a virus.
    /// # Example
    /// ```rust
    /// # use std::net::SocketAddr;
    /// # use clamd_client::ClamdClientBuilder;
    /// # use eyre::Result;
    /// # async fn doc() -> eyre::Result<()> {
    /// let address = "127.0.0.1:3310".parse::<SocketAddr>()?;
    /// let mut clamd_client = ClamdClientBuilder::tcp_socket(&address).build();
    ///
    /// // This downloads a virus signature that is benign but trips clamd.
    /// let eicar_bytes = reqwest::get("https://secure.eicar.org/eicarcom2.zip")
    ///   .await?
    ///   .bytes()
    ///   .await?;
    ///
    /// let err = clamd_client.scan_bytes(&eicar_bytes).await.unwrap_err();
    /// let msg = err.scan_error().unwrap();
    /// println!("Eicar scan returned that its a virus: {}", msg);
    /// # Ok(())
    /// # }
    /// ```
    pub fn scan_error(self) -> Option<String> {
        match self {
            Self::ScanError(s) => Some(s),
            _ => {
                trace!("ignoring non-scan error {:?}", self);
                None
            }
        }
    }
}