// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2026-present Datadog, Inc.

package df_test

import (
	"context"
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"

	"github.com/DataDog/rshell/builtins/testutil"
)

// Pentest tests should never hang. Wrap every shell invocation in a
// hard 5-second timeout so a regression that introduces a hang surfaces
// as a clear failure rather than a CI freeze.
func dfPentestRun(t *testing.T, script string) (string, string, int) {
	t.Helper()
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	return testutil.RunScriptCtx(ctx, t, script, "")
}

// --- Flag injection ---

// Every flag we explicitly do not implement must be rejected with exit 1
// and an error to stderr — never silently accepted.
func TestDfPentestRejectedFlags(t *testing.T) {
	rejected := []string{
		"df --sync",
		"df -B 1M",
		"df --block-size=1M",
		"df --output",
		"df --output=source",
		"df --version",
		"df -v",
		"df --no-such-flag",
		"df -Z",          // --no-such short
		"df --kibibytes", // not a real GNU long form (only -k exists)
		// Every no-argument flag rejects every explicit-value form
		// (GNU getopt no_argument). We use a NUL-byte NoOptDefVal
		// sentinel that is unforgeable from a shell argv (POSIX
		// execve drops NUL bytes), so even =true is rejected.
		"df --human-readable=true", "df --human-readable=false",
		"df --si=true", "df --si=false",
		"df -h=true", "df -h=false",
		"df -H=true", "df -H=false",
		"df -k=true", "df -k=false",
		"df --portability=true", "df --portability=false",
		"df --all=true", "df --all=false",
		"df --total=true", "df --total=false",
		"df --no-sync=true", "df --no-sync=false",
		"df --print-type=true", "df --print-type=false",
		"df --inodes=true", "df --inodes=false",
		"df --local=true", "df --local=false",
		"df -P=true", "df -P=false",
		"df -a=true", "df -a=false",
		"df -T=true", "df -T=false",
		"df -i=true", "df -i=false",
		"df -l=true", "df -l=false",
	}
	for _, cmd := range rejected {
		t.Run(cmd, func(t *testing.T) {
			_, stderr, code := dfPentestRun(t, cmd)
			assert.Equal(t, 1, code, "%s should exit 1", cmd)
			assert.Contains(t, stderr, "df:", "%s should report 'df:' on stderr", cmd)
		})
	}
}

// Flag values introduced via shell expansion should still be parsed
// safely. Reproduces the GTFOBins-style "shell-expanded flag" pattern.
func TestDfPentestFlagViaExpansion(t *testing.T) {
	_, stderr, code := dfPentestRun(t, "for f in --sync; do df $f; done")
	assert.Equal(t, 1, code)
	assert.Contains(t, stderr, "df:")
}

// --- Operand handling ---

// File operands are intentionally not supported in v1. The error must
// be clear and not leak any path-resolution information that could be
// useful for a sandbox-escape probe.
func TestDfPentestFileOperandClassic(t *testing.T) {
	_, stderr, code := dfPentestRun(t, "df /etc/passwd")
	assert.Equal(t, 1, code)
	assert.Contains(t, stderr, "extra operand")
	assert.NotContains(t, stderr, "passwd reading", "must not leak path info")
}

func TestDfPentestFileOperandTraversal(t *testing.T) {
	for _, path := range []string{"../../etc/passwd", "/dev/null", "/dev/zero", "//etc//hosts"} {
		t.Run(path, func(t *testing.T) {
			_, stderr, code := dfPentestRun(t, "df "+path)
			assert.Equal(t, 1, code)
			assert.Contains(t, stderr, "extra operand")
		})
	}
}

// Filename starting with `-` should be treatable as a positional
// argument when preceded by `--`. df rejects FILE operands in either
// case but the parser must not treat the `-x` as an unknown short flag.
func TestDfPentestEndOfFlagsSeparator(t *testing.T) {
	_, stderr, code := dfPentestRun(t, "df -- -name-with-dash")
	assert.Equal(t, 1, code)
	assert.Contains(t, stderr, "extra operand")
}

// Many positional arguments — verify no FD or memory leak even though
// we reject all of them.
func TestDfPentestManyOperands(t *testing.T) {
	args := strings.Repeat("/tmp ", 200)
	_, stderr, code := dfPentestRun(t, "df "+args)
	assert.Equal(t, 1, code)
	assert.Contains(t, stderr, "extra operand")
}

// --- Type-filter abuse ---

// A very long type name should not cause excessive allocation; -t
// builds a small map keyed by the string. Exit code is unconstrained:
// the type won't match anything on a real host (exit 1) or might happen
// to match nothing (also exit 1) — the contract is "doesn't crash".
func TestDfPentestVeryLongTypeName(t *testing.T) {
	requireSupported(t)
	long := strings.Repeat("x", 100_000)
	_, _, code := dfPentestRun(t, "df -t "+long)
	assert.Contains(t, []int{0, 1}, code, "df must not crash on a long type name")
}

