next.js icon indicating copy to clipboard operation
next.js copied to clipboard

Middleware throws `TypeError` when trying to parse FormData

Open lessp opened this issue 2 years ago • 5 comments

Verify canary release

  • [X] I verified that the issue exists in Next.js canary release

Provide environment information

Operating System:
  Platform: darwin
  Arch: x64
  Version: Darwin Kernel Version 21.4.0: Mon Feb 21 20:34:37 PST 2022; root:xnu-8020.101.4~2/RELEASE_X86_64
Binaries:
  Node: 16.13.2
  npm: 8.1.2
  Yarn: 1.22.15
  pnpm: N/A
Relevant packages:
  next: 12.1.6-canary.9
  react: 17.0.2

What browser are you using? (if relevant)

No response

How are you deploying your application? (if relevant)

No response

Describe the Bug

Not sure if I'm doing anything unexpected, but here goes:

request.formData() - throws

While trying to parse FormData in a middleware like so:

// pages/_middleware.ts
const handler = async (request: NextRequest, _nfe: NextFetchEvent) => {
    const data = await request.formData();
};

export default handler;

With the following request (application/x-www-form-urlencoded):

curl --request POST \
  --url http://localhost:3000/foo \    
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --data foo=bar

or (multipart/form-data):

curl --request POST \
  --url http://localhost:3000/foo \
  --header 'Content-Type: multipart/form-data; boundary=---011000010111000001101001' \
  --form foo=bar

Next throws the following error:

TypeError: Unrecognized Content-Type header value. 
FormData can only parse the following MIME types: multipart/form-data, application/x-www-form-urlencoded.

request.json() - works

Making a similar request but with JSON, using the request.json()-method everything works as expected.

curl --request POST \
  --url http://localhost:3000/foo \
  --header 'Content-Type: application/json' \
  --data '{ "foo": "bar" }'

Expected Behavior

request.formData() to parse and return the data for multipart/form-data and application/x-www-form-urlencoded

To Reproduce

  1. Create a middleware in pages/_middleware.ts
const handler = async (request: NextRequest, _nfe: NextFetchEvent) => {
    const data = await request.formData();
};

export default handler;
  1. Make a request similar to one of these:
curl --request POST \
  --url http://localhost:3000/foo \    
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --data foo=bar
curl --request POST \
  --url http://localhost:3000/foo \
  --header 'Content-Type: multipart/form-data; boundary=---011000010111000001101001' \
  --form foo=bar
  1. Next should throw the following error:
TypeError: Unrecognized Content-Type header value. 
FormData can only parse the following MIME types: multipart/form-data, application/x-www-form-urlencoded.

lessp avatar Apr 27 '22 07:04 lessp

Is anyone working on this? Can I? Thanks 🙏🏻

nelopuchades avatar May 01 '22 00:05 nelopuchades

hey @nelodev, I am not sure if you have any progress with it, so I'm going to check this out :pray:

Schniz avatar Jun 24 '22 07:06 Schniz

summary of #37980: a url encoded body does work, but multipart form data does not. it's blocked on undici/the edge-runtime library to implement this. :pray: lets do it

Schniz avatar Jun 24 '22 08:06 Schniz

Is there any way to convert the x-www-form-urlencoded data to JSON data, similar to bodyParser.urlencoded?

vaibhav2110 avatar Aug 03 '22 06:08 vaibhav2110

related: https://github.com/nodejs/undici/issues/974

Kikobeats avatar Aug 04 '22 11:08 Kikobeats

The latest Next.js canary should resolve this issue.

In fact, I built a little demo at https://with-edge.vercel.app/api/form-data :✨

// pages/api/form-data.ts

import { NextRequest, NextResponse } from 'next/server'

export const config = { runtime: 'experimental-edge' }

/**
 * Original source
 * https://gist.github.com/eligrey/8335f09276492e69b747fb4017e9570e
 * 
 * Get the cryptographic hash of an ArrayBuffer
 *
 * @param ab - ArrayBuffer to digest
 * @param algorithm - Cryptographic hash digest algorithm
 * @returns Hexadecimal hash digest string
 */
 export const hash = async (
  algorithm: string,
  ab: ArrayBuffer,
): Promise<string> =>
  new Uint8Array(await crypto.subtle.digest(algorithm, ab)).reduce(
    (memo, i) => memo + i.toString(16).padStart(2, '0'),
    '',
  );

const ENDPOINT = process.env.NODE_ENV === 'production' 
  ? 'https://with-edge.vercel.app'
  : 'http://localhost:3000'

export default async function handler(req: NextRequest) {
  if (req.method === 'GET') {
    return NextResponse.json({
      description: 'Upload a file as multipart.',
      usage: `curl -X POST ${ENDPOINT}/api/form-data -F "file=@/logo.png" | jq`
    })
  }

  const formData = await req.formData()
  const file = formData.get('file') as File
  const arrayBuffer = await file.arrayBuffer()

  return new Response(JSON.stringify({
    name: file.name,
    type: file.type,
    size: file.size,
    sha256: await hash('SHA-256', arrayBuffer),
    sha1: await hash('SHA-1', arrayBuffer),
    sha384: await hash('SHA-384', arrayBuffer),
    sha512: await hash('SHA-512', arrayBuffer)
  }))
}

Can you confirm is works for you using Next.js v12.3.2-canary.22 or above?

(if not, I will reopen the issue)

Kikobeats avatar Oct 07 '22 10:10 Kikobeats

This closed issue has been automatically locked because it had no new activity for a month. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.

github-actions[bot] avatar Nov 06 '22 12:11 github-actions[bot]