multer: Multer does not throw an error when limits: fileSize is exceeded and hangs

I have the following code to handle profile pic uploads. Everything works fine if under the limits: fileSize, but when fileSize is exceeded no error is thrown. Error handling based on https://github.com/expressjs/multer/issues/336. I have a custom disk storage engine. The problem with this is that I cannot send any error message or a response to react frontend.

var storage = ProfileStorage({
  square: true,
  responsive: true,
  greyscale: false,
  quality: 60
});

var limits = {
  //files: 1, // allow only 1 file per request
  fileSize: 100 * 1024
};

var fileFilter = function(req, file, cb) {
  var allowedMimes = ['image/jpeg', 'image/pjpeg', 'image/png', 'image/gif'];

  if (_.includes(allowedMimes, file.mimetype)) {
    cb(null, true);
  } else {
    cb(new Error('Invalid file type. Only jpg, png and gif image files are allowed.'));
  }
};

var upload = multer({
  storage: storage,
  limits: limits,
  fileFilter: fileFilter
});

The post method:

var profileUpload = upload.single('Profiilikuva');

module.exports = app => {
  app.post('/api/profile/save', requireLogin, (req, res) => {
    console.log('Entered profile save');

    profileUpload(req, res, (err) => {
      console.log('Entered profile upload.');
      if (err) {
        console.log('Error from multer: ', err);
        return res.status(400).send(err);
      }

      if (req.file) {
        console.log('Stored file: ', req.file.filename); // Storage engine stores under random filename
      }

      const {firstName, lastName, email, address,
        postalCode, descriptionText, lat, lng } = req.body;

      req.user.profileCompleted = true;
      req.user.firstName = firstName;
      // some more of these...
      req.user.save().then((user) => { 
        console.log('User saved');
        return res.send(user);
      }).catch(err => {
        console.log('Error saving user. ', err);
        return res.status(400).send('Error while saving user information');
      });
    });
  });
};

Log output when uploading 75KB:

Entered profile save
_handleFile called. { fieldname: 'Profiilikuva',
   originalname: '75KB.jpg',
   encoding: '7bit',
   mimetype: 'image/jpeg' }
 Entered profile upload.
 Stored file:  0b3dc28730ecc387115a03b3f860af20_lg.jpg
 User saved

Log output when uploading 190KB:

 Entered profile save
 _handleFile called. { fieldname: 'Profiilikuva',
   originalname: '190KB.jpg',
   encoding: '7bit',
   mimetype: 'image/jpeg' }
_removeFile called

About this issue

  • Original URL
  • State: open
  • Created 6 years ago
  • Reactions: 14
  • Comments: 40 (4 by maintainers)

Most upvoted comments

I am also facing the same issue. It doesn’t throw an error when the file limit exceeded.

“Though I think it is my custom storage engine that messes things up” I am using the google cloud storage engine for buckets.

“Seems the error handling is a bit tricky.” That’s a huge understatement.

I think the issue might have something to do with the file streams. As far as I know, the file streams for writing to the file system have more meta information than, e.g. streams to google storage buckets.

I faced this error using multer in nest.js app. It seems like multer just ignores limits.fileSize and goes on handling the uploaded file calling destination, filename callbacks etc, without any error. I just use diskStorage, not custom one.

Multer’s version is 1.4.2.

Improvised solution

image

I’ve added an additional middleware to handle file limits middleware


export const processFileMiddleware = multer({
  storage: memoryStorage(),
}).single('file');

export const fileLimitsMiddleware = asyncHandler(
  (request: express.Request, response: express.Response, next: express.NextFunction) => {
    if (!request.file) {
      throw new ServerError({
        code: ErrorCodes.FILE_NOT_FOUND,
        message: 'File not found',
        statusCode: 400,
      });
    }
    if (request.file.size > config.fileStorage.maxFileSize) {
      throw new ServerError({
        code: ErrorCodes.FILE_TOO_LARGE,
        message: `File too large. max ${humanFileSize(
          config.fileStorage.maxFileSize
        )}, received ${humanFileSize(request.file.size)}`,
        statusCode: 400,
      });
    }
    next();
  }
);

later I’ve used

protectedRouter.post('/file/upload', processFileMiddleware, fileLimitsMiddleware, uploadFileController);

working tests with vitest and supertest. make sure to have valid file paths

import supertest from 'supertest';
import { afterAll, beforeAll, describe, expect, test } from 'vitest';
import { Express } from 'express';
import { Server } from 'http';
import path from 'path';
import { initServer } from '../app';

describe('Upload file', () => {
  let app: Express;
  let server: Server;
  let request: supertest.SuperTest<supertest.Test>;
  beforeAll(async () => {
    ({ server, app } = await initServer());
    request = supertest(app);
  });

  afterAll(() => {
    server.close();
  });

  describe('POST /api/file/upload', () => {
    const bigFile = path.join(__dirname, './mocks/big.pdf');
    const smallFile = path.join(__dirname, './mocks/small.pdf');
    test('should upload successfuly', async () => {
      const res = await request
        .post('/api/file/upload')
        .set({ auth: 'any-token' })
        .attach('file', smallFile);

      expect(res.body).toEqual({ message: 'File uploaded successfully' });
      expect(res.status).toBe(200);
    });

    test('should return 400 if file is too large', async () => {
      const res = await request
        .post('/api/file/upload')
        .set({ auth: 'any-token' })
        .attach('file', bigFile);
      expect(res.status).toBe(400);
      expect(res.body).toEqual({
        errorCode: 'FILE_TOO_LARGE',
        message: 'File too large. max 1 byte, received 132 byte',
        statusCode: 400,
      });
    });
  });
});

This is so weird. It works perfectly on your sandbox @jonchurch . I’m here because I tried to get the file filter errors working:

export const uploadImage = multer({
    limits:{
        fileSize: 5241288,
        files: 1
    },
    fileFilter:  (req, file, callback) => {
        callback(new Error('Bad file type'), false)
    },
})
export const setAvatarImage = (req: Request, res: Response, next: NextFunction) => {   
    uploadImage.single('image')(req, res, error => {
        if(error) return res.status(400).send({errors})
    }
}

Which returns

{
    "error": {
        "storageErrors": []
    }
}

But the sandbox that I made seems to be working perfectly https://codesandbox.io/s/vibrant-lovelace-3bn90

Edit: I ended up manually setting req.fileValidationError on the request object because the documented way of cb(new Error('Text'), false) was not working for me.

Inspired by this answer: https://stackoverflow.com/a/35069987/1800515

I am currently using the latest version 1.4.2 and I have the following limit set: const upload = multer({ dest: "avatars", limit: { fileSize: 1000000, }, });

It still doesn’t throw any error