55import logging
66import os
77import shutil
8+ import types
89
910import orjson
1011from rich .console import Console
11- from rich .syntax import Syntax
12+ from rich .live import Live
13+ from rich .markdown import Markdown
1214from rich .table import Table
1315
1416from vdb .lib import config , db6 as db_lib , search
1517from vdb .lib .aqua import AquaSource
18+ from vdb .lib .cve_model import CVE
1619from vdb .lib .gha import GitHubSource
1720from vdb .lib .osv import OSVSource
1821
@@ -85,27 +88,62 @@ def build_args():
8588 parser .add_argument (
8689 "--search" ,
8790 dest = "search" ,
88- help = "Search for the package or CVE ID in the database. Use purl, cpe, or colon-separated values." ,
91+ help = "Search for the package or CVE ID in the database. Use purl, cpe, or git http url." ,
92+ )
93+ parser .add_argument (
94+ "--bom" ,
95+ dest = "bom_file" ,
96+ help = "Search for packages in the CycloneDX BOM file." ,
8997 )
9098 return parser .parse_args ()
9199
92100
101+ def add_table_row (table : Table , res : dict , added_row_keys : dict ):
102+ # matched_by is the purl or cpe string
103+ row_key = f"""{ res ["matched_by" ]} |res.get("source_data_hash")"""
104+ # Filter duplicate rows from getting printed
105+ if added_row_keys .get (row_key ):
106+ return
107+ source_data : CVE = res .get ("source_data" )
108+ description = ""
109+ if (
110+ source_data .root .containers .cna
111+ and source_data .root .containers .cna .descriptions
112+ and source_data .root .containers .cna .descriptions .root
113+ ):
114+ description = (
115+ source_data .root .containers .cna .descriptions .root [0 ]
116+ .value .replace ("\\ n" , "\n " )
117+ .replace ("\\ t" , " " )
118+ )
119+ table .add_row (
120+ res .get ("cve_id" ),
121+ res .get ("matched_by" ),
122+ Markdown (description , justify = "left" , hyperlinks = True ),
123+ )
124+ added_row_keys [row_key ] = True
125+
126+
93127def print_results (results ):
94- table = Table (title = "VDB Results" )
128+ added_row_keys = {}
129+ table = Table (title = "VDB Results" , show_lines = True )
95130 table .add_column ("CVE" , justify = "left" )
96- table .add_column ("Type" )
97- table .add_column ("Namespace" )
98- table .add_column ("Name" )
99- table .add_column ("Hash" )
100- table .add_column ("Source Data" )
101- for res in results :
102- table .add_row (res .get ("cve_id" ), res .get ("type" ),
103- res .get ("namespace" , "" ), res .get ("name" ),
104- res .get ("source_data_hash" ),
105- Syntax (orjson .dumps (
106- res .get ("source_data" ).model_dump (mode = "json" , exclude_none = True ),
107- option = orjson .OPT_INDENT_2 | orjson .OPT_APPEND_NEWLINE ).decode ("utf-8" , errors = "ignore" ), "json" , word_wrap = True ))
108- console .print (table )
131+ table .add_column ("Locator" )
132+ table .add_column ("Description" )
133+ if isinstance (results , types .GeneratorType ):
134+ with Live (
135+ table , console = console , refresh_per_second = 4 , vertical_overflow = "visible"
136+ ):
137+ for result_gen in results :
138+ if isinstance (result_gen , dict ):
139+ add_table_row (table , result_gen , added_row_keys )
140+ if isinstance (result_gen , types .GeneratorType ):
141+ for res in result_gen :
142+ add_table_row (table , res , added_row_keys )
143+ elif isinstance (results , list ):
144+ for res in results :
145+ add_table_row (table , res )
146+ console .print (table )
109147
110148
111149def main ():
@@ -133,12 +171,18 @@ def main():
133171 LOG .info ("Refreshing %s" , s .__class__ .__name__ )
134172 s .refresh ()
135173 cve_data_count , cve_index_count = db_lib .stats ()
136- console .print ("cve_data_count" , cve_data_count , "cve_index_count" , cve_index_count )
174+ console .print (
175+ "cve_data_count" , cve_data_count , "cve_index_count" , cve_index_count
176+ )
137177 db_lib .optimize_and_close_all ()
138178 if args .search :
139179 if args .search .startswith ("pkg:" ):
140180 results = search .search_by_purl_like (args .search , with_data = True )
141- elif args .search .startswith ("CVE-" ) or args .search .startswith ("GHSA-" ) or args .search .startswith ("MAL-" ):
181+ elif (
182+ args .search .startswith ("CVE-" )
183+ or args .search .startswith ("GHSA-" )
184+ or args .search .startswith ("MAL-" )
185+ ):
142186 results = search .search_by_cve (args .search , with_data = True )
143187 elif args .search .startswith ("http" ):
144188 results = search .search_by_url (args .search , with_data = True )
@@ -148,6 +192,10 @@ def main():
148192 print_results (results )
149193 else :
150194 console .print ("No results found!" )
195+ elif args .bom_file :
196+ if os .path .exists (args .bom_file ):
197+ results_generator = search .search_by_cdx_bom (args .bom_file , with_data = True )
198+ print_results (results_generator )
151199
152200
153201if __name__ == "__main__" :
0 commit comments