]>
code.octet-stream.net Git - hashgood/blob - src/main.rs
2 use std
::path
::{Path
, PathBuf
};
4 use structopt
::StructOpt
;
6 /// Calculate digests for given input data
9 /// Display output nicely in the terminal
12 /// Collect candidate hashes based on options and match them against a calculated hash
15 /// Problem running the program
16 const EXIT_ERR
: i32 = 1;
17 /// Verification was performed and was not a match
18 const EXIT_MISMATCH
: i32 = 2;
21 #[structopt(name = "hashgood")]
23 /// Read the hash from the clipboard
24 #[cfg(feature = "paste")]
25 #[structopt(short = "p", long = "paste")]
28 /// Disable ANSI colours in output
29 #[structopt(short = "C", long = "no-colour")]
32 /// A file containing the hash to verify. It can either be a raw hash or a SHASUMS-style listing. Use `-` for standard input.
33 #[structopt(short = "c", long = "check", parse(from_os_str))]
34 hash_file
: Option
<PathBuf
>,
36 /// The file to be verified or `-` for standard input
37 #[structopt(name = "input", parse(from_os_str))]
40 /// A hash to verify, supplied directly on the command line
41 #[structopt(name = "hash")]
46 fn get_paste(&self) -> bool
{
47 #[cfg(feature = "paste")]
51 #[cfg(not(feature = "paste"))]
58 /// Types of supported digest algorithm
59 #[derive(Debug, PartialEq, Copy, Clone)]
67 /// Assume a hash type from the binary length. Fortunately the typical 3 algorithms we care about are different lengths.
68 pub fn from_len(len
: usize) -> Result
<Algorithm
, String
> {
70 16 => Ok(Algorithm
::Md5
),
71 20 => Ok(Algorithm
::Sha1
),
72 32 => Ok(Algorithm
::Sha256
),
73 _
=> Err(format
!("Unrecognised hash length: {} bytes", len
)),
78 /// The method by which one or more hashes were supplied to verify the calculated digest
79 #[derive(Debug, PartialEq)]
80 pub enum VerificationSource
{
87 /// A complete standalone hash result
95 pub fn new(alg
: Algorithm
, bytes
: Vec
<u8>, path
: &Path
) -> Self {
96 // Taking the filename component should always work?
97 // If not, just fall back to the full path
98 let filename
= match path
.file
_name
() {
99 Some(filename
) => filename
.to_string_lossy(),
100 None
=> path
.to_string_lossy(),
105 filename
: filename
.to_string(),
110 /// A possible hash to match against. The algorithm is assumed.
111 #[derive(Debug, PartialEq)]
112 pub struct CandidateHash
{
114 filename
: Option
<String
>,
117 /// A list of candidate hashes that our input could potentially match. At this point it is
118 /// assumed that we will be verifying a digest of a particular, single algorithm.
119 #[derive(Debug, PartialEq)]
120 pub struct CandidateHashes
{
122 hashes
: Vec
<CandidateHash
>,
123 source
: VerificationSource
,
126 /// Summary of an atetmpt to match the calculated digest against candidates
128 pub enum MatchLevel
{
134 /// The severity of any informational messages to be printed before the final result
135 pub enum MessageLevel
{
141 /// Overall details of an attempt to match the calculated digest against candidates
142 pub struct Verification
<'a
> {
143 match_level
: MatchLevel
,
144 comparison_hash
: Option
<&'a CandidateHash
>,
145 messages
: Vec
<(MessageLevel
, String
)>,
148 /// Entry point - run the program and handle errors ourselves cleanly.
150 /// At the moment there aren't really any errors that can be handled by the application. Therefore
151 /// stringly-typed errors are used and they are all captured here, where the problem is printed
152 /// and the application terminates with a non-zero return code.
154 hashgood().unwrap
_or
_else
(|e
| {
155 eprintln
!("Error: {}", e
);
156 process
::exit(EXIT_ERR
);
160 /// Main application logic
161 fn hashgood() -> Result
<(), Box
<dyn Error
>> {
162 let opt
= get_verified_options()?
;
163 let candidates
= verify
::get_candidate_hashes(&opt
)?
;
164 let input
= calculate
::get_input_reader(opt
.inp
ut
.as_path())?
;
165 if let Some(c
) = candidates
{
166 // If we have a candidate hash of a particular type, use that specific algorithm
167 let hashes
= calculate
::create_digests(&[c
.alg
], input
)?
;
168 for (alg
, bytes
) in hashes
{
169 // Should always be true
171 let hash
= Hash
::new(alg
, bytes
, &opt
.inp
ut
);
172 let verification
= verify
::verify_hash(&hash
, &c
);
173 let successful_match
= verification
.match_level
== MatchLevel
::Ok
;
176 verification
.comparison_hash
,
180 display
::print_messages(verification
.messages
, opt
.no_colour
)?
;
181 display
::print_match_level(verification
.match_level
, opt
.no_colour
)?
;
182 if !successful_match
{
183 process
::exit(EXIT_MISMATCH
);
188 // If no candidate, calculate all three common digest types for output
189 let hashes
= calculate
::create_digests(
190 &[Algorithm
::Md5
, Algorithm
::Sha1
, Algorithm
::Sha256
],
193 for (alg
, bytes
) in hashes
{
197 filename
: opt
.inp
ut
.file
_name
().unwrap
().to_string_lossy().to_string(),
199 display
::print_hash(&hash
, None
, None
, opt
.no_colour
)?
;
205 /// Parse the command line options and check for ambiguous or inconsistent settings
206 fn get_verified_options() -> Result
<Opt
, String
> {
207 let opt
= Opt
::from_args();
209 opt
.hash
.is
_some
() as i32 + opt
.get_paste() as i32 + opt
.hash_file
.is
_some
() as i32;
210 if hash_methods
> 1 {
211 if opt
.hash
.is
_some
() {
212 eprintln
!("* specified as command line argument");
215 eprintln
!("* paste from clipboard (-p)")
217 if opt
.hash_file
.is
_some
() {
218 eprintln
!("* check hash from file (-c)")
220 return Err("Error: Hashes were provided by multiple methods. Use only one.".to_owned());
222 if opt
.inp
ut
.to_str() == Some("-")
223 && opt
.hash_file
.as_ref().and_then(|h
| h
.to_str()) == Some("-")
225 return Err("Error: Cannot use use stdin for both hash file and input data".to_owned());