22// Licensed under the MIT License.
33
44use rust_i18n:: t;
5+ use serde:: Deserialize ;
56use serde_json:: { Map , Value } ;
67use std:: { path:: PathBuf , process:: Command } ;
78use tracing:: { debug, warn, Level } ;
@@ -13,6 +14,13 @@ use crate::inputs::{CommandInfo, Metadata, SshdCommandArgs};
1314use crate :: metadata:: { MULTI_ARG_KEYWORDS_COMMA_SEP , SSHD_CONFIG_DEFAULT_PATH_UNIX , SSHD_CONFIG_DEFAULT_PATH_WINDOWS } ;
1415use crate :: parser:: parse_text_to_map;
1516
17+ #[ derive( Debug , Deserialize ) ]
18+ struct MatchBlockFmt {
19+ criteria : Map < String , Value > ,
20+ #[ serde( flatten) ]
21+ contents : Map < String , Value > ,
22+ }
23+
1624/// Enable tracing.
1725///
1826/// # Arguments
@@ -106,52 +114,55 @@ pub fn enable_tracing(trace_level: Option<TraceLevel>, trace_format: &TraceForma
106114/// # Errors
107115///
108116/// Returns an error if the value type is not supported or if formatting fails.
109- pub fn format_sshd_value ( key : & str , value : & Value ) -> Result < Option < String > , SshdConfigError > {
117+ pub fn format_sshd_value ( key : & str , value : & Value ) -> Result < String , SshdConfigError > {
110118 let key_lower = key. to_lowercase ( ) ;
119+ let result = if key_lower == "match" {
120+ format_match_block ( value) ?
121+ } else {
122+ display_sshd_value ( value, MULTI_ARG_KEYWORDS_COMMA_SEP . contains ( & key_lower. as_str ( ) ) ) ?
123+ } ;
111124
125+ if result. is_empty ( ) {
126+ return Err ( SshdConfigError :: ParserError ( t ! ( "util.invalidValue" , key = key) . to_string ( ) ) )
127+ }
128+ Ok ( result)
129+ }
130+
131+ fn display_sshd_value ( value : & Value , is_comma_separated : bool ) -> Result < String , SshdConfigError > {
112132 match value {
113133 Value :: Array ( arr) => {
114134 if arr. is_empty ( ) {
115- return Ok ( None ) ;
135+ return Ok ( String :: new ( ) ) ;
116136 }
117137
118138 // Convert array elements to strings
119139 let mut string_values = Vec :: new ( ) ;
120140 for item in arr {
121- match item {
122- Value :: Number ( n) => string_values. push ( n. to_string ( ) ) ,
123- Value :: String ( s) => string_values. push ( s. clone ( ) ) ,
124- _ => return Err ( SshdConfigError :: InvalidInput (
125- t ! ( "util.arrayElementMustBeStringNumber" , key = key) . to_string ( )
126- ) ) ,
141+ let result = display_sshd_value ( item, false ) ?;
142+ if !result. is_empty ( ) {
143+ string_values. push ( result) ;
127144 }
128145 }
129146
130147 if string_values. is_empty ( ) {
131- return Ok ( None ) ;
148+ return Ok ( String :: new ( ) ) ;
132149 }
133150
134- let separator = if MULTI_ARG_KEYWORDS_COMMA_SEP . contains ( & key_lower . as_str ( ) ) {
151+ let separator = if is_comma_separated {
135152 ","
136153 } else {
137154 " "
138155 } ;
139156
140- Ok ( Some ( string_values. join ( separator) ) )
157+ Ok ( string_values. join ( separator) )
141158 } ,
142159 Value :: Bool ( b) => {
143160 let bool_str = if * b { "yes" } else { "no" } ;
144- Ok ( Some ( bool_str. to_string ( ) ) )
161+ Ok ( bool_str. to_string ( ) )
145162 } ,
146- Value :: Null => Ok ( None ) ,
147- Value :: Number ( n) => Ok ( Some ( n. to_string ( ) ) ) ,
148- Value :: Object ( obj) => {
149- if key_lower == "match" {
150- return format_match_block ( obj) ;
151- }
152- Err ( SshdConfigError :: InvalidInput ( t ! ( "util.objectValuesNotSupported" , key = key) . to_string ( ) ) )
153- }
154- Value :: String ( s) => Ok ( Some ( s. clone ( ) ) ) ,
163+ Value :: Number ( n) => Ok ( n. to_string ( ) ) ,
164+ Value :: String ( s) => Ok ( s. clone ( ) ) ,
165+ _ => Ok ( String :: new ( ) )
155166 }
156167}
157168
@@ -331,45 +342,39 @@ fn get_bool_or_default(map: &mut Map<String, Value>, key: &str, default: bool) -
331342 }
332343}
333344
334- fn format_match_block ( match_obj : & Map < String , Value > ) -> Result < Option < String > , SshdConfigError > {
335- let mut lines = Vec :: new ( ) ;
336-
337- let Some ( criteria_value) = match_obj. get ( "criteria" ) else {
338- return Err ( SshdConfigError :: InvalidInput ( t ! ( "util.matchBlockMissingCriteria" ) . to_string ( ) ) ) ;
345+ fn format_match_block ( match_obj : & Value ) -> Result < String , SshdConfigError > {
346+ let match_block = match serde_json:: from_value :: < MatchBlockFmt > ( match_obj. clone ( ) ) {
347+ Ok ( result) => {
348+ result
349+ }
350+ Err ( e) => {
351+ return Err ( SshdConfigError :: ParserError ( t ! ( "util.deserializeFailed" , error = e) . to_string ( ) ) ) ;
352+ }
339353 } ;
340354
341- let Value :: Object ( criteria) = criteria_value else {
342- return Err ( SshdConfigError :: InvalidInput ( t ! ( "util.matchBlockCriteriaMustBeObject" ) . to_string ( ) ) ) ;
343- } ;
355+ if match_block. criteria . is_empty ( ) {
356+ return Err ( SshdConfigError :: InvalidInput (
357+ t ! ( "util.matchBlockMissingCriteria" ) . to_string ( )
358+ ) ) ;
359+ }
344360
345361 let mut match_parts = vec ! [ ] ;
362+ let mut result = vec ! [ ] ;
346363
347- for ( criterion_key, criterion_value) in criteria {
348- let Value :: Array ( values) = criterion_value else {
349- return Err ( SshdConfigError :: InvalidInput ( t ! ( "util.matchCriterionMustBeArray" , key = criterion_key) . to_string ( ) ) ) ;
350- } ;
351-
352- // Convert array values to comma-separated string
353- let value_strings: Vec < String > = values. iter ( )
354- . filter_map ( |v| v. as_str ( ) . map ( String :: from) )
355- . collect ( ) ;
356-
357- if !value_strings. is_empty ( ) {
358- match_parts. push ( format ! ( "{} {}" , criterion_key, value_strings. join( "," ) ) ) ;
359- }
364+ for ( key, value) in & match_block. criteria {
365+ // all match criteria values are comma-separated
366+ let value_formatted = display_sshd_value ( value, true ) ?;
367+ match_parts. push ( format ! ( "{key} {value_formatted}" ) ) ;
360368 }
361369
362- lines. push ( match_parts. join ( " " ) ) ;
370+ // Write the Match line with the formatted criteria(s)
371+ result. push ( match_parts. join ( " " ) ) ;
363372
364373 // Format other keywords in the match block
365- for ( key, value) in match_obj {
366- if key == "criteria" {
367- continue ; // handled above
368- }
369-
370- if let Some ( formatted_value) = format_sshd_value ( key, value) ? {
371- lines. push ( format ! ( " {key} {formatted_value}" ) ) ;
372- }
374+ for ( key, value) in & match_block. contents {
375+ let formatted_value = format_sshd_value ( key, value) ?;
376+ result. push ( format ! ( " {key} {formatted_value}" ) ) ;
373377 }
374- Ok ( Some ( lines. join ( "\n " ) ) )
378+
379+ Ok ( result. join ( "\n " ) )
375380}
0 commit comments