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.

133 lines
3.6 KiB

9 months ago
  1. #!/usr/bin/env node
  2. "use strict";
  3. const Promise = require("bluebird");
  4. const yargs = require("yargs");
  5. const fs = require("fs");
  6. const fsPromises = require("fs").promises;
  7. const path = require("path");
  8. const isGlob = require("is-glob");
  9. const globby = require("globby");
  10. const splitFilterN = require("split-filter-n");
  11. const chalk = require("chalk");
  12. const matchValue = require("match-value");
  13. const detectSVGScripts = require("detect-svg-scripts");
  14. let argv = yargs
  15. .boolean("errors-only")
  16. .argv;
  17. let failureTypes = [ "externalScriptFile", "inlineScriptTag", "eventHandler" ];
  18. Promise.map(argv._, (target) => {
  19. return Promise.try(() => {
  20. return fsPromises.stat(target);
  21. }).then((stats) => {
  22. if (stats.isDirectory()) {
  23. // Literal directory
  24. return {
  25. type: "glob",
  26. glob: path.posix.join(target, "**/*.svg")
  27. };
  28. } else {
  29. // Literal single file
  30. return {
  31. type: "file",
  32. path: target
  33. };
  34. }
  35. }).catch({ code: "ENOENT" }, (error) => {
  36. // Probably meant as a glob pattern
  37. if (isGlob(target)) {
  38. return {
  39. type: "glob",
  40. glob: target
  41. };
  42. } else {
  43. // TODO: Make this output nicer
  44. throw error;
  45. }
  46. });
  47. }).then((targets) => {
  48. if (targets.length === 0) {
  49. // Default to all SVGs in the current working directory, if the user hasn't specified any explicit paths to scan.
  50. targets = [{
  51. type: "glob",
  52. glob: path.posix.join(process.cwd(), "**/*.svg")
  53. }];
  54. }
  55. let targetsByType = splitFilterN(targets, [ "file", "glob" ], (target) => target.type);
  56. let literalFiles = targetsByType.file.map((target) => target.path);
  57. let globs = targetsByType.glob.map((target) => target.glob);
  58. return Promise.try(() => {
  59. return globby(globs);
  60. }).then((globbedPaths) => {
  61. return literalFiles.concat(globbedPaths);
  62. });
  63. }).map((file) => {
  64. return Promise.try(() => {
  65. return detectSVGScripts(fs.createReadStream(file));
  66. }).then((occurrences) => {
  67. if (occurrences.length > 0) {
  68. return {
  69. file: file,
  70. passed: false,
  71. occurrences: occurrences
  72. };
  73. } else {
  74. return {
  75. file: file,
  76. passed: true,
  77. occurrences: occurrences
  78. };
  79. }
  80. });
  81. }).each((result) => {
  82. if (result.passed === true) {
  83. if (!argv.errorsOnly) {
  84. console.log(chalk.green(`${chalk.bold("[ ✔ PASSED ]")} ${result.file}`));
  85. }
  86. } else {
  87. console.log(chalk.red(`${chalk.bold("[ ✘ FAILED ]")} ${result.file}`));
  88. let failuresByType = splitFilterN(result.occurrences, failureTypes, (result) => result.type);
  89. let foundTypes = failureTypes
  90. .filter((type) => failuresByType[type].length > 0)
  91. .map((type) => {
  92. let failureCount = failuresByType[type].length;
  93. let suffix = matchValue(type, {
  94. externalScriptFile: "external script file(s)",
  95. inlineScriptTag: "inline script tag(s)",
  96. eventHandler: "inline event handler(s)"
  97. });
  98. if (type === "eventHandler") {
  99. let attributes = failuresByType.eventHandler.map((item) => item.attribute);
  100. let uniqueAttributes = Array.from(new Set(attributes));
  101. return `${failureCount} ${suffix}: ${uniqueAttributes.join(", ")}`;
  102. } else {
  103. return `${failureCount} ${suffix}`;
  104. }
  105. });
  106. // Indent at the same depth as the filename
  107. console.log(chalk.gray(` └ Found ${foundTypes.join(", ")}`));
  108. }
  109. }).then((allResults) => {
  110. let failedResults = allResults.filter((result) => result.passed !== true);
  111. let totalCount = allResults.length;
  112. let failedCount = failedResults.length;
  113. let passedCount = totalCount - failedCount;
  114. if (failedCount > 0) {
  115. process.exitCode = 1;
  116. }
  117. console.log(`Scanned ${totalCount} files, ${chalk.green(`${passedCount} passed`)}, ${chalk.red(`${failedCount} failed`)}`);
  118. });