• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

stacks-network / stacks-core / 25903914664-1

15 May 2026 06:28AM UTC coverage: 47.122% (-38.8%) from 85.959%
25903914664-1

Pull #7199

github

94e391
web-flow
Merge 109f2828c into 1c7b8e6ac
Pull Request #7199: Feat: L1 and L2 early unlocks, updating signer

103343 of 219309 relevant lines covered (47.12%)

12880462.62 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

35.09
/stacks-common/src/util/chunked_encoding.rs
1
// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation
2
// Copyright (C) 2020-2023 Stacks Open Internet Foundation
3
//
4
// This program is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// This program is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

17
use std::io::{Read, Write};
18
use std::{error, fmt, io};
19

20
use crate::codec::MAX_MESSAGE_LEN;
21
use crate::deps_common::httparse;
22

23
/// NOTE: it is imperative that the given Read and Write impls here _never_ fail with EWOULDBLOCK.
24

25
#[derive(Debug)]
26
pub enum ChunkedError {
27
    DeserializeError(String),
28
    OverflowError(String),
29
}
30

31
impl fmt::Display for ChunkedError {
32
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
×
33
        match *self {
×
34
            ChunkedError::DeserializeError(ref s) => fmt::Display::fmt(s, f),
×
35
            ChunkedError::OverflowError(ref s) => fmt::Display::fmt(s, f),
×
36
        }
37
    }
×
38
}
39

40
impl error::Error for ChunkedError {
41
    fn cause(&self) -> Option<&dyn error::Error> {
×
42
        match *self {
×
43
            ChunkedError::DeserializeError(..) => None,
×
44
            ChunkedError::OverflowError(..) => None,
×
45
        }
46
    }
×
47
}
48

49
#[allow(clippy::upper_case_acronyms)]
50
#[derive(Debug, Clone, PartialEq, Copy)]
51
enum HttpChunkedTransferParseMode {
52
    ChunkBoundary,
53
    Chunk,
54
    ChunkTrailer,
55
    EOF,
56
}
57

58
#[derive(Debug, Clone, PartialEq, Copy)]
59
pub struct HttpChunkedTransferReaderState {
60
    parse_step: HttpChunkedTransferParseMode,
61
    chunk_size: u64,
62
    chunk_read: u64,
63
    pub max_size: u64,
64
    total_size: u64,
65
    last_chunk_size: u64,
66

67
    // for parsing a chunk boundary
68
    // (we don't use extensions, so 16 bytes for size + 2 for \r\n delimiter ought to be enough)
69
    chunk_buffer: [u8; 18],
70
    i: usize,
71
}
72

73
impl HttpChunkedTransferReaderState {
74
    pub fn new(max_size: u64) -> HttpChunkedTransferReaderState {
46,280✔
75
        HttpChunkedTransferReaderState {
46,280✔
76
            parse_step: HttpChunkedTransferParseMode::ChunkBoundary,
46,280✔
77
            chunk_size: 0,
46,280✔
78
            chunk_read: 0,
46,280✔
79
            max_size,
46,280✔
80
            total_size: 0,
46,280✔
81
            last_chunk_size: u64::MAX, // if this ever becomes 0, then we should expect chunk boundary '0\r\n\r\n' and EOF
46,280✔
82
            chunk_buffer: [0u8; 18],
46,280✔
83
            i: 0,
46,280✔
84
        }
46,280✔
85
    }
46,280✔
86

87
    pub fn is_eof(&self) -> bool {
32,396✔
88
        self.parse_step == HttpChunkedTransferParseMode::EOF
32,396✔
89
    }
32,396✔
90
}
91

92
/// read adapter for chunked transfer encoding
93
pub struct HttpChunkedTransferReader<'a, R: Read> {
94
    fd: &'a mut R,
95
    state: HttpChunkedTransferReaderState,
96
}
97

98
impl<'a, R: Read> HttpChunkedTransferReader<'a, R> {
99
    pub fn from_reader(r: &'a mut R, max_size: u64) -> HttpChunkedTransferReader<'a, R> {
×
100
        HttpChunkedTransferReader {
×
101
            fd: r,
×
102
            state: HttpChunkedTransferReaderState::new(max_size),
×
103
        }
×
104
    }
×
105

106
    pub fn from_state(
×
107
        r: &'a mut R,
×
108
        state: HttpChunkedTransferReaderState,
×
109
    ) -> HttpChunkedTransferReader<'a, R> {
×
110
        HttpChunkedTransferReader { fd: r, state }
×
111
    }
×
112
}
113

