20

As question title explains, I need to filter uploaded files on the basis of file extension. So, I went through the official docs and searched this website.

What I've Tried

I've tried solutions I came across. Files are being successfully uploaded, but the problem is how to filter files. Currently my Router.js file looks like this.

Router.JS

var multer  = require('multer');
var storage = multer.diskStorage({ //multers disk storage settings
    destination: function (req, file, cb) {
        cb(null, './public/uploads/')
    },
    limits:{
        files: 1,
        fileSize: 1024 * 1024
    },
    filename: function (req, file, cb) {
        var datetimestamp = Date.now();
        cb(null, file.fieldname + '-' + datetimestamp + '.' + file.originalname.split('.')[file.originalname.split('.').length -1])
    },
    onFileUploadStart: function(file) {
        console.log("Inside uploads");
        if (file.mimetype == 'image/jpg' || file.mimetype == 'image/jpeg' || file.mimetype == 'image/png') {
            return true;
        }
        else
        {
            return false;
        }
    }
});
var upload = multer({ //multer settings
    storage: storage
}).single('profilepic');


router.post('/profile', function(req, res){
    upload(req,res,function(err){
        if(err)
        {
            console.log(err);
        }
        else
        {
            console.log("Image was uploaded");
        }
    });
});

I tried echoing something in onFileUploadStart to check if it is going into that function or not. And it was not. Besides onFileUploadStart, I also tried fileFilter as mentioned at this link, but it was not helpful. Any suggestions how to solve this? Thanks in advance.

A J
  • 3,970
  • 14
  • 38
  • 53
  • Please check this comment in multer issue. https://github.com/expressjs/multer/issues/114#issuecomment-231591339 – bgth Sep 26 '18 at 11:12

5 Answers5

49

An example using multer:

var storage = multer.diskStorage({ //multers disk storage settings
    destination: function (req, file, cb) {
        cb(null, './public/uploads/')
    },
    filename: function (req, file, cb) {
        var datetimestamp = Date.now();
        cb(null, file.fieldname + '-' + datetimestamp + '.' + file.originalname.split('.')[file.originalname.split('.').length -1])
    }
});

var upload = multer({ //multer settings
    storage: storage,
    fileFilter: function (req, file, callback) {
        var ext = path.extname(file.originalname);
        if(ext !== '.png' && ext !== '.jpg' && ext !== '.gif' && ext !== '.jpeg') {
            return callback(new Error('Only images are allowed'))
        }
        callback(null, true)
    },
    limits:{
        fileSize: 1024 * 1024
    }
}).single('profilepic');

Excerpted from Node.js - File upload. The original authors were Iceman and Mikhail. Attribution details can be found on the contributor page. The source is licenced under CC BY-SA 3.0 and may be found in the Documentation archive. Reference topic ID: 4080 and example ID: 14210.

Jon 'links in bio' Ericson
  • 20,880
  • 12
  • 98
  • 148
A J
  • 3,970
  • 14
  • 38
  • 53
  • 1
    `var ext = path.extname(file.originalname);` can you define `path` please – Khaled Ramadan Jul 17 '19 at 16:38
  • @KhaledRamadan Add `var path = require('path');` – A J Jul 18 '19 at 06:21
  • Hi Im kinda new to this package @AJ . Im trying to validate my req.body before upload, where would i pass my validation function; and if it fails, will it send the error response to the client? – O'Dane Brissett Sep 19 '19 at 12:45
  • @O'DaneBrissett I think the file should be validated separately. You can validate the all inputs in `req.body` first and then file validation. – A J Nov 13 '19 at 05:31
  • 1
    more efficient way is to remove `var ext = path.extname` line and add a `RegExp` to check eg. `const allowedExtensions = new RegExp(/.(jpg|png|jpeg)$/gi)` and test – Swapnil Soni Mar 12 '21 at 12:03
  • This is only the extension right? I mean couldnt someone just change the extension and hide something nefarious? Would checking the mime type help prevent that? – Christopher Franko Apr 18 '22 at 16:36
  • 1
    @ChristopherFranko Yes, it only validates the extension. But for better security, check mime type as well. – A J Apr 18 '22 at 16:48
  • tyvm I wrote this `/^image\/(png|p?jpeg|gif)$/.test(file.mimetype)` – Christopher Franko Apr 19 '22 at 15:23
1

I'm testing this alternative now. Seems viable to me but I'm still evaluating ( with typescript):

import crypto from 'crypto';
import multer, { FileFilterCallback } from 'multer';
import { resolve } from 'path';

const tmpFolder = resolve(__dirname, '..', '..', 'tmp');

const fileSize = 50 * 1024 * 1024;

