#!/bin/bash

## Copyright (C) 2012 - 2025 ENCRYPTED SUPPORT LLC <adrelanos@whonix.org>
## See the file COPYING for copying conditions.

## SC1091: shellcheck cannot follow runtime-dynamic 'source' targets in
##   this file (helper-scripts, boot-session-detection variants,
##   /usr/share/tb-updater/tbb_hardcoded_version*,
##   /usr/libexec/tb-updater/version-validator, per-browser config
##   snippets). The sourced scripts should be linted separately.
## SC2154: the same sourced scripts set tbb_hardcoded_version,
##   tbb_locally_installed_version_stripped, output_opts and friends.
##   shellcheck flags them as unset from its static view.
# shellcheck disable=SC1091,SC2154

# shellcheck source=../../../helper-scripts/usr/libexec/helper-scripts/check_runtime.bsh
source /usr/libexec/helper-scripts/check_runtime.bsh

: "${tpo_downloader_debug:=""}"
if [ ! "${tpo_downloader_debug:-}" = "" ] && [[ "${tpo_downloader_debug:-}" =~ ^[0-9]+$ ]]; then
   if [ "$tpo_downloader_debug" -ge "1" ]; then
      set -x
   fi
fi

if was_executed "${BASH_SOURCE[0]}"; then
  ## Script was executed.
  tb_updater_was_sourced="false"
else
  ## Script was sourced.
  ## This is useful for other programs / scripts to be able to `source` the
  ## functions of this script for code reuse. dist-installer-gui will do this.
  tb_updater_was_sourced="true"
fi

if [ "${tb_updater_was_sourced}" = "false" ]; then
  set -o errexit
  set -o nounset
  # shellcheck disable=SC2317
  set -o pipefail
  set -o errtrace
fi

who_ami="$(whoami)"

## Variable naming convention used throughout this script:
##   *_raw        -- untrusted input from files, env, network, or tool
##                   output. Never interpolate directly into MSG bodies or
##                   into paths/commands.
##   *_stripped   -- has been passed through sanitize-string from
##                   helper-scripts (strip markup, cap length); safe for
##                   MSG HTML interpolation. Use when a corresponding _raw
##                   counterpart also exists.
##   *_unixtime   -- numeric epoch seconds. Validated via read_integer_file
##                   or is_whole_number at the read boundary before use in
##                   arithmetic comparisons.
##   *_pretty     -- human-formatted from already-validated input.
##   (unsuffixed) -- locally-derived, string-literal, or a single-form
##                   sanitised/validated value with no separate _raw
##                   counterpart worth distinguishing.

# shellcheck source=../../../helper-scripts/usr/libexec/helper-scripts/strings.bsh
source /usr/libexec/helper-scripts/strings.bsh

## sets: boot_session
# shellcheck source=../../../helper-scripts/usr/libexec/helper-scripts/boot-session-detection.bsh
if test -f /usr/libexec/helper-scripts/boot-session-detection.sh; then
   ## Legacy.
   source /usr/libexec/helper-scripts/boot-session-detection.sh
else
   ## Newer.
   source /usr/libexec/helper-scripts/boot-session-detection.bsh
fi

# shellcheck source=../../../helper-scripts/usr/libexec/helper-scripts/has.sh
source /usr/libexec/helper-scripts/has.sh
# shellcheck source=../../../helper-scripts/usr/libexec/helper-scripts/package_installed_check.sh
source /usr/libexec/helper-scripts/package_installed_check.sh
## shellcheck source=../../../../helper-scripts/usr/libexec/log_run_die.sh
source /usr/libexec/helper-scripts/log_run_die.sh

log info "Start."

[[ -v SCRIPTNAME ]] || SCRIPTNAME="$(basename -- "${BASH_SOURCE[0]}")"
[[ -v ICON ]] || ICON="/usr/share/icons/icon-pack-dist/tbupdate.ico"

## Developer comment on this script:
## I would be very happy if this script would not be required.
## Unfortunately, it is required. It works around a lot bugs:
## - There is no deb/rpm/whatever package with Tor Browser.
##   https://gitlab.torproject.org/tpo/community/trac/-/issues/5236
##   https://gitlab.torproject.org/tpo/applications/tor-browser/-/issues/3994
## - There is no way to add the Documentation page well visible to Tor Browser:
##   https://gitlab.torproject.org/legacy/trac/-/issues/6025
##   https://gitlab.torproject.org/tpo/applications/tor-browser/-/issues/6053
## - Tor Browsers support for Isolating and Transparent Proxies is very limited:
##   https://gitlab.torproject.org/tpo/applications/tor-browser/-/issues/5611
## - RecommendedTBBVersions format is not finalized
##   https://gitlab.torproject.org/tpo/web/tpo/-/issues/307
## - counter downgrade / stale mirror attacks on RecommendedTBBVersions - sign / verify tbb versions file
##   https://gitlab.torproject.org/tpo/applications/tor-browser/-/issues/13065
## - GETINFO consensus/packages exists but not yet populated.
##   https://gitlab.torproject.org/tpo/core/tor/-/issues/10395
## - Using torbrowser-launcher instead of tb-updater in Whonix
##   https://forums.whonix.org/t/using-torbrowser-launcher-instead-of-tb-updater-in-whonix/385

tb_print_help() {
cat <<'EOF'
usage: update-torbrowser [options]

Tor Browser Downloader (by Whonix developers)

Options:
  --help,              Show this help and exit.
  --debug              Enable shell tracing (set -x).
  --ordinary           Use curl's ordinary progress bar (disables curl-prgrs).
  --onion              Use Tor Project onion endpoints where applicable.
  --alpha              Use alpha release channel.
  --resume             Resume previous download.
  --reset              Hard reset using already downloaded archive (no re-download).
  --only-if-newer      Do not download if already up-to-date.
  --noask              Non-interactive mode (skip questions; sets input to none).
  --noaskstart         Do not ask to start Tor Browser after completion.
  --nokilltb           Do not kill running Tor Browser instances.
  --no-tor-con-check   Skip Tor connectivity checks (where applicable).
  --input METHOD       Input method: stdin | gui | none
  --no-install         Download only; do not install.
  --is-chroot          Running inside chroot.
  --postinst           Post-install mode (used by packaging); may drop privileges.

Notes:
  - For details, see: man update-torbrowser
EOF
}

tb_exit_function() {
   local exit_code="$1"
   trap "" ERR

   if [ "${exit_code:-}" = "0" ]; then
      log notice "END: Success."
      exit 0
   fi

   if [ "${anon_shared_inst_tb:-}" = "open" ]; then
      if [ "${tb_postinst:-}" = "true" ]; then
         log notice "END: Failing open.
More info:
$tb_documentation_base_url_clearnet/wiki/$tb_wiki/Advanced_Users#Tor_Browser_Update:_Technical_Details"
      fi
      exit 0
   else
      if [ "${tb_postinst:-}" = "true" ]; then
         log error "END: Failing closed.
More info:
$tb_documentation_base_url_clearnet/wiki/$tb_wiki/Advanced_Users#Tor_Browser_Update:_Technical_Details"
      fi
      exit "$exit_code"
   fi
}

tb_error_handler() {
   local exit_code="$?"
   trap "" ERR

   local MSG="<p>###########################################################
<br></br>## $SCRIPTNAME script bug.
<br></br>## No panic. Nothing is broken. Just some rare condition
<br></br>## has been hit. Try again later. There is likely a
<br></br>## solution for this problem. Please see the News,
<br></br>## User Help Forum and Documentation.
<br></br>## <a href=$tb_documentation_base_url_clearnet/wiki/$tb_wiki>$tb_documentation_base_url_clearnet/wiki/$tb_wiki</a>
<br></br>## Please report this bug!
<br></br>##
<br></br>## who_ami: <code>$who_ami</code>
<br></br>## BASH_COMMAND: <code>$BASH_COMMAND</code>
<br></br>## exit_code: <code>$exit_code</code>
<br></br>##
<br></br>## output_opts: <code>${output_opts[*]}</code>
<br></br>## progressbaridx: <code>$progressbaridx</code>
<br></br>##
<br></br>## For debugging, run:
<br></br>## <code>$SCRIPTNAME</code> --debug
<br></br>###########################################################</p>"

   [ -n "${tb_user_home:-}" ] || tb_user_home=~
   [ -n "${tb_install_folder:-}" ] || tb_install_folder="tb"

   if test -w "$tb_user_home/.cache/$tb_install_folder" ; then
      mkdir --parents -- "$tb_user_home/.cache/$tb_install_folder"
   fi
   if test -w "$tb_user_home/.cache/$tb_install_folder/torbrowser_updater_error.log" ; then
      append "$tb_user_home/.cache/$tb_install_folder/torbrowser_updater_error.log" "$MSG"
   fi

   ## In case function output_gui does not exist yet.
   if [ ! "$(type -t "output_gui")" = "function" ]; then
      output_gui() {
         stecho
      }
   fi

   if [ "${progressbaridx:-}" = "" ]; then
      true
   else
      output_gui "${output_opts[@]}" --progressbaridx "$progressbaridx" --progressx "100" || true
      progressbaridx=""
   fi

   output_gui "${output_opts[@]}" --messagex --titlex "$TITLE" --typex "error" --message "$MSG" --done
   output_gui "${output_opts[@]}" --messagecli --titlecli "$TITLE" --typecli "error" --message "$MSG" --done
   tb_exit_function 1
}

trap "tb_error_handler" ERR

download_fail_help_set() {
   DOWNLOAD_FAIL_HELP="<p>Possible reasons for download failure:</p>
<ul>
  <li>Internet connectivity issue.</li>
  <li>The download server is down.</li>
  <li>File size exceeded (endless data attack triggered).</li>
  <li>Other software update attack (rollback, indefinite freeze).</li>
  <li><code>$tb_title</code> Downloader (by <code>$tb_downloader_developers</code> developers) has been broken due to upstream changes.</li>
</ul>
<p>Recommendations:</p>
<ul>
  <li>Re-run <code>$SCRIPTNAME</code> with or without <code>--resume<c/ode> in terminal. For example: <code>$SCRIPTNAME --resume</code></li>
  <li>For more information, see the <a href='$tb_documentation_base_url_clearnet/wiki/$tb_wiki#Troubleshooting'>$tb_documentation_base_url_clearnet/wiki/$tb_wiki#Troubleshooting</a>.</li>
</ul>"
}

tb_run_function() {
   [ -v tb_skip_functions ] || tb_skip_functions=''
   case $tb_skip_functions in
   *"$@"*) log notice "Skipping '$*', because tb_skip_functions includes it."
                  return 0
                  ;;
   esac

   true "INFO: Running '$*', because tb_skip_functions does not include it."
   "$@"
}

root_check() {
   local tb_sandbox_exit_code
   if [ "$(id --user)" != "0" ]; then
     true "INFO: ${FUNCNAME[0]}: running as unprivileged account (non-root)."
     tb_running_as_root=false
     return 0
   fi
   tb_running_as_root=true

   if printf '%s\n' "$@" | grep -- "--postinst" >/dev/null 2>/dev/null ; then
      log notice "Dropping privileges to account 'tb-updater' because using '--postinst' option."
      tb_sandbox_exit_code='0'
      /usr/libexec/tb-updater/sandbox-update-torbrowser "$@" || tb_sandbox_exit_code="$?"
      tb_exit_function "$tb_sandbox_exit_code"
   fi

   log error "Do not run '$SCRIPTNAME' as root (sudo)! Instead, '$SCRIPTNAME' should be run as unprivileged account (non-root)."
   tb_exit_function 2
}

qubes_template_check() {
   local tb_sandbox_exit_code
   if ! [ -f /run/qubes/this-is-templatevm ]; then
      log notice "Not running in a Qubes TemplateVM, ok."
      return 0
   fi
   if [ "${who_ami}" = 'tb-updater' ]; then
      log notice "Running in a Qubes TemplateVM as user 'tb-updater', ok."
      return 0
   fi
   if pkg_installed user-sysmaint-split && [ "$(whoami)" != 'sysmaint' ]; then
      log error "'$SCRIPTNAME' must be run using account 'sysmaint' when running in a Qubes TemplateVM with user-sysmaint-split installed!"
      tb_exit_function 2
   fi
   tb_sandbox_exit_code='0'
   log notice "Dropping privileges to account 'tb-updater' because running in a Qubes TemplateVM, ok."
   sudo /usr/libexec/tb-updater/sandbox-update-torbrowser "$@" || tb_sandbox_exit_code="$?"
   tb_exit_function "$tb_sandbox_exit_code"
}

sysmaint_check() {
   if ! [ "${boot_session:-}" = "sysmaint_session" ]; then
      true "INFO: sysmaint_session: no"
      if ! [ "${who_ami}" = "sysmaint" ]; then
         true "INFO: sysmaint_session: no and not running as sysmaint, ok."
         return 0
      fi
      ## No need to check for account tb-updater, since we're blacklisting
      ## accounts here, not whitelisting them.
      log error "Do not run '$SCRIPTNAME' under account '$who_ami' in user session! Instead, in user session '$SCRIPTNAME' should be run under account 'user' or another unprivileged user."
      tb_exit_function 2
   fi

   true "INFO: sysmaint_session: yes"
   if [ "${who_ami}" = "sysmaint" ]; then
      true "INFO: sysmaint_session: yes and running as account 'sysmaint', ok."
      return 0
   fi
   ## We do need to check for account tb-updater here because we're
   ## whitelisting accounts in this scenario.
   if [ "${who_ami}" = "tb-updater" ]; then
      true "INFO: sysmaint_session: yes and running as account 'tb-updater', ok."
      return 0
   fi
   log error "Do not run '$SCRIPTNAME' under account '$who_ami' in sysmaint session! Instead, in sysmaint session '$SCRIPTNAME' should be run under account 'sysmaint'."
   tb_exit_function 2
}

tb_sanity_tests() {
   has id
   has basename
   has touch
   has uname
   has mkdir
   has pidof
   has chmod
   has cp
   has mv
   has killall
   has safe-rm
   has head
   has tar
   has date
   has /usr/libexec/msgcollector/msgcollector
   has /usr/libexec/msgcollector/pv_wrapper
   ## tbbversion function
   has grep
   ## tbbversion function
   has sed
   has sort
   has pv
   has mkfifo
   has jq
   has sanitize-string
   has sq
   has sqop
}

