{"record":{"id":"5106d8e463cfb3c3","repo":"denoland/deno","slug":"cannot-construct-multipartparser-multipart-form-d","errorCode":null,"errorMessage":"Cannot construct MultipartParser: multipart/form-data must provide a boundary","messagePattern":"Cannot construct MultipartParser: multipart/form-data must provide a boundary","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"ext/fetch/21_formdata.js","lineNumber":409,"sourceCode":"const MAX_MULTIPART_PART_HEADER_SIZE = 16 * 1024;\nconst MAX_MULTIPART_PART_HEADER_COUNT = 128;\n\n/**\n * @param {Uint8Array} bytes\n * @returns {string}\n */\nfunction decodeLatin1Bytes(bytes) {\n  return ReflectApply(StringFromCharCode, null, bytes);\n}\n\nclass MultipartParser {\n  /**\n   * @param {Uint8Array} body\n   * @param {string | undefined} boundary\n   */\n  constructor(body, boundary) {\n    if (!boundary) {\n      throw new TypeError(\n        \"Cannot construct MultipartParser: multipart/form-data must provide a boundary\",\n      );\n    }\n\n    this.boundary = `--${boundary}`;\n    this.body = body;\n    this.boundaryChars = core.encode(this.boundary);\n  }\n\n  /**\n   * @param {string} headersText\n   * @returns {{ headers: Headers, disposition: Map<string, string> }}\n   */\n  #parseHeaders(headersText) {\n    const headers = new Headers();\n    const rawHeaders = StringPrototypeSplit(headersText, \"\\r\\n\");\n    let headerCount = 0;\n    for (let i = 0; i < rawHeaders.length; ++i) {","sourceCodeStart":391,"sourceCodeEnd":427,"githubUrl":"https://github.com/denoland/deno/blob/89f33cbef296a2b287f323d42de54c871fa69c77/ext/fetch/21_formdata.js#L391-L427","documentation":"MultipartParser (internal class in ext/fetch/21_formdata.js) requires a truthy boundary string; its constructor throws immediately otherwise. The public route is request.formData()/response.formData(): 22_body.js extracts boundary from the Content-Type and only rejects a null (absent) parameter, so an empty boundary= value slips through as \"\" and fails here.","triggerScenarios":"A request/response with Content-Type: multipart/form-data; boundary= (empty parameter) reaching .formData(); hand-rolled multipart senders that emit the parameter with no value; malformed proxy rewrites that truncate the Content-Type.","commonSituations":"Custom test servers or PHP/Python scripts that build multipart Content-Types manually and forget the boundary value; proxies or middlewares that normalize and truncate header parameters; fuzzed input in security tests.","solutions":["Fix the sending side to always generate a real boundary (browsers and the FormData + fetch path do this automatically)","Validate before parsing: read the Content-Type, confirm the essence is multipart/form-data and the boundary parameter is present and non-empty","Return 400 early when the boundary check fails instead of letting formData() throw"],"exampleFix":"// before\nconst form = await req.formData(); // throws on boundary=\n\n// after\nconst ct = req.headers.get(\"content-type\") ?? \"\";\nconst m = /boundary=([^;]+)/i.exec(ct);\nif (!m || m[1].trim() === \"\") {\n  return new Response(\"bad multipart content-type\", { status: 400 });\n}\nconst form = await req.formData();","handlingStrategy":"validation","validationCode":"const ct = req.headers.get(\"content-type\") ?? \"\";\nconst boundary = /boundary=([^;]+)/i.exec(ct)?.[1]?.trim();\nif (!boundary) return new Response(\"multipart boundary missing\", { status: 400 });\nconst form = await req.formData();","typeGuard":"function hasMultipartBoundary(contentType: string | null): boolean {\n  return contentType != null && /multipart\\/form-data/i.test(contentType) &&\n    /boundary=[^;]/i.test(contentType) && !/boundary=\\s*(;|$)/i.test(contentType);\n}","tryCatchPattern":"try { return await req.formData(); } catch (e) {\n  if (e instanceof TypeError && e.message.includes(\"must provide a boundary\")) {\n    return new Response(\"bad multipart body\", { status: 400 });\n  }\n  throw e;\n}","preventionTips":["Always generate the boundary on the sender; never hand-write boundary=","Validate Content-Type parameters before parsing untrusted requests","Return 400 for malformed multipart instead of letting the parser throw to the framework"],"tags":["fetch","form-data","multipart","http"],"backgroundTag":null,"analyzedSha":"89f33cbef296a2b287f323d42de54c871fa69c77","analyzedAt":"2026-08-16T07:54:21.310Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}