feat: Add KeyValueAssignmentValue

This commit is contained in:
Myzel394 2024-07-29 22:13:14 +02:00
parent 014d9ebe94
commit d5cb9c53e9
No known key found for this signature in database
GPG Key ID: DEC4AAB876F73185
6 changed files with 138 additions and 31 deletions

3
TODO.md Normal file
View File

@ -0,0 +1,3 @@
* SSH ~/.ssh/config config file
* FTP config file

View File

@ -266,6 +266,42 @@ func (v PathValue) CheckIsValid(value string) error {
return PathInvalidError{}
}
type KeyValueAssignmentValue struct {
Key Value
Value Value
Separator string
}
func (v KeyValueAssignmentValue) GetTypeDescription() []string {
return []string{
fmt.Sprintf("Key-Value pair in form of 'key%svalue'", v.Separator),
fmt.Sprintf("#### Key\n%s", strings.Join(v.Key.GetTypeDescription(), "\n")),
fmt.Sprintf("#### Value:\n%s", strings.Join(v.Value.GetTypeDescription(), "\n")),
}
}
func (v KeyValueAssignmentValue) CheckIsValid(value string) error {
parts := strings.Split(value, v.Separator)
if len(parts) != 2 {
return KeyValueAssignmentError{}
}
err := v.Key.CheckIsValid(parts[0])
if err != nil {
return err
}
err = v.Value.CheckIsValid(parts[1])
if err != nil {
return err
}
return nil
}
type Option struct {
Documentation string
Value Value

View File

@ -136,6 +136,12 @@ func (e PathDoesNotExistError) Error() string {
return "This path does not exist"
}
type KeyValueAssignmentError struct{}
func (e KeyValueAssignmentError) Error() string {
return "This is not valid key-value assignment"
}
type PathInvalidError struct{}
func (e PathInvalidError) Error() string {

View File

@ -100,3 +100,21 @@ func IsPathFile(path string) bool {
return err == nil && !info.IsDir()
}
func OffsetLineAtLeft(offset uint32, line string, cursor uint32) (string, uint32) {
if offset >= uint32(len(line)) {
return line, cursor
}
return line[offset:], cursor - offset
}
func FindPreviousCharacter(line string, character string) (uint32, bool) {
for index := len(line) - 1; index >= 0; index-- {
if string(line[index]) == character {
return uint32(index), true
}
}
return 0, false
}

View File

@ -205,21 +205,41 @@ See PATTERNS in ssh_config(5) for more information on patterns. This keyword may
},
},
),
// "ChannelTimeout": `Specifies whether and how quickly sshd(8) should close inactive channels. Timeouts are specified as one or more “type=interval” pairs separated by whitespace, where the “type” must be the special keyword “global” or a channel type name from the list below, optionally containing wildcard characters.
// The timeout value “interval” is specified in seconds or may use any of the units documented in the “TIME FORMATS” section. For example, “session=5m” would cause interactive sessions to terminate after five minutes of inactivity. Specifying a zero value disables the inactivity timeout.
// The special timeout “global” applies to all active channels, taken together. Traffic on any active channel will reset the timeout, but when the timeout expires then all open channels will be closed. Note that this global timeout is not matched by wildcards and must be specified explicitly.
// The available channel type names include:
// agent-connection Open connections to ssh-agent(1).
// direct-tcpip, direct-streamlocal@openssh.com Open TCP or Unix socket (respectively) connections that have been established from a ssh(1) local forwarding, i.e. LocalForward or DynamicForward.
// forwarded-tcpip, forwarded-streamlocal@openssh.com Open TCP or Unix socket (respectively) connections that have been established to a sshd(8) listening on behalf of a ssh(1) remote forwarding, i.e. RemoteForward.
//
// `,
// "session": `The interactive main session, including shell session, command execution, scp(1), sftp(1), etc.
// tun-connection Open TunnelForward connections.
// x11-connection Open X11 forwarding sessions.
// Note that in all the above cases, terminating an inactive session does not guarantee to remove all resources associated with the session, e.g. shell processes or X11 clients relating to the session may continue to execute.
// Moreover, terminating an inactive channel or session does not necessarily close the SSH connection, nor does it prevent a client from requesting another channel of the same type. In particular, expiring an inactive forwarding session does not prevent another identical forwarding from being subsequently created.
// The default is not to expire channels of any type for inactivity.`,
"ChannelTimeout": common.NewOption(`Specifies whether and how quickly sshd(8) should close inactive channels. Timeouts are specified as one or more type=interval pairs separated by whitespace, where the type must be the special keyword global or a channel type name from the list below, optionally containing wildcard characters.
The timeout value interval is specified in seconds or may use any of the units documented in the TIME FORMATS section. For example, session=5m would cause interactive sessions to terminate after five minutes of inactivity. Specifying a zero value disables the inactivity timeout.
The special timeout global applies to all active channels, taken together. Traffic on any active channel will reset the timeout, but when the timeout expires then all open channels will be closed. Note that this global timeout is not matched by wildcards and must be specified explicitly.
The available channel type names include:
agent-connection Open connections to ssh-agent(1).
direct-tcpip, direct-streamlocal@openssh.com Open TCP or Unix socket (respectively) connections that have been established from a ssh(1) local forwarding, i.e. LocalForward or DynamicForward.
forwarded-tcpip, forwarded-streamlocal@openssh.com Open TCP or Unix socket (respectively) connections that have been established to a sshd(8) listening on behalf of a ssh(1) remote forwarding, i.e. RemoteForward.
session The interactive main session, including shell session, command execution, scp(1), sftp(1), etc.
tun-connection Open TunnelForward connections.
x11-connection Open X11 forwarding sessions.
Note that in all the above cases, terminating an inactive session does not guarantee to remove all resources associated with the session, e.g. shell processes or X11 clients relating to the session may continue to execute.
Moreover, terminating an inactive channel or session does not necessarily close the SSH connection, nor does it prevent a client from requesting another channel of the same type. In particular, expiring an inactive forwarding session does not prevent another identical forwarding from being subsequently created.
The default is not to expire channels of any type for inactivity.`,
common.ArrayValue{
Separator: " ",
AllowDuplicates: false,
SubValue: common.KeyValueAssignmentValue{
Separator: "=",
Key: common.EnumValue{
Values: []string{
"global",
"agent-connection",
"direct-tcpip", "direct-streamlocal@openssh.com",
"forwarded-tcpip", "forwarded-streamlocal@openssh.com",
"session",
"tun-connection",
"x11-connection",
},
},
Value: common.StringValue{},
},
},
),
"ChrootDirectory": common.NewOption(`Specifies the pathname of a directory to chroot(2) to after authentication. At session startup sshd(8) checks that all components of the pathname are root-owned directories which are not writable by group or others. After the chroot, sshd(8) changes the working directory to the user's home directory. Arguments to ChrootDirectory accept the tokens described in the TOKENS section.
The ChrootDirectory must contain the necessary files and directories to support the user's session. For an interactive session this requires at least a shell, typically sh(1), and basic /dev nodes such as null(4), zero(4), stdin(4), stdout(4), stderr(4), and tty(4) devices. For file transfer sessions using SFTP no additional configuration of the environment is necessary if the in-process sftp-server is used, though sessions which use logging may require /dev/log inside the chroot directory on some operating systems (see sftp-server(8) for details).
For safety, it is very important that the directory hierarchy be prevented from modification by other processes on the system (especially those outside the jail). Misconfiguration can lead to unsafe environments which sshd(8) cannot detect.

View File

@ -3,6 +3,7 @@ package openssh
import (
"config-lsp/common"
"errors"
"unicode/utf8"
"github.com/tliron/glsp"
protocol "github.com/tliron/glsp/protocol_3_16"
@ -12,13 +13,19 @@ import (
)
func TextDocumentCompletion(context *glsp.Context, params *protocol.CompletionParams) (interface{}, error) {
option, line, err := Parser.FindByLineNumber(uint32(params.Position.Line))
optionName, line, err := Parser.FindByLineNumber(uint32(params.Position.Line))
if err == nil {
if line.IsCursorAtRootOption(int(params.Position.Character)) {
return getRootCompletions(), nil
} else {
return getOptionCompletions(option), nil
rawLine, cursor := common.OffsetLineAtLeft(
uint32(utf8.RuneCountInString(optionName + " ")),
line.Value,
params.Position.Character,
)
return getOptionCompletions(optionName, rawLine, cursor), nil
}
} else if errors.Is(err, common.LineNotFoundError{}) {
return getRootCompletions(), nil
@ -52,11 +59,11 @@ func getRootCompletions() []protocol.CompletionItem {
return completions
}
func getCompletionsFromValue(value common.Value) []protocol.CompletionItem {
switch value.(type) {
func getCompletionsFromValue(requiredValue common.Value, line string, cursor uint32) []protocol.CompletionItem {
switch requiredValue.(type) {
case common.EnumValue:
enumValue := value.(common.EnumValue)
completions := make([]protocol.CompletionItem, len(value.(common.EnumValue).Values))
enumValue := requiredValue.(common.EnumValue)
completions := make([]protocol.CompletionItem, len(requiredValue.(common.EnumValue).Values))
for index, value := range enumValue.Values {
textFormat := protocol.InsertTextFormatPlainText
@ -71,37 +78,54 @@ func getCompletionsFromValue(value common.Value) []protocol.CompletionItem {
return completions
case common.CustomValue:
customValue := value.(common.CustomValue)
customValue := requiredValue.(common.CustomValue)
val := customValue.FetchValue()
return getCompletionsFromValue(val)
return getCompletionsFromValue(val, line, cursor)
case common.ArrayValue:
arrayValue := value.(common.ArrayValue)
arrayValue := requiredValue.(common.ArrayValue)
relativePosition, found := common.FindPreviousCharacter(line, arrayValue.Separator)
return getCompletionsFromValue(arrayValue.SubValue)
if found {
line, cursor = common.OffsetLineAtLeft(relativePosition, line, cursor)
}
return getCompletionsFromValue(arrayValue.SubValue, line, cursor)
case common.OrValue:
orValue := value.(common.OrValue)
orValue := requiredValue.(common.OrValue)
completions := make([]protocol.CompletionItem, 0)
for _, subValue := range orValue.Values {
completions = append(completions, getCompletionsFromValue(subValue)...)
completions = append(completions, getCompletionsFromValue(subValue, line, cursor)...)
}
return completions
case common.PrefixWithMeaningValue:
prefixWithMeaningValue := value.(common.PrefixWithMeaningValue)
prefixWithMeaningValue := requiredValue.(common.PrefixWithMeaningValue)
return getCompletionsFromValue(prefixWithMeaningValue.SubValue)
return getCompletionsFromValue(prefixWithMeaningValue.SubValue, line, cursor)
case common.KeyValueAssignmentValue:
keyValueAssignmentValue := requiredValue.(common.KeyValueAssignmentValue)
relativePosition, found := common.FindPreviousCharacter(line, keyValueAssignmentValue.Separator)
if found {
line, cursor = common.OffsetLineAtLeft(relativePosition, line, cursor)
return getCompletionsFromValue(keyValueAssignmentValue.Value, line, cursor)
} else {
return getCompletionsFromValue(keyValueAssignmentValue.Key, line, cursor)
}
}
return []protocol.CompletionItem{}
}
func getOptionCompletions(optionName string) []protocol.CompletionItem {
func getOptionCompletions(optionName string, line string, cursor uint32) []protocol.CompletionItem {
option := Options[optionName]
completions := getCompletionsFromValue(option.Value)
completions := getCompletionsFromValue(option.Value, line, cursor)
return completions
}