package ssh import "strings" // flagsWithArg lists all SSH flags that consume the following argument. var flagsWithArg = map[byte]bool{ 'B': true, 'P': true, 'b': true, 'c': true, 'D': true, 'E': true, 'e': true, 'F': true, 'I': true, 'i': true, 'J': true, 'L': true, 'l': true, 'm': true, 'o': true, 'O': true, 'p': true, 'Q': true, 'R': true, 'S': true, 'w': true, 'W': true, } // ParsedArgs holds the result of parsing SSH arguments. type ParsedArgs struct { Host string // hostname without the user@ prefix or :path User string // empty if not specified Path string // sftp only: the remote path after host: DestIdx int // index in Args where [user@]host sits Args []string } // Parse scans SSH arguments and extracts the destination ([user@]host). // Returns nil if no destination is found. func Parse(args []string) *ParsedArgs { return SSH.Parse(args) } // Parse extracts the destination: [user@]host, or [user@]host[:path] for sftp. func (c Client) Parse(args []string) *ParsedArgs { i := 0 for i < len(args) { arg := args[i] // "--" ends option processing if arg == "--" { i++ break } if strings.HasPrefix(arg, "-") && len(arg) > 1 { // OpenSSH accepts clusters such as -vp 2222 and -vladmin. if c.takesSeparateValue(arg) { i++ } i++ continue } // first non-flag argument is the destination return c.parsed(args, i) } // handle arguments after "--" if i < len(args) { return c.parsed(args, i) } return nil } func (c Client) parsed(args []string, i int) *ParsedArgs { dest, path := args[i], "" if c.paths { if d, p, remote := splitRemote(dest); remote { dest, path = d, p } } host, user := splitUserHost(dest) return &ParsedArgs{Host: host, User: user, Path: path, DestIdx: i, Args: args} } // ReplaceHost returns a copy of args with the destination replaced by newHost, // preserving any user@ prefix. func ReplaceHost(args []string, destIdx int, newHost string) []string { return SSH.replaceHost(args, []int{destIdx}, newHost) } // replaceHost keeps user@ and, for sftp and scp, :path. Those clients read // host:path, so an IPv6 address must be bracketed. func (c Client) replaceHost(args []string, dests []int, newHost string) []string { result := make([]string, len(args)) copy(result, args) if c.paths && strings.Contains(newHost, ":") { newHost = "[" + newHost + "]" } for _, destIdx := range dests { original := args[destIdx] prefix, suffix := "", "" if at := strings.Index(original, "@"); at != -1 { prefix, original = original[:at+1], original[at+1:] } if c.paths { if _, path, remote := splitRemote(original); remote { suffix = ":" + path } } result[destIdx] = prefix + newHost + suffix } return result } // HasPortFlag reports whether a port was explicitly supplied. func HasPortFlag(args []string) bool { return SSH.HasPortFlag(args) } // HasUserFlag reports whether a login user was explicitly supplied. func HasUserFlag(args []string) bool { return SSH.HasUserFlag(args) } // HasPortFlag reports whether a port was explicitly supplied. func (c Client) HasPortFlag(args []string) bool { return c.hasOption(args, c.port, "Port") } // HasUserFlag reports whether a login user was explicitly supplied. func (c Client) HasUserFlag(args []string) bool { return c.hasOption(args, c.user, "User") } func hasOption(args []string, wanted byte, keyword string) bool { return SSH.hasOption(args, wanted, keyword) } func (c Client) hasOption(args []string, wanted byte, keyword string) bool { for i := 0; i < len(args); i++ { arg := args[i] if arg == "--" || len(arg) < 2 || arg[0] != '-' { break } for j := 1; j < len(arg); j++ { flag := arg[j] if !c.withArg[flag] { continue } value := arg[j+1:] if value == "" { i++ if i >= len(args) { return false } value = args[i] } if wanted != 0 && flag == wanted { return true } if flag == 'o' { fields := strings.FieldsFunc(value, func(r rune) bool { return r == '=' || r == ' ' || r == '\t' }) if len(fields) >= 2 && strings.EqualFold(fields[0], keyword) { return true } } break } } return false } func splitUserHost(dest string) (host, user string) { if at := strings.Index(dest, "@"); at != -1 { return dest[at+1:], dest[:at] } return dest, "" }