#!/bin/bash

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

#set -x
set -e

## 'set -e is important'. For example if open-link-confirmation is called from
## other applications that are confined (by AppArmor or otherwise),
## sanitize-string might fail.
# /usr/bin/sanitize-string https://www.whonix.org
# /usr/libexec/open-link-confirmation/open-link-confirmation: /usr/bin/sanitize-string: /usr/bin/python3: bad interpreter: Permission denied
# + input_object_stripped_and_trimmed=
# /usr/bin/torbrowser: /usr/libexec/msgcollector/msgcollector: /bin/bash: bad interpreter: Permission denied
# /usr/bin/torbrowser: /usr/libexec/msgcollector/msgcollector: /bin/bash: bad interpreter: Permission denied
# /usr/bin/torbrowser: /usr/libexec/msgcollector/msgcollector: /bin/bash: bad interpreter: Permission denied

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

trap "error_handler" ERR

error_handler() {
   local exit_code="$?"

   ## sanitize-string may fail (e.g. AppArmor-confined caller, see file header).
   ## Tolerate it with '|| true' so this ERR trap still shows its dialog instead
   ## of aborting / re-entering under 'set -e'.
   local MSG
   MSG="$0 script bug.

No panic. Nothing is broken. Just some rare condition has been hit.
Try again later. If this is a transient issue, it can be safely ignored.

Debugging information:

BASH_COMMAND: <code>$(sanitize-string -- 255 "$BASH_COMMAND" || true)</code>
exit_code: <code>$exit_code</code>"

   MSG="$(/usr/libexec/msgcollector/br_add "$MSG")"

   local title="open-link-confirmation bug"
   local question=""
   local button="ok"
   local msg="<p>$MSG</p>"
   /usr/libexec/msgcollector/generic_gui_message "error" "$title" "$msg" "$question" "$button"

   exit 1
}

source /usr/libexec/helper-scripts/has.sh

