node-formidable / formidable

The most used, flexible, fast and streaming parser for multipart form data. Supports uploading to serverless environments, AWS S3, Azure, GCP or the filesystem. Used in production.
MIT License
7k stars 680 forks source link

ContentType: text/plain not showing in fields #875

Open neronim1141 opened 1 year ago

neronim1141 commented 1 year ago

Support plan

Context

What are you trying to achieve or the steps to reproduce?

Im trying to make custom server functionality on nextjs api route, where i get some request data from game as "multipart/form-data;" but some of the data has "text/plain" content type.

router.post(async (req, res) => {

  const form = new IncomingForm();
  form.parse(req, (err, fields, files) => {
    console.log(fields);
    console.log(files);
  });

  res.end();
});

export const config = {
  api: {
    bodyParser: false,
  },
};

What was the result you got?

Only the files variable gets populated. As i needed to disable parser for nextjs i cant really use req.body. looping for 'text/plain' mimetype in files and parsing them manualy is not desired either

What result did you expect?

'text/plain' content type parsed into fields variable

GrosSacASac commented 1 year ago

If I understand correctly you want to access files sent as text/plain as if it was another field ?

GrosSacASac commented 1 year ago

This line in the code separates fields from files: if (!part.mimetype) {

So if you don't have a mimetype it will appear as a field

neronim1141 commented 1 year ago

Yes, i managed to do it in an ugly way around

for (let [fileField, filesArray] of Object.entries(files)) {
          const file = (filesArray as File[])[0];
          switch (fileField) {
            case "id":
              id = await fileToString(file.filepath);
              fs.rm(file.filepath);
              break;
            case "extraData":
              extraData = JSON.parse(await fileToString(file.filepath));
              fs.rm(file.filepath);
              break;
            default:
              tile = file;
              break;
          }
        }

but i think as text/plain usually contain string, if they arent too big maybe they should convert to fileds also?

for disclaimer i cant really change how i receive data, and 'id','extradata' are bassically oneliners i remember that i used that API with multer some time ago and it was quite simple

GrosSacASac commented 1 year ago

Do you suggest to change formidable ?

ajmeese7 commented 1 year ago

@GrosSacASac I would like to see this change as well, I missed this issue but I commented about running into the same problem in #495

GrosSacASac commented 1 year ago

What exactly should change ?

neronim1141 commented 1 year ago

if mimetype is text/plain it should be decoded into fields instead of file with provided charset, unless explicity specified, or allow us to exclude fields from converting to files

tunnckoCore commented 1 year ago

not automatically, and not default behavior

or allow us to exclude fields from converting to files

yep, through option.

prigaux commented 1 year ago

Formidable v1 used to put "a" in fields for:

Invoke-RestMethod -Uri '...' -Method Post -Form  @{ a = 'A'; file = Get-Item -Path '.\foo.txt' }

which sends:

Content-Type: text/plain; charset=utf-8
Content-Disposition: form-data; name="a"

Upgrading to Formidable v2 broke my app.

NB: curl does not send "Content-Type" for non-files.

prigaux commented 1 year ago

Workaround tested on v2:

form.onPart = function (part) {
    if (part.mimetype && !part.headers['content-disposition']?.match(/filename="/)) {
        // we want it to be a "field" (cf https://github.com/node-formidable/formidable/issues/875 )
        delete part.mimetype
    }
    form._handlePart(part);
}
okcompute commented 1 year ago

I have the same issue. My user is calling my API with a C# client. Their multipart/form implemtation always adds the text/plain mimetype. To solve the issue, I have to apply the solution from @prigaux above.

I think this should be fixed upstream in this library, no?

tunnckoCore commented 1 year ago

That's why we even expose internals and allow overriding, so it can be extended. ;)

Converting text/plain to fields by passing some option, could be a good feature too.

Formidable v1 used to put "a" in fields for:

Really strange.. :D Never noticed that, or I forgot it. That's weird. And what happens when multiple? Multiple as or what? haha.

CollapsedMetal commented 1 year ago

I can confirm that there's no way to succesfully get fields data from submited forms using HttpClient on C# Had no previous issues using v1 @prigaux workaround works great!

kkirby commented 1 year ago

So, the code used to look at the existence of a filename and would consider it a field if a file name did not exist.

I've been tracing back the code and it used to be what I would consider correct: https://github.com/node-formidable/formidable/blob/15f174f2457684fbfb76197fced22ae0fa52e8b2/src/incoming_form.js#L195

But then was changed to look at mime type here: https://github.com/node-formidable/formidable/commit/621e72a4deac6acf5f47f84986648d908eb2ae89

I'm not really sure what the change fixed, but it's not correct. A field is a file if it contains a filename. Otherwise, it's a field. Right??

pankaj89 commented 6 months ago

As of now only workaround by @prigaux is working, Is there any fix in library? I am using Retrofit as client in Android app.

msojocs commented 3 months ago

I am using this code:

const form = formidable({
        uploadDir: storagePath,
        createDirsFromUploads: true,
        keepExtensions: true,
    });
    form.onPart =  function (part) {
        log.info('part:', part)
        if (part.mimetype && !(part as any).headers['content-disposition']?.match(/filename="/)) {
            // we want it to be a "field" (cf https://github.com/node-formidable/formidable/issues/875 )
            part.mimetype = null
        }
        form._handlePart(part);
    }

After add 'onPart' function, the uploaded file can not open any more; if I comment them, the uploaded file can open again.