"use strict"; const sax = require("sax"); const unreachable = require("@joepie91/unreachable")("detect-svg-scripts"); const { validateArguments } = require("@validatem/core"); const either = require("@validatem/either"); const required = require("@validatem/required"); const isString = require("@validatem/is-string"); const isBuffer = require("@validatem/is-buffer"); const isNodeStream = require("@validatem/is-node-stream"); module.exports = function detectSVGScripts(svgFile) { validateArguments(arguments, { svgFile: [ required, either([ isString, isBuffer, isNodeStream.anyReadable ]) ] }); let parser = sax.createStream(false); if (typeof svgFile === "string" || Buffer.isBuffer(svgFile)) { // Make sure that all event listeners have been attached before we feed in any data process.nextTick(() => { parser.end(svgFile); }); } else { svgFile.pipe(parser); } return new Promise((resolve, reject) => { let occurrences = []; function detachListeners() { parser.removeListener("end", onEnd); parser.removeListener("error", onError); parser.removeListener("opentag", onTag); } function onEnd() { resolve(occurrences); detachListeners(); } function onError(error) { reject(error); detachListeners(); } function onTag(tag) { let normalizedTag = tag.name .toLowerCase() .replace(/.+:([^:]+)$/, (_, actualTag) => actualTag); let normalizedAttributes = Object.entries(tag.attributes) .map(([ attribute, value ]) => [ attribute.toLowerCase(), value ]); if (normalizedTag === "script") { let externalSources = normalizedAttributes .filter(([ attribute, _value ]) => attribute === "src") .map(([ _attribute, value ]) => value); if (externalSources.length === 1) { occurrences.push({ type: "externalScriptFile", tag: tag, file: externalSources[0] }); } else if (externalSources.length === 0) { occurrences.push({ type: "inlineScriptTag", tag: tag }); } else { unreachable("Encountered more than one 'src' key"); } } // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/Events let eventHandlers = normalizedAttributes .filter(([ attribute, _value ]) => attribute.startsWith("on")); for (let [ attribute, _value ] of eventHandlers) { occurrences.push({ type: "eventHandler", tag: tag, attribute: attribute }); } } parser.on("error", onError); parser.on("end", onEnd); parser.on("opentag", onTag); }); };