From d2a7ca6ad2a13a1b7e4678ff4f879d754e294811 Mon Sep 17 00:00:00 2001 From: Tess Gauthier Date: Thu, 20 Nov 2025 17:38:28 -0500 Subject: [PATCH 1/9] update grammar so quotedString node is not hidden --- .../tree-sitter-ssh-server-config/grammar.js | 4 ++-- .../test/corpus/valid_expressions.txt | 18 ++++++++++++------ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/grammars/tree-sitter-ssh-server-config/grammar.js b/grammars/tree-sitter-ssh-server-config/grammar.js index 9722a5f64..ce34d1286 100644 --- a/grammars/tree-sitter-ssh-server-config/grammar.js +++ b/grammars/tree-sitter-ssh-server-config/grammar.js @@ -34,7 +34,7 @@ module.exports = grammar({ repeat1(choice($.comment, $.keyword)), ), - arguments: $ => repeat1(choice($.boolean, $.number, $._quotedString, $._commaSeparatedString)), + arguments: $ => repeat1(choice($.boolean, $.number, $.quotedString, $._commaSeparatedString)), alphanumeric: $ => /[a-zA-Z0-9]+/i, boolean: $ => choice('yes', 'no'), @@ -43,7 +43,7 @@ module.exports = grammar({ string: $ => /[^\r\n,"]+/, _commaSeparatedString: $ => seq($.string, repeat(seq(',', $.string))), - _quotedString: $ => seq('\"', $.string, '\"'), + quotedString: $ => seq('\"', $.string, '\"'), } }); diff --git a/grammars/tree-sitter-ssh-server-config/test/corpus/valid_expressions.txt b/grammars/tree-sitter-ssh-server-config/test/corpus/valid_expressions.txt index b8842d877..061c6a481 100644 --- a/grammars/tree-sitter-ssh-server-config/test/corpus/valid_expressions.txt +++ b/grammars/tree-sitter-ssh-server-config/test/corpus/valid_expressions.txt @@ -9,7 +9,8 @@ authorizedKEYSfile "path to authorized keys file" (keyword (alphanumeric) (arguments - (string)))) + (quotedString + (string))))) ===== comment ===== @@ -36,7 +37,8 @@ authorizedkeysfile "path to authorized keys file" (keyword (alphanumeric) (arguments - (string))))) + (quotedString + (string)))))) ===== boolean and match ===== @@ -59,7 +61,8 @@ authorizedkeysfile "path to authorized keys file" (keyword (alphanumeric) (arguments - (string))))) + (quotedString + (string)))))) ===== directive with = operator ===== @@ -108,7 +111,8 @@ AllowGroups group1 "group two" (alphanumeric) (arguments (string) - (string)))) + (quotedString + (string))))) ===== directive with comma-separated arguments ===== @@ -316,7 +320,8 @@ passwordauthentication yes (alphanumeric) (arguments (string) - (string)))) + (quotedString + (string))))) (match (keyword (alphanumeric) @@ -367,4 +372,5 @@ allowgroups administrators "openssh users" (alphanumeric) (arguments (string) - (string)))) \ No newline at end of file + (quotedString + (string))))) \ No newline at end of file From d2c697dd8359b2f98857fb90f95e89e68dff88c1 Mon Sep 17 00:00:00 2001 From: Tess Gauthier Date: Fri, 21 Nov 2025 16:05:21 -0500 Subject: [PATCH 2/9] fix test --- resources/sshdconfig/src/parser.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/sshdconfig/src/parser.rs b/resources/sshdconfig/src/parser.rs index 53447ced1..716c6629b 100644 --- a/resources/sshdconfig/src/parser.rs +++ b/resources/sshdconfig/src/parser.rs @@ -155,7 +155,7 @@ fn parse_arguments_node(arg_node: tree_sitter::Node, input: &str, input_bytes: & return Err(SshdConfigError::ParserError(t!("parser.failedToParseChildNode", input = input).to_string())); } let argument: Value = match node.kind() { - "boolean" | "string" => { + "boolean" | "string" | "quotedString" => { let Ok(arg) = node.utf8_text(input_bytes) else { return Err(SshdConfigError::ParserError( t!("parser.failedToParseNode", input = input).to_string() @@ -176,7 +176,7 @@ fn parse_arguments_node(arg_node: tree_sitter::Node, input: &str, input_bytes: & return Err(SshdConfigError::ParserError( t!("parser.invalidValue").to_string() )); - } + }, _ => return Err(SshdConfigError::ParserError(t!("parser.unknownNode", kind = node.kind()).to_string())) }; if is_vec { @@ -242,7 +242,7 @@ mod tests { let result: Map = parse_text_to_map(input).unwrap(); let expected = vec![ Value::String("administrators".to_string()), - Value::String("openssh users".to_string()), + Value::String("\"openssh users\"".to_string()), ]; assert_eq!(result.get("allowgroups").unwrap(), &Value::Array(expected)); } From 9e88c7414baa068b9aed45d572ff23040a05b33b Mon Sep 17 00:00:00 2001 From: Tess Gauthier Date: Tue, 2 Dec 2025 13:15:09 -0500 Subject: [PATCH 3/9] revert parser test to original behavior --- resources/sshdconfig/src/parser.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/sshdconfig/src/parser.rs b/resources/sshdconfig/src/parser.rs index 716c6629b..abfb7d942 100644 --- a/resources/sshdconfig/src/parser.rs +++ b/resources/sshdconfig/src/parser.rs @@ -242,7 +242,7 @@ mod tests { let result: Map = parse_text_to_map(input).unwrap(); let expected = vec![ Value::String("administrators".to_string()), - Value::String("\"openssh users\"".to_string()), + Value::String("openssh users".to_string()), ]; assert_eq!(result.get("allowgroups").unwrap(), &Value::Array(expected)); } From 4b577501e0385966cddf48149df15b14d3ea70fc Mon Sep 17 00:00:00 2001 From: Tess Gauthier Date: Tue, 2 Dec 2025 13:16:00 -0500 Subject: [PATCH 4/9] update grammar for quoted strings and match criteria logic to support multiple criteria --- .../tree-sitter-ssh-server-config/grammar.js | 29 +++-- .../test/corpus/invalid_expressions.txt | 6 +- .../test/corpus/valid_expressions.txt | 112 ++++++++++++------ 3 files changed, 96 insertions(+), 51 deletions(-) diff --git a/grammars/tree-sitter-ssh-server-config/grammar.js b/grammars/tree-sitter-ssh-server-config/grammar.js index ce34d1286..2119bc2be 100644 --- a/grammars/tree-sitter-ssh-server-config/grammar.js +++ b/grammars/tree-sitter-ssh-server-config/grammar.js @@ -4,8 +4,8 @@ //------------------------------------------------------------------------------------------------------- const PREC = { - MATCH: 2, - OPERATOR: 1, + MATCH: 5, + OPERATOR: 1 } module.exports = grammar({ @@ -14,10 +14,10 @@ module.exports = grammar({ extras: $ => [' ', '\t', '\r'], rules: { - server_config: $ => seq(repeat(choice($.empty_line, $.comment, $.keyword)), repeat($.match)), + server_config: $ => seq(repeat(choice($._empty_line, $.comment, $.keyword)), repeat($.match)), // check for an empty line that is just a /n character - empty_line: $ => '\n', + _empty_line: $ => '\n', comment: $ => /#.*\n/, keyword: $ => seq( @@ -30,20 +30,31 @@ module.exports = grammar({ match: $ => seq( token(prec(PREC.MATCH, /match/i)), - field('criteria', $.keyword), + seq(repeat1($.criteria), $._empty_line), repeat1(choice($.comment, $.keyword)), ), - arguments: $ => repeat1(choice($.boolean, $.number, $.quotedString, $._commaSeparatedString)), + criteria: $ => seq( + field('criteria', $.alpha), + choice(seq(/[ \t]/, optional('=')), '='), + field('argument', $._argument) + ), + _argument: $ => choice($.boolean, $.number, $.string, $._commaSeparatedString, $._doublequotedString, $._singlequotedString), + arguments: $ => repeat1($._argument), + + alpha: $ => /[a-zA-Z]+/i, alphanumeric: $ => /[a-zA-Z0-9]+/i, boolean: $ => choice('yes', 'no'), number: $ => /\d+/, operator: $ => token(prec(PREC.OPERATOR, /[-+\^]/)), - string: $ => /[^\r\n,"]+/, + string: $ => /[^\r\n,"'\s]+/, /* cannot contains spaces */ + + _quotedString: $ => /[^\r\n,"']+/, /* can contain spaces */ + _doublequotedString: $ => seq('"', alias($._quotedString, $.string), repeat(seq(',', alias($._quotedString, $.string))), '"'), + _singlequotedString: $ => seq('\'', alias($._quotedString, $.string), repeat(seq(',', alias($._quotedString, $.string))), '\''), - _commaSeparatedString: $ => seq($.string, repeat(seq(',', $.string))), - quotedString: $ => seq('\"', $.string, '\"'), + _commaSeparatedString: $ => prec(1, seq($.string, repeat1(seq(',', $.string)))) } }); diff --git a/grammars/tree-sitter-ssh-server-config/test/corpus/invalid_expressions.txt b/grammars/tree-sitter-ssh-server-config/test/corpus/invalid_expressions.txt index 44c270b0e..d1cc06cd3 100644 --- a/grammars/tree-sitter-ssh-server-config/test/corpus/invalid_expressions.txt +++ b/grammars/tree-sitter-ssh-server-config/test/corpus/invalid_expressions.txt @@ -8,8 +8,7 @@ AuthorizedKeysFile"ARG" (server_config (ERROR (alphanumeric) - (UNEXPECTED 'A')) - (empty_line)) + (string))) ===== missing argument after keyword ===== @@ -19,5 +18,4 @@ AuthorizedKeysFile (server_config (ERROR - (alphanumeric)) - (empty_line)) + (alphanumeric))) diff --git a/grammars/tree-sitter-ssh-server-config/test/corpus/valid_expressions.txt b/grammars/tree-sitter-ssh-server-config/test/corpus/valid_expressions.txt index 061c6a481..f3543df53 100644 --- a/grammars/tree-sitter-ssh-server-config/test/corpus/valid_expressions.txt +++ b/grammars/tree-sitter-ssh-server-config/test/corpus/valid_expressions.txt @@ -9,8 +9,7 @@ authorizedKEYSfile "path to authorized keys file" (keyword (alphanumeric) (arguments - (quotedString - (string))))) + (string)))) ===== comment ===== @@ -30,15 +29,13 @@ authorizedkeysfile "path to authorized keys file" (server_config (match + (criteria + (alpha) + (string)) (keyword (alphanumeric) (arguments - (string))) - (keyword - (alphanumeric) - (arguments - (quotedString - (string)))))) + (string))))) ===== boolean and match ===== @@ -54,15 +51,13 @@ authorizedkeysfile "path to authorized keys file" (arguments (boolean))) (match + (criteria + (alpha) + (string)) (keyword (alphanumeric) (arguments - (string))) - (keyword - (alphanumeric) - (arguments - (quotedString - (string)))))) + (string))))) ===== directive with = operator ===== @@ -111,8 +106,7 @@ AllowGroups group1 "group two" (alphanumeric) (arguments (string) - (quotedString - (string))))) + (string)))) ===== directive with comma-separated arguments ===== @@ -233,19 +227,17 @@ passwordauthentication yes (server_config (match - (keyword - (alphanumeric) - (arguments - (string))) + (criteria + (alpha) + (string)) (keyword (alphanumeric) (arguments (boolean)))) (match - (keyword - (alphanumeric) - (arguments - (string))) + (criteria + (alpha) + (string)) (keyword (alphanumeric) (arguments @@ -285,6 +277,11 @@ Subsystem sftp sftp-server.exe -f LOCAL0 -l DEBUG3 (keyword (alphanumeric) (arguments + (string) + (string) + (string) + (string) + (string) (string)))) ===== parse mini config @@ -308,10 +305,9 @@ passwordauthentication yes (arguments (string))) (match - (keyword - (alphanumeric) - (arguments - (string))) + (criteria + (alpha) + (string)) (keyword (alphanumeric) (arguments @@ -320,13 +316,11 @@ passwordauthentication yes (alphanumeric) (arguments (string) - (quotedString - (string))))) + (string)))) (match - (keyword - (alphanumeric) - (arguments - (string))) + (criteria + (alpha) + (string)) (keyword (alphanumeric) (arguments @@ -359,8 +353,7 @@ parse empty line --- -(server_config - (empty_line)) +(server_config) ==== parse repeatable keyword ==== @@ -372,5 +365,48 @@ allowgroups administrators "openssh users" (alphanumeric) (arguments (string) - (quotedString - (string))))) \ No newline at end of file + (string)))) +==== +parse repeatable strings without quotes +==== +allowgroups groupOne groupTwo + +--- +(server_config + (keyword + (alphanumeric) + (arguments + (string) + (string)))) +==== +parse comma separated string with quotes +==== +ciphers "chacha20-poly1305@openssh.com,aes128-gcm@openssh.com" + +--- +(server_config + (keyword + (alphanumeric) + (arguments + (string) + (string)))) +==== +parse multiple match criteria +==== +match user User1,User2 Address 192.0.2.0/24 +passwordauthentication no + +--- +(server_config + (match + (criteria + (alpha) + (string) + (string)) + (criteria + (alpha) + (string)) + (keyword + (alphanumeric) + (arguments + (boolean))))) From f3ade8845e5471bff88ca012fe372ca45c05da89 Mon Sep 17 00:00:00 2001 From: Tess Gauthier Date: Tue, 2 Dec 2025 13:20:56 -0500 Subject: [PATCH 5/9] add test for single-quoted args --- .../test/corpus/valid_expressions.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/grammars/tree-sitter-ssh-server-config/test/corpus/valid_expressions.txt b/grammars/tree-sitter-ssh-server-config/test/corpus/valid_expressions.txt index f3543df53..da7662778 100644 --- a/grammars/tree-sitter-ssh-server-config/test/corpus/valid_expressions.txt +++ b/grammars/tree-sitter-ssh-server-config/test/corpus/valid_expressions.txt @@ -391,6 +391,17 @@ ciphers "chacha20-poly1305@openssh.com,aes128-gcm@openssh.com" (string) (string)))) ==== +parse single-quoted argument +==== +allowgroups 'openssh users' + +--- +(server_config + (keyword + (alphanumeric) + (arguments + (string)))) +==== parse multiple match criteria ==== match user User1,User2 Address 192.0.2.0/24 From 74ab420a4db14f0923250e342fc4e939611c7a56 Mon Sep 17 00:00:00 2001 From: Tess Gauthier Date: Tue, 2 Dec 2025 13:24:59 -0500 Subject: [PATCH 6/9] revert unneeded change to rust parser --- resources/sshdconfig/src/parser.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/sshdconfig/src/parser.rs b/resources/sshdconfig/src/parser.rs index abfb7d942..53447ced1 100644 --- a/resources/sshdconfig/src/parser.rs +++ b/resources/sshdconfig/src/parser.rs @@ -155,7 +155,7 @@ fn parse_arguments_node(arg_node: tree_sitter::Node, input: &str, input_bytes: & return Err(SshdConfigError::ParserError(t!("parser.failedToParseChildNode", input = input).to_string())); } let argument: Value = match node.kind() { - "boolean" | "string" | "quotedString" => { + "boolean" | "string" => { let Ok(arg) = node.utf8_text(input_bytes) else { return Err(SshdConfigError::ParserError( t!("parser.failedToParseNode", input = input).to_string() @@ -176,7 +176,7 @@ fn parse_arguments_node(arg_node: tree_sitter::Node, input: &str, input_bytes: & return Err(SshdConfigError::ParserError( t!("parser.invalidValue").to_string() )); - }, + } _ => return Err(SshdConfigError::ParserError(t!("parser.unknownNode", kind = node.kind()).to_string())) }; if is_vec { From 1924dd0d4fa709c35b4eed6408a5df4d0bc12fe5 Mon Sep 17 00:00:00 2001 From: Tess Gauthier Date: Tue, 2 Dec 2025 13:25:14 -0500 Subject: [PATCH 7/9] revert unneeded grammar precendence change --- grammars/tree-sitter-ssh-server-config/grammar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grammars/tree-sitter-ssh-server-config/grammar.js b/grammars/tree-sitter-ssh-server-config/grammar.js index 2119bc2be..66aa50a63 100644 --- a/grammars/tree-sitter-ssh-server-config/grammar.js +++ b/grammars/tree-sitter-ssh-server-config/grammar.js @@ -4,7 +4,7 @@ //------------------------------------------------------------------------------------------------------- const PREC = { - MATCH: 5, + MATCH: 2, OPERATOR: 1 } From 09ac3c1862d9a1163f46ab6c86f4a3eec62342e0 Mon Sep 17 00:00:00 2001 From: Tess Gauthier Date: Tue, 2 Dec 2025 14:47:14 -0500 Subject: [PATCH 8/9] add rekeyLimit to multi-arg keyword list --- resources/sshdconfig/src/metadata.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/resources/sshdconfig/src/metadata.rs b/resources/sshdconfig/src/metadata.rs index 7a94c5109..f2678ec96 100644 --- a/resources/sshdconfig/src/metadata.rs +++ b/resources/sshdconfig/src/metadata.rs @@ -3,7 +3,7 @@ // keywords that can have multiple argments per line but cannot be repeated over multiple lines, // as subsequent entries are ignored, should be represented as arrays -pub const MULTI_ARG_KEYWORDS: [&str; 16] = [ +pub const MULTI_ARG_KEYWORDS: [&str; 17] = [ "authenticationmethods", "authorizedkeysfile", "casignaturealgorithms", @@ -19,7 +19,8 @@ pub const MULTI_ARG_KEYWORDS: [&str; 16] = [ "permituserenvironment", "persourcepenalties", "persourcepenaltyexemptlist", - "pubkeyacceptedalgorithms" + "pubkeyacceptedalgorithms", + "rekeylimit" // first arg is bytes, second arg (optional) is amount of time ]; // keywords that can be repeated over multiple lines and should be represented as arrays. From 466c3d7ab7d2392347b192cb259b32763c46773c Mon Sep 17 00:00:00 2001 From: Tess Gauthier Date: Tue, 2 Dec 2025 15:43:15 -0500 Subject: [PATCH 9/9] address copilot feedback on typos/spacing --- .../tree-sitter-ssh-server-config/grammar.js | 2 +- .../test/corpus/valid_expressions.txt | 20 +++++++++---------- resources/sshdconfig/src/metadata.rs | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/grammars/tree-sitter-ssh-server-config/grammar.js b/grammars/tree-sitter-ssh-server-config/grammar.js index 66aa50a63..cad4a1a88 100644 --- a/grammars/tree-sitter-ssh-server-config/grammar.js +++ b/grammars/tree-sitter-ssh-server-config/grammar.js @@ -48,7 +48,7 @@ module.exports = grammar({ boolean: $ => choice('yes', 'no'), number: $ => /\d+/, operator: $ => token(prec(PREC.OPERATOR, /[-+\^]/)), - string: $ => /[^\r\n,"'\s]+/, /* cannot contains spaces */ + string: $ => /[^\r\n,"'\s]+/, /* cannot contain spaces */ _quotedString: $ => /[^\r\n,"']+/, /* can contain spaces */ _doublequotedString: $ => seq('"', alias($._quotedString, $.string), repeat(seq(',', alias($._quotedString, $.string))), '"'), diff --git a/grammars/tree-sitter-ssh-server-config/test/corpus/valid_expressions.txt b/grammars/tree-sitter-ssh-server-config/test/corpus/valid_expressions.txt index da7662778..3d7352e37 100644 --- a/grammars/tree-sitter-ssh-server-config/test/corpus/valid_expressions.txt +++ b/grammars/tree-sitter-ssh-server-config/test/corpus/valid_expressions.txt @@ -277,7 +277,7 @@ Subsystem sftp sftp-server.exe -f LOCAL0 -l DEBUG3 (keyword (alphanumeric) (arguments - (string) + (string) (string) (string) (string) @@ -412,12 +412,12 @@ passwordauthentication no (match (criteria (alpha) - (string) - (string)) - (criteria - (alpha) - (string)) - (keyword - (alphanumeric) - (arguments - (boolean))))) + (string) + (string)) + (criteria + (alpha) + (string)) + (keyword + (alphanumeric) + (arguments + (boolean))))) diff --git a/resources/sshdconfig/src/metadata.rs b/resources/sshdconfig/src/metadata.rs index f2678ec96..0bbc6bbd0 100644 --- a/resources/sshdconfig/src/metadata.rs +++ b/resources/sshdconfig/src/metadata.rs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -// keywords that can have multiple argments per line but cannot be repeated over multiple lines, +// keywords that can have multiple arguments per line but cannot be repeated over multiple lines, // as subsequent entries are ignored, should be represented as arrays pub const MULTI_ARG_KEYWORDS: [&str; 17] = [ "authenticationmethods",