Skip to main content

tokio_util/codec/
lines_codec.rs

1use crate::codec::decoder::Decoder;
2use crate::codec::encoder::Encoder;
3
4use bytes::{Buf, BufMut, BytesMut};
5use std::{cmp, fmt, io, str};
6
7/// A simple [`Decoder`] and [`Encoder`] implementation that splits up data into lines.
8///
9/// This uses the `\n` character as the line ending on all platforms.
10///
11/// [`Decoder`]: crate::codec::Decoder
12/// [`Encoder`]: crate::codec::Encoder
13#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
14pub struct LinesCodec {
15    // Stored index of the next index to examine for a `\n` character.
16    // This is used to optimize searching.
17    // For example, if `decode` was called with `abc`, it would hold `3`,
18    // because that is the next index to examine.
19    // The next time `decode` is called with `abcde\n`, the method will
20    // only look at `de\n` before returning.
21    next_index: usize,
22
23    /// The maximum length for a given line. If `usize::MAX`, lines will be
24    /// read until a `\n` character is reached.
25    max_length: usize,
26
27    /// Are we currently discarding the remainder of a line which was over
28    /// the length limit?
29    is_discarding: bool,
30}
31
32impl LinesCodec {
33    /// Returns a `LinesCodec` for splitting up data into lines.
34    ///
35    /// # Note
36    ///
37    /// The returned `LinesCodec` will not have an upper bound on the length
38    /// of a buffered line. See the documentation for [`new_with_max_length`]
39    /// for information on why this could be a potential security risk.
40    ///
41    /// [`new_with_max_length`]: crate::codec::LinesCodec::new_with_max_length()
42    pub fn new() -> LinesCodec {
43        LinesCodec {
44            next_index: 0,
45            max_length: usize::MAX,
46            is_discarding: false,
47        }
48    }
49
50    /// Returns a `LinesCodec` with a maximum line length limit.
51    ///
52    /// If this is set, calls to `LinesCodec::decode` will return a
53    /// [`LinesCodecError`] when a line exceeds the length limit. Subsequent calls
54    /// will discard up to `limit` bytes from that line until a newline
55    /// character is reached, returning `None` until the line over the limit
56    /// has been fully discarded. After that point, calls to `decode` will
57    /// function as normal.
58    ///
59    /// # Note
60    ///
61    /// Setting a length limit is highly recommended for any `LinesCodec` which
62    /// will be exposed to untrusted input. Otherwise, the size of the buffer
63    /// that holds the line currently being read is unbounded. An attacker could
64    /// exploit this unbounded buffer by sending an unbounded amount of input
65    /// without any `\n` characters, causing unbounded memory consumption.
66    ///
67    /// [`LinesCodecError`]: crate::codec::LinesCodecError
68    pub fn new_with_max_length(max_length: usize) -> Self {
69        LinesCodec {
70            max_length,
71            ..LinesCodec::new()
72        }
73    }
74
75    /// Returns the maximum line length when decoding.
76    ///
77    /// ```
78    /// use std::usize;
79    /// use tokio_util::codec::LinesCodec;
80    ///
81    /// let codec = LinesCodec::new();
82    /// assert_eq!(codec.max_length(), usize::MAX);
83    /// ```
84    /// ```
85    /// use tokio_util::codec::LinesCodec;
86    ///
87    /// let codec = LinesCodec::new_with_max_length(256);
88    /// assert_eq!(codec.max_length(), 256);
89    /// ```
90    pub fn max_length(&self) -> usize {
91        self.max_length
92    }
93}
94
95fn utf8(buf: &[u8]) -> Result<&str, io::Error> {
96    str::from_utf8(buf)
97        .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Unable to decode input as UTF8"))
98}
99
100fn without_carriage_return(s: &[u8]) -> &[u8] {
101    if let Some(&b'\r') = s.last() {
102        &s[..s.len() - 1]
103    } else {
104        s
105    }
106}
107
108impl Decoder for LinesCodec {
109    type Item = String;
110    type Error = LinesCodecError;
111
112    fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<String>, LinesCodecError> {
113        loop {
114            // Determine how far into the buffer we'll search for a newline. If
115            // there's no max_length set, we'll read to the end of the buffer.
116            let read_to = cmp::min(self.max_length.saturating_add(1), buf.len());
117
118            let newline_offset = crate::util::memchr::memchr(b'\n', &buf[self.next_index..read_to]);
119
120            match (self.is_discarding, newline_offset) {
121                (true, Some(offset)) => {
122                    // If we found a newline, discard up to that offset and
123                    // then stop discarding. On the next iteration, we'll try
124                    // to read a line normally.
125                    buf.advance(offset + self.next_index + 1);
126                    self.is_discarding = false;
127                    self.next_index = 0;
128                }
129                (true, None) => {
130                    // Otherwise, we didn't find a newline, so we'll discard
131                    // everything we read. On the next iteration, we'll continue
132                    // discarding up to max_len bytes unless we find a newline.
133                    buf.advance(read_to);
134                    self.next_index = 0;
135                    if buf.is_empty() {
136                        return Ok(None);
137                    }
138                }
139                (false, Some(offset)) => {
140                    // Found a line!
141                    let newline_index = offset + self.next_index;
142                    self.next_index = 0;
143                    let line = buf.split_to(newline_index + 1);
144                    let line = &line[..line.len() - 1];
145                    let line = without_carriage_return(line);
146                    let line = utf8(line)?;
147                    return Ok(Some(line.to_string()));
148                }
149                (false, None) if buf.len() > self.max_length => {
150                    // Reached the maximum length without finding a
151                    // newline, return an error and start discarding on the
152                    // next call.
153                    self.is_discarding = true;
154                    return Err(LinesCodecError::MaxLineLengthExceeded);
155                }
156                (false, None) => {
157                    // We didn't find a line or reach the length limit, so the next
158                    // call will resume searching at the current offset.
159                    self.next_index = read_to;
160                    return Ok(None);
161                }
162            }
163        }
164    }
165
166    fn decode_eof(&mut self, buf: &mut BytesMut) -> Result<Option<String>, LinesCodecError> {
167        Ok(match self.decode(buf)? {
168            Some(frame) => Some(frame),
169            None => {
170                self.next_index = 0;
171                // No terminating newline - return remaining data, if any
172                if buf.is_empty() || buf == &b"\r"[..] {
173                    None
174                } else {
175                    let line = buf.split_to(buf.len());
176                    let line = without_carriage_return(&line);
177                    let line = utf8(line)?;
178                    Some(line.to_string())
179                }
180            }
181        })
182    }
183}
184
185impl<T> Encoder<T> for LinesCodec
186where
187    T: AsRef<str>,
188{
189    type Error = LinesCodecError;
190
191    fn encode(&mut self, line: T, buf: &mut BytesMut) -> Result<(), LinesCodecError> {
192        let line = line.as_ref();
193        buf.reserve(line.len() + 1);
194        buf.put(line.as_bytes());
195        buf.put_u8(b'\n');
196        Ok(())
197    }
198}
199
200impl Default for LinesCodec {
201    fn default() -> Self {
202        Self::new()
203    }
204}
205
206/// An error occurred while encoding or decoding a line.
207#[derive(Debug)]
208pub enum LinesCodecError {
209    /// The maximum line length was exceeded.
210    MaxLineLengthExceeded,
211    /// An IO error occurred.
212    Io(io::Error),
213}
214
215impl fmt::Display for LinesCodecError {
216    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
217        match self {
218            LinesCodecError::MaxLineLengthExceeded => write!(f, "max line length exceeded"),
219            LinesCodecError::Io(e) => write!(f, "{e}"),
220        }
221    }
222}
223
224impl From<io::Error> for LinesCodecError {
225    fn from(e: io::Error) -> LinesCodecError {
226        LinesCodecError::Io(e)
227    }
228}
229
230impl std::error::Error for LinesCodecError {}