Coverage Report

Created: 2026-10-28 06:40

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/ureq-proto-0.6.5/src/chunk.rs
Line
Count
Source
1
use core::str;
2
3
use crate::Error;
4
use crate::util::find_crlf;
5
6
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7
pub(crate) enum Dechunker {
8
    Size,
9
    Chunk(usize),
10
    CrLf,
11
    Ending,
12
    Trailer,
13
    Ended,
14
}
15
16
#[derive(Debug)]
17
struct Pos {
18
    index_in: usize,
19
    index_out: usize,
20
}
21
22
impl Dechunker {
23
0
    pub fn new() -> Self {
24
0
        Dechunker::Size
25
0
    }
26
27
0
    pub fn parse_input(&mut self, src: &[u8], dst: &mut [u8]) -> Result<(usize, usize), Error> {
28
0
        let mut pos = Pos {
29
0
            index_in: 0,
30
0
            index_out: 0,
31
0
        };
32
33
        loop {
34
0
            let more = match self {
35
0
                Dechunker::Size => self.read_size(src, &mut pos)?,
36
0
                Dechunker::Chunk(_) => self.read_data(src, dst, &mut pos)?,
37
0
                Dechunker::CrLf => self.expect_crlf(src, &mut pos)?,
38
0
                Dechunker::Ending => self.trailer_or_ended(src, &mut pos)?,
39
0
                Dechunker::Trailer => self.trailer(src, &mut pos)?,
40
0
                Dechunker::Ended => false,
41
            };
42
43
0
            if !more {
44
0
                break;
45
0
            }
46
        }
47
48
0
        Ok((pos.index_in, pos.index_out))
49
0
    }
50
51
0
    pub fn is_on_chunk_boundary(&self) -> bool {
52
0
        *self == Self::Size
53
0
    }
54
55
    #[cfg(test)]
56
    fn left(&self) -> usize {
57
        if let Self::Chunk(l) = self { *l } else { 0 }
58
    }
59
60
0
    pub fn is_ended(&self) -> bool {
61
0
        matches!(self, Self::Ended)
62
0
    }
63
64
    #[cfg(feature = "client")]
65
0
    pub fn is_ending(&self) -> bool {
66
0
        matches!(self, Self::Ending)
67
0
    }
68
69
0
    fn read_size(&mut self, src: &[u8], pos: &mut Pos) -> Result<bool, Error> {
70
0
        let src = &src[pos.index_in..];
71
72
0
        let i = match find_crlf(src) {
73
0
            Some(v) => v,
74
0
            None => return Ok(false),
75
        };
76
77
        const SANITY_CHECK: usize = 20;
78
79
        // Some sanity check for how long the chunk length is
80
0
        if i > SANITY_CHECK {
81
0
            return Err(Error::ChunkExpectedCrLf);
82
0
        }
83
0
        let maybe_meta = src.iter().take(100).position(|c| *c == b';');
84
85
0
        let len_end = maybe_meta.unwrap_or(SANITY_CHECK + 1).min(i);
86
0
        let len_str = str::from_utf8(&src[..len_end])
87
0
            .map_err(|_| Error::ChunkLenNotAscii)?
88
0
            .trim();
89
90
0
        let len = usize::from_str_radix(len_str, 16).map_err(|_| Error::ChunkLenNotANumber)?;
91
92
0
        pos.index_in += i + 2;
93
0
        *self = if len == 0 {
94
0
            Self::Ending
95
        } else {
96
0
            Self::Chunk(len)
97
        };
98
99
0
        Ok(true)
100
0
    }
101
102
0
    fn read_data(&mut self, src: &[u8], dst: &mut [u8], pos: &mut Pos) -> Result<bool, Error> {
103
0
        let src = &src[pos.index_in..];
104
0
        let dst = &mut dst[pos.index_out..];
105
106
0
        let left = match self {
107
0
            Self::Chunk(v) => v,
108
0
            _ => unreachable!(),
109
        };
110
111
        // Read the smallest amount of input/output or length left of chunk.
112
0
        let to_read = src.len().min(dst.len()).min(*left);
113
114
0
        dst[..to_read].copy_from_slice(&src[..to_read]);
115
0
        pos.index_in += to_read;
116
0
        pos.index_out += to_read;
117
0
        *left -= to_read;
118
119
0
        if *left == 0 {
120
0
            *self = Self::CrLf;
121
0
        }
122
123
0
        Ok(to_read > 0)
124
0
    }
125
126
0
    fn expect_crlf(&mut self, src: &[u8], pos: &mut Pos) -> Result<bool, Error> {
127
0
        let src = &src[pos.index_in..];
128
129
0
        let i = match find_crlf(src) {
130
0
            Some(v) => v,
131
0
            None => return Ok(false),
132
        };
133
134
0
        if i > 0 {
135
0
            return Err(Error::ChunkExpectedCrLf);
136
0
        }
137
138
0
        pos.index_in += 2;
139
0
        *self = Self::Size;
140
141
0
        Ok(false)
142
0
    }
143
144
0
    fn trailer_or_ended(&mut self, src: &[u8], pos: &mut Pos) -> Result<bool, Error> {
145
0
        let src = &src[pos.index_in..];
146
147
0
        let i = match find_crlf(src) {
148
0
            Some(v) => v,
149
0
            None => return Ok(false),
150
        };
151
152
0
        if i == 0 {
153
0
            pos.index_in += 2;
154
0
            *self = Self::Ended;
155
0
        } else {
156
0
            // Non-crlf before
157
0
            *self = Self::Trailer;
158
0
        }
159
160
0
        Ok(true)
161
0
    }
162
163
0
    fn trailer(&mut self, src: &[u8], pos: &mut Pos) -> Result<bool, Error> {
164
0
        let src = &src[pos.index_in..];
165
166
0
        let i = match find_crlf(src) {
167
0
            Some(v) => v,
168
0
            None => return Ok(false),
169
        };
170
0
        assert!(i > 0);
171
172
        // advance the trailer, and 2 for the crlf.
173
0
        pos.index_in += i + 2;
174
0
        *self = Self::Ending;
175
176
0
        Ok(true)
177
0
    }
178
}
179
180
#[cfg(test)]
181
mod test {
182
    use super::*;
183
184
    #[test]
185
    fn test_dechunk_size() -> Result<(), Error> {
186
        let mut d = Dechunker::new();
187
        let mut b = [0; 1024];
188
        assert_eq!(d.parse_input(b"", &mut b)?, (0, 0));
189
        assert_eq!(d.parse_input(b"2", &mut b)?, (0, 0));
190
        assert_eq!(d.parse_input(b"2\r", &mut b)?, (0, 0));
191
        assert_eq!(d.left(), 0);
192
        assert_eq!(d.parse_input(b"2\r\n", &mut b)?, (3, 0));
193
        assert_eq!(d.left(), 2);
194
        Ok(())
195
    }
196
197
    #[test]
198
    fn test_dechunk_size_meta() -> Result<(), Error> {
199
        let mut d = Dechunker::new();
200
        let mut b = [0; 1024];
201
        assert_eq!(d.parse_input(b"2;meta\r", &mut b)?, (0, 0));
202
        assert_eq!(d.parse_input(b"2;meta\r\n", &mut b)?, (8, 0));
203
        Ok(())
204
    }
205
206
    #[test]
207
    fn test_dechunk_size_not_meta() -> Result<(), Error> {
208
        let mut d = Dechunker::new();
209
        let mut b = [0; 1024];
210
        assert_eq!(d.parse_input(b"9\r\nnot meta;\r\n", &mut b)?, (14, 9));
211
        assert_eq!(String::from_utf8_lossy(&b[..9]), "not meta;");
212
        Ok(())
213
    }
214
215
    #[test]
216
    #[cfg(feature = "client")]
217
    fn test_dechunk_data() -> Result<(), Error> {
218
        let mut d = Dechunker::new();
219
        let mut b = [0; 1024];
220
        assert_eq!(d.parse_input(b"2\r\nOK", &mut b)?, (5, 2));
221
        assert_eq!(&b[..2], b"OK");
222
        assert_eq!(d.left(), 0);
223
        assert_eq!(d.parse_input(b"\r\n", &mut b)?, (2, 0));
224
        assert_eq!(d.left(), 0);
225
        assert!(!d.is_ended());
226
        assert!(!d.is_ending());
227
        assert_eq!(d.parse_input(b"0\r\n", &mut b)?, (3, 0));
228
        assert!(!d.is_ended());
229
        assert!(d.is_ending());
230
        assert_eq!(d.parse_input(b"\r\n", &mut b)?, (2, 0));
231
        assert!(d.is_ended());
232
        assert!(!d.is_ending());
233
        Ok(())
234
    }
235
236
    #[test]
237
    fn test_dechunk_one_chunk_at_a_time() -> Result<(), Error> {
238
        let mut d = Dechunker::new();
239
        let mut b = [0; 1024];
240
        const DATA: &[u8] = b"4\r\ndata\r\n4\r\nmoar\r\n";
241
        assert_eq!(d.parse_input(DATA, &mut b)?, (9, 4));
242
243
        // Stop reading on every chunk boundary.
244
        assert!(d.is_on_chunk_boundary());
245
246
        assert_eq!(String::from_utf8_lossy(&b[..4]), "data");
247
        Ok(())
248
    }
249
}