fastify / busboy

A streaming parser for HTML form data for node.js

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

How to break out of busboy and send back a regular response

ebitogu opened this issue · comments

Prerequisites

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

Issue

Hi there! Thank you so much for porting this amazing library. Probably the only award winning chunked file uploads library in the entire NodeJS ecosystem. I migrated to this library just now and it's been an amazing journey already!!!!

I however have a few questions about breaking out of busboy to send a regular response back to the client.

Here is my code:


 static async createNewPost(req, res) {
        let busboy = new Busboy({
            headers: req.headers,
            highWaterMark: 2 * 1024 * 1024,
            limits: {
                fileSize: Constants.PlatformData.MAXIMUM_ALLOWED_FILE_UPLOAD_SIZE,
            },
        });
        let apiKey = req.header(Strings.API_KEY);
        let badRequestError = Preconditions.checkNotNull({ apiKey});
        if (badRequestError) {
         // I wish to send back a response here like this:
         return res.status(404).send({   //But this line seems to be throwing a Server Request Interrupted error. How can I fix it?
            statusCode: 404,
            message: "You provided an invalid api key"
        });
        }
   }

But breaking out to send a response like the above is throwing Server Request Interrupted error.

Any insight on how best to break out of busboy and send back a regular response like the above will be truly appreciated.

Thank you.

So you check if the request header contains the API KEY. So actually you can do this earlier before piping the request into busboy.

@hashweather Yes, as @Uzlopak said, in your case it isn't even worth it to start reading the content stream. You should move the apiKey check to the very beginning of your function and do early return with reply.
You might want to actually put this check into a lifecycle hook outside of controller logic: https://www.fastify.io/docs/latest/Lifecycle/
Probably preHandler is the right phase:
https://www.fastify.io/docs/latest/Hooks/#prehandler

But what if I want to do it at the middle of the request E.g within this callback

busboy.on('field', function (fieldname, val, fieldnameTruncated, valTruncated, encoding, mimetype) {
            let data = JSON.parse(val);
            requestBody['data'] = data; ///If a required field is missing here how can I break out and send a response for the user to include that field and resubmit the request?
        });

Even within this callback


busboy.on('file', function (fieldname, file, filename, encoding, mimetype) {
            let newFileName = `${Math.random() * 100000000000}${filename}`;
            let fileExtension = filename.split('.')[filename.split('.').length - 1];
            newFileName = SHA256(newFileName);
            newFileName = `${newFileName}.${fileExtension}`;
            let filePath = path.join(uploadPath, newFileName);
            if (!filePaths.includes(filePath)) {
                filePaths.push(filePath);
                fileNames.push(newFileName);
            }
            let writeStream = fs.createWriteStream(filePath);
            file.pipe(writeStream);
        });

What if the current file size is too large and I wish to send back a response that the file size is too large?

I think there should be a clean way to break out and send back response to the user that the file size is too large. Isn't it?

To your first question:
Thats the risk of multipart. What if a field is missing or wrong after you parsed already some megabytes into your redis/RAM? This can be actually an attack vector, as you store more and more data, and maybe the malicious attacker keeps the connection open and your service waits for the trailer marker, so never hitting the clean up for the temporary stored data.

The solution has to be, that you design your api differently. E.g. you break the upload process in two parts. First you send all the non file data against a rest endpoint and after validation returns an upload toke. Then you use the upload token in the header or as an url parameter and finish the actual file upload.

To your second question:
Imho you have to pipe the streams and listen on the fileLimit-and Error Event.

Wow! I see. Cool. I never saw it in that security light. I'll refactor the API to reflect the suggested changes. Thank you so much👍

This doesn't cut it. Yes, the file is parsed but I still want to choose whether I save it to disk or not. I hoped I would find a solution for that here
Edit: the solution was to consume the file using file.resume()