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), Text(String), }
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 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 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 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(); 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 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 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 }