You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

103 lines
3.7KB

  1. const fs = require("fs");
  2. const request = require("request");
  3. const { exec } = require('child_process');
  4. const vt = require("node-virustotal");//https://github.com/natewatson999/node-virustotal
  5. const con = vt.MakePublicConnection();
  6. const archArr = ['win', 'lin', 'mac'];
  7. const verbose = false;
  8. //VT_API_KEY is set in travisci (or set when run locally)
  9. con.setKey(process.env.VT_API_KEY);
  10. if(verbose){console.log('key', con.getKey());}
  11. //15 seconds between calls is required by virus total (4 per minute)
  12. //https://www.virustotal.com/en/documentation/public-api/v2/
  13. con.setDelay(15000);
  14. if(verbose){console.log('delay', con.getDelay());}
  15. //We always just check all JSON because it's easy.
  16. //However, we only want to check zip files of manifests that have changed.
  17. exec('git diff -w --stat --name-only origin/master -- plugins/', (error, stdout, stderr) => {
  18. if (error) {
  19. console.error(`exec error: ${error}`);
  20. return;
  21. }
  22. const changedManifestFiles = stdout.trim().split('\n');
  23. testAllManifests(changedManifestFiles);
  24. });
  25. function testAllManifests(changedManifestFiles){
  26. if(verbose){console.log("changedManifestFiles", changedManifestFiles);}
  27. fs.readdir('plugins', function(err, files) {
  28. if (err){ throw err; }
  29. for (let index in files) {
  30. const filePath = `plugins/${files[index]}`;
  31. if(!filePath.toLowerCase().endsWith('.json')){
  32. throw new Error("manifests should have .json extension");
  33. }
  34. fs.readFile(filePath, 'utf8', (err, data) => {
  35. if (err){ throw err; }
  36. if(verbose){console.log("testing: ", filePath);}
  37. const shouldTestZip = changedManifestFiles.includes(filePath);
  38. testOneManifest(filePath, data, shouldTestZip);
  39. });
  40. }
  41. });
  42. }
  43. function testOneManifest(filePath, fileContent, shouldTestZip = false) {
  44. let manifestObj;
  45. try {
  46. manifestObj = JSON.parse(fileContent);
  47. } catch(err){
  48. console.error(`Invalid JSON: ${filePath}`);
  49. throw err;
  50. }
  51. if (manifestObj.downloads) {
  52. let zipUrlsChecked = [];
  53. let lastSha256;
  54. archArr.map(arch => {
  55. const archObj = manifestObj.downloads[arch];
  56. if (archObj && archObj.download) {
  57. if(zipUrlsChecked.includes(archObj.download)){
  58. if(lastSha256 !== archObj.sha256){
  59. throw new Error('SHA256 should be the same if the download URL is the same.');
  60. }
  61. } else {
  62. zipUrlsChecked.push(archObj.download);
  63. lastSha256 = archObj.sha256;
  64. if(shouldTestZip){
  65. testOneArch(archObj);
  66. } else {
  67. if(verbose){console.log("not testing zip because the manifest hasn't changed: ", filePath);}
  68. }
  69. }
  70. }
  71. });
  72. }
  73. }
  74. function testOneArch(archObj) {
  75. const urlParts = archObj.download.split('/');
  76. const zipName = urlParts[urlParts.length - 1].split('\?')[0];
  77. if(verbose){console.log(`Downloading ${archObj.download}`);}
  78. request(archObj.download).pipe(fs.createWriteStream(zipName)).on('finish', ()=>{
  79. con.FileEvaluation(zipName, "application/zip", fs.readFileSync(zipName), function(data) {
  80. console.log(data);
  81. if(archObj.sha256 !== data.sha256){
  82. throw new Error(`Invalid sha256 value. manifest:${archObj.sha256} virustotal:${data.sha256}`);
  83. }
  84. if(data.positives > 2){
  85. throw new Error(`Too many positives from virustotal.`);
  86. }
  87. }, function(err) {
  88. if (err){ throw err; }
  89. });
  90. });
  91. }