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)
}

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.

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.

Carriage returns, line feeds, and NUL bytes in name are silently stripped 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 strip is data loss the caller cannot observe — 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 the bad input as Error(NameContainsControlBytes(value:)) instead. 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:)).

The non-strict add_field silently strips these bytes (so add_field("name\n", _) produces a part with name=""). 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”. (#40)

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.

Carriage returns, line feeds, and NUL bytes in name, filename, and content_type are silently stripped 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(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.

The non-strict add_file silently strips these 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)

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