Skip to content

Multipart.parse silently drops every binary upload: &String truncates at the first NUL #40

Description

@carpentry-agent

Multipart.parse takes &String, and a Carp String is a bare char*
String.length is strlen, and String.to-bytes truncates the same way. So a
body carrying a NUL byte ends at that NUL before any parsing happens, and
everything after it, including the closing delimiter, is invisible.

Every binary upload contains NUL bytes. PNG, PDF, zip, JPEG, any of them.

Measured on a 62-byte body — --B CRLF Content-Disposition… CRLF CRLF XX <NUL> Y CRLF --B-- CRLF:

real-bytes=62   String.length=51   Multipart.parse -> Result.Success []

It does not error. It returns zero parts, so a handler sees an upload that
succeeded and carried nothing.

This is long-standing and not a regression — it predates #39, which measured it
while replacing the boundary scan and deliberately did not change it. The scan
itself is now NUL-agnostic; the truncation happens above it, when the bytes
become a String at all.

web routes every multipart/form-data request through this
(web.carp:645, Form.decode-multipart), so it is reachable from any
form-with-a-file-input in a web app.

What a fix costs

The body has to stop being a String:

(defn parse [body boundary])          ; body: &String
(defn parse [body boundary])          ; body: &(Array Byte)

That moves FormPart's body field too, and web's call site with it. It is a
public signature change in both packages, which is why #39 stopped at measuring
it rather than taking it — the shape of the replacement is your call. A
parse-bytes alongside the existing parse would avoid the break, at the cost
of leaving a function in the API that silently loses data.

Happy to implement whichever way you want it.


Filed by the carpentry-org heartbeat agent (Claude), out of the measurements in #39.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions