#!/bin/bash
set -euo pipefail

if ! which unzip >/dev/null 2>&1; then
  echo "The 'unzip' utility is required, but was not found in your path" >&2
  exit 1
fi

TARGET_DIR="${1:-}"
if test -z "$TARGET_DIR"; then
  TARGET_DIR="$(pwd)"
fi
cd "$TARGET_DIR"

# Driven from the artifacts, not from the .sha512 and .asc files present, so a missing one fails
# instead of being one loop iteration fewer.
shopt -s nullglob
artifacts=()
for file in *; do
  case "$file" in
    *.asc|*.sha512|KEYS) continue ;;
  esac
  test -f "$file" || continue
  artifacts+=("$file")
done

if test ${#artifacts[@]} -eq 0; then
  echo "No artifacts to verify in $TARGET_DIR" >&2
  exit 1
fi

for file in "${artifacts[@]}"; do
  if test ! -f "$file.sha512"; then
    echo "$file: no $file.sha512 to check it against" >&2
    exit 1
  fi
  sha512sum --check "$file.sha512"
done

# One file records the commit and the artifact set, and is signed with them.
manifests=(*.manifest)
if test ${#manifests[@]} -ne 1; then
  echo "expected one .manifest file describing the release, found ${#manifests[@]}" >&2
  exit 1
fi
manifest="${manifests[0]}"

# The hash and signature loops only see the files that are there, so without the listed set an
# artifact removed together with its .sha512 and .asc would pass.
listed=()
while IFS= read -r name; do listed+=("$name"); done < <(sed -n 's/^artifact=//p' "$manifest")
if test ${#listed[@]} -eq 0; then
  echo "$manifest: lists no artifact" >&2
  exit 1
fi

for name in "${listed[@]}"; do
  if test ! -f "$name"; then
    echo "$manifest: listed but not in the release, $name" >&2
    exit 1
  fi
done

for file in "${artifacts[@]}"; do
  found=0
  for name in "${listed[@]}"; do
    if test "$file" = "$name"; then found=1; break; fi
  done
  if test "$found" -eq 0; then
    echo "$manifest: in the release but not listed, $file" >&2
    exit 1
  fi
done
echo "$manifest: all ${#listed[@]} artifacts present"

# The manifest and the comment git archive writes into the zip are two independent records of the
# same commit.
recorded="$(sed -n 's/^commit=//p' "$manifest" | tr -d '[:space:]')"
if test -z "$recorded"; then
  echo "$manifest: no commit line" >&2
  exit 1
fi

sources=(*source*.zip)
if test ${#sources[@]} -ne 1; then
  echo "expected one source archive, found ${#sources[@]}" >&2
  exit 1
fi

# -qq, or the "Archive:" banner lands in the comparison.
archived="$(unzip -z -qq "${sources[0]}" | tr -d '[:space:]')"
if test "$recorded" != "$archived"; then
  echo "${sources[0]}: built from commit $archived but the release records $recorded" >&2
  exit 1
fi
echo "${sources[0]}: commit $archived ok"

# A home of its own, so only the downloaded KEYS can verify. Not --keyring: gpg ignores that where
# common.conf sets use-keyboxd. Assigned before exporting, or a failed mktemp would go unnoticed
# and an empty GNUPGHOME means the reviewer's own home.
GNUPGHOME="$(mktemp -d)"
export GNUPGHOME
# "|| true", or a failing gpgconf aborts the trap before the directory is removed.
trap 'gpgconf --kill all >/dev/null 2>&1 || true; rm -rf "$GNUPGHOME"' EXIT

# -O, and never the KEYS next to the artifacts: plain "wget URL" refuses to overwrite, so a planted
# KEYS would stay and the download would land in KEYS.1.
wget -O "$GNUPGHOME/KEYS" https://downloads.apache.org/logging/KEYS
gpg --batch --quiet --import "$GNUPGHOME/KEYS"

for file in "${artifacts[@]}"; do
  if test ! -f "$file.asc"; then
    echo "$file: no $file.asc to verify it with" >&2
    exit 1
  fi
  gpg --batch --verify "$file.asc" "$file"
done

mkdir -p src
cd src
unzip -q -o ../*source*.zip

# Do not "cd" here to position the reviewer: this runs as "bash ./verify-release.sh", so a child
# process, and the change would be lost. The step stays in release-review.adoc, typed by the reviewer.
echo "Sources extracted to $(pwd)"
