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    ///
250    /// # Examples
251    ///
252    /// ```
253    /// use parse_net::{ErrorKind, ParseError};
254    ///
255    /// let e = ParseError::new(ErrorKind::TooLong, 12);
256    /// assert_eq!((e.kind(), e.offset(), e.line()), (ErrorKind::TooLong, 12, 0));
257    /// assert_eq!(ParseError::new(ErrorKind::TooLong, usize::MAX).offset(), u32::MAX as usize);
258    /// ```
259    #[must_use]
260    pub const fn new(kind: ErrorKind, offset: usize) -> Self {
261        let at = if offset > u32::MAX as usize {
262            u32::MAX
263        } else {
264            offset as u32
265        };
266        Self { kind, line: 0, at }
267    }
268
269    /// What was refused.
270    ///
271    /// # Examples
272    ///
273    /// ```
274    /// use parse_net::ErrorKind;
275    ///
276    /// let e = parse_net::list(b"a, \"b").unwrap_err();
277    /// assert_eq!(e.kind(), ErrorKind::UnterminatedQuote);
278    /// ```
279    #[must_use]
280    pub const fn kind(&self) -> ErrorKind {
281        self.kind
282    }
283
284    /// The offset of the first byte that could not be accepted, relative to
285    /// the start of the value passed in, or of the field line named by
286    /// [`line`](Self::line) when several lines were parsed as one field.
287    ///
288    /// # Examples
289    ///
290    /// ```
291    /// use parse_net::ErrorKind;
292    ///
293    /// let e = parse_net::list(b"gzip, b\x01r").unwrap_err();
294    /// assert_eq!((e.kind(), e.offset()), (ErrorKind::Control, 7));
295    /// ```
296    #[must_use]
297    pub const fn offset(&self) -> usize {
298        self.at as usize
299    }
300
301    /// The zero-based index of the field line that failed when several lines
302    /// were parsed as one field; `0` otherwise. Saturates at 255.
303    ///
304    /// # Examples
305    ///
306    /// ```
307    /// use parse_net::{list, ErrorKind, Limits};
308    ///
309    /// let lines: [&[u8]; 3] = [b"gzip", b"br", b"x\x00"];
310    /// let e = list::combined(lines, &Limits::DEFAULT).unwrap_err();
311    /// assert_eq!((e.kind(), e.line(), e.offset()), (ErrorKind::Nul, 2, 1));
312    /// ```
313    #[must_use]
314    pub const fn line(&self) -> usize {
315        self.line as usize
316    }
317
318    /// The same error, attributed to field line `line` (saturating at 255).
319    pub(crate) const fn on_line(self, line: usize) -> Self {
320        let line = if line > u8::MAX as usize {
321            u8::MAX
322        } else {
323            line as u8
324        };
325        Self { line, ..self }
326    }
327
328    /// The same error with its offset moved forward by `by` bytes
329    /// (saturating), for errors found in a sub-slice.
330    pub(crate) const fn shifted(self, by: usize) -> Self {
331        let by = if by > u32::MAX as usize {
332            u32::MAX
333        } else {
334            by as u32
335        };
336        Self {
337            at: self.at.saturating_add(by),
338            ..self
339        }
340    }
341}
342
343impl fmt::Display for ParseError {
344    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
345        if self.line == 0 {
346            write!(f, "{} at offset {}", self.kind.message(), self.at)
347        } else {
348            write!(
349                f,
350                "{} at offset {} of field line {}",
351                self.kind.message(),
352                self.at,
353                self.line
354            )
355        }
356    }
357}
358
359impl core::error::Error for ParseError {}
360
361/// The result type of every parser.
362pub(crate) type Result<T> = core::result::Result<T, ParseError>;
363
364/// Shorthand for an `Err(ParseError)`.
365pub(crate) const fn err<T>(kind: ErrorKind, at: usize) -> Result<T> {
366    Err(ParseError::new(kind, at))
367}
368
369/// A sink has no room for the bytes it was given.
370///
371/// Returned by [`Sink::put`](crate::Sink::put); writers turn it into
372/// [`WriteError::Full`].
373///
374/// # Examples
375///
376/// ```
377/// use parse_net::{Full, Sink, SliceSink};
378///
379/// let mut buf = [0u8; 2];
380/// let mut out = SliceSink::new(&mut buf);
381/// assert_eq!(out.put(b"abc"), Err(Full));
382/// ```
383#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
384pub struct Full;
385
386impl fmt::Display for Full {
387    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
388        f.write_str("output sink is full")
389    }
390}
391
392impl core::error::Error for Full {}
393
394/// Why a writer refused.
395///
396/// # Examples
397///
398/// ```
399/// use parse_net::{write, SliceSink, WriteError};
400///
401/// let mut buf = [0u8; 64];
402/// let mut out = SliceSink::new(&mut buf);
403/// let err = write::write_field_value(b"evil\r\nSet-Cookie: x=1", &mut out).unwrap_err();
404/// assert_eq!(err, WriteError::Forbidden { at: 4 });
405/// assert!(out.written().is_empty());
406/// ```
407#[non_exhaustive]
408#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
409pub enum WriteError {
410    /// The sink has no room. When the sink reports its room (as
411    /// [`SliceSink`](crate::SliceSink) does), nothing was written. Retry with
412    /// a larger buffer; [`CountSink`](crate::CountSink) gives the exact
413    /// length.
414    Full,
415    /// The input byte at offset `at` is not allowed in the output grammar
416    /// (CR, LF, NUL, another control, or a byte the format forbids). Refuse
417    /// the input; never strip it and retry silently.
418    Forbidden {
419        /// Offset of the refused byte in the input.
420        at: u32,
421    },
422    /// A number outside the format's range.
423    OutOfRange,
424    /// A position that requires a token got something else.
425    NotToken,
426    /// A structured writer was called out of order, such as a parameter
427    /// before any item. A bug in the calling code.
428    InvalidSequence,
429    /// Two parameters with the same name (ASCII case-insensitive) where the
430    /// grammar allows each name once, so every parser would refuse the
431    /// output.
432    DuplicateName,
433}
434
435impl WriteError {
436    /// A `Forbidden` error at byte `at` (saturating).
437    pub(crate) const fn forbidden(at: usize) -> Self {
438        let at = if at > u32::MAX as usize {
439            u32::MAX
440        } else {
441            at as u32
442        };
443        Self::Forbidden { at }
444    }
445}
446
447impl From<Full> for WriteError {
448    fn from(_: Full) -> Self {
449        Self::Full
450    }
451}
452
453impl fmt::Display for WriteError {
454    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
455        match self {
456            Self::Full => f.write_str("output sink is full"),
457            Self::Forbidden { at } => write!(f, "forbidden byte at offset {at}"),
458            Self::OutOfRange => f.write_str("value out of range"),
459            Self::NotToken => f.write_str("not a token"),
460            Self::InvalidSequence => f.write_str("writer called out of order"),
461            Self::DuplicateName => f.write_str("duplicate parameter name"),
462        }
463    }
464}
465
466impl core::error::Error for WriteError {}
467
468#[cfg(test)]
469mod tests {
470    use std::string::ToString;
471
472    use super::*;
473
474    #[test]
475    fn test_parse_error_size_is_eight_bytes() {
476        assert_eq!(core::mem::size_of::<ParseError>(), 8);
477        assert_eq!(core::mem::size_of::<ErrorKind>(), 1);
478    }
479
480    #[test]
481    fn test_parse_error_offset_saturates() {
482        let e = ParseError::new(ErrorKind::Empty, usize::MAX);
483        assert_eq!(e.offset(), u32::MAX as usize);
484        assert_eq!(e.shifted(10).offset(), u32::MAX as usize);
485        assert_eq!(e.on_line(1000).line(), 255);
486    }
487
488    #[test]
489    fn test_parse_error_display_with_line() {
490        let e = ParseError::new(ErrorKind::InvalidByte, 3).on_line(2);
491        assert_eq!(e.to_string(), "invalid byte at offset 3 of field line 2");
492        assert_eq!(e.shifted(4).offset(), 7);
493    }
494
495    #[test]
496    fn test_every_message_is_nonempty_and_lowercase_led() {
497        use ErrorKind::*;
498        let all = [
499            Empty,
500            NotToken,
501            InvalidByte,
502            BareCr,
503            LineFeed,
504            Nul,
505            Control,
506            UnterminatedQuote,
507            InvalidQuotedPair,
508            UnbalancedComment,
509            CommentTooDeep,
510            TooManyMembers,
511            TooManyEmptyElements,
512            TrailingSeparator,
513            ExpectedSeparator,
514            TrailingData,
515            DuplicateParameter,
516            MissingValue,
517            InvalidNumber,
518            NumberOverflow,
519            OutOfRange,
520            InvalidDate,
521            WeekdayMismatch,
522            InvalidMediaType,
523            Wildcard,
524            InvalidBoundary,
525            InvalidQValue,
526            InvalidEntityTag,
527            InvalidRange,
528            InvalidCharset,
529            UnsupportedCharset,
530            InvalidPercentEncoding,
531            InvalidUtf8,
532            ControlInDecoded,
533            InvalidHost,
534            InvalidPort,
535            InvalidAddress,
536            InvalidOrigin,
537            InvalidKey,
538            TooLong,
539            InvalidBase64,
540            InvalidEncodedWord,
541            SectionGap,
542            TooManySections,
543            InvalidAddrSpec,
544            LineTooLong,
545            Malformed,
546            TooManyHops,
547            BufferTooSmall,
548        ];
549        for k in all {
550            let m = k.message();
551            assert!(!m.is_empty());
552            assert_eq!(k.to_string(), m);
553        }
554    }
555
556    #[test]
557    fn test_write_error_display_and_from_full() {
558        assert_eq!(WriteError::from(Full), WriteError::Full);
559        assert_eq!(
560            WriteError::forbidden(7).to_string(),
561            "forbidden byte at offset 7"
562        );
563        assert_eq!(
564            WriteError::forbidden(usize::MAX),
565            WriteError::Forbidden { at: u32::MAX }
566        );
567        assert_eq!(Full.to_string(), "output sink is full");
568        assert_eq!(
569            WriteError::InvalidSequence.to_string(),
570            "writer called out of order"
571        );
572        assert_eq!(
573            WriteError::DuplicateName.to_string(),
574            "duplicate parameter name"
575        );
576    }
577}