package main

import (
	"bytes"
	"crypto/subtle"
	"encoding/json"
	"errors"
	"fmt"
	"html/template"
	"io"
	"net/http"
	"net/http/httptest"
	"net/netip"
	"net/url"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"sync/atomic"
	texttemplate "text/template"
)

func render() (string, string, error) {
	const source = `<p>{{.}}</p>`
	input := "<b>guest</b>"
	var raw, safe bytes.Buffer
	if err := texttemplate.Must(texttemplate.New("raw").Parse(source)).Execute(&raw, input); err != nil {
		return "", "", err
	}
	if err := template.Must(template.New("safe").Parse(source)).Execute(&safe, input); err != nil {
		return "", "", err
	}
	return raw.String(), safe.String(), nil
}

// A deliberately narrow destination policy. 192.0.2.10 is a public TEST FIXTURE
// address from TEST-NET-1; nothing here dials it or resolves external DNS.
func allowedTarget(raw string, resolved []netip.Addr) bool {
	u, err := url.Parse(raw)
	if err != nil || u.Scheme != "https" || u.User != nil || u.Fragment != "" || u.Hostname() != "assets.example" || (u.Port() != "" && u.Port() != "443") {
		return false
	}
	if len(resolved) == 0 {
		return false
	}
	expected := netip.MustParseAddr("192.0.2.10")
	for _, ip := range resolved {
		if ip.Unmap() != expected {
			return false
		}
	}
	return true
}
func csrfAllowed(origin, got, sessionToken string) bool {
	return origin == "https://app.example" && len(sessionToken) >= 32 && len(got) == len(sessionToken) && subtle.ConstantTimeCompare([]byte(got), []byte(sessionToken)) == 1
}

type Input struct {
	Name string `json:"name"`
}

func decode(raw string) error {
	if len(raw) > 128 {
		return errors.New("body too large")
	}
	d := json.NewDecoder(strings.NewReader(raw))
	d.DisallowUnknownFields()
	var in Input
	if err := d.Decode(&in); err != nil {
		return err
	}
	var extra any
	if err := d.Decode(&extra); err != io.EOF {
		return errors.New("expected one JSON value")
	}
	if len(in.Name) == 0 || len(in.Name) > 20 {
		return errors.New("invalid name")
	}
	return nil
}
func destination(key string) (string, bool) {
	p, ok := map[string]string{"account": "/account", "home": "/"}[key]
	return p, ok
}
func pathChecks() (bool, bool, error) {
	dir, err := os.MkdirTemp("", "path-boundary-")
	if err != nil {
		return false, false, err
	}
	defer os.RemoveAll(dir)
	root := filepath.Join(dir, "root")
	if err = os.Mkdir(root, 0700); err != nil {
		return false, false, err
	}
	if err = os.WriteFile(filepath.Join(dir, "outside.txt"), []byte("public fixture"), 0600); err != nil {
		return false, false, err
	}
	if err = os.Symlink("../outside.txt", filepath.Join(root, "escape")); err != nil {
		return false, false, err
	}
	r, err := os.OpenRoot(root)
	if err != nil {
		return false, false, err
	}
	defer r.Close()
	f, e1 := r.Open("../outside.txt")
	if e1 == nil {
		f.Close()
	}
	g, e2 := r.Open("escape")
	if e2 == nil {
		g.Close()
	}
	return e1 != nil, e2 != nil, nil
}
func redirectChecks() (bool, int32, error) {
	var hits atomic.Int32
	target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { hits.Add(1); w.WriteHeader(204) }))
	defer target.Close()
	source := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, target.URL, http.StatusFound) }))
	defer source.Close()
	c := &http.Client{CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
	resp, err := c.Get(source.URL)
	if err != nil {
		return false, hits.Load(), err
	}
	defer resp.Body.Close()
	return resp.StatusCode == http.StatusFound, hits.Load(), nil
}
func literalArg() (string, error) {
	b, err := exec.Command("/usr/bin/printf", "%s", "hello; printf INJECTED").Output()
	return string(b), err
}
func main() {
	raw, safe, err := render()
	must(err)
	fmt.Println("text:", raw)
	fmt.Println("html:", safe)
	fmt.Println("approved-address:", allowedTarget("https://assets.example/a", []netip.Addr{netip.MustParseAddr("192.0.2.10")}))
	fmt.Println("changed-dns:", allowedTarget("https://assets.example/a", []netip.Addr{netip.MustParseAddr("127.0.0.1")}))
	blocked, hits, err := redirectChecks()
	must(err)
	fmt.Printf("redirect-stopped=%v target-hits=%d\n", blocked, hits)
	dot, link, err := pathChecks()
	must(err)
	fmt.Printf("dotdot-rejected=%v symlink-rejected=%v\n", dot, link)
	arg, err := literalArg()
	must(err)
	fmt.Println("literal-argument:", arg)
	token := strings.Repeat("t", 32)
	fmt.Printf("csrf-good=%v csrf-foreign=%v\n", csrfAllowed("https://app.example", token, token), csrfAllowed("https://other.example", token, token))
	fmt.Printf("unknown-field-rejected=%v trailing-json-rejected=%v\n", decode(`{"name":"a","role":"admin"}`) != nil, decode(`{"name":"a"}{"name":"b"}`) != nil)
	_, ok := destination("https://other.example")
	fmt.Println("external-redirect-rejected:", !ok)
}
func must(err error) {
	if err != nil {
		panic(err)
	}
}
