#!/usr/bin/env bash

# Safe mode loads project config without trust, and project config can choose
# the host a request goes to. The operator's credentials must not follow it
# there: a project api_url gets no forge token, a project version_list_url no
# netrc credentials, and a project [tool_alias] cannot redirect a tool that
# global config declares.

export MISE_USE_VERSIONS_HOST=0
export GITHUB_TOKEN=ghp-operator-token
export MISE_GITHUB_ENTERPRISE_TOKEN=ghe-operator-token
export GITLAB_TOKEN=glpat-operator-token
mkdir -p served
echo "1.0.0" >served/versions.txt
start_http_server --dir "$PWD/served" --headers-log "$PWD/logs"
server="http://127.0.0.1:$HTTP_PORT"

# a cached version list would skip the request under test
reset_logs() {
  rm -rf logs
  mise cache clear >/dev/null 2>&1
}

echo "=== a project api_url gets no forge token ==="
for tool in "github:owner/repo|$server/api/v3" "gitlab:owner/repo|$server/api/v4"; do
  name="${tool%%|*}"
  api_url="${tool#*|}"
  cat <<EOF >mise.toml
[tools]
"$name" = { version = "latest", api_url = "$api_url" }
EOF
  reset_logs
  MISE_SAFE=1 mise ls-remote "$name" >/dev/null 2>&1 || true
  assert_succeed "test -d logs"
  assert_fail "grep -rqs operator-token logs"
  # without safe mode the config is trusted here, so the token goes along
  reset_logs
  mise ls-remote "$name" >/dev/null 2>&1 || true
  assert_succeed "grep -rqs operator-token logs"
done

echo "=== the same through a project tool alias ==="
cat <<EOF >mise.toml
[tool_alias]
aliased = "github:owner/repo[api_url=$server/api/v3]"

[tools]
aliased = "latest"
EOF
reset_logs
MISE_SAFE=1 mise ls-remote aliased >/dev/null 2>&1 || true
assert_succeed "test -d logs"
assert_fail "grep -rqs operator-token logs"

echo "=== an api_url in global config keeps its token ==="
rm mise.toml
cat <<EOF >"$MISE_CONFIG_DIR/config.toml"
[tools]
"github:owner/repo" = { version = "latest", api_url = "$server/api/v3" }
EOF
reset_logs
MISE_SAFE=1 mise ls-remote github:owner/repo >/dev/null 2>&1 || true
assert_succeed "grep -rqs operator-token logs"
rm "$MISE_CONFIG_DIR/config.toml"

echo "=== a project version_list_url gets no netrc credentials ==="
printf 'machine 127.0.0.1\nlogin netrc-user\npassword netrc-secret\n' >"$HOME/.netrc"
chmod 600 "$HOME/.netrc"
basic="$(printf 'netrc-user:netrc-secret' | base64)"
cat <<EOF >mise.toml
[tools]
"http:netrc-tool" = { version = "latest", url = "$server/tool-{{ version }}", version_list_url = "$server/versions.txt" }
EOF
reset_logs
assert_contains "MISE_SAFE=1 mise ls-remote http:netrc-tool" "1.0.0"
assert_fail "grep -rqs -- '$basic' logs"
reset_logs
assert_contains "mise ls-remote http:netrc-tool" "1.0.0"
assert_succeed "grep -rqs -- '$basic' logs"
# a tool in global config on the same host keeps its credentials for the URL
# the operator configured, while the project's request still goes without
cat <<EOF >"$MISE_CONFIG_DIR/config.toml"
[tools]
"http:global-tool" = { version = "latest", url = "$server/tool-{{ version }}", version_list_url = "$server/global-versions.txt" }
EOF
cp served/versions.txt served/global-versions.txt
reset_logs
assert_contains "MISE_SAFE=1 mise ls-remote http:global-tool" "1.0.0"
assert_succeed "grep -rqs -- '$basic' logs"
reset_logs
assert_contains "MISE_SAFE=1 mise ls-remote http:netrc-tool" "1.0.0"
assert_fail "grep -rqs -- '$basic' logs"

# the same goes for the url a tool downloads from, templated or not
printf '#!/bin/sh\necho dl\n' >served/dl-tool-1.0.0
cp served/dl-tool-1.0.0 served/global-dl-1.0.0
cat <<EOF >mise.toml
[tools]
"http:dl-tool" = { version = "1.0.0", url = "$server/dl-tool-{{ version }}" }
EOF
cat <<EOF >"$MISE_CONFIG_DIR/config.toml"
[tools]
"http:global-dl" = { version = "1.0.0", url = "$server/global-dl-{{ version }}" }
EOF
reset_logs
assert_succeed "MISE_SAFE=1 mise install http:dl-tool"
assert_succeed "grep -rqs dl-tool-1.0.0 logs"
assert_fail "grep -rqs -- '$basic' logs"
reset_logs
assert_succeed "MISE_SAFE=1 mise install http:global-dl"
assert_succeed "grep -rqs -- '$basic' logs"

