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

clamscan

Package Overview
Dependencies
Maintainers
2
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 1.4.2 to 2.0.0

.eslintignore

81

.eslintrc.json
{
"parser": "babel-eslint",
"env": {
"browser": true,
"commonjs": true,
"es6": true,
"browser": false,
"node": true,
"mocha": true
"es6": true
},
"extends": "eslint:recommended",
"extends": ["airbnb-base", "plugin:prettier/recommended", "plugin:chai-friendly/recommended"],
"plugins": ["prettier", "chai-friendly", "jsdoc"],
"globals": {

@@ -15,23 +15,58 @@ "Atomics": "readonly",

"parserOptions": {
"ecmaVersion": 2018
"ecmaVersion": 2018,
"sourceType": "module"
},
"rules": {
"indent": [
"error",
4,
{ "SwitchCase" : 1 }
"no-console": "off",
"no-param-reassign": "off",
"prettier/prettier": "error",
"no-underscore-dangle": "off",
"class-methods-use-this": "off",
"require-jsdoc": "error",
"valid-jsdoc": "off",
"global-require": "warn",
"lines-between-class-members": "off",
"jsdoc/check-alignment": 1, // Recommended
"jsdoc/check-indentation": 1,
"jsdoc/check-param-names": 1, // Recommended
"jsdoc/check-syntax": 1,
"jsdoc/check-tag-names": [
"warn",
{
"definedTags": ["typicalname", "route", "authentication", "bodyparam", "routeparam"]
}
],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"single"
],
"semi": [
"error",
"always"
]
"jsdoc/check-types": 1, // Recommended
"jsdoc/implements-on-classes": 1, // Recommended
"jsdoc/match-description": 1,
"jsdoc/newline-after-description": 1, // Recommended
"jsdoc/no-undefined-types": [
"warn",
{
"definedTypes": ["DELETE", "POST", "PATCH", "PUT", "GET"]
}
], // Recommended
"jsdoc/require-description": 1,
"jsdoc/require-hyphen-before-param-description": 1,
"jsdoc/require-jsdoc": 1, // Recommended
"jsdoc/require-param": 1, // Recommended
"jsdoc/require-param-description": 1, // Recommended
"jsdoc/require-param-name": 1, // Recommended
"jsdoc/require-param-type": 1, // Recommended
"jsdoc/require-returns": 1, // Recommended
"jsdoc/require-returns-check": 1, // Recommended
"jsdoc/require-returns-description": 1, // Recommended
"jsdoc/require-returns-type": 1, // Recommended
"jsdoc/valid-types": 1 // Recommended
},
"settings": {
"jsdoc": {
"plugins": ["jsdoc-route-plugin"],
"structuredTags": {
"route": {
"type": ["DELETE", "POST", "PATCH", "PUT", "GET"]
}
}
}
}
}
}

@@ -1,14 +0,13 @@

const request = require('request');
// eslint-disable-next-line import/no-extraneous-dependencies
const axios = require('axios');
const fs = require('fs');
const fake_virus_url = 'https://secure.eicar.org/eicar_com.txt';
const normal_file_url = 'https://raw.githubusercontent.com/kylefarris/clamscan/sockets/README.md';
const temp_dir = __dirname;
const scan_file = `${temp_dir}/tmp_file.txt`;
//const test_file = normal_file_url;
const test_file = fake_virus_url;
const fakeVirusUrl = 'https://secure.eicar.org/eicar_com.txt';
const tempDir = __dirname;
const scanFile = `${tempDir}/tmp_file.txt`;
const config = {
remove_infected: true,
debug_mode: false,
scan_recursively: false,
removeInfected: true,
debugMode: false,
scanRecursively: false,
clamdscan: {

@@ -18,3 +17,3 @@ path: '/usr/bin/clamdscan',

},
preference: 'clamdscan'
preference: 'clamdscan',
};

@@ -25,38 +24,44 @@

