From d02e0808837227cfeccb3c056ee4a2360de7a0a4 Mon Sep 17 00:00:00 2001 From: Gabriel Rondon Date: Mon, 23 Mar 2026 23:30:15 +0000 Subject: [PATCH] Fix SkipWhitespace returning early EOF on whitespace-only chunks When a read() call returns a buffer containing only whitespace bytes, SkipWhitespace returns Ok(0) which the Read trait interprets as EOF. This causes silent truncation of base64 input containing whitespace (e.g., newlines from terminal paste or file formatting). Fix by looping until at least one non-whitespace byte is found or the inner reader returns true EOF. Closes #2431 --- cmd/soroban-cli/src/commands/tx/xdr.rs | 73 +++++++++++++++++++++++--- 1 file changed, 65 insertions(+), 8 deletions(-) diff --git a/cmd/soroban-cli/src/commands/tx/xdr.rs b/cmd/soroban-cli/src/commands/tx/xdr.rs index f9d0ef0c63..ead4fe89e6 100644 --- a/cmd/soroban-cli/src/commands/tx/xdr.rs +++ b/cmd/soroban-cli/src/commands/tx/xdr.rs @@ -54,20 +54,77 @@ impl SkipWhitespace { impl Read for SkipWhitespace { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { - let n = self.inner.read(buf)?; + loop { + let n = self.inner.read(buf)?; + if n == 0 { + return Ok(0); + } + + let mut written = 0; + for read in 0..n { + if !buf[read].is_ascii_whitespace() { + buf[written] = buf[read]; + written += 1; + } + } - let mut written = 0; - for read in 0..n { - if !buf[read].is_ascii_whitespace() { - buf[written] = buf[read]; - written += 1; + if written > 0 { + return Ok(written); } } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io::Cursor; + + #[test] + fn skip_whitespace_preserves_content() { + let input = Cursor::new(b"helloworld"); + let mut reader = SkipWhitespace::new(input); + let mut result = String::new(); + reader.read_to_string(&mut result).unwrap(); + assert_eq!(result, "helloworld"); + } + + #[test] + fn skip_whitespace_strips_all_whitespace_types() { + let input = Cursor::new(b"hello \t\n\r world"); + let mut reader = SkipWhitespace::new(input); + let mut result = String::new(); + reader.read_to_string(&mut result).unwrap(); + assert_eq!(result, "helloworld"); + } + + #[test] + fn skip_whitespace_handles_only_whitespace() { + let input = Cursor::new(b"\n \t \r\n"); + let mut reader = SkipWhitespace::new(input); + let mut result = String::new(); + reader.read_to_string(&mut result).unwrap(); + assert_eq!(result, ""); + } + + #[test] + fn skip_whitespace_handles_empty_input() { + let input = Cursor::new(b""); + let mut reader = SkipWhitespace::new(input); + let mut result = String::new(); + reader.read_to_string(&mut result).unwrap(); + assert_eq!(result, ""); + } - Ok(written) + #[test] + fn skip_whitespace_handles_leading_trailing_whitespace() { + let input = Cursor::new(b"\n\nhello\n\n"); + let mut reader = SkipWhitespace::new(input); + let mut result = String::new(); + reader.read_to_string(&mut result).unwrap(); + assert_eq!(result, "hello"); } } -// pub fn unwrap_envelope_v1(tx_env: TransactionEnvelope) -> Result { let TransactionEnvelope::Tx(TransactionV1Envelope { tx, .. }) = tx_env else {