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}