UNPKG

@cyclonedx/cdxgen

Version:

Creates CycloneDX Software Bill-of-Materials (SBOM) from source or container image

125 lines (117 loc) 3.52 kB
const connect = require("connect"); const http = require("http"); const bodyParser = require("body-parser"); const url = require("url"); const { spawnSync } = require("child_process"); const os = require("os"); const fs = require("fs"); const path = require("path"); const bom = require("./index.js"); const compression = require("compression"); // Timeout milliseconds. Default 10 mins const TIMEOUT_MS = parseInt(process.env.CDXGEN_SERVER_TIMEOUT_MS) || 10 * 60 * 1000; const app = connect(); app.use( bodyParser.json({ deflate: true, limit: "1mb" }) ); app.use(compression()); const gitClone = (repoUrl) => { const tempDir = fs.mkdtempSync( path.join(os.tmpdir(), path.basename(repoUrl)) ); console.log("Cloning", repoUrl, "to", tempDir); const result = spawnSync("git", ["clone", repoUrl, "--depth", "1", tempDir], { encoding: "utf-8", shell: false }); if (result.status !== 0 || result.error) { console.log(result.error); } return tempDir; }; const parseQueryString = (q, body, options = {}) => { if (body && Object.keys(body).length) { options = Object.assign(options, body); } if (q.type) { options.projectType = q.type; } if (q.multiProject && q.multiProject !== "false") { options.multiProject = true; } if (q.requiredOnly && q.requiredOnly !== "false") { options.requiredOnly = true; } if (q.noBabel) { options.noBabel = q.noBabel; } if (q.installDeps) { options.installDeps = q.installDeps; } if (q.project) { options.project = q.project; } if (q.projectName) { options.projectName = q.projectName; } if (q.projectGroup) { options.projectGroup = q.projectGroup; } if (q.projectVersion) { options.projectVersion = q.projectVersion; } return options; }; const configureServer = (cdxgenServer) => { cdxgenServer.headersTimeout = TIMEOUT_MS; cdxgenServer.requestTimeout = TIMEOUT_MS; cdxgenServer.timeout = 0; cdxgenServer.keepAliveTimeout = 0; }; const start = async (options) => { console.log("Listening on", options.serverHost, options.serverPort); const cdxgenServer = http .createServer(app) .listen(options.serverPort, options.serverHost); configureServer(cdxgenServer); app.use("/sbom", async function (req, res) { const q = url.parse(req.url, true).query; let cleanup = false; options = parseQueryString(q, req.body, options); let filePath = q.path || q.url || req.body.path || req.body.url; if (!filePath) { res.writeHead(500, { "Content-Type": "application/json" }); return res.end( "{'error': 'true', 'message': 'path or url is required.'}\n" ); } res.writeHead(200, { "Content-Type": "application/json" }); let srcDir = filePath; if (filePath.startsWith("http") || filePath.startsWith("git")) { srcDir = gitClone(filePath); cleanup = true; } console.log("Generating SBoM for", srcDir); const bomNSData = (await bom.createBom(srcDir, options)) || {}; if (bomNSData.bomJson) { if ( typeof bomNSData.bomJson === "string" || bomNSData.bomJson instanceof String ) { res.write(bomNSData.bomJson); } else { res.write(JSON.stringify(bomNSData.bomJson, null, 2)); } } res.end("\n"); if (cleanup && srcDir && srcDir.startsWith(os.tmpdir()) && fs.rmSync) { console.log(`Cleaning up ${srcDir}`); fs.rmSync(srcDir, { recursive: true, force: true }); } }); }; exports.start = start;