Skip to main content

qubit_io/ext/
buf_read_ext.rs

1// =============================================================================
2//    Copyright (c) 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8use std::io::{
9    BufRead,
10    Error,
11    ErrorKind,
12    Result,
13};
14
15use crate::ext::internal::read_ext_impl;
16use crate::util::{
17    try_reserve_string,
18    try_reserve_vec,
19};
20
21/// Extension methods for [`BufRead`] values.
22///
23/// `BufReadExt` provides bounded delimiter-oriented reads. These helpers are
24/// useful for line-based and delimiter-based formats where accepting unbounded
25/// input would make parsers vulnerable to excessive memory use.
26pub trait BufReadExt: BufRead {
27    /// Reads bytes through `delimiter` while enforcing `max_len`.
28    ///
29    /// The returned vector includes the delimiter when it is found. EOF before
30    /// the delimiter is accepted as long as the accumulated bytes do not exceed
31    /// `max_len`. If the limit is exceeded, no vector is returned and the
32    /// reader may still consume bytes while detecting the violation.
33    ///
34    /// # Parameters
35    /// - `delimiter`: Delimiter byte to search for.
36    /// - `max_len`: Maximum accepted result length, including the delimiter.
37    ///
38    /// # Returns
39    /// Bytes read from the stream.
40    ///
41    /// # Errors
42    /// Returns [`ErrorKind::InvalidData`] when more than `max_len` bytes are
43    /// required before reaching `delimiter` or EOF. Returns the first I/O error
44    /// reported by the underlying reader.
45    fn read_until_limited(
46        &mut self,
47        delimiter: u8,
48        max_len: usize,
49    ) -> Result<Vec<u8>>;
50
51    /// Reads bytes through `delimiter` into `output` while enforcing `max_len`.
52    ///
53    /// This method appends at most `max_len` bytes from the current reader
54    /// position to `output`. The delimiter is included when it is found. If the
55    /// limit is exceeded, `output` is truncated back to its original length.
56    /// The reader may still consume bytes while detecting the violation.
57    ///
58    /// # Parameters
59    /// - `delimiter`: Delimiter byte to search for.
60    /// - `output`: Destination vector to append to.
61    /// - `max_len`: Maximum accepted result length, including the delimiter.
62    ///
63    /// # Returns
64    /// Number of bytes appended to `output`.
65    ///
66    /// # Errors
67    /// Returns [`ErrorKind::InvalidData`] when more than `max_len` bytes are
68    /// required before reaching `delimiter` or EOF. Returns the first I/O error
69    /// reported by the underlying reader.
70    fn read_until_limited_into(
71        &mut self,
72        delimiter: u8,
73        output: &mut Vec<u8>,
74        max_len: usize,
75    ) -> Result<usize>;
76
77    /// Reads one UTF-8 line while enforcing `max_len`.
78    ///
79    /// The returned string includes the trailing `\n` when it is present. EOF
80    /// before a newline is accepted as long as the accumulated bytes do not
81    /// exceed `max_len`.
82    ///
83    /// # Parameters
84    /// - `max_len`: Maximum accepted line length in bytes, including `\n`.
85    ///
86    /// # Returns
87    /// The decoded UTF-8 line.
88    ///
89    /// # Errors
90    /// Returns [`ErrorKind::InvalidData`] when the line exceeds `max_len` or is
91    /// not valid UTF-8. Returns the first I/O error reported by the underlying
92    /// reader.
93    fn read_line_limited(&mut self, max_len: usize) -> Result<String>;
94
95    /// Reads one UTF-8 line into `output` while enforcing `max_len`.
96    ///
97    /// This method reads at most `max_len` bytes, validates the line as UTF-8,
98    /// and appends it to `output`. If the line is oversized or invalid UTF-8,
99    /// `output` is truncated back to its original length. Oversized input may
100    /// still consume bytes from the reader while detecting the limit violation.
101    ///
102    /// # Parameters
103    /// - `output`: Destination string to append to.
104    /// - `max_len`: Maximum accepted line length in bytes, including `\n`.
105    ///
106    /// # Returns
107    /// Number of bytes appended to `output`.
108    ///
109    /// # Errors
110    /// Returns [`ErrorKind::InvalidData`] when the line exceeds `max_len` or is
111    /// not valid UTF-8. Returns the first I/O error reported by the underlying
112    /// reader.
113    fn read_line_limited_into(
114        &mut self,
115        output: &mut String,
116        max_len: usize,
117    ) -> Result<usize>;
118
119    /// Discards bytes through `delimiter` while enforcing `max_len`.
120    ///
121    /// The delimiter is consumed when it is found. EOF before the delimiter is
122    /// accepted as long as no more than `max_len` bytes are consumed.
123    ///
124    /// # Parameters
125    /// - `delimiter`: Delimiter byte to search for.
126    /// - `max_len`: Maximum number of bytes to discard, including the
127    ///   delimiter.
128    ///
129    /// # Returns
130    /// Number of bytes discarded.
131    ///
132    /// # Errors
133    /// Returns [`ErrorKind::InvalidData`] when more than `max_len` bytes are
134    /// required before reaching `delimiter` or EOF. Returns the first I/O error
135    /// reported by the underlying reader.
136    fn discard_until_limited(
137        &mut self,
138        delimiter: u8,
139        max_len: usize,
140    ) -> Result<usize>;
141}
142
143impl<T> BufReadExt for T
144where
145    T: BufRead + ?Sized,
146{
147    #[inline]
148    fn read_until_limited(
149        &mut self,
150        delimiter: u8,
151        max_len: usize,
152    ) -> Result<Vec<u8>> {
153        let mut output = Vec::new();
154        try_reserve_vec(&mut output, max_len.min(8192))?;
155        read_ext_impl::read_until_limited_into(
156            self,
157            delimiter,
158            &mut output,
159            max_len,
160        )?;
161        Ok(output)
162    }
163
164    #[inline]
165    fn read_until_limited_into(
166        &mut self,
167        delimiter: u8,
168        output: &mut Vec<u8>,
169        max_len: usize,
170    ) -> Result<usize> {
171        read_ext_impl::read_until_limited_into(self, delimiter, output, max_len)
172    }
173
174    #[inline]
175    fn read_line_limited(&mut self, max_len: usize) -> Result<String> {
176        let mut output = String::new();
177        self.read_line_limited_into(&mut output, max_len)?;
178        Ok(output)
179    }
180
181    fn read_line_limited_into(
182        &mut self,
183        output: &mut String,
184        max_len: usize,
185    ) -> Result<usize> {
186        let original_len = output.len();
187        let mut bytes = Vec::new();
188        try_reserve_vec(&mut bytes, max_len.min(8192))?;
189        let result = (|| {
190            let count = read_ext_impl::read_until_limited_into(
191                self, b'\n', &mut bytes, max_len,
192            )?;
193            let line = String::from_utf8(bytes).map_err(|error| {
194                Error::new(
195                    ErrorKind::InvalidData,
196                    format!("limited line is not valid UTF-8: {error}"),
197                )
198            })?;
199            try_reserve_string(output, line.len())?;
200            output.push_str(&line);
201            Ok(count)
202        })();
203        if result.is_err() {
204            output.truncate(original_len);
205        }
206        result
207    }
208
209    fn discard_until_limited(
210        &mut self,
211        delimiter: u8,
212        max_len: usize,
213    ) -> Result<usize> {
214        let mut discarded = 0;
215        loop {
216            let available = self.fill_buf()?;
217            if available.is_empty() {
218                return Ok(discarded);
219            }
220
221            let delimiter_position =
222                available.iter().position(|byte| *byte == delimiter);
223            let requested = delimiter_position
224                .map_or(available.len(), |position| position + 1);
225            let remaining = max_len.saturating_sub(discarded);
226            if requested > remaining {
227                if remaining > 0 {
228                    self.consume(remaining);
229                }
230                return Err(Error::new(
231                    ErrorKind::InvalidData,
232                    format!(
233                        "input exceeds maximum length of {max_len} bytes before delimiter {delimiter}"
234                    ),
235                ));
236            }
237
238            self.consume(requested);
239            discarded += requested;
240            if delimiter_position.is_some() {
241                return Ok(discarded);
242            }
243        }
244    }
245}