Skip to main content

base_web_multipart/
part_head.rs

1// Released under the MIT License.
2// Copyright, 2026, by Samuel Williams.
3
4use std::fmt;
5use std::iter::FusedIterator;
6
7/// A part's header section, borrowed from the parser's input and already checked: each line is a field name, a colon, and a value, ending with CRLF.
8///
9/// Interpret it with a [`base_message::FieldSection`], such as [`PartHeaders`](crate::PartHeaders), from [`Self::fields`].
10#[derive(Clone, Copy, PartialEq, Eq, Hash)]
11pub struct PartHead<'a> {
12    /// The header lines, each ending with CRLF, without the empty line that ends the section.
13    section: &'a [u8],
14}
15
16impl<'a> PartHead<'a> {
17    pub(crate) fn new(section: &'a [u8]) -> Self {
18        Self { section }
19    }
20
21    /// The fields, in order, as name and value pairs, with spaces and tabs around each value removed.
22    pub fn fields(&self) -> PartFields<'a> {
23        PartFields { rest: self.section }
24    }
25
26    /// The number of fields, counted by reading the section.
27    pub fn len(&self) -> usize {
28        self.fields().count()
29    }
30
31    /// Whether the part has no fields.
32    pub fn is_empty(&self) -> bool {
33        self.section.is_empty()
34    }
35
36    /// The header section as it was written, without the empty line that ends it.
37    pub fn as_bytes(&self) -> &'a [u8] {
38        self.section
39    }
40}
41
42/// Shows field names and the lengths of their values, since values can carry credentials that must not reach logs.
43impl fmt::Debug for PartHead<'_> {
44    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
45        let fields = self
46            .fields()
47            .map(|(name, value)| (name, format!("<{} bytes>", value.len())));
48        formatter
49            .debug_struct("PartHead")
50            .field("fields", &fields.collect::<Vec<_>>())
51            .finish()
52    }
53}
54
55impl<'a> IntoIterator for PartHead<'a> {
56    type Item = (&'a str, &'a [u8]);
57    type IntoIter = PartFields<'a>;
58
59    fn into_iter(self) -> Self::IntoIter {
60        self.fields()
61    }
62}
63
64/// The fields of a [`PartHead`].
65#[derive(Clone)]
66pub struct PartFields<'a> {
67    rest: &'a [u8],
68}
69
70/// Shows the length of the remaining header section, since field values must not reach logs.
71impl fmt::Debug for PartFields<'_> {
72    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
73        formatter
74            .debug_struct("PartFields")
75            .field("remaining", &format_args!("<{} bytes>", self.rest.len()))
76            .finish()
77    }
78}
79
80impl<'a> Iterator for PartFields<'a> {
81    type Item = (&'a str, &'a [u8]);
82
83    fn next(&mut self) -> Option<Self::Item> {
84        // The parser checked every line, so each has a colon and ends with CRLF, and its name is ASCII.
85        let end = memchr::memmem::find(self.rest, b"\r\n")?;
86        let line = &self.rest[..end];
87        self.rest = &self.rest[end + 2..];
88        let colon = memchr::memchr(b':', line).expect("a checked header line has a colon");
89        let name = std::str::from_utf8(&line[..colon]).expect("a checked field name is ASCII");
90        Some((name, base_message::trim_whitespace(&line[colon + 1..])))
91    }
92}
93
94impl FusedIterator for PartFields<'_> {}