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}