ezcheck/
lib.rs

1#[cfg(not(any(
2    feature = "hashes_backend",
3    feature = "ring_backend",
4    feature = "mix_backend"
5)))]
6compile_error!("You must enable at least one of the features: 'hashes_backend', 'ring_backend' or 'mix_backend'.");
7#[cfg(any(
8    all(feature = "hashes_backend", feature = "ring_backend"),
9    all(feature = "hashes_backend", feature = "mix_backend"),
10    all(feature = "ring_backend", feature = "mix_backend"),
11    all(
12        feature = "hashes_backend",
13        feature = "ring_backend",
14        feature = "mix_backend"
15    )
16))]
17compile_error!(
18    "Only one of the features `hashes_backend`, `ring_backend`, or `mix_backend` can be enabled at a time."
19);
20
21pub mod calculator;
22pub mod extra;
23use std::fmt;
24use std::fs::File;
25use std::io::{stdin, BufRead, BufReader};
26
27pub struct Calculate {
28    data: Data,
29    algorithm: calculator::SupportedAlgorithm,
30}
31
32impl Calculate {
33    pub fn new(data: Data, algorithm: calculator::SupportedAlgorithm) -> Calculate {
34        Self { data, algorithm }
35    }
36}
37
38impl Calculate {
39    pub fn compute(&self) -> Result<String, String> {
40        self.data.compute_hash(self.algorithm)
41    }
42}
43
44pub struct Compare {
45    pub data: Data,
46    compare: String,
47    algorithm: calculator::SupportedAlgorithm,
48}
49
50impl Compare {
51    pub fn new(data: Data, compare: String, algorithm: calculator::SupportedAlgorithm) -> Compare {
52        Self {
53            data,
54            compare,
55            algorithm,
56        }
57    }
58}
59
60#[derive(Debug)]
61pub enum IfMatch {
62    Match(String),
63    Failed(String),
64}
65
66impl PartialEq for IfMatch {
67    fn eq(&self, other: &Self) -> bool {
68        match (self, other) {
69            (IfMatch::Match(_), IfMatch::Match(_)) => true,
70            (IfMatch::Failed(_), IfMatch::Failed(_)) => true,
71            _ => false,
72        }
73    }
74}
75
76impl Compare {
77    pub fn compute(&self) -> Result<IfMatch, String> {
78        let hash_result = match self.data.compute_hash(self.algorithm) {
79            Ok(hash_result) => hash_result,
80            Err(error) => return Err(error),
81        };
82
83        if hash_result == self.compare {
84            Ok(IfMatch::Match(format!("{:8} OK", self.algorithm)))
85        } else {
86            Ok(IfMatch::Failed(format!(
87                "{:8} FAILED  Current Hash:  {}",
88                self.algorithm, hash_result
89            )))
90        }
91    }
92}
93
94pub enum Data {
95    ReadFile(String), // Input data from a file
96    Text(String),     // Input data from user input
97}
98
99impl fmt::Display for Data {
100    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
101        let algorithm = match self {
102            Data::ReadFile(file_name) => file_name,
103            Data::Text(text) => text,
104        };
105        write!(f, "{}", algorithm)
106    }
107}
108
109pub trait ComputeHash {
110    fn compute_hash(&self, algorithm: calculator::SupportedAlgorithm) -> Result<String, String>;
111}
112
113impl ComputeHash for Data {
114    fn compute_hash(&self, algorithm: calculator::SupportedAlgorithm) -> Result<String, String> {
115        match self {
116            Data::ReadFile(path) => {
117                if path == "-" {
118                    // Input from standard input
119                    let stdin_lock = stdin().lock();
120                    match calculator::hash_calculator(stdin_lock, algorithm) {
121                        Ok(hash) => Ok(hash),
122                        Err(e) => Err(format!("Error: Error calculating hash: {}", e)),
123                    }
124                } else {
125                    // Input from file
126                    let file = match File::open(path) {
127                        Ok(file) => file,
128                        Err(e) => {
129                            return Err(format!("Error: Cannot opening file {}: {}", path, e))
130                        }
131                    };
132                    let reader = BufReader::new(file);
133                    match calculator::hash_calculator(reader, algorithm) {
134                        Ok(hash) => Ok(hash),
135                        Err(e) => Err(format!("Error: Error calculating hash: {}", e)),
136                    }
137                }
138            }
139            Data::Text(text) => {
140                let reader = BufReader::new(text.as_bytes());
141                match calculator::hash_calculator(reader, algorithm) {
142                    Ok(hash) => Ok(hash),
143                    Err(e) => Err(format!("Error: Error calculating hash: {}", e)),
144                }
145            }
146        }
147    }
148}
149
150#[cfg(any(feature = "mix_backend"))]
151pub fn match_algorithm<S: AsRef<str>>(
152    algorithm: S,
153) -> Result<calculator::SupportedAlgorithm, String> {
154    let algorithm = algorithm.as_ref().to_lowercase();
155    let algorithm = algorithm.as_ref();
156
157    match algorithm {
158        "md2" => Ok(calculator::SupportedAlgorithm::MD2),
159        "md4" => Ok(calculator::SupportedAlgorithm::MD4),
160        "md5" => Ok(calculator::SupportedAlgorithm::MD5),
161        "sha1" => Ok(calculator::SupportedAlgorithm::SHA1),
162        "sha224" => Ok(calculator::SupportedAlgorithm::SHA224),
163        "sha256" => Ok(calculator::SupportedAlgorithm::SHA256),
164        "sha384" => Ok(calculator::SupportedAlgorithm::SHA384),
165        "sha512" => Ok(calculator::SupportedAlgorithm::SHA512),
166        "sha512_256" | "sha512-256" | "sha512/256" => {
167            Ok(calculator::SupportedAlgorithm::SHA512_256)
168        }
169        "xxhash32" | "xxh32" => Ok(calculator::SupportedAlgorithm::XXHASH32),
170        "xxhash64" | "xxh64" => Ok(calculator::SupportedAlgorithm::XXHASH64),
171        "xxh3" | "xxh3_64" | "xxh3-64" | "xxh3/64" | "xxhash3_64" | "xxhash3-64" | "xxhash3/64" => {
172            Ok(calculator::SupportedAlgorithm::XXHASH3_64)
173        }
174        _ => Err(format!("Error: Unsupported algorithm: {}", algorithm)),
175    }
176}
177
178#[cfg(any(feature = "hashes_backend"))]
179pub fn match_algorithm<S: AsRef<str>>(
180    algorithm: S,
181) -> Result<calculator::SupportedAlgorithm, String> {
182    let algorithm = algorithm.as_ref().to_lowercase();
183    let algorithm = algorithm.as_ref();
184
185    match algorithm {
186        "md2" => Ok(calculator::SupportedAlgorithm::MD2),
187        "md4" => Ok(calculator::SupportedAlgorithm::MD4),
188        "md5" => Ok(calculator::SupportedAlgorithm::MD5),
189        "sha1" => Ok(calculator::SupportedAlgorithm::SHA1),
190        "sha224" => Ok(calculator::SupportedAlgorithm::SHA224),
191        "sha256" => Ok(calculator::SupportedAlgorithm::SHA256),
192        "sha384" => Ok(calculator::SupportedAlgorithm::SHA384),
193        "sha512" => Ok(calculator::SupportedAlgorithm::SHA512),
194        "sha512_256" | "sha512-256" | "sha512/256" => {
195            Ok(calculator::SupportedAlgorithm::SHA512_256)
196        }
197        _ => Err(format!("Error: Unsupported algorithm: {}", algorithm)),
198    }
199}
200
201#[cfg(feature = "ring_backend")]
202pub fn match_algorithm<S: AsRef<str>>(
203    algorithm: S,
204) -> Result<calculator::SupportedAlgorithm, String> {
205    let algorithm = algorithm.as_ref().to_lowercase();
206    let algorithm = algorithm.as_ref();
207
208    match algorithm {
209        "sha256" => Ok(calculator::SupportedAlgorithm::SHA256),
210        "sha384" => Ok(calculator::SupportedAlgorithm::SHA384),
211        "sha512" => Ok(calculator::SupportedAlgorithm::SHA512),
212        "sha512_256" | "sha512-256" | "sha512/256" => {
213            Ok(calculator::SupportedAlgorithm::SHA512_256)
214        }
215        _ => Err(format!("Error: Unsupported algorithm: {}", algorithm)),
216    }
217}
218
219pub fn phase_shasum_file<S: AsRef<str>>(
220    shasum_file_path: S,
221    algorithm: Option<calculator::SupportedAlgorithm>,
222) -> Result<Vec<Compare>, String> {
223    /*
224    Example shasum file:
225        ee1fb7719c31070f1fbdc8f2d32370c9d1ca6962  image.png
226        ee1fb7719c31070f1fbdc8f2d32370c9d1ca6962 *image.png
227                                                 ^ In binary mode, neglected.
228     */
229    let shasum_file_path = shasum_file_path.as_ref();
230    let mut detect_algorithm = true;
231    if algorithm.is_some() {
232        detect_algorithm = false;
233    }
234
235    let file = match File::open(shasum_file_path) {
236        Ok(file) => file,
237        Err(error) => {
238            return Err(format!(
239                "Error: Cannot opening file {}: {}",
240                shasum_file_path, error
241            ))
242        }
243    };
244    let reader = BufReader::new(file);
245
246    let mut compare_tasks = vec![];
247
248    for line in reader.lines() {
249        let line = line.unwrap();
250        let parts: Vec<&str> = line.split_whitespace().collect(); // Split
251
252        if parts.len() == 2 {
253            let hash = parts[0];
254            let mut file_path = parts[1].to_string();
255
256            let algorithms = match detect_algorithm {
257                true => match extra::detect_hash_algorithm(hash) {
258                    Ok(result) => result,
259                    Err(e) => return Err(format!("{} {}", e, hash)),
260                },
261                false => vec![algorithm.unwrap()],
262            };
263
264            if file_path.starts_with("*") {
265                // Neglect * starts with filename
266                file_path.remove(0);
267            }
268
269            for algorithm in algorithms {
270                compare_tasks.push(Compare::new(
271                    Data::ReadFile(file_path.clone()),
272                    hash.to_string(),
273                    algorithm,
274                ));
275            }
276        } else if parts.is_empty() {
277            // Blank line
278            continue;
279        } else {
280            return Err("Error: Not a valid shasum file.".to_string());
281        }
282    }
283    Ok(compare_tasks)
284}
285
286#[cfg(test)]
287mod test_core {
288    use super::{Calculate, Compare, Data};
289    use crate::calculator;
290    use crate::IfMatch::{Failed, Match};
291
292    #[test]
293    fn test_calculate_compute_hash_file() {
294        let task = Calculate::new(
295            Data::ReadFile(String::from("tests/滕王阁序.txt")),
296            calculator::SupportedAlgorithm::SHA256,
297        );
298        assert_eq!(
299            task.compute().unwrap(),
300            "00691413c731ee37f551bfaca6a34b8443b3e85d7c0816a6fe90aa8fc8eaec95"
301        );
302    }
303
304    #[test]
305    fn test_calculate_compute_hash_text() {
306        let task = Calculate::new(
307            Data::Text(String::from("Veni, vidi, vici")),
308            calculator::SupportedAlgorithm::SHA256,
309        );
310        assert_eq!(
311            task.compute().unwrap(),
312            "b1610284c94bbf9aa78333e57ddce234a5e845d61e09ce91a7e19fa24737f466"
313        );
314    }
315
316    #[test]
317    fn test_compare_hash_file() {
318        let task = Compare::new(
319            Data::ReadFile(String::from("tests/滕王阁序.txt")),
320            String::from("00691413c731ee37f551bfaca6a34b8443b3e85d7c0816a6fe90aa8fc8eaec95"),
321            calculator::SupportedAlgorithm::SHA256,
322        );
323        assert_eq!(task.compute().unwrap(), Match("".to_string()))
324    }
325
326    #[test]
327    fn test_compare_hash_text() {
328        let task = Compare::new(
329            Data::Text(String::from("Veni, vidi, vici")),
330            String::from("a1610284c94bbf9aa78333e57ddce234a5e845d61e09ce91a7e19fa24737f466"),
331            calculator::SupportedAlgorithm::SHA256,
332        );
333        assert_eq!(task.compute().unwrap(), Failed(String::from("")))
334    }
335
336    // This test is only available in tests dir.
337    // use crate::core::phase_shasum_file;
338    // #[test]
339    // fn test_phase_shasum_file() {
340    //     let mut tasks = phase_shasum_file("tests/sha256sum.txt", Option::from(calculator::SupportedAlgorithm::SHA256)).unwrap();
341    //     for task in tasks {
342    //         assert_eq!(
343    //             task.compute().unwrap(),
344    //             "SHA256 OK"
345    //         )
346    //     }
347    // }
348}