1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023-2026 The s3s Authors
//! Asynchronous streaming parser for `multipart/form-data`.
//!
//! The parser consumes any [`futures_core::Stream`] of [`bytes::Bytes`]
//! chunks and yields individual parts together with their headers and data.
//!
//! # Compatibility notes
//!
//! This crate follows RFC 2046 section 5.1 and RFC 7578, with a small set
//! of deliberate differences. Every item in this list is documented at the
//! corresponding API item and has a regression test.
//!
//! - Parts carrying more header fields than the parser reads are accepted:
//! the surplus fields are ignored. RFC 7578 section 4.8 defines exactly
//! `Content-Disposition`, `Content-Type` and the deprecated
//! `Content-Transfer-Encoding`, requires any other header field to be
//! ignored, and a conforming part carries at most those three — so for a
//! conforming part the limit can only ever drop fields that such a part
//! cannot have. A part that carries more than three fields *and* puts
//! `Content-Disposition` after them loses that field, because the parser
//! reads the first three fields and ignores the rest: section 4.8 asks for
//! the surplus fields to be ignored, not for the parser to keep looking.
//! - A preamble before the first boundary and transport padding after a
//! boundary line are accepted.
//! - Field lines are parsed with the strict header grammar: a folded field
//! (a continuation line starting with optional whitespace) and a field name
//! with leading optional whitespace are both rejected with
//! [`Error::InvalidFormat`], because the block is read by `httparse` rather
//! than by the lenient line-folding rules of RFC 7230 section 3.2.4, which
//! let a server reject a folded field anyway.
//! - Empty part headers and empty part bodies are valid.
//! - `Content-Disposition` parsing is tolerant: parameter names and the
//! `form-data` token are case-insensitive, parameter order is free, and
//! both quoted and token values are accepted.
//! - `Content-Disposition` `name` and `filename` are returned as raw bytes:
//! no UTF-8 validation is performed, and callers decide how to convert or
//! reject invalid sequences. Parsing is best-effort, so a malformed
//! parameter tail is ignored instead of failing the parse.
//! - Quoted `Content-Disposition` parameter values keep their backslash
//! escape sequences: `quoted-pair` is not decoded (`\"` stays `\"`), because
//! returned values are always sub-slices of the header value. Callers that
//! need the decoded value have to decode it themselves.
//! - The boundary is validated when it is constructed, according to RFC 2046
//! section 5.1.1 (1 to 70 characters, only `bchars`, and no trailing
//! whitespace). A boundary that other parsers accept but RFC 2046 forbids is
//! rejected, and there is no unchecked constructor.
//! - A part data stream taken with `take_data_stream` ends at the closing
//! delimiter without interpreting what follows. Converting it with
//! `into_final` yields a `FinalPartDataStream` that enforces the strict
//! closing delimiter and rejects an epilogue, because callers need an
//! exact content length.
//! - The internal buffer limit bounds the part header block, not the delivery:
//! a block that ends within the limit is accepted whichever chunk carried it,
//! and one that grows past the limit without a terminator is rejected with
//! `HeaderSizeExceeded`. Data chunks yielded while streaming part data are
//! never retained and are not charged to the limit, and the parse never
//! depends on where the transport split the body. The buffer can still hold
//! one arriving chunk beyond the limit, because the bytes that follow the
//! terminator are retained until the header block is consumed.
//! - A chunk that carries no bytes is not progress and is not handed out:
//! the parser skips such chunks, yields to the executor after a bounded
//! number of them within one poll, and reports a stream failure once a
//! consecutive run grows past a fixed bound, so a stream that never sends
//! anything cannot keep the parser busy forever.
// Internal visibility: every module below is private and none of them is
// re-exported wholesale, so items that live in them are declared `pub` — for a
// crate-root child module that is the same reach as `pub(super)`, just shorter.
// Members of types that ARE re-exported must stay `pub(super)`: an inherent
// method or a field of a public type is public API even when its impl block
// lives in a private module, which rustc reports as `missing_docs` (the crate
// denies it) and as `private_interfaces` when the member type is internal.
pub use Error;
pub use Boundary;
pub use ;
pub use Multipart;
pub use Part;
pub use PartDataStream;
pub use FinalPartDataStream;