multipartkit/form
Types
Opaque builder for multipart/form-data messages.
Form is constructed via new and accumulated with add_field /
add_file / add_file_auto / unsafe_add_part. Read it back as
List(Part) via parts. The boundary is generated lazily by
encode_form and is not part of Form’s observable state.
pub opaque type Form
Reasons the strict form-builder variants reject input.
The non-strict add_field / add_file / add_file_auto /
add_file_auto_with silently strip CR / LF / NUL bytes from
the values that flow into header lines (sealing the #28 header
injection vector). The silent strip is data loss the caller
cannot observe — add_field("name\n", _) produces a part renamed
to "_unnamed_<n>" (the lenient path never emits name=""; see
add_field), and add_file(_, "fi\nle.png", _, _) concatenates
the two halves into a different valid filename. The *_strict
variants surface this as a typed error so callers can render
“field name foo\\nbar contains forbidden control bytes”
rather than silently producing the wrong wire. (#40, #41)
pub type FormError {
NameContainsControlBytes(value: String)
FilenameContainsControlBytes(value: String)
ContentTypeContainsControlBytes(value: String)
EmptyFieldName(value: String)
}
Constructors
-
NameContainsControlBytes(value: String)add_field_strictsaw CR / LF / NUL bytes in the field name. Carries the original (un-sanitized) value. -
FilenameContainsControlBytes(value: String)add_file_strictsaw CR / LF / NUL bytes in the file’s filename. Carries the original (un-sanitized) value. -
ContentTypeContainsControlBytes(value: String)add_file_strictsaw CR / LF / NUL bytes in the file’s content type. Carries the original (un-sanitized) value. -
EmptyFieldName(value: String)add_field_strict/add_file_strictsaw an empty (or whitespace-only) field name. RFC 7578 §4.2 requires theContent-Dispositionnameparameter to be the field name, and an empty name produces a wire image whose interpretation is implementation-defined at the receiver (some servers skip the field, some overwrite siblings keyed on"", some reject the whole body). Carries the original (un-trimmed) value so callers can render diagnostics. (#51)
Values
pub fn add_field(
form: Form,
name name: String,
value value: String,
) -> Form
Append a text field. value is encoded as UTF-8 in the part body. No
filename is set.
RFC 7578 §4.2 requires the Content-Disposition name parameter
to be non-empty (it is the field name itself). This non-strict
variant does not reject a bad name — it silently strips CR /
LF / NUL bytes to prevent header injection. To guarantee the
resulting part is always RFC 7578-addressable, a name that is
empty (or becomes empty / whitespace-only after the strip) is
replaced with a generated placeholder "_unnamed_<n>", where <n>
is the part’s zero-based position in the form. This means the
observable name is never "" — add_field("", _) and
add_field("name\n", _) both produce a part named "_unnamed_0"
rather than a silently empty-named part whose receiver
interpretation is implementation-defined (#57, #58). The rename is
still a loss the caller cannot prevent here, so callers passing
user-typed or upstream data into name should prefer
add_field_strict, which surfaces both failure modes as typed
errors (EmptyFieldName(value:) and NameContainsControlBytes(value:))
instead of renaming. The cached name on the resulting Part
reflects the placeholder and survives a parse-after-encode round-trip
unchanged. The placeholder is position-based, not collision-proof: a
caller who also passes a literal "_unnamed_<k>" as a real field name
can end up with two parts sharing that name. Use unsafe_add_part if
byte-exact preservation of arbitrary header values is required.
pub fn add_field_strict(
form: Form,
name name: String,
value value: String,
) -> Result(Form, FormError)
Strict counterpart of add_field: rejects names containing CR /
LF / NUL bytes with Error(NameContainsControlBytes(value:)),
and rejects empty or whitespace-only names with
Error(EmptyFieldName(value:)).
The non-strict add_field silently strips control bytes (so
add_field("name\n", _) produces a part with name=""), and
also silently accepts a truly empty name. RFC 7578 §4.2
requires the Content-Disposition name parameter to be the
field name; an empty name produces a wire image whose
interpretation is implementation-defined at the receiver. For
callers that pass user-typed or upstream data into name and
want to surface bad inputs as a typed error rather than silent
data loss, use this variant. The value payload carries the
caller’s original input so the error renders as
“field name foo\\nbar contains forbidden control bytes” or
“field name is empty”. (#40, #51)
pub fn add_file(
form: Form,
name name: String,
filename filename: String,
content_type content_type: String,
body body: BitArray,
) -> Form
Append a file part with an explicit content type.
RFC 7578 §4.2 requires the Content-Disposition name parameter
to be non-empty (the filename parameter may legitimately be
empty). This non-strict variant does not enforce the
non-empty name rule — it silently accepts "" and also
silently strips CR / LF / NUL bytes from name, filename, and
content_type to prevent header injection. The cached name,
filename, and content_type on the resulting Part reflect
the sanitized values. The strip on filename is especially
dangerous — add_file(_, "fi\nle.png", _, _) concatenates the
two halves into the different valid filename "file.png",
which can change authorisation-relevant identifiers. Callers
passing user-typed or upstream data should prefer
add_file_strict, which surfaces the bad input as
Error(EmptyFieldName(value:)) /
Error(NameContainsControlBytes(value:)) /
Error(FilenameContainsControlBytes(value:)) /
Error(ContentTypeContainsControlBytes(value:)). Use
unsafe_add_part if byte-exact preservation of arbitrary header
values is required.
pub fn add_file_auto(
form: Form,
name: String,
filename: String,
body: BitArray,
) -> Form
Append a file part using the default (no-op) inferer.
Equivalent to add_file_auto_with(form, name, filename, body, infer.default_inferer()). The default inferer returns None from both
helpers, so this always falls through to application/octet-stream —
inference is opt-in. To resolve well-known extensions and magic-byte
signatures via nao1215/mimetype, call add_file_auto_with with
infer.builtin_inferer() (or your own Inferer).
pub fn add_file_auto_with(
form: Form,
name: String,
filename: String,
body: BitArray,
inferer: infer.Inferer,
) -> Form
Append a file part, inferring the content type via the supplied
Inferer.
Inference precedence:
inferer.from_filename(filename)inferer.from_bytes(body)application/octet-stream
The inferred content type is sanitized (CR / LF / NUL stripped) before being written to the header.
pub fn add_file_strict(
form: Form,
name name: String,
filename filename: String,
content_type content_type: String,
body body: BitArray,
) -> Result(Form, FormError)
Strict counterpart of add_file: rejects names, filenames, and
content types containing CR / LF / NUL bytes with the matching
FormError variant, and rejects an empty or whitespace-only
name with Error(EmptyFieldName(value:)). (filename may
legitimately be empty — only name is required to be
non-empty per RFC 7578 §4.2.)
The non-strict add_file silently strips control bytes. For
name the strip leaves an empty string (loud round-trip
failure); for filename it concatenates the two halves into a
different valid filename, which can change
authorisation-relevant identifiers (the attacker shape
described in #41). The strict variant catches both at the
builder boundary so the wrong wire never gets produced.
(#41, #51)
pub fn unsafe_add_part(form: Form, the_part: part.Part) -> Form
Append a pre-built Part without validation or normalisation.
The caller is responsible for keeping headers, name, filename, and
content_type mutually consistent. Prefer add_field / add_file /
add_file_auto for library-maintained consistency.