fastify / busboy

A streaming parser for HTML form data for node.js

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Malformed boundary does not throw

gurgunday opened this issue · comments

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the bug has not already been reported

Fastify version

X

Plugin version

X

Node.js version

20

Operating system

macOS

Operating system version (i.e. 20.04, 11.3, 10)

Latest

Description

I want to merge @fastify/busboy into undici, but a specific test will not currently pass:

test('busboy emit error', async (t) => {
  t.plan(1)
  const formData = new FormData()
  formData.append('field1', 'value1')

  const tempRes = new Response(formData)
  const formRaw = await tempRes.text()

  const server = createServer((req, res) => {
    res.setHeader('content-type', 'multipart/form-data; boundary=wrongboundary')
    res.write(formRaw)
    res.end()
  })
  t.teardown(server.close.bind(server))

  const listen = promisify(server.listen.bind(server))
  await listen(0)

  const res = await fetch(`http://localhost:${server.address().port}`)
  await t.rejects(res.formData(), 'Unexpected end of multipart data')
})

Busboy ignores this input rather than throwing for malformed boundary

I found within the code a part that should throw in this case (if I'm not mistaken), but it's not working in practice

I will take a look at this myself, but if anyone could help, it would be highly appreciated

Steps to Reproduce

Run this test after replacing busboy with @fastify/busboy in undici:

test('busboy emit error', async (t) => {
  t.plan(1)
  const formData = new FormData()
  formData.append('field1', 'value1')

  const tempRes = new Response(formData)
  const formRaw = await tempRes.text()

  const server = createServer((req, res) => {
    res.setHeader('content-type', 'multipart/form-data; boundary=wrongboundary')
    res.write(formRaw)
    res.end()
  })
  t.teardown(server.close.bind(server))

  const listen = promisify(server.listen.bind(server))
  await listen(0)

  const res = await fetch(`http://localhost:${server.address().port}`)
  await t.rejects(res.formData(), 'Unexpected end of multipart data')
})

Expected Behavior

Test should pass

Why should it actually throw?

That's what the undici unit test demands, and more importantly what the browser behavior is

Normal busboy does throw there, so I'm inclined to believe one of our patches prevents/ignores the error

undici throws, ok. Where is it defined that a non matching boundary should throw in the first place?

Anyhow: It should be actually no problem to patch it accordingly to throw.

Where is it defined that a non matching boundary should throw in the first place?

I actually couldn't spot it in the multipart formdata RFC, maybe they mimicked browser behavior (i.e., Safari)

commented

It's part of the fetch spec (https://fetch.spec.whatwg.org/#dom-body-formdata):

[...] Parse bytes, using the value of the `boundary` parameter from mimeType, per the rules set forth in Returning Values from Forms: multipart/form-data.
[...] If that fails for some reason, then [throw](https://webidl.spec.whatwg.org/#dfn-throw) a [TypeError](https://webidl.spec.whatwg.org/#exceptiondef-typeerror).

Returning a value from a malformed body doesn't seem like correct behavior to me - the body can't be parsed if the boundary is mismatched, but the parsing "succeeds" in the sense that an object is returned.

commented

In the RFC:

   In the case of multipart entities, in which one or more different
   sets of data are combined in a single body, a "multipart" media type
   field must appear in the entity's header.  The body must then contain
   one or more body parts, each preceded by a boundary delimiter line,
   and the last one followed by a closing boundary delimiter line.

note that the RFC says the body must contain > 1 parts and must be delimited with the boundary (new lines, etc.) https://www.rfc-editor.org/rfc/rfc2046#section-5.1

Ok, you convinced me.

Unfortunately i am currently not that deep in the codebase. But if you could provide a PR, i would review it asap.

Now all we need is a semver major release 🤠

I do wanna do some refactoring too but nothing involving behavior change

I have to bump @kibertoad to release a new major. I dont think I have npm publish rights.

Also I want to change to node-tap, so that tests, are faster

Also come to discord if possible.. have to coordinate with you.

I’d love to! Where can I find the Discord server

Click here: Discord

@climba03003 @Eomm

Although breaking, this was an important change that aligned busboy with the RFC

It will also let us merge this more performant version into undici

Can we make a release? I don't want to take initiative without asking 😁

Latest results:

{ cpu: { brand: 'M1', speed: '2.4 GHz' } }
| Node    | Option         | Msecs/op       | Ops/sec  | V8                    |
| ------- | -------------- | -------------- | -------- | --------------------- |
| 20.6.0  | fastify-busboy | 0.110915 msecs | 9015.906 | V8 11.3.244.8-node.14 |
| 20.6.0  | busboy         | 0.207797 msecs | 4812.393 | V8 11.3.244.8-node.14 |

@mcollina can you move this under the plugins team?

@gurgunday I can push out a release today

Alright, thanks!

Just to restate the obvious, this was a breaking change

@gurgunday Released 2.0.0