4
4
*--------------------------------------------------------*/
5
5
6
6
import path from 'path' ;
7
+ import fs from 'fs' ;
7
8
import * as vscode from 'vscode' ;
8
9
import { GoExtensionContext } from './context' ;
9
10
import { getBinPath } from './util' ;
@@ -12,6 +13,7 @@ import { toolExecutionEnvironment } from './goEnv';
12
13
import { killProcessTree } from './utils/processUtils' ;
13
14
import * as readline from 'readline' ;
14
15
import { URI } from 'vscode-uri' ;
16
+ import { promisify } from 'util' ;
15
17
16
18
export class VulncheckResultViewProvider implements vscode . CustomTextEditorProvider {
17
19
public static readonly viewType = 'vulncheck.view' ;
@@ -195,26 +197,39 @@ export class VulncheckProvider {
195
197
this . channel . clear ( ) ;
196
198
this . channel . appendLine ( `cd ${ dir } ; gopls vulncheck ${ pattern } ` ) ;
197
199
198
- let result = '' ;
199
200
try {
200
201
const start = new Date ( ) ;
201
202
const vuln = await vulncheck ( goCtx , dir , pattern , this . channel ) ;
202
203
203
204
if ( vuln ) {
205
+ fillAffectedPkgs ( vuln . Vuln ) ;
206
+
204
207
// record run info.
205
208
vuln . Start = start ;
206
209
vuln . Duration = Date . now ( ) - start . getTime ( ) ;
207
210
vuln . Dir = dir ;
208
211
vuln . Pattern = pattern ;
209
- result = vuln . Vuln
210
- ? vuln . Vuln . map ( renderVuln ) . join ( '----------------------\n' )
211
- : 'No known vulnerability found.' ;
212
+
213
+ // write to file and visualize it!
214
+ const fname = path . join ( dir , `vulncheck-${ Date . now ( ) } .vulncheck.json` ) ;
215
+ const writeFile = promisify ( fs . writeFile ) ;
216
+ await writeFile ( fname , JSON . stringify ( vuln ) ) ;
217
+ const uri = URI . file ( fname ) ;
218
+ const viewColumn = vscode . ViewColumn . Beside ;
219
+ vscode . commands . executeCommand (
220
+ 'vscode.openWith' ,
221
+ uri ,
222
+ VulncheckResultViewProvider . viewType ,
223
+ viewColumn
224
+ ) ;
225
+ this . channel . appendLine ( `Vulncheck - result wrote in ${ fname } ` ) ;
226
+ } else {
227
+ this . channel . appendLine ( 'Vulncheck - found no vulnerability' ) ;
212
228
}
213
229
} catch ( e ) {
214
230
vscode . window . showErrorMessage ( `error running vulncheck: ${ e } ` ) ;
215
231
this . channel . appendLine ( `Vulncheck failed: ${ e } ` ) ;
216
232
}
217
- this . channel . appendLine ( result ) ;
218
233
this . channel . show ( ) ;
219
234
}
220
235
@@ -300,48 +315,6 @@ export async function vulncheck(
300
315
return await task ;
301
316
}
302
317
303
- const renderVuln = ( v : Vuln ) => `
304
- ID: ${ v . ID }
305
- Aliases: ${ v . Aliases ?. join ( ', ' ) ?? 'None' }
306
- Symbol: ${ v . Symbol }
307
- Pkg Path: ${ v . PkgPath }
308
- Mod Path: ${ v . ModPath }
309
- URL: ${ v . URL }
310
- Current Version: ${ v . CurrentVersion }
311
- Fixed Version: ${ v . FixedVersion }
312
-
313
- ${ v . Details }
314
- ${ renderStack ( v ) } `;
315
-
316
- const renderStack = ( v : Vuln ) => {
317
- const content = [ ] ;
318
- for ( const stack of v . CallStacks ?? [ ] ) {
319
- for ( const [ , line ] of stack . entries ( ) ) {
320
- content . push ( `\t${ line . Name } ` ) ;
321
- const loc = renderUri ( line ) ;
322
- if ( loc ) {
323
- content . push ( `\t\t${ loc } ` ) ;
324
- }
325
- }
326
- content . push ( '' ) ;
327
- }
328
- return content . join ( '\n' ) ;
329
- } ;
330
-
331
- const renderUri = ( stack : CallStack ) => {
332
- if ( ! stack . URI ) {
333
- // generated file or dummy location may not have a file name.
334
- return '' ;
335
- }
336
- const parsed = vscode . Uri . parse ( stack . URI ) ;
337
- const line = stack . Pos . line + 1 ; // Position uses 0-based line number.
338
- const folder = vscode . workspace . getWorkspaceFolder ( parsed ) ;
339
- if ( folder ) {
340
- return `${ parsed . path } :${ line } :${ stack . Pos . character } ` ;
341
- }
342
- return `${ stack . URI } #${ line } :${ stack . Pos . character } ` ;
343
- } ;
344
-
345
318
interface VulncheckReport {
346
319
// Vulns populated by gopls vulncheck run.
347
320
Vuln ?: Vuln [ ] ;
@@ -366,6 +339,10 @@ interface Vuln {
366
339
FixedVersion : string ;
367
340
CallStacks ?: CallStack [ ] [ ] ;
368
341
CallStacksSummary ?: string [ ] ;
342
+
343
+ // Derived from call stacks.
344
+ // TODO(hyangah): add to gopls vulncheck.
345
+ AffectedPkgs ?: string [ ] ;
369
346
}
370
347
371
348
interface CallStack {
@@ -393,3 +370,33 @@ function safeURIParse(s: string): URI | undefined {
393
370
return undefined ;
394
371
}
395
372
}
373
+
374
+ // Computes the AffectedPkgs attribute if it's not present.
375
+ // Exported for testing.
376
+ // TODO(hyangah): move this logic to gopls vulncheck or govulncheck.
377
+ export function fillAffectedPkgs ( vulns : Vuln [ ] | undefined ) : Vuln [ ] {
378
+ if ( ! vulns ) return [ ] ;
379
+
380
+ const re = new RegExp ( / ^ ( \S + ) \/ ( [ ^ / \s ] + ) $ / ) ;
381
+ vulns . forEach ( ( vuln ) => {
382
+ // If it's already set by gopls vulncheck, great!
383
+ if ( vuln . AffectedPkgs ) return ;
384
+
385
+ const affected = new Set < string > ( ) ;
386
+ vuln . CallStacks ?. forEach ( ( cs ) => {
387
+ if ( ! cs || cs . length === 0 ) {
388
+ return ;
389
+ }
390
+ const name = cs [ 0 ] . Name || '' ;
391
+ const m = name . match ( re ) ;
392
+ if ( ! m ) {
393
+ name && affected . add ( name ) ;
394
+ } else {
395
+ const pkg = m [ 2 ] && m [ 2 ] . split ( '.' ) [ 0 ] ;
396
+ affected . add ( `${ m [ 1 ] } /${ pkg } ` ) ;
397
+ }
398
+ } ) ;
399
+ vuln . AffectedPkgs = Array . from ( affected ) ;
400
+ } ) ;
401
+ return vulns ;
402
+ }
0 commit comments