Skip to content

v1.0.0

Compare
Choose a tag to compare
@kylefarris kylefarris released this 02 May 19:38
· 207 commits to master since this release

1.0.0

This is essentially complete re-write of the clamscan module that supports a full socket pipeline allowing one to use this module as a middleman to scan the files on its way to its final destination.

A good example would be that a user uploads a file from a form on a website. As the file is being uploaded to the server, it can be scanned and piped on to AWS S3... never really touching the disk on the receiving server. If at any point the module detects a virus, the upload will stop.

This release also introduces a new initialization API, Promise support, scanning of streams and buffers, modern code, no third-party dependencies, and a host of other benefits, features, and bug fixes detailed below.

This is a huge major release in which this module was essentially completely re-written. This version introduces some breaking changes and major new features. Please read the release notes below carefully.

  • Now requires at least Node v10.0.0

  • Code re-written in ES2018 code

  • Now supports a hybrid Promise/Callback API (supports async/await)

  • Now properly supports TCP/UNIX Domain socket communication to local or remote clamav services (with optional fallback to local binary via child process).

  • Added new scan_stream method which allows you to pass an input stream.

  • Added new get_version method which allows you to check the version of ClamAV that you'll be communicating with.

  • Added new passthrough method which allows you to pipe a stream "through" the clamscan module and on to another destination (ex. S3).

  • Added new alias scan_file that points to is_infected.

  • In order to provide the name of any viruses found, a new standard viruses array is now be provided to the callback for:

    • is_infected & scan_file methods (callback format: (err, file, is_infected, viruses) => { ... }).
    • scan_files method (callback format: (err, good_files, bad_files, error_files, viruses) => { ... }).
    • scan_dir method (callback format: (err, good_files, bad_files, viruses) => { ... }).
  • In all cases, the viruses parameter will be an empty array on error or when no viruses are found.

  • scan_files now has another additional parameter in its callback:

    • error_files: An object keyed by the filenames that presented errors while scanning. The value of those keys will be the error message for that file.
  • Introduces new API to instantiate the module (NOTE: The old way will no longer work! See below for more info).

API Changes with 1.0.0:

For some full-fledged examples of how the new API works, checkout the /examples directory in the module root directory.

Module Initialization

Pre-1.0.0
const clamscan = require('clamscan')(options);
1.0.0

NOTE: Due to the new asynchronous nature of the checks that are performed upon initialization of the module, the initialization method now returns a Promise instead of the actual instantiated object. Resolving the Promise with then will return the object like before.

const ClamScan = require('clamscan');
const scanner = new NodeClam().init(options);

Making Method Calls

Pre-1.0.0
scanner.is_infected('/path/to/file.txt', (err, file, is_infected) => {
    // Do stuff
});
1.0.0
scanner.then(clamscan => {
    clamscan.is_infected('/path/to/file.txt', (err, file, is_infected, viruses) => {
        // Do stuff
    });
});

If you prefer the async/await style of coding:

;(async () => {
    const scanner = await new ClamScan().init(options);
    scanner.is_infected('/path/to/file.txt', (err, file, is_infected, viruses) => {
        // Do stuff
    });
})();

New Way to Get Results

Pre-1.0.0

The only way to get results/errors in pre-1.0.0 was through callbacks.

const clamscan = require('clamscan')(options);
clamscan.scan_dir('/path/to/directory', (err, good_files, bad_files) => {
    // Do stuff inside callback
});
1.0.0

In version 1.0.0 and beyond, you will now be able to use Promises as well (and, of course, async/await).

Promises
const clamscan = new ClamScan().init(options);
clamscan.then(scanner =>
    scanner.scan_dir('/path/to/directory').then(result => {
        const {good_files, bad_files} = result;
        // Do stuff
    }).catch(err => {
        // Handle scan error
    });
}).catch(err => {
    // Handle initialization error
});
Async/Await
;(async () => {
    try {
        const scanner = await new ClamScan().init(options);
        const {good_files, bad_files} = await scanner.scan_dir('/path/to/directory');
        // Do stuff
    } catch (err) {
        // Handle any error
    }
})();

New Methods

scan_stream

The scan_stream method allows you supply a readable stream to have it scanned. Theoretically any stream can be scanned this way. Like all methods, it supports callback and Promise response styles (full documentation is in README).

Basic Promise (async/await) Example:
;(async () => {
    try {
        const scanner = await new ClamScan().init(options);
        const stream = new Readable();
        rs.push('foooooo');
        rs.push('barrrrr');
        rs.push(null);

        const {is_infected, viruses} = await scanner.scan_stream(stream);

        // Do stuff
    } catch (err) {
        // Handle any error
    }
})();
Basic Callback Example:
;(async () => {
    try {
        const scanner = await new ClamScan().init(options);
        const stream = new Readable();
        rs.push('foooooo');
        rs.push('barrrrr');
        rs.push(null);

        scanner.scan_stream(stream, (err, results)  => {
            if (err) {
                // Handle error
            } else {
                const {is_infected, viruses} = results;
                // Do stuff
            }
        });

        // Do stuff
    } catch (err) {
        // Handle any error
    }
})();
passthrough

The passthrough method allows you supply a readable stream that will be "passed-through" the clamscan module and onto another destination. In reality, the passthrough method works more like a fork stream whereby the input stream is simultaneously streamed to ClamAV and whatever is the next destination. Events are created when ClamAV is done and/or when viruses are detected so that you can decide what to do with the data on the next destination (delete if virus detected, for instance). Data is only passed through to the next generation if the data has been successfully received by ClamAV. If anything halts the data going to ClamAV (including issues caused by ClamAV), the entire pipeline is halted and events are fired.

Normally, a file is uploaded and then scanned. This method should theoretically speed up user uploads intended to be scanned by up to 2x because the files are simultaneously scanned and written to disk. Your mileage my vary.

This method is different than all the others in that it returns a PassthroughStream object and does not support a Promise or Callback API. This makes sense once you see the example below (full documentation is in README).

Basic Example:
;(async () => {
    try {
        const scanner = await new ClamScan().init(options);
        const request = require('request');
        const input = request.get(some_url);
        const output = fs.createWriteStream(some_local_file);
        const av = scanner.passthrough();

        // Send output of RequestJS stream to ClamAV.
        // Send output of RequestJS to `some_local_file` if ClamAV receives data successfully
        input.pipe(av).pipe(output);

        // What happens when scan is completed
        av.on('scan-complete', result => {
            check(done, () => {
                const {is_infected, viruses} = result;

                // Do stuff if you want
            });
        });

        // What happens when data has been fully written to `output`
        output.on('finish', () => {
            // Do stuff if you want
        });
    } catch (err) {
        // Handle any error
    }
})();