High-accuracy, low-allocation multipart/form-data parsing with a zero-copy Rust core and thin targets for Node.js and the browser.
crates/fastmulp_core: zero-copy parser and native APIcrates/fastmulp_napi: Node.js bindings built withnapi-rscrates/fastmulp_wasm: browser bindings built withwasm-bindgen
- The Rust core parses against a borrowed
&[u8]and returns body ranges instead of copying payload bytes. Content-Dispositionis parsed eagerly, andnameis enforced forform-dataparts.- Header storage uses
SmallVecso the common case stays stack-friendly. - Node.js and browser bindings return metadata plus body ranges, so callers can slice the original buffer themselves.
- Boundary lines accept RFC 2046 transport padding, plus MIME-style preamble and epilogue.
- RFC 7578 Section 4.1: boundary handling
- RFC 7578 Section 4.2:
Content-Dispositionrequirements for each part - RFC 7578 Section 4.3: multiple files and older nested
multipart/mixed - RFC 2046 Section 5.1.1: multipart syntax, transport padding, preamble, epilogue
- HTML multipart/form-data algorithm: browser-side escaping rules for names and filenames
For deployed compatibility, fastmulp also accepts filename*= extended parameters even though RFC 7578 Section 4.2 says senders must not generate them.
filename values are untrusted input. Follow the security guidance in RFC 7578 Section 4.2 and avoid using path components blindly.
use fastmulp_core::parse;
let boundary = "demo-boundary";
let body = b"--demo-boundary\r\nContent-Disposition: form-data; name=\"field\"\r\n\r\nhello\r\n--demo-boundary--\r\n";
let multipart = parse(body, boundary.as_bytes())?;
let part = &multipart.parts()[0];
assert_eq!(part.name().and_then(|value| value.as_str().ok()), Some("field"));
assert_eq!(part.body(multipart.body()), b"hello");
# Ok::<(), fastmulp_core::Error>(())Node.js:
import { parse } from "./fastmulp.node";
const parts = parse(bodyBuffer, boundary);
const fileBytes = bodyBuffer.subarray(parts[0].bodyStart, parts[0].bodyEnd);Browser:
import { parse } from "fastmulp-wasm";
const parts = parse(formBytes, boundary);
const fieldBytes = formBytes.subarray(parts[0].body_start, parts[0].body_end);The wasm target still needs one JS-to-wasm copy at the ABI boundary, but it avoids extra copies after parsing by returning ranges instead of materialized part bodies.
Older nested multipart/mixed payloads can be handled by recursively calling parse on a part body after extracting the nested boundary from that part's Content-Type.
Use parse_with_limits or MultipartParser::new_with_limits when parsing untrusted uploads on a server boundary. ParseLimits can cap the number of parts, the number of headers per part, and the number of header bytes per part. The plain parse API stays unlimited for compatibility and small trusted payloads.
Treat filename as display metadata only. Strip path separators and platform-specific path components before using it in storage, generate your own server-side object names, and keep the original value only as untrusted metadata. Preserve duplicate field names in order unless your application explicitly defines a merge rule. For nested multipart/mixed, extract the nested boundary from that part's Content-Type and parse the nested body with its own limits.
Tag pushes build npm-compatible release artifacts for the JS targets:
fastmulp-node-linux-x64-*.tgz: platform-specific Node.js native addon containingfastmulp.node, CommonJS entrypoint, and TypeScript declarations.fastmulp-wasm-*.tgz: browser wasm package containing wasm-bindgen generated JavaScript glue, wasm, and TypeScript declarations.
The Node binding uses camelCase object fields such as bodyStart, bodyEnd, fileName, and contentType. The wasm binding preserves the existing snake_case field names generated by wasm-bindgen.
import { parse as parseNode } from "fastmulp-node-linux-x64";
import { parse as parseWasm } from "fastmulp-wasm";vp run release:patchvp run release:minorvp run release:alphavp run release:beta
Each release command updates the workspace version in Cargo.toml, creates a release commit, and creates the matching v... git tag. Tag pushes publish fastmulp-core through GitHub Actions trusted publishing and attach Node/wasm binding artifacts to the matching GitHub Release.
vp run fmtvp run fmt:checkvp run ci:localvp run lintvp run checkvp run testvp run benchvp run package:core
fastmulp is licensed under GPL-3.0-or-later. See LICENSE and the GNU GPL v3 text.