114
impl HttpChunkedTransferReaderState {
115
    /// Read until we have a chunk marker we can parse completely.
116
    /// Interruptable -- call repeatedly on EINTR.
117
    /// Reads at most one byte.
118
    fn read_chunk_boundary<R: Read>(&mut self, fd: &mut R) -> io::Result<usize> {
99,642✔
119
        assert_eq!(self.parse_step, HttpChunkedTransferParseMode::ChunkBoundary);
99,642✔
120

121
        // next byte
122
        let mut b = [0u8; 1];
99,642✔
123

124
        trace!("Read {} bytes", b.len());
99,642✔
125
        let nr = fd.read(&mut b)?;
99,642✔
126
        if nr == 0 {
99,642✔
127
            return Ok(nr);
×
128
        }
99,642✔
129
        trace!("Got {nr} bytes");
99,642✔
130

131
        self.chunk_buffer[self.i] = b[0];
99,642✔
132
        self.i += 1;
99,642✔
133

134
        if self.i >= self.chunk_buffer.len() {
99,642✔
135
            // don't allow ridiculous extension lengths
136
            return Err(io::Error::new(
×
137
                io::ErrorKind::InvalidData,
×
138
                ChunkedError::DeserializeError("Invalid HTTP chunk boundary: too long".to_string()),
×
139
            ));
×
140
        }
99,642✔
141

142
        let (offset, chunk_len) = match httparse::parse_chunk_size(&self.chunk_buffer[0..self.i]) {
99,642✔
143
            Ok(httparse::Status::Partial) => {
144
                return Ok(nr);
73,300✔
145
            }
146
            Ok(httparse::Status::Complete((offset, chunk_len))) => (offset, chunk_len),
26,342✔
147
            Err(_) => {
148
                test_debug!(
×
149
                    "Invalid chunk boundary: {:?}",
150
                    self.chunk_buffer[0..self.i].to_vec()
×
151
                );
152
                return Err(io::Error::new(
×
153
                    io::ErrorKind::InvalidData,
×
154
                    "Invalid HTTP chunk boundary: could not parse".to_string(),
×
155
                ));
×
156
            }
157
        };
158

159
        trace!("chunk offset: {offset}. chunk len: {chunk_len}");
26,342✔
160
        if chunk_len > MAX_MESSAGE_LEN as u64 {
26,342✔
161
            trace!("chunk buffer: {:?}", &self.chunk_buffer[0..self.i]);
×
162
            return Err(io::Error::new(
×
163
                io::ErrorKind::InvalidData,
×
164
                ChunkedError::DeserializeError("Invalid HTTP chunk: too big".to_string()),
×
165
            ));
×
166
        }
26,342✔
167

168
        // got an offset/len.
169
        // offset ought to equal the number of bytes taken by the encoded chunk boundary.
170
        assert_eq!(offset, self.i);
26,342✔
171

172
        // reset buffers
173
        self.i = 0;
26,342✔
174
        self.chunk_size = chunk_len;
26,342✔
175
        self.chunk_read = 0;
26,342✔
176

177
        // begin reading chunk
178
        trace!("begin reading chunk");
26,342✔
179
        self.parse_step = HttpChunkedTransferParseMode::Chunk;
26,342✔
180
        Ok(nr)
26,342✔
181
    }
99,642✔
182

183
    /// Read a chunk -- read up to self.chunk_size bytes over successive calls.
184
    /// Reads at most self.chunk_size bytes.
185
    fn read_chunk_bytes<R: Read>(&mut self, fd: &mut R, buf: &mut [u8]) -> io::Result<usize> {
26,342✔
186
        assert_eq!(self.parse_step, HttpChunkedTransferParseMode::Chunk);
26,342✔
187

188
        if self.total_size >= self.max_size && self.chunk_size > 0 {
26,342✔
189
            return Err(io::Error::other(ChunkedError::OverflowError(
×
190
                "HTTP body exceeds maximum expected length".to_string(),
×
191
            )));
×
192
        }
26,342✔
193

194
        let remaining = if self.chunk_size - self.chunk_read <= (self.max_size - self.total_size) {
26,342✔
195
            self.chunk_size - self.chunk_read
26,342✔
196
        } else {
197
            self.max_size - self.total_size
×
198
        };
199

200
        let nr = if (buf.len() as u64) < remaining {
26,342✔
201
            // can fill buffer
202
            trace!("Read {} bytes (fill buffer)", buf.len());
×
203
            fd.read(buf)? as u64
×
204
        } else {
205
            // will read up to a chunk boundary
206
            trace!("Read {remaining} bytes (fill remainder)");
26,342✔
207
            fd.read(&mut buf[0..(remaining as usize)])? as u64
26,342✔
208
        };
209

210
        trace!("Got {nr} bytes");
26,342✔
211

212
        self.chunk_read += nr;
26,342✔
213

214
        if self.chunk_read >= self.chunk_size {
26,342✔
215
            // done reading; proceed to consume trailer
216
            trace!(
26,342✔
217
                "begin reading trailer ({} >= {})",
218
                self.chunk_read,
219
                self.chunk_size
220
            );
221
            self.parse_step = HttpChunkedTransferParseMode::ChunkTrailer;
26,342✔
222
        }
×
223

224
        self.total_size += nr;
26,342✔
225
        Ok(nr as usize)
26,342✔
226
    }
26,342✔
227

228
    /// Read chunk trailer -- read end-of-chunk \r\n
229
    /// Returns number of bytes read on success
230
    /// Reads at most 2 bytes.
231
    fn read_chunk_trailer<R: Read>(&mut self, fd: &mut R) -> io::Result<usize> {
26,342✔
232
        assert_eq!(self.parse_step, HttpChunkedTransferParseMode::ChunkTrailer);
26,342✔
233

234
        let mut nr = 0;
26,342✔
235

236
        // read trailer
237
        if self.i < 2 {
26,342✔
238
            let mut trailer_buf = [0u8; 2];
26,342✔
239

240
            trace!("Read at most {} bytes", 2 - self.i);
26,342✔
241
            nr = fd.read(&mut trailer_buf[self.i..2])?;
26,342✔
242
            if nr == 0 {
26,342✔
243
                return Ok(nr);
×
244
            }
26,342✔
245

246
            self.chunk_buffer[self.i..2].copy_from_slice(&trailer_buf[self.i..2]);
26,342✔
247
            self.i += nr;
26,342✔
248
        }
×
249

250
        if self.i == 2 {
26,342✔
251
            // expect '\r\n'
252
            if self.chunk_buffer[0..2] != [0x0d, 0x0a] {
26,342✔
253
                return Err(io::Error::new(
×
254
                    io::ErrorKind::InvalidData,
×
255
                    ChunkedError::DeserializeError("Invalid chunk trailer".to_string()),
×
256
                ));
×
257
            }
26,342✔
258

259
            // end of chunk
260
            self.last_chunk_size = self.chunk_size;
26,342✔
261
            self.i = 0;
26,342✔
262

263
            trace!("begin reading boundary");
26,342✔
264
            self.parse_step = HttpChunkedTransferParseMode::ChunkBoundary;
26,342✔
265
        }
×
266

267
        trace!("Consumed {nr} bytes of chunk boundary (i = {})", self.i);
26,342✔
268
        Ok(nr)
26,342✔
269
    }
26,342✔
270

271
    /// Read from a Read.
272
    /// Returns (number of bytes decoded, number of bytes consumed from the Read)
273
    pub fn do_read<R: Read>(&mut self, fd: &mut R, buf: &mut [u8]) -> io::Result<(usize, usize)> {
24,231✔
274
        let mut decoded = 0;
24,231✔
275
        let mut consumed = 0;
24,231✔
276
        while decoded < buf.len() {
161,795✔
277
            match self.parse_step {
161,795✔
278
                HttpChunkedTransferParseMode::ChunkBoundary => {
279
                    let count = self.read_chunk_boundary(fd)?;
99,642✔
280
                    if count == 0 {
99,642✔
281
                        break;
×
282
                    }
99,642✔
283
                    consumed += count;
99,642✔
284
                }
285
                HttpChunkedTransferParseMode::Chunk => {
286
                    let nr = self.read_chunk_bytes(fd, &mut buf[decoded..])?;
26,342✔
287
                    if nr == 0 && self.parse_step == HttpChunkedTransferParseMode::Chunk {
26,342✔
288
                        // still trying to read the chunk, but got 0 bytes
289
                        break;
×
290
                    }
26,342✔
291
                    decoded += nr;
26,342✔
292
                    consumed += nr;
26,342✔
293
                }
294
                HttpChunkedTransferParseMode::ChunkTrailer => {
295
                    let count = self.read_chunk_trailer(fd)?;
26,342✔
296
                    if count == 0 {
26,342✔
297
                        break;
×
298
                    }
26,342✔
299
                    consumed += count;
26,342✔
300
                    if self.last_chunk_size == 0 {
26,342✔
301
                        // we're done
302
                        trace!("finished last chunk");
14,762✔
303
                        self.parse_step = HttpChunkedTransferParseMode::EOF;
14,762✔
304
                        break;
14,762✔
305
                    }
11,580✔
306
                }
307
                HttpChunkedTransferParseMode::EOF => {
308
                    break;
9,469✔
309
                }
310
            }
311
        }
312
        Ok((decoded, consumed))
24,231✔
313
    }
24,231✔
314
}
315

