#!/bin/bash

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

## Exit codes:
##   0 - no suspicious unicode found in the ref's new commits
##   1 - suspicious unicode found
##   2 - error (no target ref given, ref does not exist, not inside a git
##       working tree, no new commits in the ref, or a git failure)
##
## This mirrors unicode-show (0 clean / 1 found / 2 error) so a caller can tell
## a real detection apart from a usage or setup error by the exit code, rather
## than both sharing exit 1.

set -o errexit
set -o nounset
set -o errtrace
set -o pipefail
shopt -s inherit_errexit
shopt -s shift_verbose

# shellcheck source=../libexec/helper-scripts/log_run_die.sh
source "${HELPER_SCRIPTS_PATH:-}"/usr/libexec/helper-scripts/log_run_die.sh

log_level=info

check_ref_commits_for_unicode() {
  local target_ref git_log_cmd git_log_output commit_list commit commit_diff \
  unicode_report unicode_show_exit_code found_malicious_unicode

  target_ref="${1:-}"
  if [ -z "${target_ref}" ]; then
    die 2 'No target ref specified!'
  fi

  if ! [ "$(git rev-parse --is-inside-work-tree 2>/dev/null)" = 'true' ]; then
    die 2 'Current working directory is not inside a Git working tree!'
  fi

  if ! git rev-parse --verify "${target_ref}" >/dev/null 2>/dev/null; then
    die 2 'Target ref does not exist!'
  fi

  git_log_cmd=( git log --format=%H "HEAD..${target_ref}" )

  if ! git_log_output="$( "${git_log_cmd[@]}" )"; then
    die 2 "git_log_cmd failed! git_log_cmd: ${git_log_cmd[*]}"
  fi

  if [ "${git_log_output}" = "" ]; then
    die 2 'No new commits in target ref!'
  fi

  readarray -t commit_list <<< "${git_log_output}"

  if [ -z "${commit_list[0]:-}" ]; then
    die 2 'commit_list array first element is empty or missing!'
  fi

  found_malicious_unicode='false'
  for commit in "${commit_list[@]}"; do
    ## --no-ext-diff prevents use of external diff drivers.
    ##
    ## --unified=0 prevents false positives from unicode-show resulting from
    ## unmodified empty lines showing up in the diff as one (or in the case of
    ## merge commits sometimes two) spaces.
    ##
    ## --no-textconv prevents text conversion filters from running.
    ##
    ## The commit message is intentionally included since it could contain
    ## malicious unicode too.
    ## Identity and message are scanned WHOLE and unfiltered -- a commit
    ## message line may legitimately start with '-', and the removal filter
    ## below must never reach it.
    commit_meta="$(git show \
      --no-patch \
      --format=$'Author: %an\nAuthor email: %ae\nCommitter: %cn\nCommitter email: %ce\n%B' \
      "${commit}")"

    commit_diff="$(git show \
      --no-ext-diff \
      --unified=0 \
      --no-textconv \
      --format='' \
      "${commit}")"

    ## Scan what the commit ADDS, not what it removes.
    ##
    ## With the removal lines included, the commit that DELETES a hostile
    ## character is flagged as hostile -- the diff line '-dirty <U+202E>line'
    ## still contains it. So the cleanup commit gets the same warning as the
    ## attack, and a scanner that flags fixes trains reviewers to ignore it.
    ## Nothing a commit removes can end up in the resulting tree, so a removal
    ## cannot be the attack.
    ##
    ## '---' is the file header, not a removal, and both file headers are KEPT:
    ## a hostile FILENAME has to stay detectable.
    commit_diff_additions=''
    while IFS= read -r diff_line; do
      case "${diff_line}" in
        '@@ '*)
          ## A hunk header ends with a FUNCNAME suffix -- '@@ -3,0 +4 @@ text'
          ## -- where 'text' is the nearest preceding line, which the commit
          ## did NOT touch. When that surrounding line already contains hostile
          ## unicode, every later commit anywhere near it is flagged for a
          ## character it neither added nor removed. Keep the ranges, which are
          ## pure ASCII, and drop the decoration.
          hunk_ranges="${diff_line#'@@ '}"
          if [ "${hunk_ranges}" != "${hunk_ranges%%'@@'*}" ]; then
            diff_line="@@ ${hunk_ranges%%'@@'*}@@"
          fi
          ;;
        '---'*)
          ;;
        '-'*)
          continue
          ;;
      esac
      commit_diff_additions+="${diff_line}"$'\n'
    done <<< "${commit_diff}"

    unicode_show_exit_code='0'
    unicode_report="$(unicode-show <<< "${commit_meta}${commit_diff_additions}" 2>&1)" \
      || unicode_show_exit_code="$?"

    if [ -n "${unicode_report}" ] \
      || [ "${unicode_show_exit_code}" != '0' ]; then
      log warn "Potentially malicious unicode detected in commit '${commit}'! Details:"
      printf '%s\n' "${unicode_report:-'No stdout or stderr from unicode-show!'}"
      found_malicious_unicode='true'
    else
      true "INFO: No unicode detected in commit '${commit}'."
    fi
  done

  if [ "${found_malicious_unicode}" = 'true' ]; then
    die 1 'Potentially malicious unicode detected!'
  fi
  log notice 'No unicode detected.'
}

check_ref_commits_for_unicode "$@"
