@@ -2,15 +2,20 @@ use crate::utils::DisplayVec;
22use goose:: goose:: { GooseUser , TransactionError , TransactionResult } ;
33use rand:: Rng ;
44use serde_json:: json;
5- use std:: sync:: {
6- Arc ,
7- atomic:: { AtomicUsize , Ordering } ,
5+ use std:: {
6+ collections:: HashMap ,
7+ io:: Read ,
8+ sync:: {
9+ Arc ,
10+ atomic:: { AtomicUsize , Ordering } ,
11+ } ,
812} ;
9- use tokio:: sync:: OnceCell ;
13+ use tokio:: sync:: { OnceCell , RwLock } ;
1014use urlencoding:: encode;
15+ use xz2:: read:: XzDecoder ;
1116
12- /// Cache for base advisory content with file path
13- static BASE_ADVISORY_CONTENT : OnceCell < ( String , Vec < u8 > ) > = OnceCell :: const_new ( ) ;
17+ /// Thread-safe cache for multiple advisory files using async-aware RwLock
18+ static FILE_CACHE : OnceCell < RwLock < HashMap < String , Vec < u8 > > > > = OnceCell :: const_new ( ) ;
1419
1520/// Generate a new advisory with modified ID
1621fn generate_advisory_content ( base_content : & [ u8 ] ) -> Result < Vec < u8 > , Box < TransactionError > > {
@@ -42,32 +47,65 @@ fn generate_advisory_content(base_content: &[u8]) -> Result<Vec<u8>, Box<Transac
4247 } )
4348}
4449
45- /// Get base advisory content from file path - cached and initialized on first access
46- async fn get_base_advisory_content ( file_path : & str ) -> Result < & ' static Vec < u8 > , String > {
47- let result = BASE_ADVISORY_CONTENT
48- . get_or_init ( || async {
49- match tokio:: fs:: read ( file_path) . await {
50- Ok ( content) => ( file_path. to_string ( ) , content) ,
51- Err ( e) => {
52- panic ! ( "Failed to load base advisory file {}: {}" , file_path, e) ;
53- }
54- }
55- } )
50+ /// Get base upload content from file path - cached and initialized on first access
51+ async fn get_base_upload_content ( file_path : & str ) -> Result < Vec < u8 > , String > {
52+ // Initialize cache if not already done
53+ let cache = FILE_CACHE
54+ . get_or_init ( || async { RwLock :: new ( HashMap :: new ( ) ) } )
5655 . await ;
5756
58- let ( cached_path, content) = result;
57+ // Check if the file is already cached
58+ {
59+ let cache_read = cache. read ( ) . await ;
60+ if let Some ( content) = cache_read. get ( file_path) {
61+ return Ok ( content. clone ( ) ) ;
62+ }
63+ }
64+
65+ // Load the file content
66+ let content = read_upload_file ( file_path)
67+ . await
68+ . map_err ( |e| format ! ( "Failed to load base advisory file {}: {}" , file_path, e) ) ?;
5969
60- // Verify that the cached path matches the requested path
61- if cached_path != file_path {
62- return Err ( format ! (
63- "Base advisory file path mismatch: expected {}, got {}" ,
64- file_path, cached_path
65- ) ) ;
70+ // Insert into cache
71+ {
72+ let mut cache_write = cache. write ( ) . await ;
73+ cache_write. insert ( file_path. to_string ( ) , content. clone ( ) ) ;
6674 }
6775
6876 Ok ( content)
6977}
7078
79+ /// Read upload file, handling both regular JSON and XZ compressed files
80+ async fn read_upload_file ( file_path : & str ) -> Result < Vec < u8 > , String > {
81+ let file_bytes = tokio:: fs:: read ( file_path)
82+ . await
83+ . map_err ( |e| format ! ( "Failed to read file {}: {}" , file_path, e) ) ?;
84+
85+ // Check if file is XZ compressed by looking at the magic bytes
86+ if file_path. ends_with ( ".xz" ) || is_xz_compressed ( & file_bytes) {
87+ decompress_xz ( & file_bytes)
88+ } else {
89+ Ok ( file_bytes)
90+ }
91+ }
92+
93+ /// Check if data is XZ compressed by examining magic bytes
94+ fn is_xz_compressed ( data : & [ u8 ] ) -> bool {
95+ data. len ( ) >= 6 && data[ 0 ..6 ] == [ 0xFD , 0x37 , 0x7A , 0x58 , 0x5A , 0x00 ]
96+ }
97+
98+ /// Decompress XZ compressed data
99+ fn decompress_xz ( compressed_data : & [ u8 ] ) -> Result < Vec < u8 > , String > {
100+ let mut decoder = XzDecoder :: new ( compressed_data) ;
101+ let mut decompressed = Vec :: new ( ) ;
102+
103+ decoder
104+ . read_to_end ( & mut decompressed)
105+ . map_err ( |e| format ! ( "Failed to decompress XZ data: {}" , e) ) ?;
106+ Ok ( decompressed)
107+ }
108+
71109/// Upload advisory data and extract advisory ID from response
72110async fn upload_advisory_and_extract_id (
73111 file_bytes : Vec < u8 > ,
@@ -120,16 +158,16 @@ pub async fn list_advisory_labels(user: &mut GooseUser) -> TransactionResult {
120158
121159/// Upload file and return advisory ID
122160pub async fn upload_advisory_and_get_id (
123- advisory_file : String ,
161+ advisory_file : & str ,
124162 user : & mut GooseUser ,
125163) -> Result < String , Box < TransactionError > > {
126164 // Check cache first - only cache the base template
127- let base_content = get_base_advisory_content ( & advisory_file)
165+ let base_content = get_base_upload_content ( advisory_file)
128166 . await
129167 . map_err ( |e| Box :: new ( TransactionError :: Custom ( e) ) ) ?;
130168
131169 // Generate new content with modified ID
132- let generated_content = generate_advisory_content ( base_content) ?;
170+ let generated_content = generate_advisory_content ( & base_content) ?;
133171
134172 // Upload the generated content and extract advisory ID
135173 upload_advisory_and_extract_id ( generated_content, user) . await
@@ -148,7 +186,7 @@ pub async fn upload_and_immediately_delete(
148186 user : & mut GooseUser ,
149187) -> TransactionResult {
150188 // 1. Upload file and get ID
151- let advisory_id = upload_advisory_and_get_id ( advisory_file. clone ( ) , user) . await ?;
189+ let advisory_id = upload_advisory_and_get_id ( & advisory_file, user) . await ?;
152190
153191 // 2. Immediately delete (no waiting required)
154192 delete_advisory_by_id ( advisory_id, user) . await ?;
0 commit comments