From f03b0e5034e33137fa1c35ffea2e02b77399a562 Mon Sep 17 00:00:00 2001 From: ChanTsune <41658782+ChanTsune@users.noreply.github.com> Date: Fri, 26 Sep 2025 12:08:44 +0900 Subject: [PATCH] :boom: Change --password-file behavior Previously, the entire file content was used as the password. Now, only the first non-empty line is read, and trailing newlines are ignored, to align with common tool behavior. --- cli/src/cli.rs | 6 +++++- cli/src/command.rs | 15 ++++++++++++--- cli/src/utils/fs.rs | 33 ++++++++++++++++++++++++++++++++- 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/cli/src/cli.rs b/cli/src/cli.rs index d5a1c4947..3eeeb2e2d 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -277,7 +277,11 @@ pub(crate) struct PasswordArgs { help = "Password of archive. If password is not given it's asked from the tty" )] pub(crate) password: Option>, - #[arg(long, value_name = "FILE", help = "Read password from specified file")] + #[arg( + long, + value_name = "FILE", + help = "Read the password from the specified file. Only the first non-empty line is used, and trailing newlines are ignored." + )] pub(crate) password_file: Option, } diff --git a/cli/src/command.rs b/cli/src/command.rs index c26c52ac1..1f697831b 100644 --- a/cli/src/command.rs +++ b/cli/src/command.rs @@ -22,12 +22,21 @@ pub(crate) mod strip; pub mod update; pub mod xattr; -use crate::cli::{CipherAlgorithmArgs, Cli, Commands, GlobalContext, PasswordArgs}; -use std::{fs, io}; +use crate::{ + cli::{CipherAlgorithmArgs, Cli, Commands, GlobalContext, PasswordArgs}, + utils::fs, +}; +use std::io; fn ask_password(args: PasswordArgs) -> io::Result>> { if let Some(path) = args.password_file { - return Ok(Some(fs::read(path)?)); + return match fs::read_first_non_empty_line(path)? { + Some(password) => Ok(Some(password.into_bytes())), + None => Err(io::Error::new( + io::ErrorKind::InvalidData, + "password is empty", + )), + }; }; Ok(match args.password { Some(Some(password)) => { diff --git a/cli/src/utils/fs.rs b/cli/src/utils/fs.rs index a3af0d54a..e6d5f6c17 100644 --- a/cli/src/utils/fs.rs +++ b/cli/src/utils/fs.rs @@ -8,7 +8,11 @@ pub(crate) use file_id::HardlinkResolver; pub(crate) use nodump::is_nodump; pub(crate) use owner::*; pub(crate) use pna::fs::*; -use std::{fs, io, path::Path}; +use std::{ + fs, + io::{self, BufRead}, + path::Path, +}; /// Returns the current process umask. /// @@ -162,3 +166,30 @@ pub(crate) fn file_create(path: impl AsRef, overwrite: bool) -> io::Result fs::File::create_new(path) } } + +/// Returns the first non-empty line from a file as UTF-8, if any. +/// +/// Empty lines are skipped. The trailing line terminator (`\n` or `\r\n`) +/// is not included in the returned string. +/// +/// # Returns +/// +/// - `Ok(Some(line))` if a non-empty line was found. +/// - `Ok(None)` if the file contains no non-empty lines. +/// - `Err(e)` if an I/O error occurs while opening or reading the file. +/// +/// # Errors +/// +/// Propagates any I/O error that occurs while opening or reading the file. +#[inline] +pub(crate) fn read_first_non_empty_line>(path: P) -> io::Result> { + let file = fs::File::open(path)?; + let reader = io::BufReader::new(file); + for line in reader.lines() { + let line = line?; + if !line.is_empty() { + return Ok(Some(line)); + } + } + Ok(None) +}