kylefarris / clamscan

A robust ClamAV virus scanning library supporting scanning files, directories, and streams with local sockets, local/remote TCP, and local clamscan/clamdscan binaries (with failover).
MIT License
236 stars 69 forks source link

Filepath of an uploaded file #35

Closed NikhilNanjappa-zz closed 5 years ago

NikhilNanjappa-zz commented 5 years ago

Hi Guys,

First of all, thanks for the brilliant library. It really helped us a lot!

My issue/question/doubt

I understand that the .is_infected (or .scan_file) function takes in the absolute path of the file - which works perfectly fine in local.

But, what if the application has an file-upload feature (<input type="file">). The application obviously can't get the absolute path of the user uploaded file. In that case, how do we pass the filepath to the .is_infected or use another method perhaps ?

It might be a basic question, please bear with me.

kylefarris commented 5 years ago

Hey, no problem... it's not a bad question. You have a couple options. You can either allow the file to upload fully to your server like you normally would and then scan the file in place (wherever the location is that your app uploads to) or you can use the passthrough method to scan the raw file stream as it's being uploaded and pipe it on to the final destination (example: S3).

For the passthrough option, I have an example gist that will work with ExpressJS that will show you how to use it (it's a bit more complicated but has some benefits): https://gist.github.com/kylefarris/f6867b611fc5a3139a361afeb22a51b7

As for the first option, if you're using Express, you'll just need to use something like connect-multiparty (or any other multipart form-data parser) to get the uploaded files and put them in either a permanent or temporary location (I'd recommend the latter until after scanning).

Here's an extremely abridged/contrived example that might point you in the right direction:

const express = require('express');
const app = express();
const http = require('http').Server(app);
const bodyParser = require('body-parser');
const os = require('os');
const mkdirp = require('mkdirp');
const multipart = require('connect-multiparty');
const NodeClam = require('clamscan');

const clam_config = {
    debug_mode: false,
    remove_infected: true,
    scan_recursively: false,
    clamdscan: {
        socket: '/var/run/clamd.scan/clamd.sock', // Change to whatever works for you...
        timeout: 300000, // 5 minutes
        local_fallback: true,
    },
    preference: 'clamdscan'
};

const upload_dir = os.tmpdir() + '/cms_uploads';

// Middleware for checking for file uploads
const multiparty = (req, res, next) => {
    // Create upload directory if it doesn't already exist
    mkdirp(upload_dir, err => {
        if (err) {
            console.error(err);
            return res.status(500).send({error: "Sorry, but your file could not be uploaded at this time."});
        }
        multipart({
            uploadDir: upload_dir,
            maxFilesSize: 2000 * 1024 * 1024, // Change to whatever makes sense for you
            autoFiles: true
        })(req, res, next);
    });
};

app.post('/upload', multiparty, async (req, res) => {
    if (!req.files || !req.files.file || !Object.keys(req.files.file).length)
        return res.status(400).send({error: "No files found!"});

    // Get a reference to the uploaded file
    const tmp_file = req.files.file;

    // Try and scan the file, fail if something bad happens or if the file is infected.
    try {
        // Initialize a clamscan instance
        const clamscan = await new NodeClam().init(clam_config);
        // Scan the file
        const is_infected = await clamscan.is_infected(tmp_file.path);

        // Yep, file is infected...
        if (is_infected) {
            const error = "Sorry, the file you tried to upload was rejected because it is infected with a virus or malware. We recommend performing a virus scan on your computer immediately.";
            return res.status(400).send({error});
        }
    } catch (err) {
        // Something unexpected happened (ex. bad clamscan config)
        console.error(err);
        const error = "There was an error processing the validity of one of your files. Please try again.";
        return res.status(400).send({error});
    }

    // Now you can do what you want with the file (ex. move it to a permanent location, send to S3, email it, etc...)
});

// Start the webserver
http.listen(3000);
NikhilNanjappa-zz commented 5 years ago

Awesome! Thanks a lot @kylefarris for the detailed options & solutions. It was a great help! 👍