shell-rs 0.2.6

Rust reimplementation of common coreutils APIs
Documentation
// Copyright (c) 2021 Xu Shaohua <shaohua@biofan.org>. All rights reserved.
// Use of this source is governed by Apache-2.0 License that can be found
// in the LICENSE file.

use sha2::Digest;
use std::fs::File;
use std::io::Read;
use std::path::Path;

use super::error::Error;

#[inline]
pub fn crc32<P: AsRef<Path>>(file: P) -> Result<String, Error> {
    let mut reader = File::open(&file)?;
    let mut hasher = crc32fast::Hasher::new();

    let mut buffer = Vec::with_capacity(16 * 1024);
    loop {
        let n_read = reader.read_to_end(&mut buffer)?;
        if n_read == 0 {
            break;
        }
        hasher.update(&buffer[..n_read]);
    }
    let checksum = hasher.finalize();
    Ok(checksum.to_string())
}

#[inline]
pub fn b2sum<P: AsRef<Path>>(file: P, options: &Options) -> Result<String, Error> {
    checksum(file, HashAlgo::B2, options)
}

#[inline]
pub fn md5sum<P: AsRef<Path>>(file: P, options: &Options) -> Result<String, Error> {
    checksum(file, HashAlgo::Md5, options)
}

#[inline]
pub fn sha1sum<P: AsRef<Path>>(file: P, options: &Options) -> Result<String, Error> {
    checksum(file, HashAlgo::Sha1, options)
}

#[inline]
pub fn sha224sum<P: AsRef<Path>>(file: P, options: &Options) -> Result<String, Error> {
    checksum(file, HashAlgo::Sha224, options)
}

#[inline]
pub fn sha256sum<P: AsRef<Path>>(file: P, options: &Options) -> Result<String, Error> {
    checksum(file, HashAlgo::Sha256, options)
}

#[inline]
pub fn sha384sum<P: AsRef<Path>>(file: P, options: &Options) -> Result<String, Error> {
    checksum(file, HashAlgo::Sha384, options)
}

#[inline]
pub fn sha512sum<P: AsRef<Path>>(file: P, options: &Options) -> Result<String, Error> {
    checksum(file, HashAlgo::Sha512, options)
}

#[derive(Debug)]
pub struct Options {
    /// Read in binary mode or text mode. On windows this is true.
    pub binary_mode: bool,
}

#[inline]
fn default_binary_mode() -> bool {
    cfg!(windows)
}

impl Default for Options {
    fn default() -> Self {
        Options {
            binary_mode: default_binary_mode(),
        }
    }
}

#[derive(Debug)]
pub struct CheckOptions {
    /// Read in binary mode or text mode. On windows this is true.
    pub binary_mode: bool,

    pub status: bool,
    pub quiet: bool,
    pub strict: bool,
    pub warn: bool,
}

impl Default for CheckOptions {
    fn default() -> Self {
        CheckOptions {
            binary_mode: default_binary_mode(),
            status: false,
            quiet: false,
            strict: false,
            warn: false,
        }
    }
}

/// Read MD5 sums from the FILEs and check them.
pub fn md5sum_check<P: AsRef<Path>>(_file: P, _option: &CheckOptions) -> Result<bool, Error> {
    Ok(false)
}

#[derive(Debug)]
enum HashAlgo {
    B2,
    Md5,
    Sha1,
    Sha224,
    Sha256,
    Sha384,
    Sha512,
}

trait FusionDigest {
    fn input(&mut self, input: &[u8]);
    fn result(&mut self, out: &mut [u8]);
    fn output_bits(&self) -> usize;
    fn output_bytes(&self) -> usize {
        (self.output_bits() + 7) / 8
    }
    fn result_str(&mut self) -> String {
        let mut buf: Vec<u8> = vec![0; self.output_bytes()];
        self.result(&mut buf);
        hex::encode(&buf)
    }
}

impl FusionDigest for blake2b_simd::State {
    fn input(&mut self, input: &[u8]) {
        self.update(input);
    }

    fn result(&mut self, output: &mut [u8]) {
        output.copy_from_slice(self.finalize().as_bytes());
    }

    fn output_bits(&self) -> usize {
        512
    }
}

impl FusionDigest for md5::Context {
    fn input(&mut self, input: &[u8]) {
        self.consume(input);
    }

    fn result(&mut self, output: &mut [u8]) {
        output.copy_from_slice(&*self.clone().compute());
    }

    fn output_bits(&self) -> usize {
        128
    }
}

impl FusionDigest for sha1::Sha1 {
    fn input(&mut self, input: &[u8]) {
        self.update(input);
    }

    fn result(&mut self, output: &mut [u8]) {
        output.copy_from_slice(&self.finalize_reset());
    }

    fn output_bits(&self) -> usize {
        160
    }
}