316
impl<R: Read> Read for HttpChunkedTransferReader<'_, R> {
317
    /// Read a HTTP chunk-encoded stream.
318
    /// Returns number of decoded bytes (i.e. number of bytes copied to buf, as expected)
319
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
×
320
        self.state.do_read(self.fd, buf).map(|(decoded, _)| decoded)
×
321
    }
×
322
}
323

324
pub struct HttpChunkedTransferWriterState {
325
    chunk_size: usize,
326
    chunk_buf: Vec<u8>,
327
    corked: bool,
328
}
329

330
impl HttpChunkedTransferWriterState {
331
    pub fn new(chunk_size: usize) -> HttpChunkedTransferWriterState {
32,452✔
332
        HttpChunkedTransferWriterState {
32,452✔
333
            chunk_size,
32,452✔
334
            chunk_buf: vec![],
32,452✔
335
            corked: false,
32,452✔
336
        }
32,452✔
337
    }
32,452✔
338

339
    pub fn get_chunk_size(&self) -> usize {
×
340
        self.chunk_size
×
341
    }
×
342
}
343

344
pub struct HttpChunkedTransferWriter<'a, 'state, W: Write> {
345
    fd: &'a mut W,
346
    state: &'state mut HttpChunkedTransferWriterState,
347
}
348

