From 64567c4819ff76cec66a74e87c9e47b1f6f9b609 Mon Sep 17 00:00:00 2001 From: lawrence3699 Date: Tue, 28 Apr 2026 01:11:06 +1000 Subject: [PATCH] Propagate initial input read errors --- CHANGELOG.md | 1 + src/input.rs | 44 +++++++++++++++++++++++++++++++++++--------- src/lessopen.rs | 4 ++-- 3 files changed, 38 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8eabb7d4..066b1161 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ - Syntax highlighting for Python files using uv as script runner in shebang #3689 (@janlarres) ## Bugfixes +- Report initial input read errors instead of treating them as empty input. Closes #3002, see #3706 (@lawrence3699) - Treat ZIP archives as binary content based on their magic header, see #3686 (@officialasishkumar) - Fix i686 `.deb` package using incorrect architecture name (`i686` instead of `i386`), preventing installation on Debian. Closes #3611, see #3650 (@Sim-hu) - Fix inconsistent `.deb` MUSL package names (aarch64-musl used `arm64` instead of `musl-linux-arm64`, and `musleabihf` target missed `bat-musl` prefix). Closes #3482, see #3642 (@mvanhorn) diff --git a/src/input.rs b/src/input.rs index 30f13f98..a3420123 100644 --- a/src/input.rs +++ b/src/input.rs @@ -207,7 +207,7 @@ impl<'a> Input<'a> { kind: OpenedInputKind::StdIn, description, metadata: self.metadata, - reader: InputReader::new(stdin), + reader: InputReader::try_new(stdin)?, }) } @@ -236,14 +236,14 @@ impl<'a> Input<'a> { file = input_identifier.into_inner().expect("The file was lost in the clircle::Identifier, this should not have happened..."); } - InputReader::new(BufReader::new(file)) + InputReader::try_new(BufReader::new(file))? }, }), InputKind::CustomReader(reader) => Ok(OpenedInput { description, kind: OpenedInputKind::CustomReader, metadata: self.metadata, - reader: InputReader::new(BufReader::new(reader)), + reader: InputReader::try_new(BufReader::new(reader))?, }), } } @@ -257,24 +257,29 @@ pub(crate) struct InputReader<'a> { } impl<'a> InputReader<'a> { - pub(crate) fn new(mut reader: R) -> InputReader<'a> { + #[cfg(test)] + pub(crate) fn new(reader: R) -> InputReader<'a> { + Self::try_new(reader).expect("reading the first line failed") + } + + pub(crate) fn try_new(mut reader: R) -> io::Result> { let mut first_line = vec![]; - reader.read_until(b'\n', &mut first_line).ok(); + reader.read_until(b'\n', &mut first_line)?; let content_type = inspect_content_type(&first_line); if content_type == Some(ContentType::UTF_16LE) { - read_utf16_line(&mut reader, &mut first_line, 0x00, 0x0A).ok(); + read_utf16_line(&mut reader, &mut first_line, 0x00, 0x0A)?; } else if content_type == Some(ContentType::UTF_16BE) { - read_utf16_line(&mut reader, &mut first_line, 0x0A, 0x00).ok(); + read_utf16_line(&mut reader, &mut first_line, 0x0A, 0x00)?; } - InputReader { + Ok(InputReader { inner: Box::new(reader), first_line, content_type, unbuffered: false, - } + }) } pub(crate) fn read_line(&mut self, buf: &mut Vec) -> io::Result { @@ -405,6 +410,27 @@ fn non_zip_pk_prefix_is_not_treated_as_binary() { ); } +#[test] +fn input_open_returns_initial_read_errors() { + struct FailingRead; + + impl Read for FailingRead { + fn read(&mut self, _buf: &mut [u8]) -> io::Result { + Err(io::Error::other("initial read failed")) + } + } + + let input = Input::from_reader(Box::new(FailingRead)); + let result = input.open(io::empty(), None); + + assert!(result.is_err()); + assert!(result + .err() + .unwrap() + .to_string() + .contains("initial read failed")); +} + #[test] fn utf16le() { let content = b"\xFF\xFE\x73\x00\x0A\x00\x64\x00"; diff --git a/src/lessopen.rs b/src/lessopen.rs index 966bc2c0..116206ba 100644 --- a/src/lessopen.rs +++ b/src/lessopen.rs @@ -155,7 +155,7 @@ impl LessOpenPreprocessor { Ok(OpenedInput { kind, - reader: InputReader::new(BufReader::new( + reader: InputReader::try_new(BufReader::new( if matches!(self.kind, LessOpenKind::TempFile) { let lessopen_string = match String::from_utf8(lessopen_stdout) { Ok(string) => string, @@ -192,7 +192,7 @@ impl LessOpenPreprocessor { .map(|s| s.replacen("%s", &path_str, 1).replacen("%s", "-", 1)), } }, - )), + ))?, metadata: input.metadata, description: input.description, })