diff --git a/handlers/sshd_config/Config.g4 b/handlers/sshd_config/Config.g4 index 376950e..569a6a8 100644 --- a/handlers/sshd_config/Config.g4 +++ b/handlers/sshd_config/Config.g4 @@ -1,7 +1,7 @@ grammar Config; lineStatement - : (entry | (WHITESPACE? leadingComment) | WHITESPACE?) EOF + : (entry | (leadingComment) | WHITESPACE?) EOF ; entry @@ -17,7 +17,7 @@ key ; value - : (STRING WHITESPACE)? STRING WHITESPACE? + : (STRING WHITESPACE)* STRING? WHITESPACE? ; leadingComment diff --git a/handlers/sshd_config/ast/listener.go b/handlers/sshd_config/ast/listener.go index be9b529..b5c81b2 100644 --- a/handlers/sshd_config/ast/listener.go +++ b/handlers/sshd_config/ast/listener.go @@ -95,31 +95,41 @@ func (s *sshParserListener) ExitEntry(ctx *parser.EntryContext) { if s.sshContext.isKeyAMatchBlock { // Add new match block - match := match_parser.NewMatch() - errors := match.Parse(s.sshContext.currentOption.OptionValue.Value, location.Start.Line) + var match *match_parser.Match - if len(errors) > 0 { - for _, err := range errors { - s.Errors = append(s.Errors, common.LSPError{ - Range: err.Range.ShiftHorizontal(s.sshContext.currentOption.Start.Character), - Err: err.Err, - }) - } - } else { - matchBlock := &SSHMatchBlock{ - LocationRange: location, - MatchEntry: s.sshContext.currentOption, - MatchValue: match, - Options: treemap.NewWith(gods.UInt32Comparator), - } - s.Config.Options.Put( + if s.sshContext.currentOption.OptionValue != nil { + matchParser := match_parser.NewMatch() + errors := matchParser.Parse( + s.sshContext.currentOption.OptionValue.Value, location.Start.Line, - matchBlock, + s.sshContext.currentOption.OptionValue.Start.Character, ) - s.sshContext.currentMatchBlock = matchBlock + if len(errors) > 0 { + for _, err := range errors { + s.Errors = append(s.Errors, common.LSPError{ + Range: err.Range.ShiftHorizontal(s.sshContext.currentOption.Start.Character), + Err: err.Err, + }) + } + } else { + match = matchParser + } } + matchBlock := &SSHMatchBlock{ + LocationRange: location, + MatchEntry: s.sshContext.currentOption, + MatchValue: match, + Options: treemap.NewWith(gods.UInt32Comparator), + } + s.Config.Options.Put( + location.Start.Line, + matchBlock, + ) + + s.sshContext.currentMatchBlock = matchBlock + s.sshContext.isKeyAMatchBlock = false } else if s.sshContext.currentMatchBlock != nil { s.sshContext.currentMatchBlock.Options.Put( diff --git a/handlers/sshd_config/ast/parser/Config.interp b/handlers/sshd_config/ast/parser/Config.interp index b286373..2832f42 100644 --- a/handlers/sshd_config/ast/parser/Config.interp +++ b/handlers/sshd_config/ast/parser/Config.interp @@ -22,4 +22,4 @@ leadingComment atn: -[4, 1, 4, 64, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 1, 0, 1, 0, 3, 0, 15, 8, 0, 1, 0, 1, 0, 3, 0, 19, 8, 0, 3, 0, 21, 8, 0, 1, 0, 1, 0, 1, 1, 3, 1, 26, 8, 1, 1, 1, 3, 1, 29, 8, 1, 1, 1, 3, 1, 32, 8, 1, 1, 1, 3, 1, 35, 8, 1, 1, 1, 3, 1, 38, 8, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 1, 4, 3, 4, 46, 8, 4, 1, 4, 1, 4, 3, 4, 50, 8, 4, 1, 5, 1, 5, 3, 5, 54, 8, 5, 1, 5, 1, 5, 3, 5, 58, 8, 5, 4, 5, 60, 8, 5, 11, 5, 12, 5, 61, 1, 5, 0, 0, 6, 0, 2, 4, 6, 8, 10, 0, 0, 71, 0, 20, 1, 0, 0, 0, 2, 25, 1, 0, 0, 0, 4, 39, 1, 0, 0, 0, 6, 41, 1, 0, 0, 0, 8, 45, 1, 0, 0, 0, 10, 51, 1, 0, 0, 0, 12, 21, 3, 2, 1, 0, 13, 15, 5, 2, 0, 0, 14, 13, 1, 0, 0, 0, 14, 15, 1, 0, 0, 0, 15, 16, 1, 0, 0, 0, 16, 21, 3, 10, 5, 0, 17, 19, 5, 2, 0, 0, 18, 17, 1, 0, 0, 0, 18, 19, 1, 0, 0, 0, 19, 21, 1, 0, 0, 0, 20, 12, 1, 0, 0, 0, 20, 14, 1, 0, 0, 0, 20, 18, 1, 0, 0, 0, 21, 22, 1, 0, 0, 0, 22, 23, 5, 0, 0, 1, 23, 1, 1, 0, 0, 0, 24, 26, 5, 2, 0, 0, 25, 24, 1, 0, 0, 0, 25, 26, 1, 0, 0, 0, 26, 28, 1, 0, 0, 0, 27, 29, 3, 6, 3, 0, 28, 27, 1, 0, 0, 0, 28, 29, 1, 0, 0, 0, 29, 31, 1, 0, 0, 0, 30, 32, 3, 4, 2, 0, 31, 30, 1, 0, 0, 0, 31, 32, 1, 0, 0, 0, 32, 34, 1, 0, 0, 0, 33, 35, 3, 8, 4, 0, 34, 33, 1, 0, 0, 0, 34, 35, 1, 0, 0, 0, 35, 37, 1, 0, 0, 0, 36, 38, 3, 10, 5, 0, 37, 36, 1, 0, 0, 0, 37, 38, 1, 0, 0, 0, 38, 3, 1, 0, 0, 0, 39, 40, 5, 2, 0, 0, 40, 5, 1, 0, 0, 0, 41, 42, 5, 3, 0, 0, 42, 7, 1, 0, 0, 0, 43, 44, 5, 3, 0, 0, 44, 46, 5, 2, 0, 0, 45, 43, 1, 0, 0, 0, 45, 46, 1, 0, 0, 0, 46, 47, 1, 0, 0, 0, 47, 49, 5, 3, 0, 0, 48, 50, 5, 2, 0, 0, 49, 48, 1, 0, 0, 0, 49, 50, 1, 0, 0, 0, 50, 9, 1, 0, 0, 0, 51, 53, 5, 1, 0, 0, 52, 54, 5, 2, 0, 0, 53, 52, 1, 0, 0, 0, 53, 54, 1, 0, 0, 0, 54, 59, 1, 0, 0, 0, 55, 57, 5, 3, 0, 0, 56, 58, 5, 2, 0, 0, 57, 56, 1, 0, 0, 0, 57, 58, 1, 0, 0, 0, 58, 60, 1, 0, 0, 0, 59, 55, 1, 0, 0, 0, 60, 61, 1, 0, 0, 0, 61, 59, 1, 0, 0, 0, 61, 62, 1, 0, 0, 0, 62, 11, 1, 0, 0, 0, 13, 14, 18, 20, 25, 28, 31, 34, 37, 45, 49, 53, 57, 61] \ No newline at end of file +[4, 1, 4, 66, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 1, 0, 1, 0, 1, 0, 3, 0, 16, 8, 0, 3, 0, 18, 8, 0, 1, 0, 1, 0, 1, 1, 3, 1, 23, 8, 1, 1, 1, 3, 1, 26, 8, 1, 1, 1, 3, 1, 29, 8, 1, 1, 1, 3, 1, 32, 8, 1, 1, 1, 3, 1, 35, 8, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 1, 4, 5, 4, 43, 8, 4, 10, 4, 12, 4, 46, 9, 4, 1, 4, 3, 4, 49, 8, 4, 1, 4, 3, 4, 52, 8, 4, 1, 5, 1, 5, 3, 5, 56, 8, 5, 1, 5, 1, 5, 3, 5, 60, 8, 5, 4, 5, 62, 8, 5, 11, 5, 12, 5, 63, 1, 5, 0, 0, 6, 0, 2, 4, 6, 8, 10, 0, 0, 73, 0, 17, 1, 0, 0, 0, 2, 22, 1, 0, 0, 0, 4, 36, 1, 0, 0, 0, 6, 38, 1, 0, 0, 0, 8, 44, 1, 0, 0, 0, 10, 53, 1, 0, 0, 0, 12, 18, 3, 2, 1, 0, 13, 18, 3, 10, 5, 0, 14, 16, 5, 2, 0, 0, 15, 14, 1, 0, 0, 0, 15, 16, 1, 0, 0, 0, 16, 18, 1, 0, 0, 0, 17, 12, 1, 0, 0, 0, 17, 13, 1, 0, 0, 0, 17, 15, 1, 0, 0, 0, 18, 19, 1, 0, 0, 0, 19, 20, 5, 0, 0, 1, 20, 1, 1, 0, 0, 0, 21, 23, 5, 2, 0, 0, 22, 21, 1, 0, 0, 0, 22, 23, 1, 0, 0, 0, 23, 25, 1, 0, 0, 0, 24, 26, 3, 6, 3, 0, 25, 24, 1, 0, 0, 0, 25, 26, 1, 0, 0, 0, 26, 28, 1, 0, 0, 0, 27, 29, 3, 4, 2, 0, 28, 27, 1, 0, 0, 0, 28, 29, 1, 0, 0, 0, 29, 31, 1, 0, 0, 0, 30, 32, 3, 8, 4, 0, 31, 30, 1, 0, 0, 0, 31, 32, 1, 0, 0, 0, 32, 34, 1, 0, 0, 0, 33, 35, 3, 10, 5, 0, 34, 33, 1, 0, 0, 0, 34, 35, 1, 0, 0, 0, 35, 3, 1, 0, 0, 0, 36, 37, 5, 2, 0, 0, 37, 5, 1, 0, 0, 0, 38, 39, 5, 3, 0, 0, 39, 7, 1, 0, 0, 0, 40, 41, 5, 3, 0, 0, 41, 43, 5, 2, 0, 0, 42, 40, 1, 0, 0, 0, 43, 46, 1, 0, 0, 0, 44, 42, 1, 0, 0, 0, 44, 45, 1, 0, 0, 0, 45, 48, 1, 0, 0, 0, 46, 44, 1, 0, 0, 0, 47, 49, 5, 3, 0, 0, 48, 47, 1, 0, 0, 0, 48, 49, 1, 0, 0, 0, 49, 51, 1, 0, 0, 0, 50, 52, 5, 2, 0, 0, 51, 50, 1, 0, 0, 0, 51, 52, 1, 0, 0, 0, 52, 9, 1, 0, 0, 0, 53, 55, 5, 1, 0, 0, 54, 56, 5, 2, 0, 0, 55, 54, 1, 0, 0, 0, 55, 56, 1, 0, 0, 0, 56, 61, 1, 0, 0, 0, 57, 59, 5, 3, 0, 0, 58, 60, 5, 2, 0, 0, 59, 58, 1, 0, 0, 0, 59, 60, 1, 0, 0, 0, 60, 62, 1, 0, 0, 0, 61, 57, 1, 0, 0, 0, 62, 63, 1, 0, 0, 0, 63, 61, 1, 0, 0, 0, 63, 64, 1, 0, 0, 0, 64, 11, 1, 0, 0, 0, 13, 15, 17, 22, 25, 28, 31, 34, 44, 48, 51, 55, 59, 63] \ No newline at end of file diff --git a/handlers/sshd_config/ast/parser/config_parser.go b/handlers/sshd_config/ast/parser/config_parser.go index 79e7cfd..018bbb8 100644 --- a/handlers/sshd_config/ast/parser/config_parser.go +++ b/handlers/sshd_config/ast/parser/config_parser.go @@ -43,34 +43,35 @@ func configParserInit() { } staticData.PredictionContextCache = antlr.NewPredictionContextCache() staticData.serializedATN = []int32{ - 4, 1, 4, 64, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, - 2, 5, 7, 5, 1, 0, 1, 0, 3, 0, 15, 8, 0, 1, 0, 1, 0, 3, 0, 19, 8, 0, 3, - 0, 21, 8, 0, 1, 0, 1, 0, 1, 1, 3, 1, 26, 8, 1, 1, 1, 3, 1, 29, 8, 1, 1, - 1, 3, 1, 32, 8, 1, 1, 1, 3, 1, 35, 8, 1, 1, 1, 3, 1, 38, 8, 1, 1, 2, 1, - 2, 1, 3, 1, 3, 1, 4, 1, 4, 3, 4, 46, 8, 4, 1, 4, 1, 4, 3, 4, 50, 8, 4, - 1, 5, 1, 5, 3, 5, 54, 8, 5, 1, 5, 1, 5, 3, 5, 58, 8, 5, 4, 5, 60, 8, 5, - 11, 5, 12, 5, 61, 1, 5, 0, 0, 6, 0, 2, 4, 6, 8, 10, 0, 0, 71, 0, 20, 1, - 0, 0, 0, 2, 25, 1, 0, 0, 0, 4, 39, 1, 0, 0, 0, 6, 41, 1, 0, 0, 0, 8, 45, - 1, 0, 0, 0, 10, 51, 1, 0, 0, 0, 12, 21, 3, 2, 1, 0, 13, 15, 5, 2, 0, 0, - 14, 13, 1, 0, 0, 0, 14, 15, 1, 0, 0, 0, 15, 16, 1, 0, 0, 0, 16, 21, 3, - 10, 5, 0, 17, 19, 5, 2, 0, 0, 18, 17, 1, 0, 0, 0, 18, 19, 1, 0, 0, 0, 19, - 21, 1, 0, 0, 0, 20, 12, 1, 0, 0, 0, 20, 14, 1, 0, 0, 0, 20, 18, 1, 0, 0, - 0, 21, 22, 1, 0, 0, 0, 22, 23, 5, 0, 0, 1, 23, 1, 1, 0, 0, 0, 24, 26, 5, - 2, 0, 0, 25, 24, 1, 0, 0, 0, 25, 26, 1, 0, 0, 0, 26, 28, 1, 0, 0, 0, 27, - 29, 3, 6, 3, 0, 28, 27, 1, 0, 0, 0, 28, 29, 1, 0, 0, 0, 29, 31, 1, 0, 0, - 0, 30, 32, 3, 4, 2, 0, 31, 30, 1, 0, 0, 0, 31, 32, 1, 0, 0, 0, 32, 34, - 1, 0, 0, 0, 33, 35, 3, 8, 4, 0, 34, 33, 1, 0, 0, 0, 34, 35, 1, 0, 0, 0, - 35, 37, 1, 0, 0, 0, 36, 38, 3, 10, 5, 0, 37, 36, 1, 0, 0, 0, 37, 38, 1, - 0, 0, 0, 38, 3, 1, 0, 0, 0, 39, 40, 5, 2, 0, 0, 40, 5, 1, 0, 0, 0, 41, - 42, 5, 3, 0, 0, 42, 7, 1, 0, 0, 0, 43, 44, 5, 3, 0, 0, 44, 46, 5, 2, 0, - 0, 45, 43, 1, 0, 0, 0, 45, 46, 1, 0, 0, 0, 46, 47, 1, 0, 0, 0, 47, 49, - 5, 3, 0, 0, 48, 50, 5, 2, 0, 0, 49, 48, 1, 0, 0, 0, 49, 50, 1, 0, 0, 0, - 50, 9, 1, 0, 0, 0, 51, 53, 5, 1, 0, 0, 52, 54, 5, 2, 0, 0, 53, 52, 1, 0, - 0, 0, 53, 54, 1, 0, 0, 0, 54, 59, 1, 0, 0, 0, 55, 57, 5, 3, 0, 0, 56, 58, - 5, 2, 0, 0, 57, 56, 1, 0, 0, 0, 57, 58, 1, 0, 0, 0, 58, 60, 1, 0, 0, 0, - 59, 55, 1, 0, 0, 0, 60, 61, 1, 0, 0, 0, 61, 59, 1, 0, 0, 0, 61, 62, 1, - 0, 0, 0, 62, 11, 1, 0, 0, 0, 13, 14, 18, 20, 25, 28, 31, 34, 37, 45, 49, - 53, 57, 61, + 4, 1, 4, 66, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, + 2, 5, 7, 5, 1, 0, 1, 0, 1, 0, 3, 0, 16, 8, 0, 3, 0, 18, 8, 0, 1, 0, 1, + 0, 1, 1, 3, 1, 23, 8, 1, 1, 1, 3, 1, 26, 8, 1, 1, 1, 3, 1, 29, 8, 1, 1, + 1, 3, 1, 32, 8, 1, 1, 1, 3, 1, 35, 8, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, + 1, 4, 5, 4, 43, 8, 4, 10, 4, 12, 4, 46, 9, 4, 1, 4, 3, 4, 49, 8, 4, 1, + 4, 3, 4, 52, 8, 4, 1, 5, 1, 5, 3, 5, 56, 8, 5, 1, 5, 1, 5, 3, 5, 60, 8, + 5, 4, 5, 62, 8, 5, 11, 5, 12, 5, 63, 1, 5, 0, 0, 6, 0, 2, 4, 6, 8, 10, + 0, 0, 73, 0, 17, 1, 0, 0, 0, 2, 22, 1, 0, 0, 0, 4, 36, 1, 0, 0, 0, 6, 38, + 1, 0, 0, 0, 8, 44, 1, 0, 0, 0, 10, 53, 1, 0, 0, 0, 12, 18, 3, 2, 1, 0, + 13, 18, 3, 10, 5, 0, 14, 16, 5, 2, 0, 0, 15, 14, 1, 0, 0, 0, 15, 16, 1, + 0, 0, 0, 16, 18, 1, 0, 0, 0, 17, 12, 1, 0, 0, 0, 17, 13, 1, 0, 0, 0, 17, + 15, 1, 0, 0, 0, 18, 19, 1, 0, 0, 0, 19, 20, 5, 0, 0, 1, 20, 1, 1, 0, 0, + 0, 21, 23, 5, 2, 0, 0, 22, 21, 1, 0, 0, 0, 22, 23, 1, 0, 0, 0, 23, 25, + 1, 0, 0, 0, 24, 26, 3, 6, 3, 0, 25, 24, 1, 0, 0, 0, 25, 26, 1, 0, 0, 0, + 26, 28, 1, 0, 0, 0, 27, 29, 3, 4, 2, 0, 28, 27, 1, 0, 0, 0, 28, 29, 1, + 0, 0, 0, 29, 31, 1, 0, 0, 0, 30, 32, 3, 8, 4, 0, 31, 30, 1, 0, 0, 0, 31, + 32, 1, 0, 0, 0, 32, 34, 1, 0, 0, 0, 33, 35, 3, 10, 5, 0, 34, 33, 1, 0, + 0, 0, 34, 35, 1, 0, 0, 0, 35, 3, 1, 0, 0, 0, 36, 37, 5, 2, 0, 0, 37, 5, + 1, 0, 0, 0, 38, 39, 5, 3, 0, 0, 39, 7, 1, 0, 0, 0, 40, 41, 5, 3, 0, 0, + 41, 43, 5, 2, 0, 0, 42, 40, 1, 0, 0, 0, 43, 46, 1, 0, 0, 0, 44, 42, 1, + 0, 0, 0, 44, 45, 1, 0, 0, 0, 45, 48, 1, 0, 0, 0, 46, 44, 1, 0, 0, 0, 47, + 49, 5, 3, 0, 0, 48, 47, 1, 0, 0, 0, 48, 49, 1, 0, 0, 0, 49, 51, 1, 0, 0, + 0, 50, 52, 5, 2, 0, 0, 51, 50, 1, 0, 0, 0, 51, 52, 1, 0, 0, 0, 52, 9, 1, + 0, 0, 0, 53, 55, 5, 1, 0, 0, 54, 56, 5, 2, 0, 0, 55, 54, 1, 0, 0, 0, 55, + 56, 1, 0, 0, 0, 56, 61, 1, 0, 0, 0, 57, 59, 5, 3, 0, 0, 58, 60, 5, 2, 0, + 0, 59, 58, 1, 0, 0, 0, 59, 60, 1, 0, 0, 0, 60, 62, 1, 0, 0, 0, 61, 57, + 1, 0, 0, 0, 62, 63, 1, 0, 0, 0, 63, 61, 1, 0, 0, 0, 63, 64, 1, 0, 0, 0, + 64, 11, 1, 0, 0, 0, 13, 15, 17, 22, 25, 28, 31, 34, 44, 48, 51, 55, 59, + 63, } deserializer := antlr.NewATNDeserializer(nil) staticData.atn = deserializer.Deserialize(staticData.serializedATN) @@ -240,13 +241,13 @@ func (p *ConfigParser) LineStatement() (localctx ILineStatementContext) { var _la int p.EnterOuterAlt(localctx, 1) - p.SetState(20) + p.SetState(17) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit } - switch p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 2, p.GetParserRuleContext()) { + switch p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 1, p.GetParserRuleContext()) { case 1: { p.SetState(12) @@ -254,31 +255,13 @@ func (p *ConfigParser) LineStatement() (localctx ILineStatementContext) { } case 2: - p.SetState(14) - p.GetErrorHandler().Sync(p) - if p.HasError() { - goto errorExit - } - _la = p.GetTokenStream().LA(1) - - if _la == ConfigParserWHITESPACE { - { - p.SetState(13) - p.Match(ConfigParserWHITESPACE) - if p.HasError() { - // Recognition error - abort rule - goto errorExit - } - } - - } { - p.SetState(16) + p.SetState(13) p.LeadingComment() } case 3: - p.SetState(18) + p.SetState(15) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit @@ -287,7 +270,7 @@ func (p *ConfigParser) LineStatement() (localctx ILineStatementContext) { if _la == ConfigParserWHITESPACE { { - p.SetState(17) + p.SetState(14) p.Match(ConfigParserWHITESPACE) if p.HasError() { // Recognition error - abort rule @@ -301,7 +284,7 @@ func (p *ConfigParser) LineStatement() (localctx ILineStatementContext) { goto errorExit } { - p.SetState(22) + p.SetState(19) p.Match(ConfigParserEOF) if p.HasError() { // Recognition error - abort rule @@ -466,17 +449,29 @@ func (p *ConfigParser) Entry() (localctx IEntryContext) { var _la int p.EnterOuterAlt(localctx, 1) + p.SetState(22) + p.GetErrorHandler().Sync(p) + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 2, p.GetParserRuleContext()) == 1 { + { + p.SetState(21) + p.Match(ConfigParserWHITESPACE) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } else if p.HasError() { // JIM + goto errorExit + } p.SetState(25) p.GetErrorHandler().Sync(p) if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 3, p.GetParserRuleContext()) == 1 { { p.SetState(24) - p.Match(ConfigParserWHITESPACE) - if p.HasError() { - // Recognition error - abort rule - goto errorExit - } + p.Key() } } else if p.HasError() { // JIM @@ -488,7 +483,7 @@ func (p *ConfigParser) Entry() (localctx IEntryContext) { if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 4, p.GetParserRuleContext()) == 1 { { p.SetState(27) - p.Key() + p.Separator() } } else if p.HasError() { // JIM @@ -496,17 +491,15 @@ func (p *ConfigParser) Entry() (localctx IEntryContext) { } p.SetState(31) p.GetErrorHandler().Sync(p) - if p.HasError() { - goto errorExit - } - _la = p.GetTokenStream().LA(1) - if _la == ConfigParserWHITESPACE { + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 5, p.GetParserRuleContext()) == 1 { { p.SetState(30) - p.Separator() + p.Value() } + } else if p.HasError() { // JIM + goto errorExit } p.SetState(34) p.GetErrorHandler().Sync(p) @@ -515,23 +508,9 @@ func (p *ConfigParser) Entry() (localctx IEntryContext) { } _la = p.GetTokenStream().LA(1) - if _la == ConfigParserSTRING { - { - p.SetState(33) - p.Value() - } - - } - p.SetState(37) - p.GetErrorHandler().Sync(p) - if p.HasError() { - goto errorExit - } - _la = p.GetTokenStream().LA(1) - if _la == ConfigParserHASH { { - p.SetState(36) + p.SetState(33) p.LeadingComment() } @@ -625,7 +604,7 @@ func (p *ConfigParser) Separator() (localctx ISeparatorContext) { p.EnterRule(localctx, 4, ConfigParserRULE_separator) p.EnterOuterAlt(localctx, 1) { - p.SetState(39) + p.SetState(36) p.Match(ConfigParserWHITESPACE) if p.HasError() { // Recognition error - abort rule @@ -721,7 +700,7 @@ func (p *ConfigParser) Key() (localctx IKeyContext) { p.EnterRule(localctx, 6, ConfigParserRULE_key) p.EnterOuterAlt(localctx, 1) { - p.SetState(41) + p.SetState(38) p.Match(ConfigParserSTRING) if p.HasError() { // Recognition error - abort rule @@ -832,40 +811,67 @@ func (p *ConfigParser) Value() (localctx IValueContext) { p.EnterRule(localctx, 8, ConfigParserRULE_value) var _la int - p.EnterOuterAlt(localctx, 1) - p.SetState(45) - p.GetErrorHandler().Sync(p) + var _alt int - if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 8, p.GetParserRuleContext()) == 1 { + p.EnterOuterAlt(localctx, 1) + p.SetState(44) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 7, p.GetParserRuleContext()) + if p.HasError() { + goto errorExit + } + for _alt != 2 && _alt != antlr.ATNInvalidAltNumber { + if _alt == 1 { + { + p.SetState(40) + p.Match(ConfigParserSTRING) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(41) + p.Match(ConfigParserWHITESPACE) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } + p.SetState(46) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 7, p.GetParserRuleContext()) + if p.HasError() { + goto errorExit + } + } + p.SetState(48) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + if _la == ConfigParserSTRING { { - p.SetState(43) + p.SetState(47) p.Match(ConfigParserSTRING) if p.HasError() { // Recognition error - abort rule goto errorExit } } - { - p.SetState(44) - p.Match(ConfigParserWHITESPACE) - if p.HasError() { - // Recognition error - abort rule - goto errorExit - } - } - } else if p.HasError() { // JIM - goto errorExit } - { - p.SetState(47) - p.Match(ConfigParserSTRING) - if p.HasError() { - // Recognition error - abort rule - goto errorExit - } - } - p.SetState(49) + p.SetState(51) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit @@ -874,7 +880,7 @@ func (p *ConfigParser) Value() (localctx IValueContext) { if _la == ConfigParserWHITESPACE { { - p.SetState(48) + p.SetState(50) p.Match(ConfigParserWHITESPACE) if p.HasError() { // Recognition error - abort rule @@ -994,14 +1000,14 @@ func (p *ConfigParser) LeadingComment() (localctx ILeadingCommentContext) { p.EnterOuterAlt(localctx, 1) { - p.SetState(51) + p.SetState(53) p.Match(ConfigParserHASH) if p.HasError() { // Recognition error - abort rule goto errorExit } } - p.SetState(53) + p.SetState(55) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit @@ -1010,7 +1016,7 @@ func (p *ConfigParser) LeadingComment() (localctx ILeadingCommentContext) { if _la == ConfigParserWHITESPACE { { - p.SetState(52) + p.SetState(54) p.Match(ConfigParserWHITESPACE) if p.HasError() { // Recognition error - abort rule @@ -1019,7 +1025,7 @@ func (p *ConfigParser) LeadingComment() (localctx ILeadingCommentContext) { } } - p.SetState(59) + p.SetState(61) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit @@ -1028,14 +1034,14 @@ func (p *ConfigParser) LeadingComment() (localctx ILeadingCommentContext) { for ok := true; ok; ok = _la == ConfigParserSTRING { { - p.SetState(55) + p.SetState(57) p.Match(ConfigParserSTRING) if p.HasError() { // Recognition error - abort rule goto errorExit } } - p.SetState(57) + p.SetState(59) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit @@ -1044,7 +1050,7 @@ func (p *ConfigParser) LeadingComment() (localctx ILeadingCommentContext) { if _la == ConfigParserWHITESPACE { { - p.SetState(56) + p.SetState(58) p.Match(ConfigParserWHITESPACE) if p.HasError() { // Recognition error - abort rule @@ -1054,7 +1060,7 @@ func (p *ConfigParser) LeadingComment() (localctx ILeadingCommentContext) { } - p.SetState(61) + p.SetState(63) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit diff --git a/handlers/sshd_config/ast/parser_test.go b/handlers/sshd_config/ast/parser_test.go index 0061807..e883966 100644 --- a/handlers/sshd_config/ast/parser_test.go +++ b/handlers/sshd_config/ast/parser_test.go @@ -92,7 +92,7 @@ Match Address 192.168.0.1 t.Errorf("Expected second entry to be 'Match Address 192.168.0.1', but got: %v", secondEntry.MatchEntry.Value) } - if !(secondEntry.MatchValue.Entries[0].Criteria == "Address" && secondEntry.MatchValue.Entries[0].Values[0].Value == "192.168.0.1") { + if !(secondEntry.MatchValue.Entries[0].Criteria.Type == "Address" && secondEntry.MatchValue.Entries[0].Values.Values[0].Value == "192.168.0.1" && secondEntry.MatchEntry.OptionValue.Start.Character == 6) { t.Errorf("Expected second entry to be 'Match Address 192.168.0.1', but got: %v", secondEntry.MatchValue) } @@ -107,6 +107,65 @@ Match Address 192.168.0.1 } } +func TestMultipleEntriesInMatchBlock( + t *testing.T, +) { + input := utils.Dedent(` +Match User lena User root +`) + p := NewSSHConfig() + errors := p.Parse(input) + + if len(errors) != 0 { + t.Fatalf("Expected no errors, got %v", errors) + } + + _, matchBlock := p.FindOption(uint32(0)) + + if !(matchBlock.MatchEntry.Value == "Match User lena User root") { + t.Errorf("Expected match block to be 'Match User lena User root', but got: %v", matchBlock.MatchEntry.Value) + } + + if !(len(matchBlock.MatchValue.Entries) == 2) { + t.Errorf("Expected 2 entries in match block, but got: %v", matchBlock.MatchValue.Entries) + } + + if !(matchBlock.MatchValue.Entries[0].Criteria.Type == "User" && matchBlock.MatchValue.Entries[0].Values.Values[0].Value == "lena") { + t.Errorf("Expected first entry to be 'User lena', but got: %v", matchBlock.MatchValue.Entries[0]) + } + + if !(matchBlock.MatchValue.Entries[1].Criteria.Type == "User" && matchBlock.MatchValue.Entries[1].Values.Values[0].Value == "root") { + t.Errorf("Expected second entry to be 'User root', but got: %v", matchBlock.MatchValue.Entries[1]) + } +} + +func TestIncompleteMatchBlock( + t *testing.T, +) { + input := "Match User lena User " + + p := NewSSHConfig() + errors := p.Parse(input) + + if !(len(errors) == 0) { + t.Errorf("Expected 0 error, got %v", errors) + } + + _, matchBlock := p.FindOption(uint32(0)) + + if !(matchBlock.MatchEntry.Value == "Match User lena User ") { + t.Errorf("Expected match block to be 'Match User lena User ', but got: %v", matchBlock.MatchEntry.Value) + } + + if !(matchBlock.MatchValue.Entries[0].Criteria.Type == "User" && matchBlock.MatchValue.Entries[0].Values.Values[0].Value == "lena") { + t.Errorf("Expected first entry to be 'User lena', but got: %v", matchBlock.MatchValue.Entries[0]) + } + + if !(matchBlock.MatchValue.Entries[1].Value == "User " && matchBlock.MatchValue.Entries[1].Criteria.Type == "User" && matchBlock.MatchValue.Entries[1].Values == nil) { + t.Errorf("Expected second entry to be 'User ', but got: %v", matchBlock.MatchValue.Entries[1]) + } +} + func TestMultipleMatchBlocks( t *testing.T, ) { @@ -168,12 +227,12 @@ Match Address 192.168.0.2 } emptyOption, matchBlock := p.FindOption(uint32(5)) - if !(emptyOption == nil && matchBlock.MatchEntry.Value == "Match User lena" && matchBlock.MatchValue.Entries[0].Values[0].Value == "lena") { + if !(emptyOption == nil && matchBlock.MatchEntry.Value == "Match User lena" && matchBlock.MatchValue.Entries[0].Values.Values[0].Value == "lena") { t.Errorf("Expected empty option and match block to be 'Match User lena', but got: %v, %v", emptyOption, matchBlock) } matchOption, matchBlock := p.FindOption(uint32(2)) - if !(matchOption.Value == "Match User lena" && matchBlock.MatchEntry.Value == "Match User lena" && matchBlock.MatchValue.Entries[0].Values[0].Value == "lena") { + if !(matchOption.Value == "Match User lena" && matchBlock.MatchEntry.Value == "Match User lena" && matchBlock.MatchValue.Entries[0].Values.Values[0].Value == "lena" && matchBlock.MatchEntry.OptionValue.Start.Character == 6) { t.Errorf("Expected match option to be 'Match User lena', but got: %v, %v", matchOption, matchBlock) } } @@ -416,7 +475,7 @@ Match Address 172.22.100.0/24,172.22.5.0/24,127.0.0.1 t.Errorf("Expected fourth entry to be 'Match User anoncvs', but got: %v", fourthEntry.MatchEntry.Value) } - if !(fourthEntry.MatchValue.Entries[0].Criteria == "User" && fourthEntry.MatchValue.Entries[0].Values[0].Value == "anoncvs") { + if !(fourthEntry.MatchValue.Entries[0].Criteria.Type == "User" && fourthEntry.MatchValue.Entries[0].Values.Values[0].Value == "anoncvs") { t.Errorf("Expected fourth entry to be 'Match User anoncvs', but got: %v", fourthEntry.MatchValue) } @@ -440,7 +499,7 @@ Match Address 172.22.100.0/24,172.22.5.0/24,127.0.0.1 t.Errorf("Expected sixth entry to be 'Match Address 172.22.100.0/24,172.22.5.0/24,127.0.0.1', but got: %v", sixthEntry.MatchEntry.Value) } - if !(sixthEntry.MatchValue.Entries[0].Criteria == "Address" && len(sixthEntry.MatchValue.Entries[0].Values) == 3) { + if !(sixthEntry.MatchValue.Entries[0].Criteria.Type == "Address" && len(sixthEntry.MatchValue.Entries[0].Values.Values) == 3) { t.Errorf("Expected sixth entry to contain 3 values, but got: %v", sixthEntry.MatchValue) } diff --git a/handlers/sshd_config/ast/sshd_config.go b/handlers/sshd_config/ast/sshd_config.go index b448f63..fb2edee 100644 --- a/handlers/sshd_config/ast/sshd_config.go +++ b/handlers/sshd_config/ast/sshd_config.go @@ -42,14 +42,6 @@ type SSHOption struct { OptionValue *SSHValue } -func (o SSHOption) GetType() SSHEntryType { - return SSHEntryTypeOption -} - -func (o SSHOption) GetOption() SSHOption { - return o -} - type SSHMatchBlock struct { common.LocationRange MatchEntry *SSHOption @@ -59,66 +51,9 @@ type SSHMatchBlock struct { Options *treemap.Map } -func (m SSHMatchBlock) GetType() SSHEntryType { - return SSHEntryTypeMatchBlock -} - -func (m SSHMatchBlock) GetOption() SSHOption { - return *m.MatchEntry -} - type SSHConfig struct { // [uint32]SSHOption -> line number -> *SSHEntry Options *treemap.Map // [uint32]{} -> line number -> {} CommentLines map[uint32]struct{} } - -func (c SSHConfig) FindMatchBlock(line uint32) *SSHMatchBlock { - for currentLine := line; currentLine > 0; currentLine-- { - rawEntry, found := c.Options.Get(currentLine) - - if !found { - continue - } - - switch entry := rawEntry.(type) { - case *SSHMatchBlock: - return entry - } - } - - return nil -} - -func (c SSHConfig) FindOption(line uint32) (*SSHOption, *SSHMatchBlock) { - matchBlock := c.FindMatchBlock(line) - - if matchBlock != nil { - if line == matchBlock.MatchEntry.Start.Line { - return matchBlock.MatchEntry, matchBlock - } - - rawEntry, found := matchBlock.Options.Get(line) - - if found { - return rawEntry.(*SSHOption), matchBlock - } else { - return nil, matchBlock - } - } - - rawEntry, found := c.Options.Get(line) - - if found { - switch rawEntry.(type) { - case *SSHMatchBlock: - return rawEntry.(*SSHMatchBlock).MatchEntry, rawEntry.(*SSHMatchBlock) - case *SSHOption: - return rawEntry.(*SSHOption), nil - } - } - - return nil, nil - -} diff --git a/handlers/sshd_config/ast/sshd_config_fields.go b/handlers/sshd_config/ast/sshd_config_fields.go new file mode 100644 index 0000000..f82d49c --- /dev/null +++ b/handlers/sshd_config/ast/sshd_config_fields.go @@ -0,0 +1,66 @@ +package ast + +func (o SSHOption) GetType() SSHEntryType { + return SSHEntryTypeOption +} + +func (o SSHOption) GetOption() SSHOption { + return o +} + +func (m SSHMatchBlock) GetType() SSHEntryType { + return SSHEntryTypeMatchBlock +} + +func (m SSHMatchBlock) GetOption() SSHOption { + return *m.MatchEntry +} + +func (c SSHConfig) FindMatchBlock(line uint32) *SSHMatchBlock { + for currentLine := line; currentLine > 0; currentLine-- { + rawEntry, found := c.Options.Get(currentLine) + + if !found { + continue + } + + switch entry := rawEntry.(type) { + case *SSHMatchBlock: + return entry + } + } + + return nil +} + +func (c SSHConfig) FindOption(line uint32) (*SSHOption, *SSHMatchBlock) { + matchBlock := c.FindMatchBlock(line) + + if matchBlock != nil { + if line == matchBlock.MatchEntry.Start.Line { + return matchBlock.MatchEntry, matchBlock + } + + rawEntry, found := matchBlock.Options.Get(line) + + if found { + return rawEntry.(*SSHOption), matchBlock + } else { + return nil, matchBlock + } + } + + rawEntry, found := c.Options.Get(line) + + if found { + switch rawEntry.(type) { + case *SSHMatchBlock: + return rawEntry.(*SSHMatchBlock).MatchEntry, rawEntry.(*SSHMatchBlock) + case *SSHOption: + return rawEntry.(*SSHOption), nil + } + } + + return nil, nil + +} diff --git a/handlers/sshd_config/fields/fields.go b/handlers/sshd_config/fields/fields.go index b58c6f3..be277c3 100644 --- a/handlers/sshd_config/fields/fields.go +++ b/handlers/sshd_config/fields/fields.go @@ -563,27 +563,7 @@ The arguments to Match are one or more criteria-pattern pairs or the single toke The match patterns may consist of single entries or comma-separated lists and may use the wildcard and negation operators described in the “PATTERNS” section of ssh_config(5). The patterns in an Address criteria may additionally contain addresses to match in CIDR address/masklen format, such as 192.0.2.0/24 or 2001:db8::/32. Note that the mask length provided must be consistent with the address - it is an error to specify a mask length that is too long for the address or one with bits set in this host portion of the address. For example, 192.0.2.0/33 and 192.0.2.0/8, respectively. Only a subset of keywords may be used on the lines following a Match keyword. Available keywords are AcceptEnv, AllowAgentForwarding, AllowGroups, AllowStreamLocalForwarding, AllowTcpForwarding, AllowUsers, AuthenticationMethods, AuthorizedKeysCommand, AuthorizedKeysCommandUser, AuthorizedKeysFile, AuthorizedPrincipalsCommand, AuthorizedPrincipalsCommandUser, AuthorizedPrincipalsFile, Banner, CASignatureAlgorithms, ChannelTimeout, ChrootDirectory, ClientAliveCountMax, ClientAliveInterval, DenyGroups, DenyUsers, DisableForwarding, ExposeAuthInfo, ForceCommand, GatewayPorts, GSSAPIAuthentication, HostbasedAcceptedAlgorithms, HostbasedAuthentication, HostbasedUsesNameFromPacketOnly, IgnoreRhosts, Include, IPQoS, KbdInteractiveAuthentication, KerberosAuthentication, LogLevel, MaxAuthTries, MaxSessions, PasswordAuthentication, PermitEmptyPasswords, PermitListen, PermitOpen, PermitRootLogin, PermitTTY, PermitTunnel, PermitUserRC, PubkeyAcceptedAlgorithms, PubkeyAuthentication, PubkeyAuthOptions, RekeyLimit, RevokedKeys, RDomain, SetEnv, StreamLocalBindMask, StreamLocalBindUnlink, TrustedUserCAKeys, UnusedConnectionTimeout, X11DisplayOffset, X11Forwarding and X11UseLocalhost.`, - Value: docvalues.OrValue{ - Values: []docvalues.Value{ - docvalues.SingleEnumValue("All"), - docvalues.ArrayValue{ - Separator: ",", - DuplicatesExtractor: &docvalues.SimpleDuplicatesExtractor, - SubValue: docvalues.KeyEnumAssignmentValue{ - Separator: " ", - Values: map[docvalues.EnumString]docvalues.Value{ - docvalues.CreateEnumString("User"): docvalues.UserValue("", false), - docvalues.CreateEnumString("Group"): docvalues.GroupValue("", false), - docvalues.CreateEnumString("Host"): docvalues.StringValue{}, - docvalues.CreateEnumString("LocalAddress"): docvalues.StringValue{}, - docvalues.CreateEnumString("LocalPort"): docvalues.NumberValue{Min: &ZERO, Max: &MAX_PORT}, - docvalues.CreateEnumString("RDomain"): docvalues.StringValue{}, - docvalues.CreateEnumString("Address"): docvalues.StringValue{}, - }, - }, - }, - }, - }, + Value: docvalues.StringValue{}, }, "MaxAuthTries": { Documentation: `Specifies the maximum number of authentication attempts permitted per connection. Once the number of failures reaches half this value, additional failures are logged. The default is 6.`, diff --git a/handlers/sshd_config/fields/match-parser/Match.g4 b/handlers/sshd_config/fields/match-parser/Match.g4 index 9c2c54b..d7b77a4 100644 --- a/handlers/sshd_config/fields/match-parser/Match.g4 +++ b/handlers/sshd_config/fields/match-parser/Match.g4 @@ -1,25 +1,23 @@ grammar Match; root - : matchEntry? (WHITESPACE matchEntry)* EOF + : matchEntry? (WHITESPACE matchEntry?)* EOF ; matchEntry - : criteria WHITESPACE? values? + : criteria separator? values? + ; + +separator + : WHITESPACE ; criteria - : USER - | GROUP - | HOST - | LOCALADDRESS - | LOCALPORT - | RDOMAIN - | ADDRESS + : (USER | GROUP | HOST| LOCALADDRESS | LOCALPORT | RDOMAIN | ADDRESS) ; values - : value? (COMMA value?)* + : value (COMMA value?)* ; value diff --git a/handlers/sshd_config/fields/match-parser/listener.go b/handlers/sshd_config/fields/match-parser/listener.go index a7ca5ac..31bc0cb 100644 --- a/handlers/sshd_config/fields/match-parser/listener.go +++ b/handlers/sshd_config/fields/match-parser/listener.go @@ -11,16 +11,19 @@ import ( func createMatchListenerContext( line uint32, + startCharacter uint32, ) *matchListenerContext { return &matchListenerContext{ - currentEntry: nil, - line: line, + currentEntry: nil, + line: line, + startCharacter: startCharacter, } } type matchListenerContext struct { - currentEntry *MatchEntry - line uint32 + currentEntry *MatchEntry + line uint32 + startCharacter uint32 } func createListener( @@ -42,13 +45,12 @@ type matchParserListener struct { } func (s *matchParserListener) EnterMatchEntry(ctx *parser.MatchEntryContext) { - location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext) + location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext).ShiftHorizontal(s.matchContext.startCharacter) location.ChangeBothLines(s.matchContext.line) entry := &MatchEntry{ LocationRange: location, Value: ctx.GetText(), - Values: make([]*MatchValue, 0), } s.match.Entries = append(s.match.Entries, entry) @@ -70,7 +72,7 @@ var availableCriteria = map[string]MatchCriteriaType{ } func (s *matchParserListener) EnterCriteria(ctx *parser.CriteriaContext) { - location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext) + location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext).ShiftHorizontal(s.matchContext.startCharacter) location.ChangeBothLines(s.matchContext.line) criteria, found := availableCriteria[ctx.GetText()] @@ -83,11 +85,33 @@ func (s *matchParserListener) EnterCriteria(ctx *parser.CriteriaContext) { return } - s.matchContext.currentEntry.Criteria = criteria + s.matchContext.currentEntry.Criteria = MatchCriteria{ + LocationRange: location, + Type: criteria, + } +} + +func (s *matchParserListener) EnterSeparator(ctx *parser.SeparatorContext) { + location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext).ShiftHorizontal(s.matchContext.startCharacter) + location.ChangeBothLines(s.matchContext.line) + + s.matchContext.currentEntry.Separator = &MatchSeparator{ + LocationRange: location, + } +} + +func (s *matchParserListener) EnterValues(ctx *parser.ValuesContext) { + location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext).ShiftHorizontal(s.matchContext.startCharacter) + location.ChangeBothLines(s.matchContext.line) + + s.matchContext.currentEntry.Values = &MatchValues{ + LocationRange: location, + Values: make([]*MatchValue, 0), + } } func (s *matchParserListener) EnterValue(ctx *parser.ValueContext) { - location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext) + location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext).ShiftHorizontal(s.matchContext.startCharacter) location.ChangeBothLines(s.matchContext.line) value := &MatchValue{ @@ -95,5 +119,5 @@ func (s *matchParserListener) EnterValue(ctx *parser.ValueContext) { Value: ctx.GetText(), } - s.matchContext.currentEntry.Values = append(s.matchContext.currentEntry.Values, value) + s.matchContext.currentEntry.Values.Values = append(s.matchContext.currentEntry.Values.Values, value) } diff --git a/handlers/sshd_config/fields/match-parser/match_ast.go b/handlers/sshd_config/fields/match-parser/match_ast.go index 2f97f1f..d02a693 100644 --- a/handlers/sshd_config/fields/match-parser/match_ast.go +++ b/handlers/sshd_config/fields/match-parser/match_ast.go @@ -26,12 +26,23 @@ type MatchCriteria struct { Type MatchCriteriaType } +type MatchSeparator struct { + common.LocationRange +} + +type MatchValues struct { + common.LocationRange + + Values []*MatchValue +} + type MatchEntry struct { common.LocationRange Value string - Criteria MatchCriteriaType - Values []*MatchValue + Criteria MatchCriteria + Separator *MatchSeparator + Values *MatchValues } type MatchValue struct { diff --git a/handlers/sshd_config/fields/match-parser/match_fields.go b/handlers/sshd_config/fields/match-parser/match_fields.go new file mode 100644 index 0000000..dca690d --- /dev/null +++ b/handlers/sshd_config/fields/match-parser/match_fields.go @@ -0,0 +1,67 @@ +package match_parser + +import "slices" + +func (m Match) GetEntryByCursor(cursor uint32) *MatchEntry { + index, found := slices.BinarySearchFunc( + m.Entries, + cursor, + func(entry *MatchEntry, cursor uint32) int { + if cursor < entry.Start.Character { + return 1 + } + + if cursor > entry.End.Character { + return -1 + } + + return 0 + }, + ) + + if !found { + return nil + } + + entry := m.Entries[index] + + return entry +} + +func (c MatchCriteria) IsCursorBetween(cursor uint32) bool { + return cursor >= c.Start.Character && cursor <= c.End.Character +} + +func (e MatchEntry) GetValueByCursor(cursor uint32) *MatchValue { + if e.Values == nil { + return nil + } + + index, found := slices.BinarySearchFunc( + e.Values.Values, + cursor, + func(value *MatchValue, cursor uint32) int { + if cursor < value.Start.Character { + return 1 + } + + if cursor > value.End.Character { + return -1 + } + + return 0 + }, + ) + + if !found { + return nil + } + + value := e.Values.Values[index] + + return value +} + +func (v MatchValues) IsCursorBetween(cursor uint32) bool { + return cursor >= v.Start.Character && cursor <= v.End.Character +} diff --git a/handlers/sshd_config/fields/match-parser/parser.go b/handlers/sshd_config/fields/match-parser/parser.go index 96fe394..abbc2f7 100644 --- a/handlers/sshd_config/fields/match-parser/parser.go +++ b/handlers/sshd_config/fields/match-parser/parser.go @@ -20,8 +20,9 @@ func (m *Match) Clear() { func (m *Match) Parse( input string, line uint32, + startCharacter uint32, ) []common.LSPError { - context := createMatchListenerContext(line) + context := createMatchListenerContext(line, startCharacter) stream := antlr.NewInputStream(input) diff --git a/handlers/sshd_config/fields/match-parser/parser/Match.interp b/handlers/sshd_config/fields/match-parser/parser/Match.interp index 274d459..1291067 100644 --- a/handlers/sshd_config/fields/match-parser/parser/Match.interp +++ b/handlers/sshd_config/fields/match-parser/parser/Match.interp @@ -27,10 +27,11 @@ WHITESPACE rule names: root matchEntry +separator criteria values value atn: -[4, 1, 10, 46, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 1, 0, 3, 0, 12, 8, 0, 1, 0, 1, 0, 5, 0, 16, 8, 0, 10, 0, 12, 0, 19, 9, 0, 1, 0, 1, 0, 1, 1, 1, 1, 3, 1, 25, 8, 1, 1, 1, 3, 1, 28, 8, 1, 1, 2, 1, 2, 1, 3, 3, 3, 33, 8, 3, 1, 3, 1, 3, 3, 3, 37, 8, 3, 5, 3, 39, 8, 3, 10, 3, 12, 3, 42, 9, 3, 1, 4, 1, 4, 1, 4, 0, 0, 5, 0, 2, 4, 6, 8, 0, 1, 1, 0, 1, 7, 47, 0, 11, 1, 0, 0, 0, 2, 22, 1, 0, 0, 0, 4, 29, 1, 0, 0, 0, 6, 32, 1, 0, 0, 0, 8, 43, 1, 0, 0, 0, 10, 12, 3, 2, 1, 0, 11, 10, 1, 0, 0, 0, 11, 12, 1, 0, 0, 0, 12, 17, 1, 0, 0, 0, 13, 14, 5, 10, 0, 0, 14, 16, 3, 2, 1, 0, 15, 13, 1, 0, 0, 0, 16, 19, 1, 0, 0, 0, 17, 15, 1, 0, 0, 0, 17, 18, 1, 0, 0, 0, 18, 20, 1, 0, 0, 0, 19, 17, 1, 0, 0, 0, 20, 21, 5, 0, 0, 1, 21, 1, 1, 0, 0, 0, 22, 24, 3, 4, 2, 0, 23, 25, 5, 10, 0, 0, 24, 23, 1, 0, 0, 0, 24, 25, 1, 0, 0, 0, 25, 27, 1, 0, 0, 0, 26, 28, 3, 6, 3, 0, 27, 26, 1, 0, 0, 0, 27, 28, 1, 0, 0, 0, 28, 3, 1, 0, 0, 0, 29, 30, 7, 0, 0, 0, 30, 5, 1, 0, 0, 0, 31, 33, 3, 8, 4, 0, 32, 31, 1, 0, 0, 0, 32, 33, 1, 0, 0, 0, 33, 40, 1, 0, 0, 0, 34, 36, 5, 8, 0, 0, 35, 37, 3, 8, 4, 0, 36, 35, 1, 0, 0, 0, 36, 37, 1, 0, 0, 0, 37, 39, 1, 0, 0, 0, 38, 34, 1, 0, 0, 0, 39, 42, 1, 0, 0, 0, 40, 38, 1, 0, 0, 0, 40, 41, 1, 0, 0, 0, 41, 7, 1, 0, 0, 0, 42, 40, 1, 0, 0, 0, 43, 44, 5, 9, 0, 0, 44, 9, 1, 0, 0, 0, 7, 11, 17, 24, 27, 32, 36, 40] \ No newline at end of file +[4, 1, 10, 50, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 1, 0, 3, 0, 14, 8, 0, 1, 0, 1, 0, 3, 0, 18, 8, 0, 5, 0, 20, 8, 0, 10, 0, 12, 0, 23, 9, 0, 1, 0, 1, 0, 1, 1, 1, 1, 3, 1, 29, 8, 1, 1, 1, 3, 1, 32, 8, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 3, 4, 41, 8, 4, 5, 4, 43, 8, 4, 10, 4, 12, 4, 46, 9, 4, 1, 5, 1, 5, 1, 5, 0, 0, 6, 0, 2, 4, 6, 8, 10, 0, 1, 1, 0, 1, 7, 50, 0, 13, 1, 0, 0, 0, 2, 26, 1, 0, 0, 0, 4, 33, 1, 0, 0, 0, 6, 35, 1, 0, 0, 0, 8, 37, 1, 0, 0, 0, 10, 47, 1, 0, 0, 0, 12, 14, 3, 2, 1, 0, 13, 12, 1, 0, 0, 0, 13, 14, 1, 0, 0, 0, 14, 21, 1, 0, 0, 0, 15, 17, 5, 10, 0, 0, 16, 18, 3, 2, 1, 0, 17, 16, 1, 0, 0, 0, 17, 18, 1, 0, 0, 0, 18, 20, 1, 0, 0, 0, 19, 15, 1, 0, 0, 0, 20, 23, 1, 0, 0, 0, 21, 19, 1, 0, 0, 0, 21, 22, 1, 0, 0, 0, 22, 24, 1, 0, 0, 0, 23, 21, 1, 0, 0, 0, 24, 25, 5, 0, 0, 1, 25, 1, 1, 0, 0, 0, 26, 28, 3, 6, 3, 0, 27, 29, 3, 4, 2, 0, 28, 27, 1, 0, 0, 0, 28, 29, 1, 0, 0, 0, 29, 31, 1, 0, 0, 0, 30, 32, 3, 8, 4, 0, 31, 30, 1, 0, 0, 0, 31, 32, 1, 0, 0, 0, 32, 3, 1, 0, 0, 0, 33, 34, 5, 10, 0, 0, 34, 5, 1, 0, 0, 0, 35, 36, 7, 0, 0, 0, 36, 7, 1, 0, 0, 0, 37, 44, 3, 10, 5, 0, 38, 40, 5, 8, 0, 0, 39, 41, 3, 10, 5, 0, 40, 39, 1, 0, 0, 0, 40, 41, 1, 0, 0, 0, 41, 43, 1, 0, 0, 0, 42, 38, 1, 0, 0, 0, 43, 46, 1, 0, 0, 0, 44, 42, 1, 0, 0, 0, 44, 45, 1, 0, 0, 0, 45, 9, 1, 0, 0, 0, 46, 44, 1, 0, 0, 0, 47, 48, 5, 9, 0, 0, 48, 11, 1, 0, 0, 0, 7, 13, 17, 21, 28, 31, 40, 44] \ No newline at end of file diff --git a/handlers/sshd_config/fields/match-parser/parser/match_base_listener.go b/handlers/sshd_config/fields/match-parser/parser/match_base_listener.go index 2190a4a..85a1a79 100644 --- a/handlers/sshd_config/fields/match-parser/parser/match_base_listener.go +++ b/handlers/sshd_config/fields/match-parser/parser/match_base_listener.go @@ -33,6 +33,12 @@ func (s *BaseMatchListener) EnterMatchEntry(ctx *MatchEntryContext) {} // ExitMatchEntry is called when production matchEntry is exited. func (s *BaseMatchListener) ExitMatchEntry(ctx *MatchEntryContext) {} +// EnterSeparator is called when production separator is entered. +func (s *BaseMatchListener) EnterSeparator(ctx *SeparatorContext) {} + +// ExitSeparator is called when production separator is exited. +func (s *BaseMatchListener) ExitSeparator(ctx *SeparatorContext) {} + // EnterCriteria is called when production criteria is entered. func (s *BaseMatchListener) EnterCriteria(ctx *CriteriaContext) {} diff --git a/handlers/sshd_config/fields/match-parser/parser/match_listener.go b/handlers/sshd_config/fields/match-parser/parser/match_listener.go index 8db9553..4adb71d 100644 --- a/handlers/sshd_config/fields/match-parser/parser/match_listener.go +++ b/handlers/sshd_config/fields/match-parser/parser/match_listener.go @@ -14,6 +14,9 @@ type MatchListener interface { // EnterMatchEntry is called when entering the matchEntry production. EnterMatchEntry(c *MatchEntryContext) + // EnterSeparator is called when entering the separator production. + EnterSeparator(c *SeparatorContext) + // EnterCriteria is called when entering the criteria production. EnterCriteria(c *CriteriaContext) @@ -29,6 +32,9 @@ type MatchListener interface { // ExitMatchEntry is called when exiting the matchEntry production. ExitMatchEntry(c *MatchEntryContext) + // ExitSeparator is called when exiting the separator production. + ExitSeparator(c *SeparatorContext) + // ExitCriteria is called when exiting the criteria production. ExitCriteria(c *CriteriaContext) diff --git a/handlers/sshd_config/fields/match-parser/parser/match_parser.go b/handlers/sshd_config/fields/match-parser/parser/match_parser.go index 79a4ea4..79f54ba 100644 --- a/handlers/sshd_config/fields/match-parser/parser/match_parser.go +++ b/handlers/sshd_config/fields/match-parser/parser/match_parser.go @@ -40,29 +40,30 @@ func matchParserInit() { "ADDRESS", "COMMA", "STRING", "WHITESPACE", } staticData.RuleNames = []string{ - "root", "matchEntry", "criteria", "values", "value", + "root", "matchEntry", "separator", "criteria", "values", "value", } staticData.PredictionContextCache = antlr.NewPredictionContextCache() staticData.serializedATN = []int32{ - 4, 1, 10, 46, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, - 4, 1, 0, 3, 0, 12, 8, 0, 1, 0, 1, 0, 5, 0, 16, 8, 0, 10, 0, 12, 0, 19, - 9, 0, 1, 0, 1, 0, 1, 1, 1, 1, 3, 1, 25, 8, 1, 1, 1, 3, 1, 28, 8, 1, 1, - 2, 1, 2, 1, 3, 3, 3, 33, 8, 3, 1, 3, 1, 3, 3, 3, 37, 8, 3, 5, 3, 39, 8, - 3, 10, 3, 12, 3, 42, 9, 3, 1, 4, 1, 4, 1, 4, 0, 0, 5, 0, 2, 4, 6, 8, 0, - 1, 1, 0, 1, 7, 47, 0, 11, 1, 0, 0, 0, 2, 22, 1, 0, 0, 0, 4, 29, 1, 0, 0, - 0, 6, 32, 1, 0, 0, 0, 8, 43, 1, 0, 0, 0, 10, 12, 3, 2, 1, 0, 11, 10, 1, - 0, 0, 0, 11, 12, 1, 0, 0, 0, 12, 17, 1, 0, 0, 0, 13, 14, 5, 10, 0, 0, 14, - 16, 3, 2, 1, 0, 15, 13, 1, 0, 0, 0, 16, 19, 1, 0, 0, 0, 17, 15, 1, 0, 0, - 0, 17, 18, 1, 0, 0, 0, 18, 20, 1, 0, 0, 0, 19, 17, 1, 0, 0, 0, 20, 21, - 5, 0, 0, 1, 21, 1, 1, 0, 0, 0, 22, 24, 3, 4, 2, 0, 23, 25, 5, 10, 0, 0, - 24, 23, 1, 0, 0, 0, 24, 25, 1, 0, 0, 0, 25, 27, 1, 0, 0, 0, 26, 28, 3, - 6, 3, 0, 27, 26, 1, 0, 0, 0, 27, 28, 1, 0, 0, 0, 28, 3, 1, 0, 0, 0, 29, - 30, 7, 0, 0, 0, 30, 5, 1, 0, 0, 0, 31, 33, 3, 8, 4, 0, 32, 31, 1, 0, 0, - 0, 32, 33, 1, 0, 0, 0, 33, 40, 1, 0, 0, 0, 34, 36, 5, 8, 0, 0, 35, 37, - 3, 8, 4, 0, 36, 35, 1, 0, 0, 0, 36, 37, 1, 0, 0, 0, 37, 39, 1, 0, 0, 0, - 38, 34, 1, 0, 0, 0, 39, 42, 1, 0, 0, 0, 40, 38, 1, 0, 0, 0, 40, 41, 1, - 0, 0, 0, 41, 7, 1, 0, 0, 0, 42, 40, 1, 0, 0, 0, 43, 44, 5, 9, 0, 0, 44, - 9, 1, 0, 0, 0, 7, 11, 17, 24, 27, 32, 36, 40, + 4, 1, 10, 50, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, + 4, 2, 5, 7, 5, 1, 0, 3, 0, 14, 8, 0, 1, 0, 1, 0, 3, 0, 18, 8, 0, 5, 0, + 20, 8, 0, 10, 0, 12, 0, 23, 9, 0, 1, 0, 1, 0, 1, 1, 1, 1, 3, 1, 29, 8, + 1, 1, 1, 3, 1, 32, 8, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 3, 4, + 41, 8, 4, 5, 4, 43, 8, 4, 10, 4, 12, 4, 46, 9, 4, 1, 5, 1, 5, 1, 5, 0, + 0, 6, 0, 2, 4, 6, 8, 10, 0, 1, 1, 0, 1, 7, 50, 0, 13, 1, 0, 0, 0, 2, 26, + 1, 0, 0, 0, 4, 33, 1, 0, 0, 0, 6, 35, 1, 0, 0, 0, 8, 37, 1, 0, 0, 0, 10, + 47, 1, 0, 0, 0, 12, 14, 3, 2, 1, 0, 13, 12, 1, 0, 0, 0, 13, 14, 1, 0, 0, + 0, 14, 21, 1, 0, 0, 0, 15, 17, 5, 10, 0, 0, 16, 18, 3, 2, 1, 0, 17, 16, + 1, 0, 0, 0, 17, 18, 1, 0, 0, 0, 18, 20, 1, 0, 0, 0, 19, 15, 1, 0, 0, 0, + 20, 23, 1, 0, 0, 0, 21, 19, 1, 0, 0, 0, 21, 22, 1, 0, 0, 0, 22, 24, 1, + 0, 0, 0, 23, 21, 1, 0, 0, 0, 24, 25, 5, 0, 0, 1, 25, 1, 1, 0, 0, 0, 26, + 28, 3, 6, 3, 0, 27, 29, 3, 4, 2, 0, 28, 27, 1, 0, 0, 0, 28, 29, 1, 0, 0, + 0, 29, 31, 1, 0, 0, 0, 30, 32, 3, 8, 4, 0, 31, 30, 1, 0, 0, 0, 31, 32, + 1, 0, 0, 0, 32, 3, 1, 0, 0, 0, 33, 34, 5, 10, 0, 0, 34, 5, 1, 0, 0, 0, + 35, 36, 7, 0, 0, 0, 36, 7, 1, 0, 0, 0, 37, 44, 3, 10, 5, 0, 38, 40, 5, + 8, 0, 0, 39, 41, 3, 10, 5, 0, 40, 39, 1, 0, 0, 0, 40, 41, 1, 0, 0, 0, 41, + 43, 1, 0, 0, 0, 42, 38, 1, 0, 0, 0, 43, 46, 1, 0, 0, 0, 44, 42, 1, 0, 0, + 0, 44, 45, 1, 0, 0, 0, 45, 9, 1, 0, 0, 0, 46, 44, 1, 0, 0, 0, 47, 48, 5, + 9, 0, 0, 48, 11, 1, 0, 0, 0, 7, 13, 17, 21, 28, 31, 40, 44, } deserializer := antlr.NewATNDeserializer(nil) staticData.atn = deserializer.Deserialize(staticData.serializedATN) @@ -117,9 +118,10 @@ const ( const ( MatchParserRULE_root = 0 MatchParserRULE_matchEntry = 1 - MatchParserRULE_criteria = 2 - MatchParserRULE_values = 3 - MatchParserRULE_value = 4 + MatchParserRULE_separator = 2 + MatchParserRULE_criteria = 3 + MatchParserRULE_values = 4 + MatchParserRULE_value = 5 ) // IRootContext is an interface to support dynamic dispatch. @@ -251,7 +253,7 @@ func (p *MatchParser) Root() (localctx IRootContext) { var _la int p.EnterOuterAlt(localctx, 1) - p.SetState(11) + p.SetState(13) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit @@ -260,12 +262,12 @@ func (p *MatchParser) Root() (localctx IRootContext) { if (int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&254) != 0 { { - p.SetState(10) + p.SetState(12) p.MatchEntry() } } - p.SetState(17) + p.SetState(21) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit @@ -274,19 +276,29 @@ func (p *MatchParser) Root() (localctx IRootContext) { for _la == MatchParserWHITESPACE { { - p.SetState(13) + p.SetState(15) p.Match(MatchParserWHITESPACE) if p.HasError() { // Recognition error - abort rule goto errorExit } } - { - p.SetState(14) - p.MatchEntry() + p.SetState(17) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + if (int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&254) != 0 { + { + p.SetState(16) + p.MatchEntry() + } + } - p.SetState(19) + p.SetState(23) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit @@ -294,7 +306,7 @@ func (p *MatchParser) Root() (localctx IRootContext) { _la = p.GetTokenStream().LA(1) } { - p.SetState(20) + p.SetState(24) p.Match(MatchParserEOF) if p.HasError() { // Recognition error - abort rule @@ -324,7 +336,7 @@ type IMatchEntryContext interface { // Getter signatures Criteria() ICriteriaContext - WHITESPACE() antlr.TerminalNode + Separator() ISeparatorContext Values() IValuesContext // IsMatchEntryContext differentiates from other interfaces. @@ -379,8 +391,20 @@ func (s *MatchEntryContext) Criteria() ICriteriaContext { return t.(ICriteriaContext) } -func (s *MatchEntryContext) WHITESPACE() antlr.TerminalNode { - return s.GetToken(MatchParserWHITESPACE, 0) +func (s *MatchEntryContext) Separator() ISeparatorContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ISeparatorContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(ISeparatorContext) } func (s *MatchEntryContext) Values() IValuesContext { @@ -422,39 +446,135 @@ func (s *MatchEntryContext) ExitRule(listener antlr.ParseTreeListener) { func (p *MatchParser) MatchEntry() (localctx IMatchEntryContext) { localctx = NewMatchEntryContext(p, p.GetParserRuleContext(), p.GetState()) p.EnterRule(localctx, 2, MatchParserRULE_matchEntry) + var _la int + p.EnterOuterAlt(localctx, 1) { - p.SetState(22) + p.SetState(26) p.Criteria() } - p.SetState(24) - p.GetErrorHandler().Sync(p) - - if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 2, p.GetParserRuleContext()) == 1 { - { - p.SetState(23) - p.Match(MatchParserWHITESPACE) - if p.HasError() { - // Recognition error - abort rule - goto errorExit - } - } - - } else if p.HasError() { // JIM - goto errorExit - } - p.SetState(27) + p.SetState(28) p.GetErrorHandler().Sync(p) if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 3, p.GetParserRuleContext()) == 1 { { - p.SetState(26) - p.Values() + p.SetState(27) + p.Separator() } } else if p.HasError() { // JIM goto errorExit } + p.SetState(31) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + if _la == MatchParserSTRING { + { + p.SetState(30) + p.Values() + } + + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// ISeparatorContext is an interface to support dynamic dispatch. +type ISeparatorContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + WHITESPACE() antlr.TerminalNode + + // IsSeparatorContext differentiates from other interfaces. + IsSeparatorContext() +} + +type SeparatorContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptySeparatorContext() *SeparatorContext { + var p = new(SeparatorContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = MatchParserRULE_separator + return p +} + +func InitEmptySeparatorContext(p *SeparatorContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = MatchParserRULE_separator +} + +func (*SeparatorContext) IsSeparatorContext() {} + +func NewSeparatorContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *SeparatorContext { + var p = new(SeparatorContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = MatchParserRULE_separator + + return p +} + +func (s *SeparatorContext) GetParser() antlr.Parser { return s.parser } + +func (s *SeparatorContext) WHITESPACE() antlr.TerminalNode { + return s.GetToken(MatchParserWHITESPACE, 0) +} + +func (s *SeparatorContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *SeparatorContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *SeparatorContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(MatchListener); ok { + listenerT.EnterSeparator(s) + } +} + +func (s *SeparatorContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(MatchListener); ok { + listenerT.ExitSeparator(s) + } +} + +func (p *MatchParser) Separator() (localctx ISeparatorContext) { + localctx = NewSeparatorContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 4, MatchParserRULE_separator) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(33) + p.Match(MatchParserWHITESPACE) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } errorExit: if p.HasError() { @@ -571,12 +691,12 @@ func (s *CriteriaContext) ExitRule(listener antlr.ParseTreeListener) { func (p *MatchParser) Criteria() (localctx ICriteriaContext) { localctx = NewCriteriaContext(p, p.GetParserRuleContext(), p.GetState()) - p.EnterRule(localctx, 4, MatchParserRULE_criteria) + p.EnterRule(localctx, 6, MatchParserRULE_criteria) var _la int p.EnterOuterAlt(localctx, 1) { - p.SetState(29) + p.SetState(35) _la = p.GetTokenStream().LA(1) if !((int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&254) != 0) { @@ -720,25 +840,15 @@ func (s *ValuesContext) ExitRule(listener antlr.ParseTreeListener) { func (p *MatchParser) Values() (localctx IValuesContext) { localctx = NewValuesContext(p, p.GetParserRuleContext(), p.GetState()) - p.EnterRule(localctx, 6, MatchParserRULE_values) + p.EnterRule(localctx, 8, MatchParserRULE_values) var _la int p.EnterOuterAlt(localctx, 1) - p.SetState(32) - p.GetErrorHandler().Sync(p) - if p.HasError() { - goto errorExit + { + p.SetState(37) + p.Value() } - _la = p.GetTokenStream().LA(1) - - if _la == MatchParserSTRING { - { - p.SetState(31) - p.Value() - } - - } - p.SetState(40) + p.SetState(44) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit @@ -747,14 +857,14 @@ func (p *MatchParser) Values() (localctx IValuesContext) { for _la == MatchParserCOMMA { { - p.SetState(34) + p.SetState(38) p.Match(MatchParserCOMMA) if p.HasError() { // Recognition error - abort rule goto errorExit } } - p.SetState(36) + p.SetState(40) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit @@ -763,13 +873,13 @@ func (p *MatchParser) Values() (localctx IValuesContext) { if _la == MatchParserSTRING { { - p.SetState(35) + p.SetState(39) p.Value() } } - p.SetState(42) + p.SetState(46) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit @@ -862,10 +972,10 @@ func (s *ValueContext) ExitRule(listener antlr.ParseTreeListener) { func (p *MatchParser) Value() (localctx IValueContext) { localctx = NewValueContext(p, p.GetParserRuleContext(), p.GetState()) - p.EnterRule(localctx, 8, MatchParserRULE_value) + p.EnterRule(localctx, 10, MatchParserRULE_value) p.EnterOuterAlt(localctx, 1) { - p.SetState(43) + p.SetState(47) p.Match(MatchParserSTRING) if p.HasError() { // Recognition error - abort rule diff --git a/handlers/sshd_config/fields/match-parser/parser_test.go b/handlers/sshd_config/fields/match-parser/parser_test.go index df9395c..d24343a 100644 --- a/handlers/sshd_config/fields/match-parser/parser_test.go +++ b/handlers/sshd_config/fields/match-parser/parser_test.go @@ -7,10 +7,11 @@ import ( func TestComplexExample( t *testing.T, ) { + offset := uint32(5) input := "User root,admin,alice Address *,!192.168.0.1" match := NewMatch() - errors := match.Parse(input, 32) + errors := match.Parse(input, 32, offset) if len(errors) > 0 { t.Fatalf("Expected no errors, but got %v", errors) @@ -20,32 +21,32 @@ func TestComplexExample( t.Fatalf("Expected 2 entries, but got %v", len(match.Entries)) } - if !(match.Entries[0].Criteria == MatchCriteriaTypeUser) { + if !(match.Entries[0].Criteria.Type == MatchCriteriaTypeUser) { t.Fatalf("Expected User, but got %v", match.Entries[0]) } - if !(match.Entries[0].Values[0].Value == "root") { - t.Fatalf("Expected root, but got %v", match.Entries[0].Values[0]) + if !(match.Entries[0].Values.Values[0].Value == "root" && match.Entries[0].Values.Values[0].Start.Character == 5+offset && match.Entries[0].Values.Values[0].End.Character == 8+offset && match.Entries[0].Start.Character == 0+offset && match.Entries[0].End.Character == 20+offset) { + t.Errorf("Expected root, but got %v", match.Entries[0].Values.Values[0]) } - if !(match.Entries[0].Values[1].Value == "admin") { - t.Fatalf("Expected admin, but got %v", match.Entries[0].Values[1]) + if !(match.Entries[0].Values.Values[1].Value == "admin" && match.Entries[0].Values.Values[1].Start.Character == 10+offset && match.Entries[0].Values.Values[1].End.Character == 14+offset) { + t.Errorf("Expected admin, but got %v", match.Entries[0].Values.Values[1]) } - if !(match.Entries[0].Values[2].Value == "alice") { - t.Fatalf("Expected alice, but got %v", match.Entries[0].Values[2]) + if !(match.Entries[0].Values.Values[2].Value == "alice" && match.Entries[0].Values.Values[2].Start.Character == 16+offset && match.Entries[0].Values.Values[2].End.Character == 20+offset) { + t.Errorf("Expected alice, but got %v", match.Entries[0].Values.Values[2]) } - if !(match.Entries[1].Criteria == MatchCriteriaTypeAddress) { - t.Fatalf("Expected Address, but got %v", match.Entries[1]) + if !(match.Entries[1].Criteria.Type == MatchCriteriaTypeAddress) { + t.Errorf("Expected Address, but got %v", match.Entries[1]) } - if !(match.Entries[1].Values[0].Value == "*") { - t.Fatalf("Expected *, but got %v", match.Entries[1].Values[0]) + if !(match.Entries[1].Values.Values[0].Value == "*" && match.Entries[1].Values.Values[0].Start.Character == 30+offset && match.Entries[1].Values.Values[0].End.Character == 30+offset) { + t.Errorf("Expected *, but got %v", match.Entries[1].Values.Values[0]) } - if !(match.Entries[1].Values[1].Value == "!192.168.0.1") { - t.Fatalf("Expected !192.168.0.1, but got %v", match.Entries[1].Values[1]) + if !(match.Entries[1].Values.Values[1].Value == "!192.168.0.1" && match.Entries[1].Values.Values[1].Start.Character == 32+offset && match.Entries[1].Values.Values[1].End.Character == 43+offset) { + t.Errorf("Expected !192.168.0.1, but got %v", match.Entries[1].Values.Values[1]) } } @@ -55,7 +56,7 @@ func TestSecondComplexExample( input := "Address 172.22.100.0/24,172.22.5.0/24,127.0.0.1" match := NewMatch() - errors := match.Parse(input, 0) + errors := match.Parse(input, 0, 20) if len(errors) > 0 { t.Fatalf("Expected no errors, but got %v", errors) @@ -65,15 +66,69 @@ func TestSecondComplexExample( t.Fatalf("Expected 1 entries, but got %v", len(match.Entries)) } - if !(match.Entries[0].Criteria == MatchCriteriaTypeAddress) { + if !(match.Entries[0].Criteria.Type == MatchCriteriaTypeAddress) { t.Fatalf("Expected Address, but got %v", match.Entries[0]) } - if !(len(match.Entries[0].Values) == 3) { - t.Fatalf("Expected 3 values, but got %v", len(match.Entries[0].Values)) + if !(len(match.Entries[0].Values.Values) == 3) { + t.Fatalf("Expected 3 values, but got %v", len(match.Entries[0].Values.Values)) } - if !(match.Entries[0].Values[0].Value == "172.22.100.0/24") { - t.Fatalf("Expected 172.22.100.0/24, but got %v", match.Entries[0].Values[0]) + if !(match.Entries[0].Values.Values[0].Value == "172.22.100.0/24") { + t.Fatalf("Expected 172.22.100.0/24, but got %v", match.Entries[0].Values.Values[0]) + } +} + +func TestIncompleteBetweenEntriesExample( + t *testing.T, +) { + input := "User root,admin,alice " + + match := NewMatch() + errors := match.Parse(input, 0, 0) + + if len(errors) > 0 { + t.Fatalf("Expected no errors, but got %v", errors) + } + + if !(len(match.Entries) == 1) { + t.Errorf("Expected 1 entries, but got %v", len(match.Entries)) + } + + if !(match.Entries[0].Criteria.Type == MatchCriteriaTypeUser) { + t.Errorf("Expected User, but got %v", match.Entries[0]) + } + + if !(len(match.Entries[0].Values.Values) == 3) { + t.Errorf("Expected 3 values, but got %v", len(match.Entries[0].Values.Values)) + } + + if !(match.Entries[0].Start.Character == 0 && match.Entries[0].End.Character == 20) { + t.Errorf("Expected 0-20, but got %v", match.Entries[0]) + } +} + +func TestIncompleteBetweenValuesExample( + t *testing.T, +) { + input := "User " + + match := NewMatch() + errors := match.Parse(input, 0, 0) + + if len(errors) > 0 { + t.Fatalf("Expected no errors, but got %v", errors) + } + + if !(len(match.Entries) == 1) { + t.Errorf("Expected 1 entries, but got %v", len(match.Entries)) + } + + if !(match.Entries[0].Criteria.Type == MatchCriteriaTypeUser) { + t.Errorf("Expected User, but got %v", match.Entries[0]) + } + + if !(match.Entries[0].Values == nil) { + t.Errorf("Expected 0 values, but got %v", match.Entries[0].Values) } } diff --git a/handlers/sshd_config/fields/match.go b/handlers/sshd_config/fields/match.go index 332c699..59da836 100644 --- a/handlers/sshd_config/fields/match.go +++ b/handlers/sshd_config/fields/match.go @@ -1,5 +1,7 @@ package fields +import docvalues "config-lsp/doc-values" + var MatchAllowedOptions = map[string]struct{}{ "AcceptEnv": {}, "AllowAgentForwarding": {}, @@ -61,3 +63,15 @@ var MatchAllowedOptions = map[string]struct{}{ "X11Forwarding": {}, "X11UseLocalhos": {}, } + +var MatchUserField = docvalues.UserValue("", false) +var MatchGroupField = docvalues.GroupValue("", false) +var MatchHostField = docvalues.DomainValue() +var MatchLocalAddressField = docvalues.StringValue{} +var MatchLocalPortField = docvalues.StringValue{} +var MatchRDomainField = docvalues.StringValue{} +var MatchAddressField = docvalues.IPAddressValue{ + AllowIPv4: true, + AllowIPv6: true, + AllowRange: true, +} diff --git a/handlers/sshd_config/handlers/completions.go b/handlers/sshd_config/handlers/completions.go index ceba5e7..f415b25 100644 --- a/handlers/sshd_config/handlers/completions.go +++ b/handlers/sshd_config/handlers/completions.go @@ -55,6 +55,7 @@ func GetRootCompletions( func GetOptionCompletions( d *sshdconfig.SSHDocument, entry *ast.SSHOption, + matchBlock *ast.SSHMatchBlock, cursor uint32, ) ([]protocol.CompletionItem, error) { option, found := fields.Options[entry.Key.Value] @@ -63,12 +64,19 @@ func GetOptionCompletions( return nil, nil } + if entry.Key.Value == "Match" { + return getMatchCompletions( + d, + matchBlock.MatchValue, + cursor-matchBlock.MatchEntry.Start.Character, + ) + } + + line := entry.OptionValue.Value + if entry.OptionValue == nil { return option.FetchCompletions("", 0), nil } - relativeCursor := common.CursorToCharacterIndex(cursor - entry.OptionValue.Start.Character) - line := entry.OptionValue.Value - - return option.FetchCompletions(line, relativeCursor), nil + return option.FetchCompletions(line, common.CursorToCharacterIndex(cursor)), nil } diff --git a/handlers/sshd_config/handlers/completions_match.go b/handlers/sshd_config/handlers/completions_match.go new file mode 100644 index 0000000..4a6fa2f --- /dev/null +++ b/handlers/sshd_config/handlers/completions_match.go @@ -0,0 +1,111 @@ +package handlers + +import ( + sshdconfig "config-lsp/handlers/sshd_config" + "config-lsp/handlers/sshd_config/fields" + match_parser "config-lsp/handlers/sshd_config/fields/match-parser" + + protocol "github.com/tliron/glsp/protocol_3_16" +) + +func getMatchCompletions( + d *sshdconfig.SSHDocument, + match *match_parser.Match, + cursor uint32, +) ([]protocol.CompletionItem, error) { + if len(match.Entries) == 0 { + completions := getMatchCriteriaCompletions() + completions = append(completions, getMatchAllKeywordCompletion()) + + return completions, nil + } + + entry := match.GetEntryByCursor(cursor) + + if entry == nil || entry.Criteria.IsCursorBetween(cursor) { + return getMatchCriteriaCompletions(), nil + } + + return getMatchValueCompletions(entry, cursor), nil +} + +func getMatchCriteriaCompletions() []protocol.CompletionItem { + kind := protocol.CompletionItemKindEnum + + return []protocol.CompletionItem{ + { + Label: string(match_parser.MatchCriteriaTypeUser), + Kind: &kind, + }, + { + Label: string(match_parser.MatchCriteriaTypeGroup), + Kind: &kind, + }, + { + Label: string(match_parser.MatchCriteriaTypeHost), + Kind: &kind, + }, + { + Label: string(match_parser.MatchCriteriaTypeAddress), + Kind: &kind, + }, + { + Label: string(match_parser.MatchCriteriaTypeLocalAddress), + Kind: &kind, + }, + { + Label: string(match_parser.MatchCriteriaTypeLocalPort), + Kind: &kind, + }, + { + Label: string(match_parser.MatchCriteriaTypeRDomain), + Kind: &kind, + }, + } +} + +func getMatchAllKeywordCompletion() protocol.CompletionItem { + kind := protocol.CompletionItemKindKeyword + + return protocol.CompletionItem{ + Label: "all", + Kind: &kind, + } +} + +func getMatchValueCompletions( + entry *match_parser.MatchEntry, + cursor uint32, +) []protocol.CompletionItem { + value := entry.GetValueByCursor(entry.End.Character) + + var line string + var relativeCursor uint32 + + if value != nil { + line = value.Value + relativeCursor = cursor - value.Start.Character + } else { + line = "" + relativeCursor = 0 + } + + switch entry.Criteria.Type { + case match_parser.MatchCriteriaTypeUser: + return fields.MatchUserField.FetchCompletions(line, relativeCursor) + case match_parser.MatchCriteriaTypeGroup: + return fields.MatchGroupField.FetchCompletions(line, relativeCursor) + case match_parser.MatchCriteriaTypeHost: + return fields.MatchHostField.FetchCompletions(line, relativeCursor) + case match_parser.MatchCriteriaTypeAddress: + return fields.MatchAddressField.FetchCompletions(line, relativeCursor) + case match_parser.MatchCriteriaTypeLocalAddress: + return fields.MatchLocalAddressField.FetchCompletions(line, relativeCursor) + case match_parser.MatchCriteriaTypeLocalPort: + return fields.MatchLocalPortField.FetchCompletions(line, relativeCursor) + case match_parser.MatchCriteriaTypeRDomain: + return fields.MatchRDomainField.FetchCompletions(line, relativeCursor) + } + + return nil +} diff --git a/handlers/sshd_config/lsp/text-document-completion.go b/handlers/sshd_config/lsp/text-document-completion.go index db7fc30..c0d3e37 100644 --- a/handlers/sshd_config/lsp/text-document-completion.go +++ b/handlers/sshd_config/lsp/text-document-completion.go @@ -14,7 +14,7 @@ var isEmptyPattern = regexp.MustCompile(`^\s*$`) func TextDocumentCompletion(context *glsp.Context, params *protocol.CompletionParams) (any, error) { line := params.Position.Line - cursor := params.Position.Character + cursor := common.CursorToCharacterIndex(params.Position.Character) d := sshdconfig.DocumentParserMap[params.TextDocument.URI] @@ -27,7 +27,7 @@ func TextDocumentCompletion(context *glsp.Context, params *protocol.CompletionPa if entry == nil || entry.Separator == nil || entry.Key == nil || - (common.CursorToCharacterIndex(cursor)) <= entry.Key.End.Character { + cursor <= entry.Key.End.Character { return handlers.GetRootCompletions( d, @@ -37,10 +37,11 @@ func TextDocumentCompletion(context *glsp.Context, params *protocol.CompletionPa ) } - if entry.Separator != nil && cursor > entry.Separator.End.Character { + if entry.Separator != nil && cursor >= entry.Separator.End.Character { return handlers.GetOptionCompletions( d, entry, + matchBlock, cursor, ) }