349
impl<'a, 'state, W: Write> HttpChunkedTransferWriter<'a, 'state, W> {
350
    pub fn from_writer_state(
45,165✔
351
        fd: &'a mut W,
45,165✔
352
        state: &'state mut HttpChunkedTransferWriterState,
45,165✔
353
    ) -> HttpChunkedTransferWriter<'a, 'state, W> {
45,165✔
354
        HttpChunkedTransferWriter { fd, state }
45,165✔
355
    }
45,165✔
356

357
    fn send_chunk(fd: &mut W, chunk_size: usize, bytes: &[u8]) -> io::Result<usize> {
16,901✔
358
        let to_send = if chunk_size < bytes.len() {
16,901✔
359
            chunk_size
×
360
        } else {
361
            bytes.len()
16,901✔
362
        };
363

364
        fd.write_all(format!("{to_send:x}\r\n").as_bytes())?;
16,901✔
365
        fd.write_all(&bytes[0..to_send])?;
16,901✔
366
        fd.write_all("\r\n".as_bytes())?;
16,901✔
367
        Ok(to_send)
16,901✔
368
    }
16,901✔
369

370
    fn flush_chunk(&mut self) -> io::Result<usize> {
16,901✔
371
        let sent = HttpChunkedTransferWriter::send_chunk(
16,901✔
372
            &mut self.fd,
16,901✔
373
            self.state.chunk_size,
16,901✔
374
            &self.state.chunk_buf,
16,901✔
375
        )?;
×
376
        self.state.chunk_buf.clear();
16,901✔
377
        Ok(sent)
16,901✔
378
    }
16,901✔
379

380
    fn buf_chunk(&mut self, buf: &[u8]) -> usize {
15,666✔
381
        let to_copy = if self.state.chunk_size - self.state.chunk_buf.len() < buf.len() {
15,666✔
382
            self.state.chunk_size - self.state.chunk_buf.len()
81✔
383
        } else {
384
            buf.len()
15,585✔
385
        };
386

387
        self.state.chunk_buf.extend_from_slice(&buf[0..to_copy]);
15,666✔
388
        to_copy
15,666✔
389
    }
15,666✔
390

391
    pub fn cork(&mut self) {
14,790✔
392
        // block future flushes from sending trailing empty chunks -- we're done sending
393
        self.state.corked = true;
14,790✔
394
    }
14,790✔
395

396
    pub fn corked(&self) -> bool {
29,580✔
397
        self.state.corked
29,580✔
398
    }
29,580✔
399
}
400