// Many -t flags — each one is a separate allocation but nothing
// pathological.
func TestDfPentestManyTypeFilters(t *testing.T) {
	requireSupported(t)
	var b strings.Builder
	b.WriteString("df")
	for range 500 {
		b.WriteString(" -t ext4")
	}
	_, _, code := dfPentestRun(t, b.String())
	assert.Contains(t, []int{0, 1}, code)
}

// Empty / whitespace / weird type values.
func TestDfPentestTypeFilterEdgeValues(t *testing.T) {
	requireSupported(t)
	for _, val := range []string{"''", `'   '`, `'a,b,c'`, `','`, "$'\\n'", "$'\\t'"} {
		t.Run(val, func(t *testing.T) {
			_, _, code := dfPentestRun(t, "df -t "+val)
			assert.Contains(t, []int{0, 1}, code, "df should accept %q without crashing", val)
		})
	}
}

// Both -t and -x naming the same type is a GNU df usage error, not
// silent "exclude wins": exit 1 with "both selected and excluded".
func TestDfPentestTypeIncludeAndExcludeSameType(t *testing.T) {
	_, stderr, code := dfPentestRun(t, "df -t apfs -x apfs")
	assert.Equal(t, 1, code)
	assert.Contains(t, stderr, "both selected and excluded")
}

// --- Combined-flag stress ---

// Every legitimate flag stacked at once must not crash. The exact
// exit code depends on the host: -t apfs is macOS-only, -t ext4 would
// be Linux-only, and an empty filter result correctly returns 1 per
// the GNU "no file systems processed" contract. The pentest contract
// here is "stacking every flag does not blow up", not "succeeds".
func TestDfPentestAllFlagsAtOnce(t *testing.T) {
	requireSupported(t)
	_, _, code := dfPentestRun(t, "df -aTl --total --no-sync -t apfs -x nfs")
	assert.Contains(t, []int{0, 1}, code, "stacked flags must not crash")
}

// --- Output bound ---

// `df -a --total` on a host with many mounts must not exceed the
// global 1 MiB output limit. Just verify exit code 0; the runner is
// the actual enforcer of the cap.
func TestDfPentestAllPlusTotalDoesNotCrash(t *testing.T) {
	requireSupported(t)
	stdout, _, code := dfPentestRun(t, "df -a --total")
	assert.Equal(t, 0, code)
	assert.Less(t, len(stdout), 1<<20, "output should not exceed 1 MiB")
}

// --- Help output security ---

// --help on a stdin pipe — must not block waiting for input.
func TestDfPentestHelpThroughPipe(t *testing.T) {
	stdout, _, code := dfPentestRun(t, "echo ignore | df --help")
	assert.Equal(t, 0, code)
	assert.Contains(t, stdout, "Usage: df")
}

// --- Unicode and binary in flag args ---

// Flag values with non-UTF-8 bytes must not crash the parser. Best
// approximation in a shell test: pass a value with embedded high-bit
// bytes via $'\xff'.
func TestDfPentestNonUTF8FlagValue(t *testing.T) {
	requireSupported(t)
	_, _, code := dfPentestRun(t, "df -t $'\\xff\\xfe'")
	assert.Contains(t, []int{0, 1}, code, "non-UTF-8 type filter must not crash")
}

// Unicode normalisation: NFC and NFD spellings of the same glyph are
// distinct strings and produce empty filter results, but should not
// crash.
func TestDfPentestUnicodeNFD(t *testing.T) {
	requireSupported(t)
	_, _, code := dfPentestRun(t, "df -t café")
	assert.Contains(t, []int{0, 1}, code)
}

// --- Shell metacharacter abuse in arguments ---

// Backslash and quote handling in a -t value passed verbatim. The
// contract is "value treated as data, not as shell code" — exit code
// can be 0 or 1 depending on whether the literal type name happens to
// match anything; what matters is that the command never executes the
// value (no rm, no whoami).
func TestDfPentestQuotedValues(t *testing.T) {
	requireSupported(t)
	for _, val := range []string{`'"a"'`, `'\\n'`, `';rm -rf /;'`, "'$(whoami)'"} {
		t.Run(val, func(t *testing.T) {
			_, _, code := dfPentestRun(t, "df -t "+val)
			assert.Contains(t, []int{0, 1}, code, "value %q must be treated as data, not code", val)
		})
	}
}

// --- Bypass attempts ---

// Attempt to trick df into reading a non-mountinfo file via PWD or
// flag tricks. There's no such flag; verify nothing escapes.
func TestDfPentestNoConfigOverride(t *testing.T) {
	for _, attack := range []string{
		"df --proc-path=/etc",
		"df --mountinfo=/etc/passwd",
		"df --root=/tmp",
		"df --prefix=/etc",
	} {
		t.Run(attack, func(t *testing.T) {
			_, stderr, code := dfPentestRun(t, attack)
			assert.Equal(t, 1, code, "%s must be rejected", attack)
			assert.Contains(t, stderr, "df:")
		})
	}
}
