1use std::fmt;
7use std::future::poll_fn;
8use std::task::{Context, Poll, ready};
9
10use bytes::Bytes;
11use futures_core::Stream;
12
13use crate::Error;
14use crate::multipart::Multipart;
15use crate::part_data_stream::PartDataStream;
16
17pub struct Part<'m, S>
23where
24 S: Stream<Item = Result<Bytes, Error>> + Send + Unpin,
25{
26 mp: &'m mut Multipart<S>,
27}
28
29impl<S> fmt::Debug for Part<'_, S>
30where
31 S: Stream<Item = Result<Bytes, Error>> + Send + Unpin,
32{
33 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34 f.debug_struct("Part").finish_non_exhaustive()
35 }
36}
37
38impl<'m, S> Part<'m, S>
39where
40 S: Stream<Item = Result<Bytes, Error>> + Send + Unpin,
41{
42 pub(super) fn new(mp: &'m mut Multipart<S>) -> Self {
43 Part { mp }
44 }
45
46 pub fn poll_next_header(&mut self, cx: &mut Context<'_>) -> Poll<Result<Option<httparse::Header<'_>>, Error>> {
51 ready!(self.mp.poll_ensure_headers(cx)?);
52 Poll::Ready(self.mp.next_header_inner())
53 }
54
55 pub fn poll_next_data(&mut self, cx: &mut Context<'_>) -> Poll<Result<Option<Bytes>, Error>> {
57 ready!(self.mp.poll_ensure_headers(cx)?);
58 self.mp.finish_headers();
59 self.mp.poll_next_part_data_chunk(cx)
60 }
61
62 pub async fn next_header(&mut self) -> Result<Option<httparse::Header<'_>>, Error> {
76 poll_fn(|cx| self.mp.poll_ensure_headers(cx)).await?;
82 self.mp.next_header_inner()
83 }
84
85 pub async fn next_data(&mut self) -> Result<Option<Bytes>, Error> {
100 poll_fn(|cx| self.poll_next_data(cx)).await
101 }
102
103 pub fn take_data_stream(self) -> Result<PartDataStream<S>, Error> {
121 self.mp.take_data_stream()
122 }
123}
124
125#[cfg(test)]
126#[allow(
127 clippy::expect_used,
128 clippy::indexing_slicing,
129 clippy::panic,
130 clippy::unreachable,
131 clippy::unwrap_used
132)]
133mod tests {
134 use super::*;
135
136 use std::collections::VecDeque;
137 use std::pin::Pin;
138
139 use futures::executor::block_on;
140 use futures::task::noop_waker;
141 use futures_util::StreamExt;
142
143 use crate::Boundary;
144
145 const BOUNDARY: &[u8] = b"B";
146 const DATA: &[u8] = b"hello file data";
147
148 fn body() -> Vec<u8> {
150 let mut body = Vec::new();
151 body.extend_from_slice(b"--B\r\nContent-Disposition: form-data; name=\"file\"; filename=\"a.txt\"\r\n");
152 body.extend_from_slice(b"Content-Type: text/plain\r\n\r\n");
153 body.extend_from_slice(DATA);
154 body.extend_from_slice(b"\r\n--B--\r\n");
155 body
156 }
157
158 fn data_start(body: &[u8]) -> usize {
160 body.windows(4).position(|w| w == b"\r\n\r\n").map_or(0, |pos| pos + 4)
161 }
162
163 fn stream_error() -> Error {
164 Error::stream_read_failed(std::io::Error::other("boom"))
165 }
166
167 fn error_kind(err: &Error) -> &'static str {
168 if matches!(err, Error::StreamReadFailed(_)) {
169 "StreamReadFailed"
170 } else if matches!(err, Error::InvalidFormat) {
171 "InvalidFormat"
172 } else if matches!(err, Error::IncompleteStream) {
173 "IncompleteStream"
174 } else if matches!(err, Error::HeaderSizeExceeded { .. }) {
175 "HeaderSizeExceeded"
176 } else {
177 "other"
178 }
179 }
180
181 struct Scripted {
186 items: VecDeque<Result<Bytes, Error>>,
187 pending_at: Option<usize>,
188 offset: usize,
189 pending_done: bool,
190 }
191
192 impl Scripted {
193 fn new(items: Vec<Result<Bytes, Error>>, pending_at: Option<usize>) -> Self {
194 Scripted {
195 items: items.into(),
196 pending_at,
197 offset: 0,
198 pending_done: false,
199 }
200 }
201
202 fn chunked(body: &[u8], chunk: usize, pending_at: Option<usize>) -> Self {
203 Scripted::new(body.chunks(chunk.max(1)).map(|c| Ok(Bytes::copy_from_slice(c))).collect(), pending_at)
204 }
205 }
206
207 impl Stream for Scripted {
208 type Item = Result<Bytes, Error>;
209
210 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
211 if !self.pending_done && self.pending_at.is_some_and(|at| self.offset >= at) {
212 self.pending_done = true;
213 cx.waker().wake_by_ref();
214 return Poll::Pending;
215 }
216 let item = self.items.pop_front();
217 if let Some(Ok(chunk)) = &item {
218 self.offset = self.offset.saturating_add(chunk.len());
219 }
220 Poll::Ready(item)
221 }
222 }
223
224 fn parser(stream: Scripted) -> Multipart<Scripted> {
225 Multipart::new(stream, &Boundary::new(BOUNDARY).unwrap(), 4096)
226 }
227
228 fn drain_headers_async<S>(part: &mut Part<'_, S>) -> Vec<(String, Vec<u8>)>
229 where
230 S: Stream<Item = Result<Bytes, Error>> + Send + Unpin,
231 {
232 block_on(async {
233 let mut headers = Vec::new();
234 while let Some(header) = part.next_header().await.unwrap() {
235 headers.push((header.name.to_string(), header.value.to_vec()));
236 }
237 headers
238 })
239 }
240
241 fn drain_headers_poll<S>(part: &mut Part<'_, S>, cx: &mut Context<'_>) -> (Vec<(String, Vec<u8>)>, usize)
242 where
243 S: Stream<Item = Result<Bytes, Error>> + Send + Unpin,
244 {
245 let mut headers = Vec::new();
246 let mut pending = 0;
247 loop {
248 match part.poll_next_header(cx) {
249 Poll::Pending => pending += 1,
250 Poll::Ready(Ok(Some(header))) => headers.push((header.name.to_string(), header.value.to_vec())),
251 Poll::Ready(Ok(None)) => break,
252 Poll::Ready(Err(err)) => panic!("unexpected error: {err}"),
253 }
254 }
255 (headers, pending)
256 }
257
258 #[test]
261 fn poll_next_header_matches_the_async_path() {
262 let body = body();
263 let data_start = data_start(&body);
264 for (chunk, pending_at) in [(body.len(), None), (1, None), (7, Some(20)), (1, Some(data_start + 1))] {
265 let mut mp = parser(Scripted::chunked(&body, chunk, pending_at));
266 let mut part = block_on(mp.next_part()).unwrap().unwrap();
267 let expected = drain_headers_async(&mut part);
268 assert_eq!(expected.len(), 2, "chunk={chunk}");
269
270 let mut mp = parser(Scripted::chunked(&body, chunk, pending_at));
271 let mut part = block_on(mp.next_part()).unwrap().unwrap();
272 let waker = noop_waker();
273 let mut cx = Context::from_waker(&waker);
274 let (headers, pending) = drain_headers_poll(&mut part, &mut cx);
275 assert_eq!(headers, expected, "chunk={chunk}");
276 assert_eq!(headers[0].0, "Content-Disposition", "chunk={chunk}");
277 assert_eq!(headers[1].0, "Content-Type", "chunk={chunk}");
278
279 let expect_pending = pending_at.is_some_and(|at| at < data_start);
282 assert_eq!(pending > 0, expect_pending, "chunk={chunk}");
283
284 for _ in 0..2 {
286 assert!(matches!(part.poll_next_header(&mut cx), Poll::Ready(Ok(None))), "chunk={chunk}");
287 }
288 }
289 }
290
291 #[test]
294 fn poll_next_header_reports_the_same_errors_as_the_async_path() {
295 type Items = fn() -> Vec<Result<Bytes, Error>>;
296 let cases: [(&str, &str, Items); 3] = [
297 ("malformed header line", "InvalidFormat", || {
298 vec![Ok(Bytes::from_static(b"--B\r\nBad Header Line\r\n\r\nx\r\n--B--\r\n"))]
299 }),
300 ("stream error inside the header block", "StreamReadFailed", || {
301 vec![Ok(Bytes::from_static(b"--B\r\nX: y\r\n")), Err(stream_error())]
302 }),
303 ("stream ends inside the header block", "IncompleteStream", || {
304 vec![Ok(Bytes::from_static(b"--B\r\nX: y\r\n"))]
305 }),
306 ];
307
308 for (label, expected, items) in cases {
309 let mut mp = parser(Scripted::new(items(), None));
310 let mut part = block_on(mp.next_part()).unwrap().unwrap();
311 let async_kind = block_on(async {
312 loop {
313 match part.next_header().await {
314 Ok(Some(_)) => {}
315 Ok(None) => break "none",
316 Err(err) => break error_kind(&err),
317 }
318 }
319 });
320 assert_eq!(async_kind, expected, "{label}");
321
322 let mut mp = parser(Scripted::new(items(), None));
323 let mut part = block_on(mp.next_part()).unwrap().unwrap();
324 let waker = noop_waker();
325 let mut cx = Context::from_waker(&waker);
326 let poll_kind = loop {
327 match part.poll_next_header(&mut cx) {
328 Poll::Pending | Poll::Ready(Ok(Some(_))) => {}
329 Poll::Ready(Ok(None)) => break "none",
330 Poll::Ready(Err(err)) => break error_kind(&err),
331 }
332 };
333 assert_eq!(poll_kind, expected, "{label}");
334 }
335 }
336
337 #[test]
339 fn poll_next_data_reports_pending_before_the_next_chunk() {
340 let body = body();
341 let mut mp = parser(Scripted::chunked(&body, 1, Some(data_start(&body) + 1)));
342 let mut part = block_on(mp.next_part()).unwrap().unwrap();
343 let waker = noop_waker();
344 let mut cx = Context::from_waker(&waker);
345 let (headers, _) = drain_headers_poll(&mut part, &mut cx);
346 assert_eq!(headers.len(), 2);
347
348 let mut data = Vec::new();
349 let mut pending = 0;
350 loop {
351 match part.poll_next_data(&mut cx) {
352 Poll::Pending => pending += 1,
353 Poll::Ready(Ok(Some(chunk))) => data.extend_from_slice(&chunk),
354 Poll::Ready(Ok(None)) => break,
355 Poll::Ready(Err(err)) => panic!("unexpected error: {err}"),
356 }
357 }
358 assert!(pending > 0, "expected a Pending during the data phase");
359 assert_eq!(data, DATA);
360 }
361
362 #[test]
364 fn poll_next_data_reports_a_stream_error() {
365 let items = vec![Ok(Bytes::from_static(b"--B\r\nX: y\r\n\r\nhel")), Err(stream_error())];
366 let mut mp = parser(Scripted::new(items, None));
367 let mut part = block_on(mp.next_part()).unwrap().unwrap();
368 let waker = noop_waker();
369 let mut cx = Context::from_waker(&waker);
370 let (headers, _) = drain_headers_poll(&mut part, &mut cx);
371 assert_eq!(headers.len(), 1);
372
373 let kind = loop {
374 match part.poll_next_data(&mut cx) {
375 Poll::Pending | Poll::Ready(Ok(Some(_))) => {}
376 Poll::Ready(Ok(None)) => break "none",
377 Poll::Ready(Err(err)) => break error_kind(&err),
378 }
379 };
380 assert_eq!(kind, "StreamReadFailed");
381 }
382
383 #[test]
386 fn poll_next_data_reports_a_header_block_error() {
387 let items = vec![Ok(Bytes::from_static(b"--B\r\nBad Header Line\r\n\r\nx\r\n--B--\r\n"))];
388 let mut mp = parser(Scripted::new(items, None));
389 let mut part = block_on(mp.next_part()).unwrap().unwrap();
390 let waker = noop_waker();
391 let mut cx = Context::from_waker(&waker);
392 match part.poll_next_data(&mut cx) {
393 Poll::Ready(Err(err)) => assert_eq!(error_kind(&err), "InvalidFormat"),
394 other => panic!("expected InvalidFormat, got {other:?}"),
395 }
396 }
397
398 #[test]
401 fn poll_next_data_reports_pending_while_the_header_block_is_read() {
402 let body = body();
403 let mut mp = parser(Scripted::chunked(&body, 7, Some(20)));
404 let mut part = block_on(mp.next_part()).unwrap().unwrap();
405 let waker = noop_waker();
406 let mut cx = Context::from_waker(&waker);
407 assert!(matches!(part.poll_next_data(&mut cx), Poll::Pending));
408
409 let mut data = Vec::new();
410 loop {
411 match part.poll_next_data(&mut cx) {
412 Poll::Pending => {}
413 Poll::Ready(Ok(Some(chunk))) => data.extend_from_slice(&chunk),
414 Poll::Ready(Ok(None)) => break,
415 Poll::Ready(Err(err)) => panic!("unexpected error: {err}"),
416 }
417 }
418 assert_eq!(data, DATA);
419 }
420
421 #[test]
425 fn take_data_stream_after_a_partial_header_read_discards_the_rest() {
426 let body = body();
427 let mut mp = parser(Scripted::chunked(&body, body.len(), None));
428 let mut part = block_on(mp.next_part()).unwrap().unwrap();
429 let first = block_on(part.next_header()).unwrap().unwrap();
430 assert_eq!(first.name, "Content-Disposition");
431
432 let mut stream = part.take_data_stream().unwrap();
433 let data = block_on(async {
434 let mut data = Vec::new();
435 while let Some(chunk) = stream.next().await {
436 data.extend_from_slice(&chunk.unwrap());
437 }
438 data
439 });
440 assert_eq!(data, DATA);
441
442 let mut trailer = stream.into_final();
444 assert!(block_on(trailer.next()).is_none());
445 }
446
447 #[test]
450 fn next_data_without_reading_headers_yields_the_part_data() {
451 let body = body();
452 let mut mp = parser(Scripted::chunked(&body, body.len(), None));
453 let mut part = block_on(mp.next_part()).unwrap().unwrap();
454 let data = block_on(async {
455 let mut data = Vec::new();
456 while let Some(chunk) = part.next_data().await.unwrap() {
457 data.extend_from_slice(&chunk);
458 }
459 data
460 });
461 assert_eq!(data, DATA);
462 }
463}