401
impl<W: Write> Write for HttpChunkedTransferWriter<'_, '_, W> {
402
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
15,585✔
403
        let mut written = 0;
15,585✔
404
        while written < buf.len() && !self.state.corked {
33,281✔
405
            if !self.state.chunk_buf.is_empty() {
17,696✔
406
                if self.state.chunk_buf.len() < self.state.chunk_size {
6,089✔
407
                    let nw = self.buf_chunk(&buf[written..]);
4,059✔
408
                    written += nw;
4,059✔
409
                }
6,089✔
410
                if self.state.chunk_buf.len() >= self.state.chunk_size {
6,089✔
411
                    self.flush_chunk()?;
2,111✔
412
                }
3,978✔
413
            } else if written + self.state.chunk_size < buf.len() {
11,607✔
414
                let nw = HttpChunkedTransferWriter::send_chunk(
×
415
                    &mut self.fd,
×
416
                    self.state.chunk_size,
×
417
                    &buf[written..(written + self.state.chunk_size)],
×
418
                )?;
×
419
                written += nw;
×
420
            } else {
11,607✔
421
                let nw = self.buf_chunk(&buf[written..]);
11,607✔
422
                written += nw;
11,607✔
423
            }
11,607✔
424
        }
425
        Ok(written)
15,585✔
426
    }
15,585✔
427

428
    fn flush(&mut self) -> io::Result<()> {
14,790✔
429
        // send out any buffered chunk data
430
        if !self.state.corked {
14,790✔
431
            self.flush_chunk().and_then(|nw| {
14,790✔
432
                if nw > 0 {
14,790✔
433
                    // send empty chunk
434
                    self.fd.write_all(b"0\r\n\r\n").map(|_nw| ())
9,496✔
435
                } else {
436
                    Ok(())
5,294✔
437
                }
438
            })
14,790✔
439
        } else {
440
            Ok(())
×
441
        }
442
    }
14,790✔
443
}
444

445
#[cfg(test)]
446
mod test {
447
    use std::io;
448
    use std::io::Read;
449

450
    use rand::RngCore as _;
451

452
    use super::*;
453

454
    /// Simulate reading variable-length segments
455
    struct SegmentReader {
456
        segments: Vec<Vec<u8>>,
457
        i: usize, // which segment
458
        j: usize, // which offset in segment
459
    }
460

461
    impl SegmentReader {
462
        pub fn new(segments: Vec<Vec<u8>>) -> SegmentReader {
×
463
            SegmentReader {
×
464
                segments,
×
465
                i: 0,
×
466
                j: 0,
×
467
            }
×
468
        }
×
469
    }
470

471
    impl Read for SegmentReader {
472
        fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
×
473
            if self.i >= self.segments.len() {
×
474
                return Ok(0);
×
475
            }
×
476
            let mut written = 0;
×
477
            while written < buf.len() {
×
478
                let to_copy = if self.segments[self.i][self.j..].len() < buf[written..].len() {
×
479
                    self.segments[self.i][self.j..].len()
×
480
                } else {
481
                    buf[written..].len()
×
482
                };
483

484
                buf[written..(written + to_copy)]
×
485
                    .copy_from_slice(&self.segments[self.i][self.j..(self.j + to_copy)]);
×
486

487
                self.j += to_copy;
×
488
                written += to_copy;
×
489

490
                if self.j >= self.segments[self.i].len() {
×
491
                    self.i += 1;
×
492
                    self.j = 0;
×
493
                }
×
494
            }
495
            Ok(written)
×
496
        }
×
497
    }
498

499
    fn vec_u8(v: Vec<&str>) -> Vec<Vec<u8>> {
×
500
        v.into_iter().map(|s| s.as_bytes().to_vec()).collect()
×
501
    }
×
502

503
    #[test]
