Skip to main content

git_zero_rustix/network/server/
chunked.rs

1//! Bounded, linear chunked transfer intake with in-place body compaction.
2use git_zero::{Error, Result};
3
4/// Extra work limits for opt-in chunked intake. All wire framing also consumes
5/// the ordinary wire-byte quota; decoded identity bytes consume the body quota.
6#[derive(Clone, Copy, Debug, Eq, PartialEq)]
7pub struct ChunkedLimits {
8    /// Nonzero data chunks, excluding the final zero chunk.
9    pub max_chunks: usize,
10    /// Hexadecimal size line including its CRLF. Extensions are unsupported.
11    pub max_line_bytes: usize,
12}
13
14impl Default for ChunkedLimits {
15    fn default() -> Self {
16        Self {
17            max_chunks: 1024,
18            max_line_bytes: 32,
19        }
20    }
21}
22
23struct Intake<'a, 'read, F> {
24    receive: &'read mut F,
25    storage: &'a mut [u8],
26    length: usize,
27    position: usize,
28    wire_limit: Option<usize>,
29}
30
31impl<F: FnMut(&mut [u8]) -> Result<usize>> Intake<'_, '_, F> {
32    fn more(&mut self) -> Result<()> {
33        let capacity = self
34            .wire_limit
35            .map_or(self.storage.len(), |limit| limit.min(self.storage.len()));
36        if self.length >= capacity {
37            return Err(if self.wire_limit == Some(capacity) {
38                Error::LimitExceeded
39            } else {
40                Error::BufferTooSmall
41            });
42        }
43        let count = (self.receive)(&mut self.storage[self.length..capacity])?;
44        if count == 0 {
45            return Err(Error::InvalidRequest);
46        }
47        if count > capacity - self.length {
48            return Err(Error::Backend);
49        }
50        self.length += count;
51        Ok(())
52    }
53
54    fn byte(&mut self) -> Result<u8> {
55        if self.position == self.length {
56            self.more()?;
57        }
58        let byte = self.storage[self.position];
59        self.position += 1;
60        Ok(byte)
61    }
62
63    fn size(&mut self, limit: usize) -> Result<usize> {
64        let mut value = 0usize;
65        let mut digits = 0usize;
66        let mut consumed = 0usize;
67        loop {
68            if consumed >= limit {
69                return Err(Error::LimitExceeded);
70            }
71            let byte = self.byte()?;
72            consumed += 1;
73            if byte == b'\r' {
74                if consumed >= limit {
75                    return Err(Error::LimitExceeded);
76                }
77                if self.byte()? != b'\n' || digits == 0 {
78                    return Err(Error::InvalidRequest);
79                }
80                return Ok(value);
81            }
82            if byte == b';' {
83                return Err(Error::Unsupported);
84            }
85            let digit = match byte {
86                b'0'..=b'9' => byte - b'0',
87                b'a'..=b'f' => byte - b'a' + 10,
88                b'A'..=b'F' => byte - b'A' + 10,
89                _ => return Err(Error::InvalidRequest),
90            };
91            value = value
92                .checked_mul(16)
93                .and_then(|value| value.checked_add(usize::from(digit)))
94                .ok_or(Error::InvalidRequest)?;
95            digits += 1;
96        }
97    }
98}
99
100pub(super) fn read(
101    receive: &mut impl FnMut(&mut [u8]) -> Result<usize>,
102    storage: &mut [u8],
103    initial: usize,
104    wire_limit: Option<usize>,
105    body_limit: Option<usize>,
106    limits: ChunkedLimits,
107) -> Result<(usize, usize)> {
108    let mut input = Intake {
109        receive,
110        storage,
111        length: initial,
112        position: 0,
113        wire_limit,
114    };
115    let mut decoded = 0usize;
116    let mut chunks = 0usize;
117    loop {
118        let size = input.size(limits.max_line_bytes)?;
119        if size == 0 {
120            // A bounded host does not interpret authentication/HTTP fields from
121            // trailers. Reject them explicitly, including a second request line.
122            if input.byte()? != b'\r' {
123                return Err(Error::Unsupported);
124            }
125            if input.byte()? != b'\n' {
126                return Err(Error::InvalidRequest);
127            }
128            if input.length != input.position {
129                return Err(Error::InvalidRequest);
130            }
131            return Ok((input.position, decoded));
132        }
133        if chunks >= limits.max_chunks {
134            return Err(Error::LimitExceeded);
135        }
136        chunks += 1;
137        let end = decoded.checked_add(size).ok_or(Error::LimitExceeded)?;
138        if body_limit.is_some_and(|limit| end > limit) {
139            return Err(Error::LimitExceeded);
140        }
141        // Keep the complete encoded wire resident, including final framing.
142        // Reject a declared chunk that cannot fit before trying to read it.
143        let wire_end = input
144            .position
145            .checked_add(size)
146            .and_then(|value| value.checked_add(2))
147            .ok_or(Error::LimitExceeded)?;
148        if wire_limit.is_some_and(|limit| wire_end > limit) {
149            return Err(Error::LimitExceeded);
150        }
151        if wire_end > input.storage.len() {
152            return Err(Error::BufferTooSmall);
153        }
154        let mut remaining = size;
155        while remaining > 0 {
156            if input.position == input.length {
157                input.more()?;
158            }
159            let count = remaining.min(input.length - input.position);
160            input
161                .storage
162                .copy_within(input.position..input.position + count, decoded);
163            input.position += count;
164            decoded += count;
165            remaining -= count;
166        }
167        if input.byte()? != b'\r' || input.byte()? != b'\n' {
168            return Err(Error::InvalidRequest);
169        }
170    }
171}