;(async () => {
(async () => {
const clamscan = await new NodeClam().init(config);
let body;
// Request a test file from the internet...
request(test_file, async (error, response, body) => {
if (!error && response.statusCode == 200) {
// Write the file to the filesystem
fs.writeFileSync(scan_file, body);
try {
body = await axios.get(fakeVirusUrl);
} catch (err) {
if (err.response) console.err(`${err.response.status}: Request Failed. `, err.response.data);
else if (err.request) console.error('Error with Request: ', err.request);
else console.error('Error: ', err.message);
process.exit(1);
}
// Scan the file
try {
const {file, is_infected, viruses} = await clamscan.is_infected(scan_file);
// Write the file to the filesystem
fs.writeFileSync(scanFile, body);
// If `is_infected` is TRUE, file is a virus!
if (is_infected === true) {
console.log(`You've downloaded a virus (${viruses.join('')})! Don't worry, it's only a test one and is not malicious...`);
} else if (is_infected === null) {
console.log("Something didn't work right...");
} else if (is_infected === false) {
console.log(`The file (${file}) you downloaded was just fine... Carry on...`);
}
// Scan the file
try {
const { file, isInfected, viruses } = await clamscan.isInfected(scanFile);
// Remove the file (for good measure)
if (fs.existsSync(scan_file)) fs.unlinkSync(scan_file);
process.exit(0);
} catch (err) {
console.error("ERROR: " + err);
console.trace(err.stack);
process.exit(1);
}
} else {
console.log("Could not download test virus file!");
console.error(error);
process.exit(1);
// If `isInfected` is TRUE, file is a virus!
if (isInfected === true) {
console.log(
`You've downloaded a virus (${viruses.join(
''
)})! Don't worry, it's only a test one and is not malicious...`
);
} else if (isInfected === null) {
console.log("Something didn't work right...");
} else if (isInfected === false) {
console.log(`The file (${file}) you downloaded was just fine... Carry on...`);
}
});
// Remove the file (for good measure)
if (fs.existsSync(scanFile)) fs.unlinkSync(scanFile);
process.exit(0);
} catch (err) {
console.error(`ERROR: ${err}`);
console.trace(err.stack);
process.exit(1);
}
})();

@@ -1,16 +0,16 @@

const {PassThrough} = require('stream');
const request = require('request');
// eslint-disable-next-line import/no-extraneous-dependencies
const axios = require('axios');
const fs = require('fs');
const {promisify} = require('util');
const fs_unlink = promisify(fs.unlink);
const { promisify } = require('util');
const fake_virus_url = 'https://secure.eicar.org/eicar_com.txt';
const normal_file_url = 'https://raw.githubusercontent.com/kylefarris/clamscan/sockets/README.md';
const large_file_url = 'http://speedtest-ny.turnkeyinternet.net/100mb.bin';
const passthru_file = __dirname + '/output';
const fsUnlink = promisify(fs.unlink);
const test_url = normal_file_url;
// const test_url = fake_virus_url;
// const test_url = large_file_url;
// const fakeVirusUrl = 'https://secure.eicar.org/eicar_com.txt';
const normalFileUrl = 'https://raw.githubusercontent.com/kylefarris/clamscan/sockets/README.md';
// const largeFileUrl = 'http://speedtest-ny.turnkeyinternet.net/100mb.bin';
const passthruFile = `${__dirname}/output`;
const testUrl = normalFileUrl;
// const testUrl = fakeVirusUrl;
// const testUrl = largeFileUrl;

@@ -20,2 +20,19 @@ // Initialize the clamscan module

/**
* Removes whatever file was passed-through during the scan.
*/
async function removeFinalFile() {
try {
await fsUnlink(passthruFile);
console.log(`Output file: "${passthruFile}" was deleted.`);
process.exit(1);
} catch (err) {
console.error(err);
process.exit(1);
}
}
/**
* Actually run the example code.
*/
async function test() {

@@ -32,4 +49,4 @@ const clamscan = await new NodeClam().init({

const input = request.get(test_url);
const output = fs.createWriteStream(passthru_file);
const input = axios.get(testUrl);
const output = fs.createWriteStream(passthruFile);
const av = clamscan.passthrough();

@@ -39,33 +56,44 @@

av.on('error', error => {
if ('data' in error && error.data.is_infected) {
console.error("Dang, your stream contained a virus(es):", error.data.viruses);
av.on('error', (error) => {
if ('data' in error && error.data.isInfected) {
console.error('Dang, your stream contained a virus(es):', error.data.viruses);
} else {
console.error(error);
}
remove_final_file();
}).on('finish', () => {
console.log("All data has been sent to virus scanner");
}).on('end', () => {
console.log("All data has been scanned sent on to the destination!");
}).on('scan-complete', result => {
console.log("Scan Complete: Result: ", result);
if (result.is_infected === true) {
console.log(`You've downloaded a virus (${result.viruses.join(', ')})! Don't worry, it's only a test one and is not malicious...`);
} else if (result.is_infected === null) {
console.log(`There was an issue scanning the file you downloaded...`);
} else {
console.log(`The file (${test_url}) you downloaded was just fine... Carry on...`);
}
remove_final_file();
process.exit(0);
});
removeFinalFile();
})
.on('timeout', () => {
console.error('It looks like the scanning has timedout.');
process.exit(1);
})
.on('finish', () => {
console.log('All data has been sent to virus scanner');
})
.on('end', () => {
console.log('All data has been scanned sent on to the destination!');
})
.on('scan-complete', (result) => {
console.log('Scan Complete: Result: ', result);
if (result.isInfected === true) {
console.log(
`You've downloaded a virus (${result.viruses.join(
', '
)})! Don't worry, it's only a test one and is not malicious...`
);
} else if (result.isInfected === null) {
console.log(`There was an issue scanning the file you downloaded...`);
} else {
console.log(`The file (${testUrl}) you downloaded was just fine... Carry on...`);
}
removeFinalFile();
process.exit(0);
});
output.on('finish', () => {
console.log("Data has been fully written to the output...");
console.log('Data has been fully written to the output...');
output.destroy();
});
output.on('error', error => {
console.log("Final Output Fail: ", error);
output.on('error', (error) => {
console.log('Final Output Fail: ', error);
process.exit(1);

@@ -75,13 +103,2 @@ });

async function remove_final_file() {
try {
await fs_unlink(passthru_file);
console.log(`Output file: "${passthru_file}" was deleted.`);
process.exit(1);
} catch (err) {
throw err;
process.exit(1);
}
}
test();

@@ -0,1 +1,4 @@

/* eslint-disable consistent-return */
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable import/no-unresolved */
const EventEmitter = require('events');

@@ -7,23 +10,24 @@ const filesize = require('filesize');

const AWS = require('aws-sdk');
AWS.config.region = '<your region here>';
const ClamScan = new NodeClam().init({
remove_infected: true,
scan_recursively: false,
removeInfected: true,
scanRecursively: false,
clamdscan: {
socket: '/var/run/clamd.scan/clamd.sock',
timeout: 300000,
local_fallback: true,
localFallback: true,
},
preference: 'clamdscan'
preference: 'clamdscan',
});
const s3 = new AWS.S3({
const s3Config = {
params: {
Bucket: '<your bucket name here>',
},
});
const s3_stream = require('s3-upload-stream')(s3);
};
const s3 = new AWS.S3(s3Config);
const s3Stream = require('s3-upload-stream')(s3);
/**

@@ -33,29 +37,28 @@ * Example method for taking an end-user's upload stream and piping it though

* you're using ExpressJS as your server.
* -----
*
* NOTE: This method can only handle one file in a request payload.
* -----
*
* @param {object} req - An Express Request object
* @param {object} res - An Express Response object
* @returns {Promise<object>}
* @example
*
* @param {object} [opts] - Used to override defaults
* @returns {Promise<object>} Object like: { s3Details, fileInfo, fields }
*/
async function pipe2s3(req, res, opts={}) {
let debug_mode = false;
async function pipe2s3(req, res, opts = {}) {
let debugMode = false;
const pipeline = new EventEmitter();
return new Promise((resolve, reject) => {
let s3_details = null;
let scan_result = null;
let file_info = {};
let fields = {};
let num_files = 0;
let s3_upload_stream;
let s3Details = null;
let scanResult = null;
const fileInfo = {};
const fields = {};
let numFiles = 0;
let s3UploadStream;
const defaults = {
s3_path: '', // Needs trailing slash if provided...
s3_id: null,
s3Id: null,
s3_acl: 'private',
s3_metadata: {},
max_file_size: 10 * Math.pow(1024, 2), // 20 MB
max_file_size: 10 * 1024 ** 2, // 20 MB
max_files: null, // FALSEY === No max number of files

@@ -67,11 +70,14 @@ allowed_mimetypes: [], // FALSEY === Accept anything

const options = { ...defaults, ...opts };
if (!options.s3_id) options.s3_id = `${options.s3_path}${uuidv4()}`;
if (!options.s3Id) options.s3Id = `${options.s3_path}${uuidv4()}`;
// Check if debug mode is turned on
if ('debug' in options && options.debug) debug_mode = true;
if ('debug' in options && options.debug) debugMode = true;
// Instantiate BusBoy for this request
const busboy = new BusBoy({headers: req.headers, limits: { fileSize: options.max_file_size, files: options.max_files }});
const busboy = new BusBoy({
headers: req.headers,
limits: { fileSize: options.max_file_size, files: options.max_files },
});
const log_error = (err) => {
const logError = (err) => {
const code = uuidv4();

@@ -82,8 +88,11 @@ console.error(`Error Code: ${code}: ${err}`, err);

// Function to remove file from S3
const remove_s3_obj = async () => {
const removeS3Object = async () => {
try {
const result = await s3.deleteObject({Key: options.s3_id}).promise();
console.log(`S3 Object: "${options.s3_id}" was deleted due to a ClamAV error or virus detection.`, result);
const result = await s3.deleteObject({ Key: options.s3Id }).promise();
console.log(
`S3 Object: "${options.s3Id}" was deleted due to a ClamAV error or virus detection.`,
result
);
} catch (err) {
log_error(err);
logError(err);
}

@@ -93,29 +102,37 @@ };

// When file has been uploaded to S3 and has been scanned, this function is called
const pipeline_complete = async () => {
if (debug_mode) console.log('Pipeline complete!', { s3_details, scan_result, file_info });
const pipelineComplete = async () => {
if (debugMode) console.log('Pipeline complete!', { s3Details, scanResult, fileInfo });
// If file was truncated (because it was too large)
if (file_info.truncated) {
if (fileInfo.truncated) {
// Remove the S3 object
remove_s3_obj();
removeS3Object();
}
// If the S3 upload threw an error
if (s3_details instanceof Error) {
log_error(s3_details);
return reject(new Error('There was an issue with your upload (Code: 1). Please try again. If you continue to experience issues, please contact Customer Support!'));
if (s3Details instanceof Error) {
logError(s3Details);
return reject(
new Error(
'There was an issue with your upload (Code: 1). Please try again. If you continue to experience issues, please contact Customer Support!'
)
);
}
// If the scan threw an error...
else if (scan_result instanceof Error) {
if ('data' in scan_result && scan_result.data.is_infected) {
log_error('Stream contained virus(es):', scan_result.data.viruses);
else if (scanResult instanceof Error) {
if ('data' in scanResult && scanResult.data.is_infected) {
logError('Stream contained virus(es):', scanResult.data.viruses);
}
// Not sure what's going on with this ECONNRESET stuff...
if ('code' in scan_result && scan_result.code !== 'ECONNRESET') {
log_error(scan_result);
if ('code' in scanResult && scanResult.code !== 'ECONNRESET') {
logError(scanResult);
// Remove the S3 object
remove_s3_obj();
return reject(new Error('There was an issue with your upload (Code: 2). Please try again. If you continue to experience issues, please contact Customer Support!'));
removeS3Object();
return reject(
new Error(
'There was an issue with your upload (Code: 2). Please try again. If you continue to experience issues, please contact Customer Support!'
)
);
}

@@ -125,30 +142,35 @@ }

// If the file is infected
else if (scan_result && 'is_infected' in scan_result && scan_result.is_infected === true) {
console.log(`A virus (${scan_result.viruses.join(', ')}) has been uploaded!`);
else if (scanResult && 'is_infected' in scanResult && scanResult.is_infected === true) {
console.log(`A virus (${scanResult.viruses.join(', ')}) has been uploaded!`);
// Remove the S3 object
remove_s3_obj();
return reject(new Error('The file you\'ve uploaded contained a virus. Please scan your system immediately. If you feel this is in error, please contact Customer Support. Thank you!'));
removeS3Object();
return reject(
new Error(
"The file you've uploaded contained a virus. Please scan your system immediately. If you feel this is in error, please contact Customer Support. Thank you!"
)
);
}
// If we're unsure the file is infected, just note that in the logs
else if (scan_result && 'is_infected' in scan_result && scan_result.is_infected === null) {
console.log('There was an issue scanning the uploaded file... You might need to investigate manually: ', { s3_details, file_info });
else if (scanResult && 'is_infected' in scanResult && scanResult.is_infected === null) {
console.log(
'There was an issue scanning the uploaded file... You might need to investigate manually: ',
{ s3Details, fileInfo }
);
}
// If the file uploaded just fine...
else {
if (debug_mode) console.log('The file uploaded was just fine... Carrying on...');
}
else if (debugMode) console.log('The file uploaded was just fine... Carrying on...');
// Resolve upload promise with file info
if (s3_details && 'Location' in s3_details) s3_details.Location = decodeURIComponent(s3_details.Location); // Not sure why this is necessary, but, w/e...
return resolve({ s3_details, file_info, fields });
if (s3Details && 'Location' in s3Details) s3Details.Location = decodeURIComponent(s3Details.Location); // Not sure why this is necessary, but, w/e...
return resolve({ s3Details, fileInfo, fields });
};
// Wait for both the file to be uploaded to S3 and for the scan to complete
// and then call `pipeline_complete`
// and then call `pipelineComplete`
pipeline.on('part-complete', () => {
// If the full pipeline has completed...
if (scan_result !== null && s3_details !== null) pipeline_complete();
if (scanResult !== null && s3Details !== null) pipelineComplete();
});

@@ -158,4 +180,4 @@

busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
num_files++;
file_info.filesize = 0;
numFiles += 1;
fileInfo.filesize = 0;

@@ -165,3 +187,3 @@ // Keep track of the size of the file as chunks come in.

// file.on('data', (chunk) => {
// file_info.filesize += chunk.length;
// fileInfo.filesize += chunk.length;
// });

@@ -171,8 +193,10 @@

file.on('limit', () => {
// const pretty_filesize = filesize(file_info.filesize);
console.log(`The file you've provided is over the maximum ${filesize(options.max_file_size)} allowed.`. file);
// const pretty_filesize = filesize(fileInfo.filesize);
console.log(
`The file you've provided is over the maximum ${filesize(options.max_file_size)} allowed.`.file
);
// Flag file info with something so we can remove from S3 if necessary
file_info.truncated = true;
fileInfo.truncated = true;
// Kill upload stream?

@@ -182,24 +206,36 @@ file.destroy();

// Respond to front-end
return reject(new Error(`The file you've provided is over the maximum ${filesize(options.max_file_size)} allowed.`));
return reject(
new Error(
`The file you've provided is over the maximum ${filesize(options.max_file_size)} allowed.`
)
);
});
// Make sure we're only allowing the specified type of file(s)
if (Array.isArray(options.allowed_mimetypes) && options.allowed_mimetypes.length > 0 && !options.allowed_mimetypes.includes(mimetype))
if (
Array.isArray(options.allowed_mimetypes) &&
options.allowed_mimetypes.length > 0 &&
!options.allowed_mimetypes.includes(mimetype)
)
return reject(new Error('Invalid file type provided!'));
// eslint-disable-next-line no-control-regex
const filename_ascii = filename.replace(/[^\x00-\x7F]/g, '').replace(/[,;'"\\/`|><*:$]/g, '').replace(/^[.-]+(.*)/,'$1');
const filenameAscii = filename
// eslint-disable-next-line no-control-regex
.replace(/[^\x00-\x7F]/g, '')
.replace(/[,;'"\\/`|><*:$]/g, '')
.replace(/^[.-]+(.*)/, '$1');
// Update file info object
file_info.filename = filename;
file_info.encoding = encoding;
file_info.mimetype = mimetype;
file_info.filename_ascii = filename_ascii;
file_info.s3_filesize = 0;
fileInfo.filename = filename;
fileInfo.encoding = encoding;
fileInfo.mimetype = mimetype;
fileInfo.filenameAscii = filenameAscii;
fileInfo.s3_filesize = 0;
// Configure the S3 streaming upload
s3_upload_stream = s3_stream.upload({
Bucket: s3_config.bucket,
Key: options.s3_id,
ContentDisposition: `inline; filename="${filename_ascii}"`,
s3UploadStream = s3Stream.upload({
Bucket: s3Config.bucket,
Key: options.s3Id,
ContentDisposition: `inline; filename="${filenameAscii}"`,
ContentType: mimetype,

@@ -211,6 +247,6 @@ ACL: options.s3_acl,

// Additional S3 configuration
s3_upload_stream.maxPartSize(10 * Math.pow(1024, 2)); // 10 MB
s3_upload_stream.concurrentParts(5);
s3_upload_stream.on('error', err => {
s3_details = err;
s3UploadStream.maxPartSize(10 * 1024 ** 2); // 10 MB
s3UploadStream.concurrentParts(5);
s3UploadStream.on('error', (err) => {
s3Details = err;
pipeline.emit('part-complete');

@@ -220,17 +256,24 @@ });

// Do this whenever a chunk of the upload has been received by S3
s3_upload_stream.on('part', details => {
if (file.truncated) s3_upload_stream.destroy('S3 uploading has been halted due to an overly-large file.');
s3UploadStream.on('part', (details) => {
if (file.truncated) s3UploadStream.destroy('S3 uploading has been halted due to an overly-large file.');
// Keep track of amount of data uploaded to S3
if (details.receivedSize > file_info.s3_filesize) {
file_info.filesize = file_info.s3_filesize = details.receivedSize;
if (details.receivedSize > fileInfo.s3_filesize) {
fileInfo.filesize = details.receivedSize;
fileInfo.s3_filesize = details.receivedSize;
}
if (debug_mode) console.log('File uploading to S3: ', Math.round((details.uploadedSize / details.receivedSize) * 100) + `% (${details.uploadedSize} / ${details.receivedSize})`);
if (debugMode)
console.log(
'File uploading to S3: ',
`${Math.round((details.uploadedSize / details.receivedSize) * 100)}% (${
details.uploadedSize
} / ${details.receivedSize})`
);
});
// When the file has been fully uploaded to S3
s3_upload_stream.on('uploaded', details => {
if (debug_mode) console.log('File Uploaded to S3: ', { details, file_size: file_info.s3_filesize });
s3_details = details;
s3_details.filesize = file_info.s3_filesize;
s3UploadStream.on('uploaded', (details) => {
if (debugMode) console.log('File Uploaded to S3: ', { details, file_size: fileInfo.s3_filesize });
s3Details = details;
s3Details.filesize = fileInfo.s3_filesize;
pipeline.emit('part-complete');

@@ -240,29 +283,34 @@ });

// Get instance of clamscan object
ClamScan.then(clamscan => {
ClamScan.then((clamscan) => {
const av = clamscan.passthrough();
// If there's an error scanning the file
av.on('error', error => {
scan_result = error;
av.on('error', (error) => {
scanResult = error;
pipeline.emit('part-complete');
}).on('data', () => {
if (file.truncated) av.destroy('Virus scanning has been halted due to overly-large file.');
}).on('finish', () => {
if (debug_mode) console.log('All data has been sent to virus scanner');
}).on('end', () => {
if (debug_mode) console.log('All data has been retrieved by ClamAV and sent on to the destination!');
}).on('scan-complete', result => {
if (debug_mode) console.log('Scan Complete. Result: ', result);
scan_result = result;
pipeline.emit('part-complete');
});
})
.on('data', () => {
if (file.truncated) av.destroy('Virus scanning has been halted due to overly-large file.');
})
.on('finish', () => {
if (debugMode) console.log('All data has been sent to virus scanner');
})
.on('end', () => {
if (debugMode)
console.log('All data has been retrieved by ClamAV and sent on to the destination!');
})
.on('scan-complete', (result) => {
if (debugMode) console.log('Scan Complete. Result: ', result);
scanResult = result;
pipeline.emit('part-complete');
});
// Pipe stream through ClamAV and on to S3
file.pipe(av).pipe(s3_upload_stream);
}).catch(e => {
log_error(e);
file.pipe(av).pipe(s3UploadStream);
}).catch((e) => {
logError(e);
reject(e);
});
if (debug_mode) console.log('Got a file stream!', filename);
if (debugMode) console.log('Got a file stream!', filename);
});

@@ -272,4 +320,4 @@

busboy.on('finish', () => {
if (debug_mode) console.log('BusBoy has fully flushed to S3 Stream...');
if (num_files === 0) pipeline_complete();
if (debugMode) console.log('BusBoy has fully flushed to S3 Stream...');
if (numFiles === 0) pipelineComplete();
});

@@ -288,35 +336,50 @@

// Generate a unique file ID for this upload
const file_id = uuidv4();
const s3_id = `some_folder/${file_id}`;
const fileId = uuidv4();
const s3Id = `some_folder/${fileId}`;
// Scan for viruses and upload to S3
try {
const { file_info, fields } = await pipe2s3(req, res, {
s3_id,
s3_metadata: {
some_info: 'cool info here'
},
max_files: 1,
max_file_size: 20 * Math.pow(1024, 2), // 20 MB
allowed_mimetypes: ['application/pdf', 'text/csv', 'text/plain'],
});
/**
* This could be some kind of middleware or something.
*
* @param {object} req - An Express Request object
* @param {object} res - An Express Response object
* @param {Function} next - What to do it all goes well
* @returns {void}
*/
async function run(req, res, next) {
// Scan for viruses and upload to S3
try {
const { fileInfo, fields } = await pipe2s3(req, res, {
s3Id,
s3_metadata: {
some_info: 'cool info here',
},
max_files: 1,
max_file_size: 20 * 1024 ** 2, // 20 MB
allowed_mimetypes: ['application/pdf', 'text/csv', 'text/plain'],
});
// Do something now that the files have been scanned and uploaded to S3 (add info to DB or something)
console.log('Cool! Everything worked. Heres the info about the uploaded file as well as the other form fields in the request payload: ', { file_info, fields });
} catch {
// Ooops... something went wrong. Log it.
const code = uuidv4();
console.error(`Error Code: ${code}: ${err}`, err);
// Do something now that the files have been scanned and uploaded to S3 (add info to DB or something)
console.log(
'Cool! Everything worked. Heres the info about the uploaded file as well as the other form fields in the request payload: ',
{ fileInfo, fields }
);
next();
} catch (err) {
// Ooops... something went wrong. Log it.
console.error(`Error: ${err}`, err);
try {
// Delete record of file in S3 if anything went wrong
if (s3_id) await S3.deleteObject({ Key: s3_id }).promise();
} catch (err2) {
const code = uuidv4();
console.error(`Error Code: ${code}: ${err2}`, err2);
return this.respond_error(res, new Error('We were unable to finish processing the file. Please contact Customer Support with the following error code: ' + code), 400, cb);
try {
// Delete record of file in S3 if anything went wrong
if (s3Id) await s3.deleteObject({ Key: s3Id }).promise();
} catch (err2) {
const code = uuidv4();
console.error(`Error Code: ${code}: ${err2}`, err2);
return this.respond_error(res, new Error('We were unable to finish processing the file.'), 400, next);
}
// Inform the user of the issue...
res.status(400).send('There was an error uploading your file.');
}
}
// Inform the user of the issue...
res.status(400).send(`There was an error uploading your file. Please provide the following code to customer support: ${code}`);
}
run();

@@ -1,7 +0,8 @@

const {PassThrough, Readable, Writable} = require('stream');
const request = require('request');
const { PassThrough } = require('stream');
// eslint-disable-next-line import/no-extraneous-dependencies
const axios = require('axios');
const fake_virus_url = 'https://secure.eicar.org/eicar_com.txt';
const normal_file_url = 'https://raw.githubusercontent.com/kylefarris/clamscan/sockets/README.md';
const test_url = normal_file_url;
const fakeVirusUrl = 'https://secure.eicar.org/eicar_com.txt';
// const normalFileUrl = 'https://raw.githubusercontent.com/kylefarris/clamscan/sockets/README.md';
const testUrl = fakeVirusUrl;

@@ -11,2 +12,5 @@ // Initialize the clamscan module

/**
* Actually run the test.
*/
async function test() {

@@ -24,3 +28,3 @@ const clamscan = await new NodeClam().init({

const passthrough = new PassThrough();
const source = request.get(test_url);
const source = axios.get(testUrl);

@@ -31,11 +35,15 @@ // Fetch fake Eicar virus file and pipe it through to our scan screeam

try {
const {is_infected, viruses} = await clamscan.scan_stream(passthrough)
const { isInfected, viruses } = await clamscan.scanStream(passthrough);
// If `is_infected` is TRUE, file is a virus!
if (is_infected === true) {
console.log(`You've downloaded a virus (${viruses.join('')})! Don't worry, it's only a test one and is not malicious...`);
} else if (is_infected === null) {
// If `isInfected` is TRUE, file is a virus!
if (isInfected === true) {
console.log(
`You've downloaded a virus (${viruses.join(
''
)})! Don't worry, it's only a test one and is not malicious...`
);
} else if (isInfected === null) {
console.log("Something didn't work right...");
} else if (is_infected === false) {
console.log(`The file (${test_url}) you downloaded was just fine... Carry on...`);
} else if (isInfected === false) {
console.log(`The file (${testUrl}) you downloaded was just fine... Carry on...`);
}

@@ -42,0 +50,0 @@ process.exit(0);

@@ -6,2 +6,3 @@ // Initialize the clamscan module

debug_mode: false,
// prettier-ignore
clamdscan: {

@@ -18,11 +19,12 @@ // Run scan using command line

},
// prettier-ignore
clamscan: {
path: '/usr/bin/clamscan', // <-- Worst-case scenario fallback
},
preference: 'clamdscan', // Set to 'clamscan' to test getting version info from `clamav`
preference: 'clamdscan', // Set to 'clamscan' to test getting version info from `clamav`
});
ClamScan.then(async av => {
ClamScan.then(async (av) => {
const result = await av.get_version();
console.log("Version: ", result);
console.log('Version: ', result);
});

@@ -308,2 +308,3 @@ # Changes

### SECURITY PATCH
An important security patch was released in this version which fixes a bug causing false negatives in specific edge cases. Please upgrade immediately and only use this version from this point on.

@@ -315,2 +316,17 @@

This just has some bug fixes and updates to dependencies. Technically, a new `'timeout'` event was added to the `passthrough` stream method, but, its not fully fleshed out and doesn't seem to work so it will remain undocumented for now.
This just has some bug fixes and updates to dependencies. Technically, a new `'timeout'` event was added to the `passthrough` stream method, but, its not fully fleshed out and doesn't seem to work so it will remain undocumented for now.
## 1.4.0
- Updated Mocha to v8.1.1. Subsequently, the oldest version of NodeJS allowed for this module is now v10.12.0.
- Fixed issue with the method not throwing errors when testing existence and viability of remote/local socket.
## 1.4.1
All sockets clients should now close when they are done being used, fail, or timeout.
## 1.4.2
- Fixed initialization to pass a config-file option during clamav version check
- Added new contributor
- Fixed tests
{
"name": "clamscan",
"version": "1.4.2",
"version": "2.0.0",
"author": "Kyle Farris <kyle.farris@infotechinc.com> (https://infotechinc.com)",

@@ -16,2 +16,5 @@ "description": "Use Node JS to scan files on your server with ClamAV's clamscan/clamdscan binary or via TCP to a remote server or local UNIX Domain socket. This is especially useful for scanning uploaded files provided by un-trusted sources.",

"scripts": {
"docs": "jsdoc2md index.js lib/* > API.md",
"format": "node_modules/.bin/prettier '**/*.{js,json}' --write",
"lint": "node_modules/.bin/eslint '**/*.js'",
"test": "make test"

@@ -41,8 +44,17 @@ },

"devDependencies": {
"axios": "^0.21.1",
"babel-eslint": "^10.1.0",
"chai": "^4.2.0",
"chai-as-promised": "^7.1.1",
"eslint": "^6.4.0",
"eslint": "^7.25.0",
"eslint-config-airbnb": "^15.0.1",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-chai-friendly": "^0.6.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jsdoc": "^33.1.0",
"eslint-plugin-prettier": "^3.4.0",
"jsdoc-to-markdown": "^7.0.1",
"mocha": "^8.1.1",
"request": "^2.88.0"
"prettier": "^2.2.1"
}
}
# NodeJS Clamscan Virus Scanning Utility
[![NPM Version][npm-version-image]][npm-url] [![NPM Downloads][npm-downloads-image]][npm-url] [![Node.js Version][node-image]][node-url]
[![NPM Version][npm-version-image]][npm-url] [![NPM Downloads][npm-downloads-image]][npm-url] [![Node.js Version][node-image]][node-url] [![Test Suite](https://github.com/kylefarris/clamscan/actions/workflows/test.yml/badge.svg)](https://github.com/kylefarris/clamscan/actions/workflows/test.yml)
Use Node JS to scan files on your server with ClamAV's clamscan/clamdscan binary or via TCP to a remote server or local UNIX Domain socket. This is especially useful for scanning uploaded files provided by un-trusted sources.
# !!IMPORTANT!!
# !!IMPORTANT

@@ -13,3 +13,3 @@ If you are using a version prior to 1.2.0, please upgrade! There was a security vulnerability in previous versions that can cause false negative in some edge cases. Specific details on how the attack could be implemented will not be disclosed here. Please update to 1.2.0 or greater ASAP. No breaking changes are included, only the security patch.

# Version 1.0.0 Information:
# Version 1.0.0 Information

@@ -29,7 +29,7 @@ If you are migrating from v0.8.5 or less to v1.0.0 or greater, please read the [release notes](https://github.com/kylefarris/clamscan/releases/tag/v1.0.0) as there are some breaking changes (but also some awesome new features!).

- [API](#api)
- [get_version](#get_version)
- [is_infected (alias: scan_file)](#is_infected)
- [scan_dir](#scan_dir)
- [scan_files](#scan_files)
- [scan_stream](#scan_stream)
- [getVersion](#getVersion)
- [isInfected (alias: scan_file)](#isInfected)
- [scanDir](#scanDir)
- [scanFiles](#scanFiles)
- [scanStream](#scanStream)
- [passthrough](#passthrough)

@@ -41,3 +41,3 @@ - [Contribute](#contribute)

## To use local binary method of scanning:
## To use local binary method of scanning

@@ -48,3 +48,3 @@ You will need to install ClamAV's clamscan binary and/or have clamdscan daemon running on your server. On linux, it's quite simple.

```
```bash
sudo yum install clamav

@@ -55,13 +55,13 @@ ```

```bash
sudo apt-get install clamav clamav-daemon
```
sudo apt-get install clamav
```
For OS X, you can install clamav with brew:
```
```bash
sudo brew install clamav
```
## To use ClamAV using TCP sockets:
## To use ClamAV using TCP sockets

@@ -72,12 +72,12 @@ You will need access to either:

- Follow instructions in [To use local binary method of scanning](#user-content-to-use-local-binary-method-of-scanning).
- Socket file is usually: `/var/run/clamd.scan/clamd.sock`
- Make sure `clamd` is running on your local server
- Follow instructions in [To use local binary method of scanning](#user-content-to-use-local-binary-method-of-scanning).
- Socket file is usually: `/var/run/clamd.scan/clamd.sock`
- Make sure `clamd` is running on your local server
2. A local/remote `clamd` daemon
- Must know the port the daemon is running on
- If running on remote server, you must have the IP address/domain name
- If running on remote server, it's firewall must have the appropriate TCP port(s) open
- Make sure `clamd` is running on your local/remote server
- Must know the port the daemon is running on
- If running on remote server, you must have the IP address/domain name
- If running on remote server, it's firewall must have the appropriate TCP port(s) open
- Make sure `clamd` is running on your local/remote server

@@ -88,3 +88,3 @@ **NOTE:** This module is not intended to work on a Windows server. This would be a welcome addition if someone wants to add that feature (I may get around to it one day but have no urgent need for this).

```
```bash
npm install clamscan

@@ -117,12 +117,12 @@ ```

const ClamScan = new NodeClam().init({
remove_infected: false, // If true, removes infected files
quarantine_infected: false, // False: Don't quarantine, Path: Moves files to this place.
scan_log: null, // Path to a writeable log file to write scan results into
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 (for scan_files method)
scan_recursively: true, // If true, deep scan folders recursively
removeInfected: false, // If true, removes infected files
quarantineInfected: false, // False: Don't quarantine, Path: Moves files to this place.
scanLog: null, // Path to a writeable log file to write scan results into
debugMode: false, // Whether or not to log info/debug/error msgs to the console
fileList: null, // path to file containing list of files to scan (for scanFiles method)
scanRecursively: true, // If true, deep scan folders recursively
clamscan: {
path: '/usr/bin/clamscan', // Path to clamscan binary on your server
db: null, // Path to a custom virus definition database
scan_archives: true, // If true, scan archives (ex. zip, rar, tar, dmg, iso, etc...)
scanArchives: true, // If true, scan archives (ex. zip, rar, tar, dmg, iso, etc...)
active: true // If true, this module will consider using the clamscan binary

@@ -135,9 +135,9 @@ },

timeout: 60000, // Timeout for scanning files
local_fallback: false, // Do no fail over to binary-method of scanning
localFallback: false, // Do no fail over to binary-method of scanning
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
configFile: null, // Specify config file if it's in an unusual place
multiscan: true, // Scan using all available cores! Yay!
reload_db: false, // If true, will re-load the DB on every call (slow)
reloadDb: false, // If true, will re-load the DB on every call (slow)
active: true, // If true, this module will consider using the clamdscan binary
bypass_test: false, // Check to see if socket is available when applicable
bypassTest: false, // Check to see if socket is available when applicable
},

@@ -153,11 +153,11 @@ preference: 'clamdscan' // If clamdscan is found and active, it will be used by default

const ClamScan = new NodeClam().init({
remove_infected: true, // Removes files if they are infected
quarantine_infected: '~/infected/', // Move file here. remove_infected must be FALSE, though.
scan_log: '/var/log/node-clam', // You're a detail-oriented security professional.
debug_mode: true, // This will put some debug info in your js console
file_list: '/home/webuser/scan_files.txt', // path to file containing list of files to scan
scan_recursively: false, // Choosing false here will save some CPU cycles
removeInfected: true, // Removes files if they are infected
quarantineInfected: '~/infected/', // Move file here. removeInfected must be FALSE, though.
scanLog: '/var/log/node-clam', // You're a detail-oriented security professional.
debugMode: true, // This will put some debug info in your js console
fileList: '/home/webuser/scanFiles.txt', // path to file containing list of files to scan
scanRecursively: false, // Choosing false here will save some CPU cycles
clamscan: {
path: '/usr/bin/clam', // I dunno, maybe your clamscan is just call "clam"
scan_archives: false, // Choosing false here will save some CPU cycles
scanArchives: false, // Choosing false here will save some CPU cycles
db: '/usr/bin/better_clam_db', // Path to a custom virus definition database

@@ -171,9 +171,9 @@ active: false // you don't want to use this at all because it's evil

timeout: 300000, // 5 minutes
local_fallback: true, // Use local preferred binary to scan if socket/tcp fails
localFallback: true, // Use local preferred binary to scan if socket/tcp fails
path: '/bin/clamdscan', // Special path to the clamdscan binary on your server
config_file: '/etc/clamd.d/daemon.conf', // A fairly typical config location
configFile: '/etc/clamd.d/daemon.conf', // A fairly typical config location
multiscan: false, // You hate speed and multi-threaded awesome-sauce
reload_db: true, // You want your scans to run slow like with clamscan
reloadDb: true, // You want your scans to run slow like with clamscan
active: false, // you don't want to use this at all because it's evil
bypass_test: true, // Don't check to see if socket is available. You should probably never set this to true.
bypassTest: true, // Don't check to see if socket is available. You should probably never set this to true.
},

@@ -188,3 +188,3 @@ preference: 'clamscan' // If clamscan is found and active, it will be used by default

If you specify a valid clamscan/clamdscan binary in your config and you set `clamdscan.local_fallback: true` in your config, this module _will_ fallback to the traditional way this module has worked--using a binary directly/locally.
If you specify a valid clamscan/clamdscan binary in your config and you set `clamdscan.localFallback: true` in your config, this module _will_ fallback to the traditional way this module has worked--using a binary directly/locally.

@@ -195,10 +195,10 @@ Also, there are some caveats to using the socket/tcp based approach:

- `remove_infected` - remote clamd service config will dictate this
- `quarantine_infected` - remote clamd service config will dictate this
- `scan_log` - remote clamd service config will dictate this
- `file_list` - this simply won't be available
- `removeInfected` - remote clamd service config will dictate this
- `quarantineInfected` - remote clamd service config will dictate this
- `scanLog` - remote clamd service config will dictate this
- `fileList` - this simply won't be available
- `clamscan.db` - only available on fallback
- `clamscan.scan_archives` - only available on fallback
- `clamscan.scanArchives` - only available on fallback
- `clamscan.path` - only available on fallback
- `clamdscan.config_file` - only available on fallback
- `clamdscan.configFile` - only available on fallback
- `clamdscan.path` - only available on fallback

@@ -220,7 +220,7 @@

// You can re-use the `clamscan` object as many times as you want
const version = await clamscan.get_version();
const version = await clamscan.getVersion();
console.log(`ClamAV Version: ${version}`);
const {is_infected, file, viruses} = await clamscan.is_infected('/some/file.zip');
if (is_infected) console.log(`${file} is infected with ${viruses}!`);
const {isInfected, file, viruses} = await clamscan.isInfected('/some/file.zip');
if (isInfected) console.log(`${file} is infected with ${viruses}!`);
} catch (err) {

@@ -243,3 +243,3 @@ // Handle any errors raised by the code in the try block

const clamscan = await new NodeClam().init(options);
const {good_files, bad_files} = await clamscan.scan_dir('/foo/bar');
const {goodFiles, badFiles} = await clamscan.scanDir('/foo/bar');
} catch (err) {

@@ -257,9 +257,9 @@ // Handle any errors raised by the code in the try block

[]()
<a name="getVersion"></a>
## .get_version([callback])
## .getVersion([callback])
This method allows you to determine the version of ClamAV you are interfacing with. It supports a callback and Promise API. If no callback is supplied, a Promise will be returned.
### Parameters:
### Parameters

@@ -271,3 +271,3 @@ - `callback` (function) (optional) Will be called when the scan is complete. It receives 2 parameters:

### Returns:
### Returns

@@ -278,6 +278,6 @@ - Promise

### Callback Example:
### Callback Example
```javascript
clamscan.get_version((err, version) => {
clamscan.getVersion((err, version) => {
if (err) return console.error(err);

@@ -288,6 +288,6 @@ console.log(`ClamAV Version: ${version}`);

### Promise Example:
### Promise Example
```javascript
clamscan.get_version().then(version => {
clamscan.getVersion().then(version => {
console.log(`ClamAV Version: ${version}`);

@@ -299,13 +299,13 @@ }).catch(err => {

[]()
<a name="isInfected"></a>
## .is_infected(file_path[,callback])
## .isInfected(file_path[,callback])
This method allows you to scan a single file. It supports a callback and Promise API. If no callback is supplied, a Promise will be returned. This method will likely be the most common use-case for this module.
### Alias:
### Alias
`.scan_file`
### Parameters:
### Parameters

@@ -316,7 +316,7 @@ - `file_path` (string) Represents a path to the file to be scanned.

- `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. **NULL**: Unable to scan.
- `file` (string) The original `file_path` passed into the `isInfected` method.
- `isInfected` (boolean) **True**: File is infected; **False**: File is clean. **NULL**: Unable to scan.
- `viruses` (array) An array of any viruses found in the scanned file.
### Returns:
### Returns

@@ -327,13 +327,13 @@ - Promise

- `file` (string) The original `file_path` passed into the `is_infected` method.
- `is_infected` (boolean) **True**: File is infected; **False**: File is clean. **NULL**: Unable to scan.
- `file` (string) The original `file_path` passed into the `isInfected` method.
- `isInfected` (boolean) **True**: File is infected; **False**: File is clean. **NULL**: Unable to scan.
- `viruses` (array) An array of any viruses found in the scanned file.
### Callback Example:
### Callback Example
```javascript
clamscan.is_infected('/a/picture/for_example.jpg', (err, file, is_infected, viruses) => {
clamscan.isInfected('/a/picture/for_example.jpg', (err, file, isInfected, viruses) => {
if (err) return console.error(err);
if (is_infected) {
if (isInfected) {
console.log(`${file} is infected with ${viruses.join(', ')}.`);

@@ -344,8 +344,8 @@ }

### Promise Example:
### Promise Example
```javascript
clamscan.is_infected('/a/picture/for_example.jpg').then(result => {
const {file, is_infected, viruses} = result;
if (is_infected) console.log(`${file} is infected with ${viruses.join(', ')}.`);
clamscan.isInfected('/a/picture/for_example.jpg').then(result => {
const {file, isInfected, viruses} = result;
if (isInfected) console.log(`${file} is infected with ${viruses.join(', ')}.`);
}).then(err => {

@@ -356,13 +356,13 @@ console.error(err);

### Async/Await Example:
### Async/Await Example
```javascript
const {file, is_infected, viruses} = await clamscan.is_infected('/a/picture/for_example.jpg');
const {file, isInfected, viruses} = await clamscan.isInfected('/a/picture/for_example.jpg');
```
[]()
<a name="scanDir"></a>
## .scan_dir(dir_path[,end_callback[,file_callback]])
## .scanDir(dir_path[,end_callback[,file_callback]])
Allows you to scan an entire directory for infected files. This obeys your `recursive` option even for `clamdscan` which does not have a native way to turn this feature off. If you have multiple paths, send them in an array to `scan_files`.
Allows you to scan an entire directory for infected files. This obeys your `recursive` option even for `clamdscan` which does not have a native way to turn this feature off. If you have multiple paths, send them in an array to `scanFiles`.

@@ -373,8 +373,8 @@ **TL;DR:** For maximum speed, don't supply a `file_callback`.

### NOTE:
### 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:
The `goodFiles` and `badFiles` 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_.
- A `file_callback` callback is provided, and `scanRecursively` is set to _true_.
- The scanner is set to `clamdscan` and `scanRecursively` is set to _false_.

@@ -387,4 +387,4 @@ ### Parameters

- `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_.
- `goodFiles` (array) List of the full paths to all files that are _clean_.
- `badFiles` (array) List of the full paths to all files that are _infected_.
- `viruses` (array) List of all the viruses found (feature request: associate to the bad files).

@@ -396,5 +396,5 @@

- `file` (string) Path to the file that just got scanned.
- `is_infected` (boolean) **True**: File is infected; **False**: File is clean. **NULL**: Unable to scan file.
- `isInfected` (boolean) **True**: File is infected; **False**: File is clean. **NULL**: Unable to scan file.
### Returns:
### Returns

@@ -405,6 +405,6 @@ - Promise

- `path` (string) The original `dir_path` passed into the `scan_dir` method.
- `is_infected` (boolean) **True**: File is infected; **False**: File is clean. **NULL**: Unable to scan.
- `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_.
- `path` (string) The original `dir_path` passed into the `scanDir` method.
- `isInfected` (boolean) **True**: File is infected; **False**: File is clean. **NULL**: Unable to scan.
- `goodFiles` (array) List of the full paths to all files that are _clean_.
- `badFiles` (array) List of the full paths to all files that are _infected_.
- `viruses` (array) List of all the viruses found (feature request: associate to the bad files).

@@ -415,7 +415,7 @@

```javascript
clamscan.scan_dir('/some/path/to/scan', (err, good_files, bad_files, viruses) {
clamscan.scanDir('/some/path/to/scan', (err, goodFiles, badFiles, viruses) {
if (err) return console.error(err);
if (bad_files.length > 0) {
console.log(`${path} was infected. The offending files (${bad_files.join (', ')}) have been quarantined.`);
if (badFiles.length > 0) {
console.log(`${path} was infected. The offending files (${badFiles.join (', ')}) have been quarantined.`);
console.log(`Viruses Found: ${viruses.join(', ')}`);

@@ -431,4 +431,4 @@ } else {

```javascript
clamscan.scan_dir('/some/path/to/scan').then(results => {
const {path, is_infected, good_files, bad_files, viruses} = results;
clamscan.scanDir('/some/path/to/scan').then(results => {
const { path, isInfected, goodFiles, badFiles, viruses } = results;
//...

@@ -443,19 +443,19 @@ }).catch(err => {

```javascript
const {path, is_infected, good_files, bad_files, viruses} = await clamscan.scan_dir('/some/path/to/scan');
const { path, isInfected, goodFiles, badFiles, viruses } = await clamscan.scanDir('/some/path/to/scan');
```
[]()
<a name="scanFiles"></a>
## .scan_files(files[,end_callback[,file_callback]])
## .scanFiles(files[,end_callback[,file_callback]])
This allows you to scan many files that might be in different directories or maybe only certain files of a single directory. This is essentially a wrapper for `is_infected` that simplifies the process of scanning many files or directories.
This allows you to scan many files that might be in different directories or maybe only certain files of a single directory. This is essentially a wrapper for `isInfected` that simplifies the process of scanning many files or directories.
### Parameters
- `files` (array) (optional) A list of strings representing full paths to files you want scanned. If not supplied, the module will check for a `file_list` config option. If neither is found, the method will throw an error.
- `files` (array) (optional) A list of strings representing full paths to files you want scanned. If not supplied, the module will check for a `fileList` config option. If neither is found, the method will throw an error.
- `end_callback` (function) (optional) Will be called when the entire list of files has been completely scanned. This callback takes 3 parameters:
- `err` (object or null) 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_.
- `goodFiles` (array) List of the full paths to all files that are _clean_.
- `badFiles` (array) List of the full paths to all files that are _infected_.

@@ -466,3 +466,3 @@ - `file_callback` (function) (optional) Will be called after each file in the list has been scanned. This is useful for keeping track of the progress of the scan. This callback takes 3 parameters:

- `file` (string) Path to the file that just got scanned.
- `is_infected` (boolean) **True**: File is infected; **False**: File is clean. **NULL**: Unable to scan file.
- `isInfected` (boolean) **True**: File is infected; **False**: File is clean. **NULL**: Unable to scan file.

@@ -475,4 +475,4 @@ ### Returns

- `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_.
- `goodFiles` (array) List of the full paths to all files that are _clean_.
- `badFiles` (array) List of the full paths to all files that are _infected_.
- `errors` (object) Per-file errors keyed by the filename in which the error happened. (ex. `{'foo.txt': Error}`)

@@ -484,6 +484,3 @@ - `viruses` (array) List of all the viruses found (feature request: associate to the bad files).

```javascript
const scan_status = {
good: 0,
bad: 0
};
const scan_status = { good: 0, bad: 0 };
const files = [

@@ -494,9 +491,9 @@ '/path/to/file/1.jpg',

];
clamscan.scan_files(files, (err, good_files, bad_files, viruses) => {
clamscan.scanFiles(files, (err, goodFiles, badFiles, viruses) => {
if (err) return console.error(err);
if (bad_files.length > 0) {
if (badFiles.length > 0) {
console.log({
msg: `${good_files.length} files were OK. ${bad_files.length} were infected!`,
bad_files,
good_files,
msg: `${goodFiles.length} files were OK. ${badFiles.length} were infected!`,
badFiles,
goodFiles,
viruses,

@@ -507,5 +504,5 @@ });

}
}, (err, file, is_infected, viruses) => {
;(is_infected ? scan_status.bad++ : scan_status.good++);
console.log(`${file} is ${(is_infected ? `infected with ${viruses}` : 'ok')}.`);
}, (err, file, isInfected, viruses) => {
;(isInfected ? scan_status.bad++ : scan_status.good++);
console.log(`${file} is ${(isInfected ? `infected with ${viruses}` : 'ok')}.`);
console.log('Scan Status: ', `${(scan_status.bad + scan_status.good)}/${files.length}`);

@@ -520,4 +517,4 @@ });

```javascript
clamscan.scan_files(files).then(results => {
const {good_files, bad_files, errors, viruses} = results;
clamscan.scanFiles(files).then(results => {
const { goodFiles, badFiles, errors, viruses } = results;
// ...

@@ -532,6 +529,6 @@ }).catch(err => {

```javascript
const {good_files, bad_files, errors, viruses} = await clamscan.scan_files(files);
const { goodFiles, badFiles, errors, viruses } = await clamscan.scanFiles(files);
```
#### Scanning files listed in file_list
#### Scanning files listed in fileList

@@ -542,3 +539,3 @@ 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.

```
```bash
/some/path/to/file.zip

@@ -553,14 +550,14 @@ /some/other/path/to/file.exe

const ClamScan = new NodeClam().init({
file_list: '/path/to/file_list.txt'
fileList: '/path/to/fileList.txt'
});
ClamScan.then(async clamscan => {
// Supply nothing to first parameter to use `file_list`
const {good_files, bad_files, errors, viruses} = await clamscan.scan_files();
// Supply nothing to first parameter to use `fileList`
const { goodFiles, badFiles, errors, viruses } = await clamscan.scanFiles();
});
```
[]()
<a name="scanStream"></a>
## .scan_stream(stream[,callback])
## .scanStream(stream[,callback])

@@ -575,3 +572,3 @@ This method allows you to scan a binary stream. **NOTE**: This method will only work if you've configured the module to allow the use of a TCP or UNIX Domain socket. In other words, this will not work if you only have access to a local ClamAV binary.

- `err` (object or null) A standard JavaScript Error object (null if no error)
- `is_infected` (boolean) **True**: Stream is infected; **False**: Stream is clean. **NULL**: Unable to scan file.
- `isInfected` (boolean) **True**: Stream is infected; **False**: Stream is clean. **NULL**: Unable to scan file.

@@ -585,3 +582,3 @@ ### Returns

- `file` (string) **NULL** as no file path can be provided with the stream
- `is_infected` (boolean) **True**: File is infected; **False**: File is clean. **NULL**: Unable to scan.
- `isInfected` (boolean) **True**: File is infected; **False**: File is clean. **NULL**: Unable to scan.
- `viruses` (array) An array of any viruses found in the scanned file.

@@ -611,5 +608,5 @@

clamscan.scan_stream(stream, (err, is_infected) => {
clamscan.scanStream(stream, (err, isInfected) => {
if (err) return console.error(err);
if (is_infected) return console.log("Stream is infected! Booo!");
if (isInfected) return console.log("Stream is infected! Booo!");
console.log("Stream is not infected! Yay!");

@@ -622,4 +619,4 @@ });

```javascript
clamscan.scan_stream(stream).then(({is_infected}) => {
if (is_infected) return console.log("Stream is infected! Booo!");
clamscan.scanStream(stream).then(({isInfected}) => {
if (isInfected) return console.log("Stream is infected! Booo!");
console.log("Stream is not infected! Yay!");

@@ -634,6 +631,6 @@ }).catch(err => {

```javascript
const {is_infected, viruses} = await clamscan.scan_stream(stream);
const { isInfected, viruses } = await clamscan.scanStream(stream);
```
[]()
<a name="passthrough"></a>

@@ -664,7 +661,7 @@ ## .passthrough()

// For example's sake, we're using the RequestJS module
const request = require('request');
// For example's sake, we're using the Axios module
const axios = require('Axios');
// Get a readable stream for a URL request
const input = request.get(some_url);
const input = axios.get(some_url);

@@ -677,4 +674,4 @@ // Create a writable stream to a local file

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

@@ -684,3 +681,3 @@

av.on('scan-complete', result => {
const {is_infected, viruses} = result;
const { isInfected, viruses } = result;
// Do stuff if you want

@@ -697,3 +694,2 @@ });

# Contribute

@@ -703,3 +699,3 @@

# Resources used to help develop this module:
# Resources used to help develop this module

@@ -706,0 +702,0 @@ - <https://stuffivelearned.org/doku.php?id=apps:clamav:general:remoteclamdscan>

const fs = require('fs');
const { Readable } = require('stream');
const bad_scan_dir = __dirname + '/bad_scan_dir';
const bad_scan_file = `${bad_scan_dir}/bad_file_1.txt`;
const badScanDir = `${__dirname}/bad_scan_dir`;
const badScanFile = `${badScanDir}/bad_file_1.txt`;
// prettier-ignore
const eicarByteArray = [

@@ -12,3 +14,3 @@ 88, 53, 79, 33, 80, 37, 64, 65, 80, 91, 52, 92,

73, 82, 85, 83, 45, 84, 69, 83, 84, 45, 70, 73,
76, 69, 33, 36, 72, 43, 72, 42
76, 69, 33, 36, 72, 43, 72, 42,
];

@@ -19,3 +21,3 @@

const EicarGen = {
writeFile: () => fs.writeFileSync(bad_scan_file, eicarBuffer.toString()),
writeFile: () => fs.writeFileSync(badScanFile, eicarBuffer.toString()),
getStream: () => Readable.from(eicarBuffer),

@@ -26,2 +28,2 @@ };

module.exports = EicarGen;
module.exports = EicarGen;

@@ -5,25 +5,32 @@ const fs = require('fs');

// walk $PATH to find bin
const which = bin => {
const which = (bin) => {
const path = process.env.PATH.split(p.delimiter);
for (let i in path) {
const file = path[i] + p.sep + bin;
if (fs.existsSync(file)) return file;
}
return '';
let file = '';
path.find((v) => {
const testPath = v + p.sep + bin;
if (fs.existsSync(testPath)) {
file = testPath;
return true;
}
return false;
});
return file;
};
const config = {
remove_infected: false, // don't change
quarantine_infected: __dirname + '/infected', // required for testing
//scan_log: __dirname + '/clamscan-log', // not required
removeInfected: false, // don't change
quarantineInfected: `${__dirname}/infected`, // required for testing
// scan_log: `${__dirname}/clamscan-log`, // not required
clamscan: {
path: which('clamscan'), // required for testing
path: which('clamscan'), // required for testing
},
clamdscan: {
socket: '/usr/local/var/clamav/clamd.socket', // required for testing (change for your system e.g. '/var/run/clamd.scan/clamd.sock') - can be set to null
host: '127.0.0.1', // required for testing (change for your system) - can be set to null
port: 3310, // required for testing (change for your system) - can be set to null
path: which('clamdscan'), // required for testing
socket: '/usr/local/var/clamav/clamd.socket', // required for testing (change for your system e.g. '/var/run/clamd.scan/clamd.sock') - can be set to null
host: '127.0.0.1', // required for testing (change for your system) - can be set to null
port: 3310, // required for testing (change for your system) - can be set to null
path: which('clamdscan'), // required for testing
timeout: 1000,
// config_file: '/etc/clamd.d/scan.conf' // set if required
// config_file: '/etc/clamd.d/scan.conf' // set if required
},

@@ -33,5 +40,5 @@ debug_mode: false,

// Force specific socket when on travis CI.
// Force specific socket when on GitHub Actions
if (process.env.CI) config.clamdscan.socket = '/var/run/clamav/clamd.ctl';
module.exports = config;

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

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