# (C) 2010-2018 magicant

# Completion script for the "ssh" command.
# Completion function "completion/ssh" is used for the "scp" and "sftp" commands
# as well.
# Supports OpenSSH 7.7.

function completion/ssh {

        typeset OPTIONS ARGOPT PREFIX
        OPTIONS=( #>#
        #"1; use the protocol version 1 only"
        #"2; use the protocol version 2 only"
        "4; use IPv4 addresses only"
        "6; use IPv6 addresses only"
        "C; enable data compression"
        "c:; specify the encryption algorithm"
        "F:; specify the configuration file"
        "i:; specify the private key (identity) file"
        "o:; specify an option in the configuration file format"
        "v; print debugging messages"
        ) #<#

        case ${WORDS[1]} in
        (ssh)
                OPTIONS=("$OPTIONS" #>#
                "A; enable authentication agent forwarding"
                "a; disable authentication agent forwarding"
                "B:; specify the network interface to connect from"
                "b:; specify the local IP address to connect from"
                "D:; specify a port for local dynamic application-level port forwarding"
                "E:; specify the log file"
                "e:; specify the escape character"
                "f; run in the background after authentication"
                "G; print the configuration"
                "g; allow remote hosts to connect to local forwarded ports"
                "I:; specify the PKCS#11 shared library"
                "J:; specify jump hosts"
                "K; enable GSSAPI-based authentication and forwarding"
                "k; disable GSSAPI-based authentication and forwarding"
                "L:; specify a local port and a remote host/port to forward"
                "l:; specify the user name to log in as"
                "M; run in the master mode for connection sharing"
                "m:; specify MACs (message authentication codes)"
                "N; don't run any remote command"
                "n; redirect the standard input of the remote command to /dev/null"
                "O:; specify a command to control the master process"
                "p:; specify the port to connect to"
                "Q:; specify the feature to query"
                "q; suppress warning and diagnostic messages"
                "R:; specify a remote port and a local host/port to forward"
                "S:; specify the control socket for connection sharing"
                "s; run the command as the SSH2 subsystem"
                "T; run the command without a pseudo-terminal"
                "t; run the command in a pseudo-terminal"
                "V; print version info"
                "W:; specify a remote host/port to directly connect to"
                "w:; specify the tunnel device to forward"
                "X; enable X11 forwarding"
                "x; disable X11 forwarding"
                "Y; enable trusted X11 forwarding"
                "y; use syslog for logging"
                ) #<#
                ;;
        (scp)
                OPTIONS=("$OPTIONS" #>#
                "3; copy via local host between remote source and destination"
                "B; batch mode: don't ask for passwords/phrases"
                ) #<#
                ;;
        (sftp)
                OPTIONS=("$OPTIONS" #>#
                "a; continue on interruption"
                "B:; specify the buffer size in bytes"
                "b:; batch mode: specify the file to read commands from"
                "D:; specify the path of local sftp server to connect to"
                "R:; specify the max number of outstanding requests"
                "s:; specify the SSH2 subsystem or the path for the remote sftp server"
                ) #<#
                ;;
        (*)
                return 1
                ;;
        esac
        case ${WORDS[1]} in (scp|sftp)
                OPTIONS=("$OPTIONS" #>#
                "l:; specify the max bandwidth in Kbps"
                "P:; specify the port to connect to"
                "p; preserve file attributes"
                "q; suppress progress bar and warning and diagnostic messages"
                "r; recursively copy directories"
                "S:; specify the connection program used instead of ssh"
                ) #<#
        esac

        command -f completion//parseoptions
        case $ARGOPT in
        (-)
                command -f completion//completeoptions
                ;;
        ([BDPp])
                ;;
        (b)
                case ${WORDS[1]} in
                (sftp)
                        complete -P "$PREFIX" -f
                        ;;
                esac
                ;;
        ([Fi])
                complete -P "$PREFIX" -f
                ;;
        (c)
                #TODO
                ;;
        (e) #>>#
                complete -P "$PREFIX" '~'
                complete -P "$PREFIX" -D "none" none
                ;; #<<#
        (I)
                #TODO
                ;;
        (J)
                PREFIX=${TARGETWORD%"${${TARGETWORD#"$PREFIX"}##*,}"}
                command -f completion/${WORDS[1]}::operand
                ;;
        (L)
                #TODO
                ;;
        (l)
                case ${WORDS[1]} in
                (ssh)
                        complete -P "$PREFIX" -u
                        ;;
                esac
                ;;
        (m)
                #TODO
                ;;
        (O) #>>#
                complete -P "$PREFIX" -D "request the master process to stop port forwarding" cancel
                complete -P "$PREFIX" -D "check that the master process is running" check
                complete -P "$PREFIX" -D "request the master process to exit" exit
                complete -P "$PREFIX" -D "request forwarding without command execution" forward
                complete -P "$PREFIX" -D "request the master process to accept no more connections" stop
                ;; #<<#
        (o)
                #TODO
                ;;
        (Q) #>>#
                complete -P "$PREFIX" -D "supported symmetric ciphers" cipher
                complete -P "$PREFIX" -D "supported symmetric ciphers that support authenticated encryption" cipher-auth
                complete -P "$PREFIX" -D "key exchange algorithms" kex
                complete -P "$PREFIX" -D "key types" key
                complete -P "$PREFIX" -D "certificate key types" key-cert
                complete -P "$PREFIX" -D "non-certificate key types" key-plain
                complete -P "$PREFIX" -D "supported message integrity codes" mac
                complete -P "$PREFIX" -D "supported SSH protocol versions" protocol-version
                ;; #<<#
        (R)
                #TODO
                ;;
        (S)
                case ${WORDS[1]} in
                (ssh)
                        complete -P "$PREFIX" -f
                        ;;
                (scp|sftp)
                        WORDS=()
                        command -f completion//reexecute -e
                        ;;
                esac
                ;;
        (W)
                #TODO
                ;;
        (w)
                #TODO
                ;;
        ('')
                command -f completion/${WORDS[1]}::operand
                ;;
        esac

}

