11use cre_wasm_exports:: { __clear_registry, extend_wasm_exports} ;
22use javy_plugin_api:: {
3- import_namespace,
3+ Config , import_namespace,
44 javy:: { Runtime , quickjs:: prelude:: * } ,
5- Config ,
65} ;
76
7+ use base64:: Engine ;
88use javy_plugin_api:: javy:: quickjs:: {
99 ArrayBuffer , Ctx , Error , Exception , FromJs , TypedArray , Value ,
1010} ;
11+ use rand:: { Rng , SeedableRng } ;
12+ use rand_chacha:: ChaCha8Rng ;
1113use std:: collections:: HashMap ;
1214use std:: env;
1315use std:: sync:: { Mutex , OnceLock } ;
14- use base64:: Engine ;
15- use rand:: { Rng , SeedableRng } ;
16- use rand_chacha:: ChaCha8Rng ;
1716
1817static CURRENT_MODE : Mutex < i32 > = Mutex :: new ( 0 ) ;
1918static RANDOM_GENERATORS : OnceLock < Mutex < HashMap < i32 , ChaCha8Rng > > > = OnceLock :: new ( ) ;
19+ const MAX_RESPONSE_LEN_BYTES : i32 = 64 * 1024 * 1024 ;
2020
2121// ✅ Host imports: implemented in Go
2222#[ link( wasm_import_module = "env" ) ]
@@ -96,8 +96,63 @@ pub fn config() -> Config {
9696 config
9797}
9898
99- /// Applies CRE plugin globals and host bindings. Used by the default plugin build and by generated host crates that add `--cre-exports` extensions.
100- ///
99+ fn validate_max_response_len ( max_len : i32 ) -> Result < usize , & ' static str > {
100+ if max_len < 0 {
101+ return Err ( "maxLen < 0" ) ;
102+ }
103+
104+ if max_len > MAX_RESPONSE_LEN_BYTES {
105+ return Err ( "maxLen exceeds maximum allowed response size" ) ;
106+ }
107+
108+ Ok ( max_len as usize )
109+ }
110+
111+ fn checked_response_buffer_len ( ctx : & Ctx < ' _ > , max_len : i32 ) -> Result < usize , Error > {
112+ validate_max_response_len ( max_len) . map_err ( |message| Exception :: throw_range ( ctx, message) )
113+ }
114+
115+ /// Invokes a host fn with shape `(req_ptr, req_len, buf_ptr, max_len) -> i64`,
116+ /// validates `max_len`, returns the populated response slice or maps host errors
117+ /// to JS exceptions. `op_name` appears in the empty-error fallback; `capacity_msg`
118+ /// is static because `Error::new_into_js` requires `&'static str`.
119+ fn dispatch_host_call (
120+ ctx : & Ctx < ' _ > ,
121+ req : ArgBytes ,
122+ max_len : i32 ,
123+ op_name : & str ,
124+ capacity_msg : & ' static str ,
125+ host_fn : unsafe extern "C" fn ( * const u8 , i32 , * mut u8 , i32 ) -> i64 ,
126+ ) -> Result < Vec < u8 > , Error > {
127+ let max_len_usize = checked_response_buffer_len ( ctx, max_len) ?;
128+ let req_bytes = req. 0 ;
129+ let mut buf = vec ! [ 0u8 ; max_len_usize] ;
130+
131+ let n = unsafe {
132+ host_fn (
133+ req_bytes. as_ptr ( ) ,
134+ req_bytes. len ( ) as i32 ,
135+ buf. as_mut_ptr ( ) ,
136+ max_len,
137+ )
138+ } ;
139+ if n < 0 {
140+ let error_len = ( -n) as usize ;
141+ let error_msg =
142+ String :: from_utf8_lossy ( & buf[ ..error_len. min ( max_len_usize) ] ) . into_owned ( ) ;
143+ let error_msg = if error_msg. is_empty ( ) {
144+ format ! ( "{op_name} failed" )
145+ } else {
146+ error_msg
147+ } ;
148+ return Err ( Exception :: throw_message ( ctx, & error_msg) ) ;
149+ }
150+ if n > max_len_usize as i64 {
151+ return Err ( Error :: new_into_js ( "Error" , capacity_msg) ) ;
152+ }
153+ Ok ( buf[ ..n as usize ] . to_vec ( ) )
154+ }
155+
101156/// Duplicate export names are caught eagerly by `extend_wasm_exports`.
102157pub fn modify_runtime ( runtime : Runtime ) -> Runtime {
103158 __clear_registry ( ) ;
@@ -118,118 +173,44 @@ pub fn modify_runtime(runtime: Runtime) -> Runtime {
118173 & ctx,
119174 "awaitCapabilities" ,
120175 Func :: from ( |ctx : Ctx < ' _ > , req : ArgBytes , max_len : i32 | {
121- if max_len < 0 {
122- return Err ( Exception :: throw_range ( & ctx, "maxLen < 0" ) ) ;
123- }
124- let req_bytes = req. 0 ;
125- let mut buf = vec ! [ 0u8 ; max_len as usize ] ;
126-
127- let n = unsafe {
128- await_capabilities (
129- req_bytes. as_ptr ( ) ,
130- req_bytes. len ( ) as i32 ,
131- buf. as_mut_ptr ( ) ,
132- max_len,
133- )
134- } ;
135- if n < 0 {
136- let error_len = ( -n) as usize ;
137- let error_msg =
138- String :: from_utf8_lossy ( & buf[ ..error_len. min ( max_len as usize ) ] ) . into_owned ( ) ;
139- let error_msg_static: & ' static str = Box :: leak ( error_msg. into_boxed_str ( ) ) ;
140- return Err ( Error :: new_into_js ( "Error" , error_msg_static) ) ;
141- }
142- if n > max_len as i64 {
143- return Err ( Error :: new_into_js (
144- "Error" ,
145- "await_capabilities: host returned length exceeding buffer capacity" ,
146- ) ) ;
147- }
148-
149- let out = & buf[ ..n as usize ] ;
150- Ok :: < Vec < u8 > , Error > ( out. to_vec ( ) )
176+ dispatch_host_call (
177+ & ctx,
178+ req,
179+ max_len,
180+ "await_capabilities" ,
181+ "await_capabilities: host returned length exceeding buffer capacity" ,
182+ await_capabilities,
183+ )
151184 } ) ,
152185 ) ;
153186
154187 extend_wasm_exports (
155188 & ctx,
156189 "getSecrets" ,
157190 Func :: from ( |ctx : Ctx < ' _ > , req : ArgBytes , max_len : i32 | {
158- if max_len < 0 {
159- return Err ( Exception :: throw_range ( & ctx, "maxLen < 0" ) ) ;
160- }
161- let req_bytes = req. 0 ;
162- let mut buf = vec ! [ 0u8 ; max_len as usize ] ;
163-
164- let n = unsafe {
165- get_secrets (
166- req_bytes. as_ptr ( ) ,
167- req_bytes. len ( ) as i32 ,
168- buf. as_mut_ptr ( ) ,
169- max_len,
170- )
171- } ;
172- if n < 0 {
173- let error_len = ( -n) as usize ;
174- let error_msg =
175- String :: from_utf8_lossy ( & buf[ ..error_len. min ( max_len as usize ) ] ) . into_owned ( ) ;
176- let error_msg = if error_msg. is_empty ( ) {
177- "get_secrets failed" . to_string ( )
178- } else {
179- error_msg
180- } ;
181- return Err ( Exception :: throw_message ( & ctx, & error_msg) ) ;
182- }
183- if n > max_len as i64 {
184- return Err ( Error :: new_into_js (
185- "Error" ,
186- "get_secrets: host returned length exceeding buffer capacity" ,
187- ) ) ;
188- }
189-
190- let out = & buf[ ..n as usize ] ;
191- Ok :: < Vec < u8 > , Error > ( out. to_vec ( ) )
191+ dispatch_host_call (
192+ & ctx,
193+ req,
194+ max_len,
195+ "get_secrets" ,
196+ "get_secrets: host returned length exceeding buffer capacity" ,
197+ get_secrets,
198+ )
192199 } ) ,
193200 ) ;
194201
195202 extend_wasm_exports (
196203 & ctx,
197204 "awaitSecrets" ,
198205 Func :: from ( |ctx : Ctx < ' _ > , req : ArgBytes , max_len : i32 | {
199- if max_len < 0 {
200- return Err ( Exception :: throw_range ( & ctx, "maxLen < 0" ) ) ;
201- }
202- let req_bytes = req. 0 ;
203- let mut buf = vec ! [ 0u8 ; max_len as usize ] ;
204-
205- let n = unsafe {
206- await_secrets (
207- req_bytes. as_ptr ( ) ,
208- req_bytes. len ( ) as i32 ,
209- buf. as_mut_ptr ( ) ,
210- max_len,
211- )
212- } ;
213- if n < 0 {
214- let error_len = ( -n) as usize ;
215- let error_msg =
216- String :: from_utf8_lossy ( & buf[ ..error_len. min ( max_len as usize ) ] ) . into_owned ( ) ;
217- let error_msg = if error_msg. is_empty ( ) {
218- "await_secrets failed" . to_string ( )
219- } else {
220- error_msg
221- } ;
222- return Err ( Exception :: throw_message ( & ctx, & error_msg) ) ;
223- }
224- if n > max_len as i64 {
225- return Err ( Error :: new_into_js (
226- "Error" ,
227- "await_secrets: host returned length exceeding buffer capacity" ,
228- ) ) ;
229- }
230-
231- let out = & buf[ ..n as usize ] ;
232- Ok :: < Vec < u8 > , Error > ( out. to_vec ( ) )
206+ dispatch_host_call (
207+ & ctx,
208+ req,
209+ max_len,
210+ "await_secrets" ,
211+ "await_secrets: host returned length exceeding buffer capacity" ,
212+ await_secrets,
213+ )
233214 } ) ,
234215 ) ;
235216
@@ -312,9 +293,8 @@ pub fn modify_runtime(runtime: Runtime) -> Runtime {
312293 "getWasiArgs" ,
313294 Func :: from ( |_ctx : Ctx < ' _ > | -> Result < String , Error > {
314295 let args: Vec < String > = env:: args ( ) . collect ( ) ;
315- let args_json = serde_json:: to_string ( & args) . map_err ( |_| {
316- Error :: new_into_js ( "Error" , "Failed to serialize args to JSON" )
317- } ) ?;
296+ let args_json = serde_json:: to_string ( & args)
297+ . map_err ( |_| Error :: new_into_js ( "Error" , "Failed to serialize args to JSON" ) ) ?;
318298 Ok ( args_json)
319299 } ) ,
320300 ) ;
@@ -346,3 +326,30 @@ pub fn modify_runtime(runtime: Runtime) -> Runtime {
346326
347327 runtime
348328}
329+
330+ #[ cfg( test) ]
331+ mod tests {
332+ use super :: * ;
333+
334+ #[ test]
335+ fn validate_max_response_len_accepts_values_within_cap ( ) {
336+ assert_eq ! ( validate_max_response_len( 0 ) , Ok ( 0 ) ) ;
337+ assert_eq ! (
338+ validate_max_response_len( MAX_RESPONSE_LEN_BYTES ) ,
339+ Ok ( MAX_RESPONSE_LEN_BYTES as usize )
340+ ) ;
341+ }
342+
343+ #[ test]
344+ fn validate_max_response_len_rejects_negative_values ( ) {
345+ assert_eq ! ( validate_max_response_len( -1 ) , Err ( "maxLen < 0" ) ) ;
346+ }
347+
348+ #[ test]
349+ fn validate_max_response_len_rejects_values_above_cap ( ) {
350+ assert_eq ! (
351+ validate_max_response_len( MAX_RESPONSE_LEN_BYTES + 1 ) ,
352+ Err ( "maxLen exceeds maximum allowed response size" )
353+ ) ;
354+ }
355+ }
0 commit comments