base_web_multipart/
multipart_writer.rs1use crate::Boundary;
5use crate::syntax::{is_field_name_byte, is_field_value_byte};
6use memchr::memmem::Finder;
7use std::fmt;
8
9#[derive(Clone, Debug)]
28pub struct MultipartWriter {
29 boundary: Boundary,
30 delimiter: Vec<u8>,
32 finder: Finder<'static>,
34 state: State,
35}
36
37#[derive(Clone, Copy, Debug, PartialEq, Eq)]
38enum State {
39 Start,
41 Content { matched: Option<usize> },
43}
44
45#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
47#[non_exhaustive]
48pub enum MultipartWriteError {
49 InvalidFieldName,
51 InvalidFieldValue,
53 Delimiter,
55 NoPart,
57}
58
59impl MultipartWriter {
60 pub fn new(boundary: Boundary) -> Self {
62 let delimiter = [b"\r\n--", boundary.as_str().as_bytes()].concat();
63 let finder = Finder::new(&delimiter[2..]).into_owned();
64 Self {
65 boundary,
66 delimiter,
67 finder,
68 state: State::Start,
69 }
70 }
71
72 pub fn boundary(&self) -> &Boundary {
74 &self.boundary
75 }
76
77 pub fn start_part<N, V>(
83 &mut self,
84 fields: impl IntoIterator<Item = (N, V)>,
85 output: &mut Vec<u8>,
86 ) -> Result<(), MultipartWriteError>
87 where
88 N: AsRef<str>,
89 V: AsRef<[u8]>,
90 {
91 let length = output.len();
92 let result = self.write_head(fields, output);
93 if result.is_err() {
94 output.truncate(length);
95 }
96 result
97 }
98
99 fn write_head<N, V>(
100 &mut self,
101 fields: impl IntoIterator<Item = (N, V)>,
102 output: &mut Vec<u8>,
103 ) -> Result<(), MultipartWriteError>
104 where
105 N: AsRef<str>,
106 V: AsRef<[u8]>,
107 {
108 let delimiter = if self.state == State::Start {
109 &self.delimiter[2..]
110 } else {
111 &self.delimiter[..]
112 };
113 output.extend_from_slice(delimiter);
114 output.extend_from_slice(b"\r\n");
115 for (name, value) in fields {
116 let (name, value) = (name.as_ref().as_bytes(), value.as_ref());
117 if name.is_empty() || !name.iter().all(|&byte| is_field_name_byte(byte)) {
118 return Err(MultipartWriteError::InvalidFieldName);
119 }
120 if !value.iter().all(|&byte| is_field_value_byte(byte)) {
121 return Err(MultipartWriteError::InvalidFieldValue);
122 }
123 let line = output.len();
124 output.extend_from_slice(name);
125 output.extend_from_slice(b": ");
126 output.extend_from_slice(value);
127 if output[line..].starts_with(&self.delimiter[2..]) {
129 return Err(MultipartWriteError::Delimiter);
130 }
131 output.extend_from_slice(b"\r\n");
132 }
133 output.extend_from_slice(b"\r\n");
134 self.state = State::Content { matched: Some(0) };
136 Ok(())
137 }
138
139 pub fn write_content(
145 &mut self,
146 content: &[u8],
147 output: &mut Vec<u8>,
148 ) -> Result<(), MultipartWriteError> {
149 let State::Content { matched } = self.state else {
150 return Err(MultipartWriteError::NoPart);
151 };
152 let matched = self.scan(matched, content)?;
153 output.extend_from_slice(content);
154 self.state = State::Content { matched };
155 Ok(())
156 }
157
158 pub fn finish(self, output: &mut Vec<u8>) {
160 let delimiter = if self.state == State::Start {
161 &self.delimiter[2..]
162 } else {
163 &self.delimiter[..]
164 };
165 output.extend_from_slice(delimiter);
166 output.extend_from_slice(b"--\r\n");
167 }
168
169 fn scan(
171 &self,
172 matched: Option<usize>,
173 content: &[u8],
174 ) -> Result<Option<usize>, MultipartWriteError> {
175 let dash_boundary = &self.delimiter[2..];
176 let continues = |matched: usize, after: &[u8]| {
177 let remaining = &dash_boundary[matched..];
178 let length = remaining.len().min(after.len());
179 (after[..length] == remaining[..length]).then_some(matched + length)
180 };
181 if let Some(matched) = matched.and_then(|matched| continues(matched, content)) {
182 return if matched == dash_boundary.len() {
183 Err(MultipartWriteError::Delimiter)
184 } else {
185 Ok(Some(matched))
186 };
187 }
188 if self
189 .finder
190 .find_iter(content)
191 .any(|index| index > 0 && matches!(content[index - 1], b'\r' | b'\n'))
192 {
193 return Err(MultipartWriteError::Delimiter);
194 }
195 let start = content.len().saturating_sub(dash_boundary.len());
196 Ok(memchr::memchr2_iter(b'\r', b'\n', &content[start..])
197 .find_map(|index| continues(0, &content[start + index + 1..])))
198 }
199}
200
201impl fmt::Display for MultipartWriteError {
202 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
203 formatter.write_str(match self {
204 Self::InvalidFieldName => "invalid multipart part header field name",
205 Self::InvalidFieldValue => "invalid multipart part header field value",
206 Self::Delimiter => "multipart part contains its boundary delimiter",
207 Self::NoPart => "multipart content written before any part",
208 })
209 }
210}
211
212impl std::error::Error for MultipartWriteError {}