macro_rules! impl_digest_for_sha2 {
    ($type: ty, $bits: expr) => {
        impl FusionDigest for $type {
            fn input(&mut self, input: &[u8]) {
                self.update(input);
            }

            fn result(&mut self, output: &mut [u8]) {
                output.copy_from_slice(&self.clone().finalize());
            }

            fn output_bits(&self) -> usize {
                $bits
            }
        }
    };
}

impl_digest_for_sha2!(sha2::Sha224, 224);
impl_digest_for_sha2!(sha2::Sha256, 256);
impl_digest_for_sha2!(sha2::Sha384, 384);
impl_digest_for_sha2!(sha2::Sha512, 512);

fn digest_for_algo(algo: HashAlgo) -> Box<dyn FusionDigest> {
    match algo {
        HashAlgo::B2 => Box::new(blake2b_simd::State::new()),
        HashAlgo::Md5 => Box::new(md5::Context::new()),
        HashAlgo::Sha1 => Box::new(sha1::Sha1::new()),
        HashAlgo::Sha224 => Box::new(sha2::Sha224::new()),
        HashAlgo::Sha256 => Box::new(sha2::Sha256::new()),
        HashAlgo::Sha384 => Box::new(sha2::Sha384::new()),
        HashAlgo::Sha512 => Box::new(sha2::Sha512::new()),
    }
}

fn checksum<P: AsRef<Path>>(file: P, algo: HashAlgo, _options: &Options) -> Result<String, Error> {
    let mut digest = digest_for_algo(algo);
    let mut reader = File::open(&file)?;

    let mut buffer = Vec::with_capacity(16 * 1024);
    loop {
        let n_read = reader.read_to_end(&mut buffer)?;
        if n_read == 0 {
            break;
        }
        digest.input(&buffer[..n_read]);
    }

    Ok(digest.result_str())
}

#[cfg(test)]
mod tests {
    use super::*;

    const RUST_WIKIPEDIA: &str = "tests/Rust_Wikipedia.pdf";

    #[test]
    fn test_crc32() {
        let ret = crc32(RUST_WIKIPEDIA);
        assert!(ret.is_ok());
        let hex_str = ret.unwrap();
        assert_eq!(&hex_str, "2751965731");
    }

    #[test]
    fn test_b2sum() {
        let ret = b2sum(RUST_WIKIPEDIA, &Options::default());
        assert!(ret.is_ok());
        let hex_str = ret.unwrap();
        assert_eq!(&hex_str, "e5bd6bbb61edd4cc800ed4982a8226cf0f9582b717702ee5ff79fa286b0f6c70b0533d1f63661b50fa5e739dd78e74616955d7008d6f5c18715ee52235ef32a3");
    }

    #[test]
    fn test_md5sum() {
        let ret = md5sum(RUST_WIKIPEDIA, &Options::default());
        assert!(ret.is_ok());
        let hex_str = ret.unwrap();
        assert_eq!(&hex_str, "c7f5281f3a03cdd3a247966869cf0ba3");
    }

    #[test]
    fn test_sha1sum() {
        let ret = sha1sum(RUST_WIKIPEDIA, &Options::default());
        assert!(ret.is_ok());
        let hex_str = ret.unwrap();
        assert_eq!(&hex_str, "671c0b590385ac473ec3bd4d1c196363252d5d2b");
    }

    #[test]
    fn test_sha224sum() {
        let ret = sha224sum(RUST_WIKIPEDIA, &Options::default());
        assert!(ret.is_ok());
        let hex_str = ret.unwrap();
        assert_eq!(
            &hex_str,
            "2cbb67e54546408c512cef08964bb49a3071452347ce1e0ced931a0d"
        );
    }

    #[test]
    fn test_sha256sum() {
        let ret = sha256sum(RUST_WIKIPEDIA, &Options::default());
        assert!(ret.is_ok());
        let hex_str = ret.unwrap();
        assert_eq!(
            &hex_str,
            "79e1819c944cdbe4c02b92647d63a9f9d18f6cc6f3795dcedecc685a8a4d476b"
        );
    }

    #[test]
    fn test_sha384sum() {
        let ret = sha384sum(RUST_WIKIPEDIA, &Options::default());
        assert!(ret.is_ok());
        let hex_str = ret.unwrap();
        assert_eq!(
            &hex_str,
            "e45720b17f461732d4787edd37e23f62149ffb05482fbf2201b885ea6d4f79afcbbd1134112e56d83a479b8fdc02d6cf"
        );
    }

    #[test]
    fn test_sha512sum() {
        let ret = sha512sum(RUST_WIKIPEDIA, &Options::default());
        assert!(ret.is_ok());
        let hex_str = ret.unwrap();
        assert_eq!(
            &hex_str,
            "91a731cb4e6d2f3e63eaff98a99ea0dbd7d3ad70c2e671f533dd98271d270f63356bf8dd154a73d539548a0aed1dcfc69f499ed72d91655f451bb029bbd26c82"
        );
    }
}