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 name="", 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_strict saw CR / LF / NUL bytes in the field name. Carries the original (un-sanitized) value.

  • FilenameContainsControlBytes(value: String)

    add_file_strict saw CR / LF / NUL bytes in the file’s filename. Carries the original (un-sanitized) value.

  • ContentTypeContainsControlBytes(value: String)

    add_file_strict saw CR / LF / NUL bytes in the file’s content type. Carries the original (un-sanitized) value.

  • EmptyFieldName(value: String)

    add_field_strict / add_file_strict saw an empty (or whitespace-only) field name. RFC 7578 §4.2 requires the Content-Disposition name parameter 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 enforce that — it silently accepts "" and also silently strips CR / LF / NUL bytes from name to prevent header injection. The cached name on the resulting Part reflects the sanitized value, matching what a parse-after-encode round-trip would produce. The silent acceptance and strip is data loss the caller cannot observe — add_field("", _) produces name="", and add_field("name\n", _) produces a part with name="" — 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:)). 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 in v0.1.0, so this falls through to application/octet-stream unless you call add_file_auto_with with a real 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:

  1. inferer.from_filename(filename)
  2. inferer.from_bytes(body)
  3. 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 new() -> Form

A new empty form.

pub fn parts(form: Form) -> List(part.Part)

Read the parts in insertion order.

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.

Search Document