22// SPDX-License-Identifier: Apache-2.0
33
44use crate :: paths:: {
5- last_sandbox_path, system_active_gateway_path, system_gateways_dir , user_active_gateway_path ,
6- user_gateways_dir,
5+ last_sandbox_path, system_active_gateway_path, system_gateway_dir , system_gateways_dir ,
6+ user_active_gateway_path , user_gateway_dir , user_gateways_dir, validated_gateway_name ,
77} ;
88use miette:: { IntoDiagnostic , Result , WrapErr } ;
99use openshell_core:: paths:: ensure_parent_dir_restricted;
@@ -98,11 +98,11 @@ pub struct ListedGateway {
9898}
9999
100100fn user_gateway_metadata_path ( name : & str ) -> Result < PathBuf > {
101- Ok ( user_gateways_dir ( ) ? . join ( name) . join ( "metadata.json" ) )
101+ Ok ( user_gateway_dir ( name) ? . join ( "metadata.json" ) )
102102}
103103
104- fn system_gateway_metadata_path ( name : & str ) -> PathBuf {
105- system_gateways_dir ( ) . join ( name) . join ( "metadata.json" )
104+ fn system_gateway_metadata_path ( name : & str ) -> Result < PathBuf > {
105+ Ok ( system_gateway_dir ( name) ? . join ( "metadata.json" ) )
106106}
107107
108108fn resolve_gateway_metadata_path ( name : & str ) -> Result < Option < ( PathBuf , GatewayMetadataSource ) > > {
@@ -111,7 +111,7 @@ fn resolve_gateway_metadata_path(name: &str) -> Result<Option<(PathBuf, GatewayM
111111 return Ok ( Some ( ( user, GatewayMetadataSource :: User ) ) ) ;
112112 }
113113
114- let system = system_gateway_metadata_path ( name) ;
114+ let system = system_gateway_metadata_path ( name) ? ;
115115 if system. exists ( ) {
116116 return Ok ( Some ( ( system, GatewayMetadataSource :: System ) ) ) ;
117117 }
@@ -212,7 +212,7 @@ pub fn gateway_metadata_source(name: &str) -> Result<Option<GatewayMetadataSourc
212212
213213pub fn load_gateway_metadata ( name : & str ) -> Result < GatewayMetadata > {
214214 let primary = user_gateway_metadata_path ( name) ?;
215- let system = system_gateway_metadata_path ( name) ;
215+ let system = system_gateway_metadata_path ( name) ? ;
216216 let Some ( ( path, _source) ) = resolve_gateway_metadata_path ( name) ? else {
217217 return Err ( miette:: miette!(
218218 "no metadata found for gateway '{name}' (looked in {} and {})" ,
@@ -230,13 +230,25 @@ pub fn get_gateway_metadata(name: &str) -> Option<GatewayMetadata> {
230230
231231/// Save the active gateway name to persistent storage.
232232pub fn save_active_gateway ( name : & str ) -> Result < ( ) > {
233+ validated_gateway_name ( name) ?;
233234 let path = user_active_gateway_path ( ) ?;
234235 ensure_parent_dir_restricted ( & path) ?;
235236 std:: fs:: write ( & path, name)
236237 . into_diagnostic ( )
237238 . wrap_err_with ( || format ! ( "failed to write active gateway to {}" , path. display( ) ) ) ?;
238239 Ok ( ( ) )
239240}
241+
242+ fn read_gateway_name ( path : & Path ) -> Option < String > {
243+ let value = read_nonempty_trimmed ( path) ?;
244+ match validated_gateway_name ( & value) {
245+ Ok ( _) => Some ( value) ,
246+ Err ( err) => {
247+ tracing:: warn!( path = %path. display( ) , error = %err, "ignoring invalid active gateway name" ) ;
248+ None
249+ }
250+ }
251+ }
240252fn read_nonempty_trimmed ( path : & Path ) -> Option < String > {
241253 let contents = std:: fs:: read_to_string ( path) . ok ( ) ?;
242254 let value = contents. trim ( ) ;
@@ -250,7 +262,7 @@ pub fn load_user_active_gateway() -> Option<String> {
250262 user_active_gateway_path ( )
251263 . ok ( )
252264 . as_deref ( )
253- . and_then ( read_nonempty_trimmed )
265+ . and_then ( read_gateway_name )
254266}
255267
256268/// Load the active gateway name from persistent storage.
@@ -259,7 +271,7 @@ pub fn load_user_active_gateway() -> Option<String> {
259271/// system-level active gateway file when no per-user selection exists, so
260272/// installer-provided defaults can take effect on a fresh system.
261273pub fn load_active_gateway ( ) -> Option < String > {
262- load_user_active_gateway ( ) . or_else ( || read_nonempty_trimmed ( & system_active_gateway_path ( ) ) )
274+ load_user_active_gateway ( ) . or_else ( || read_gateway_name ( & system_active_gateway_path ( ) ) )
263275}
264276
265277/// Save the last-used sandbox name for a gateway to persistent storage.
@@ -899,4 +911,34 @@ mod tests {
899911 assert_eq ! ( gateways[ 0 ] . source, GatewayMetadataSource :: System ) ;
900912 } ) ;
901913 }
914+
915+ #[ test]
916+ fn gateway_names_must_be_single_path_components ( ) {
917+ let user = tempfile:: tempdir ( ) . unwrap ( ) ;
918+ let system = tempfile:: tempdir ( ) . unwrap ( ) ;
919+ with_tmp_xdg_and_system ( user. path ( ) , system. path ( ) , || {
920+ let meta = GatewayMetadata {
921+ name : "shared" . to_string ( ) ,
922+ gateway_endpoint : "https://example.com" . to_string ( ) ,
923+ ..Default :: default ( )
924+ } ;
925+ assert ! ( store_gateway_metadata( "../escape" , & meta) . is_err( ) ) ;
926+ assert ! ( load_gateway_metadata( "../escape" ) . is_err( ) ) ;
927+ assert ! ( save_last_sandbox( "../escape" , "sb-123" ) . is_err( ) ) ;
928+ assert ! ( save_active_gateway( "../escape" ) . is_err( ) ) ;
929+ } ) ;
930+ }
931+
932+ #[ test]
933+ fn load_active_gateway_ignores_invalid_user_name_and_falls_back_to_system ( ) {
934+ let user = tempfile:: tempdir ( ) . unwrap ( ) ;
935+ let system = tempfile:: tempdir ( ) . unwrap ( ) ;
936+ with_tmp_xdg_and_system ( user. path ( ) , system. path ( ) , || {
937+ std:: fs:: write ( user. path ( ) . join ( "active_gateway" ) , "../escape" ) . unwrap ( ) ;
938+ std:: fs:: write ( system. path ( ) . join ( "active_gateway" ) , "system-default" ) . unwrap ( ) ;
939+
940+ assert_eq ! ( load_user_active_gateway( ) , None ) ;
941+ assert_eq ! ( load_active_gateway( ) , Some ( "system-default" . to_string( ) ) ) ;
942+ } ) ;
943+ }
902944}
0 commit comments