| /*--------------------------------------------------------- | |
| * Copyright 2022 The Go Authors. All rights reserved. | |
| * Licensed under the MIT License. See LICENSE in the project root for license information. | |
| *--------------------------------------------------------*/ | |
| import vscode = require('vscode'); | |
| import cp = require('child_process'); | |
| import { URI } from 'vscode-uri'; | |
| import { getGoConfig } from './config'; | |
| import { getWorkspaceFolderPath } from './util'; | |
| import { IProgressTerminal } from './progressTerminal'; | |
| // VulncheckReport is the JSON data type of gopls's vulncheck result. | |
| export interface VulncheckReport { | |
| Entries?: { [key: string]: unknown }; // map: osv.ID -> osv.Entry (we don't need to know the Entry shape) | |
| Findings?: unknown[]; // []Finding. We don't need to know the exact Fingings shape either. | |
| Mode?: 'govulncheck' | 'imports'; | |
| // Legacy: Vulns populated by gopls vulncheck run. | |
| Vulns?: unknown; | |
| } | |
| export async function writeVulns( | |
| res: VulncheckReport, | |
| term: IProgressTerminal | undefined, | |
| goplsBinPath: string | |
| ): Promise<void> { | |
| if (term === undefined) { | |
| return; | |
| } | |
| term.appendLine(''); | |
| let stdout = ''; | |
| let stderr = ''; | |
| const pr = new Promise<number | null>((resolve) => { | |
| const p = cp.spawn(goplsBinPath, ['vulncheck', '--', '-mode=convert', '-show=color'], { | |
| cwd: getWorkspaceFolderPath() | |
| }); | |
| p.stdout.on('data', (data) => { | |
| stdout += data; | |
| }); | |
| p.stderr.on('data', (data) => { | |
| stderr += data; | |
| }); | |
| // 'close' fires after exit or error when the subprocess closes all stdio. | |
| p.on('close', (exitCode) => { | |
| // When vulnerabilities are found, vulncheck -mode=convert returns a non-zero exit code. | |
| // TODO: can we use the exitCode to set the status of terminal? | |
| resolve(exitCode); | |
| }); | |
| // vulncheck -mode=convert expects a stream of osv.Entry and govulncheck Finding json objects. | |
| if (res.Entries) { | |
| Object.values(res.Entries).forEach((osv) => { | |
| const we = { osv: osv }; | |
| p.stdin.write(`${JSON.stringify(we)}`); | |
| }); | |
| } | |
| if (res.Findings) { | |
| Object.values(res.Findings).forEach((finding) => { | |
| const we = { finding: finding }; | |
| p.stdin.write(`${JSON.stringify(we)}`); | |
| }); | |
| } | |
| p.stdin.end(); | |
| }); | |
| try { | |
| await pr; | |
| } catch (e) { | |
| console.error(`writeVulns: ${e}`); | |
| } finally { | |
| // Combining stderr and stdout streams in the exact order they were received | |
| // is tricky since they are buffered separately. | |
| // Normally, govulncheck will print the text-based report to stdout first | |
| // and then report whether there are vulnerabilities to stderr at the end. | |
| // So, we just process stdout first and then stderr. | |
| stdout.split('\n').forEach((l) => term.appendLine(l)); | |
| stderr.split('\n').forEach((l) => term.appendLine(l)); | |
| } | |
| return; | |
| } | |
| export const toggleVulncheckCommandFactory = () => () => { | |
| const editor = vscode.window.activeTextEditor; | |
| const documentUri = editor?.document.uri; | |
| toggleVulncheckCommand(documentUri); | |
| }; | |
| function toggleVulncheckCommand(uri?: URI) { | |
| const goCfgName = 'diagnostic.vulncheck'; | |
| const cfg = getGoConfig(uri); | |
| const { globalValue, workspaceValue, workspaceFolderValue } = cfg.inspect(goCfgName) || {}; | |
| if (workspaceFolderValue) { | |
| const newValue = workspaceFolderValue === 'Imports' ? 'Off' : 'Imports'; | |
| cfg.update(goCfgName, newValue); | |
| return; | |
| } | |
| if (workspaceValue) { | |
| const newValue = workspaceValue === 'Imports' ? 'Off' : 'Imports'; | |
| cfg.update(goCfgName, newValue, false); | |
| return; | |
| } | |
| if (globalValue) { | |
| const newValue = globalValue === 'Imports' ? 'Off' : 'Imports'; | |
| cfg.update(goCfgName, newValue, true); | |
| return; | |
| } | |
| cfg.update(goCfgName, 'Imports'); | |
| } |