From 655013f86e5d34b349110a355573c61e9bd8d645 Mon Sep 17 00:00:00 2001 From: ddidderr Date: Fri, 12 Jun 2026 22:53:29 +0200 Subject: [PATCH] refactor: name the output/input paths passed to OutSink OutSink::open_with_options took two adjacent positional Option<&Path> parameters (output_file, input_file). The type system cannot tell them apart, so a future call site that swaps them still compiles - and the same-file-aliasing guard would then check the wrong path, silently re-enabling the accidental-overwrite protection bypass it exists to prevent. All three current call sites were correct; this hardens the signature before a wrong one appears. Introduce OutSinkPaths, a struct with named output_file/input_file fields, so every call site must label which path plays which role. No behavior change. Test plan: cargo clippy (default/--tests) clean; cargo test passes all 56 tests. --- src/crypto.rs | 18 ++++++++++++------ src/utils.rs | 17 +++++++++++++---- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/crypto.rs b/src/crypto.rs index 5def79a..4a72b70 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -179,8 +179,10 @@ pub fn encrypt( let plaintext_length = input.length; let mut f_plain = AheadReader::from(input.reader, chunk_sz); let mut f_encrypted = OutSink::open_with_options( - options.output_file.as_deref(), - options.input_file.as_deref(), + OutSinkPaths { + output_file: options.output_file.as_deref(), + input_file: options.input_file.as_deref(), + }, &options.output, )?; @@ -292,8 +294,10 @@ pub fn decrypt( let mut f_encrypted = AheadReader::from(reader, cipher_chunk); let mut f_plain = OutSink::open_with_options( - options.output_file.as_deref(), - options.input_file.as_deref(), + OutSinkPaths { + output_file: options.output_file.as_deref(), + input_file: options.input_file.as_deref(), + }, &options.output, )?; @@ -423,8 +427,10 @@ pub fn decrypt_range( let last_idx = n_chunks - 1; let mut out = OutSink::open_with_options( - options.output_file.as_deref(), - Some(&options.input_file), + OutSinkPaths { + output_file: options.output_file.as_deref(), + input_file: Some(&options.input_file), + }, &options.output, )?; diff --git a/src/utils.rs b/src/utils.rs index 89686c1..8e4585b 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -198,6 +198,16 @@ fn output_aliases_input(output: &Path, input: Option<&Path>) -> io::Result } } +/// Paths for [`OutSink::open_with_options`], named so the output and input +/// roles cannot be swapped silently at a call site (both are `Option<&Path>`). +pub(crate) struct OutSinkPaths<'a> { + /// Where the finished output is renamed to; `None` means stdout. + pub output_file: Option<&'a Path>, + /// The input being read; only consulted by the aliasing guard that + /// permits in-place overwrite of the input without `--force`. + pub input_file: Option<&'a Path>, +} + /// Output sink that supports atomic file replacement. /// /// For file outputs: bytes are written to a private, randomly named temp file. @@ -219,11 +229,10 @@ pub(crate) enum OutSink { impl OutSink { pub(crate) fn open_with_options( - output_file: Option<&Path>, - input_file: Option<&Path>, + paths: OutSinkPaths<'_>, options: &OutputOptions, ) -> io::Result { - match output_file { + match paths.output_file { None if options.buffer_verify_stdout => { let dir = temp_dir_for_stdout(options.temp_dir.as_deref()); Ok(Self::BufferVerify { @@ -235,7 +244,7 @@ impl OutSink { let final_path = f.to_path_buf(); if final_path.exists() && !options.force - && !output_aliases_input(&final_path, input_file)? + && !output_aliases_input(&final_path, paths.input_file)? { return Err(io::Error::new( io::ErrorKind::AlreadyExists,