// 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 echo_test

import (
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

const pentestTimeout = 10 * time.Second

func mustNotHang(t *testing.T, fn func()) {
	t.Helper()
	done := make(chan struct{})
	go func() {
		fn()
		close(done)
	}()
	select {
	case <-done:
	case <-time.After(pentestTimeout):
		t.Fatal("operation did not complete within timeout")
	}
}

// --- Flag injection ---

func TestEchoPentestFlagInjectionViaExpansion(t *testing.T) {
	stdout, _, code := runScript(t, `flag="-n"; echo $flag hello`)
	assert.Equal(t, 0, code)
	assert.Equal(t, "hello", stdout)
}

func TestEchoPentestDoubleDashNotEndOfOptions(t *testing.T) {
	stdout, _, code := runScript(t, "echo -- -n hello")
	assert.Equal(t, 0, code)
	assert.Equal(t, "-- -n hello\n", stdout)
}

func TestEchoPentestHelpNotInterpreted(t *testing.T) {
	stdout, _, code := runScript(t, "echo --help")
	assert.Equal(t, 0, code)
	assert.Equal(t, "--help\n", stdout)
}

func TestEchoPentestVersionNotInterpreted(t *testing.T) {
	stdout, _, code := runScript(t, "echo --version")
	assert.Equal(t, 0, code)
	assert.Equal(t, "--version\n", stdout)
}

// --- Escape sequence edge cases ---

func TestEchoPentestEscapeAtEndOfString(t *testing.T) {
	// Trailing backslash with no character after it
	stdout, _, code := runScript(t, `echo -e 'hello\'`)
	assert.Equal(t, 0, code)
	assert.Equal(t, "hello\\\n", stdout)
}

func TestEchoPentestOctalOverflow(t *testing.T) {
	// \0777 = octal 777 = 511, wraps to byte 0xFF (511 & 0xFF)
	stdout, _, code := runScript(t, `echo -e '\0777'`)
	assert.Equal(t, 0, code)
	assert.Equal(t, "\xff\n", stdout)
}

func TestEchoPentestHexNoDigits(t *testing.T) {
	stdout, _, code := runScript(t, `echo -e '\xGG'`)
	assert.Equal(t, 0, code)
	assert.Equal(t, "\\xGG\n", stdout)
}

func TestEchoPentestUnicodeNoDigits(t *testing.T) {
	stdout, _, code := runScript(t, `echo -e '\uZZZZ'`)
	assert.Equal(t, 0, code)
	assert.Equal(t, "\\uZZZZ\n", stdout)
}

func TestEchoPentestUnicode8NoDigits(t *testing.T) {
	stdout, _, code := runScript(t, `echo -e '\UZZZZZZZZ'`)
	assert.Equal(t, 0, code)
	assert.Equal(t, "\\UZZZZZZZZ\n", stdout)
}

func TestEchoPentestBackslashCMidArg(t *testing.T) {
	// \c in the middle of an arg suppresses everything after
	stdout, _, code := runScript(t, `echo -e 'ab\cde' 'more'`)
	assert.Equal(t, 0, code)
	assert.Equal(t, "ab", stdout)
}

func TestEchoPentestBackslashCAlone(t *testing.T) {
	// \c as the only content = empty output
	stdout, _, code := runScript(t, `echo -e '\c'`)
	assert.Equal(t, 0, code)
	assert.Equal(t, "", stdout)
}

// --- Memory / resource exhaustion ---

func TestEchoPentestManyArgs(t *testing.T) {
	var args []string
	for range 1000 {
		args = append(args, "arg")
	}
	mustNotHang(t, func() {
		stdout, _, code := runScript(t, "echo "+strings.Join(args, " "))
		assert.Equal(t, 0, code)
		assert.Equal(t, strings.Join(args, " ")+"\n", stdout)
	})
}

func TestEchoPentestLargeArg(t *testing.T) {
	large := strings.Repeat("x", 100000)
	mustNotHang(t, func() {
		stdout, _, code := runScript(t, "echo "+large)
		assert.Equal(t, 0, code)
		assert.Equal(t, large+"\n", stdout)
	})
}

func TestEchoPentestManyEscapes(t *testing.T) {
	// Many escape sequences in one arg
	esc := strings.Repeat(`\n`, 500)
	mustNotHang(t, func() {
		stdout, _, code := runScript(t, `echo -e '`+esc+`'`)
		assert.Equal(t, 0, code)
		assert.Equal(t, strings.Repeat("\n", 500)+"\n", stdout)
	})
}

// --- Flag parsing boundary ---

func TestEchoPentestBareDash(t *testing.T) {
	stdout, _, code := runScript(t, "echo -")
	assert.Equal(t, 0, code)
	assert.Equal(t, "-\n", stdout)
}

func TestEchoPentestEmptyStringArg(t *testing.T) {
	stdout, _, code := runScript(t, `echo -n ""`)
	assert.Equal(t, 0, code)
	assert.Equal(t, "", stdout)
}

func TestEchoPentestRepeatedFlags(t *testing.T) {
	stdout, _, code := runScript(t, "echo -n -n -n hello")
	assert.Equal(t, 0, code)
	assert.Equal(t, "hello", stdout)
}

func TestEchoPentestAllFlagsNoText(t *testing.T) {
	// -n suppresses newline, -eE sets escapes off; no text args = empty output
	stdout, _, code := runScript(t, "echo -neE")
	assert.Equal(t, 0, code)
	assert.Equal(t, "", stdout)
}

func TestEchoPentestEscapesDisabledByDefault(t *testing.T) {
	stdout, _, code := runScript(t, `echo 'hello\nworld'`)
	assert.Equal(t, 0, code)
	assert.Equal(t, "hello\\nworld\n", stdout)
}

// --- Null byte handling ---

func TestEchoPentestOctalNullByte(t *testing.T) {
	stdout, _, code := runScript(t, `echo -e '\0'`)
	assert.Equal(t, 0, code)
	assert.Equal(t, "\x00\n", stdout)
}

func TestEchoPentestHexNullByte(t *testing.T) {
	stdout, _, code := runScript(t, `echo -e '\x00'`)
	assert.Equal(t, 0, code)
	assert.Equal(t, "\x00\n", stdout)
}

// --- Unicode edge cases (bidi override, out-of-range) ---

func TestEchoPentestUnicodeBidiOverride(t *testing.T) {
	// U+202E RIGHT-TO-LEFT OVERRIDE — can reverse displayed text for visual spoofing
	stdout, _, code := runScript(t, `echo -e '\u202E'`)
	assert.Equal(t, 0, code)
	assert.Equal(t, "\u202E\n", stdout)
}

func TestEchoPentestUnicodeBidiLRO(t *testing.T) {
	// U+202D LEFT-TO-RIGHT OVERRIDE
	stdout, _, code := runScript(t, `echo -e '\u202D'`)
	assert.Equal(t, 0, code)
	assert.Equal(t, "\u202D\n", stdout)
}

func TestEchoPentestUnicode8MaxValue(t *testing.T) {
	// \UFFFFFFFF is beyond U+10FFFF — bash silently drops, so we match that behavior
	stdout, _, code := runScript(t, `echo -e '\UFFFFFFFF'`)
	assert.Equal(t, 0, code)
	assert.Equal(t, "\n", stdout)
}

func TestEchoPentestUnicodeSurrogate(t *testing.T) {
	// U+D800 is a surrogate — Go's WriteRune replaces with U+FFFD
	stdout, _, code := runScript(t, `echo -e '\uD800'`)
	assert.Equal(t, 0, code)
	assert.Equal(t, "\uFFFD\n", stdout)
}
