rsign2 0.6.6

A command-line tool to sign files and verify signatures.
use std::fs::{DirBuilder, File, OpenOptions};
use std::io::{BufReader, BufWriter};
#[cfg(unix)]
use std::os::unix::fs::OpenOptionsExt;
use std::path::Path;
use std::time::{SystemTime, UNIX_EPOCH};

use minisign::*;

pub fn open_data_file<P>(data_path: P) -> Result<BufReader<File>>
where
    P: AsRef<Path>,
{
    let data_path = data_path.as_ref();
    let file = OpenOptions::new()
        .read(true)
        .open(data_path)
        .map_err(|e| PError::new(ErrorKind::Io, e))?;

    Ok(BufReader::new(file))
}

pub fn create_dir<P>(path: P) -> Result<()>
where
    P: AsRef<Path>,
{
    DirBuilder::new()
        .recursive(true)
        .create(&path)
        .map_err(|e| {
            PError::new(
                ErrorKind::Io,
                format!("while creating: {} - {}", path.as_ref().display(), e),
            )
        })?;
    Ok(())
}

#[cfg(not(unix))]
pub fn create_file<P>(path: P, _mode: u32) -> Result<BufWriter<File>>
where
    P: AsRef<Path>,
{
    let path = path.as_ref();
    let file = OpenOptions::new()
        .write(true)
        .create_new(true)
        .open(path)
        .map_err(|e| {
            PError::new(
                ErrorKind::Io,
                format!("while creating: {} - {}", path.display(), e),
            )
        })?;
    Ok(BufWriter::new(file))
}

#[cfg(unix)]
pub fn create_file<P>(path: P, mode: u32) -> Result<BufWriter<File>>
where
    P: AsRef<Path>,
{
    let path = path.as_ref();
    let file = OpenOptions::new()
        .mode(mode)
        .write(true)
        .create_new(true)
        .open(path)
        .map_err(|e| {
            PError::new(
                ErrorKind::Io,
                format!("while creating: {} - {}", path.display(), e),
            )
        })?;
    Ok(BufWriter::new(file))
}

pub fn create_sig_file<P>(path: P) -> Result<BufWriter<File>>
where
    P: AsRef<Path>,
{
    let path = path.as_ref();
    let file = OpenOptions::new()
        .write(true)
        .create(true)
        .truncate(true)
        .open(path)
        .map_err(|e| {
            PError::new(
                ErrorKind::Io,
                format!("while creating: {} - {}", path.display(), e),
            )
        })?;
    Ok(BufWriter::new(file))
}

pub fn unix_timestamp() -> u64 {
    let start = SystemTime::now();
    let since_the_epoch = start
        .duration_since(UNIX_EPOCH)
        .expect("system clock is incorrect");
    since_the_epoch.as_secs()
}

pub fn is_printable(s: &str) -> bool {
    for c in s.chars() {
        match c {
            '\t' => continue,
            '\x00'..='\x1f' | '\x7f'..='\u{9f}' => return false,
            _ => continue,
        }
    }
    true
}

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

    #[test]
    fn test_printable_normal_text() {
        assert!(is_printable("hello world"));
        assert!(is_printable("timestamp:1234567890"));
    }

    #[test]
    fn test_printable_rejects_escape_sequence() {
        assert!(!is_printable("\x1b[31mred\x1b[0m"));
    }

    #[test]
    fn test_printable_rejects_control_characters() {
        assert!(!is_printable("\x00"));
        assert!(!is_printable("a\x07b"));
        assert!(!is_printable("\x7f"));
    }

    #[test]
    fn test_printable_rejects_newlines() {
        assert!(!is_printable("line1\nline2"));
        assert!(!is_printable("line1\r\nline2"));
    }

    #[test]
    fn test_printable_allows_tab() {
        assert!(is_printable("a\tb"));
    }

    #[test]
    fn test_printable_allows_unicode() {
        assert!(is_printable("héllo 世界"));
    }

    #[test]
    fn test_printable_rejects_c1_control() {
        assert!(!is_printable("\u{80}"));
        assert!(!is_printable("\u{9f}"));
    }
}