tb_ex_funct() {
   local MSG
   MSG="$0: INFO: ${FUNCNAME[0]}: Signal '$SIGNAL_TYPE' received. Cleaning up..."

   if [ "${last_pid_list:-}" = "" ]; then
      true
   else
      local childpids_list childpid_item
      for pid in $last_pid_list; do
         childpids_list=$(pgrep --parent "$pid")
         for childpid_item in $childpids_list ; do
            pkill --signal sigkill --parent "$childpid_item" &>/dev/null || true
         done
      done
   fi

   if [ "${progressbaridx:-}" = "" ]; then
      true
   else
      output_gui "${output_opts[@]}" --progressbaridx "$progressbaridx" --progressx "100" || true
      progressbaridx=""
   fi

   output_gui "${output_opts[@]}" --messagecli --typecli "info" --message "$MSG" --done

   #MSG="Aborted."
   #output_gui "${output_opts[@]}" --messagex --typex "error" --message "$MSG" --done
   #log error "$MSG"

   MSG="$0: INFO: ${FUNCNAME[0]}: Signal '$SIGNAL_TYPE' received. Exiting."
   output_gui "${output_opts[@]}" --messagecli --typecli "info" --message "$MSG" --done
}

tb_signal_sigterm() {
   SIGNAL_TYPE="sigterm"
   tb_ex_funct
   tb_exit_function 143
}

trap "tb_signal_sigterm" SIGTERM

tb_signal_sigint() {
   SIGNAL_TYPE="sigint"
   tb_ex_funct
   tb_exit_function 130
}

trap "tb_signal_sigint" SIGINT ## ctrl + c

trap_sigusr2() {
   SIGNAL_TYPE="SIGUSR2"
   tb_ex_funct
   tb_exit_function 3
}

trap "trap_sigusr2" SIGUSR2 ## msgcollector, yad cancel button

tb_parse_cmd_options() {
   ## Thanks to:
   ## https://mywiki.wooledge.org/BashFAQ/035

   while [ $# -gt 0 ]
   do
       case $1 in
           --help|-h)
               tb_print_help
               tb_exit_function 0
               ;;
           --debug)
               set -x
               shift
               ;;
           --ordinary)
               ordinary="true"
               shift
               ;;
           --update)
               true ## legacy
               shift
               ;;
           --noask)
               TB_FORCE_INSTALL="1"
               shift
               ;;
           --only-if-newer)
               UPDATE_ONLY_IF_NEWER="1"
               shift
               ;;
           --nokilltb)
               NOKILLTB="1"
               shift
               ;;
           --postinst)
               tb_postinst="true"
               UPDATE_ONLY_IF_NEWER="1"
               shift
               ;;
           --is-chroot)
               is_chroot="true"
               shift
               ;;
           --noaskstart)
               noaskstart="true"
               shift
               ;;
           --alpha)
               tbb_download_alpha_version="true"
               shift
               ;;
           --no-tor-con-check)
               TB_NO_TOR_CON_CHECK="1"
               shift 1
               ;;
           --input)
               TB_INPUT="$2"
               shift 2
               ;;
           --no-install)
               TB_NO_INSTALL="true"
               shift
               ;;
           --resume)
               CURL_RESUME="--continue-at -"
               shift
               ;;
           --onion)
               [ -n "${tb_onion:-}" ] || tb_onion="true"
               shift
               ;;
           --reset)
               [ -n "${tb_hard_reset:-}" ] || tb_hard_reset="true"
               [ -n "${TB_NO_CLEANUP:-}" ] || TB_NO_CLEANUP="true"
               shift
               ;;
           --danger-insecure-self-test)
               ## Deliberately obscure flag name. Enables a self-test mode
               ## that replaces the real signing key and download URLs with
               ## file:// fixtures in the supplied directory. In this mode
               ## the real $tb_title installation will be overwritten just
               ## as in a normal run; do NOT use on production systems.
               if [ -z "${2:-}" ] || [ ! -d "$2" ]; then
                  log error "--danger-insecure-self-test requires a fixture directory that exists."
                  tb_exit_function 4
               fi
               log warn "=============================================================================="
               log warn " DANGER: --danger-insecure-self-test enabled."
               log warn "   Fixtures under '$2' replace the signing key, download URLs and"
               log warn "   recommended-versions feed. The real '$SCRIPTNAME' installation on this"
               log warn "   system WILL be overwritten with fixture content. Do NOT run on a"
               log warn "   production install."
               log warn "=============================================================================="
               export danger_insecure_self_test_mode='yes'
               export signing_key="$2/test-key.asc"
               export TBB_REMOTE_FOLDER="file://$2"
               export tbb_versions_base_url="file://$2"
               ## tb_connectivity_checks_curl curls $tbb_download_base_url
               ## and fails on zero-byte output, so point it at an actual
               ## file rather than the fixture directory.
               export tbb_download_base_url="file://$2/recommended-versions.json"
               export TBB_VERSIONS_FILE_LINK="file://$2/recommended-versions.json"
               ## The default CURL_FORCE_SSL pins '--proto =https'; widen
               ## it to allow the file:// fixtures.
               export CURL_FORCE_SSL="--proto =https,file"
               export TB_NO_TOR_CON_CHECK="1"
               export TB_SKIP_CONNECTIVITY_CHECK="1"
               ## 'sq cert lint' only exists from sq 1.x and 'sq verify'
               ## has moved argument names between versions. The self-test
               ## runs against whatever sq the host has; pointing both at
               ## 'echo' skips those two calls without losing the real
               ## sqop verify in the middle -- which is what actually
               ## decides verification_success.
               export sq_cert_lint_binary="echo"
               export sq_verify_binary="echo"
               ## curl-prgrs assumes an HTTP-like transport; use plain
               ## curl for file:// fixtures.
               ordinary="true"
               shift 2
               ;;
           --)
               shift
               break
               ;;
           -*)
               log error "Unknown option: '$1'"
               log error "See:"
               log error "    man $SCRIPTNAME"
               tb_exit_function 4
               ;;
           *)
               break
               ;;
       esac
   done

   ## If there are input files (for example) that follow the options, they
   ## will remain in the "$@" positional parameters.
   true "${BASH_SOURCE[0]} \$*: $*"
}

tb_stdin() {
   if [ ! "${TB_INPUT:-}" = "" ]; then
      true "INFO: TB_INPUT is already set to '$TB_INPUT', skipping auto detection, ok."
      return 0
   fi
   if [ "${TB_FORCE_INSTALL:-}" = "1" ]; then
      log notice "TB_FORCE_INSTALL is set to '1', therefore setting TB_INPUT to 'none'."
      TB_INPUT="none"
      export TB_USE_MSGCOLLECTOR="false"
      return 0
   fi
   if [ -t "0" ]; then
      #log notice "stdin connected to terminal, setting \
#TB_INPUT to 'stdin', will use terminal for input, ok."
      #log notice "Alternatively, if want to run from \
#command line, but still use the graphical user interface for input, \
#you could add to command line: --input gui"
      TB_INPUT="stdin"
      export TB_USE_MSGCOLLECTOR="false"
      return 0
   fi
   log notice "stdin not connected to terminal, probably run in graphical environment, ok."
}

tb_qubes_dvm_template() {
   if printf '%s\n' "${qubes_vm_name:-}" | grep --invert-match -- "-dvm" >/dev/null 2>/dev/null ; then
      log notice "Not running inside Qubes Disposable Template, ok."
      return 0
   fi

   local MSG="\
<p>Do not run <code>$TITLE</code> in Qubes Disposable Template!<br></br>
<br></br>
More info: <a href=$tb_documentation_base_url_clearnet/wiki/$tb_wiki/Advanced_Users#Running_Tor_Browser_in_Qubes_Template_or_Disposable_Template>$tb_documentation_base_url_clearnet/wiki/$tb_wiki/Advanced_Users#Running_Tor_Browser_in_Qubes_Template_or_Disposable_Template</a></p>"

   output_gui "${output_opts[@]}" --messagex --titlex "$TITLE" --typex "error" --message "$MSG" --done

   output_cli error "$MSG"

   tb_exit_function 1
}

output_cli() {
   local log_level MSG
   log_level="$1"
   shift || true
   MSG="$(sanitize-string -- nolimit "$@")"
   log "$log_level" "$MSG"
}

output_gui() {
   local MSG output_tool
   true "${FUNCNAME[0]}: START"
   true "${FUNCNAME[0]}: args: ${1+$@}"
   output_tool="/usr/libexec/msgcollector/msgcollector"
   ## Variable initialization required because 'curl-prgrs' uses "set -o nounset".
   [[ -v TB_USE_MSGCOLLECTOR ]] || TB_USE_MSGCOLLECTOR=""
   [[ -v outputfunc_verbose ]] || outputfunc_verbose=""
   if [ "${TB_USE_MSGCOLLECTOR:-}" = "false" ]; then
      while true; do
         case $1 in
            --messagex)
               ## Only show --messagecli. Skip --messagex.
               true "${FUNCNAME[0]}: --messagex END"
               return 0
               ;;
            --message)
               MSG="$2"
               shift 2
               break
               ;;
            *)
               break
               ;;
         esac
      done
      ## MSG will be unset for '--progressx'.
      [[ -v MSG ]] || MSG=""
      if [ ! "${MSG:-}" = "" ]; then
         MSG="$(sanitize-string -- nolimit "$MSG")"
         log notice "$MSG"
      fi
   elif [ "${outputfunc_verbose:-}" = "true" ]; then
      printf '%s\n' "RUNNING: bash -x $output_tool --identifier $IDENTIFIER ${1+$@}"
      bash -x $output_tool --identifier "$IDENTIFIER" "$@"
   else
      $output_tool --identifier "$IDENTIFIER" "$@"
   fi
   true "${FUNCNAME[0]}: END END"
}

