@@ -3,7 +3,8 @@ use crate::utils::get_sanitize_filename;
33use actix_web:: body:: BoxBody ;
44use actix_web:: http:: header:: ContentType ;
55use actix_web:: { HttpResponse , ResponseError } ;
6- use csv:: WriterBuilder ;
6+ use core:: time:: Duration ;
7+ use csv:: { Writer , WriterBuilder } ;
78use flate2:: write:: GzEncoder ;
89use flate2:: Compression ;
910use http:: StatusCode ;
@@ -12,6 +13,8 @@ use trustification_common::error::ErrorInformation;
1213
1314extern crate sanitize_filename;
1415
16+ type CSVs = ( Writer < Vec < u8 > > , Writer < Vec < u8 > > ) ;
17+
1518pub struct LicenseExporter {
1619 sbom_license : SbomLicense ,
1720 extracted_licensing_infos : Vec < ExtractedLicensingInfos > ,
@@ -71,6 +74,65 @@ impl LicenseExporter {
7174 }
7275
7376 pub fn generate ( & self ) -> Result < Vec < u8 > , LicenseExporterError > {
77+ let ( wtr_sbom, wtr_license_ref) = self . generate_csvs ( ) ?;
78+
79+ let sbom_csv = wtr_sbom
80+ . into_inner ( )
81+ . map_err ( |err| LicenseExporterError :: CsvIntoInnerError ( format ! ( "csv into inner error: {}" , err) ) ) ?;
82+ let license_ref_csv = wtr_license_ref
83+ . into_inner ( )
84+ . map_err ( |err| LicenseExporterError :: CsvIntoInnerError ( format ! ( "csv into inner error: {}" , err) ) ) ?;
85+
86+ let mut compressed_data = Vec :: new ( ) ;
87+ {
88+ let encoder = GzEncoder :: new ( & mut compressed_data, Compression :: default ( ) ) ;
89+
90+ let mut archive = Builder :: new ( encoder) ;
91+
92+ let mut header = tar:: Header :: new_gnu ( ) ;
93+ header. set_size ( sbom_csv. len ( ) as u64 ) ;
94+ header. set_mode ( 0o644 ) ;
95+ header. set_cksum ( ) ;
96+ header. set_mtime (
97+ std:: time:: UNIX_EPOCH
98+ . elapsed ( )
99+ . unwrap_or ( Duration :: from_secs ( 0 ) )
100+ . as_secs ( ) ,
101+ ) ;
102+ archive. append_data (
103+ & mut header,
104+ format ! (
105+ "{}_sbom_licenses.csv" ,
106+ & get_sanitize_filename( String :: from( & self . sbom_license. sbom_name) )
107+ ) ,
108+ & * sbom_csv,
109+ ) ?;
110+
111+ let mut header = tar:: Header :: new_gnu ( ) ;
112+ header. set_size ( license_ref_csv. len ( ) as u64 ) ;
113+ header. set_mode ( 0o644 ) ;
114+ header. set_cksum ( ) ;
115+ header. set_mtime (
116+ std:: time:: UNIX_EPOCH
117+ . elapsed ( )
118+ . unwrap_or ( Duration :: from_secs ( 0 ) )
119+ . as_secs ( ) ,
120+ ) ;
121+ archive. append_data (
122+ & mut header,
123+ format ! (
124+ "{}_license_ref.csv" ,
125+ & get_sanitize_filename( String :: from( & self . sbom_license. sbom_name) )
126+ ) ,
127+ & * license_ref_csv,
128+ ) ?;
129+
130+ archive. finish ( ) ?;
131+ }
132+ Ok ( compressed_data)
133+ }
134+
135+ fn generate_csvs ( & self ) -> Result < CSVs , LicenseExporterError > {
74136 let mut wtr_sbom = WriterBuilder :: new ( )
75137 . delimiter ( b'\t' )
76138 . quote_style ( csv:: QuoteStyle :: Always )
@@ -104,76 +166,35 @@ impl LicenseExporter {
104166 ] ) ?;
105167 }
106168
107- for pl in & self . sbom_license . packages {
108- let alternate_package_reference = pl
169+ for package in & self . sbom_license . packages {
170+ let alternate_package_reference = package
109171 . other_reference
110172 . iter ( )
111173 . map ( |reference| reference. as_str ( ) )
112174 . collect :: < Vec < _ > > ( )
113175 . join ( "\n " ) ;
114176
115- let spdx_licenses = pl
177+ let spdx_licenses = package
116178 . spdx_licenses
117179 . iter ( )
118180 . map ( |reference| reference. as_str ( ) )
119181 . collect :: < Vec < _ > > ( )
120182 . join ( "\n " ) ;
121183
122184 wtr_sbom. write_record ( [
123- & pl. name ,
185+ // &package.name,
186+ & self . sbom_license . sbom_name ,
124187 & self . sbom_license . sbom_namespace ,
125188 & self . sbom_license . component_group ,
126189 & self . sbom_license . component_version ,
127- & pl . purl ,
190+ & package . purl ,
128191 & spdx_licenses,
129- & pl . license_name ,
130- & pl . license_text ,
192+ & package . license_name ,
193+ & package . license_text ,
131194 alternate_package_reference. as_str ( ) ,
132195 ] ) ?;
133196 }
134-
135- let sbom_csv = wtr_sbom
136- . into_inner ( )
137- . map_err ( |err| LicenseExporterError :: CsvIntoInnerError ( format ! ( "csv into inner error: {}" , err) ) ) ?;
138- let license_ref_csv = wtr_license_ref
139- . into_inner ( )
140- . map_err ( |err| LicenseExporterError :: CsvIntoInnerError ( format ! ( "csv into inner error: {}" , err) ) ) ?;
141-
142- let mut compressed_data = Vec :: new ( ) ;
143- {
144- let encoder = GzEncoder :: new ( & mut compressed_data, Compression :: default ( ) ) ;
145-
146- let mut archive = Builder :: new ( encoder) ;
147-
148- let mut header = tar:: Header :: new_gnu ( ) ;
149- header. set_size ( sbom_csv. len ( ) as u64 ) ;
150- header. set_mode ( 0o644 ) ;
151- header. set_cksum ( ) ;
152- archive. append_data (
153- & mut header,
154- format ! (
155- "{}_sbom_licenses.csv" ,
156- & get_sanitize_filename( String :: from( & self . sbom_license. sbom_name) )
157- ) ,
158- & * sbom_csv,
159- ) ?;
160-
161- let mut header = tar:: Header :: new_gnu ( ) ;
162- header. set_size ( license_ref_csv. len ( ) as u64 ) ;
163- header. set_mode ( 0o644 ) ;
164- header. set_cksum ( ) ;
165- archive. append_data (
166- & mut header,
167- format ! (
168- "{}_license_ref.csv" ,
169- & get_sanitize_filename( String :: from( & self . sbom_license. sbom_name) )
170- ) ,
171- & * license_ref_csv,
172- ) ?;
173-
174- archive. finish ( ) ?;
175- }
176- Ok ( compressed_data)
197+ Ok ( ( wtr_sbom, wtr_license_ref) )
177198 }
178199}
179200
@@ -200,6 +221,81 @@ mod tests {
200221 assert ! ( !result. contains( '/' ) ) ;
201222 }
202223
224+ #[ tokio:: test]
225+ async fn test_generate_csvs_cyclonedx ( ) {
226+ let sbom =
227+ load_sbom_file ( "../test-data/application.cdx.json" ) . unwrap_or_else ( |_| panic ! ( "failed to parse test data" ) ) ;
228+
229+ let license_scanner = LicenseScanner :: new ( sbom) ;
230+
231+ let ( sbom_licenses, extracted_licensing_info) = license_scanner
232+ . scanner ( )
233+ . unwrap_or_else ( |_| panic ! ( "failed to parse test data" ) ) ;
234+
235+ let export = LicenseExporter :: new ( sbom_licenses, extracted_licensing_info) ;
236+ let ( writer_sbom_licenses, _license_ref) = export. generate_csvs ( ) . unwrap ( ) ;
237+ let sbom_licenses = String :: from_utf8 ( writer_sbom_licenses. into_inner ( ) . unwrap ( ) ) . unwrap ( ) ;
238+ // https://issues.redhat.com/browse/TC-2212
239+ assert_eq ! ( 97 , sbom_licenses. matches( "spring-petclinic" ) . count( ) ) ;
240+ assert_eq ! ( 97 , sbom_licenses. matches( "org.springframework.samples" ) . count( ) ) ;
241+ assert_eq ! ( 97 , sbom_licenses. matches( "3.3.0-SNAPSHOT" ) . count( ) ) ;
242+ assert_eq ! ( 96 , sbom_licenses. matches( "pkg:maven/" ) . count( ) ) ;
243+ // check some PURLs appear multiple times because they have multiple licenses
244+ assert_eq ! (
245+ 2 ,
246+ sbom_licenses
247+ . matches
( "pkg:maven/ch.qos.logback/[email protected] ?type=jar" ) 248+ . count( )
249+ ) ;
250+ assert_eq ! (
251+ 2 ,
252+ sbom_licenses
253+ . matches
( "pkg:maven/ch.qos.logback/[email protected] ?type=jar" ) 254+ . count( )
255+ ) ;
256+ assert_eq ! (
257+ 2 ,
258+ sbom_licenses
259+ . matches
( "pkg:maven/jakarta.annotation/[email protected] ?type=jar" ) 260+ . count( )
261+ ) ;
262+ assert_eq ! (
263+ 2 ,
264+ sbom_licenses
265+ . matches
( "pkg:maven/org.hdrhistogram/[email protected] ?type=jar" ) 266+ . count( )
267+ ) ;
268+ assert_eq ! ( 63 , sbom_licenses. matches( "Apache-2.0" ) . count( ) ) ;
269+ }
270+
271+ #[ tokio:: test]
272+ async fn test_generate_csvs_spdx ( ) {
273+ let sbom = load_sbom_file ( "../test-data/mtv-2.6.json" ) . unwrap_or_else ( |_| panic ! ( "failed to parse test data" ) ) ;
274+
275+ let license_scanner = LicenseScanner :: new ( sbom) ;
276+
277+ let ( sbom_licenses, extracted_licensing_info) = license_scanner
278+ . scanner ( )
279+ . unwrap_or_else ( |_| panic ! ( "failed to parse test data" ) ) ;
280+
281+ let export = LicenseExporter :: new ( sbom_licenses, extracted_licensing_info) ;
282+ let ( writer_sbom_licenses, _license_ref) = export. generate_csvs ( ) . unwrap ( ) ;
283+ let sbom_licenses = String :: from_utf8 ( writer_sbom_licenses. into_inner ( ) . unwrap ( ) ) . unwrap ( ) ;
284+ // https://issues.redhat.com/browse/TC-2212
285+ assert_eq ! ( 10776 , sbom_licenses. matches( "MTV-2.6" ) . count( ) ) ;
286+ assert_eq ! (
287+ 5388 ,
288+ sbom_licenses
289+ . matches( "https://access.redhat.com/security/data/sbom/spdx/MTV-2.6" )
290+ . count( )
291+ ) ;
292+ assert_eq ! ( 28 , sbom_licenses. matches( "pkg:oci/" ) . count( ) ) ;
293+ assert_eq ! ( 1976 , sbom_licenses. matches( "pkg:npm/" ) . count( ) ) ;
294+ assert_eq ! ( 2185 , sbom_licenses. matches( "pkg:golang/" ) . count( ) ) ;
295+ assert_eq ! ( 1191 , sbom_licenses. matches( "pkg:rpm/" ) . count( ) ) ;
296+ assert_eq ! ( 4664 , sbom_licenses. matches( "NOASSERTION" ) . count( ) ) ;
297+ }
298+
203299 #[ tokio:: test]
204300 async fn is_works_cydx ( ) {
205301 let sbom =
0 commit comments