504
    fn test_segment_reader() {
×
505
        let tests = vec![
×
506
            (vec_u8(vec!["a", "b"]), "ab"),
×
507
            (vec_u8(vec!["aa", "bbb", "cccc"]), "aabbbcccc"),
×
508
            (vec_u8(vec!["aaaa", "bbb", "cc", "d", ""]), "aaaabbbccd"),
×
509
            (vec_u8(vec!["", "a", "", "b", ""]), "ab"),
×
510
            (vec_u8(vec![""]), ""),
×
511
        ];
512
        for (input_vec, expected) in tests.into_iter() {
×
513
            let num_segments = input_vec.len();
×
514
            let mut segment_io = SegmentReader::new(input_vec);
×
515
            let mut output = vec![0u8; expected.len()];
×
516
            let mut offset = 0;
×
517
            for i in 0..num_segments {
×
518
                let nw = segment_io.read(&mut output[offset..]).unwrap();
×
519
                offset += nw;
×
520
            }
×
521
            assert_eq!(output, expected.as_bytes().to_vec());
×
522
        }
523
    }
×
524

525
    #[test]
526
    fn test_http_chunked_encode() {
×
527
        let tests = [
×
528
            // (chunk size, byte string, expected encoding)
×
529
            (10, "aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd", "a\r\naaaaaaaaaa\r\na\r\nbbbbbbbbbb\r\na\r\ncccccccccc\r\na\r\ndddddddddd\r\n0\r\n\r\n"),
×
530
            (10, "aaaaaaaaaabbbbbbbbbbccccccccccdddddddddde", "a\r\naaaaaaaaaa\r\na\r\nbbbbbbbbbb\r\na\r\ncccccccccc\r\na\r\ndddddddddd\r\n1\r\ne\r\n0\r\n\r\n"),
×
531
            (10, "aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeee", "a\r\naaaaaaaaaa\r\na\r\nbbbbbbbbbb\r\na\r\ncccccccccc\r\na\r\ndddddddddd\r\n5\r\neeeee\r\n0\r\n\r\n"),
×
532
            (1, "abcd", "1\r\na\r\n1\r\nb\r\n1\r\nc\r\n1\r\nd\r\n0\r\n\r\n"),
×
533
            (3, "abcd", "3\r\nabc\r\n1\r\nd\r\n0\r\n\r\n"),
×
534
            (10, "", "0\r\n\r\n")
×
535
        ];
×
536
        for (chunk_size, input_bytes, encoding) in tests.iter() {
×
537
            let mut bytes = vec![];
×
538
            {
×
539
                let mut write_state = HttpChunkedTransferWriterState::new(*chunk_size as usize);
×
540
                let mut encoder =
×
541
                    HttpChunkedTransferWriter::from_writer_state(&mut bytes, &mut write_state);
×
542
                encoder.write_all(input_bytes.as_bytes()).unwrap();
×
543
                encoder.flush().unwrap();
×
544
            }
×
545

546
            assert_eq!(bytes, encoding.as_bytes().to_vec());
×
547
        }
548
    }
×
549

550
    #[test]
551
    fn test_http_chunked_encode_multi() {
×
552
        let tests = [
×
553
            // chunk size, sequence of writes, expected encoding
×
554
            (10, vec!["aaaaaaaaaa", "bbbbb", "bbbbb", "ccc", "ccc", "ccc", "c", "dd", "ddddd", "ddd"], "a\r\naaaaaaaaaa\r\na\r\nbbbbbbbbbb\r\na\r\ncccccccccc\r\na\r\ndddddddddd\r\n0\r\n\r\n"),
×
555
            (10, vec!["a", "a", "a", "a", "a", "a", "a", "a", "a", "a"], "a\r\naaaaaaaaaa\r\n0\r\n\r\n"),
×
556
            (10, vec!["a", "", "a", "", "a", "", "a", "", "a", "", "a", "", "a", "", "a", "", "a", "", "a", ""], "a\r\naaaaaaaaaa\r\n0\r\n\r\n"),
×
557
        ];
×
558

559
        for (chunk_size, input_vec, encoding) in tests.iter() {
×
560
            let mut bytes = vec![];
×
561
            {
562
                let mut write_state = HttpChunkedTransferWriterState::new(*chunk_size as usize);
×
563
                let mut encoder =
×
564
                    HttpChunkedTransferWriter::from_writer_state(&mut bytes, &mut write_state);
×
565
                for input in input_vec.iter() {
×
566
                    encoder.write_all(input.as_bytes()).unwrap();
×
567
                }
×
568
                encoder.flush().unwrap();
×
569
            }
570

571
            assert_eq!(bytes, encoding.as_bytes().to_vec());
×
572
        }
573
    }
×
574

575
    #[test]
