77// You may not use this file except in accordance with one or both of these
88// licenses.
99
10+ use std:: env:: VarError ;
1011use std:: path:: PathBuf ;
1112
1213use ldk_server_client:: config:: {
@@ -20,10 +21,20 @@ pub struct ResolvedConfig {
2021 pub tls_cert_pem : Vec < u8 > ,
2122}
2223
24+ /// Reads an environment variable, treating a set but non-UTF-8 value as an
25+ /// error rather than as unset so it cannot trigger a fallback.
26+ fn read_env_var ( name : & str ) -> Result < Option < String > , String > {
27+ match std:: env:: var ( name) {
28+ Ok ( value) => Ok ( Some ( value) ) ,
29+ Err ( VarError :: NotPresent ) => Ok ( None ) ,
30+ Err ( VarError :: NotUnicode ( _) ) => Err ( format ! ( "{name} is set but is not valid UTF-8" ) ) ,
31+ }
32+ }
33+
2334pub fn resolve_config ( config_path : Option < String > ) -> Result < ResolvedConfig , String > {
24- let env_base_url = std :: env :: var ( "LDK_BASE_URL" ) . ok ( ) ;
25- let env_macaroon = std :: env :: var ( "LDK_MACAROON" ) . ok ( ) ;
26- let env_tls_cert_path = std :: env :: var ( "LDK_TLS_CERT_PATH" ) . ok ( ) . map ( PathBuf :: from) ;
35+ let env_base_url = read_env_var ( "LDK_BASE_URL" ) ? ;
36+ let env_macaroon = read_env_var ( "LDK_MACAROON" ) ? ;
37+ let env_tls_cert_path = read_env_var ( "LDK_TLS_CERT_PATH" ) ? . map ( PathBuf :: from) ;
2738 let env_overrides_complete =
2839 env_base_url. is_some ( ) && env_macaroon. is_some ( ) && env_tls_cert_path. is_some ( ) ;
2940
@@ -335,4 +346,70 @@ mod tests {
335346
336347 std:: fs:: remove_dir_all ( temp_dir) . unwrap ( ) ;
337348 }
349+
350+ #[ cfg( unix) ]
351+ fn assert_non_utf8_env_var_rejected ( name : & str , test_name : & str ) {
352+ use std:: ffi:: OsStr ;
353+ use std:: os:: unix:: ffi:: OsStrExt ;
354+
355+ let _lock = ENV_LOCK . lock ( ) . unwrap ( ) ;
356+
357+ let temp_dir = std:: env:: temp_dir ( ) . join ( format ! (
358+ "ldk-server-mcp-non-utf8-{}-{}" ,
359+ test_name,
360+ std:: process:: id( )
361+ ) ) ;
362+ let storage_dir = temp_dir. join ( "storage" ) ;
363+ let macaroons_dir = storage_dir. join ( "regtest" ) . join ( "macaroons" ) ;
364+ std:: fs:: create_dir_all ( & macaroons_dir) . unwrap ( ) ;
365+
366+ // Valid credentials exist on disk, so any fallback would succeed.
367+ let cert_path = storage_dir. join ( "tls.crt" ) ;
368+ std:: fs:: write ( & cert_path, b"storage-cert" ) . unwrap ( ) ;
369+ std:: fs:: write ( macaroons_dir. join ( "admin.macaroon" ) , "0201000207746573742d6964000006203846eea2ed59d53650493222c2380a3540668ade20044e28f9cb1e0b5b4e4429" ) . unwrap ( ) ;
370+
371+ let config_path = temp_dir. join ( "config.toml" ) ;
372+ std:: fs:: write (
373+ & config_path,
374+ format ! (
375+ r#"
376+ [node]
377+ network = "regtest"
378+
379+ [storage.disk]
380+ dir_path = "{}"
381+ "# ,
382+ storage_dir. display( )
383+ ) ,
384+ )
385+ . unwrap ( ) ;
386+
387+ std:: env:: remove_var ( "LDK_MACAROON" ) ;
388+ std:: env:: remove_var ( "LDK_TLS_CERT_PATH" ) ;
389+ std:: env:: remove_var ( "LDK_BASE_URL" ) ;
390+ std:: env:: set_var ( name, OsStr :: from_bytes ( b"\xff \xfe " ) ) ;
391+ let result = resolve_config ( Some ( config_path. display ( ) . to_string ( ) ) ) ;
392+ std:: env:: remove_var ( name) ;
393+
394+ std:: fs:: remove_dir_all ( temp_dir) . unwrap ( ) ;
395+ assert_eq ! ( result. err( ) . unwrap( ) , format!( "{name} is set but is not valid UTF-8" ) ) ;
396+ }
397+
398+ #[ test]
399+ #[ cfg( unix) ]
400+ fn resolve_config_rejects_non_utf8_macaroon ( ) {
401+ assert_non_utf8_env_var_rejected ( "LDK_MACAROON" , "macaroon" ) ;
402+ }
403+
404+ #[ test]
405+ #[ cfg( unix) ]
406+ fn resolve_config_rejects_non_utf8_base_url ( ) {
407+ assert_non_utf8_env_var_rejected ( "LDK_BASE_URL" , "base-url" ) ;
408+ }
409+
410+ #[ test]
411+ #[ cfg( unix) ]
412+ fn resolve_config_rejects_non_utf8_tls_cert_path ( ) {
413+ assert_non_utf8_env_var_rejected ( "LDK_TLS_CERT_PATH" , "tls-cert-path" ) ;
414+ }
338415}
0 commit comments