#!/bin/bash

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

#set -x
set -e
set -o errexit
set -o nounset
set -o errtrace
set -o pipefail

true "$0: START."

MYDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

if [ "$MYDIR" = "/usr/bin" ]; then
   true "INFO: Run from: /usr/bin"
   ## XXX: hardcoded path
   derivative_maker_source_code_dir="$HOME/derivative-maker"
else
   true "INFO: Run from: source code folder"
   derivative_maker_source_code_dir="$(cd -- "$MYDIR" && cd -- "../../../../../" && pwd)"
fi

grep_find_unicode_wrapper="${derivative_maker_source_code_dir}/packages/kicksecure/helper-scripts/usr/bin/grep-find-unicode-wrapper"

## Sanity tests.
test -d "$derivative_maker_source_code_dir"
test -x "$grep_find_unicode_wrapper"

## Need to 'cd' for relative paths.
cd "$derivative_maker_source_code_dir"

declare -A symlink_whitelist

symlink_whitelist["./live-build/share/bootloaders/isolinux/isolinux.bin"]="/usr/lib/ISOLINUX/isolinux.bin"
symlink_whitelist["./live-build/share/bootloaders/isolinux/ldlinux.c32"]="/usr/lib/syslinux/modules/bios/ldlinux.c32"
symlink_whitelist["./live-build/share/bootloaders/syslinux_common/hdt.c32"]="/usr/lib/syslinux/modules/bios/hdt.c32"
symlink_whitelist["./live-build/share/bootloaders/syslinux_common/libgpl.c32"]="/usr/lib/syslinux/modules/bios/libgpl.c32"
symlink_whitelist["./live-build/share/bootloaders/syslinux_common/libutil.c32"]="/usr/lib/syslinux/modules/bios/libutil.c32"
symlink_whitelist["./live-build/share/bootloaders/syslinux_common/libcom32.c32"]="/usr/lib/syslinux/modules/bios/libcom32.c32"
symlink_whitelist["./live-build/share/bootloaders/syslinux_common/vesamenu.c32"]="/usr/lib/syslinux/modules/bios/vesamenu.c32"
symlink_whitelist["./live-build/share/bootloaders/syslinux_common/libmenu.c32"]="/usr/lib/syslinux/modules/bios/libmenu.c32"
symlink_whitelist["./live-build/share/bootloaders/pxelinux/pxelinux.0"]="/usr/lib/PXELINUX/pxelinux.0"
symlink_whitelist["./live-build/share/bootloaders/pxelinux/ldlinux.c32"]="/usr/lib/syslinux/modules/bios/ldlinux.c32"
symlink_whitelist["./live-build/data/debian-cd/bullseye"]="squeeze/"
symlink_whitelist["./live-build/data/debian-cd/jessie"]="squeeze"
symlink_whitelist["./live-build/data/debian-cd/stretch"]="squeeze"
symlink_whitelist["./live-build/data/debian-cd/unstable"]="sid"
symlink_whitelist["./live-build/data/debian-cd/buster"]="squeeze"
symlink_whitelist["./live-build/data/debian-cd/kali-last-snapshot"]="sid"
symlink_whitelist["./live-build/data/debian-cd/sid"]="trixie"
symlink_whitelist["./live-build/data/debian-cd/bookworm"]="squeeze/"
symlink_whitelist["./live-build/data/debian-cd/kali-dev"]="sid"
symlink_whitelist["./live-build/data/debian-cd/kali-rolling"]="sid"
symlink_whitelist["./live-build/data/debian-cd/testing"]="trixie"
symlink_whitelist["./live-build-data/d-i-branding/logo_installer_dark.png"]="logo_debian.png"
symlink_whitelist["./live-build-data/d-i-branding/logo_debian_dark.png"]="logo_debian.png"
symlink_whitelist["./live-build-data/d-i-branding/logo_installer.png"]="logo_debian.png"
symlink_whitelist["./qubes/qubes-template-whonix/whonix-workstation"]="whonix-gateway"

check_symlink() {
   local symlink
   symlink="$1"
   local target
   target="$(readlink -- "$symlink")"

   if [ "${symlink_whitelist[$symlink]+_}" ]; then
      if [ "${symlink_whitelist[$symlink]}" == "$target" ]; then
         printf "%s\n" "$0: INFO: Whitelisted symlink: '$symlink' -> '$target'" >/dev/null
         return 0
      fi
      printf "%s\n" "$0: ERROR: Whitelisted symlink: '$symlink' but wrong target '$target'" >&2
      return 1
   fi

   printf "%s\n" "$0: ERROR: NOT whitelisted symlink: '$symlink' -> '$target'" >&2
   return 1
}

find_symlinks() {
   local symlink found_symlink
   found_symlink=""
   while IFS= read -r -d '' symlink; do
      if check_symlink "$symlink" ; then
         continue
      fi
      found_symlink=true
      printf "%s\n" "----------" >&2
   ## Need to use '"."' for relative paths.
   done < <(find "." -type l -not -iwholename '*.git*' -print0)
   if [ "$found_symlink" = "true" ]; then
      exit 1
   fi
}

find_symlinks

