Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

postForm's form-data serialization breaks for Blob in Node.js #6352

Open
exchgr opened this issue Apr 12, 2024 · 1 comment
Open

postForm's form-data serialization breaks for Blob in Node.js #6352

exchgr opened this issue Apr 12, 2024 · 1 comment

Comments

@exchgr
Copy link

exchgr commented Apr 12, 2024

Describe the bug

When I try to have postForm automatically serialize a Blob in Node.js, it fails with the error message "Blob is not supported. Use a Buffer instead." I dug into it a bit, and it seems the culprit is a combination of things.

To Reproduce

  1. Install the latest version of Node.js (at the time of writing: v21.7.3) and
  2. Run a server that accepts file uploads at POST /api/upload (or whatever endpoint you'd like)
  3. Have an image file in the path /tmp/image.jpg (or wherever you'd like)
  4. Run the code below in Node.js

Code snippet

const filename = "/tmp/image.jpg"

axios.postForm(
	"/api/upload",
	{
		"files": {file: await fs.openAsBlob(filename), filename}
	}
)

Expected behavior

postForm serializes the object containing the Blob as FormData and uploads it successfully

Axios Version

1.6.8

Adapter Version

No response

Browser

Node.js

Browser Version

v21.7.3

Node.js Version

v21.7.3

OS

macOS 14.4.1

Additional Library Versions

N/A

Additional context/Screenshots

On lib/helpers/toFormData.js:92, formData gets instantiated as PlatformFormData, which comes from the form-data package. This probably shouldn't happen, since Node.js has its own built-in FormData. I'd suggest swapping the order of that || statement to have any environment's built-in FormData take precedence.

There's a long-standing bug in form-data, form-data/form-data#396, wherein FormData[Symbol.toStringTag] is undefined. FormData[Symbol.iterator] is also undefined, but that's not covered in the issue. However, it is relevant here. The function isSpecCompliantForm (lib/utils.js:632) uses these to check whether whatever's getting passed in (thing) is indeed an instance of FormData. I'd suggest doing that a different way, such as using instanceof.

What does all this have to do with Blob? Well, for some reason that's beyond me, in toFormData, isSpecCompliantForm is tied to useBlob (lib/helpers/toFormData.js:110), the boolean that determines whether or not Blob is supported. In fact Blob is supported, by virtue of the fact that _Blob is defined in the previous line. I'd suggest decoupling these expressions, since bundling together FormData validation and Blob validation is too complex for one boolean, especially when the name of that boolean only references one of them. It unnecessarily breaks Blob support based on a bug in a third-party implementation of FormData—which, as you'll recall from earlier, we probably shouldn't even prefer to use in the first place.

Another question that comes to mind is: why do we even need to validate FormData at all here? Since we're already preferring a specific version of a third-party implementation of FormData, I think we can safely assume that what we have on our hands is actually a spec-compliant FormData (except, of course, for the finer details of Symbol.toStringTag where form-data differs from FormData). The only reason I can think of to validate FormData is if (as we should) we preferred to use the environment's built-in FormData, which might differ between environments. And doing that would actually fix this bug altogether.

@DigitalBrainJS
Copy link
Collaborator

DigitalBrainJS commented Apr 28, 2024

Axios supports Node v12 and above, so we can't just go the easy way and use the latest built-ins but have to support different formData implementations, as Axios is designed to be as flexible as possible.
In Axios 1.x, form-data is the default implementation. It does not support Blobs, but Streams and Buffers and doesn't follow web specs. Before version 2.x, it is not possible to change the usage priority of the FormData implementations without introducing breaking changes. And most likely even in the next major version, form-data support will still be relevant, because a lot of educational materials and codebases use it.
So for now you can either use streams/buffers in the context of the form-data module

const {data} = await axios.postForm('http://httpbin.org/post', {
  x: 1,
  stream: fs.createReadStream('1.txt')
});

or set another(native) FormData implementation as default.

const {data} = await axios.postForm('http://httpbin.org/post', {
  x: 1,
  blob: await fs.openAsBlob('2.txt')
}, {
  env: {
    FormData // native FormData constructor
  }
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants