parse_net/write.rs
1//! Output: the [`Sink`] trait, [`SliceSink`], [`CountSink`], and the writers
2//! for numbers, tokens, quoted strings, parameters, and field values.
3//!
4//! Every writer validates all of its input before the first
5//! [`put`](Sink::put), and checks [`remaining`](Sink::remaining) when its
6//! output takes several `put` calls, so after any error a [`SliceSink`] holds
7//! exactly what it held before. No writer can emit CR, LF, or NUL, and no
8//! writer produces a value its own parser would refuse.
9//!
10//! # Examples
11//!
12//! ```
13//! use parse_net::{self as pn, write};
14//!
15//! let mut buf = [0u8; 64];
16//! let mut out = pn::SliceSink::new(&mut buf);
17//! write::write_word(b"gzip", &mut out)?;
18//! write::write_param(b"title", b"say \"hi\"", &mut out)?;
19//! assert_eq!(out.written(), b"gzip; title=\"say \\\"hi\\\"\"");
20//! assert_eq!(write::write_quoted(b"a\nb", &mut out), Err(pn::WriteError::Forbidden { at: 1 }));
21//! # Ok::<(), pn::WriteError>(())
22//! ```
23
24use core::fmt;
25
26pub use crate::error::Full;
27use crate::{class, error::WriteError, text};
28
29/// The output abstraction every writer takes.
30///
31/// `put` appends all of `bytes` or fails with [`Full`]. `remaining` reports
32/// how many more bytes fit, when the sink knows; writers that produce a value
33/// in several `put` calls check it first, so a sink that knows its room never
34/// holds half a value after an error.
35///
36/// # Examples
37///
38/// ```
39/// use parse_net::{Full, Sink};
40///
41/// /// A sink that keeps only the last byte it was given.
42/// struct Last(Option<u8>);
43/// impl Sink for Last {
44/// fn put(&mut self, bytes: &[u8]) -> Result<(), Full> {
45/// if let Some(&b) = bytes.last() {
46/// self.0 = Some(b);
47/// }
48/// Ok(())
49/// }
50/// }
51///
52/// let mut last = Last(None);
53/// parse_net::write::write_token(b"gzip", &mut last)?;
54/// assert_eq!(last.0, Some(b'p'));
55/// # Ok::<(), parse_net::WriteError>(())
56/// ```
57pub trait Sink {
58 /// Appends all of `bytes`, or fails with [`Full`] and appends nothing.
59 ///
60 /// # Errors
61 ///
62 /// [`Full`] when the sink has no room for `bytes`.
63 fn put(&mut self, bytes: &[u8]) -> Result<(), Full>;
64
65 /// How many more bytes fit, when the sink knows; `None` otherwise.
66 fn remaining(&self) -> Option<usize> {
67 None
68 }
69}
70
71impl<S: Sink + ?Sized> Sink for &mut S {
72 #[inline]
73 fn put(&mut self, bytes: &[u8]) -> Result<(), Full> {
74 (**self).put(bytes)
75 }
76
77 #[inline]
78 fn remaining(&self) -> Option<usize> {
79 (**self).remaining()
80 }
81}
82
83#[cfg(feature = "alloc")]
84#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
85impl Sink for alloc::vec::Vec<u8> {
86 #[inline]
87 fn put(&mut self, bytes: &[u8]) -> Result<(), Full> {
88 self.extend_from_slice(bytes);
89 Ok(())
90 }
91}
92
93/// A cursor over a caller buffer.
94///
95/// No allocation; reports [`remaining`](Sink::remaining); a `put` that does
96/// not fit writes nothing. `Debug` prints the lengths only, never the bytes.
97///
98/// # Examples
99///
100/// ```
101/// use parse_net::{Sink, SliceSink};
102///
103/// let mut buf = [0u8; 8];
104/// let mut out = SliceSink::new(&mut buf);
105/// out.put(b"gzip")?;
106/// assert_eq!(out.written(), b"gzip");
107/// assert_eq!(out.remaining(), Some(4));
108/// out.clear();
109/// assert!(out.is_empty());
110/// # Ok::<(), parse_net::Full>(())
111/// ```
112pub struct SliceSink<'a> {
113 buf: &'a mut [u8],
114 len: usize,
115}
116
117impl<'a> SliceSink<'a> {
118 /// A sink that writes into `buf`, starting at its first byte.
119 #[must_use]
120 pub fn new(buf: &'a mut [u8]) -> Self {
121 Self { buf, len: 0 }
122 }
123
124 /// The bytes written so far.
125 #[must_use]
126 pub fn written(&self) -> &[u8] {
127 self.buf.get(..self.len).unwrap_or(&[])
128 }
129
130 /// The number of bytes written so far.
131 #[must_use]
132 pub fn len(&self) -> usize {
133 self.len
134 }
135
136 /// Whether nothing has been written.
137 #[must_use]
138 pub fn is_empty(&self) -> bool {
139 self.len == 0
140 }
141
142 /// Forgets what was written; the buffer is reused from its start.
143 pub fn clear(&mut self) {
144 self.len = 0;
145 }
146}
147
148impl Sink for SliceSink<'_> {
149 #[inline]
150 fn put(&mut self, bytes: &[u8]) -> Result<(), Full> {
151 let end = self.len.checked_add(bytes.len()).ok_or(Full)?;
152 let dst = self.buf.get_mut(self.len..end).ok_or(Full)?;
153 dst.copy_from_slice(bytes);
154 self.len = end;
155 Ok(())
156 }
157
158 #[inline]
159 fn remaining(&self) -> Option<usize> {
160 Some(self.buf.len().saturating_sub(self.len))
161 }
162}
163
164impl fmt::Debug for SliceSink<'_> {
165 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166 f.debug_struct("SliceSink")
167 .field("len", &self.len)
168 .field("capacity", &self.buf.len())
169 .finish()
170 }
171}
172
173/// A sink that counts bytes and writes nothing.
174///
175/// Run a writer into it to learn the exact output length, then size a buffer
176/// once. Saturates at `usize::MAX`.
177///
178/// # Examples
179///
180/// ```
181/// use parse_net::{self as pn, write};
182///
183/// let mut count = pn::CountSink::new();
184/// write::write_param(b"filename", b"my report.pdf", &mut count)?;
185/// let mut buf = [0u8; 64];
186/// let mut out = pn::SliceSink::new(&mut buf[..count.count()]);
187/// write::write_param(b"filename", b"my report.pdf", &mut out)?;
188/// assert_eq!(out.written(), b"; filename=\"my report.pdf\"");
189/// # Ok::<(), pn::WriteError>(())
190/// ```
191#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
192pub struct CountSink {
193 n: usize,
194}
195
196impl CountSink {
197 /// A counter at zero.
198 #[must_use]
199 pub const fn new() -> Self {
200 Self { n: 0 }
201 }
202
203 /// The number of bytes put so far.
204 #[must_use]
205 pub const fn count(&self) -> usize {
206 self.n
207 }
208}
209
210impl Sink for CountSink {
211 #[inline]
212 fn put(&mut self, bytes: &[u8]) -> Result<(), Full> {
213 self.n = self.n.saturating_add(bytes.len());
214 Ok(())
215 }
216}
217
218/// Fails with `Full` before anything is written when the sink knows it has
219/// less than `len` bytes of room.
220#[inline]
221pub(crate) fn reserve(out: &impl Sink, len: usize) -> Result<(), WriteError> {
222 match out.remaining() {
223 Some(room) if room < len => Err(WriteError::Full),
224 _ => Ok(()),
225 }
226}
227
228/// Writes `n` in decimal, with no sign and no padding.
229///
230/// # Errors
231///
232/// [`WriteError::Full`].
233///
234/// # Examples
235///
236/// ```
237/// use parse_net::{write::write_decimal_u64, SliceSink};
238///
239/// let mut buf = [0u8; 20];
240/// let mut out = SliceSink::new(&mut buf);
241/// write_decimal_u64(u64::MAX, &mut out)?;
242/// assert_eq!(out.written(), b"18446744073709551615");
243/// # Ok::<(), parse_net::WriteError>(())
244/// ```
245pub fn write_decimal_u64(n: u64, out: &mut impl Sink) -> Result<(), WriteError> {
246 let mut digits = [0u8; 20];
247 let mut i = digits.len();
248 let mut v = n;
249 loop {
250 i -= 1;
251 if let Some(d) = digits.get_mut(i) {
252 // `v % 10` is below 10, so the cast cannot truncate.
253 *d = b'0' + (v % 10) as u8;
254 }
255 v /= 10;
256 if v == 0 || i == 0 {
257 break;
258 }
259 }
260 out.put(digits.get(i..).unwrap_or(&[]))?;
261 Ok(())
262}
263
264/// Writes `n` in lowercase hexadecimal, with no prefix and no padding.
265///
266/// # Errors
267///
268/// [`WriteError::Full`].
269///
270/// # Examples
271///
272/// ```
273/// use parse_net::{write::write_hex_u64, SliceSink};
274///
275/// let mut buf = [0u8; 16];
276/// let mut out = SliceSink::new(&mut buf);
277/// write_hex_u64(0xdead_beef, &mut out)?;
278/// assert_eq!(out.written(), b"deadbeef");
279/// # Ok::<(), parse_net::WriteError>(())
280/// ```
281pub fn write_hex_u64(n: u64, out: &mut impl Sink) -> Result<(), WriteError> {
282 const HEX: &[u8; 16] = b"0123456789abcdef";
283 let mut digits = [0u8; 16];
284 let mut i = digits.len();
285 let mut v = n;
286 loop {
287 i -= 1;
288 if let (Some(d), Some(&h)) = (digits.get_mut(i), HEX.get((v & 0xf) as usize)) {
289 *d = h;
290 }
291 v >>= 4;
292 if v == 0 || i == 0 {
293 break;
294 }
295 }
296 out.put(digits.get(i..).unwrap_or(&[]))?;
297 Ok(())
298}
299
300/// Writes `s` as a token, after checking it is one.
301///
302/// # Errors
303///
304/// [`WriteError::NotToken`] (also for empty input), [`WriteError::Full`].
305///
306/// # Examples
307///
308/// ```
309/// use parse_net::{write::write_token, SliceSink, WriteError};
310///
311/// let mut buf = [0u8; 16];
312/// let mut out = SliceSink::new(&mut buf);
313/// write_token(b"no-cache", &mut out)?;
314/// assert_eq!(write_token(b"no cache", &mut out), Err(WriteError::NotToken));
315/// assert_eq!(out.written(), b"no-cache");
316/// # Ok::<(), WriteError>(())
317/// ```
318pub fn write_token(s: &[u8], out: &mut impl Sink) -> Result<(), WriteError> {
319 if !text::is_token(s) {
320 return Err(WriteError::NotToken);
321 }
322 out.put(s)?;
323 Ok(())
324}
325
326/// The length of `s` written as a quoted string, after checking every byte
327/// may be written.
328pub(crate) fn quoted_len(s: &[u8]) -> Result<usize, WriteError> {
329 let mut len = s.len().saturating_add(2);
330 for (i, &b) in s.iter().enumerate() {
331 match b {
332 b'"' | b'\\' => len = len.saturating_add(1),
333 b'\t' => {}
334 _ if class::is_quoted_pair_char(b) => {}
335 _ => return Err(WriteError::forbidden(i)),
336 }
337 }
338 Ok(len)
339}
340
341/// Writes `s` as a quoted string.
342///
343/// Only `"` and `\` are escaped (RFC 9110 section 5.6.4: senders SHOULD NOT
344/// generate other quoted pairs); HTAB, SP, VCHAR, and obs-text are written as
345/// they are.
346///
347/// # Errors
348///
349/// [`WriteError::Forbidden`] for CR, LF, NUL, another control byte, or DEL;
350/// [`WriteError::Full`].
351///
352/// # Examples
353///
354/// ```
355/// use parse_net::{write::write_quoted, SliceSink};
356///
357/// let mut buf = [0u8; 16];
358/// let mut out = SliceSink::new(&mut buf);
359/// write_quoted(b"a \"b\"", &mut out)?;
360/// assert_eq!(out.written(), b"\"a \\\"b\\\"\"");
361/// # Ok::<(), parse_net::WriteError>(())
362/// ```
363pub fn write_quoted(s: &[u8], out: &mut impl Sink) -> Result<(), WriteError> {
364 let len = quoted_len(s)?;
365 reserve(out, len)?;
366 put_quoted(s, out)
367}
368
369/// Writes an already validated quoted string.
370fn put_quoted(s: &[u8], out: &mut impl Sink) -> Result<(), WriteError> {
371 out.put(b"\"")?;
372 let mut rest = s;
373 while let Some(i) = rest.iter().position(|&b| b == b'"' || b == b'\\') {
374 let (plain, tail) = rest.split_at(i);
375 out.put(plain)?;
376 if let Some((escaped, after)) = tail.split_first() {
377 out.put(&[b'\\', *escaped])?;
378 rest = after;
379 } else {
380 rest = &[];
381 }
382 }
383 out.put(rest)?;
384 out.put(b"\"")?;
385 Ok(())
386}
387
388/// The length of `s` written by [`write_word`].
389pub(crate) fn word_len(s: &[u8]) -> Result<usize, WriteError> {
390 if text::is_token(s) {
391 Ok(s.len())
392 } else {
393 quoted_len(s)
394 }
395}
396
397/// Writes `s` as a token when it is a non-empty token, and as a quoted string
398/// otherwise (`token / quoted-string`, RFC 9110 section 5.6.6).
399///
400/// # Errors
401///
402/// As [`write_quoted`].
403///
404/// # Examples
405///
406/// ```
407/// use parse_net::{write::write_word, SliceSink};
408///
409/// let mut buf = [0u8; 32];
410/// let mut out = SliceSink::new(&mut buf);
411/// write_word(b"utf-8", &mut out)?;
412/// write_word(b" ", &mut out)?;
413/// write_word(b"", &mut out)?;
414/// assert_eq!(out.written(), b"utf-8\" \"\"\"");
415/// # Ok::<(), parse_net::WriteError>(())
416/// ```
417pub fn write_word(s: &[u8], out: &mut impl Sink) -> Result<(), WriteError> {
418 if text::is_token(s) {
419 out.put(s)?;
420 Ok(())
421 } else {
422 write_quoted(s, out)
423 }
424}
425
426/// Writes `s` as a field value, after checking every byte is `field-vchar`,
427/// SP, HTAB, or `obs-text`.
428///
429/// This is the header-injection guard: a value containing CR, LF, or NUL can
430/// never reach the wire through it.
431///
432/// # Errors
433///
434/// [`WriteError::Forbidden`] at the first refused byte; [`WriteError::Full`].
435///
436/// # Examples
437///
438/// ```
439/// use parse_net::{write::write_field_value, SliceSink, WriteError};
440///
441/// let mut buf = [0u8; 32];
442/// let mut out = SliceSink::new(&mut buf);
443/// write_field_value(b"text/html; charset=utf-8", &mut out)?;
444/// assert_eq!(write_field_value(b"a\r\nb", &mut out), Err(WriteError::Forbidden { at: 1 }));
445/// # Ok::<(), WriteError>(())
446/// ```
447pub fn write_field_value(s: &[u8], out: &mut impl Sink) -> Result<(), WriteError> {
448 if let Err(e) = crate::scan::check_field_value(s) {
449 return Err(WriteError::forbidden(e.offset()));
450 }
451 out.put(s)?;
452 Ok(())
453}
454
455/// Writes `; name=value`, the value through [`write_word`].
456///
457/// # Errors
458///
459/// [`WriteError::NotToken`] for a name that is not a token, then as
460/// [`write_word`].
461///
462/// # Examples
463///
464/// ```
465/// use parse_net::{write::write_param, SliceSink};
466///
467/// let mut buf = [0u8; 32];
468/// let mut out = SliceSink::new(&mut buf);
469/// write_param(b"charset", b"utf-8", &mut out)?;
470/// assert_eq!(out.written(), b"; charset=utf-8");
471/// # Ok::<(), parse_net::WriteError>(())
472/// ```
473pub fn write_param(name: &[u8], value: &[u8], out: &mut impl Sink) -> Result<(), WriteError> {
474 if !text::is_token(name) {
475 return Err(WriteError::NotToken);
476 }
477 let value_len = word_len(value)?;
478 reserve(out, name.len().saturating_add(3).saturating_add(value_len))?;
479 out.put(b"; ")?;
480 out.put(name)?;
481 out.put(b"=")?;
482 if text::is_token(value) {
483 out.put(value)?;
484 Ok(())
485 } else {
486 put_quoted(value, out)
487 }
488}
489
490#[cfg(test)]
491mod tests {
492 use std::vec::Vec;
493
494 use super::*;
495
496 fn written(f: impl Fn(&mut SliceSink<'_>) -> Result<(), WriteError>) -> Vec<u8> {
497 let mut buf = [0u8; 256];
498 let mut out = SliceSink::new(&mut buf);
499 f(&mut out).unwrap();
500 out.written().to_vec()
501 }
502
503 #[test]
504 fn test_number_writers_known_values() {
505 assert_eq!(written(|o| write_decimal_u64(0, o)), b"0");
506 assert_eq!(written(|o| write_decimal_u64(10, o)), b"10");
507 assert_eq!(
508 written(|o| write_decimal_u64(u64::MAX, o)),
509 b"18446744073709551615"
510 );
511 assert_eq!(written(|o| write_hex_u64(0, o)), b"0");
512 assert_eq!(written(|o| write_hex_u64(u64::MAX, o)), b"ffffffffffffffff");
513 assert_eq!(written(|o| write_hex_u64(0x10, o)), b"10");
514 }
515
516 #[test]
517 fn test_writers_refuse_crlf_in_every_position() {
518 let bad: Vec<u8> = (0u8..0x20).filter(|&b| b != b'\t').chain([0x7f]).collect();
519 for &b in &bad {
520 for pos in 0..3 {
521 let mut input = *b"abc";
522 input[pos] = b;
523 let mut buf = [0u8; 64];
524 let mut out = SliceSink::new(&mut buf);
525 let at = pos as u32;
526 assert_eq!(
527 write_quoted(&input, &mut out),
528 Err(WriteError::Forbidden { at })
529 );
530 assert_eq!(
531 write_word(&input, &mut out),
532 Err(WriteError::Forbidden { at })
533 );
534 assert_eq!(
535 write_field_value(&input, &mut out),
536 Err(WriteError::Forbidden { at })
537 );
538 assert_eq!(
539 write_param(b"n", &input, &mut out),
540 Err(WriteError::Forbidden { at })
541 );
542 assert_eq!(write_token(&input, &mut out), Err(WriteError::NotToken));
543 assert!(out.is_empty());
544 }
545 }
546 }
547
548 #[test]
549 fn test_quoted_escapes_only_quote_and_backslash() {
550 assert_eq!(written(|o| write_quoted(b"", o)), b"\"\"");
551 assert_eq!(
552 written(|o| write_quoted(b"a\\b\"c\td\x80", o)),
553 b"\"a\\\\b\\\"c\td\x80\""
554 );
555 }
556
557 #[test]
558 fn test_slice_sink_one_byte_short_writes_nothing() {
559 for (input, needed) in [(&b"a\"b"[..], 6usize), (b"x y", 5)] {
560 let mut buf = [0u8; 16];
561 let mut out = SliceSink::new(&mut buf[..needed - 1]);
562 assert_eq!(write_quoted(input, &mut out), Err(WriteError::Full));
563 assert!(out.is_empty());
564 let mut out = SliceSink::new(&mut buf[..needed]);
565 assert_eq!(write_quoted(input, &mut out), Ok(()));
566 }
567 let mut buf = [0u8; 9];
568 let mut out = SliceSink::new(&mut buf);
569 assert_eq!(
570 write_param(b"name", b"a b", &mut out),
571 Err(WriteError::Full)
572 );
573 assert!(out.is_empty());
574 let mut buf = [0u8; 3];
575 let mut out = SliceSink::new(&mut buf);
576 assert_eq!(write_decimal_u64(1234, &mut out), Err(WriteError::Full));
577 assert!(out.is_empty());
578 }
579
580 #[test]
581 fn test_count_sink_matches_slice_output() {
582 let mut count = CountSink::new();
583 write_param(b"a", b"x\"y", &mut count).unwrap();
584 assert_eq!(
585 count.count(),
586 written(|o| write_param(b"a", b"x\"y", o)).len()
587 );
588 let mut c = CountSink { n: usize::MAX };
589 c.put(b"x").unwrap();
590 assert_eq!(c.count(), usize::MAX);
591 }
592
593 #[test]
594 fn test_param_name_must_be_token() {
595 let mut buf = [0u8; 16];
596 let mut out = SliceSink::new(&mut buf);
597 assert_eq!(
598 write_param(b"a b", b"x", &mut out),
599 Err(WriteError::NotToken)
600 );
601 assert_eq!(write_param(b"", b"x", &mut out), Err(WriteError::NotToken));
602 }
603
604 #[test]
605 fn test_sink_for_mut_reference_and_debug() {
606 let mut buf = [0u8; 4];
607 let mut out = SliceSink::new(&mut buf);
608 {
609 let r = &mut out;
610 write_token(b"ab", &mut &mut *r).unwrap();
611 assert_eq!(Sink::remaining(&r), Some(2));
612 }
613 assert_eq!(std::format!("{out:?}"), "SliceSink { len: 2, capacity: 4 }");
614 }
615
616 #[cfg(feature = "alloc")]
617 #[test]
618 fn test_vec_sink_grows() {
619 let mut v = Vec::new();
620 write_quoted(b"a b", &mut v).unwrap();
621 assert_eq!(v, b"\"a b\"");
622 assert_eq!(Sink::remaining(&v), None);
623 }
624}