mirror of https://github.com/keeweb/keeweb.git
98 lines
3.6 KiB
JavaScript
98 lines
3.6 KiB
JavaScript
module.exports = function (grunt) {
|
|
grunt.registerMultiTask('virustotal', 'Checks if a file has issues on VirusTotal', function () {
|
|
const done = this.async();
|
|
const opt = this.options();
|
|
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
const fetch = require('node-fetch');
|
|
const FormData = require('form-data');
|
|
|
|
Promise.all(
|
|
this.files[0].src.map((file) =>
|
|
checkFile(opt, file).catch((err) => {
|
|
grunt.warn('VirusTotal check failed: ' + err);
|
|
})
|
|
)
|
|
).then(done);
|
|
|
|
async function checkFile(opt, file) {
|
|
grunt.log.writeln(`Uploading to VirusTotal: ${file}...`);
|
|
|
|
const timeStarted = Date.now();
|
|
|
|
const { apiKey, prefix, timeout = 60 * 1000 } = opt;
|
|
const interval = 5000;
|
|
|
|
const headers = { 'x-apikey': apiKey };
|
|
|
|
const fileData = fs.readFileSync(file);
|
|
const fileName = (prefix || '') + path.basename(file);
|
|
|
|
const form = new FormData();
|
|
form.append('file', fileData, fileName);
|
|
const fileUploadResp = await fetch('https://www.virustotal.com/api/v3/files', {
|
|
method: 'POST',
|
|
headers,
|
|
body: form
|
|
});
|
|
const fileUploadRespData = await fileUploadResp.json();
|
|
if (fileUploadRespData.error) {
|
|
const errStr = JSON.stringify(fileUploadRespData.error);
|
|
throw new Error(`File upload error: ${errStr}`);
|
|
}
|
|
|
|
const id = fileUploadRespData.data.id;
|
|
if (!id) {
|
|
throw new Error('File upload error: empty id');
|
|
}
|
|
|
|
grunt.log.writeln(`Uploaded ${file} to VirusTotal, id: ${id}`);
|
|
|
|
let elapsed;
|
|
do {
|
|
const checkResp = await fetch(`https://www.virustotal.com/api/v3/analyses/${id}`, {
|
|
headers
|
|
});
|
|
const checkRespData = await checkResp.json();
|
|
if (checkRespData.error) {
|
|
const errStr = JSON.stringify(checkRespData.error);
|
|
throw new Error(`File check error: ${errStr}`);
|
|
}
|
|
const { attributes } = checkRespData.data;
|
|
if (attributes.status === 'completed') {
|
|
const { stats } = attributes;
|
|
if (stats.malicious > 0) {
|
|
throw new Error(
|
|
`File ${file} reported as malicious ${stats.malicious} time(s)`
|
|
);
|
|
}
|
|
if (stats.suspicious > 0) {
|
|
throw new Error(
|
|
`File ${file} reported as malicious ${stats.suspicious} time(s)`
|
|
);
|
|
}
|
|
const statsStr = Object.entries(stats)
|
|
.map(([k, v]) => `${k}=${v}`)
|
|
.join(', ');
|
|
grunt.log.writeln(`VirusTotal check OK: ${file}, stats:`, statsStr);
|
|
return;
|
|
}
|
|
|
|
elapsed = Date.now() - timeStarted;
|
|
grunt.log.writeln(
|
|
`VirusTotal check status: ${attributes.status}, elapsed ${elapsed}ms`
|
|
);
|
|
|
|
await wait(interval);
|
|
} while (elapsed < timeout);
|
|
|
|
throw new Error(`Timed out after ${timeout}ms`);
|
|
}
|
|
|
|
function wait(ms) {
|
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
}
|
|
});
|
|
};
|