#!/bin/sh
# Offline adversarial tests for fetch-sources.sh. No upstream network is contacted.
set -eu

here=$(CDPATH= cd -- "$(dirname "$0")" && pwd)
subject="$here/fetch-sources.sh"
temp_root=$(mktemp -d "${TMPDIR:-/tmp}/neocodec-source-fetch-tests.XXXXXX")
trap 'rm -rf -- "$temp_root"' EXIT HUP INT TERM

fail() {
  echo "FAIL: $*" >&2
  exit 1
}

expect_failure() {
  label=$1
  shift
  if "$@" > "$temp_root/last.out" 2> "$temp_root/last.err"; then
    fail "$label unexpectedly succeeded"
  fi
}

hash_file() {
  if command -v shasum >/dev/null 2>&1; then
    shasum -a 256 "$1" | awk '{print $1}'
  else
    sha256sum "$1" | awk '{print $1}'
  fi
}

fixture="$temp_root/fixture"
downloads="$temp_root/downloads"
fakebin="$temp_root/fakebin"
mkdir -p "$fixture/libs" "$downloads/libs" "$fakebin"
cp "$subject" "$fixture/fetch-sources.sh"
chmod +x "$fixture/fetch-sources.sh"

printf 'alpha payload\n' > "$downloads/alpha.tar.gz"
printf 'beta payload\n' > "$downloads/libs/beta.tar.xz"
cp "$downloads/alpha.tar.gz" "$fixture/alpha.tar.gz"
cp "$downloads/libs/beta.tar.xz" "$fixture/libs/beta.tar.xz"

printf '%s  %s\n' "$(hash_file "$downloads/alpha.tar.gz")" "alpha.tar.gz" \
  > "$fixture/SHA256SUMS"
printf '%s  %s\n' "$(hash_file "$downloads/libs/beta.tar.xz")" "libs/beta.tar.xz" \
  >> "$fixture/SHA256SUMS"
printf 'https://example.test/alpha.tar.gz\talpha.tar.gz\n' > "$fixture/SOURCE-URLS.tsv"
printf 'https://example.test/beta.tar.xz\tlibs/beta.tar.xz\n' >> "$fixture/SOURCE-URLS.tsv"
cp "$fixture/SHA256SUMS" "$temp_root/SHA256SUMS.original"
cp "$fixture/SOURCE-URLS.tsv" "$temp_root/SOURCE-URLS.original"

"$fixture/fetch-sources.sh" --verify >/dev/null

printf 'corrupt\n' > "$fixture/alpha.tar.gz"
expect_failure "checksum mismatch" "$fixture/fetch-sources.sh" --verify
cmp -s "$fixture/SHA256SUMS" "$temp_root/SHA256SUMS.original" ||
  fail "verification changed committed SHA256SUMS"
cp "$downloads/alpha.tar.gz" "$fixture/alpha.tar.gz"

mv "$fixture/libs/beta.tar.xz" "$temp_root/beta.saved"
expect_failure "missing artifact" "$fixture/fetch-sources.sh" --verify
mv "$temp_root/beta.saved" "$fixture/libs/beta.tar.xz"

mv "$fixture/libs/beta.tar.xz" "$temp_root/beta.saved"
ln -s "$downloads/libs/beta.tar.xz" "$fixture/libs/beta.tar.xz"
expect_failure "symlinked artifact" "$fixture/fetch-sources.sh" --verify
rm "$fixture/libs/beta.tar.xz"
mv "$temp_root/beta.saved" "$fixture/libs/beta.tar.xz"

printf 'unexpected\n' > "$fixture/libs/extra.tar.gz"
expect_failure "extra artifact" "$fixture/fetch-sources.sh" --verify
rm "$fixture/libs/extra.tar.gz"

printf 'partial\n' > "$fixture/alpha.tar.gz.part"
expect_failure "partial artifact" "$fixture/fetch-sources.sh" --verify
rm "$fixture/alpha.tar.gz.part"

sed '1s#https://#http://#' "$temp_root/SOURCE-URLS.original" > "$fixture/SOURCE-URLS.tsv"
expect_failure "plain HTTP inventory" "$fixture/fetch-sources.sh" --verify
cp "$temp_root/SOURCE-URLS.original" "$fixture/SOURCE-URLS.tsv"

printf 'https://example.test/duplicate.tar.gz\talpha.tar.gz\n' \
  >> "$fixture/SOURCE-URLS.tsv"
expect_failure "duplicate URL destination" "$fixture/fetch-sources.sh" --verify
cp "$temp_root/SOURCE-URLS.original" "$fixture/SOURCE-URLS.tsv"

printf 'https://example.test/alpha.tar.gz\t../alpha.tar.gz\n' \
  > "$fixture/SOURCE-URLS.tsv"