tb_config_folder_parser() {
   [ -n "${tb_settings_folder:-}" ] || tb_settings_folder="torbrowser.d"
   true "tb_settings_folder: $tb_settings_folder"
   ## $tb_settings_folder is an environment variable used to build paths
   ## that get 'source'd, so an unvalidated value could traverse out of
   ## /etc or /usr/local/etc into arbitrary .conf files. Restrict to the
   ## shape the wrappers actually use: lowercase letters, digits, dot,
   ## hyphen, underscore. Anything else falls back to the Tor Browser
   ## default.
   if ! [[ "${tb_settings_folder:-}" =~ ^[a-z0-9._-]+$ ]]; then
      log warn "Ignoring unsafe tb_settings_folder='$tb_settings_folder', falling back to 'torbrowser.d'."
      tb_settings_folder="torbrowser.d"
   fi
   shopt -s nullglob
   local i
   for i in \
      "/etc/${tb_settings_folder}"/*.conf \
      "/usr/local/etc/${tb_settings_folder}"/*.conf \
      ; do
         bash -n "$i"
         source "$i"
   done
}

tb_settings_chroot_common() {
   ## Do not run in chroot if tb_install_in_chroot=false.
   if [ ! "${tb_install_in_chroot:-}" = "false" ]; then
      [ -n "${tb_updater_run:-}" ] || tb_updater_run=true
   fi
   ## Fail open, if there was an error.
   [ -n "${anon_shared_inst_tb:-}" ] || anon_shared_inst_tb=open
   ## Skip Tor connectivity check when running inside chroot.
   [ -n "${TB_NO_TOR_CON_CHECK:-}" ] || TB_NO_TOR_CON_CHECK="1"
   ## Hack to disable using proxy settings when running inside chroot.
   ## We are using --fail anyhow. No problem to duplicate it.
   [ -n "${CURL_PROXY:-}" ] || CURL_PROXY=( "--fail" )

   ## Careful as it would leak information to logs:
   ## Connection #0 to host 10.137.0.82 left intact
   #[ -n "${CURL_OPTS:-}" ] || CURL_OPTS="--verbose"

   ## Debugging.
   [ -n "${pv_wrapper_debug:-}" ] || pv_wrapper_debug=true
}

tb_settings_postinst_common() {
   ## Skip installation confirmation messages when running with
   ## --postinst, because we will be using hardcoded version numbers to verify
   ## upstream's version info.
   ## Downgrade / freeze attacks should not be possible, because file names
   ## that include the version number will be verified using gpg.
   [ -n "${tb_confirm_installation_skip:-}" ] || tb_confirm_installation_skip=true

   [ -n "${NOKILLTB:-}" ] || NOKILLTB="1"
   [ -n "${noaskstart:-}" ] || noaskstart="true"
   [ -n "${TB_INPUT:-}" ] || TB_INPUT="none"
   export TB_USE_MSGCOLLECTOR="false"

   ## Fail open, if there was an error.
   [ -n "${anon_shared_inst_tb:-}" ] || anon_shared_inst_tb=open

   ## Using curl instead of curl-prgrs to debug server issue.
   ## --ordinary
   #[ -n "${ordinary:-}" ] || ordinary="true"

   ## Careful as it would like information to logs:
   ## Connection #0 to host 10.137.0.82 left intact
   #[ -n "${CURL_OPTS:-}" ] || CURL_OPTS="--verbose"
}

tb_settings_qubes_common_templatevm() {
   ## Skip Tor connectivity check when running inside Qubes Template,
   ## because since Qubes R4 Templates are non-networked by default.
   [ -n "${TB_NO_TOR_CON_CHECK:-}" ] || TB_NO_TOR_CON_CHECK="1"
}

tb_settings_not_templatevm() {
   ## TODO: Unused, remove?
   true
}

tb_settings_qubes_postinst_templatevm() {
   ## Do not run during Qubes Template postinst if tb_install_follow=false.
   if [ ! "${tb_install_follow:-}" = "false" ]; then
      [ -n "${tb_updater_run:-}" ] || tb_updater_run=true
   fi
}

tb_settings_qubes_manual_run_templatevm() {
   [ -n "${tb_updater_run:-}" ] || tb_updater_run=true
}

tb_settings_manual_run_common() {
   [ -n "${tb_updater_run:-}" ] || tb_updater_run=true
}

tb_preparation() {
   [ -n "${tb_wiki:-}" ] || tb_wiki="Tor_Browser"
   [ -n "${tb_title:-}" ] || tb_title="Tor Browser"
   [ -n "${IDENTIFIER:-}" ] || IDENTIFIER="torbrowser-downloader"
   [ -n "${tb_install_folder:-}" ] || tb_install_folder="tb"
   [ -n "${tb_install_folder_dot:-}" ] || tb_install_folder_dot=".tb"
   [ -n "${tb_browser_name:-}" ] || tb_browser_name="tor-browser"
   [ -n "${tb_bin:-}" ] || tb_bin="torbrowser"
   [ -n "${tb_downloader_developers:-}" ] || tb_downloader_developers="Whonix"
   [ -n "${tb_documentation_base_url_clearnet:-}" ] || tb_documentation_base_url_clearnet="https://www.whonix.org"
   [ -n "${tb_global_binary_dir:-}" ] || tb_global_binary_dir='/var/cache/tb-binary'

   if [ "${tb_postinst:-}" = "true" ]; then
      [ -n "${tb_manual_run:-}" ] || tb_manual_run=false
   else
      [ -n "${tb_manual_run:-}" ] || tb_manual_run=true
   fi

   if has qubesdb-read; then
      [ -n "${is_qubes:-}" ] || is_qubes=true

      ## qubesdb-read fails
      ## - inside chroot,
      ## - perhaps during upgrades
      ##   - https://github.com/QubesOS/qubes-issues/issues/2497
      ##   - https://github.com/QubesOS/qubes-issues/issues/2509
      ## therefore overwriting with '|| true'.
      [ -n "${qubes_vm_name:-}" ] || qubes_vm_name="$(qubesdb-read /name)" || true
   else
      [ -n "${is_qubes:-}" ] || is_qubes=false
   fi

   if [ "${is_chroot:-}" = "true" ]; then
      log notice "chroot: is_chroot=true"
      tb_settings_chroot_common
   else
      true "INFO: chroot: is_chroot=true is not set, ok."
   fi

   if test -f /run/qubes/this-is-templatevm ; then
      [ -n "${running_inside_qubes_template_vm:-}" ] || running_inside_qubes_template_vm=true
   else
      [ -n "${running_inside_qubes_template_vm:-}" ] || running_inside_qubes_template_vm=false
   fi

   if [ "${running_inside_qubes_template_vm:-}" = "true" ] ; then
      tb_settings_qubes_common_templatevm
   else
      tb_settings_not_templatevm
   fi

   if [ "${tb_postinst:-}" = "true" ]; then
      tb_settings_postinst_common
      if [ "${running_inside_qubes_template_vm:-}" = "true" ] ; then
         tb_settings_qubes_postinst_templatevm
      fi
   fi

   true "tb_manual_run: $tb_manual_run"
   if [ "${tb_manual_run:-}" = "true" ]; then
      tb_settings_manual_run_common
      if [ "${running_inside_qubes_template_vm:-}" = "true" ]; then
         tb_settings_qubes_manual_run_templatevm
      fi
   fi

   if [ "${tb_user_home:-}" = "" ]; then
      tb_user_home=~
      if [ "${tb_user_home:-}" = '/root' ] || [ "${tb_running_as_root}" = 'true' ]; then
         tb_user_home="$tb_global_binary_dir"
         tb_auto_set_user_home_msg="Automatically setting download folder to '$tb_user_home', because running as root."
      fi
   fi

   [ -n "${tb_home_folder:-}" ] || tb_home_folder="$tb_user_home/$tb_install_folder_dot"
   [ -n "${tb_browser_folder:-}" ] || tb_browser_folder="$tb_home_folder/$tb_browser_name"
   [ -n "${tb_cache_folder:-}" ] || tb_cache_folder="$tb_user_home/.cache/$tb_install_folder"
   [ -n "${tb_temp_folder:-}" ] || tb_temp_folder="$tb_cache_folder/temp"
   [ -n "${tb_downloaded_files_folder:-}" ] || tb_downloaded_files_folder="$tb_cache_folder/files"
   [ -n "${tb_extract_temp_folder:-}" ] || tb_extract_temp_folder="$tb_cache_folder/$tb_browser_name"

   [ -n "${tb_local_version_filename:-}" ] || tb_local_version_filename="tbb_version.json"
   [ -n "${tb_local_version_file:-}" ] || tb_local_version_file="$tb_browser_folder/Browser/$tb_local_version_filename"
   [ -n "${tbb_download_alpha_version:-}" ] || tbb_download_alpha_version="false"

   [ -n "${TB_KEEP_OLD_VERSIONS_COUNT:-}" ] || TB_KEEP_OLD_VERSIONS_COUNT="0"

   if [ ! "${tb_updater_run:-}" = "true" ]; then
      true "tb_updater_run is set to: '$tb_updater_run'"
      log notice "Using '--postinst' option but outside of Qubes Template, skipping, ok."
      tb_exit_function 0
   fi

   ## Fortunately, on linux-image-486 kernel, while "uname --all" returns for example
   ## "Linux host 3.2.0-4-486 #1 Debian 3.2.41-2 i686 GNU/Linux",
   ## "uname --machine" returns "i686" and Tor Browser works fine with linux-image-486 kernel.
   ## (There are no 486 downloads for Tor Browser.)
   ##ARCH="x86_64"
   ##ARCH="i686"
   if [ "${ARCH:-}" = "" ]; then
      true "INFO: Auto detecting ARCH..."
      ARCH="$(uname --machine)"
      log notice "ARCH '$ARCH' detected."
   else
      log notice "Skipping auto detecting ARCH because already set."
      log notice "ARCH is set to '$ARCH'."
   fi

   if [ "${ARCH_DOWNLOAD:-}" = "" ]; then
      true "INFO: Auto detecting ARCH_DOWNLOAD..."
      if [ "${ARCH:-}" = "i386" ]; then
         ARCH="i686"
      fi
      if printf '%s\n' "$ARCH" | grep -- "aarch64" >/dev/null 2>/dev/null ; then
         ARCH="arm64"
      fi

      if [ "${ARCH:-}" = "x86_64" ]; then
         [ -n "${ARCH_DOWNLOAD:-}" ] || ARCH_DOWNLOAD="linux-x86_64"
      elif [ "${ARCH:-}" = "i686" ]; then
         [ -n "${ARCH_DOWNLOAD:-}" ] || ARCH_DOWNLOAD="linux-i686"
      elif [ "${ARCH:-}" = "arm64" ]; then
         [ -n "${ARCH_DOWNLOAD:-}" ] || ARCH_DOWNLOAD="linux-arm64"
      else
         [ -n "${ARCH_DOWNLOAD:-}" ] || ARCH_DOWNLOAD="linux-$ARCH"
         log warning "Guessing. Setting ARCH_DOWNLOAD to '$ARCH_DOWNLOAD'. If functional, this is probably OK."
      fi

      log notice "ARCH_DOWNLOAD '$ARCH_DOWNLOAD' detected."
   else
      log notice "Skipping auto detecting ARCH_DOWNLOAD because already set."
      log notice "ARCH_DOWNLOAD is set to '$ARCH_DOWNLOAD'."
   fi

   ## provides tbbversion function
   source /usr/libexec/tb-updater/version-validator

   if [ "${my_tty:-}" = "" ]; then
      local my_tty_exit_code
      my_tty_exit_code="0"
      my_tty="$(tty)" || { my_tty_exit_code="$?" ; true; };
      if [ ! "${my_tty_exit_code:-}" = "0" ]; then
         my_tty="none"
      fi
      ## Just in case.
      if [ "${my_tty:-}" = "" ]; then
         my_tty="none"
      fi
   fi

   TITLE="$tb_title Downloader (by $tb_downloader_developers developers)"

   output_gui --icon "$ICON"
   output_gui --parenttty "$my_tty"
   output_gui --titlex "$TITLE"
   output_gui --titlecli "$TITLE"

   ## Make visible to curl-prgrs.
   export IDENTIFIER
   export who_ami

   if [ "${who_ami}" = 'tb-updater' ]; then
      noaskstart='true'
   fi

   ret="0"
   has curl.anondist-orig || { ret="$?" ; true; };

   if [ "${ret:-}" = "0" ]; then
      ## using the non-uwt-wrapped version, if the uwt wrapper is installed,
      ## which is the case on a default Whonix installation
      CURL=curl.anondist-orig
   else
      ret="0"
      has curl || { ret="$?" ; true; };
      if [ "${ret:-}" = "0" ]; then
         ## falling back to real curl, if the uwt wrapper has been uninstalled
         CURL=curl
      else
         local MSG="uwt_tool: Can not find curl. Please report this bug!"
         output_gui "${output_opts[@]}" --messagex --typex "error" --message "$MSG"
         log error "$MSG"
         return 0
      fi
   fi

   if [ "${CURL_PRGRS:-}" = "" ]; then
      if [ "${ordinary:-}" = "true" ]; then
         CURL_PRGRS="$CURL"
      elif [ -x /usr/libexec/helper-scripts/curl-prgrs ]; then
         ## Curl progress wrapper.
         CURL_PRGRS="/usr/libexec/helper-scripts/curl-prgrs"
      else
         true "/usr/libexec/helper-scripts/curl-prgrs not available, skipping."
         true "Setting CURL_PRGRS to $CURL."
         CURL_PRGRS="$CURL"
      fi
   fi

   ## Debugging CURL_PRGRS.
   #CURL_PRGRS="bash -x /usr/libexec/helper-scripts/curl-prgrs"

   ## Debugging / disabling CURL_PRGRS.
   #CURL_PRGRS="$CURL"

   ## Export CURL variable, so it can be read by $CURL_PRGRS.
   export CURL

   if [ "${tbb_download_alpha_version:-}" = "true" ]; then
      [ -n "${TBB_RELEASE_CHANNEL:-}" ] || TBB_RELEASE_CHANNEL="alpha"
   else
      [ -n "${TBB_RELEASE_CHANNEL:-}" ] || TBB_RELEASE_CHANNEL="release"
   fi

   ## https://web.archive.org/web/20210603225307/https://dist.torproject.org/torbrowser/10.0.17/tor-browser-linux64-10.0.17_en-US.tar.xz
   [ -n "${TBB_DOWNLOAD_APPENDIX:-}" ] || TBB_DOWNLOAD_APPENDIX=""
   [ -n "${tbb_versions_base_folder:-}" ] || tbb_versions_base_folder="torbrowser/update_3"

   ## https://web.archive.org/web/20141004085451/https://www.torproject.org/projects/torbrowser/RecommendedTBBVersions file was later abolished by upstream
   ## https://gitlab.torproject.org/tpo/applications/tor-browser/-/issues/33521
   ## https://web.archive.org/web/20201210123314/https://www.torproject.org/projects/torbrowser/RecommendedTBBVersions/
   ## https://forums.whonix.org/t/update-torbrowser-does-not-see-version-10-0-6/10711
   ##
   ## example:
   ## http://2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion/dist/torbrowser/10.0.6/tor-browser-linux64-10.0.6_en-US.tar.xz

   if [ "${tb_onion:-}" = "true" ]; then
      ## dist.torproject.org has no corresponding onion address at time of writing.
      ## https://web.archive.org/web/20210424113513/https://securityheaders.com/?q=https%3A%2F%2Fdist.torproject.org%2F&followRedirects=on
      ##
      ## https://onion.torproject.org/
      ## https://web.archive.org/web/20210424113401/https://onion.torproject.org/
      ##
      ## https://securityheaders.com/?q=https%3A%2F%2Fwww.torproject.org&followRedirects=on
      ## https://web.archive.org/web/20210424113646/https://securityheaders.com/?q=https%3A%2F%2Fwww.torproject.org&followRedirects=on
      [ -n "${tbb_download_base_url:-}" ] || tbb_download_base_url="http://2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion"
      [ -n "${TBB_REMOTE_FOLDER:-}" ] || TBB_REMOTE_FOLDER="http://2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion/dist/torbrowser"

      ## https://securityheaders.com/?q=https%3A%2F%2Faus1.torproject.org%2Ftorbrowser%2Fupdate_3%2Frelease%2Fdownloads.json&followRedirects=off
      ## https://web.archive.org/web/20210424113759/https://securityheaders.com/?q=https%3A%2F%2Faus1.torproject.org%2Ftorbrowser%2Fupdate_3%2Frelease%2Fdownloads.json&followRedirects=off
      ## Onion-Location	http://ot3ivcdxmalbsbponeeq5222hftpf3pqil24q3s5ejwo5t52l65qusid.onion/torbrowser/update_3/release/downloads.json
      [ -n "${tbb_versions_base_url:-}" ] || tbb_versions_base_url="http://ot3ivcdxmalbsbponeeq5222hftpf3pqil24q3s5ejwo5t52l65qusid.onion"
      [ -n "${TBB_VERSIONS_FILE_LINK:-}" ] || TBB_VERSIONS_FILE_LINK="${tbb_versions_base_url}/${tbb_versions_base_folder}/${TBB_RELEASE_CHANNEL}/download-${ARCH_DOWNLOAD}.json"
   else
      ## Download from the torproject.org clearnet by default.
      [ -n "${tbb_download_base_url:-}" ] || tbb_download_base_url="https://www.torproject.org"
      [ -n "${TBB_REMOTE_FOLDER:-}" ] || TBB_REMOTE_FOLDER="https://www.torproject.org/dist/torbrowser"
      [ -n "${CURL_FORCE_SSL:-}" ] || CURL_FORCE_SSL="--tlsv1.3 --proto =https"

      [ -n "${tbb_versions_base_url:-}" ] || tbb_versions_base_url="https://aus1.torproject.org"
      [ -n "${TBB_VERSIONS_FILE_LINK:-}" ] || TBB_VERSIONS_FILE_LINK="${tbb_versions_base_url}/${tbb_versions_base_folder}/${TBB_RELEASE_CHANNEL}/download-${ARCH_DOWNLOAD}.json"
   fi

   if [ "${DEV_PASSTHROUGH:-}" = "1" ]; then
      [ -n "${CURL_PROXY:-}" ] || CURL_PROXY=()
   elif [ "${running_inside_qubes_template_vm:-}" = "true" ] ; then
      ## Use Qubes updates proxy.
      if [ "${CURL_PROXY:-}" = "" ]; then
         PROXY_SERVER='http://127.0.0.1:8082/'
         CURL_PROXY=( "--proxy" "$PROXY_SERVER" )
      fi
   elif [ -f /usr/share/whonix/marker ]; then
      ## sets: GATEWAY_IP
      eval "$(/usr/libexec/helper-scripts/settings_echo)"
      [ -n "${SOCKS_PORT_TBB_DOWNLOAD:-}" ] || SOCKS_PORT_TBB_DOWNLOAD="9115"
      local random_socks_password
      ## https://spec.torproject.org/socks-extensions.html
      random_socks_password="$(random_alpha_numeric 43)"
      [ -n "${CURL_PROXY:-}" ] || CURL_PROXY=(
         '--proxy' "socks5h://$GATEWAY_IP:$SOCKS_PORT_TBB_DOWNLOAD"
         '--proxy-user' "<torS0X>0:tb-updater_${random_socks_password}"
      )
   elif [ "${tb_onion:-}" = 'true' ]; then
      ## Onion download on non-Whonix.
      ##
      ## sets: GATEWAY_IP
      eval "$(/usr/libexec/helper-scripts/settings_echo)"
      ## Using port 9050 rather than 9115 here since Tor is likely not
      ## configured with a separate port for downloading Tor Browser.
      [ -n "${SOCKS_PORT_TBB_DOWNLOAD:-}" ] || SOCKS_PORT_TBB_DOWNLOAD="9050"
      local random_socks_password
      ## https://spec.torproject.org/socks-extensions.html
      random_socks_password="$(random_alpha_numeric 43)"
      [ -n "${CURL_PROXY:-}" ] || CURL_PROXY=(
         '--proxy' "socks5h://$GATEWAY_IP:$SOCKS_PORT_TBB_DOWNLOAD"
         '--proxy-user' "<torS0X>0:tb-updater_${random_socks_password}"
      )
   else
      [ -n "${CURL_PROXY:-}" ] || CURL_PROXY=()
   fi

   log notice "CURL_PROXY: '${CURL_PROXY[*]}'"

   ## Also used by function tbbversion.
   [ -n "${RecommendedTBBVersions:-}" ] || RecommendedTBBVersions="$tb_cache_folder/RecommendedTBBVersions"

   if [ -z "${tbb_version_last_downloaded_save_file:-}" ]; then
      tbb_version_last_downloaded_save_file="$tb_cache_folder/tbb_version_last_downloaded_save_file"
      if [ "${who_ami}" = 'tb-updater' ]; then
         ## Read the existing version from the system-wide location.
         tbb_version_last_downloaded_save_read_file="$tb_global_binary_dir/.cache/$tb_install_folder/tbb_version_last_downloaded_save_file"
      fi
   fi
   [ -n "${tbb_version_last_downloaded_save_read_file:-}" ] || tbb_version_last_downloaded_save_read_file="$tbb_version_last_downloaded_save_file"

   if [ -f "$tbb_version_last_downloaded_save_read_file" ]; then
      [ -n "${tbb_version_previous_downloaded_version:-}" ] || tbb_version_previous_downloaded_version="$(stcat "$tbb_version_last_downloaded_save_read_file")"
   fi
   [ -n "${tbb_version_previous_downloaded_version:-}" ] || tbb_version_previous_downloaded_version="none"
   tbb_version_previous_downloaded_version_stripped="$(sanitize-string -- 20 "$tbb_version_previous_downloaded_version")"

   if ! test -f /usr/share/whonix/marker ; then
      [ -n "${TB_NO_TOR_CON_CHECK:-}" ] || TB_NO_TOR_CON_CHECK="1"
   fi

   if [ ! "${tb_auto_set_user_home_msg:-}" = "" ]; then
      log notice "$tb_auto_set_user_home_msg"
   fi
}

tb_create_folders() {
   mkdir --parents -- "$tb_home_folder"
   mkdir --parents -- "$tb_cache_folder"
   mkdir --parents -- "$tb_downloaded_files_folder"

   ## Required for /usr/libexec/helper-scripts/tor_bootstrap_check.bsh.
   TEMP_DIR="$tb_temp_folder"
   export TEMP_DIR
   safe-rm --recursive --force -- "$TEMP_DIR"
   mkdir --parents -- "$TEMP_DIR"
}

# tb_skip_if_higher_or_equal_version_already_downloaded() {
#    ## Do this only when using --postinst.
#    if [ ! "${tb_postinst:-}" = "true" ]; then
#       true "INFO: Not using --postinst, therefore skipping ${FUNCNAME[0]}."
#       return 0
#    fi
#    if [ "${tbb_version_previous_downloaded_version_stripped:-}" = "none" ]; then
#       true "INFO: Could not determine tbb_version_previous_downloaded_version_stripped (set to 'none'), therefore skipping ${FUNCNAME[0]}."
#       return 0
#    fi
#    if [ "${tbb_hardcoded_version:-}" = "" ]; then
#       true "INFO: tbb_hardcoded_version is empty, therefore skipping ${FUNCNAME[0]}."
#       return 0
#    fi
#    if dpkg --compare-versions -- "$tbb_version_previous_downloaded_version_stripped" ge "$tbb_hardcoded_version" ; then
#       log notice "Previously downloaded version: '$tbb_version_previous_downloaded_version_stripped'"
#       log notice "Previously downloaded version is greater or equal tbb_hardcoded_version, good, ok."
#    fi
#
#    ## TODO: Does not belong here?
#    ## Being careful with deletion.
# #    if [ "${tb_downloaded_files_folder:-}" = "$tb_global_binary_dir/.cache/$tb_install_folder/files" ]; then
# #       log notice "No need to keep older versions in Qubes root image. Saving disk space. Therefore deleting..."
# #       printf '%s\n' "safe-rm -r -f -- '$tb_downloaded_files_folder'"
# #       safe-rm -r -f -- "$tb_downloaded_files_folder"
# #       mkdir --parents -- "$tb_downloaded_files_folder"
# #    fi
# }

tb_connectivity_checks_interfaces() {
   local MSG
   if [ "${running_inside_qubes_template_vm:-}" = "true" ] ; then
      ## Qubes Templates are non-networked as per Qubes default.
      return 0
   fi

   log notice "Running network interface available check... "
   if /usr/libexec/helper-scripts/check-network-access; then
      log notice "Network interface available."
      return 0
   fi

   MSG="<p><b>No network access.</b></p>

<p>Please check: <blockquote>Start menu -> System -> systemcheck
         <br></br>or in Terminal: systemcheck
         <br></br>or in Terminal with debugging: systemcheck -v</blockquote></p>

<p>Run systemcheck on Whonix-Gateway as well.</p>

<p>If systemcheck reports no problems with internet activity and downloading $tb_title still fails, please report a bug!</p>

<p>Debugging information:<br/>
/usr/libexec/helper-scripts/check-network-access non-zero exit code.</p>"

   output_gui "${output_opts[@]}" --messagex --typex "error" --message "$MSG" --done

   output_cli error "$MSG"

   tb_exit_function 18
}

tb_connectivity_checks_tor() {
   local MSG
   if [ "${TB_NO_TOR_CON_CHECK:-}" = "1" ]; then
      return 0
   fi

   if [ "${CURL_PROXY:-}" = "" ]; then
      return 0
   fi

   if [ -x /usr/libexec/helper-scripts/tor_enabled_check ]; then
      log notice "Running Tor enabled check... "

      source /usr/libexec/helper-scripts/tor_enabled_check
      ## sets: TOR_ENABLED
      check_tor_enabled_do

      if [ "${TOR_ENABLED:-}" = "0" ]; then
         MSG="<p><b>Tor not enabled yet.</b></p>

<p>Please check: <blockquote>Start menu -> System -> systemcheck
              <br></br>or in Terminal: systemcheck
              <br></br>or in Terminal with debugging: systemcheck -v</blockquote></p>

<p>Run systemcheck on Whonix-Gateway as well.</p>

<p>If systemcheck reports no problems with internet activity and downloading $tb_title still fails, please report a bug!</p>

<p>Debugging information:<br />
You could use <code>--no-tor-con-check</code> if you think function <code>${FUNCNAME[0]}</code> should be skipped.</p>"

         output_gui "${output_opts[@]}" --messagex --typex "error" --message "$MSG" --done

         output_cli error "$MSG"

         tb_exit_function 5
      fi
      log notice "Tor enabled check: success"
   else
      true "/usr/libexec/helper-scripts/tor_enabled_check does not exist, skipping."
   fi

   if [ -x /usr/libexec/helper-scripts/tor_bootstrap_check.bsh ]; then
      log notice "Running Tor bootstrap check... "
      source /usr/libexec/helper-scripts/tor_bootstrap_check.bsh
      ## sets: tor_bootstrap_percent
      tb_run_function check_tor_circuit_established

      if [ ! "${tor_circuit_established:-}" = "1" ]; then
         MSG="<p></b>Tor not fully bootstrapped.</b></p>

<p>Possible reasons:
<br></br>- no internet connectivity</p>

<p>Please check: <blockquote>Start menu -> System -> systemcheck
              <br></br>or in Terminal: systemcheck
              <br></br>or in Terminal with debugging: systemcheck -v</blockquote></p>

<p>Run systemcheck on Whonix-Gateway as well.</p>

<p>If systemcheck reports no problems with internet activity and downloading $tb_title still fails, please report a bug!</p>"

         output_gui "${output_opts[@]}" --messagex --typex "error" --message "$MSG" --done

         output_cli error "$MSG"

         tb_exit_function 6
      fi
      log notice "Tor Bootstrap Check: success"
   else
      true "/usr/libexec/helper-scripts/tor_bootstrap_check.bsh not available, skipping."
   fi

   true "Tor fully bootstrapped."
}

tb_connectivity_checks_curl() {
   local MSG
   if [ "${TB_SKIP_CONNECTIVITY_CHECK:-}" = "1" ]; then
      log notice "TB_SKIP_CONNECTIVITY_CHECK is set, skipping HTTP connectivity probe."
      return 0
   fi
   ## TODO: Maybe we should split the actual curl operation into a yet
   ## different user account? That would prevent a compromised curl from
   ## being able to affect later signature checks. Or maybe we need a
   ## general-purpose sandboxed curl wrapper.
   tb_notify_details="Checking connectivity... Will take a moment..."
   log notice "Running connectivity check...  Downloading...: '$tbb_download_base_url'"

   [ -n "${timeout_connectivity_checks_curl:-}" ] || timeout_connectivity_checks_curl=180
   ## 1 MB = 1048576 bytes
   ## 2 MB = 2097152 bytes
   ## Export CURL_PRGRS_MAX_FILE_SIZE_BYTES, so $CURL_PRGRS can read it.
   export CURL_PRGRS_MAX_FILE_SIZE_BYTES="2097152"
   ## Export CURL_OUT_FILE, so $CURL_PRGRS can read it.
   export CURL_OUT_FILE="$tb_temp_folder/tbb_remote_folder"
   safe-rm --force -- "$CURL_OUT_FILE"
   curl_download_max_time="$timeout_connectivity_checks_curl"
   curl_download_target_url="$tbb_download_base_url"
   tb_download_common_exit_on_fail="false"
   tb_download_common

   ## Check if curl failed.
   if [ ! "${curl_exit_code:-}" = "0" ]; then
      MSG="<p>$curl_download_target_url could not be reached.</p>

<p>Possible reasons:
<br></br>- $TBB_REMOTE_FOLDER is down
<br></br>- download location changed</p>

<p>Please check: <blockquote>Start menu -> System -> systemcheck
              <br></br>or in Terminal: systemcheck
              <br></br>or in Terminal with debugging: systemcheck -v</blockquote></p>

<p>If systemcheck reports no problems with internet activity and downloading $tb_title keeps failing, please report a bug!</p>

<p>(Debugging information: curl_status_message: $curl_status_message)</p>"
      output_gui "${output_opts[@]}" --messagex --typex "error" --message "$MSG" --done

      output_cli error "$MSG"

      tb_exit_function 7
   fi

   log notice "Connectivity check succeeded."
}

tb_update_check() {
   if [ ! "${tbb_version_stripped:-}" = "" ]; then
      true "INFO: tbb_version already set to '$tbb_version_stripped'. Skipping ${FUNCNAME[0]}, ok."
      return 0
   fi

   tb_notify_details="Checking $tb_title version... Will take a moment..."
   log notice "Find out latest version... Downloading...: '$TBB_VERSIONS_FILE_LINK'"

   [ -n "${timeout_version_file_download:-}" ] || timeout_version_file_download=300
   ## 1 MB = 1048576 bytes
   ## 2 MB = 2097152 bytes
   ## Export CURL_PRGRS_MAX_FILE_SIZE_BYTES, so $CURL_PRGRS can read it.
   export CURL_PRGRS_MAX_FILE_SIZE_BYTES="2097152"
   ## Export CURL_OUT_FILE, so $CURL_PRGRS can read it.
   export CURL_OUT_FILE="$RecommendedTBBVersions"
   safe-rm --force -- "$CURL_OUT_FILE"
   curl_download_max_time="$timeout_version_file_download"
   curl_download_target_url="$TBB_VERSIONS_FILE_LINK"
   tb_download_common_exit_on_fail="8"
   tb_download_common

   test -f "$RecommendedTBBVersions"
}

tb_remote_version_parser() {
   if [ ! "${tbb_version_stripped:-}" = "" ]; then
      true "INFO: tbb_version_stripped already set to '$tbb_version_stripped'. Simplified ${FUNCNAME[0]}, ok."
      [ -n "${tbb_recommended_versions_error:-}" ] || tbb_recommended_versions_error=""
      return 0
   fi

   ## needs: $RecommendedTBBVersions
   ## sets: tbb_version_raw
   ## sets: tbb_version_stripped
   ## sets: tbb_recommended_versions_error
   tb_run_function tbbversion
}

tb_remote_version_sanity_test() {
   if [ "${tbb_version_stripped:-}" = "UNKNOWN" ]; then
      download_fail_help_set
      local MSG="<p>$installed_or_not_text</p>

<p>Could not find out latest <code>$tb_title</code> version! <code>$tbb_recommended_versions_error</code></p>

<p>Either <code>$TBB_VERSIONS_FILE_LINK</code> is invalid or this is a <code>$SCRIPTNAME</code> (by <code>$tb_downloader_developers</code> developers) bug.</p>

<p>$DOWNLOAD_FAIL_HELP</p>"
      output_gui "${output_opts[@]}" --messagex --typex "error" --message "$MSG" --done

      output_cli error "$MSG"

      tb_exit_function 9
   fi

   if dpkg --compare-versions -- "$tbb_version_stripped" lt "$tbb_hardcoded_version" ; then
      download_fail_help_set
      local MSG="<p>$installed_or_not_text</p>

<p>Detected latest <code>$tb_title</code> version <code>$tbb_version_stripped</code> is older than hardcoded known-good version <code>$tbb_hardcoded_version</code>!</p>

<p>Either <code>$TBB_VERSIONS_FILE_LINK</code> is invalid or this is a <code>$SCRIPTNAME</code> (by <code>$tb_downloader_developers</code> developers) bug.</p>

<p>$DOWNLOAD_FAIL_HELP</p>"
      output_gui "${output_opts[@]}" --messagex --typex "error" --message "$MSG" --done

      output_cli error "$MSG"

      tb_exit_function 9
   elif dpkg --compare-versions -- "$tbb_version_stripped" gt "$tbb_hardcoded_version" ; then
       true "OK"
   elif dpkg --compare-versions -- "$tbb_version_stripped" eq "$tbb_hardcoded_version" ; then
       true "OK"
   else
      download_fail_help_set
      local MSG="<p>$installed_or_not_text</p>

<p>Detected latest <code>$tb_title</code> version: <code>$tbb_version_stripped</code>
hardcoded known-good version: <code>$tbb_hardcoded_version</code>
dpkg --compare-versions failed!</p>

<p>Either <code>$TBB_VERSIONS_FILE_LINK</code> is invalid or this is a <code>$SCRIPTNAME</code> (by <code>$tb_downloader_developers</code> developers) bug.</p>

<p>$DOWNLOAD_FAIL_HELP</p>"
      output_gui "${output_opts[@]}" --messagex --typex "error" --message "$MSG" --done

      output_cli error "$MSG"

      tb_exit_function 9
   fi
}

tb_local_version_detection() {
   ## sets: tbb_hardcoded_version
   true "INFO: tbb_download_alpha_version: $tbb_download_alpha_version"
   if [ "${tbb_download_alpha_version:-}" = "true" ]; then
      ## sets: tbb_hardcoded_version
      source /usr/share/tb-updater/tbb_hardcoded_version_alpha
      log notice "Using alpha version. See:
$tb_documentation_base_url_clearnet/wiki/$tb_wiki#Alpha"
   else
      ## sets: tbb_hardcoded_version
      source /usr/share/tb-updater/tbb_hardcoded_version
      log notice "Using stable version. For alpha version, see: $tb_documentation_base_url_clearnet/wiki/$tb_wiki#Alpha"
   fi

   ## Used by function tbbversion_installed
   if [ -z "${tbb_folder:-}" ]; then
      if [ "${who_ami}" = 'tb-updater' ]; then
         ## We install the browser into the tb-updater home folder, but it will
         ## be moved into a system-wide location later. Thus we need to check
         ## the system-wide location to see if the browser is already
         ## installed and what version it's at.
         tbb_folder="$tb_global_binary_dir/$tb_install_folder_dot/$tb_browser_name"
         tb_local_version_file="$tbb_folder/Browser/$tb_local_version_filename"
      else
         tbb_folder="$tb_browser_folder"
         ## tb_local_version_file set earlier by function tb_preparation.
      fi
   fi

   ## sets: tbb_locally_installed_version_stripped
   ## sets: tbb_locally_installed_version_detect_success
   tb_run_function tbbversion_installed

   if [ -d "$tbb_folder" ]; then
      installed_or_not_result="true"
      installed_or_not_text=""
   else
      installed_or_not_result="false"
      installed_or_not_text="$tb_title is currently not installed.
(Folder $tbb_folder does not exist.)"
   fi
}

tb_confirm_update() {
   if [ "${running_inside_qubes_template_vm:-}" = "true" ] ; then
      local folder_name found
      for folder_name in "/home/$who_ami/$tb_install_folder_dot" "/home/$who_ami/.cache/${tb_install_folder}" ; do
         if [ -d "$folder_name" ]; then
            found=true
            break
         fi
      done
      if [ "${found:-}" = "true" ]; then
         local MSG="\
<p>Obsolete folders exist. It is recommended to delete them. \
Otherwise updating <code>$tb_title</code> in this Template will not result in newly created App Qubes to inherit the updated version of <code>$tb_title</code>.
<br />
<br />To delete these obsolete folders, please run:
<br />
<br /><code>safe-rm -r -f -- '/home/$who_ami/$tb_install_folder_dot'</code>
<br /><code>safe-rm -r -f -- '/home/$who_ami/.cache/${tb_install_folder}'</code>
<br />
<br />(Or delete them using a file manager.)
<br />
<br />More info: <a href=$tb_documentation_base_url_clearnet/wiki/$tb_wiki/Advanced_Users#Qubes-specific>$tb_documentation_base_url_clearnet/wiki/$tb_wiki/Advanced_Users#Qubes-specific</a></p>"
         output_gui "${output_opts[@]}" --messagex --typex "info" --message "$MSG" --done

         output_cli notice "$MSG"
      fi
   fi

   log notice "Previously downloaded version: '$tbb_version_previous_downloaded_version_stripped'"
   log notice "Currently installed version  : '$tbb_locally_installed_version_stripped'"
   log notice "Online detected version      : '$tbb_version_stripped'"

   local MSG question button type
   button="yesno"
   question="Download now?"

   local updater_vs_downloader_msg confirmation_screen_learn_more_link

   updater_vs_downloader_msg="\
If you would like to keep your browser profile and update rather than re-downloading $tb_title, \
you must use \
<a href=$tb_documentation_base_url_clearnet/wiki/$tb_wiki#Internal_Updater>$tb_title's internal updater</a>. \
In that case, say no now.<br></br>
<br></br>
This program \
(<a href=$tb_documentation_base_url_clearnet/wiki/$tb_wiki#Downloader>$tb_title Downloader (by $tb_downloader_developers developers)</a>) \
is incapable of keeping user data.<br></br>
<br></br>
YOUR BROWSER WILL BE KILLED.<br></br>
YOUR OLD BROWSER PROFILE INCLUDING BOOKMARKS AND PASSWORDS WILL GET DELETED."

   confirmation_screen_learn_more_link="\
<a href=$tb_documentation_base_url_clearnet/wiki/$tb_wiki#Download_Confirmation_Notification>Learn more about this Download Confirmation Notification.</a>"

   if [ "${running_inside_qubes_template_vm:-}" = "true" ] ; then
      qubes_running_inside_template_vm_msg="\
<u>Qubes specific notice</u>: Currently running inside a Template.
Due to <a href=$tb_documentation_base_url_clearnet/wiki/Dev/Qubes#tb-updater_vs_Template>technical limitations</a>,
running this program in this Template will not update $tb_title in App Qubes, which are based on this Template.
Only newly created App Qubes, which are based on this Template will benefit from this.
To update $tb_title inside existing App Qubes, please update $tb_title inside App Qubes."
   fi

   if [ "${installed_or_not_result:-}" = "true" ]; then
      type="warning"
      if [ "${tbb_locally_installed_version_detect_success:-}" = "false" ]; then
         ## installed, but local version number unknown
         MSG="\
<p>$updater_vs_downloader_msg</p>
<p>$confirmation_screen_learn_more_link</p>
<p>$qubes_running_inside_template_vm_msg</p>"
      else
         ## installed, local version number known
         if dpkg --compare-versions -- "$tbb_version_stripped" eq "$tbb_locally_installed_version_stripped"; then
            up_to_date_or_not_text="Looks like $tb_title is already up to date."
            re_install_or_install_text="Please close $tb_title if you want to (re-)install!"
	          if [ "${UPDATE_ONLY_IF_NEWER:-}" = "1" ]; then
               true "INFO: UPDATE_ONLY_IF_NEWER is set 1."
               log notice "Skip downloading: current '$tb_title' '$tbb_locally_installed_version_stripped' is already up-to-date."
               tb_exit_function 0
	          fi
         elif dpkg --compare-versions -- "$tbb_version_stripped" gt "$tbb_locally_installed_version_stripped"; then
            up_to_date_or_not_text="Looks like there is an upgrade for $tb_title."
            re_install_or_install_text="Please close $tb_title if you want to (re-)install!"
         elif dpkg --compare-versions -- "$tbb_version_stripped" lt "$tbb_locally_installed_version_stripped"; then
            up_to_date_or_not_text="<b><u>WARNING: Looks like a downgrade attack!</u></b>"
            re_install_or_install_text="Please close $tb_title if you want to (re-)install!"
         else
            error "Failed to compare versions using dpkg.
<br></br>
<br></br>tbb_locally_installed_version_detect_success: $tbb_locally_installed_version_detect_success"
            tb_exit_function 1
         fi
         MSG="\
<p>$up_to_date_or_not_text</p>

<p>$re_install_or_install_text</p>

<p>If your currently installed version is:<blockquote>
   - higher: you are likely target of a downgrade attack, SAY NO NOW.<br></br>
   - equal : only proceed, if you want to create a new browser profile.<br></br>
   - lower : you should upgrade.</blockquote></p>

<p>$updater_vs_downloader_msg</p>
<p>$confirmation_screen_learn_more_link</p>"
      fi

      output_cli notice "$MSG"
   else
      ## not installed
      type="info"
      MSG="\
<p>$confirmation_screen_learn_more_link</p>"

      if ! [ "${tb_postinst:-}" = "true" ]; then
         output_cli notice "$MSG"
      fi
   fi

   if ! [ "${tb_postinst:-}" = "true" ]; then
      printf '%s\n' "$tb_documentation_base_url_clearnet/wiki/$tb_wiki#Download_Confirmation_Notification"
   fi

   if [ "${TB_INPUT:-}" = "none" ]; then
      true "INFO: TB_INPUT set to none, ok."
   else
      local answer
      if [ "${TB_INPUT:-}" = "stdin" ]; then
         log question "$question
y/n?"
         read -r answer
         if [ ! "${answer:-}" = "y" ]; then
            log notice "Canceled. Exit."
            tb_exit_function 10
         fi
      else
         #printf '%s\n' "/usr/libexec/msgcollector/tb_updater_gui \"$type\" \"$TITLE\" \"$tbb_locally_installed_version_stripped\" \"$tbb_recommended_versions_comma_separated\" \"$MSG\" \"$question\" \"$button\")"

         answer="$("/usr/libexec/msgcollector/tb_updater_gui" "$type" "$TITLE" "$tbb_locally_installed_version_stripped" "$tbb_version_stripped" "$MSG" "$question" "$button")"
         if [ "${answer:-}" = "65536" ]; then ## Button 'Yes' has not been pressed.
            log notice "Canceled. Exit."
            tb_exit_function 10
         fi
      fi
   fi
}

tb_version_processing() {
   if [ "${tb_hard_reset:-}" == "true" ]; then
      tbb_version_stripped="$tbb_version_previous_downloaded_version_stripped"
   fi

   if [ "${tbb_version_stripped:-}" = "" ]; then
      log error "in function tb_version_processing: variable $tbb_version_stripped not set!"
      tb_exit_function 17
   fi

   [ -n "${tbb_version_folder:-}" ] || tbb_version_folder="$tbb_version_stripped"
   [ -n "${tb_browser_download_name:-}" ] || tb_browser_download_name="tor-browser"
   [ -n "${TBB_EXTRA_FOLDER:-}" ] || TBB_EXTRA_FOLDER="${tbb_version_folder}"

   [ -n "${TBB_SIG_FILENAME:-}" ] || TBB_SIG_FILENAME="${tb_browser_download_name}-${ARCH_DOWNLOAD}-${tbb_version_stripped}.tar.xz.asc"
   [ -n "${TBB_PACKAGE_FILENAME:-}" ] || TBB_PACKAGE_FILENAME="${tb_browser_download_name}-${ARCH_DOWNLOAD}-${tbb_version_stripped}.tar.xz"

   [ -n "${TBB_SIG_LINK:-}" ] || TBB_SIG_LINK="${TBB_REMOTE_FOLDER}/${TBB_EXTRA_FOLDER}/${TBB_SIG_FILENAME}${TBB_DOWNLOAD_APPENDIX}"
   [ -n "${TBB_SIG_FULL_PATH:-}" ] || TBB_SIG_FULL_PATH="$tb_downloaded_files_folder/$TBB_SIG_FILENAME"

   [ -n "${TBB_PACKAGE_LINK:-}" ] || TBB_PACKAGE_LINK="$TBB_REMOTE_FOLDER/${TBB_EXTRA_FOLDER}/$TBB_PACKAGE_FILENAME${TBB_DOWNLOAD_APPENDIX}"
   [ -n "${TBB_PACKAGE_FULL_PATH:-}" ] || TBB_PACKAGE_FULL_PATH="$tb_downloaded_files_folder/$TBB_PACKAGE_FILENAME"
}

tb_reset() {
   if [ ! "${tb_hard_reset:-}" == "true" ]; then
      return 0
   fi

   log notice "Using '--reset' option.. Performing hard reset rather than download. See: $tb_documentation_base_url_clearnet/wiki/$tb_wiki/Advanced_Users#Tor_Browser_Hard_Reset"

   if ! test -f "$TBB_PACKAGE_FULL_PATH" ; then
      local MSG="\
<p><b>Tor Browser archive file <code>$TBB_PACKAGE_FULL_PATH</code> not yet downloaded. Run without <code>--reset</code> is required.</b>
<br/>
<br/>See:
<br/><a href=$tb_documentation_base_url_clearnet/wiki/$tb_wiki/Advanced_Users#Tor_Browser_Hard_Reset>$tb_documentation_base_url_clearnet/wiki/$tb_wiki/Advanced_Users#Tor_Browser_Hard_Reset</a></p>"
      output_gui "${output_opts[@]}" --messagex --typex "error" --message "$MSG" --done

      output_cli error "$MSG"

      tb_exit_function 16
   fi

   if ! test -f "$TBB_SIG_FULL_PATH" ; then
      local MSG="\
<p><b>Tor Browser digital (OpenPGP) signature <code>$TBB_SIG_FULL_PATH</code> not yet downloaded. Run without <code>--reset</code> is required.</b>
<br/>
<br/>See:
<br/><a href=$tb_documentation_base_url_clearnet/wiki/$tb_wiki/Advanced_Users#Tor_Browser_Hard_Reset>$tb_documentation_base_url_clearnet/wiki/$tb_wiki/Advanced_Users#Tor_Browser_Hard_Reset</a></p>"
      output_gui "${output_opts[@]}" --messagex --typex "error" --message "$MSG" --done

      output_cli error "$MSG"

      tb_exit_function 16
   fi
}

tb_kill_already_running_tb_maybe() {
   if [ ! "${NOKILLTB:-}" = "1" ]; then
      [ -n "${tb_process_name:-}" ] || tb_process_name="firefox.real"

      log notice "Because you are not using the '--nokilltb' option, now killing potentially still running instances of '$tb_title'..."
      killall "$tb_process_name" || true
   fi
}

tb_download_common() {
   local MSG
   if [ "${TB_FORCE_INSTALL:-}" = "1" ]; then
      ## No progress bar: explicitly clear the cross-function global so
      ## a previous call's UUID does not leak into the close-progress
      ## logic further down in this function or into tb_error_handler.
      progressbaridx=""
   else
      progressbaridx="$(cat -- "/proc/sys/kernel/random/uuid")"
      tb_notify_msg="Download
----------------------------------------------------------------------
$tb_notify_details"
      if [ "${TB_INPUT:-}" = "" ] || [ "${TB_INPUT:-}" = "gui" ]; then
         output_gui "${output_opts[@]}" --progressbaridx "$progressbaridx" --progressbarx --parentpid "$$" --typex "info" --progressbartitlex "$TITLE" --message "$tb_notify_msg" --parentpid "$$" --done
         ## $CURL_PRGRS honors the $CURL and the $CURL_PRGRS_EXEC environment
         ## variables. (See above.)
         ## Define what CURL_PRGRS is supposed to eval.
         ## ($percent is a local variable provided by $CURL_PRGRS.)
         export -f output_gui
         CURL_PRGRS_EXEC="output_gui ${output_opts[*]} --progressbaridx $progressbaridx --progressx"
         export CURL_PRGRS_EXEC
      fi
   fi

   log notice "CURL_OUT_FILE: '$CURL_OUT_FILE'"

   ## Debugging: Simulating endless data attack.
   #export CURL_PRGRS_MAX_FILE_SIZE_BYTES="1048576"

   curl_exit_code="0"
   ## CURL_RESUME, CURL_FORCE_SSL and CURL_OPTS carry multi-word curl
   ## flag strings (e.g. '--tlsv1.3 --proto =https') that are meant to
   ## be word-split into separate argv entries. Quoting them would
   ## pass the whole string as a single argument and curl would
   ## reject it.
   # shellcheck disable=SC2086
   $CURL_PRGRS \
      ${CURL_RESUME:-} \
      --fail \
      "${CURL_PROXY[@]}" \
      ${CURL_FORCE_SSL:-} \
      --retry-connrefused \
      --retry 10 \
      --retry-delay 60 \
      --max-time "$curl_download_max_time" \
      --location \
      ${CURL_OPTS:-} \
      --output "$CURL_OUT_FILE" \
      "$curl_download_target_url" \
      &

   last_pid_list="$!"
   wait "$last_pid_list" || { curl_exit_code="$?" ; true; };
   last_pid_list=""

   if [ -n "$progressbaridx" ]; then
      output_gui "${output_opts[@]}" --progressbaridx "$progressbaridx" --progressx "100" || true
      progressbaridx=""
   fi

   if [ -x "/usr/libexec/helper-scripts/curl_exit_codes" ]; then
      curl_status_message="$(/usr/libexec/helper-scripts/curl_exit_codes "$curl_exit_code")" || true
   else
      curl_status_message="$curl_exit_code"
   fi

   if [ "${tb_download_common_exit_on_fail:-}" = "false" ]; then
      true "${FUNCNAME[0]} is not supposed to exit, because tb_download_common_exit_on_fail is set to $tb_download_common_exit_on_fail."
      return 0
   fi

   ## Check if curl failed.
   if [ ! "${curl_exit_code:-}" = "0" ]; then
      download_fail_help_set
      MSG="<p>Failed to download: $curl_download_target_url</p>

<p>$DOWNLOAD_FAIL_HELP</p>

<p>(Debugging information: curl_status_message: $curl_status_message)</p>"
      output_gui "${output_opts[@]}" --messagex --typex "error" --message "$MSG" --done

      output_cli error "$MSG"

      tb_exit_function "$tb_download_common_exit_on_fail"
   fi

   true "INFO: Done, downloaded: $curl_download_target_url"
}

tb_download_files() {
   ### signature ###

   tb_notify_details="Downloading $tb_title digital signature (OpenPGP)... Will take a moment..."
   log notice "Digital signature (OpenPGP) download... Will take a moment..."
   log notice "Downloading...: '$TBB_SIG_LINK'"

   [ -n "${timeout_signature_file_download:-}" ] || timeout_signature_file_download=300
   ## 1 MB = 1048576 bytes
   ## 2 MB = 2097152 bytes
   ## Export CURL_PRGRS_MAX_FILE_SIZE_BYTES, so $CURL_PRGRS can read it.
   export CURL_PRGRS_MAX_FILE_SIZE_BYTES="2097152"
   ## Export CURL_OUT_FILE, so $CURL_PRGRS can read it.
   export CURL_OUT_FILE="$TBB_SIG_FULL_PATH"
   safe-rm --force -- "$CURL_OUT_FILE"
   curl_download_max_time="$timeout_signature_file_download"
   curl_download_target_url="$TBB_SIG_LINK"
   tb_download_common_exit_on_fail="8"
   tb_download_common

   ### archive file ###

   tb_notify_details="Downloading $tb_title... Will take a while..."
   log notice "Downloading '$tb_title'..."
   log notice "Downloading...: '$TBB_PACKAGE_LINK'"

   [ -n "${timeout_archive_file_download:-}" ] || timeout_archive_file_download=3600
   ## 1 MB = 1048576 bytes
   ## 200 MB = 209715200 bytes
   ## Export CURL_PRGRS_MAX_FILE_SIZE_BYTES, so $CURL_PRGRS can read it.
   export CURL_PRGRS_MAX_FILE_SIZE_BYTES="209715200"
   ## Export CURL_OUT_FILE, so $CURL_PRGRS can read it.
   export CURL_OUT_FILE="$TBB_PACKAGE_FULL_PATH"
   ## Not deleting to support resume.
   #safe-rm --force -- "$CURL_OUT_FILE"
   curl_download_max_time="$timeout_archive_file_download"
   curl_download_target_url="$TBB_PACKAGE_LINK"
   tb_download_common_exit_on_fail="17"
   tb_download_common
}

tb_openpgp_verify() {
   local verification_success
   local sig_max_age_seconds

   declare -g sqop_output sqop_output_stripped
   declare -g sq_output sq_output_stripped

   ## One month has 2592000 seconds.
   ## (60 [seconds] * 60 [minutes] * 24 [hours] * 30 [days])
   ## Setting this to 3 months. (7776000 seconds)
   ## (2592000 * 3 [months])
   is_whole_number "${sig_max_age_seconds:-}" || sig_max_age_seconds="7776000"

   local MSG
   local now_epoch not_before_epoch not_after_epoch
   local sqop_not_before sqop_not_after sig_creation_time sig_unix_time
   ## Overridable via environment or the '--danger-insecure-self-test' flag
   ## so the self-test fixtures can supply their own signing certificate.
   ## Not declared 'local' so the env-exported value is visible here.
   [ -n "${signing_key:-}" ] || signing_key="/usr/share/torbrowser-updater-keys.d/tbb-team.asc"

   log notice "Digital signature (OpenPGP) verification using Sequoia-PGP... This will take a moment..."
   log notice "Using digital signature signing key by The Tor Project."

   ## Sanity test: verify the signing key is well-formed. Output is
   ## suppressed since it is not useful to the user. If this fails, the
   ## non-zero exit code will trigger 'set -o errexit' and stop the script.
   ## Overridable so the self-test can point the lint binary at something
   ## harmless (e.g. 'echo') when the host sq predates 'cert lint'.
   [ -n "${sq_cert_lint_binary:-}" ] || sq_cert_lint_binary="sq"
   "$sq_cert_lint_binary" cert lint --cert-file "$signing_key" &>/dev/null

   ## Build signature-time validity window for sqop verify:
   ## - not-before: reject signatures older than max age
   ## - not-after : reject signatures from the future
   now_epoch="$(date --utc +%s)"
   not_after_epoch="$now_epoch"
   not_before_epoch="$(( now_epoch - sig_max_age_seconds ))"

   ## 'sqop' expects a timestamp in RFC3339/ISO8601 format. UTC is a safe
   ## choice. 'date' has an '--iso-8601' option, however we do not use it here
   ## since 'sqop' uses a 'Z' at the end to indicate a UTC timezone in its
   ## output. It is therefore reasonable to assume that it will take
   ## timestamps in that same format with the least chances of malfunctioning.
   if ! sqop_not_before="$(date --utc --date "@$not_before_epoch" '+%Y-%m-%dT%H:%M:%SZ')"; then
      MSG="<p><b>Could NOT compute signature age lower bound.</b></p>"
      output_gui "${output_opts[@]}" --messagex --typex "error" --message "$MSG" --done
      output_cli error "$MSG"
      tb_exit_function 12
   fi

   if ! sqop_not_after="$(date --utc --date "@$not_after_epoch" '+%Y-%m-%dT%H:%M:%SZ')"; then
      MSG="<p><b>Could NOT compute signature age upper bound.</b></p>"
      output_gui "${output_opts[@]}" --messagex --typex "error" --message "$MSG" --done
      output_cli error "$MSG"
      tb_exit_function 12
   fi

   log notice "sqop verify --not-before '$sqop_not_before' --not-after '$sqop_not_after' '$TBB_SIG_FULL_PATH' '$signing_key' < '$TBB_PACKAGE_FULL_PATH'"

   ## stderr is merged into stdout with '2>&1' deliberately.
   ## Per the SOP spec, successful runs emit nothing to stderr (--debug not
   ## used here). On failure, stderr diagnostics are captured for the error
   ## message and sig_creation_time parsing is never reached.
   ## https://www.kicksecure.com/wiki/OpenPGP#sqop_parsing
   if sqop_output="$(
      timeout --kill-after="60" "10" \
         sqop verify \
           --not-before "$sqop_not_before" \
           --not-after "$sqop_not_after" \
           "$TBB_SIG_FULL_PATH" "$signing_key" \
           < "$TBB_PACKAGE_FULL_PATH" 2>&1
   )"; then
      true "INFO: sqop success. Setting: verification_success=true"
      verification_success=true
   else
      verification_success=false
   fi

   ## 'sq verify' is used for informational output only (shown in the
   ## installation confirmation screen). It intentionally has no
   ## --not-before/--not-after time window because 'sqop verify' above
   ## already enforces signature freshness constraints. Signature creation
   ## timestamps are extracted from 'sqop' output only, since 'sq verify'
   ## does not expose them in a machine-parseable way.
   ## https://www.kicksecure.com/wiki/OpenPGP#sqop_vs_sq_for_automations
   ## https://forums.whonix.org/t/tor-browser-downloader-confirmation-has-changed-with-less-info/23146
   ## Overridable for the same reason as sq_cert_lint_binary above: the
   ## 'sq verify' CLI has moved argument names between versions, and the
   ## self-test runs against whatever sq the host has.
   [ -n "${sq_verify_binary:-}" ] || sq_verify_binary="sq"
   log notice "sq verify --signer-file '$signing_key' --signature-file '$TBB_SIG_FULL_PATH' -- '$TBB_PACKAGE_FULL_PATH'"

   if sq_output="$(
      timeout --kill-after="60" "10" \
         "$sq_verify_binary" verify \
         --signer-file "$signing_key" \
         --signature-file "$TBB_SIG_FULL_PATH" \
         -- \
         "$TBB_PACKAGE_FULL_PATH" 2>&1
   )"; then
      true "INFO: sq success. Not setting verification_success=true by design. That is up to sqop only."
      #verification_success=false
   else
      verification_success=false
   fi

   ## Bound-checked, markup-stripped copies for any interpolation into the
   ## MSG dialog body. sqop verification lines come from an OpenPGP tool
   ## whose output is generally safe, but sq also surfaces UID strings from
   ## the keyring which are arbitrary UTF-8 and could carry rich-text
   ## metadata; and both commands are invoked with '2>&1', so stderr
   ## diagnostics end up in the same buffer. 1024 chars is ample for a
   ## couple of verification lines.
   sqop_output_stripped="$(sanitize-string -- 1024 "$sqop_output")"
   sq_output_stripped="$(sanitize-string -- 1024 "$sq_output")"

   if ! [ "$verification_success" = "true" ]; then
      ## Trust classes of the values interpolated below, so the MSG body
      ## stays safe against future regressions:
      ##   $tb_title              -- wrapper-supplied; constant per front-end.
      ##   $sqop_output_stripped   -- tool output, already passed through
      ##                             sanitize-string above. Never interpolate
      ##                             the raw $sqop_output here.
      MSG="<p><b>Digital signature (OpenPGP) could NOT be verified.</b>
<br></br>$tb_title update failed! Try again later.</p>

<p>sqop_output: <code>$sqop_output_stripped</code></p>"
      output_gui "${output_opts[@]}" --messagex --typex "error" --message "$MSG" --done
      output_cli error "$MSG"
      tb_exit_function 12
   fi

   log notice "sqop_output: '$sqop_output'"
   log notice "sq_output: '$sq_output'"

   sig_creation_time="${sqop_output%% *}"
   log notice "sig_creation_time: '$sig_creation_time'"

   if [[ $sig_creation_time =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$ ]]; then
      log notice "Valid 'sqop' timestamp format."
   else
      MSG="Unexpected 'sqop' timestamp format."
      output_gui "${output_opts[@]}" --messagex --typex "error" --message "$MSG" --done
      output_cli error "$MSG"
      tb_exit_function 12
   fi

   sig_unix_time="$(date --utc --date "$sig_creation_time" +%s)"

   ## Debugging: Outdated signature.
   #sig_unix_time=$(( sig_unix_time - 100000000 ))

   log notice "sig_unix_time: '$sig_unix_time'"

   if ! is_whole_number "$sig_unix_time"; then
      MSG="Unexpected 'date' output."
      output_gui "${output_opts[@]}" --messagex --typex "error" --message "$MSG" --done
      output_cli error "$MSG"
      tb_exit_function 12
   fi

   declare -g sig_creation_unixtime
   sig_creation_unixtime="$sig_unix_time"

   #declare -g sig_creation_unixtime_minus_current_unixtime_pretty
   declare -g sig_age_pretty

   # Unused.
   #local signed_minus_current
   #signed_minus_current="$(( sig_creation_unixtime - now_epoch ))"
   #if [ "$signed_minus_current" -lt 0 ]; then
   #   signed_minus_current=0
   #fi

   local current_minus_signed
   current_minus_signed="$(( now_epoch - sig_creation_unixtime ))"
   if [ "$current_minus_signed" -lt 0 ]; then
      current_minus_signed=0
   fi

   # Unused.
   #sig_creation_unixtime_minus_current_unixtime_pretty="$(gpg_bash_lib_function_displaytime "$signed_minus_current")"
   #[ -n "${sig_creation_unixtime_minus_current_unixtime_pretty:-}" ] || sig_creation_unixtime_minus_current_unixtime_pretty="0 seconds"

   ## gpg_bash_lib_function_displaytime and is_whole_number are provided by
   ## helper-scripts (strings.bsh). The 'gpg_bash_lib_' prefix is a legacy
   ## name; the downloader itself no longer depends on gpg-bash-lib (ported
   ## to sq/sqop) but the helper function retains the old name until
   ## helper-scripts renames it upstream.
   sig_age_pretty="$(gpg_bash_lib_function_displaytime "$current_minus_signed")"
   [ -n "${sig_age_pretty:-}" ] || sig_age_pretty="0 seconds"

   log notice "Digital signature (OpenPGP) verification ok."
}

## Read the cached signature-creation timestamp from the given cache
## folder. Output is either a validated integer within [1, 4294967295] or
## the empty string. Never emits an error on a missing or invalid file --
## those just degrade the candidate to "absent".
tb_read_cached_unixtime() {
   local cache_folder="$1" value=""
   if [ -f "$cache_folder/last_used_gpg_bash_lib_output_signed_on_unixtime" ]; then
      value="$(read_integer_file "$cache_folder/last_used_gpg_bash_lib_output_signed_on_unixtime" 1 4294967295)" || value=""
   fi
   printf '%s' "$value"
}

tb_confirm_install() {
   if [ "${tb_confirm_installation_skip:-}" = "true" ]; then
      return 0
   fi

   local MSG question button answer type clock_hint
   local signature_freshness_msg signature_creation_msg
   local last_used_sig_creation_date
   local last_used_sig_creation_unixtime
   local real_cache_folder secondary_cache_folder
   local primary_unixtime secondary_unixtime effective_unixtime
   local strict_numeric
   local sig_creation_date sig_freshness_detail

   if [ "${who_ami}" = 'tb-updater' ]; then
      real_cache_folder="${tb_global_binary_dir}/.cache/${tb_install_folder}"
   else
      real_cache_folder="${tb_cache_folder}"
   fi

   ## Freshness baseline sources. Primary is always $real_cache_folder.
   ## Direct-as-user invocations additionally consult
   ## $tb_global_binary_dir/.cache/$tb_install_folder, which the
   ## sandbox/postinst path maintains and sandbox-update-torbrowser makes
   ## world-readable. When both are readable use whichever timestamp is
   ## newer: the most recent signature this machine has already accepted is
   ## the correct baseline for the downgrade/freeze check, regardless of
   ## which account or invocation path observed it. Writes still go to
   ## $tb_cache_folder; the sandbox persists its own copy separately, so the
   ## fallback is read-only.
   ##
   ## Each candidate is read through read_integer_file (strings.bsh), which
   ## enforces non-empty, strictly-numeric contents and numeric bounds. The
   ## upper bound is the unsigned 32-bit Unix epoch max (year 2106) --
   ## deliberately loose enough not to need maintenance and tight enough
   ## that a tampered cache with an absurd far-future timestamp cannot make
   ## every real signature look like a downgrade attack. Validation failure
   ## degrades that candidate to "absent".
   primary_unixtime="$(tb_read_cached_unixtime "$real_cache_folder")"
   if ! [ "${who_ami}" = 'tb-updater' ] \
      && ! [ "${tb_global_binary_dir}/.cache/${tb_install_folder}" = "$real_cache_folder" ]; then
      secondary_cache_folder="${tb_global_binary_dir}/.cache/${tb_install_folder}"
      secondary_unixtime="$(tb_read_cached_unixtime "$secondary_cache_folder")"
   else
      secondary_cache_folder=""
      secondary_unixtime=""
   fi

   if [ -n "$secondary_unixtime" ] \
      && { [ -z "$primary_unixtime" ] || [ "$secondary_unixtime" -gt "$primary_unixtime" ]; }; then
      effective_unixtime="$secondary_unixtime"
      log notice "Using system-wide freshness cache '$secondary_cache_folder' (newer than per-user cache '$real_cache_folder')."
   elif [ -n "$primary_unixtime" ] && [ -n "$secondary_unixtime" ]; then
      effective_unixtime="$primary_unixtime"
      log notice "Using per-user freshness cache '$real_cache_folder' (at least as recent as system-wide cache '$secondary_cache_folder')."
   elif [ -n "$primary_unixtime" ]; then
      effective_unixtime="$primary_unixtime"
      log notice "Using freshness cache '$real_cache_folder'."
   else
      effective_unixtime=""
      ## The inner '$secondary_cache_folder' sits inside a double-quoted
      ## log string; the single quotes around it are literal. shellcheck
      ## reads the nested single quotes as delimiters.
      # shellcheck disable=SC2016
      log notice "No previously accepted signature timestamp found at '$real_cache_folder'${secondary_cache_folder:+ or '$secondary_cache_folder'}."
   fi

   ## Derive the display string from the validated integer rather than
   ## reading the on-disk *_date file. The write side still maintains that
   ## file for backward compatibility, but its contents are never
   ## interpolated into the MSG HTML here, eliminating it as an injection
   ## vector into the confirmation dialog's 'Previous Signature Creation
   ## Date' cell.
   if [ -n "$effective_unixtime" ]; then
      last_used_sig_creation_unixtime="$effective_unixtime"
      last_used_sig_creation_date="$(date --utc --date "@$effective_unixtime" '+%B %d %H:%M:%S UTC %Y')"
   else
      last_used_sig_creation_unixtime="unknown"
      last_used_sig_creation_date="Unknown. Probably never downloaded a signature before."
   fi
   ## Defence-in-depth: bound the display string before MSG interpolation
   ## even though the branches above only produce known-safe values. Any
   ## future regression that fed a cache read back in here would still be
   ## capped and stripped.
   last_used_sig_creation_date="$(sanitize-string -- 64 "$last_used_sig_creation_date")"

   if is_whole_number "$last_used_sig_creation_unixtime"; then
      strict_numeric=true
   else
      strict_numeric=false
   fi
   ## Already sanity checked in function 'tb_openpgp_verify'.
   is_whole_number "$sig_creation_unixtime"

   sig_creation_date="$(date --utc --date "@$sig_creation_unixtime" '+%B %d %H:%M:%S UTC %Y')"
   ## Defence-in-depth cap to match the treatment of
   ## $last_used_sig_creation_date above. The 'date' output
   ## here is known-safe on a validated integer, but capping keeps MSG
   ## interpolations symmetrical and guards against future regressions.
   sig_creation_date="$(sanitize-string -- 64 "$sig_creation_date")"

   if [ "$strict_numeric" = "false" ]; then
      sig_freshness_detail="skip"
      signature_freshness_msg="We have not previously accepted a signature yet (or signature timestamps are unavailable). Therefore assisted check for downgrade \
or indefinite freeze attacks skipped. Please check if the Current Signature Creation Date looks sane."
      type="info"
   else
      ## strict_numeric is not 'false'
      true "'$last_used_sig_creation_unixtime' and '$sig_creation_unixtime' are strictly numeric."
      if [ "$last_used_sig_creation_unixtime" -lt "$sig_creation_unixtime" ]; then
         sig_freshness_detail="current"
         signature_freshness_msg="The downloaded signature is newer than the last known signature as expected."
         type="info"
      elif [ "$last_used_sig_creation_unixtime" -gt "$sig_creation_unixtime" ]; then
         sig_freshness_detail="slow"
         signature_freshness_msg="<b>You are likely target of a downgrade attack</b>, SAY NO NOW! The downloaded signature is older than the last known signature. \
Unless you are downgrading, you should abort now and try again later!"
         type="warning"
      elif [ "${last_used_sig_creation_unixtime:-}" = "$sig_creation_unixtime" ]; then
         sig_freshness_detail="current"
         signature_freshness_msg="<b>You could be target of an indefinite freeze attack!</b> The downloaded signature has the same creation date as the last known signature. \
Unless you are re-installing the same version, you should abort now and try again later!"
         type="warning"
      else
         error "last_used_sig_creation_unixtime $last_used_sig_creation_unixtime \
neither -lt, -gt nor equals sig_creation_unixtime $sig_creation_unixtime"
         signature_freshness_msg="Signature freshness comparison unavailable."
         type="error"
      fi
   fi

   if [ -f /usr/share/whonix/marker ]; then
      clock_hint="In that case, please check your clock is correct."
   else
      clock_hint=""
   fi

   case "$sig_freshness_detail" in
      "skip")
         signature_creation_msg="Signature creation age details are unavailable."
         ;;
      "slow")
         signature_creation_msg="<b>The signature looks quite old already.</b>
<br></br>
<br></br>Either,
<br></br>- your clock might be fast. $clock_hint
<br></br>- there is really no newer signature yet.
<br></br>- this is a $SCRIPTNAME bug
<br></br>- this is an attack"
         ;;
      "current")
         signature_creation_msg="According to your system clock, the signature was created $sig_age_pretty ago."
         ;;
      *)
         signature_creation_msg="Signature creation age details are unavailable."
         error "sig_freshness_detail is neither slow, nor current, it is: '$sig_freshness_detail'"
         return 1
         ;;
   esac

   ## Trust classes of the values interpolated into the installation
   ## confirmation dialog. Keep this list current when adding new fields so
   ## the MSG body stays safe against future regressions:
   ##   *_stripped                    -- sanitize-string-capped elsewhere
   ##                                    (version-validator / tbbversion).
   ##   $signature_freshness_msg,
   ##   $signature_creation_msg       -- built from string literals + the
   ##                                    already-validated unixtime above.
   ##   $last_used_sig_creation_date,
   ##   $sig_creation_date
   ##                                 -- derived from a validated unixtime
   ##                                    via 'date' and passed through
   ##                                    sanitize-string -- 64; never read
   ##                                    the *_date cache file here.
   ##   $sqop_output_stripped,
   ##   $sq_output_stripped            -- sanitize-string -- 1024 copies of
   ##                                    the tool output. Never interpolate
   ##                                    the raw $sqop_output / $sq_output.
   ##   $tb_documentation_base_url_clearnet,
   ##   $tb_wiki                      -- wrapper-supplied; constant per
   ##                                    front-end, validated by the
   ##                                    wrapper/config layer.
   MSG="<p><b>Installation confirmation</b></p><p><table><tr>
<td>Currently installed version:</td><td><tt> <code>$tbb_locally_installed_version_stripped</code></tt></td></tr><tr>
<td>Downloaded version         :</td><td><tt> <code>$tbb_version_stripped</code></tt></td></tr></table></p>
<p>$signature_freshness_msg</p><p><table><tr>
<td>Previous Signature Creation Date:</td><td><tt> <code>$last_used_sig_creation_date</code></tt></td></tr><tr>
<td>Last Signature Creation Date    :</td><td><tt> <code>$sig_creation_date</code></tt></td></tr></table></p>
<p>$signature_creation_msg</p>
<p><u>'sqop' reports</u>:<br></br>
$sqop_output_stripped</p>
<p><u>'sq' reports</u>:<br></br>
$sq_output_stripped</p>
<p><a href=\"$tb_documentation_base_url_clearnet/wiki/$tb_wiki#Installation_Confirmation_Notification\">Learn more about this Installation Confirmation Notification.</a></p>"

   output_cli notice "$MSG"

   printf '%s\n' "$tb_documentation_base_url_clearnet/wiki/$tb_wiki#Installation_Confirmation_Notification"

   button="yesno"
   question="Install now?"

   if [ "${TB_INPUT:-}" = "none" ]; then
      true "INFO: TB_INPUT is set to '$TB_INPUT', continuing without asking."
   else
      if [ "${TB_INPUT:-}" = "stdin" ]; then
         log question "QUESTION: $question
y/n?"
         read -r answer
         if [ ! "${answer:-}" = "y" ]; then
            log notice "Canceled. Exit."
            tb_exit_function 14
         fi
      else
         answer="$(/usr/libexec/msgcollector/generic_gui_message "$type" "$TITLE" "$MSG" "$question" "$button")"
         if [ ! "${answer:-}" = "16384" ]; then ## Button 'Yes' has not been pressed.
            log notice "Canceled. Exit."
            tb_exit_function 14
         fi
      fi
   fi

   ## Intentionally using tb_cache_folder and not real_cache_folder here. If
   ## running under sandbox-update-torbrowser, we will have no permission to
   ## write to real_cache_folder directly. sandbox-update-torbrowser will move
   ## these files into real_cache_folder later.
   overwrite "$tb_cache_folder/last_used_gpg_bash_lib_output_signed_on_date" "$sig_creation_date"
   overwrite "$tb_cache_folder/last_used_gpg_bash_lib_output_signed_on_unixtime" "$sig_creation_unixtime"
}

tb_extract() {
   if [ "${TB_NO_EXTRACT:-}" = "true" ]; then
      true "INFO: Skipping ${FUNCNAME[0]}, because TB_NO_EXTRACT is 'true', ok."
      return 0
   fi

   if ! [ "${TB_FORCE_INSTALL:-}" = "1" ]; then
      progressbaridx="$(cat -- "/proc/sys/kernel/random/uuid")"
      tb_notify_msg="Extraction
----------------------------------------------------------------------
Extracting $tb_title... This could take a moment..."
   else
      ## Same reset as tb_download_common: clear cross-function state.
      progressbaridx=""
   fi
   if [ -n "$progressbaridx" ]; then
      if [ "${TB_INPUT:-}" = "" ] || [ "${TB_INPUT:-}" = "gui" ]; then
         output_gui "${output_opts[@]}" --progressbaridx "$progressbaridx" --progressbarx --parentpid "$$" --typex "info" --progressbartitlex "$TITLE" --message "$tb_notify_msg" --parentpid "$$" --done
      fi
   fi

   local timeout_after kill_after file_name
   local lastpid pv_pid pv_wrapper_pid pv_exit_code pv_wrapper_exit_code tb_tar_exit_code MSG
   timeout_after="180"
   kill_after="30"
   file_name="$TBB_PACKAGE_FULL_PATH"

   log notice "Extracting '$file_name'..."

   ## Folder must be already exist, otherwise `tar` would fail.
   mkdir --parents -- "$tb_extract_temp_folder"

   ## Debugging.
   #timeout_after="0.001"
   #kill_after="0.001"

   ## `tar` vs `bsdtar`
   ##
   ## Using `bsdtar` because it can auto detect the archive type.
   ## When using `tar` in a pipe, we would need to explicitly set '--xz'.
   ##
   ## Using '--strip-components 1':
   ## - Because we are also using '--directory "$tb_extract_temp_folder"'
   ## - To remove language specific default folder names such as tor-browser_en-US.

   tb_tar_exit_code="0"

   if [ "${TB_FORCE_INSTALL:-}" = "1" ]; then
      timeout \
         --kill-after="$kill_after" \
         "$timeout_after" \
            bsdtar \
               --strip-components 1 \
               --extract \
               --directory "$tb_extract_temp_folder" \
               --file "$file_name" \
               &

      lastpid="$!"
      true "INFO: wait bsdtar lastpid"
      wait "$lastpid" || { tb_tar_exit_code="$?" ; true; };
   else
     ## pv_echo_command is parsed using `eval` later. The single-quoted
     ## '$percent' is kept literal on purpose so the eval picks up the
     ## percent value from its own scope.
     # shellcheck disable=SC2016,SC2089
      pv_echo_command='echo "extraction percent done: "$percent" / 100" >&2'
      # shellcheck disable=SC2090
      export pv_echo_command
      export -f output_gui
     ## pv_wrapper_command is parsed using `eval`.
     # shellcheck disable=SC2089
      pv_wrapper_command="output_gui ${output_opts[*]} --progressbaridx $progressbaridx --progressx \"\$percent\""
      # shellcheck disable=SC2090
      export pv_wrapper_command

      local tar_fifo tar_pid pv_wrapper_fifo pv_wrapper_fifo
      local pv_exit_code tar_exit_code pv_wrapper_exit_code
      tar_fifo="$TEMP_DIR/tar_fifo"
      pv_wrapper_fifo="$TEMP_DIR/pv_wrapper_fifo"
      pv_exit_code="0"
      tar_exit_code="0"
      pv_wrapper_exit_code="0"

      safe-rm --force -- "$tar_fifo"
      safe-rm --force -- "$pv_wrapper_fifo"
      mkfifo -- "$tar_fifo"
      mkfifo -- "$pv_wrapper_fifo"

      timeout \
         --kill-after="$kill_after" \
         "$timeout_after" \
            bsdtar \
               --strip-components 1 \
               --extract \
               --directory "$tb_extract_temp_folder" \
               --file \
               - \
               < "$tar_fifo" \
               &
      tar_pid="$!"
      last_pid_list+=" $tar_pid"

      ## Debugging.
      #export pv_wrapper_debug=true

      timeout \
         --kill-after="$kill_after" \
         "$timeout_after" \
            bash \
               /usr/libexec/msgcollector/pv_wrapper \
               < "$pv_wrapper_fifo" \
               &
      pv_wrapper_pid="$!"
      last_pid_list+=" $pv_wrapper_pid"

      timeout \
         --kill-after="$kill_after" \
         "$timeout_after" \
            pv \
               --numeric \
               "$file_name" \
               1> "$tar_fifo" \
               2> "$pv_wrapper_fifo" \
               &
      pv_pid="$!"
      last_pid_list+=" $pv_pid"

      true "INFO: wait pv_pid"
      wait "$pv_pid" || { pv_exit_code="$?" ; true; };
      true "INFO: wait tar_pid"
      wait "$tar_pid" || { tar_exit_code="$?" ; true; };
      true "INFO: wait pv_wrapper_pid"
      wait "$pv_wrapper_pid" || { pv_wrapper_exit_code="$?" ; true; };

      if [ ! "${pv_exit_code:-}" = "0" ]; then
         tb_tar_exit_code="1"
      fi
      if [ ! "${tar_exit_code:-}" = "0" ]; then
         tb_tar_exit_code="1"
      fi
      if [ ! "${pv_wrapper_exit_code:-}" = "0" ]; then
         tb_tar_exit_code="1"
      fi
   fi

   ## `timeout` returns:
   ## - 124 if sigterm was sufficient
   ## - 137 if needed to use kill.

   if [ "${progressbaridx:-}" = "" ]; then
      true
   else
      output_gui "${output_opts[@]}" --progressbaridx "$progressbaridx" --progressx "100" || true
      progressbaridx=""
   fi

   if [ ! "${tb_tar_exit_code:-}" = "0" ]; then
      ## Debugging.
      ls -la "$tb_extract_temp_folder" || true

      local MSG="<p><b>Could not extract <code>$file_name</code>!</b>
<br></br>
<br></br>(Debugging information:
<br></br>tb_tar_exit_code: <code>$tb_tar_exit_code</code>
<br></br>pv_exit_code: <code>$pv_exit_code</code>
<br></br>tar_exit_code: <code>$tar_exit_code</code>
<br></br>pv_wrapper_exit_code: <code>$pv_wrapper_exit_code</code>)
<br></br>
<br></br>Please report this bug!</p>"
      output_gui "${output_opts[@]}" --messagex --typex "error" --message "$MSG" --done

      output_cli error "$MSG"

      tb_exit_function 15
   fi

   log notice "Extraction of '$file_name' has been completed."
}

tb_patch_download_folder_create() {
   mkdir --parents -- "$tb_extract_temp_folder/Browser/Downloads"
}

tb_patch() {
   if [ "${TB_NO_PATCH:-}" = "true" ]; then
      true "INFO: Skipping ${FUNCNAME[0]}, because TB_NO_PATCH is 'true', ok."
      return 0
   fi

   tb_run_function tb_patch_download_folder_create
}

tb_install() {
   if [ "${TB_NO_INSTALL:-}" = "true" ]; then
      true "INFO: Skipping ${FUNCNAME[0]}, because TB_NO_INSTALL is 'true', ok."
      return 0
   fi

   log notice "Deleting old folder '$tb_browser_folder'."
   safe-rm --recursive --force -- "$tb_browser_folder"

   log notice "Moving temporary folder '$tb_extract_temp_folder' to '$tb_browser_folder'."
   mv -- "$tb_extract_temp_folder" "$tb_browser_folder"
}

tb_cleanup() {
   if [ "${TB_NO_INSTALL:-}" = "true" ]; then
      true "INFO: Skipping ${FUNCNAME[0]}, because TB_NO_INSTALL is 'true', ok."
      return 0
   fi
   if [ "${TB_NO_CLEANUP:-}" = "true" ]; then
      log notice "Skipping ${FUNCNAME[0]}, because TB_NO_CLEANUP is 'true', ok."
      return 0
   fi

   log notice "Deleting no longer required files '$TBB_PACKAGE_FULL_PATH' and '$TBB_SIG_FULL_PATH' to save space."
   safe-rm --force -- "$TBB_PACKAGE_FULL_PATH"
   safe-rm --force -- "$TBB_SIG_FULL_PATH"
}

tb_hardcoded_version_last_downloaded_save() {
   overwrite "$tbb_version_last_downloaded_save_file" "$tbb_version_stripped"
   log notice "Saved '$tbb_version_stripped' in '$tbb_version_last_downloaded_save_file'."
}

tb_start_ask() {
   if [ "${noaskstart:-}" = "true" ]; then
      true "INFO: noaskstart is set to 'true'. Skipping question if '$tb_title' should be started."
      return 0
   fi

   if [ "${TB_FORCE_INSTALL:-}" = "1" ]; then
      log notice "Finished installing '$tb_title'. Can be found in folder '$tb_browser_folder'."
      log notice "Not starting '$tb_title', because variable TB_FORCE_INSTALL is set to '1'."
      return 0
   fi

   local has_torbrowser_exit_code="0"
   has "$tb_bin" || { has_torbrowser_exit_code="$?" ; true; };

   if [ ! "${has_torbrowser_exit_code:-}" = "0" ]; then
      log notice "'$tb_bin' binary not found in path. tb-starter is probably not installed. \
Skipping question to start '$tb_title'."
      return 0
   fi

   if [ "${noaskstart:-}" = "false" ]; then
      true "noaskstart is 'false'"
   else
      if [ "${running_inside_qubes_template_vm:-}" = "true" ] ; then
         MSG="<p>Finished installing '$tb_title'. Can be found in '$tb_browser_folder'.</p>

<p>Not asking to start $tb_title, because running in a Template.</p>

<p>To run '$tb_title', start an App Qube, Standalone or Disposable, then run:<br />
<code>$tb_bin</code></p>"
         output_gui "${output_opts[@]}" --messagex --typex "info" --message "$MSG" --done

         output_cli notice "$MSG"

         return 0
      fi
   fi

   MSG="<p>Finished installing $tb_title. Can be found in $tb_browser_folder.</p>

<p>Please donate!
<br></br><a href=$tb_documentation_base_url_clearnet/wiki/Donate>$tb_documentation_base_url_clearnet/wiki/Donate</a></p>"
   local question="Start $tb_title?"
   local button="yesno"
   local answer

   if [ "${TB_INPUT:-}" = "none" ]; then
      true "INFO: TB_INPUT is set to '$TB_INPUT'. Skipping question if $tb_title should be started."
      return 0
   fi

   if [ "${TB_INPUT:-}" = "stdin" ]; then
      log question "QUESTION: $question
y/n?"
      read -r answer
      if [ ! "${answer:-}" = "y" ]; then
         log notice "Canceled starting '$tb_title', ok."
      else
         $tb_bin
      fi
      return 0
   fi

   log notice "GUI is asking to start $tb_title."
   answer="$(/usr/libexec/msgcollector/generic_gui_message "info" "$TITLE" "$MSG" "$question" "$button")"
   if [ ! "${answer:-}" = "16384" ]; then ## Button 'Yes' has not been pressed.
      log notice "Canceled starting '$tb_title', ok."
      return 0
   else
      log notice "Running '$tb_title'."
      $tb_bin
      log notice "'$tb_title' was terminated, ok."
      return 0
   fi
}

main_function() {
   tb_run_function root_check "$@"
   tb_run_function qubes_template_check "$@"

   tb_run_function sysmaint_check "$@"
   tb_run_function tb_sanity_tests
   tb_run_function tb_config_folder_parser
   tb_run_function tb_parse_cmd_options "$@"
   tb_run_function tb_preparation
   tb_run_function tb_stdin
   tb_run_function tb_qubes_dvm_template
   tb_run_function tb_create_folders
   tb_run_function tb_local_version_detection

   if [ "${tb_hard_reset:-}" == "true" ]; then
      tb_run_function tb_kill_already_running_tb_maybe
      tb_run_function tb_version_processing
      tb_run_function tb_reset
      tb_run_function tb_openpgp_verify
      tb_run_function tb_confirm_install
      tb_run_function tb_extract
      tb_run_function tb_patch
      tb_run_function tb_install
      tb_run_function tb_cleanup
      tb_run_function tb_start_ask
      return 0
   fi

   #tb_run_function tb_skip_if_higher_or_equal_version_already_downloaded
   tb_run_function tb_connectivity_checks_interfaces
   tb_run_function tb_connectivity_checks_tor
   tb_run_function tb_connectivity_checks_curl
   tb_run_function tb_update_check
   tb_run_function tb_remote_version_parser
   tb_run_function tb_remote_version_sanity_test

   tb_run_function tb_confirm_update
   tb_run_function tb_version_processing
   tb_run_function tb_kill_already_running_tb_maybe
   tb_run_function tb_download_files
   tb_run_function tb_openpgp_verify
   tb_run_function tb_confirm_install
   tb_run_function tb_extract
   tb_run_function tb_patch
   tb_run_function tb_install
   tb_run_function tb_cleanup
   tb_run_function tb_hardcoded_version_last_downloaded_save
   tb_run_function tb_start_ask
}

if [ "${tb_updater_was_sourced}" = "true" ]; then
  true "INFO: Not running because it was sourced by another script."
else
  true "INFO: Running because it was executed."
  tb_run_function main_function "$@"
fi
