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

stacks-network / stacks-core / 25904007932-1

15 May 2026 06:31AM UTC coverage: 47.459% (-38.5%) from 85.959%
25904007932-1

Pull #7210

github

869a54
web-flow
Merge 27877974d into 1c7b8e6ac
Pull Request #7210: [wip] epoch 4 release branch

36 of 53 new or added lines in 1 file covered. (67.92%)

88645 existing lines in 346 files now uncovered.

104136 of 219422 relevant lines covered (47.46%)

12897381.15 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 {
UNCOV
32
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
×
UNCOV
33
        match *self {
×
UNCOV
34
            ChunkedError::DeserializeError(ref s) => fmt::Display::fmt(s, f),
×
35
            ChunkedError::OverflowError(ref s) => fmt::Display::fmt(s, f),
×
36
        }
UNCOV
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 {
44,860✔
75
        HttpChunkedTransferReaderState {
44,860✔
76
            parse_step: HttpChunkedTransferParseMode::ChunkBoundary,
44,860✔
77
            chunk_size: 0,
44,860✔
78
            chunk_read: 0,
44,860✔
79
            max_size,
44,860✔
80
            total_size: 0,
44,860✔
81
            last_chunk_size: u64::MAX, // if this ever becomes 0, then we should expect chunk boundary '0\r\n\r\n' and EOF
44,860✔
82
            chunk_buffer: [0u8; 18],
44,860✔
83
            i: 0,
44,860✔
84
        }
44,860✔
85
    }
44,860✔
86

87
    pub fn is_eof(&self) -> bool {
31,402✔
88
        self.parse_step == HttpChunkedTransferParseMode::EOF
31,402✔
89
    }
31,402✔
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> {
UNCOV
99
    pub fn from_reader(r: &'a mut R, max_size: u64) -> HttpChunkedTransferReader<'a, R> {
×
UNCOV
100
        HttpChunkedTransferReader {
×
UNCOV
101
            fd: r,
×
UNCOV
102
            state: HttpChunkedTransferReaderState::new(max_size),
×
UNCOV
103
        }
×
UNCOV
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> {
103,095✔
119
        assert_eq!(self.parse_step, HttpChunkedTransferParseMode::ChunkBoundary);
103,095✔
120

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

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

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

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

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

159
        trace!("chunk offset: {offset}. chunk len: {chunk_len}");
27,217✔
160
        if chunk_len > MAX_MESSAGE_LEN as u64 {
27,217✔
UNCOV
161
            trace!("chunk buffer: {:?}", &self.chunk_buffer[0..self.i]);
×
UNCOV
162
            return Err(io::Error::new(
×
UNCOV
163
                io::ErrorKind::InvalidData,
×
UNCOV
164
                ChunkedError::DeserializeError("Invalid HTTP chunk: too big".to_string()),
×
UNCOV
165
            ));
×
166
        }
27,217✔
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);
27,217✔
171

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

177
        // begin reading chunk
178
        trace!("begin reading chunk");
27,217✔
179
        self.parse_step = HttpChunkedTransferParseMode::Chunk;
27,217✔
180
        Ok(nr)
27,217✔
181
    }
103,095✔
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> {
27,217✔
186
        assert_eq!(self.parse_step, HttpChunkedTransferParseMode::Chunk);
27,217✔
187

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

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

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

210
        trace!("Got {nr} bytes");
27,217✔
211

212
        self.chunk_read += nr;
27,217✔
213

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

224
        self.total_size += nr;
27,217✔
225
        Ok(nr as usize)
27,217✔
226
    }
27,217✔
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> {
27,217✔
232
        assert_eq!(self.parse_step, HttpChunkedTransferParseMode::ChunkTrailer);
27,217✔
233

234
        let mut nr = 0;
27,217✔
235

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

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

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

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

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

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

267
        trace!("Consumed {nr} bytes of chunk boundary (i = {})", self.i);
27,217✔
268
        Ok(nr)
27,217✔
269
    }
27,217✔
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)> {
25,115✔
274
        let mut decoded = 0;
25,115✔
275
        let mut consumed = 0;
25,115✔
276
        while decoded < buf.len() {
167,457✔
277
            match self.parse_step {
167,457✔
278
                HttpChunkedTransferParseMode::ChunkBoundary => {
279
                    let count = self.read_chunk_boundary(fd)?;
103,095✔
280
                    if count == 0 {
103,095✔
UNCOV
281
                        break;
×
282
                    }
103,095✔
283
                    consumed += count;
103,095✔
284
                }
285
                HttpChunkedTransferParseMode::Chunk => {
286
                    let nr = self.read_chunk_bytes(fd, &mut buf[decoded..])?;
27,217✔
287
                    if nr == 0 && self.parse_step == HttpChunkedTransferParseMode::Chunk {
27,217✔
288
                        // still trying to read the chunk, but got 0 bytes
UNCOV
289
                        break;
×
290
                    }
27,217✔
291
                    decoded += nr;
27,217✔
292
                    consumed += nr;
27,217✔
293
                }
294
                HttpChunkedTransferParseMode::ChunkTrailer => {
295
                    let count = self.read_chunk_trailer(fd)?;
27,217✔
296
                    if count == 0 {
27,217✔
UNCOV
297
                        break;
×
298
                    }
27,217✔
299
                    consumed += count;
27,217✔
300
                    if self.last_chunk_size == 0 {
27,217✔
301
                        // we're done
302
                        trace!("finished last chunk");
15,187✔
303
                        self.parse_step = HttpChunkedTransferParseMode::EOF;
15,187✔
304
                        break;
15,187✔
305
                    }
12,030✔
306
                }
307
                HttpChunkedTransferParseMode::EOF => {
308
                    break;
9,928✔
309
                }
310
            }
311
        }
312
        Ok((decoded, consumed))
25,115✔
313
    }
25,115✔
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)
UNCOV
319
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
×
UNCOV
320
        self.state.do_read(self.fd, buf).map(|(decoded, _)| decoded)
×
UNCOV
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 {
31,430✔
332
        HttpChunkedTransferWriterState {
31,430✔
333
            chunk_size,
31,430✔
334
            chunk_buf: vec![],
31,430✔
335
            corked: false,
31,430✔
336
        }
31,430✔
337
    }
31,430✔
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(
47,048✔
351
        fd: &'a mut W,
47,048✔
352
        state: &'state mut HttpChunkedTransferWriterState,
47,048✔
353
    ) -> HttpChunkedTransferWriter<'a, 'state, W> {
47,048✔
354
        HttpChunkedTransferWriter { fd, state }
47,048✔
355
    }
47,048✔
356

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

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

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

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

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

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

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

401
impl<W: Write> Write for HttpChunkedTransferWriter<'_, '_, W> {
402
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
16,638✔
403
        let mut written = 0;
16,638✔
404
        while written < buf.len() && !self.state.corked {
35,378✔
405
            if !self.state.chunk_buf.is_empty() {
18,740✔
406
                if self.state.chunk_buf.len() < self.state.chunk_size {
6,692✔
407
                    let nw = self.buf_chunk(&buf[written..]);
4,662✔
408
                    written += nw;
4,662✔
409
                }
6,692✔
410
                if self.state.chunk_buf.len() >= self.state.chunk_size {
6,692✔
411
                    self.flush_chunk()?;
2,102✔
412
                }
4,590✔
413
            } else if written + self.state.chunk_size < buf.len() {
12,048✔
UNCOV
414
                let nw = HttpChunkedTransferWriter::send_chunk(
×
UNCOV
415
                    &mut self.fd,
×
UNCOV
416
                    self.state.chunk_size,
×
UNCOV
417
                    &buf[written..(written + self.state.chunk_size)],
×
418
                )?;
×
UNCOV
419
                written += nw;
×
420
            } else {
12,048✔
421
                let nw = self.buf_chunk(&buf[written..]);
12,048✔
422
                written += nw;
12,048✔
423
            }
12,048✔
424
        }
425
        Ok(written)
16,638✔
426
    }
16,638✔
427

428
    fn flush(&mut self) -> io::Result<()> {
15,205✔
429
        // send out any buffered chunk data
430
        if !self.state.corked {
15,205✔
431
            self.flush_chunk().and_then(|nw| {
15,205✔
432
                if nw > 0 {
15,205✔
433
                    // send empty chunk
434
                    self.fd.write_all(b"0\r\n\r\n").map(|_nw| ())
9,946✔
435
                } else {
436
                    Ok(())
5,259✔
437
                }
438
            })
15,205✔
439
        } else {
440
            Ok(())
×
441
        }
442
    }
15,205✔
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 {
UNCOV
462
        pub fn new(segments: Vec<Vec<u8>>) -> SegmentReader {
×
UNCOV
463
            SegmentReader {
×
UNCOV
464
                segments,
×
UNCOV
465
                i: 0,
×
UNCOV
466
                j: 0,
×
UNCOV
467
            }
×
UNCOV
468
        }
×
469
    }
470

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

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

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

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

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

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

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

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

550
    #[test]
UNCOV
551
    fn test_http_chunked_encode_multi() {
×
UNCOV
552
        let tests = [
×
UNCOV
553
            // chunk size, sequence of writes, expected encoding
×
UNCOV
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"),
×
UNCOV
555
            (10, vec!["a", "a", "a", "a", "a", "a", "a", "a", "a", "a"], "a\r\naaaaaaaaaa\r\n0\r\n\r\n"),
×
UNCOV
556
            (10, vec!["a", "", "a", "", "a", "", "a", "", "a", "", "a", "", "a", "", "a", "", "a", "", "a", ""], "a\r\naaaaaaaaaa\r\n0\r\n\r\n"),
×
UNCOV
557
        ];
×
558

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

UNCOV
758
            assert_eq!(data, decoded_data);
×
759
        }
UNCOV
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