function completion/ssh::operand {
        typeset config ssh sshopts cmd="${WORDS[1]}"
        command -f completion/ssh::parseconfig
        if [ ${WORDS[#]} -gt 0 ]; then
                # complete the command
                WORDS=("${WORDS[2,-1]}")
                command -f completion//reexecute
        else
                command -f completion/ssh::completehostname
        fi
}

function completion/scp::operand {
        typeset config ssh sshopts cmd="${WORDS[1]}"
        command -f completion/ssh::parseconfig

        case $TARGETWORD in
        (${cmd}://*/*)
                typeset path="${TARGETWORD#"${cmd}"://*/}"
                command -f completion/ssh::completeremotepath
                ;;
        (${cmd}://*)
                command -f completion/ssh::completehostname -T -S /
                ;;
        (*:*)
                case ${TARGETWORD%%:*} in
                (*/*) # a host name cannot contain a slash
                        command -f completion/ssh::completelocalpath
                        ;;
                (*)
                        typeset path="${TARGETWORD#*:}"
                        command -f completion/ssh::completeremotepath
                        ;;
                esac
                ;;
        (*)
                command -f completion/ssh::completehostname -T -S :
                command -f completion/ssh::completelocalpath
                ;;
        esac
}

function completion/sftp::operand {
        command -f completion/scp::operand
}

function completion/ssh::parseconfig {
        typeset i=2
        config=${HOME:+"$HOME/.ssh/config"}
        ssh=ssh sshopts=()
        while [ $i -le ${WORDS[#]} ]; do
                case ${WORDS[i]} in
                        (-F*)
                                sshopts=("$sshopts" "${WORDS[i]}")
                                config=${WORDS[i]#-F} ;;
                        (-[1246BCiJo]*)
                                sshopts=("$sshopts" "${WORDS[i]}") ;;
                        (-P*)
                                sshopts=("$sshopts" -p"${WORDS[i]#-P}") ;;
                        (-S*)
                                case ${WORDS[1]} in (scp|sftp)
                                        ssh=${WORDS[1]#-S}
                                esac
                                ;;
                        (--)
                                i=$((i+1)); break ;;
                        (-*)
                                ;;
                        (*)
                                break ;;
                esac
                i=$((i+1))
        done
        WORDS=("${WORDS[i,-1]}")
}

function completion/ssh::completehostname {
        case ${TARGETWORD#"$PREFIX"} in (${cmd}://*)
                PREFIX=${PREFIX}${cmd}://
        esac
        case ${TARGETWORD#"$PREFIX"} in (*@*)
                PREFIX=${TARGETWORD%"${${TARGETWORD#"$PREFIX"}#*@}"}
        esac
        typeset key values file words host
        while read -Ar key values; do
                case $key in ([Hh][Oo][Ss][Tt])
                        complete -P "$PREFIX" -R '*[*?]*' -R '!*' "$@" -- \
                                "$values"
                esac
        done <(sed -e 's/#.*//' -e 's/[Hh][Oo][Ss][Tt][[:blank:]]*=/host /' "$config" 2>/dev/null)
        # currently, we always read known-hosts files from the default location
        for file in /etc/ssh/ssh_known_hosts ~/.ssh/known_hosts; do
                if ! [ -r "$file" ]; then
                        continue
                fi
                while read -Ar words; do
                        case ${words[1]} in
                        (\#*)
                                continue
                                ;;
                        (@*)
                                words=("${words[2,-1]}")
                                ;;
                        esac
                        for host in ${words[1]//,/ }; do
                                case $host in
                                ([\|!]*|*[*?]*) ;;
                                (\[*]:*)
                                        complete -P "$PREFIX" "$@" -- "${{host#\[}%]:*}"
                                        ;;
                                (*)
                                        complete -P "$PREFIX" "$@" -- "$host"
                                        ;;
                                esac
                        done
                        hosts=(${words[1]//,/ })
                        case $key in
                        (\|*) ;;
                        (*)
                                complete -P "$PREFIX" "$@" -- ${key//,/ }
                        esac
                done <"$file"
        done
}

function completion/ssh::completeremotepath
        case $cmd in (scp|sftp|rsync)
                PREFIX=${TARGETWORD%"$path"}
                typeset host="${${PREFIX%[/:]}/#${cmd}:\/\//ssh:\/\/}"
                typeset name="${path##*/}"
                typeset dir="${path%"$name"}"
                typeset filter f flags
                if [ -o dotglob ]; then
                        filter=()
                else
                        case $name in
                                (.*) filter=(-A '.*') ;;
                                (*)  filter=(-R '.*') ;;
                        esac
                fi
                while read -r f; do
                        case $f in
                                (*/) flags='-T' ;;
                                (*)  flags=     ;;
                        esac
                        complete -P "$PREFIX$dir" "$filter" $flags -- "$f"
                done <("$ssh" "$sshopts" -o BatchMode=yes -- "$host" "ls -ap -- '${${dir:-.}//\'/\'\\\'\'}'" <>/dev/null 2>&0)
        esac

function completion/ssh::completelocalpath
        case $cmd in (scp|rsync)
                typeset slash=false
                case $TARGETWORD in (*/*)
                        slash=
                esac
                complete ${slash:+-R '*:*'} -f
        esac


# vim: set ft=sh ts=8 sts=8 sw=8 et:
