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}