# What decides is which config declares the tool, not how its URL compares to
# global ones: a project URL under a global tool's URL gets no credentials...
cat <<EOF >mise.toml
[tools]
"http:probe" = { version = "latest", url = "$server/x-{{ version }}", version_list_url = "$server/global-dl-private.env" }
EOF
echo "1.0.0" >served/global-dl-private.env
reset_logs
assert_contains "MISE_SAFE=1 mise ls-remote http:probe" "1.0.0"
assert_fail "grep -rqs -- '$basic' logs"
# ...while global tools keep theirs whatever the project names, also when their
# own URL has a templated host or is written differently
cat <<EOF >mise.toml
[tools]
"http:dl-any" = { version = "1.0.0", url = "http://{{ os(linux='127.0.0.1', macos='127.0.0.1') }}:$HTTP_PORT/dl-tool-{{ version }}" }
EOF
cat <<EOF >"$MISE_CONFIG_DIR/config.toml"
[tools]
"http:global-host" = { version = "1.0.0", url = "http://{{ os(linux='127.0.0.1', macos='127.0.0.1') }}:$HTTP_PORT/global-dl-{{ version }}" }
"http:global-case" = { version = "1.0.0", url = "HTTP://127.0.0.1:$HTTP_PORT/global-dl-{{ version }}" }
EOF
reset_logs
assert_succeed "MISE_SAFE=1 mise install http:dl-any"
assert_fail "grep -rqs -- '$basic' logs"
for tool in global-host global-case; do
  reset_logs
  assert_succeed "MISE_SAFE=1 mise install http:$tool"
  assert_succeed "grep -rqs global-dl-1.0.0 logs"
  assert_succeed "grep -rqs -- '$basic' logs"
done
rm "$MISE_CONFIG_DIR/config.toml"

# a forge request goes without a token to a project api_url, and must not fall
# back to netrc there either
for tool in "github:owner/repo|$server/api/v3" "gitlab:owner/repo|$server/api/v4"; do
  name="${tool%%|*}"
  cat <<EOF >mise.toml
[tools]
"$name" = { version = "latest", api_url = "${tool#*|}" }
EOF
  reset_logs
  MISE_SAFE=1 mise ls-remote "$name" >/dev/null 2>&1 || true
  assert_succeed "test -d logs"
  assert_fail "grep -rqs -- '$basic' logs"
  assert_fail "grep -rqs operator-token logs"
done
# whichever command resolves or installs it: the latest version, or an exact one
cat <<EOF >mise.toml
[tools]
"github:owner/repo" = { version = "latest", api_url = "$server/api/v3" }
EOF
for request in latest 1.0.0; do
  reset_logs
  MISE_SAFE=1 mise install "github:owner/repo@$request" >/dev/null 2>&1 || true
  assert_succeed "test -d logs"
  assert_fail "grep -rqs -- '$basic' logs"
done

# a project tool counts whatever its version or its platform options look like
cat <<'EOF' >"$MISE_CONFIG_DIR/config.toml"
[vars]
dlver = "1.0.0"
EOF
cat <<EOF >mise.toml
[tools]
"http:dl-vars" = { version = "{{ vars.dlver }}", url = "$server/dl-tool-{{ version }}" }
"http:dl-plat" = { version = "1.0.0", platforms_linux_x64_url = "$server/dl-tool-{{ version }}", platforms_linux_arm64_url = "$server/dl-tool-{{ version }}", platforms_macos_x64_url = "$server/dl-tool-{{ version }}", platforms_macos_arm64_url = "$server/dl-tool-{{ version }}" }
EOF
for tool in dl-vars dl-plat; do
  reset_logs
  assert_succeed "MISE_SAFE=1 mise install http:$tool"
  assert_succeed "grep -rqs dl-tool-1.0.0 logs"
  assert_fail "grep -rqs -- '$basic' logs"
done

# a global tool keeps its credentials in the tasks its backend spawns: the go
# backend queries GOPROXY from tasks of their own, here on a host a project
# tool also names
cat <<'EOF' >"$MISE_CONFIG_DIR/config.toml"
[tools]
"go:example.com/tool" = "latest"
EOF
cat <<EOF >mise.toml
[tools]
"http:dl-tool" = { version = "1.0.0", url = "$server/dl-tool-{{ version }}" }
EOF
reset_logs
GOPROXY="$server/goproxy" MISE_SAFE=1 mise ls-remote go:example.com/tool >/dev/null 2>&1 || true
assert_succeed "grep -rqs goproxy/example.com/tool logs"
assert_succeed "grep -rqs -- '$basic' logs"
rm "$HOME/.netrc" mise.toml "$MISE_CONFIG_DIR/config.toml"

echo "=== a project tool alias cannot redirect a tool from global config ==="
cat <<'EOF' >"$MISE_CONFIG_DIR/config.toml"
[tools]
tiny = "1.0.0"
EOF
cat <<'EOF' >mise.toml
[tool_alias]
tiny = "http:evil-tiny"
EOF
assert_contains "mise tool tiny" "http:evil-tiny"
assert_not_contains "MISE_SAFE=1 mise tool tiny" "http:evil-tiny"
# an alias for a tool only the project declares still works
cat <<'EOF' >mise.toml
[tool_alias]
own-tool = "http:own-tool"

[tools]
own-tool = { version = "1.0.0", url = "https://example.invalid/own-tool" }
EOF
assert_contains "MISE_SAFE=1 mise tool own-tool" "http:own-tool"
