Skip to content

Commit a3b7bf7

Browse files
Fix SkipWhitespace returning early EOF on whitespace-only chunks (#2461)
1 parent 4848f9e commit a3b7bf7

1 file changed

Lines changed: 105 additions & 9 deletions

File tree

  • cmd/soroban-cli/src/commands/tx

cmd/soroban-cli/src/commands/tx/xdr.rs

Lines changed: 105 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -54,20 +54,26 @@ impl<R: Read> SkipWhitespace<R> {
5454

5555
impl<R: Read> Read for SkipWhitespace<R> {
5656
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
57-
let n = self.inner.read(buf)?;
57+
loop {
58+
let n = self.inner.read(buf)?;
59+
if n == 0 {
60+
return Ok(0);
61+
}
5862

59-
let mut written = 0;
60-
for read in 0..n {
61-
if !buf[read].is_ascii_whitespace() {
62-
buf[written] = buf[read];
63-
written += 1;
63+
let mut written = 0;
64+
for read in 0..n {
65+
if !buf[read].is_ascii_whitespace() {
66+
buf[written] = buf[read];
67+
written += 1;
68+
}
6469
}
65-
}
6670

67-
Ok(written)
71+
if written > 0 {
72+
return Ok(written);
73+
}
74+
}
6875
}
6976
}
70-
//
7177

7278
pub fn unwrap_envelope_v1(tx_env: TransactionEnvelope) -> Result<Transaction, Error> {
7379
let TransactionEnvelope::Tx(TransactionV1Envelope { tx, .. }) = tx_env else {
@@ -83,3 +89,93 @@ pub fn add_op(tx_env: TransactionEnvelope, op: Operation) -> Result<TransactionE
8389
tx.operations = ops.try_into().map_err(|_| Error::TooManyOperations)?;
8490
Ok(tx.into())
8591
}
92+
93+
#[cfg(test)]
94+
mod tests {
95+
use super::*;
96+
use std::io::Cursor;
97+
98+
struct ChunkedReader {
99+
chunks: Vec<Vec<u8>>,
100+
pos: usize,
101+
}
102+
103+
impl ChunkedReader {
104+
fn new(chunks: Vec<&[u8]>) -> Self {
105+
Self {
106+
chunks: chunks.iter().map(|c| c.to_vec()).collect(),
107+
pos: 0,
108+
}
109+
}
110+
}
111+
112+
impl Read for ChunkedReader {
113+
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
114+
if self.pos >= self.chunks.len() {
115+
return Ok(0);
116+
}
117+
let chunk = &self.chunks[self.pos];
118+
let n = chunk.len().min(buf.len());
119+
buf[..n].copy_from_slice(&chunk[..n]);
120+
self.pos += 1;
121+
Ok(n)
122+
}
123+
}
124+
125+
#[test]
126+
fn skip_whitespace_preserves_content() {
127+
let input = Cursor::new(b"helloworld");
128+
let mut reader = SkipWhitespace::new(input);
129+
let mut result = String::new();
130+
reader.read_to_string(&mut result).unwrap();
131+
assert_eq!(result, "helloworld");
132+
}
133+
134+
#[test]
135+
fn skip_whitespace_strips_all_whitespace_types() {
136+
let input = Cursor::new(b"hello \t\n\r world");
137+
let mut reader = SkipWhitespace::new(input);
138+
let mut result = String::new();
139+
reader.read_to_string(&mut result).unwrap();
140+
assert_eq!(result, "helloworld");
141+
}
142+
143+
#[test]
144+
fn skip_whitespace_handles_only_whitespace() {
145+
let input = Cursor::new(b"\n \t \r\n");
146+
let mut reader = SkipWhitespace::new(input);
147+
let mut result = String::new();
148+
reader.read_to_string(&mut result).unwrap();
149+
assert_eq!(result, "");
150+
}
151+
152+
#[test]
153+
fn skip_whitespace_handles_empty_input() {
154+
let input = Cursor::new(b"");
155+
let mut reader = SkipWhitespace::new(input);
156+
let mut result = String::new();
157+
reader.read_to_string(&mut result).unwrap();
158+
assert_eq!(result, "");
159+
}
160+
161+
#[test]
162+
fn skip_whitespace_loops_past_whitespace_only_chunks() {
163+
// Exercises the loop iterating more than once: first chunk is all
164+
// whitespace, second chunk has content. A Cursor would satisfy both
165+
// reads in one shot and would never trigger the loop.
166+
let reader = ChunkedReader::new(vec![b"\n\n", b"hello", b""]);
167+
let mut skipper = SkipWhitespace::new(reader);
168+
let mut result = String::new();
169+
skipper.read_to_string(&mut result).unwrap();
170+
assert_eq!(result, "hello");
171+
}
172+
173+
#[test]
174+
fn skip_whitespace_handles_leading_trailing_whitespace() {
175+
let input = Cursor::new(b"\n\nhello\n\n");
176+
let mut reader = SkipWhitespace::new(input);
177+
let mut result = String::new();
178+
reader.read_to_string(&mut result).unwrap();
179+
assert_eq!(result, "hello");
180+
}
181+
}

0 commit comments

Comments
 (0)