Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

clamscan

Package Overview
Dependencies
Maintainers
1
Versions
62
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

clamscan - npm Package Compare versions

Comparing version 0.6.4 to 0.7.0

Makefile

9

HISTORY.md

@@ -53,1 +53,10 @@ # Changes

* Fixed error messages
### 0.7.0 (2015-06-01)
* Fixed a bug caused by not passing a `file_cb` paramter to the `scan_file` method. Thanks nicolaspeixoto!
* Added tests
* Fixed poor validation of method parameters
* Changed API of `scan_dir` such that the paramaters passed to the `end_cb` for certain situations. See documentation for details.
* Changed `err` paramter in all callbacks from a simple string to a proper javascript `Error` object.
* Added documentation for how to use a file_list file for scanning.

929

index.js

@@ -11,361 +11,575 @@ /*!

var exec = require('child_process').exec;
var execSync = require('child_process').execSync;
var spawn = require('child_process').spawn;
var os = require('os');
// ****************************************************************************
// NodeClam class definition
// -----
// @param Object options Key => Value pairs to override default settings
// ****************************************************************************
function NodeClam(options) {
options = options || {};
this.default_scanner = 'clamdscan';
// Configuration Settings
this.defaults = Object.freeze({
remove_infected: false,
quarantine_infected: false,
scan_log: null,
debug_mode: false,
file_list: null,
scan_recursively: true,
clamscan: {
path: '/usr/bin/clamscan',
scan_archives: true,
db: null,
active: true
},
clamdscan: {
path: '/usr/bin/clamdscan',
config_file: '/etc/clamd.conf',
multiscan: true,
reload_db: false,
active: true
},
preference: this.default_scanner
});
this.settings = __.extend({},this.defaults);
// Override defaults with user preferences
if (options.hasOwnProperty('clamscan') && Object.keys(options.clamscan).length > 0) {
this.settings.clamscan = __.extend({},this.settings.clamscan, options.clamscan);
delete options.clamscan;
}
if (options.hasOwnProperty('clamdscan') && Object.keys(options.clamdscan).length > 0) {
this.settings.clamdscan = __.extend({},this.settings.clamdscan, options.clamdscan);
delete options.clamdscan;
}
this.settings = __.extend({},this.settings,options);
// Backwards compatibilty section
if (this.settings.quarantine_path && !__.isEmpty(this.settings.quarantine_path)) {
this.settings.quarantine_infected = this.settings.quarantine_path;
}
// Determine whether to use clamdscan or clamscan
this.scanner = this.default_scanner;
if (typeof this.settings.preference !== 'string' || ['clamscan','clamdscan'].indexOf(this.settings.preference) === -1) {
throw new Error("Invalid virus scanner preference defined!");
}
if (this.settings.preference === 'clamscan' && this.settings.clamscan.active === true) {
this.scanner = 'clamscan';
}
// Check to make sure preferred scanner exists and actually is a clamscan binary
if (!this.is_clamav_binary_sync(this.scanner)) {
// Fall back to other option:
if (this.scanner == 'clamdscan' && this.settings.clamscan.active === true && this.is_clamav_binary_sync('clamscan')) {
this.scanner == 'clamscan';
} else if (this.scanner == 'clamscan' && this.settings.clamdscan.active === true && this.is_clamav_binary_sync('clamdscan')) {
this.scanner == 'clamdscan';
} else {
throw new Error("No valid & active virus scanning binaries are active and available!");
}
}
// Make sure quarantine infected path exists at specified location
if (!__.isEmpty(this.settings.quarantine_infected) && !fs.existsSync(this.settings.quarantine_infected)) {
var err_msg = "Quarantine infected path (" + this.settings.quarantine_infected + ") is invalid.";
this.settings.quarantine_infected = false;
throw new Error(err_msg);
if (this.settings.debug_mode)
console.log("node-clam: " + err_msg);
}
// Make sure scan_log exists at specified location
if (!__.isEmpty(this.settings.scan_log) && !fs.existsSync(this.settings.scan_log)) {
var err_msg = "node-clam: Scan Log path (" + this.settings.scan_log + ") is invalid.";
this.settings.scan_log = null;
if (this.settings.debug_mode)
console.log(err_msg);
}
// If using clamscan, make sure definition db exists at specified location
if (this.scanner === 'clamscan') {
if (!__.isEmpty(this.settings.clamscan.db) && !fs.existsSync(this.settings.db)) {
var err_msg = "node-clam: Definitions DB path (" + this.db + ") is invalid.";
this.db = null;
if(this.settings.debug_mode)
console.log(err_msg);
}
}
// Build clam flags
this.clam_flags = build_clam_flags(this.scanner, this.settings);
}
// ****************************************************************************
// Return a new NodeClam object.
// Checks to see if a particular path contains a clamav binary
// -----
// @param Object options Supplied to the NodeClam object for configuration
// @return Function / Class
// @api Public
// NOTE: Not currently being used (maybe for future implementations)
// SEE: in_clamav_binary_sync()
// -----
// @param String scanner Scanner (clamscan or clamdscan) to check
// @param Function cb Callback function to call after check
// @return VOID
// ****************************************************************************
module.exports = function(options){
// ****************************************************************************
// NodeClam class definition
// -----
// @param Object options Key => Value pairs to override default settings
// ****************************************************************************
function NodeClam(options) {
this.default_scanner = 'clamdscan';
// Configuration Settings
this.settings = {
remove_infected: false,
quarantine_infected: false,
scan_log: null,
debug_mode: false,
file_list: null,
scan_recursively: true,
clamscan: {
path: '/usr/bin/clamscan',
scan_archives: true,
db: null,
active: true
},
clamdscan: {
path: '/usr/bin/clamdscan',
config_file: '/etc/clamd.conf',
multiscan: true,
reload_db: false,
active: true
},
preference: this.default_scanner
};
NodeClam.prototype.is_clamav_binary = function(scanner, cb) {
var path = this.settings[scanner].path || null;
if (!path) {
if (this.settings.debug_mode) {
console.log("node-clam: Could not determine path for clamav binary.");
}
return cb(false);
}
var version_cmds = {
clamdscan: path + ' -c ' + this.settings.clamdscan.config_file + ' --version',
clamscan: path + ' --version'
};
fs.exists(path, function(exists) {
if (exists === false) {
if (this.settings.debug_mode) {
console.log("node-clam: Could not verify the " + scanner + " binary.");
}
return cb(false);
}
exec(version_cmds[scanner], function(err, stdout, stderr) {
if (stdout.toString().match(/ClamAV/) === null) {
if (this.settings.debug_mode) {
console.log("node-clam: Could not verify the " + scanner + " binary.");
}
return cb(false);
}
return cb(true);
})
});
}
// Override defaults with user preferences
this.settings = __.extend(this.settings,options);
// Backwards compatibilty
if (this.settings.quarantine_path && !__.isEmpty(this.settings.quarantine_path)) {
this.settings.quarantine_infected = this.settings.quarantine_path;
}
// Determine whether to use clamdscan or clamscan
this.scanner = this.default_scanner;
if (this.settings.preference == 'clamscan' && this.settings.clamscan.active === true) {
this.scanner = 'clamscan';
}
// Check to make sure preferred scanner exists
if (!fs.existsSync(this.settings[this.scanner].path)) {
// Fall back to other option:
if (this.scanner == 'clamdscan' && this.settings.clamscan.active === true) {
this.scanner == 'clamscan';
} else if (this.scanner == 'clamscan' && this.settings.clamdscan.active === true) {
this.scanner == 'clamdscan';
} else {
throw new Error("No valid virus scanning binaries are active and available!");
}
// Neither scanners are available!
if (!fs.existsSync(this.settings[this.scanner].path)) {
throw new Error("No valid virus scanning binaries have been found in the paths provided!");
}
}
// Make sure quarantine path exists at specified location
if (!__.isEmpty(this.settings.quarantine_infected) && !fs.existsSync(this.settings.quarantine_infected)) {
var err_msg = "Quarantine path (" + this.quarantine_infected + ") is invalid.";
this.quarantine_infected = false;
throw new Error(err_msg);
if (this.settings.debug_mode)
console.log("node-clam: " + err_msg);
}
// Make sure scan_log exists at specified location
if (!__.isEmpty(this.settings.scan_log) && !fs.existsSync(this.settings.scan_log)) {
var err_msg = "node-clam: Scan Log path (" + this.scan_log + ") is invalid.";
this.scan_log = null;
if (this.settings.debug_mode)
console.log(err_msg);
}
// If using clamscan, make sure definition db exists at specified location
if (this.scanner == 'clamscan') {
if (!__.isEmpty(this.settings.clamscan.db) && !fs.existsSync(this.settings.db)) {
var err_msg = "node-clam: Definitions DB path (" + this.db + ") is invalid.";
this.db = null;
if(this.settings.debug_mode)
console.log(err_msg);
}
}
// Build clam flags
this.clam_flags = build_clam_flags(this.scanner, this.settings);
}
// ****************************************************************************
// Checks if a particular file is infected.
// -----
// @param String file Path to the file to check
// @param Function callback What to do after the scan
// ****************************************************************************
NodeClam.prototype.is_infected = function(file, callback) {
var self = this;
if(this.settings.debug_mode)
console.log("node-clam: Scanning " + file);
// Build the actual command to run
var command = this.settings[this.scanner].path + this.clam_flags + file;
if(this.settings.debug_mode === true)
console.log('node-clam: Configured clam command: ' + command);
// Execute the clam binary with the proper flags
exec(command, function(err, stdout, stderr) {
if (err || stderr) {
if (err) {
if(err.hasOwnProperty('code') && err.code === 1) {
callback(null, file, true);
} else {
if(self.settings.debug_mode)
console.log("node-clam: " + err);
callback(err, file, null);
}
} else {
console.error("node-clam: " + stderr);
callback(err, file, null);
}
} else {
var result = stdout.trim();
if(result.match(/OK$/)) {
if(self.settings.debug_mode)
console.log("node-clam: " + file + ' is OK!');
callback(null, file, false);
} else {
if(self.settings.debug_mode)
console.log("node-clam: " + file + ' is INFECTED!');
callback(null, file, true);
}
}
});
}
// ****************************************************************************
// Scans an array of files or paths. You must provide the full paths of the
// files and/or paths.
// -----
// @param Array files A list of files or paths (full paths) to be scanned.
// @param Function end_cb What to do after the scan
// @param Function file_cb What to do after each file has been scanned
// ****************************************************************************
NodeClam.prototype.scan_files = function(files, end_cb, file_cb) {
files = files || [];
end_cb = end_cb || null;
files_cb = file_cb || null;
var bad_files = [];
var good_files = [];
var completed_files = 0;
var self = this;
var file;
if (typeof files === 'string') {
files = [files];
}
var num_files = files.length;
if (this.settings.debug_mode === true) {
console.log("node-clam: Scanning a list of " + num_files + " passed files.");
}
// Slower but more verbose way...
if (typeof file_cb === 'function') {
(function scan_file() {
file = files.shift();
self.is_infected(file, function(err, file, infected) {
completed_files++;
if (self.settings.debug_mode)
console.log("node-clam: " + completed_files + "/" + num_files + " have been scanned!");
if(!infected) {
good_files.push(file);
} else if(infected || err) {
bad_files.push(file);
}
if(__.isFunction(file_cb)) file_cb(err, file, infected);
if(completed_files >= num_files) {
if(self.settings.debug_mode) {
console.log('node-clam: Scan Complete!');
console.log("node-clam: Bad Files: ");
console.dir(bad_files);
console.log("node-clam: Good Files: ");
console.dir(good_files);
}
if(__.isFunction(end_cb)) end_cb(null, good_files, bad_files);
}
// All files have not been scanned yet, scan next item.
else {
// Using setTimeout to avoid crazy stack trace madness.
setTimeout(scan_file, 0);
}
});
})();
}
// The MUCH quicker but less-verbose way
else {
var all_files = [];
if (this.scanner === 'clamdscan' && this.scan_recursively === false) {
for(var i in files) {
if (!fs.statSync(files[i]).isFile()) {
all_files = _.uniq(all_files.concat(fs.readdirSync(files[i])));
} else {
all_files.push(files[i]);
}
}
} else {
all_files = files;
}
// Make sure there are no dupes... just cause we can
all_files = __.uniq(all_files);
// List files by space and escape
var items = files.map(function(file) {
return file.replace(/ /g,'\\ ');
}).join(' ');
// Build the actual command to run
var command = this.settings[this.scanner].path + this.clam_flags + items;
if(this.settings.debug_mode === true)
console.log('node-clam: Configured clam command: ' + command);
// Execute the clam binary with the proper flags
exec(command, function(err, stdout, stderr) {
if (err || stderr) {
if(this.settings.debug_mode === true)
console.error(stderr);
return end_cb(err, path, null);
} else {
var result = stdout.trim();
if(result.match(/OK$/)) {
if(self.settings.debug_mode)
console.log(path + ' is OK!');
return end_cb(null, path, false);
} else {
if(self.settings.debug_mode)
console.log(path + ' is INFECTED!');
return end_cb(null, path, true);
}
}
});
}
}
// ****************************************************************************
// Scans an entire directory. Provides 3 params to end callback: Error, path
// scanned, and whether its infected or not. To scan multiple directories, pass
// them as an array to the scan_files method.
// -----
// NOTE: While possible, it is NOT advisable to use the file_cb parameter when
// using the clamscan binary. Doing so with clamdscan is okay, however. This
// method also allows for non-recursive scanning with the clamdscan binary.
// -----
// @param String path The directory to scan files of
// @param Function en_cb What to do when all files have been scanned
// @param Function file_cb What to do after each file has been scanned
// ****************************************************************************
NodeClam.prototype.scan_dir = function(path,end_cb,file_cb) {
var self = this;
path = path || '';
end_cb = end_cb || null;
files_cb = file_cb || null;
if (typeof path !== 'string' || path.length <= 0) {
return end_cb(new Error("Invalid path provided! Path must be a string!"));
}
// Trim trailing slash
path = path.replace(/\/$/, '');
if(this.settings.debug_mode)
console.log("node-clam: Scanning Directory: " + path);
// Get all files recursively
if (this.settings.scan_recursively && typeof file_cb == 'function') {
exec('find ' + path, function(err, stdout, stderr) {
if (err || stderr) {
if(this.settings.debug_mode === true)
console.error(stderr);
return end_cb(err, path, null);
} else {
var files = stdout.split("\n").map(function(path) { return path.replace(/ /g,'\\ '); });
self.scan_files(files, end_cb, file_cb);
}
});
}
// Clamdscan always does recursive, so, here's a way to avoid that if you want...
else if (this.settings.scan_recursively === false && this.scanner === 'clamdscan') {
fs.readdir(path, function(err, files) {
files.filter(function (file) {
return fs.statSync(file).isFile();
});
self.scan_files(files, end_file, file_cb);
});
}
// If you don't care about individual file progress (which is very slow for clamscan but fine for clamdscan...)
else if (this.settings.scan_recursively && typeof file_cb !== 'function') {
var command = this.settings[this.scanner].path + this.clam_flags + path;
if(this.settings.debug_mode === true)
console.log('node-clam: Configured clam command: ' + command);
// Execute the clam binary with the proper flags
exec(command, function(err, stdout, stderr) {
if (err || stderr) {
if(self.settings.debug_mode === true) {
console.log("An Error Occurred.");
console.error(stderr);
}
return end_cb(err, [], []);
} else {
var result = stdout.trim();
if(result.match(/OK$/)) {
if(self.settings.debug_mode)
console.log(path + ' is OK!');
return end_cb(null, [path], []);
} else {
if(self.settings.debug_mode)
console.log(path + ' is INFECTED!');
return end_cb(null, [], [path]);
}
}
});
}
}
return new NodeClam(options);
// ****************************************************************************
// Checks to see if a particular path contains a clamav binary
// -----
// @param String scanner Scanner (clamscan or clamdscan) to check
// @return Boolean TRUE: Is binary; FALSE: Not binary
// ****************************************************************************
NodeClam.prototype.is_clamav_binary_sync = function(scanner) {
var path = this.settings[scanner].path || null;
if (!path) {
if (this.settings.testing_mode) {
console.log("node-clam: Could not determine path for clamav binary.");
}
return false;
}
var version_cmds = {
clamdscan: path + ' -c ' + this.settings.clamdscan.config_file + ' --version',
clamscan: path + ' --version'
};
if (!fs.existsSync(path) || execSync(version_cmds[scanner]).toString().match(/ClamAV/) === null) {
if (this.settings.testing_mode) {
console.log("node-clam: Could not verify the " + scanner + " binary.");
}
return false;
}
return true;
}
// ****************************************************************************
// Checks if a particular file is infected.
// -----
// @param String file Path to the file to check
// @param Function callback (optional) What to do after the scan
// ****************************************************************************
NodeClam.prototype.is_infected = function(file, callback) {
// Verify second param, if supplied, is a function
if (callback && typeof callback !== 'function') {
throw new Error("Invalid callback provided. Second paramter, if provided, must be a function!");
}
// Verify string is passed to the file parameter
if (typeof file !== 'string' || file.trim() === '') {
var err = new Error("Invalid or empty file name provided.");
if (callback && typeof callback === 'function') {
return callback(err, '', null);
} else {
throw err;
}
}
var self = this;
if(this.settings.debug_mode)
console.log("node-clam: Scanning " + file);
// Build the actual command to run
var command = this.settings[this.scanner].path + this.clam_flags + file;
if(this.settings.debug_mode === true)
console.log('node-clam: Configured clam command: ' + command);
// Execute the clam binary with the proper flags
exec(command, function(err, stdout, stderr) {
if (err || stderr) {
if (err) {
if(err.hasOwnProperty('code') && err.code === 1) {
callback(null, file, true);
} else {
if(self.settings.debug_mode)
console.log("node-clam: " + err);
callback(new Error(err), file, null);
}
} else {
console.error("node-clam: " + stderr);
callback(err, file, null);
}
} else {
var result = stdout.trim();
if(self.settings.debug_mode) {
console.log('node-clam: file size: ' + fs.statSync(file).size);
console.log('node-clam: ' + result);
}
if(result.match(/OK$/)) {
if(self.settings.debug_mode)
console.log("node-clam: " + file + ' is OK!');
callback(null, file, false);
} else {
if(self.settings.debug_mode)
console.log("node-clam: " + file + ' is INFECTED!');
callback(null, file, true);
}
}
});
}
// ****************************************************************************
// Scans an array of files or paths. You must provide the full paths of the
// files and/or paths.
// -----
// @param Array files A list of files or paths (full paths) to be scanned.
// @param Function end_cb What to do after the scan
// @param Function file_cb What to do after each file has been scanned
// ****************************************************************************
NodeClam.prototype.scan_files = function(files, end_cb, file_cb) {
files = files || [];
end_cb = end_cb || null;
file_cb = file_cb || null;
var bad_files = [];
var good_files = [];
var completed_files = 0;
var self = this;
var file, file_list;
// Verify second param, if supplied, is a function
if (end_cb && typeof end_cb !== 'function') {
throw new Error("Invalid end-scan callback provided. Second paramter, if provided, must be a function!");
}
// Verify second param, if supplied, is a function
if (file_cb && typeof file_cb !== 'function') {
throw new Error("Invalid per-file callback provided. Third paramter, if provided, must be a function!");
}
// The function that parses the stdout from clamscan/clamdscan
var parse_stdout = function(err, stdout) {
stdout.trim()
.split(String.fromCharCode(10))
.forEach(function(result){
if (result.match(/^[\-]+$/) !== null) return;
//console.log("PATH: " + result)
var path = result.match(/^(.*): /);
if (path && path.length > 0) {
path = path[1];
} else {
path = '<Unknown File Path!>';
}
if (result.match(/OK$/)) {
if (self.settings.debug_mode === true){
console.log(path + ' is OK!');
}
good_files.push(path);
} else {
if (self.settings.debug_mode === true){
console.log(path + ' is INFECTED!');
}
bad_files.push(path);
}
});
if (err)
return end_cb(err, [], bad_files);
return end_cb(null, good_files, bad_files);
};
// The function that actually scans the files
var do_scan = function(files) {
var num_files = files.length;
if (self.settings.debug_mode === true) {
console.log("node-clam: Scanning a list of " + num_files + " passed files.");
}
// Slower but more verbose way...
if (typeof file_cb === 'function') {
(function scan_file() {
file = files.shift();
self.is_infected(file, function(err, file, infected) {
completed_files++;
if (self.settings.debug_mode)
console.log("node-clam: " + completed_files + "/" + num_files + " have been scanned!");
if(!infected) {
good_files.push(file);
} else if(infected || err) {
bad_files.push(file);
}
if(__.isFunction(file_cb)) file_cb(err, file, infected);
if(completed_files >= num_files) {
if(self.settings.debug_mode) {
console.log('node-clam: Scan Complete!');
console.log("node-clam: Bad Files: ");
console.dir(bad_files);
console.log("node-clam: Good Files: ");
console.dir(good_files);
}
if(__.isFunction(end_cb)) end_cb(null, good_files, bad_files);
}
// All files have not been scanned yet, scan next item.
else {
// Using setTimeout to avoid crazy stack trace madness.
setTimeout(scan_file, 0);
}
});
})();
}
// The MUCH quicker but less-verbose way
else {
var all_files = [];
var finish_scan = function() {
// Make sure there are no dupes and no falsy values... just cause we can
all_files = __.uniq(__.compact(all_files));
// If file list is empty, return error
if (all_files.length <= 0)
return end_cb(new Error("No valid files provided to scan!"), [], []);
// List files by space and escape
var items = files.map(function(file) {
return file.replace(/ /g,'\\ ');
}).join(' ');
// Build the actual command to run
var command = self.settings[self.scanner].path + self.clam_flags + items;
if(self.settings.debug_mode === true)
console.log('node-clam: Configured clam command: ' + command);
// Execute the clam binary with the proper flags
exec(command, function(err, stdout, stderr) {
if(self.settings.debug_mode === true) {
console.log('node-clam: stdout:', stdout);
}
if (err && stderr) {
if(self.settings.debug_mode === true){
console.log('node-clam: An error occurred.');
console.error(err);
console.log('node-clam: ' + stderr);
}
if (stderr.length > 0) {
bad_files = stderr.split(os.EOL).map(function(err_line) {
var match = err_line.match(/^ERROR: Can't access file (.*)+$/); //'// fix for some bad syntax highlighters
if (match !== null && match.length > 1 && typeof match[1] === 'string') {
return match[1];
}
return '';
});
bad_files = __.compact(bad_files);
}
}
return parse_stdout(err, stdout);
});
};
if (self.scanner === 'clamdscan' && self.scan_recursively === false) {
(function get_dir_files() {
if (files.length > 0) {
var file = files.pop();
fs.stat(file, function(err, file) {
if (!file.isFile()) {
fs.readdir(file, function(err, dir_file) {
all_files = __.uniq(all_files.concat(dir_file));
});
} else {
all_files.push(file);
}
get_dir_files();
});
} else {
finish_scan();
}
})();
} else {
all_files = files;
finish_scan();
}
}
};
// If string is provided in files param, forgive them... create an array
if (typeof files === 'string' && files.trim().length > 0) {
files = files.trim().split(',').map(function(v) { return v.trim(); });
}
// Do some parameter validation
if (!__.isArray(files) || files.length <= 0) {
if (__.isEmpty(this.settings.file_list)) {
var err = new Error("No files provided to scan and no file list provided!");
return end_cb(err, [], []);
}
fs.exists(this.settings.file_list, function(exists) {
if (exists === false) {
var err = new Error("No files provided and file list provided ("+this.settings.file_list+") could not be found!");
return end_cb(err, [], []);
}
fs.readFile(self.settings.file_list, function(err, data) {
if (err) {
return end_cb(err, [], []);
}
data = data.toString().split(os.EOL);
return do_scan(data);
});
});
} else {
return do_scan(files);
}
}
// ****************************************************************************
// Scans an entire directory. Provides 3 params to end callback: Error, path
// scanned, and whether its infected or not. To scan multiple directories, pass
// them as an array to the scan_files method.
// -----
// NOTE: While possible, it is NOT advisable to use the file_cb parameter when
// using the clamscan binary. Doing so with clamdscan is okay, however. This
// method also allows for non-recursive scanning with the clamdscan binary.
// -----
// @param String path The directory to scan files of
// @param Function end_cb What to do when all files have been scanned
// @param Function file_cb What to do after each file has been scanned
// ****************************************************************************
NodeClam.prototype.scan_dir = function(path, end_cb, file_cb) {
var self = this;
path = path || '';
end_cb = end_cb || null;
file_cb = file_cb || null;
// Verify path provided is a string
if (typeof path !== 'string' || path.length <= 0) {
return end_cb(new Error("Invalid path provided! Path must be a string!"));
}
// Verify second param, if supplied, is a function
if (end_cb && typeof end_cb !== 'function') {
return end_cb(new Error("Invalid end-scan callback provided. Second paramter, if provided, must be a function!"));
}
// Trim trailing slash
path = path.replace(/\/$/, '');
if(this.settings.debug_mode)
console.log("node-clam: Scanning Directory: " + path);
// Get all files recursively
if (this.settings.scan_recursively && typeof file_cb === 'function') {
exec('find ' + path, function(err, stdout, stderr) {
if (err || stderr) {
if(this.settings.debug_mode === true)
console.error(stderr);
return end_cb(err, path, null);
} else {
var files = stdout.split("\n").map(function(path) { return path.replace(/ /g,'\\ '); });
self.scan_files(files, end_cb, file_cb);
}
});
}
// Clamdscan always does recursive, so, here's a way to avoid that if you want...
else if (this.settings.scan_recursively === false && this.scanner === 'clamdscan') {
fs.readdir(path, function(err, files) {
var good_files = [];
(function get_file_stats() {
if (files.length > 0) {
var file = files.pop();
fs.stat(file, function(err, info) {
if (info.isFile()) good_files.push(file);
get_file_stats();
});
} else {
self.scan_files(good_files, end_file, file_cb);
}
})();
});
}
// If you don't care about individual file progress (which is very slow for clamscan but fine for clamdscan...)
else if (this.settings.scan_recursively && typeof file_cb !== 'function') {
var command = this.settings[this.scanner].path + this.clam_flags + path;
if(this.settings.debug_mode === true)
console.log('node-clam: Configured clam command: ' + command);
// Execute the clam binary with the proper flags
exec(command, function(err, stdout, stderr) {
if (err || stderr) {
if (err) {
if(err.hasOwnProperty('code') && err.code === 1) {
end_cb(null, [], [path]);
} else {
if(self.settings.debug_mode)
console.log("node-clam: " + err);
end_cb(new Error(err), [], [path]);
}
} else {
console.error("node-clam: " + stderr);
end_cb(err, [], [path]);
}
} else {
var result = stdout.trim();
if(result.match(/OK$/)) {
if(self.settings.debug_mode)
console.log(path + ' is OK!');
return end_cb(null, [path], []);
} else {
if(self.settings.debug_mode)
console.log(path + ' is INFECTED!');
return end_cb(null, [], [path]);
}
}
});
}
}
module.exports = function(options) {
return new NodeClam(options);
};

@@ -387,4 +601,9 @@

flags_array.push('--stdout');
// Remove infected files
if (settings.remove_infected === true) flags_array.push('--remove=yes');
if (settings.remove_infected === true) {
flags_array.push('--remove=yes');
} else {
flags_array.push('--remove=no');
}
// Database file

@@ -408,2 +627,4 @@ if (!__.isEmpty(settings.clamscan.db)) flags_array.push('--database=' + settings.clamscan.db);

else if (scanner == 'clamdscan') {
flags_array.push('--fdpass');
// Remove infected files

@@ -410,0 +631,0 @@ if (settings.remove_infected === true) flags_array.push('--remove');

{
"name": "clamscan",
"version": "0.6.4",
"version": "0.7.0",
"author": "Kyle Farris <kfarris@chomponllc.com> (http://chomponllc.com)",

@@ -8,6 +8,7 @@ "description": "Use Node JS to scan files on your server with ClamAV's clamscan binary or clamdscan daemon. This is especially useful for scanning uploaded files provided by un-trusted sources.",

"contributors": [
"dietervds"
"dietervds",
"nicolaspeixoto"
],
"scripts": {
"test": "echo 'Error: no test specified' && exit 1"
"test": "make test"
},

@@ -38,3 +39,7 @@ "repository": {

},
"devDependencies": {}
"devDependencies": {
"chai": "^2.3.0",
"mocha": "^2.2.5",
"request": "^2.57.0"
}
}

@@ -51,3 +51,3 @@ ## NodeJS Clamscan Virus Scanning Utility

debug_mode: false // Whether or not to log info/debug/error msgs to the console
file_list: null, // path to file containing list of files to scan
file_list: null, // path to file containing list of files to scan (for scan_files method)
scan_recursively: true, // If true, deep scan folders recursively

@@ -62,3 +62,3 @@ clamscan: {

path: '/usr/bin/clamdscan', // Path to the clamdscan binary on your server
config_file: null, // Specify config file if it's in an unusual place
config_file: '/etc/clamd.conf', // Specify config file if it's in an unusual place
multiscan: true, // Scan using all available cores! Yay!

@@ -108,6 +108,6 @@ reload_db: false, // If true, will re-load the DB on every call (slow)

* `file_path` (string) Represents a path to the file to be scanned.
* `callback` (function) Will be called when the scan is complete. It takes 3 parameters:
* `err` (string or null) A standard error message string (null if no error)
* `file` (string) The original `file_path` passed into the `is_infected` method.
* `is_infected` (boolean) __True__: File is infected; __False__: File is clean.
* `callback` (function) (optional) Will be called when the scan is complete. It takes 3 parameters:
* `err` (object or null) A standard javascript Error object (null if no error)
* `file` (string) The original `file_path` passed into the `is_infected` method.
* `is_infected` (boolean) __True__: File is infected; __False__: File is clean.

@@ -135,6 +135,13 @@

__TLDR:__ For maximum speed, don't supply a `file_callback`.
__TL;DR:__ For maximum speed, don't supply a `file_callback`.
If you choose to supply a `file_callback`, the scan will run a little bit slower (depending on number of files to be scanned) for `clamdscan`. If you are using `clamscan`, while it will work, I'd highly advise you to NOT pass a `file_callback`... it will run incredibly slow.
#### NOTE:
The `good_files` and `bad_files` parameters of the `end_callback` callback in this method will only contain the directories that were scanned in __all__ __but__ the following scenarios:
* A `file_callback` callback is provided, and `scan_recursively` is set to _true_.
* The scanner is set to `clamdscan` and `scan_recursively` is set to _false_.
#### Parameters

@@ -144,9 +151,9 @@

* `end_callback` (function) Will be called when the entire directory has been completely scanned. This callback takes 3 parameters:
* `err`(string or null) A standard error message string (null if no error)
* `good_files` (array) List of the full paths to all files that are _clean_.
* `bad_files` (array) List of the full paths to all files that are _infected_.
* `err` (object) A standard javascript Error object (null if no error)
* `good_files` (array) List of the full paths to all files that are _clean_.
* `bad_files` (array) List of the full paths to all files that are _infected_.
* `file_callback` (function) Will be called after each file in the directory has been scanned. This is useful for keeping track of the progress of the scan. This callback takes 3 parameters:
* `err` (string or null) A standard error message string (null if no error)
* `file` (string) Path to the file that just got scanned.
* `is_infected` (boolean) __True__: File is infected; __False__: File is clean.
* `err` (object or null) A standard Javascript Error object (null if no error)
* `file` (string) Path to the file that just got scanned.
* `is_infected` (boolean) __True__: File is infected; __False__: File is clean.

@@ -176,9 +183,9 @@ #### Example

* `end_callback` (function) Will be called when the entire directory has been completely scanned. This callback takes 3 parameters:
* `err` A standard error message string (null if no error)
* `good_files` (array) List of the full paths to all files that are _clean_.
* `bad_files` (array) List of the full paths to all files that are _infected_.
* `err` (object) A standard javascript Error object (null if no error)
* `good_files` (array) List of the full paths to all files that are _clean_.
* `bad_files` (array) List of the full paths to all files that are _infected_.
* `file_callback` (function) Will be called after each file in the directory has been scanned. This is useful for keeping track of the progress of the scan. This callback takes 3 parameters:
* `err` (string or null) A standard error message string (null if no error)
* `file` (string) Path to the file that just got scanned.
* `is_infected` (boolean) __True__: File is infected; __False__: File is clean.
* `err` (object or null)A standard javascript Error object (null if no error)
* `file` (string) Path to the file that just got scanned.
* `is_infected` (boolean) __True__: File is infected; __False__: File is clean.

@@ -221,4 +228,46 @@ #### Example

#### Scanning files listed in file list
If this modules is configured with a valid path to a file containing a newline-delimited list of files, it will use the list in that file when scanning if the first paramter passed is falsy.
__Files List:__
```
/some/path/to/file.zip
/some/other/path/to/file.exe
/one/more/file/to/scan.rb
```
__Script:__
```javascript
var clam = require('clamscan')({
file_list: '/path/to/file_list.txt'
});
clam.scan_files(null, function(err, good_files, bad_files) {
// doo stuff...
});
```
#### Changing Configuration After Instantiation
You can set settings directly on an instance of this module using the following syntax:
```javascript
var clam = require('clamscan')({ /** Some configs here... */});
// will quarantine files
clam.settings.quarantine_infected = true;
clam.is_infected('/some/file.txt');
// will not quarantine files
clam.settings.quarantine_infected = false;
clam.is_infected('/some/file.txt');
```
Just keep in mind that some of the nice validation that happens on instantiation won't happen if it's done this way. Of course, you could also just create a new instance with different a different initial configuration.
## Contribute
Got a missing feature you'd like to use? Found a bug? Go ahead and fork this repo, build the feature and issue a pull request.

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc