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.

159 lines
5.8KB

  1. const TEMP_DIR = ".tmp-zips/";
  2. const CUR_VER_PREFIX = '0.5';
  3. const MF_DIR = 'plugins';
  4. const fs = require("fs");
  5. const Ajv = require('ajv'); //https://github.com/epoberezkin/ajv
  6. const ajv = new Ajv({allErrors:true});
  7. const validate = ajv.compile(require('./manifest.json'));
  8. const request = require("request");
  9. const { execSync } = require('child_process');
  10. const AdmZip = require('adm-zip'); //https://github.com/cthackers/adm-zip
  11. const hashFiles = require('hash-files'); //https://github.com/mac-/hash-files
  12. const manifestsToTest = getManifestsThatApply();
  13. if(manifestsToTest.length === 0){
  14. console.log("No manifest files to test.");
  15. return;
  16. }
  17. //virus total stuff
  18. const VIRUS_TOTAL_ENABLED = false;
  19. const vt = require("node-virustotal"); //https://github.com/natewatson999/node-virustotal
  20. const con = vt.MakePublicConnection();
  21. if(VIRUS_TOTAL_ENABLED){
  22. con.setKey(process.env.VT_API_KEY);
  23. con.setDelay(15000);
  24. jasmine.DEFAULT_TIMEOUT_INTERVAL = manifestsToTest.length * 4 * con.getDelay();
  25. }
  26. describe("test manifests", function() {
  27. beforeEach(()=>{
  28. execSync(`mkdir -p ${TEMP_DIR}`)
  29. });
  30. afterEach(()=>{
  31. execSync(`rm ${TEMP_DIR}*.zip`)
  32. fs.rmdirSync(TEMP_DIR);
  33. });
  34. const testMF = (filePath) => {
  35. it("valid properties and zip files", function(done) {
  36. try {
  37. if (!filePath.toLowerCase().endsWith('.json')) {
  38. fail("manifests should have .json extension");
  39. }
  40. const fileContent = fs.readFileSync(filePath, 'utf8');
  41. const mfObj = JSON.parse(fileContent);
  42. const valid = validate(mfObj);
  43. if (!valid) {
  44. validate.errors.map(e => e.message += ` in ${filePath}`)
  45. fail(validate.errors);
  46. }
  47. if (!(/^[a-zA-Z0-9_\-]*$/).test(mfObj.slug)) {
  48. fail(`slug does not match regex in ${filePath}`);
  49. }
  50. const fileName = filePath.replace('plugins/', '');
  51. if (fileName.replace('.json', '') !== mfObj.slug) {
  52. fail(`slug '${mfObj.slug}' does not match fileName: ${fileName}`);
  53. }
  54. if (mfObj.version && !mfObj.version.startsWith(CUR_VER_PREFIX)) {
  55. fail(`version '${mfObj.version}' must start with '${CUR_VER_PREFIX}'`);
  56. }
  57. if(mfObj.downloads){
  58. const urlsChecked = [];
  59. let lastSha256;
  60. ['win', 'lin', 'mac'].map(os => {
  61. const osObj = mfObj.downloads[os];
  62. if(osObj && osObj.download && osObj.sha256){
  63. const zipUrl = osObj.download;
  64. if(urlsChecked.includes(zipUrl)){
  65. if(lastSha256 !== osObj.sha256){
  66. fail("SHA256 should be the same if the download URL is the same");
  67. }
  68. } else {
  69. urlsChecked.push(zipUrl);
  70. lastSha256 = osObj.sha256;
  71. testOneZip(mfObj.slug, osObj, done);
  72. }
  73. }
  74. });
  75. }
  76. } catch(err){
  77. fail(`Error while trying to validate manifest: ${filePath}\n${err}`);
  78. }
  79. });
  80. };
  81. manifestsToTest.map(testMF);
  82. });
  83. function testOneZip(expectedRootDir, osObj, done) {
  84. const urlParts = osObj.download.split('/');
  85. const zipName = urlParts[urlParts.length - 1].split('\?')[0];
  86. request(osObj.download).pipe(fs.createWriteStream(TEMP_DIR+zipName)).on('finish', ()=>{
  87. console.log(`Downloaded ${TEMP_DIR+zipName}`);
  88. const zip = new AdmZip(TEMP_DIR+zipName);
  89. const zipEntries = zip.getEntries();
  90. // zipEntries.map(ze=>console.log(ze.toString()));
  91. const slugDirFound = zipEntries.find(ze => ze.isDirectory &&
  92. (ze.entryName === expectedRootDir+'/' || ze.entryName === expectedRootDir+'\\')
  93. );
  94. if(slugDirFound){
  95. const invalidEntry = zipEntries.find(ze => !ze.entryName.startsWith(slugDirFound.entryName));
  96. if(invalidEntry){
  97. fail(`Zip entries should all be under a dir named ${expectedRootDir} but this entry was found: ${invalidEntry.entryName}`);
  98. }
  99. } else {
  100. fail(`Zip should have one dir named ${expectedRootDir}`);
  101. }
  102. if(VIRUS_TOTAL_ENABLED){
  103. con.FileEvaluation(zipName, "application/zip", fs.readFileSync(TEMP_DIR+zipName), function(data) {
  104. console.log(data);
  105. if(osObj.sha256 !== data.sha256){
  106. throw new Error(`Invalid sha256 value. manifest:${osObj.sha256} virustotal:${data.sha256}`);
  107. }
  108. if(data.positives > 2){
  109. throw new Error(`Too many positives from virustotal.`);
  110. }
  111. done();
  112. }, function(err) {
  113. if (err){ throw err; }
  114. done();
  115. });
  116. } else {
  117. hashFiles({files:[TEMP_DIR+zipName], algorithm:'sha256'}, function(error, hash) {
  118. if(osObj.sha256 !== hash){
  119. throw new Error(`Invalid sha256 value. manifest:${osObj.sha256} hash:${hash}`);
  120. }
  121. done();
  122. });
  123. }
  124. });
  125. }
  126. function getManifestsThatApply(){
  127. let paths = "";
  128. if(process.env.TEST_MANIFEST){
  129. paths = process.env.TEST_MANIFEST;
  130. } else {
  131. paths = execSync(`git diff -w --stat --name-only origin/master -- ${MF_DIR}/`, {encoding:'utf8'});
  132. }
  133. return paths.trim().split('\n').filter(s=>s.trim() !== '');
  134. }