Skip to content

Commit eaa1997

Browse files
committed
use struct to deserialize match and use helper function for formatting
1 parent fc50d61 commit eaa1997

File tree

3 files changed

+65
-65
lines changed

3 files changed

+65
-65
lines changed

resources/sshdconfig/locales/en-us.toml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,12 @@ validatingTempConfig = "Validating temporary sshd_config file"
7171
writingTempConfig = "Writing temporary sshd_config file"
7272

7373
[util]
74-
arrayElementMustBeStringNumber = "array element must be a string or number for key '%{key}'"
7574
cleanupFailed = "Failed to clean up temporary file %{path}: %{error}"
75+
deserializeFailed = "Failed to deserialize match input: %{error}"
7676
getIgnoresInputFilters = "get command does not support filtering based on input settings, provided input will be ignored"
7777
inputMustBeBoolean = "value of '%{input}' must be true or false"
78+
invalidValue = "Key: '%{key}' cannot have empty value"
7879
matchBlockMissingCriteria = "Match block must contain 'criteria' field"
79-
matchBlockCriteriaMustBeObject = "Match block 'criteria' must be an object"
80-
matchCriterionMustBeArray = "Match criterion '%{key}' must be an array"
81-
objectValuesNotSupported = "Object values are not supported for key '%{key}'"
8280
sshdConfigNotFound = "sshd_config not found at path: '%{path}'"
8381
sshdConfigReadFailed = "failed to read sshd_config at path: '%{path}'"
8482
sshdElevation = "elevated security context required"

resources/sshdconfig/src/set.rs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -120,21 +120,18 @@ fn set_sshd_config(cmd_info: &CommandInfo) -> Result<(), SshdConfigError> {
120120
if REPEATABLE_KEYWORDS.contains(&key_lower.as_str()) {
121121
if let Value::Array(arr) = value {
122122
for item in arr {
123-
if let Some(formatted) = format_sshd_value(key, item)? {
124-
writeln!(&mut config_text, "{key} {formatted}")?;
125-
}
123+
let formatted = format_sshd_value(key, item)?;
124+
writeln!(&mut config_text, "{key} {formatted}")?;
126125
}
127126
} else {
128127
// Single value for repeatable keyword, write as-is
129-
if let Some(formatted) = format_sshd_value(key, value)? {
130-
writeln!(&mut config_text, "{key} {formatted}")?;
131-
}
128+
let formatted = format_sshd_value(key, value)?;
129+
writeln!(&mut config_text, "{key} {formatted}")?;
132130
}
133131
} else {
134132
// Handle non-repeatable keywords - format and write single line
135-
if let Some(formatted) = format_sshd_value(key, value)? {
136-
writeln!(&mut config_text, "{key} {formatted}")?;
137-
}
133+
let formatted = format_sshd_value(key, value)?;
134+
writeln!(&mut config_text, "{key} {formatted}")?;
138135
}
139136
}
140137
} else {

resources/sshdconfig/src/util.rs

Lines changed: 57 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT License.
33

44
use rust_i18n::t;
5+
use serde::Deserialize;
56
use serde_json::{Map, Value};
67
use std::{path::PathBuf, process::Command};
78
use tracing::{debug, warn, Level};
@@ -13,6 +14,13 @@ use crate::inputs::{CommandInfo, Metadata, SshdCommandArgs};
1314
use crate::metadata::{MULTI_ARG_KEYWORDS_COMMA_SEP, SSHD_CONFIG_DEFAULT_PATH_UNIX, SSHD_CONFIG_DEFAULT_PATH_WINDOWS};
1415
use 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

Comments
 (0)