576
    fn test_http_chunked_decode() {
×
577
        let tests = [
×
578
            ("a\r\naaaaaaaaaa\r\na\r\nbbbbbbbbbb\r\na\r\ncccccccccc\r\na\r\ndddddddddd\r\n0\r\n\r\n", "aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd"),
×
579
            ("A\r\naaaaaaaaaa\r\nA\r\nbbbbbbbbbb\r\nA\r\ncccccccccc\r\nA\r\ndddddddddd\r\n0\r\n\r\n", "aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd"),
×
580
            ("1\r\na\r\n2\r\nbb\r\n3\r\nccc\r\n4\r\ndddd\r\n0\r\n\r\n", "abbcccdddd"),
×
581
            ("1\r\na\r\n0\r\n\r\n", "a"),
×
582
            ("1\r\na\r\n0\r\n\r\n1\r\nb\r\n0\r\n\r\n", "a"),     // stop reading after the first 0-length chunk encountered
×
583
            ("1; a; b\r\na\r\n0; c\r\n\r\n", "a"),                          // ignore short extensions
×
584
            ("1  ; a ; b \r\na\r\n0     ; extension003\r\n\r\n", "a"),      // ignore short extensions
×
585
            ("1 \t; a\t;\tb ;\r\na\r\n0\t\t;c\r\n\r\n", "a"),               // ignore short extensions
×
586
        ];
×
587
        for (encoded, expected) in tests.iter() {
×
588
            let mut cursor = io::Cursor::new(encoded.as_bytes());
×
589
            let mut decoder = HttpChunkedTransferReader::from_reader(&mut cursor, 50);
×
590
            let mut output = vec![0u8; expected.len()];
×
591
            decoder.read_exact(&mut output).unwrap();
×
592

593
            assert_eq!(output, expected.as_bytes().to_vec());
×
594
        }
595
    }
×
596

597
    #[test]
598
    fn test_http_chunked_decode_multi() {
×
599
        let tests = [
×
600
            (vec_u8(vec!["1\r\na", "\r\n", "0\r\n\r\n"]), "a"),
×
601
            (vec_u8(vec!["1\r\na\r", "\n0\r\n\r\n"]), "a"),
×
602
            (vec_u8(vec!["1\r\na\r\n", "0\r\n\r", "\n"]), "a"),
×
603
            (vec_u8(vec!["1\r\na\r\n0\r\n", "\r\n"]), "a"),
×
604
            (vec_u8(vec!["1\r\na\r\n0\r", "\n\r\n"]), "a"),
×
605
            (vec_u8(vec!["1\r\na\r\n0\r", "\n", "\r\n"]), "a"),
×
606
            (vec_u8(vec!["1\r\na\r\n0\r", "\n\r", "\n"]), "a"),
×
607
            (vec_u8(vec!["1\r\na\r\n0\r", "\n", "\r", "\n"]), "a"),
×
608
            (
×
609
                vec_u8(vec![
×
610
                    "1", "\r", "\n", "a", "\r", "\n", "0", "\r", "\n", "\r", "\n",
×
611
                ]),
×
612
                "a",
×
613
            ),
×
614
            (
×
615
                vec_u8(vec![
×
616
                    "a\r",
×
617
                    "\n",
×
618
                    "aaaa",
×
619
                    "aaaaa",
×
620
                    "a",
×
621
                    "\r\n",
×
622
                    "a\r\n",
×
623
                    "bbbbbbbbbb\r",
×
624
                    "\na\r\nccc",
×
625
                    "ccccccc",
×
626
                    "\r",
×
627
                    "\na\r",
×
628
                    "\ndddddd",
×
629
                    "dddd",
×
630
                    "\r\n0\r",
×
631
                    "\n",
×
632
                    "\r",
×
633
                    "\n",
×
634
                ]),
×
635
                "aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd",
×
636
            ),
×
637
            (
×
638
                vec_u8(vec![
×
639
                    "a\r\naaaaaaaaaa",
×
640
                    "\r",
×
641
                    "\n",
×
642
                    "a\r\nbbbbbbbbbb\r",
×
643
                    "\n",
×
644
                    "a\r\ncccccccccc\r",
×
645
                    "\na\r\nddddd",
×
646
                    "ddddd\r",
×
647
                    "\n0\r",
×
648
                    "\n\r",
×
649
                    "\n",
×
650
                ]),
×
651
                "aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd",
×
652
            ),
×
653
            (
×
654
                vec_u8(vec![
×
655
                    "1",
×
656
                    "\r",
×
657
                    "\n",
×
658
                    "",
×
659
                    "a",
×
660
                    "\r",
×
661
                    "\n",
×
662
                    "2",
×
663
                    "\r\n",
×
664
                    "bb",
×
665
                    "\r\n",
×
666
                    "3\r\n",
×
667
                    "ccc\r",
×
668
                    "\n4\r\n",
×
669
                    "dddd\r\n",
×
670
                    "0\r\n\r\n",
×
671
                ]),
×
672
                "abbcccdddd",
×
673
            ),
×
674
        ];
×
675
        for (encoded_vec, expected) in tests.iter() {
×
676
            test_debug!("expect {expected:?}");
×
677

678
            let mut output = vec![];
×
679
            let mut cursor = SegmentReader::new((*encoded_vec).clone());
×
680
            let mut decoder = HttpChunkedTransferReader::from_reader(&mut cursor, 50);
×
681

682
            for encoded in encoded_vec.iter() {
×
683
                let mut tmp = vec![0u8; encoded.len()];
×
684
                let nr = decoder.read(&mut tmp).unwrap();
×
685

×
686
                output.extend_from_slice(&tmp[0..nr]);
×
687
            }
×
688

689
            assert_eq!(output, expected.as_bytes().to_vec());
×
690
        }
691
    }
