Skip to content

Commit 7836e51

Browse files
committed
💥 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.
1 parent 0d62ace commit 7836e51

3 files changed

Lines changed: 48 additions & 5 deletions

File tree

cli/src/cli.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,10 @@ pub(crate) struct PasswordArgs {
163163
help = "Password of archive. If password is not given it's asked from the tty"
164164
)]
165165
pub(crate) password: Option<Option<String>>,
166-
#[arg(long, help = "Read password from specified file")]
166+
#[arg(
167+
long,
168+
help = "Read the password from the specified file. Only the first non-empty line is used, and trailing newlines are ignored."
169+
)]
167170
pub(crate) password_file: Option<PathBuf>,
168171
}
169172

cli/src/command.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,21 @@ pub(crate) mod strip;
2121
pub mod update;
2222
pub mod xattr;
2323

24-
use crate::cli::{CipherAlgorithmArgs, Cli, Commands, PasswordArgs};
25-
use std::{fs, io};
24+
use crate::{
25+
cli::{CipherAlgorithmArgs, Cli, Commands, PasswordArgs},
26+
utils::fs,
27+
};
28+
use std::io;
2629

2730
fn ask_password(args: PasswordArgs) -> io::Result<Option<Vec<u8>>> {
2831
if let Some(path) = args.password_file {
29-
return Ok(Some(fs::read(path)?));
32+
return match fs::read_first_non_empty_line(path)? {
33+
Some(password) => Ok(Some(password.into_bytes())),
34+
None => Err(io::Error::new(
35+
io::ErrorKind::InvalidData,
36+
"password is empty",
37+
)),
38+
};
3039
};
3140
Ok(match args.password {
3241
Some(Some(password)) => {

cli/src/utils/fs.rs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ pub(crate) use file_id::HardlinkResolver;
88
pub(crate) use nodump::is_nodump;
99
pub(crate) use owner::*;
1010
pub(crate) use pna::fs::*;
11-
use std::{fs, io, path::Path};
11+
use std::{
12+
fs,
13+
io::{self, BufRead},
14+
path::Path,
15+
};
1216

1317
pub(crate) fn is_pna<P: AsRef<Path>>(path: P) -> io::Result<bool> {
1418
let file = fs::File::open(path)?;
@@ -74,3 +78,30 @@ pub(crate) fn file_create(path: impl AsRef<Path>, overwrite: bool) -> io::Result
7478
fs::File::create_new(path)
7579
}
7680
}
81+
82+
/// Returns the first non-empty line from a file as UTF-8, if any.
83+
///
84+
/// Empty lines are skipped. The trailing line terminator (`\n` or `\r\n`)
85+
/// is not included in the returned string.
86+
///
87+
/// # Returns
88+
///
89+
/// - `Ok(Some(line))` if a non-empty line was found.
90+
/// - `Ok(None)` if the file contains no non-empty lines.
91+
/// - `Err(e)` if an I/O error occurs while opening or reading the file.
92+
///
93+
/// # Errors
94+
///
95+
/// Propagates any I/O error that occurs while opening or reading the file.
96+
#[inline]
97+
pub(crate) fn read_first_non_empty_line<P: AsRef<Path>>(path: P) -> io::Result<Option<String>> {
98+
let file = fs::File::open(path)?;
99+
let reader = io::BufReader::new(file);
100+
for line in reader.lines() {
101+
let line = line?;
102+
if !line.is_empty() {
103+
return Ok(Some(line));
104+
}
105+
}
106+
Ok(None)
107+
}

0 commit comments

Comments
 (0)