Skip to main content

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/// Fails with `DuplicateName` when two parameter names are equal ignoring
229/// ASCII case: the output would be refused by every parser of the field.
230pub(crate) fn unique_names(params: &[(&str, &str)]) -> Result<(), WriteError> {
231    for (i, (a, _)) in params.iter().enumerate() {
232        let later = params.get(i + 1..).unwrap_or(&[]);
233        if later.iter().any(|(b, _)| a.eq_ignore_ascii_case(b)) {
234            return Err(WriteError::DuplicateName);
235        }
236    }
237    Ok(())
238}
239
240/// Writes `n` in decimal, with no sign and no padding.
241///
242/// # Errors
243///
244/// [`WriteError::Full`].
245///
246/// # Examples
247///
248/// ```
249/// use parse_net::{write::write_decimal_u64, SliceSink};
250///
251/// let mut buf = [0u8; 20];
252/// let mut out = SliceSink::new(&mut buf);
253/// write_decimal_u64(u64::MAX, &mut out)?;
254/// assert_eq!(out.written(), b"18446744073709551615");
255/// # Ok::<(), parse_net::WriteError>(())
256/// ```
257pub fn write_decimal_u64(n: u64, out: &mut impl Sink) -> Result<(), WriteError> {
258    let mut digits = [0u8; 20];
259    let mut i = digits.len();
260    let mut v = n;
261    loop {
262        i -= 1;
263        if let Some(d) = digits.get_mut(i) {
264            // `v % 10` is below 10, so the cast cannot truncate.
265            *d = b'0' + (v % 10) as u8;
266        }
267        v /= 10;
268        if v == 0 || i == 0 {
269            break;
270        }
271    }
272    out.put(digits.get(i..).unwrap_or(&[]))?;
273    Ok(())
274}
275
276/// Writes `n` in lowercase hexadecimal, with no prefix and no padding.
277///
278/// # Errors
279///
280/// [`WriteError::Full`].
281///
282/// # Examples
283///
284/// ```
285/// use parse_net::{write::write_hex_u64, SliceSink};
286///
287/// let mut buf = [0u8; 16];
288/// let mut out = SliceSink::new(&mut buf);
289/// write_hex_u64(0xdead_beef, &mut out)?;
290/// assert_eq!(out.written(), b"deadbeef");
291/// # Ok::<(), parse_net::WriteError>(())
292/// ```
293pub fn write_hex_u64(n: u64, out: &mut impl Sink) -> Result<(), WriteError> {
294    const HEX: &[u8; 16] = b"0123456789abcdef";
295    let mut digits = [0u8; 16];
296    let mut i = digits.len();
297    let mut v = n;
298    loop {
299        i -= 1;
300        if let (Some(d), Some(&h)) = (digits.get_mut(i), HEX.get((v & 0xf) as usize)) {
301            *d = h;
302        }
303        v >>= 4;
304        if v == 0 || i == 0 {
305            break;
306        }
307    }
308    out.put(digits.get(i..).unwrap_or(&[]))?;
309    Ok(())
310}
311
312/// Writes `s` as a token, after checking it is one.
313///
314/// # Errors
315///
316/// [`WriteError::NotToken`] (also for empty input), [`WriteError::Full`].
317///
318/// # Examples
319///
320/// ```
321/// use parse_net::{write::write_token, SliceSink, WriteError};
322///
323/// let mut buf = [0u8; 16];
324/// let mut out = SliceSink::new(&mut buf);
325/// write_token(b"no-cache", &mut out)?;
326/// assert_eq!(write_token(b"no cache", &mut out), Err(WriteError::NotToken));
327/// assert_eq!(out.written(), b"no-cache");
328/// # Ok::<(), WriteError>(())
329/// ```
330pub fn write_token(s: &[u8], out: &mut impl Sink) -> Result<(), WriteError> {
331    if !text::is_token(s) {
332        return Err(WriteError::NotToken);
333    }
334    out.put(s)?;
335    Ok(())
336}
337
338/// The length of `s` written as a quoted string, after checking every byte
339/// may be written.
340pub(crate) fn quoted_len(s: &[u8]) -> Result<usize, WriteError> {
341    let mut len = s.len().saturating_add(2);
342    for (i, &b) in s.iter().enumerate() {
343        match b {
344            b'"' | b'\\' => len = len.saturating_add(1),
345            b'\t' => {}
346            _ if class::is_quoted_pair_char(b) => {}
347            _ => return Err(WriteError::forbidden(i)),
348        }
349    }
350    Ok(len)
351}
352
353/// Writes `s` as a quoted string.
354///
355/// Only `"` and `\` are escaped (RFC 9110 section 5.6.4: senders SHOULD NOT
356/// generate other quoted pairs); HTAB, SP, VCHAR, and obs-text are written as
357/// they are.
358///
359/// # Errors
360///
361/// [`WriteError::Forbidden`] for CR, LF, NUL, another control byte, or DEL;
362/// [`WriteError::Full`].
363///
364/// # Examples
365///
366/// ```
367/// use parse_net::{write::write_quoted, SliceSink};
368///
369/// let mut buf = [0u8; 16];
370/// let mut out = SliceSink::new(&mut buf);
371/// write_quoted(b"a \"b\"", &mut out)?;
372/// assert_eq!(out.written(), b"\"a \\\"b\\\"\"");
373/// # Ok::<(), parse_net::WriteError>(())
374/// ```
375pub fn write_quoted(s: &[u8], out: &mut impl Sink) -> Result<(), WriteError> {
376    let len = quoted_len(s)?;
377    reserve(out, len)?;
378    put_quoted(s, out)
379}
380
381/// Writes an already validated quoted string.
382fn put_quoted(s: &[u8], out: &mut impl Sink) -> Result<(), WriteError> {
383    out.put(b"\"")?;
384    let mut rest = s;
385    while let Some(i) = rest.iter().position(|&b| b == b'"' || b == b'\\') {
386        let (plain, tail) = rest.split_at(i);
387        out.put(plain)?;
388        if let Some((escaped, after)) = tail.split_first() {
389            out.put(&[b'\\', *escaped])?;
390            rest = after;
391        } else {
392            rest = &[];
393        }
394    }
395    out.put(rest)?;
396    out.put(b"\"")?;
397    Ok(())
398}
399
400/// The length of `s` written by [`write_word`].
401pub(crate) fn word_len(s: &[u8]) -> Result<usize, WriteError> {
402    if text::is_token(s) {
403        Ok(s.len())
404    } else {
405        quoted_len(s)
406    }
407}
408
409/// Writes `s` as a token when it is a non-empty token, and as a quoted string
410/// otherwise (`token / quoted-string`, RFC 9110 section 5.6.6).
411///
412/// # Errors
413///
414/// As [`write_quoted`].
415///
416/// # Examples
417///
418/// ```
419/// use parse_net::{write::write_word, SliceSink};
420///
421/// let mut buf = [0u8; 32];
422/// let mut out = SliceSink::new(&mut buf);
423/// write_word(b"utf-8", &mut out)?;
424/// write_word(b" ", &mut out)?;
425/// write_word(b"", &mut out)?;
426/// assert_eq!(out.written(), b"utf-8\" \"\"\"");
427/// # Ok::<(), parse_net::WriteError>(())
428/// ```
429pub fn write_word(s: &[u8], out: &mut impl Sink) -> Result<(), WriteError> {
430    if text::is_token(s) {
431        out.put(s)?;
432        Ok(())
433    } else {
434        write_quoted(s, out)
435    }
436}
437
438/// Writes `s` as a field value, after checking every byte is `field-vchar`,
439/// SP, HTAB, or `obs-text`.
440///
441/// This is the header-injection guard: a value containing CR, LF, or NUL can
442/// never reach the wire through it.
443///
444/// # Errors
445///
446/// [`WriteError::Forbidden`] at the first refused byte; [`WriteError::Full`].
447///
448/// # Examples
449///
450/// ```
451/// use parse_net::{write::write_field_value, SliceSink, WriteError};
452///
453/// let mut buf = [0u8; 32];
454/// let mut out = SliceSink::new(&mut buf);
455/// write_field_value(b"text/html; charset=utf-8", &mut out)?;
456/// assert_eq!(write_field_value(b"a\r\nb", &mut out), Err(WriteError::Forbidden { at: 1 }));
457/// # Ok::<(), WriteError>(())
458/// ```
459pub fn write_field_value(s: &[u8], out: &mut impl Sink) -> Result<(), WriteError> {
460    if let Err(e) = crate::scan::check_field_value(s) {
461        return Err(WriteError::forbidden(e.offset()));
462    }
463    out.put(s)?;
464    Ok(())
465}
466
467/// Writes `; name=value`, the value through [`write_word`].
468///
469/// # Errors
470///
471/// [`WriteError::NotToken`] for a name that is not a token, then as
472/// [`write_word`].
473///
474/// # Examples
475///
476/// ```
477/// use parse_net::{write::write_param, SliceSink};
478///
479/// let mut buf = [0u8; 32];
480/// let mut out = SliceSink::new(&mut buf);
481/// write_param(b"charset", b"utf-8", &mut out)?;
482/// assert_eq!(out.written(), b"; charset=utf-8");
483/// # Ok::<(), parse_net::WriteError>(())
484/// ```
485pub fn write_param(name: &[u8], value: &[u8], out: &mut impl Sink) -> Result<(), WriteError> {
486    if !text::is_token(name) {
487        return Err(WriteError::NotToken);
488    }
489    let value_len = word_len(value)?;
490    reserve(out, name.len().saturating_add(3).saturating_add(value_len))?;
491    out.put(b"; ")?;
492    out.put(name)?;
493    out.put(b"=")?;
494    if text::is_token(value) {
495        out.put(value)?;
496        Ok(())
497    } else {
498        put_quoted(value, out)
499    }
500}
501
502#[cfg(test)]
503mod tests {
504    use std::vec::Vec;
505
506    use super::*;
507
508    fn written(f: impl Fn(&mut SliceSink<'_>) -> Result<(), WriteError>) -> Vec<u8> {
509        let mut buf = [0u8; 256];
510        let mut out = SliceSink::new(&mut buf);
511        f(&mut out).unwrap();
512        out.written().to_vec()
513    }
514
515    #[test]
516    fn test_number_writers_known_values() {
517        assert_eq!(written(|o| write_decimal_u64(0, o)), b"0");
518        assert_eq!(written(|o| write_decimal_u64(10, o)), b"10");
519        assert_eq!(
520            written(|o| write_decimal_u64(u64::MAX, o)),
521            b"18446744073709551615"
522        );
523        assert_eq!(written(|o| write_hex_u64(0, o)), b"0");
524        assert_eq!(written(|o| write_hex_u64(u64::MAX, o)), b"ffffffffffffffff");
525        assert_eq!(written(|o| write_hex_u64(0x10, o)), b"10");
526    }
527
528    #[test]
529    fn test_writers_refuse_crlf_in_every_position() {
530        let bad: Vec<u8> = (0u8..0x20).filter(|&b| b != b'\t').chain([0x7f]).collect();
531        for &b in &bad {
532            for pos in 0..3 {
533                let mut input = *b"abc";
534                input[pos] = b;
535                let mut buf = [0u8; 64];
536                let mut out = SliceSink::new(&mut buf);
537                let at = pos as u32;
538                assert_eq!(
539                    write_quoted(&input, &mut out),
540                    Err(WriteError::Forbidden { at })
541                );
542                assert_eq!(
543                    write_word(&input, &mut out),
544                    Err(WriteError::Forbidden { at })
545                );
546                assert_eq!(
547                    write_field_value(&input, &mut out),
548                    Err(WriteError::Forbidden { at })
549                );
550                assert_eq!(
551                    write_param(b"n", &input, &mut out),
552                    Err(WriteError::Forbidden { at })
553                );
554                assert_eq!(write_token(&input, &mut out), Err(WriteError::NotToken));
555                assert!(out.is_empty());
556            }
557        }
558    }
559
560    #[test]
561    fn test_quoted_escapes_only_quote_and_backslash() {
562        assert_eq!(written(|o| write_quoted(b"", o)), b"\"\"");
563        assert_eq!(
564            written(|o| write_quoted(b"a\\b\"c\td\x80", o)),
565            b"\"a\\\\b\\\"c\td\x80\""
566        );
567    }
568
569    #[test]
570    fn test_slice_sink_one_byte_short_writes_nothing() {
571        for (input, needed) in [(&b"a\"b"[..], 6usize), (b"x y", 5)] {
572            let mut buf = [0u8; 16];
573            let mut out = SliceSink::new(&mut buf[..needed - 1]);
574            assert_eq!(write_quoted(input, &mut out), Err(WriteError::Full));
575            assert!(out.is_empty());
576            let mut out = SliceSink::new(&mut buf[..needed]);
577            assert_eq!(write_quoted(input, &mut out), Ok(()));
578        }
579        let mut buf = [0u8; 9];
580        let mut out = SliceSink::new(&mut buf);
581        assert_eq!(
582            write_param(b"name", b"a b", &mut out),
583            Err(WriteError::Full)
584        );
585        assert!(out.is_empty());
586        let mut buf = [0u8; 3];
587        let mut out = SliceSink::new(&mut buf);
588        assert_eq!(write_decimal_u64(1234, &mut out), Err(WriteError::Full));
589        assert!(out.is_empty());
590    }
591
592    #[test]
593    fn test_count_sink_matches_slice_output() {
594        let mut count = CountSink::new();
595        write_param(b"a", b"x\"y", &mut count).unwrap();
596        assert_eq!(
597            count.count(),
598            written(|o| write_param(b"a", b"x\"y", o)).len()
599        );
600        let mut c = CountSink { n: usize::MAX };
601        c.put(b"x").unwrap();
602        assert_eq!(c.count(), usize::MAX);
603    }
604
605    #[test]
606    fn test_param_name_must_be_token() {
607        let mut buf = [0u8; 16];
608        let mut out = SliceSink::new(&mut buf);
609        assert_eq!(
610            write_param(b"a b", b"x", &mut out),
611            Err(WriteError::NotToken)
612        );
613        assert_eq!(write_param(b"", b"x", &mut out), Err(WriteError::NotToken));
614    }
615
616    #[test]
617    fn test_sink_for_mut_reference_and_debug() {
618        let mut buf = [0u8; 4];
619        let mut out = SliceSink::new(&mut buf);
620        {
621            let r = &mut out;
622            write_token(b"ab", &mut &mut *r).unwrap();
623            assert_eq!(Sink::remaining(&r), Some(2));
624        }
625        assert_eq!(std::format!("{out:?}"), "SliceSink { len: 2, capacity: 4 }");
626    }
627
628    #[cfg(feature = "alloc")]
629    #[test]
630    fn test_vec_sink_grows() {
631        let mut v = Vec::new();
632        write_quoted(b"a b", &mut v).unwrap();
633        assert_eq!(v, b"\"a b\"");
634        assert_eq!(Sink::remaining(&v), None);
635    }
636}