×
692

693
    #[test]
694
    fn test_http_chunked_decode_err() {
×
695
        let tests = [
×
696
            (
×
697
                "1; reallyreallyreallyreallylongextension;\r\na\r\n0\r\n\r\n",
×
698
                1,
×
699
                "too long",
×
700
            ),
×
701
            ("ffffffff\r\n", 1, "too big"),
×
702
            ("nope\r\n", 1, "could not parse"),
×
703
            ("1\na\r\n0\r\n\r\n", 1, "could not parse"),
×
704
            ("a\r\naaaaaaaaaa", 11, "failed to fill whole buffer"),
×
705
            ("1\r\nab\r\n0\r\n\r\n", 2, "Invalid chunk trailer"),
×
706
            (
×
707
                "15\r\naaaaaaaaaabbbbbbbbbbb\r\n0\r\n\r\n",
×
708
                21,
×
709
                "HTTP body exceeds maximum expected length",
×
710
            ),
×
711
            (
×
712
                "7\r\naaaaaaa\r\n8\r\nbbbbbbbb\r\n6\r\ncccccc\r\n0\r\n\r\n",
×
713
                21,
×
714
                "HTTP body exceeds maximum expected length",
×
715
            ),
×
716
        ];
×
717
        for (encoded, expected_len, expected) in tests.iter() {
×
718
            test_debug!("expect '{expected}'");
×
719
            let mut cursor = io::Cursor::new(encoded.as_bytes());
×
720
            let mut decoder = HttpChunkedTransferReader::from_reader(&mut cursor, 20);
×
721
            let mut output = vec![0u8; *expected_len as usize];
×
722

723
            let err = decoder.read_exact(&mut output).unwrap_err();
×
724
            let errstr = format!("{err:?}");
×
725

726
            assert!(
×
727
                errstr.contains(expected),
×
728
                "Expected '{expected}' in '{errstr:?}'"
729
            );
730
        }
731
    }
×
732

733
    #[test]
734
    fn test_http_chunked_encode_decode_roundtrip() {
×
735
        let mut rng = rand::thread_rng();
×
736
        for i in 0..100 {
×
737
            let mut data = vec![0u8; 256];
×
738
            rng.fill_bytes(&mut data);
×
739

740
            let mut encoded_data = vec![];
×
741
            {
×
742
                let mut write_state = HttpChunkedTransferWriterState::new(i + 1);
×
743
                let mut encoder = HttpChunkedTransferWriter::from_writer_state(
×
744
                    &mut encoded_data,
×
745
                    &mut write_state,
×
746
                );
×
747
                encoder.write_all(&data).unwrap();
×
748
                encoder.flush().unwrap();
×
749
            }
×
750

751
            let mut decoded_data = vec![0u8; 256];
×
752
            {
×
753
                let mut cursor = io::Cursor::new(&encoded_data);
×
754
                let mut decoder = HttpChunkedTransferReader::from_reader(&mut cursor, 256);
×
755
                decoder.read_exact(&mut decoded_data).unwrap();
×
756
            }
×
757

758
            assert_eq!(data, decoded_data);
×
759
        }
760
    }
×
761
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc