#!/bin/bash

set -e -u -o pipefail
shopt -s inherit_errexit nullglob
export -n IFS=$'\t'

# shellcheck disable=SC1090
for f in {,/usr/local}/etc/split-browser/*.bash; do source "$f"; done
export SB_CMD_INPUT="${SB_CMD_INPUT:-/run/split-browser/cmd-$$}"


# Parse command-line arguments

args=()
cli_prefs=()

for arg; do
    case "$arg" in
        -h|--help)
            exec cat <<END

  Usage:  split-browser [--safest] [--pref=<line>...] [<argument>...]


  --safest       set Tor Browser's Security Level to Safest; equivalent to
                 --pref='lockPref("browser.security_level.security_slider", 1);'

  --pref=<line>  additional browser preference line, appended after those
                 stored in /etc/split-browser/prefs/*.js and
                 /usr/local/etc/split-browser/prefs/*.js

  <argument>     e.g. a URL; passed to the browser when starting (but not
                 when restarting due to Ctrl-Shift-u)

END
        ;;
        --safest|--high)  # --high is deprecated
            cli_prefs+=( 'lockPref("browser.security_level.security_slider", 1);' )
        ;;
        --pref=*)
            cli_prefs+=( "${arg#*=}" )
        ;;
        *)
            args+=( "$arg" )
        ;;
    esac
done


# When the DisposableVM sends us a request, the page URL and title fields are
# supplied in two versions each: printable ASCII, and UTF-8. Printable ASCII
# *URLs* use Punycode IDNs and percent-encoding. Printable ASCII *titles*
# actually come in as UTF-8 (normalized to NFKD, which tends to look better
# than the other normalization forms after all bytes outside of the printable
# ASCII range have been sanitized).

[[ ${SB_CHARSET-} == utf-8 ]] || SB_CHARSET=ascii

declare -A first_page_field=( [ascii]=2 [utf-8]=4 )

sanitize_ascii() {  # replace any byte with _ but printable ASCII, tab, newline
    LC_ALL=C stdbuf -oL tr -c '\040-\176\t\n' _
}

sanitize_utf-8() {  # replace null byte with _; then validate as UTF-8
    LC_ALL=C stdbuf -oL tr '\0' _ |
    LC_ALL=C PYTHONIOENCODING=utf-8:strict \
    python3 -Suc 'import sys; sys.stdout.writelines(sys.stdin)'
}


# Launch via qubes.VMShell in the DisposableVM, because a straightforward
# 'qrexec-client-vm @dispvm split-browser-disp' call would require the user to
# manually create a policy in dom0. Transition to split-browser-disp with the
# trick described in /usr/lib/qubes/qrun-in-vm (not reused here, because it
# doesn't preserve exit status).
#
# Sanitize stdout and handle those requests (one per line), e.g. to get a login
# credential. The handler might then send commands into the input FIFO with
# split-browser-cmd. (This is essentially a crappy bidirectional RPC system
# implemented *inside* a qrexec RPC call's data streams. It would be much nicer
# to do multiple qrexec calls back and forth, but that requires lots of policy
# configuration for each persistent VM.) Also strictly sanitize stderr and
# distinguish it with a prefix.

dispvm() (
    exec {saved_stdout_fd}>&1 >&2
    trap 'rm -f -- "$SB_CMD_INPUT"{.tmp,}' EXIT
    mkfifo -- "$SB_CMD_INPUT".tmp
    exec {cmd_fd}<>"$SB_CMD_INPUT".tmp

    d=/etc/qubes-rpc
    bash_line="PATH=/usr/local$d:$d:\$PATH exec split-browser-disp"
    printf '%s\n' "$bash_line" "$@" >&"$cmd_fd"  # 64 KB pipe capacity
    mv -T -- "$SB_CMD_INPUT"{.tmp,}

    {
        qrexec-client-vm -T "$SB_DISP" qubes.VMShell+WaitForSession \
                         2>&1 >&"$req_fd" {req_fd}>&- <&"$cmd_fd" |
        sanitize_ascii                    {req_fd}>&- |
        sed -u 's/^/disp: /'  >&2         {req_fd}>&-
    } {req_fd}>&1 |
    sanitize_"$SB_CHARSET" |
    while IFS= read -r req_line; do
        readarray -d $'\t' -t req <<<"$req_line"
        req[-1]=${req[-1]%$'\n'}

        [[ $SB_CHARSET == ascii ]] || req[0]=$(sanitize_ascii <<<"${req[0]}")
        req=( "${req[@]::2}" "${req[@]:${first_page_field[$SB_CHARSET]}:2}" )

        case "${req[0]}" in
            bookmark|login)
                # shellcheck disable=SC2145
                split-browser-"${req[@]}" &
            ;;
            restart)
                echo x >&"$saved_stdout_fd"
            ;;
            *)
                printf 'Unknown request from DisposableVM: %s\n' "${req[0]}"
            ;;
        esac
    done
)


# Main loop: Configure and open the disposable browser. After clean shutdown,
# do it again (if restart was requested).

while :; do
    config_prefs=()
    for f in {,/usr/local}/etc/split-browser/prefs/*.js; do
        readarray -t -O ${#config_prefs[@]} config_prefs <"$f"
    done
    setup_cmd=( setup "${config_prefs[@]}" "${cli_prefs[@]}" )
    master_cmd=( master "${args[@]}" )
    args=()  # on restart, don't load given URLs again

    restart=$(dispvm "${setup_cmd[*]}" "${master_cmd[*]}")
    [[ $restart ]] || exit 0
done