export default {
  tmpFolder,
  fileFilter: (
    request: Express.Request,
    file: Express.Multer.File,
    callback: FileFilterCallback,
  ) => {
    const acceptedTypes = file.mimetype.split('/');

    if (acceptedTypes[0] === 'image' || acceptedTypes[0] === 'video') {
      callback(null, true);
    } else {
      callback(null, false);
      callback(new Error('Only images and videos formats allowed!'));
    }
  },
  limits: {
    fileSize,
  },
  storage: multer.diskStorage({
    destination: tmpFolder,
    filename: (request, file, callback) => {
      const fileHash = crypto.randomBytes(16).toString('hex');
      const fileName = `${fileHash}-${file.originalname}`;

      return callback(null, fileName);
    },
  }),
};
  • This is a better answer than the accepted one: we should check for MIME type of the file, not its extension... – MarcoS Mar 29 '23 at 15:22
0

This is very helpful thank you. In my case I needed to add a middleware to ensure a JWToken was in place and build it with XHR. If someone else is needing help with this here is what worked for me. It can be easily accomplished when passing the token in the XHR header.

Client:

    const loadHandler = event =>{
        // loadHandler mandate
    }

    const errorHandler = event => {
        // error mandate
    }

    const abortHandler = event => {
        // abort mandate
    }

    const data = new FormData()
    data.append('file', upFile)

    var formData = new FormData();
    var xhr = new window.XMLHttpRequest();

    formData.append('files', upFile); // this is a state object set onChange
    xhr.open('post', '/uploadFile', true);
    xhr.setRequestHeader('token', thetoken); // Passing token in header

    xhr.addEventListener("load", loadHandler, false);
    xhr.addEventListener("error", errorHandler, false);
    xhr.addEventListener("abort", abortHandler, false);

    xhr.send(formData);

Server

const express = require('express'),
      app= express.Router(),
      rf = require('./RoutFuctions');
...
app.post('/uploadFile', rf.verifyToken, function (req, res, next) {
     // upload portion above
});

RoutFunctions.js

const jwt = require('jsonwebtoken')

const tokenTest = (token, res, jwt, caller, next) => {
    jwt.verify(token, process.env.SECRET_KEY, err => {
        if(err) {
          res.sendStatus(403);
          console.log('could not verify token');
        } else {
          console.log("token verified");
          next(); // Next middleware
        }
    });
}

exports.verifyToken = function(req, res, next) {

    if(req.body.token !== undefined) { // non upload scenario
        var caller = ''
        if(req.body.caller !== undefined) caller = req.body.caller;
        tokenTest(req.body.token, res, jwt, caller, next)

    } else {  // attempt to extract token in XHR header upload scenario

      if(req.headers.token !== undefined){
          var token = req.headers.token
          tokenTest(req.headers.token, res, jwt, caller, next)
      } else {
          res.sendStatus(403);
      }
    }
}
Michael Nelles
  • 5,426
  • 8
  • 41
  • 57
0

Complete Implementation for multiple or single files.

Note: for single file use .single('filename') or .array('filename',1)

  const express = require("express");
  const app = express();
  var session = require('express-session')
  const multer = require("multer");
  const path = require("path");
  var storage = multer.diskStorage({ //multers disk storage settings
    destination: function (req, file, cb) {
        cb(null, path.join(__dirname,"uploads"))
    },
    filename: function (req, file, cb) {
        var datetimestamp = Date.now();
        cb(null, file.fieldname + '-' + datetimestamp + '.' + file.originalname.split('.')[file.originalname.split('.').length -1])
    }
  });

  var upload = multer({ //multer settings
    storage: storage,
    fileFilter: function (req, file, callback) {
        var ext = path.extname(file.originalname);
        if(ext !== '.png' && ext !== '.jpg' && ext !== '.gif' && ext !== '.jpeg') {
            return callback(new Error('Only images are allowed'))
        }
        callback(null, true)
    },
    limits:{
        fileSize: 1024 * 1024
    }
  }).array('profile',2);
  app.post("/upload", upload,(req, res) => {
    res.json({
      msg:"uploaded"
    })
  });

  app.use((err,req,res,next)=>{
    console.log(err.message);
    res.status(404).json({
      msg:err.message
    })
  })
  app.listen(
    8080,
    (error) => {
      if (error) {
        console.log("error");
      }
    },
    () => {
      console.log("listening at port 8080");
    }
  );

Note: You can create your own custom error messages by extending Error and send custom messages and status in err,req,res,next block

Sundar Gautam
  • 445
  • 1
  • 6
  • 10
-10

If you are used to web development, you can be validated at the front end, not passed to the back end,you can try:

var file = files[0];  
var imageType = /image.*/;  

if (!file.type.match(imageType)) {  
     // do something  
} 
jamacy
  • 1
  • 2
    Note that your scripts can still be manipulated at the source file in the browser console. So never rely only on frontend validation – Fillipo Sniper Jun 19 '18 at 13:38
  • 12
    You should never rely on frontend validation, thats 100% true, but you should `ALSO` do frontend validation, not all users are developers that can change the source code, some of them are just stupid and validation on client side will save some traffic – Daniel Krom Jun 26 '18 at 09:36