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    /// Two parameters with the same name (ASCII case-insensitive) where the
392    /// grammar allows each name once, so every parser would refuse the
393    /// output.
394    DuplicateName,
395}
396
397impl WriteError {
398    /// A `Forbidden` error at byte `at` (saturating).
399    pub(crate) const fn forbidden(at: usize) -> Self {
400        let at = if at > u32::MAX as usize {
401            u32::MAX
402        } else {
403            at as u32
404        };
405        Self::Forbidden { at }
406    }
407}
408
409impl From<Full> for WriteError {
410    fn from(_: Full) -> Self {
411        Self::Full
412    }
413}
414
415impl fmt::Display for WriteError {
416    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
417        match self {
418            Self::Full => f.write_str("output sink is full"),
419            Self::Forbidden { at } => write!(f, "forbidden byte at offset {at}"),
420            Self::OutOfRange => f.write_str("value out of range"),
421            Self::NotToken => f.write_str("not a token"),
422            Self::InvalidSequence => f.write_str("writer called out of order"),
423            Self::DuplicateName => f.write_str("duplicate parameter name"),
424        }
425    }
426}
427
428impl core::error::Error for WriteError {}
429
430#[cfg(test)]
431mod tests {
432    use std::string::ToString;
433
434    use super::*;
435
436    #[test]
437    fn test_parse_error_size_is_eight_bytes() {
438        assert_eq!(core::mem::size_of::<ParseError>(), 8);
439        assert_eq!(core::mem::size_of::<ErrorKind>(), 1);
440    }
441
442    #[test]
443    fn test_parse_error_offset_saturates() {
444        let e = ParseError::new(ErrorKind::Empty, usize::MAX);
445        assert_eq!(e.offset(), u32::MAX as usize);
446        assert_eq!(e.shifted(10).offset(), u32::MAX as usize);
447        assert_eq!(e.on_line(1000).line(), 255);
448    }
449
450    #[test]
451    fn test_parse_error_display_with_line() {
452        let e = ParseError::new(ErrorKind::InvalidByte, 3).on_line(2);
453        assert_eq!(e.to_string(), "invalid byte at offset 3 of field line 2");
454        assert_eq!(e.shifted(4).offset(), 7);
455    }
456
457    #[test]
458    fn test_every_message_is_nonempty_and_lowercase_led() {
459        use ErrorKind::*;
460        let all = [
461            Empty,
462            NotToken,
463            InvalidByte,
464            BareCr,
465            LineFeed,
466            Nul,
467            Control,
468            UnterminatedQuote,
469            InvalidQuotedPair,
470            UnbalancedComment,
471            CommentTooDeep,
472            TooManyMembers,
473            TooManyEmptyElements,
474            TrailingSeparator,
475            ExpectedSeparator,
476            TrailingData,
477            DuplicateParameter,
478            MissingValue,
479            InvalidNumber,
480            NumberOverflow,
481            OutOfRange,
482            InvalidDate,
483            WeekdayMismatch,
484            InvalidMediaType,
485            Wildcard,
486            InvalidBoundary,
487            InvalidQValue,
488            InvalidEntityTag,
489            InvalidRange,
490            InvalidCharset,
491            UnsupportedCharset,
492            InvalidPercentEncoding,
493            InvalidUtf8,
494            ControlInDecoded,
495            InvalidHost,
496            InvalidPort,
497            InvalidAddress,
498            InvalidOrigin,
499            InvalidKey,
500            TooLong,
501            InvalidBase64,
502            InvalidEncodedWord,
503            SectionGap,
504            TooManySections,
505            InvalidAddrSpec,
506            LineTooLong,
507            Malformed,
508            TooManyHops,
509            BufferTooSmall,
510        ];
511        for k in all {
512            let m = k.message();
513            assert!(!m.is_empty());
514            assert_eq!(k.to_string(), m);
515        }
516    }
517
518    #[test]
519    fn test_write_error_display_and_from_full() {
520        assert_eq!(WriteError::from(Full), WriteError::Full);
521        assert_eq!(
522            WriteError::forbidden(7).to_string(),
523            "forbidden byte at offset 7"
524        );
525        assert_eq!(
526            WriteError::forbidden(usize::MAX),
527            WriteError::Forbidden { at: u32::MAX }
528        );
529        assert_eq!(Full.to_string(), "output sink is full");
530        assert_eq!(
531            WriteError::InvalidSequence.to_string(),
532            "writer called out of order"
533        );
534        assert_eq!(
535            WriteError::DuplicateName.to_string(),
536            "duplicate parameter name"
537        );
538    }
539}