Skip to main content

parse_net/
error.rs

1//! The error types: [`ParseError`] for refused input, [`WriteError`] for
2//! refused output.
3//!
4//! Both carry a kind and, where it applies, a byte offset. Neither ever holds
5//! input bytes, so either is safe to log: an attacker cannot use an error
6//! message to inject text into a log line.
7
8use core::fmt;
9
10/// What a parser refused.
11///
12/// Every variant names what was refused. Unless a variant says otherwise, the
13/// caller's response is the same: treat the field as malformed. For a request
14/// field that means refusing the request (HTTP 400) or ignoring the field
15/// where its RFC allows that; for a field a server produced, it means a bug
16/// in the producer.
17///
18/// The enum is `#[non_exhaustive]`: new kinds may be added in minor versions.
19///
20/// # Examples
21///
22/// ```
23/// use parse_net::{scan, ErrorKind};
24///
25/// let err = scan::check_field_value(b"a\rb").unwrap_err();
26/// assert_eq!(err.kind(), ErrorKind::BareCr);
27/// assert_eq!(ErrorKind::BareCr.message(), "bare CR");
28/// ```
29#[non_exhaustive]
30#[repr(u8)]
31#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
32pub enum ErrorKind {
33    /// A value or element that must not be empty was empty.
34    Empty,
35    /// Bytes that must form a token (RFC 9110 section 5.6.2) do not.
36    NotToken,
37    /// A byte not allowed at this position.
38    InvalidByte,
39    /// A CR byte. CR is a request-smuggling vector: refuse the whole message,
40    /// do not just drop the field.
41    BareCr,
42    /// An LF byte inside a value. Refuse the whole message.
43    LineFeed,
44    /// A NUL byte. Refuse the whole message.
45    Nul,
46    /// Another control byte (0x01 to 0x1F except HTAB, or DEL).
47    Control,
48    /// A quoted string with no closing `"`, or one that would have to
49    /// continue on the next field line.
50    UnterminatedQuote,
51    /// The byte after `\` in a quoted string is not `HTAB / SP / VCHAR /
52    /// obs-text`.
53    InvalidQuotedPair,
54    /// A `(` without its `)`, or a `)` without its `(`.
55    UnbalancedComment,
56    /// Comment nesting deeper than [`Limits::max_comment_depth`](crate::Limits::max_comment_depth).
57    CommentTooDeep,
58    /// More list members, parameters, dictionary members, or encoded words
59    /// than the configured limit allows.
60    TooManyMembers,
61    /// More empty list elements (`a, , , b`) than
62    /// [`Limits::max_empty_elements`](crate::Limits::max_empty_elements).
63    TooManyEmptyElements,
64    /// A separator with nothing after it where the grammar forbids that.
65    TrailingSeparator,
66    /// Two members without the separator between them.
67    ExpectedSeparator,
68    /// Bytes after a complete value.
69    TrailingData,
70    /// A parameter name repeated where repetition is refused.
71    DuplicateParameter,
72    /// A name without `=value` where a value is required.
73    MissingValue,
74    /// Digits were expected; a sign, space, or other byte was found.
75    InvalidNumber,
76    /// A number larger than its type or the RFC allows.
77    NumberOverflow,
78    /// A value outside its valid range, such as a year outside 1 to 9999.
79    OutOfRange,
80    /// A date that does not exist, or that matches no accepted format.
81    InvalidDate,
82    /// The weekday does not agree with the date.
83    WeekdayMismatch,
84    /// A media type that does not match `type "/" subtype parameters`.
85    InvalidMediaType,
86    /// `*` as a type or subtype where a concrete media type is required.
87    Wildcard,
88    /// A `boundary` parameter that breaks RFC 2046 section 5.1.1.
89    InvalidBoundary,
90    /// A `q` value outside the RFC 9110 section 12.4.2 grammar.
91    InvalidQValue,
92    /// An entity tag outside `[ "W/" ] DQUOTE *etagc DQUOTE`.
93    InvalidEntityTag,
94    /// A range set or `Content-Range` that is malformed or inconsistent.
95    InvalidRange,
96    /// A malformed charset name.
97    InvalidCharset,
98    /// A well-formed charset that parse-net does not decode. Only UTF-8,
99    /// US-ASCII, and ISO-8859-1 are decoded.
100    UnsupportedCharset,
101    /// A `%` not followed by two hex digits, or uppercase hex where only
102    /// lowercase is allowed.
103    InvalidPercentEncoding,
104    /// Bytes that must be UTF-8 are not (overlong forms and surrogates
105    /// included).
106    InvalidUtf8,
107    /// A decoded value contains a control byte. Never copy such a value into
108    /// another header or a log.
109    ControlInDecoded,
110    /// A host that is not a reg-name, IPv4 address, or IP literal, or that
111    /// carries userinfo (`@`).
112    InvalidHost,
113    /// A port that is not digits, or is above 65535.
114    InvalidPort,
115    /// An IP literal that is not a valid address, or a name made only of
116    /// digits and dots that is not a valid IPv4 address.
117    InvalidAddress,
118    /// An `Origin` that is not `null` or `scheme "://" host [ ":" port ]`.
119    InvalidOrigin,
120    /// A Structured Field key outside `( lcalpha / "*" ) *( lcalpha / DIGIT /
121    /// "_" / "-" / "." / "*" )`.
122    InvalidKey,
123    /// A string, token, key, byte sequence, address, or local part longer
124    /// than its limit.
125    TooLong,
126    /// Bytes outside the base64 alphabet, or a misplaced `=`.
127    InvalidBase64,
128    /// A malformed RFC 2047 encoded word.
129    InvalidEncodedWord,
130    /// RFC 2231 sections that are missing, repeated, or out of order, or a
131    /// charset on a section other than 0.
132    SectionGap,
133    /// More RFC 2231 sections than
134    /// [`Limits::max_rfc2231_sections`](crate::Limits::max_rfc2231_sections).
135    TooManySections,
136    /// An address outside the strict RFC 5322 `addr-spec`.
137    InvalidAddrSpec,
138    /// An email header line over 998 bytes.
139    LineTooLong,
140    /// A structure that is wrong as a whole. For a forwarded chain, use the
141    /// peer address.
142    Malformed,
143    /// A forwarded chain longer than the hop limit. Use the peer address.
144    TooManyHops,
145    /// The output buffer is shorter than the decoded value. Nothing was
146    /// written; retry with a buffer of the size the matching `*_len` function
147    /// reports.
148    BufferTooSmall,
149}
150
151impl ErrorKind {
152    /// The fixed text [`ParseError`]'s `Display` uses for this kind.
153    ///
154    /// # Examples
155    ///
156    /// ```
157    /// assert_eq!(parse_net::ErrorKind::InvalidByte.message(), "invalid byte");
158    /// ```
159    #[must_use]
160    pub const fn message(self) -> &'static str {
161        match self {
162            Self::Empty => "empty value",
163            Self::NotToken => "not a token",
164            Self::InvalidByte => "invalid byte",
165            Self::BareCr => "bare CR",
166            Self::LineFeed => "line feed in value",
167            Self::Nul => "NUL byte",
168            Self::Control => "control byte",
169            Self::UnterminatedQuote => "unterminated quoted string",
170            Self::InvalidQuotedPair => "invalid quoted pair",
171            Self::UnbalancedComment => "unbalanced comment",
172            Self::CommentTooDeep => "comment nested too deeply",
173            Self::TooManyMembers => "too many members",
174            Self::TooManyEmptyElements => "too many empty list elements",
175            Self::TrailingSeparator => "trailing separator",
176            Self::ExpectedSeparator => "expected a separator",
177            Self::TrailingData => "trailing data",
178            Self::DuplicateParameter => "duplicate parameter",
179            Self::MissingValue => "missing value",
180            Self::InvalidNumber => "invalid number",
181            Self::NumberOverflow => "number overflow",
182            Self::OutOfRange => "value out of range",
183            Self::InvalidDate => "invalid date",
184            Self::WeekdayMismatch => "weekday does not match the date",
185            Self::InvalidMediaType => "invalid media type",
186            Self::Wildcard => "wildcard where a concrete value is required",
187            Self::InvalidBoundary => "invalid multipart boundary",
188            Self::InvalidQValue => "invalid quality value",
189            Self::InvalidEntityTag => "invalid entity tag",
190            Self::InvalidRange => "invalid range",
191            Self::InvalidCharset => "invalid charset",
192            Self::UnsupportedCharset => "unsupported charset",
193            Self::InvalidPercentEncoding => "invalid percent-encoding",
194            Self::InvalidUtf8 => "invalid UTF-8",
195            Self::ControlInDecoded => "control byte in decoded value",
196            Self::InvalidHost => "invalid host",
197            Self::InvalidPort => "invalid port",
198            Self::InvalidAddress => "invalid IP address",
199            Self::InvalidOrigin => "invalid origin",
200            Self::InvalidKey => "invalid structured field key",
201            Self::TooLong => "value too long",
202            Self::InvalidBase64 => "invalid base64",
203            Self::InvalidEncodedWord => "invalid encoded word",
204            Self::SectionGap => "RFC 2231 sections out of order",
205            Self::TooManySections => "too many RFC 2231 sections",
206            Self::InvalidAddrSpec => "invalid email address",
207            Self::LineTooLong => "line too long",
208            Self::Malformed => "malformed field",
209            Self::TooManyHops => "too many forwarding hops",
210            Self::BufferTooSmall => "output buffer too small",
211        }
212    }
213}
214
215impl fmt::Display for ErrorKind {
216    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
217        f.write_str(self.message())
218    }
219}
220
221/// Why a value was refused, and where.
222///
223/// Eight bytes, `Copy`, and it never contains input bytes, so it is safe to
224/// log.
225///
226/// # Examples
227///
228/// ```
229/// use parse_net::{ErrorKind, ParseError};
230///
231/// let err = ParseError::new(ErrorKind::InvalidByte, 4);
232/// assert_eq!(err.kind(), ErrorKind::InvalidByte);
233/// assert_eq!(err.offset(), 4);
234/// assert_eq!(err.to_string(), "invalid byte at offset 4");
235/// assert_eq!(core::mem::size_of::<ParseError>(), 8);
236/// ```
237#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
238pub struct ParseError {
239    kind: ErrorKind,
240    line: u8,
241    at: u32,
242}
243
244impl ParseError {
245    /// Builds an error of `kind` at byte `offset`.
246    ///
247    /// For callers that implement a visitor or wrap parse-net. Offsets above
248    /// `u32::MAX` saturate.
249    #[must_use]
250    pub const fn new(kind: ErrorKind, offset: usize) -> Self {
251        let at = if offset > u32::MAX as usize {
252            u32::MAX
253        } else {
254            offset as u32
255        };
256        Self { kind, line: 0, at }
257    }
258
259    /// What was refused.
260    #[must_use]
261    pub const fn kind(&self) -> ErrorKind {
262        self.kind
263    }
264
265    /// The offset of the first byte that could not be accepted, relative to
266    /// the start of the value passed in, or of the field line named by
267    /// [`line`](Self::line) when several lines were parsed as one field.
268    #[must_use]
269    pub const fn offset(&self) -> usize {
270        self.at as usize
271    }
272
273    /// The zero-based index of the field line that failed when several lines
274    /// were parsed as one field; `0` otherwise. Saturates at 255.
275    #[must_use]
276    pub const fn line(&self) -> usize {
277        self.line as usize
278    }
279
280    /// The same error, attributed to field line `line` (saturating at 255).
281    pub(crate) const fn on_line(self, line: usize) -> Self {
282        let line = if line > u8::MAX as usize {
283            u8::MAX
284        } else {
285            line as u8
286        };
287        Self { line, ..self }
288    }
289
290    /// The same error with its offset moved forward by `by` bytes
291    /// (saturating), for errors found in a sub-slice.
292    pub(crate) const fn shifted(self, by: usize) -> Self {
293        let by = if by > u32::MAX as usize {
294            u32::MAX
295        } else {
296            by as u32
297        };
298        Self {
299            at: self.at.saturating_add(by),
300            ..self
301        }
302    }
303}
304
305impl fmt::Display for ParseError {
306    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
307        if self.line == 0 {
308            write!(f, "{} at offset {}", self.kind.message(), self.at)
309        } else {
310            write!(
311                f,
312                "{} at offset {} of field line {}",
313                self.kind.message(),
314                self.at,
315                self.line
316            )
317        }
318    }
319}
320
321impl core::error::Error for ParseError {}
322
323/// The result type of every parser.
324pub(crate) type Result<T> = core::result::Result<T, ParseError>;
325
326/// Shorthand for an `Err(ParseError)`.
327pub(crate) const fn err<T>(kind: ErrorKind, at: usize) -> Result<T> {
328    Err(ParseError::new(kind, at))
329}
330
331/// A sink has no room for the bytes it was given.
332///
333/// Returned by [`Sink::put`](crate::Sink::put); writers turn it into
334/// [`WriteError::Full`].
335///
336/// # Examples
337///
338/// ```
339/// use parse_net::{Full, Sink, SliceSink};
340///
341/// let mut buf = [0u8; 2];
342/// let mut out = SliceSink::new(&mut buf);
343/// assert_eq!(out.put(b"abc"), Err(Full));
344/// ```
345#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
346pub struct Full;
347
348impl fmt::Display for Full {
349    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
350        f.write_str("output sink is full")
351    }
352}
353
354impl core::error::Error for Full {}
355
356/// Why a writer refused.
357///
358/// # Examples
359///
360/// ```
361/// use parse_net::{write, SliceSink, WriteError};
362///
363/// let mut buf = [0u8; 64];
364/// let mut out = SliceSink::new(&mut buf);
365/// let err = write::write_field_value(b"evil\r\nSet-Cookie: x=1", &mut out).unwrap_err();
366/// assert_eq!(err, WriteError::Forbidden { at: 4 });
367/// assert!(out.written().is_empty());
368/// ```
369#[non_exhaustive]
370#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
371pub enum WriteError {
372    /// The sink has no room. When the sink reports its room (as
373    /// [`SliceSink`](crate::SliceSink) does), nothing was written. Retry with
374    /// a larger buffer; [`CountSink`](crate::CountSink) gives the exact
375    /// length.
376    Full,
377    /// The input byte at offset `at` is not allowed in the output grammar
378    /// (CR, LF, NUL, another control, or a byte the format forbids). Refuse
379    /// the input; never strip it and retry silently.
380    Forbidden {
381        /// Offset of the refused byte in the input.
382        at: u32,
383    },
384    /// A number outside the format's range.
385    OutOfRange,
386    /// A position that requires a token got something else.
387    NotToken,
388    /// A structured writer was called out of order, such as a parameter
389    /// before any item. A bug in the calling code.
390    InvalidSequence,
391}
392
393impl WriteError {
394    /// A `Forbidden` error at byte `at` (saturating).
395    pub(crate) const fn forbidden(at: usize) -> Self {
396        let at = if at > u32::MAX as usize {
397            u32::MAX
398        } else {
399            at as u32
400        };
401        Self::Forbidden { at }
402    }
403}
404
405impl From<Full> for WriteError {
406    fn from(_: Full) -> Self {
407        Self::Full
408    }
409}
410
411impl fmt::Display for WriteError {
412    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
413        match self {
414            Self::Full => f.write_str("output sink is full"),
415            Self::Forbidden { at } => write!(f, "forbidden byte at offset {at}"),
416            Self::OutOfRange => f.write_str("value out of range"),
417            Self::NotToken => f.write_str("not a token"),
418            Self::InvalidSequence => f.write_str("writer called out of order"),
419        }
420    }
421}
422
423impl core::error::Error for WriteError {}
424
425#[cfg(test)]
426mod tests {
427    use std::string::ToString;
428
429    use super::*;
430
431    #[test]
432    fn test_parse_error_size_is_eight_bytes() {
433        assert_eq!(core::mem::size_of::<ParseError>(), 8);
434        assert_eq!(core::mem::size_of::<ErrorKind>(), 1);
435    }
436
437    #[test]
438    fn test_parse_error_offset_saturates() {
439        let e = ParseError::new(ErrorKind::Empty, usize::MAX);
440        assert_eq!(e.offset(), u32::MAX as usize);
441        assert_eq!(e.shifted(10).offset(), u32::MAX as usize);
442        assert_eq!(e.on_line(1000).line(), 255);
443    }
444
445    #[test]
446    fn test_parse_error_display_with_line() {
447        let e = ParseError::new(ErrorKind::InvalidByte, 3).on_line(2);
448        assert_eq!(e.to_string(), "invalid byte at offset 3 of field line 2");
449        assert_eq!(e.shifted(4).offset(), 7);
450    }
451
452    #[test]
453    fn test_every_message_is_nonempty_and_lowercase_led() {
454        use ErrorKind::*;
455        let all = [
456            Empty,
457            NotToken,
458            InvalidByte,
459            BareCr,
460            LineFeed,
461            Nul,
462            Control,
463            UnterminatedQuote,
464            InvalidQuotedPair,
465            UnbalancedComment,
466            CommentTooDeep,
467            TooManyMembers,
468            TooManyEmptyElements,
469            TrailingSeparator,
470            ExpectedSeparator,
471            TrailingData,
472            DuplicateParameter,
473            MissingValue,
474            InvalidNumber,
475            NumberOverflow,
476            OutOfRange,
477            InvalidDate,
478            WeekdayMismatch,
479            InvalidMediaType,
480            Wildcard,
481            InvalidBoundary,
482            InvalidQValue,
483            InvalidEntityTag,
484            InvalidRange,
485            InvalidCharset,
486            UnsupportedCharset,
487            InvalidPercentEncoding,
488            InvalidUtf8,
489            ControlInDecoded,
490            InvalidHost,
491            InvalidPort,
492            InvalidAddress,
493            InvalidOrigin,
494            InvalidKey,
495            TooLong,
496            InvalidBase64,
497            InvalidEncodedWord,
498            SectionGap,
499            TooManySections,
500            InvalidAddrSpec,
501            LineTooLong,
502            Malformed,
503            TooManyHops,
504            BufferTooSmall,
505        ];
506        for k in all {
507            let m = k.message();
508            assert!(!m.is_empty());
509            assert_eq!(k.to_string(), m);
510        }
511    }
512
513    #[test]
514    fn test_write_error_display_and_from_full() {
515        assert_eq!(WriteError::from(Full), WriteError::Full);
516        assert_eq!(
517            WriteError::forbidden(7).to_string(),
518            "forbidden byte at offset 7"
519        );
520        assert_eq!(
521            WriteError::forbidden(usize::MAX),
522            WriteError::Forbidden { at: u32::MAX }
523        );
524        assert_eq!(Full.to_string(), "output sink is full");
525        assert_eq!(
526            WriteError::InvalidSequence.to_string(),
527            "writer called out of order"
528        );
529    }
530}