## overwrite with '|| true' because `grep` exits non-zero if no match was found.
##
## Using because a real name contains a special character.
## XXX: This is clearly a non-ideal solution but fixing this is an issue for
##      whole Free and Open Source community. See also:
##      https://www.kicksecure.com/wiki/Unicode
##      https://forums.whonix.org/t/detecting-malicious-unicode-in-source-code-and-pull-requests/13754
## --exclude=LICENSE
## --exclude=lkrg-openrc.sh
grep_find_unicode_wrapper_output="$(\
   "$grep_find_unicode_wrapper" \
      --recursive \
      --binary-files=without-match \
      --exclude=control.authcookie \
      --exclude=LICENSE \
      --exclude=lkrg-openrc.sh \
      --exclude=changelog \
      --exclude=changelog.upstream \
      --exclude-dir=.git \
      -- \
      "./" \
   )" \
   || true

whitelist_list=(
   './live-build/share/bootloaders/syslinux_common/menu.cfg'
   './live-build/debian/control'
   './live-build/debian/copyright'
   './live-build/manpages/po/fr/.*'
   './live-build/manpages/fr/*'
   './live-build/manpages/ja/*'
   './live-build/manpages/po/ja/.*'
   './packages/kicksecure/kicksecure-base-files/debian/copyright'
   './packages/kicksecure/helper-scripts/usr/lib/python3/dist-packages/stdisplay/tests/stdisplay.py'
)
whitelist_pattern="($(IFS=$'\n'; echo -n "${whitelist_list[*]}" | sed -z 's/\n/|/g'))";

filtered_output=$(printf "%s\n" "$grep_find_unicode_wrapper_output" | grep --invert-match --extended-regexp -- "$whitelist_pattern" || true)

if [ -z "$filtered_output" ]; then
   true "INFO: No Unicode issues found, everything is OK."
else
   printf "%s\n" "$filtered_output" >&2

   printf "%s\n" "\
$0: ERROR: Unicode found!

See also:
- https://www.kicksecure.com/wiki/Unicode
- https://forums.whonix.org/t/detecting-malicious-unicode-in-source-code-and-pull-requests/13754
"
   exit 1
fi

## Check for trailing spaces.
## TODO: remove '--exclude-dir=live-build' once live-build is free of trailing spaces.
if grep \
      --line-number \
      --recursive \
      --binary-files=without-match \
      --exclude=control.authcookie \
      --exclude=LICENSE \
      --exclude=lkrg-openrc.sh \
      --exclude=changelog \
      --exclude=changelog.upstream \
      --exclude-dir=.git \
      --exclude-dir=live-build \
      -- \
      '[[:blank:]]$' ; then
   true "\
$0: ERROR: Trailing whitespaces found!"
   exit 1
else
   true "INFO: No trailing whitespace issues found, everything is OK."
fi

## Check for missing newline at the end of the file (EOF).
##
## Thanks to:
## Julien Palard
## https://stackoverflow.com/a/25686825/2605155
##
## grep -Pzlvr '\x0a$'
## --perl-regexp --null --files-with-matches --invert-match
##
## '--null-data' unfortunately breaks '--binary-files'. Hence,
## binary file types need to be manually excluded.
grep_missing_newline_result_raw="$( \
   grep \
      --recursive \
      --binary-files=without-match \
      --exclude='*.png' --exclude='*.jpg' --exclude='*.jpeg' \
      --exclude='*.ico' --exclude='*.svg' --exclude='*.ai' \
      --exclude='*.gpg' --exclude='*.kbx' --exclude='*.pf2' \
      --perl-regexp --null-data --files-with-matches --invert-match \
      --exclude="control.authcookie" \
      --exclude="RecommendedTBBVersions" \
      --exclude-dir="live-build" \
      --exclude-dir=".git" \
      -- \
      '\x0a$'
   )" \
   || true

true "grep_missing_newline_result_raw:
$grep_missing_newline_result_raw"

grep_missing_newline_result_filtered=()

for file_name in $grep_missing_newline_result_raw ; do
   ## git style symlinks actually not allowed.
#    if [ "$file_name" = "packages/kicksecure/helper-scripts/usr/bin/append-once" ]; then
#       size="$(stat -c '%s' -- "$file_name")"
#       if [ "$size" = "6" ]; then
#          continue
#       fi
#    fi
#    if [ "$file_name" = "packages/kicksecure/helper-scripts/usr/bin/overwrite" ]; then
#       size="$(stat -c '%s' -- "$file_name")"
#       ## Coincidentally also 6 bytes size.
#       if [ "$size" = "6" ]; then
#          continue
#       fi
#    fi

   ## git symlink when using 'symlinks = true' (git default setting)
   if [ "$file_name" = "qubes/qubes-template-whonix/whonix-workstation" ]; then
      file_content="$(stcat "$file_name")"
      if [ "$file_content" = "whonix-gateway" ]; then
         continue
      fi
   fi

   grep_missing_newline_result_filtered+=("$file_name")
done

if [ "${#grep_missing_newline_result_filtered[@]}" -eq "0" ]; then
   true "INFO: No missing newlines at the end of file issues found, everything is OK."
else
   printf "%s\n" "$0: ERROR: Missing newlines at the end of file found! grep_missing_newline_result_filtered:
${grep_missing_newline_result_filtered[@]}"
   exit 1
fi

true "$0: END."