tail -n +2 "$temp_root/SOURCE-URLS.original" >> "$fixture/SOURCE-URLS.tsv"
expect_failure "traversal destination" "$fixture/fetch-sources.sh" --verify
cp "$temp_root/SOURCE-URLS.original" "$fixture/SOURCE-URLS.tsv"

head -n 1 "$fixture/SHA256SUMS" >> "$fixture/SHA256SUMS"
expect_failure "duplicate checksum destination" "$fixture/fetch-sources.sh" --verify
cp "$temp_root/SHA256SUMS.original" "$fixture/SHA256SUMS"

expect_failure "provenance overwrite" \
  "$fixture/fetch-sources.sh" --candidate "$fixture/SHA256SUMS"
"$fixture/fetch-sources.sh" --candidate "$fixture/SHA256SUMS.candidate" >/dev/null
[ -s "$fixture/SHA256SUMS.candidate" ] || fail "candidate manifest was not created"
cmp -s "$fixture/SHA256SUMS" "$temp_root/SHA256SUMS.original" ||
  fail "candidate generation changed committed SHA256SUMS"

# A future source-set inventory must be able to produce a separate candidate even though the
# prior committed checksum path set no longer matches it.
printf 'https://example.test/alpha.tar.gz\tgamma.tar.gz\n' > "$fixture/SOURCE-URLS.tsv"
tail -n +2 "$temp_root/SOURCE-URLS.original" >> "$fixture/SOURCE-URLS.tsv"
mv "$fixture/alpha.tar.gz" "$fixture/gamma.tar.gz"
"$fixture/fetch-sources.sh" --candidate "$fixture/SHA256SUMS.future" >/dev/null
grep -q '  gamma.tar.gz$' "$fixture/SHA256SUMS.future" ||
  fail "future-inventory candidate omitted its new path"
mv "$fixture/gamma.tar.gz" "$fixture/alpha.tar.gz"
cp "$temp_root/SOURCE-URLS.original" "$fixture/SOURCE-URLS.tsv"

cat > "$fakebin/curl" <<'FAKE_CURL'
#!/bin/sh
set -eu
initial_https=0
redirect_https=0
tls_min=0
config_disabled=0
output=
url=
while [ "$#" -gt 0 ]; do
  case "$1" in
    --proto)
      shift
      [ "$1" = "=https" ] || exit 91
      initial_https=1
      ;;
    --proto-redir)
      shift
      [ "$1" = "=https" ] || exit 92
      redirect_https=1
      ;;
    --tlsv1.2) tls_min=1 ;;
    --disable) config_disabled=1 ;;
    --output)
      shift
      output=$1
      ;;
    https://*) url=$1 ;;
  esac
  shift
done
[ "$initial_https" -eq 1 ] && [ "$redirect_https" -eq 1 ] &&
  [ "$tls_min" -eq 1 ] && [ "$config_disabled" -eq 1 ]
[ -n "$output" ] && [ -n "$url" ]
case "$url" in
  */alpha.tar.gz) cp "$FETCH_FIXTURE_DOWNLOADS/alpha.tar.gz" "$output" ;;
  */beta.tar.xz) cp "$FETCH_FIXTURE_DOWNLOADS/libs/beta.tar.xz" "$output" ;;
  *) exit 93 ;;
esac
FAKE_CURL
chmod +x "$fakebin/curl"

printf 'old alpha\n' > "$fixture/alpha.tar.gz"
printf 'old beta\n' > "$fixture/libs/beta.tar.xz"
FETCH_FIXTURE_DOWNLOADS="$downloads" PATH="$fakebin:$PATH" \
  "$fixture/fetch-sources.sh" --fetch >/dev/null
cmp -s "$fixture/alpha.tar.gz" "$downloads/alpha.tar.gz" ||
  fail "verified top-level download was not promoted"
cmp -s "$fixture/libs/beta.tar.xz" "$downloads/libs/beta.tar.xz" ||
  fail "verified library download was not promoted"

printf 'intercepted bytes\n' > "$downloads/alpha.tar.gz"
before_alpha=$(hash_file "$fixture/alpha.tar.gz")
before_beta=$(hash_file "$fixture/libs/beta.tar.xz")
expect_failure "download checksum mismatch" env \
  FETCH_FIXTURE_DOWNLOADS="$downloads" PATH="$fakebin:$PATH" \
  "$fixture/fetch-sources.sh" --fetch
[ "$(hash_file "$fixture/alpha.tar.gz")" = "$before_alpha" ] ||
  fail "failed staged fetch changed the existing top-level artifact"
[ "$(hash_file "$fixture/libs/beta.tar.xz")" = "$before_beta" ] ||
  fail "failed staged fetch changed the existing library artifact"
cmp -s "$fixture/SHA256SUMS" "$temp_root/SHA256SUMS.original" ||
  fail "fetch path changed committed SHA256SUMS"

echo "test-fetch-sources: OK"