source_config() {
   ## Environment variables take precedence over the configuration files.
   ## Remember any values provided via the environment before the
   ## configuration files are sourced, then restore them afterwards so the
   ## environment wins over the on-disk defaults.
   local link_confirmation_for_links_env link_confirmation_for_files_env
   link_confirmation_for_links_env="${link_confirmation_for_links:-}"
   link_confirmation_for_files_env="${link_confirmation_for_files:-}"

   shopt -s nullglob
   local i
   for i in \
      /etc/open_link_confirm.d/*.conf \
      /usr/local/etc/open_link_confirm.d/*.conf \
      ; do
      ## TODO
      bash -n "$i" || exit 1
      source "$i"
   done

   if [ -n "$link_confirmation_for_links_env" ]; then
      link_confirmation_for_links="$link_confirmation_for_links_env"
   fi
   if [ -n "$link_confirmation_for_files_env" ]; then
      link_confirmation_for_files="$link_confirmation_for_files_env"
   fi
}

preparation() {
   ## Infinite loop protection.
   is_whole_number "$OPEN_LINK_CONFIRMATION_MAXIMUM" || OPEN_LINK_CONFIRMATION_MAXIMUM="5"
   is_whole_number "$OPEN_LINK_CONFIRMATION_COUNTER" || OPEN_LINK_CONFIRMATION_COUNTER="0"
   OPEN_LINK_CONFIRMATION_COUNTER=$(( OPEN_LINK_CONFIRMATION_COUNTER + 1 ))
   export OPEN_LINK_CONFIRMATION_COUNTER
   if [ "$OPEN_LINK_CONFIRMATION_COUNTER" -ge "$OPEN_LINK_CONFIRMATION_MAXIMUM" ]; then
      echo "$0: ERROR: recursively called more than $OPEN_LINK_CONFIRMATION_MAXIMUM times!" >&2
      title="Link Confirm Open ERROR"
      msg="<p>Recursively called more than $OPEN_LINK_CONFIRMATION_MAXIMUM times, maybe because no browser is installed, that supports open-link-confirmation.</p>"
      question=""
      button="ok"
      /usr/libexec/msgcollector/generic_gui_message "error" "$title" "$msg" "$question" "$button"
      exit 211
   fi

   ## Used by tb-starter.
   export OPEN_LINK_CONFIRMATION="true"

   if [ "$#" = "0" ]; then
      ## Zero arguments.
      is_file="0"
   fi

   input_object_original="$*"

   trim="128"

   input_object_string_length="${#input_object_original}"

   ## Trim input_object_original to 128 characters and sanitize.
   input_object_stripped_and_trimmed="$(/usr/bin/sanitize-string -- "$trim" "$input_object_original")"

   if [ "$input_object_string_length" -gt "$trim" ]; then
      extra_long_link="<p><b>Note</b>: The address is too long, so only the first <u>$trim</u> characters are shown.</p>"
   fi

   if [ -f "$input_object_original" ]; then
      is_file="1"
      input_type="file"
   else
      is_file="0"
      input_type="link"
   fi

   if has qubesdb-read; then
      qubes_detected=true
      ## Overwrite with '|| true' since 'qubesdb-read /type' is only available in
      ## Qubes R4.0 and above.
      qubes_type="$(qubesdb-read /type)" || true
   fi

  kernel_cmdline=''
  if [ -f /proc/cmdline ]; then
    kernel_cmdline="$(cat -- /proc/cmdline)"
  elif [ -f /proc/1/cmdline ]; then
    kernel_cmdline="$(cat -- /proc/1/cmdline)"
  fi

  if [[ "${kernel_cmdline}" =~ 'boot-role=sysmaint' ]]; then
    in_sysmaint_mode='yes'
  else
    in_sysmaint_mode='no'
  fi
}

gateway() {
   if [ ! "$EDITOR" = "" ]; then
      open_in_tool_bin="$EDITOR"
      open_in_tool_bin_name="\$EDITOR $EDITOR"
   ## What other useful condition could we check to determine the default editor?
   #elif [ "1" = "2" ]; then
      #open_in_tool_bin="?"
      #open_in_tool_bin_name="?"
   else
      if has featherpad; then
         open_in_tool_bin="featherpad"
         open_in_tool_bin_name="FeatherPad"
      elif has mousepad; then
        open_in_tool_bin="mousepad"
        open_in_tool_bin_name="Mousepad"
      else
         ## TODO: implement better fallback
         open_in_tool_bin="featherpad"
         open_in_tool_bin_name="FeatherPad"
      fi
   fi

   [ -n "$tb_title" ] || tb_title="Tor Browser"

   open_in_tool_extra_opts=""

   if [ "$is_file" = "1" ]; then
      ## Open files on gateway without confirmation.

      ## We could easily change our mind and ask for confirmation.
      title="Link Confirm Open"
      #msg="$input_object_original will be opened in $open_in_tool_bin_name. Continue?"

      open_in_tool_exit_code="0"
      "$open_in_tool_bin" $open_in_tool_extra_opts "$input_object_original" >/dev/null 2>/dev/null || { open_in_tool_exit_code="$?" ; true; };
      exit "$open_in_tool_exit_code"
   fi

   ## Does not work.
   ## Opens in Qubes-Whonix-Gateway based DispVM instead.
   ## sys-whonix's 'default_dispvm' is not set to 'whonix-ws-14-dvm' by default.
   ## https://github.com/QubesOS/qubes-issues/issues/4363
   #if [ "$qubes_detected" = "true" ] ; then
      #qubes_redirect "$@"
      #return 0
   #fi

   title="Link Confirm Open ERROR"
   msg="<p>Link Confirm Open does not support opening links on Gateway for security reasons.</p>
<p>Use $tb_title under Workstation to browse the internet.</p>"
   question=""
   button="ok"
   /usr/libexec/msgcollector/generic_gui_message "error" "$title" "$msg" "$question" "$button"
   exit 0
}

qubes_redirect() {
   vm_name="$(qubesdb-read /name)"

   [ -n "$link_confirmation_vm_open_tool" ] || link_confirmation_vm_open_tool="qvm-open-in-dvm"

   link_confirmation_vm_open_tool_exit_code="0"
   link_confirmation_vm_open_tool_output="$("$link_confirmation_vm_open_tool" "$input_object_original" 2>&1)" \
      || { link_confirmation_vm_open_tool_exit_code="$?" ; true; };

   if [ "$link_confirmation_vm_open_tool_exit_code" = "0" ]; then
      exit 0
   fi

   if [ "$link_confirmation_vm_open_tool_output" = "Request refused" ]; then
      ## Qubes 'no' clicked.
      exit 0
   fi

   title="Link Confirm Open ERROR"
   question=""
   button="ok"
   msg="\
<p>The following <u>$input_type</u> could not be opened.</p>

<p><code><blockquote>$input_object_stripped_and_trimmed$extra_long_link</blockquote></code></p>

<p>Please copy the link to the Workstation and open it there.</p>
<p>Use $tb_title under Workstation to browse the internet.</p>

<p>Debugging information:
<br></br>link_confirmation_vm_open_tool: <code>$(sanitize-string -- 255 "$link_confirmation_vm_open_tool")</code>
<br></br>input_object_stripped_and_trimmed: <code>$input_object_stripped_and_trimmed</code>
<br></br>link_confirmation_vm_open_tool_output: <code>$(sanitize-string -- 255 "$link_confirmation_vm_open_tool_output")</code>
<br></br>link_confirmation_vm_open_tool_exit_code: <u>$link_confirmation_vm_open_tool_exit_code</u></p>"
   /usr/libexec/msgcollector/generic_gui_message "error" "$title" "$msg" "$question" "$button"
   exit 1
}

sysmaint_redirect() {
   title="Link Confirm Open ERROR"
   question=""
   button="ok"
   msg="\
<p>The following <u>$input_type</u> could not be opened.</p>

<p><code><blockquote>$input_object_stripped_and_trimmed$extra_long_link</blockquote></code></p>

<p>For security reasons, opening links while booted in '<code>PERSISTENT Mode | SYSMAINT Session</code>' is not supported.</p>
<p>Ensure a suitable web browser is installed, then reboot into '<code>PERSISTENT Mode | USER Session</code>' or '<code>LIVE Mode | USER Session</code>' to browse the Web.</p>"
   /usr/libexec/msgcollector/generic_gui_message "error" "$title" "$msg" "$question" "$button"
   exit 1
}

workstation() {
   if [ "$is_file" = "1" ]; then
      [ -n "$open_in_tool_bin" ] || open_in_tool_bin="xdg-open"

      if [ "$open_in_tool_bin" = "xdg-open" ]; then
         ## Qubes sets 'GNOME_DESKTOP_SESSION_ID=c1' therefore `xdg-open`
         ## detects gnome and sets 'DE=gnome3' which leads to
         ## `xdg-mime query filetype` output being empty "". Therefore,
         ## setting 'DE=generic'.
         xdg_file_type="$(DE=generic xdg-mime query filetype "$input_object_original")" || true
         xdg_desktop_file="$(DE=generic xdg-mime query default "$xdg_file_type")" || true
         open_in_tool_bin_name="$xdg_desktop_file"
      fi
   else
      if test -e /usr/share/kicksecure/marker ; then
         if has brave-browser; then
            [ -n "$open_in_tool_bin" ] || open_in_tool_bin="brave-browser"
         fi
         if has chromium; then
            [ -n "$open_in_tool_bin" ] || open_in_tool_bin="chromium"
         fi
         if has firefox; then
            [ -n "$open_in_tool_bin" ] || open_in_tool_bin="firefox"
         fi
         if has mullvad-browser; then
            [ -n "$open_in_tool_bin" ] || open_in_tool_bin="mullvad-browser"
         fi
         ## Intentionally skipping torbrowser, because the command may exist
         ## even if the browser is not installed.
         if has browser-choice ; then
            [ -n "$open_in_tool_bin" ] || open_in_tool_bin="browser-choice"
         fi
      else
        if has torbrowser; then
          [ -n "$open_in_tool_bin" ] || open_in_tool_bin="torbrowser"
        fi
      fi

      [ -n "$open_in_tool_bin" ] || open_in_tool_bin="x-www-browser"
      #[ -n "$open_in_tool_extra_opts" ] || open_in_tool_extra_opts="--new-tab"

      [ -n "$open_in_tool_bin_name_readlink" ] || open_in_tool_bin_name_readlink="$(readlink -f "$(command -v "$open_in_tool_bin")")"
      [ -n "$open_in_tool_bin_name" ] || open_in_tool_bin_name="$open_in_tool_bin ($open_in_tool_bin_name_readlink)"

      ## Prettier name in default case.
      if [ "$open_in_tool_bin_name" = "x-www-browser (/usr/bin/torbrowser)" ]; then
         open_in_tool_bin_name="Tor Browser"
      fi
   fi

   ## Fallback.
   [ ! -n "$tb_title" ] || open_in_tool_bin_name="$tb_title"
   [ -n "$open_in_tool_bin_name" ] || open_in_tool_bin_name="$open_in_tool_bin"

   if [ "$open_in_tool_bin_name_readlink" = "${BASH_SOURCE[0]}" ]; then
      title="Link Confirm Open ERROR"
      msg="<p>The following <b>$input_type</b> cannot be opened in <u>$open_in_tool_bin_name</u>, because no browser is installed, that supports open-link-confirmation.</p>
<p><code><blockquote>$input_object_stripped_and_trimmed$extra_long_link</blockquote></code></p>"
      question=""
      button="ok"
      /usr/libexec/msgcollector/generic_gui_message "error" "$title" "$msg" "$question" "$button"
      exit 0
   fi

   local careful_text

   if test -e /usr/share/kicksecure/marker ; then
      careful_text="<p></p>"
   else
      careful_text="<p>Be careful if <b>$open_in_tool_bin_name</b> is already running as your activities might get linked.</p>"
   fi

   if [ "$input_object_stripped_and_trimmed" = "" ] || [ "$input_object_stripped_and_trimmed" = " " ]; then
      if test -e /usr/share/kicksecure/marker ; then
         skip_open_link_confirmation="1"
         return 0
      else
         title="Confirm Open"
         msg="$careful_text
<p>$input_object_stripped_and_trimmed$extra_long_link</p>"
         question="Do you want to open <b>$open_in_tool_bin_name</b>?"
         button="yesno"
         return 0
      fi
   fi

   title="Confirm Open"
   msg="<p>The following <b>$input_type</b> will be opened in <u>$open_in_tool_bin_name</u>.</p>
$careful_text
<p><code><blockquote>$input_object_stripped_and_trimmed$extra_long_link</blockquote></code></p>"
   question="Continue?"
   button="yesno"
   return 0
}

final() {
   local ask_for_confirmation="1"
   if [ "$is_file" = "1" ]; then
      ## Got a File.
      if [ "$link_confirmation_for_files" = "0" ]; then
         local ask_for_confirmation="0"
      else
         local ask_for_confirmation="1"
      fi
   else
      ## Got a Link.
      if [ "$link_confirmation_for_links" = "0" ]; then
         local ask_for_confirmation="0"
      else
         local ask_for_confirmation="1"
      fi
   fi

   ## On first invocation in Qubes DispVM, skip asking for confirmation.
   ## https://github.com/QubesOS/qubes-issues/issues/4113
   if [ "$qubes_type" = "DispVM" ]; then
      if [ -f ~/.open-link-confirmation ]; then
         true "${HOME}/.open-link-confirmation already exists. Leaving variable ask_for_confirmation as is."
      else
         true "${HOME}/.open-link-confirmation does not exist yet. Setting ask_for_confirmation=0."
         local ask_for_confirmation="0"
         touch ~/.open-link-confirmation
      fi
   fi

   if [ "$skip_open_link_confirmation" = "1" ]; then
      true
   else
      if [ "$ask_for_confirmation" = "1" ]; then
         local answer
         answer="0"
         answer="$(/usr/libexec/msgcollector/generic_gui_message "warning" "$title" "$msg" "$question" "$button")"
         if [ ! "$answer" = "16384" ]; then ## Button 'Yes' has not been pressed.
            exit 0
         fi
      fi
   fi

   if ! has "$open_in_tool_bin"; then
      local question=""
      local button="ok"
      local msg="<p><b><u>ERROR</b></u>: <b>$open_in_tool_bin</b> does not exist! Please report this bug!</p>"
      /usr/libexec/msgcollector/generic_gui_message "error" "$title" "$msg" "$question" "$button"
      exit 1
   fi

   local open_in_tool_exit_code
   open_in_tool_exit_code="0"
   DE=generic "$open_in_tool_bin" $open_in_tool_extra_opts "$@" >/dev/null 2>/dev/null || { open_in_tool_exit_code="$?" ; true; };

   ## Do not show an error popup.
   ## For example if Tor Browser or Firefox gets killed, the exit code should be handled by the calling application as per usual.
#    if [ ! "$open_in_tool_exit_code" = "0" ]; then
#       local question=""
#       local button="ok"
#       local msg="<p><b><u>ERROR</b></u>: <b>$open_in_tool_bin</b> returned <u>$open_in_tool_exit_code</u>! Please report this bug!</p>"
#       /usr/libexec/msgcollector/generic_gui_message "error" "$title" "$msg" "$question" "$button"
#    fi

   exit "$open_in_tool_exit_code"
}

main_function() {
   source_config "$@"
   preparation "$@"

   if [ -f "/run/qubes/this-is-templatevm" ]; then
      qubes_redirect "$@"
   elif [ "${in_sysmaint_mode}" = 'yes' ]; then
      sysmaint_redirect "$@"
   elif [ -f "/usr/share/anon-gw-base-files/gateway" ]; then
      gateway "$@"
   else
      workstation "$@"
   fi

   final "$@"
}

main_function "$@"
