Files
ipecho/main_test.go
zzgandClaude Opus 5.5 0edc63fd2f Serve all output types on i.z.gd; redirect i.zk.gd to it
i.z.gd now takes ?output=txt|json|xml|csv|html (?nice kept as an
alias for html). i.zk.gd becomes a plain nginx 301 to i.z.gd, so the
service no longer routes on Host.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 10:15:13 -07:00

88 lines
2.7 KiB
Go

package main
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
)
const evilUA = `Test/1.0 (a,b) <script>alert(1)</script>`
func get(t *testing.T, host, target, remote, realIP string) *httptest.ResponseRecorder {
t.Helper()
r := httptest.NewRequest("GET", target, nil)
r.Host = host
r.RemoteAddr = remote
r.Header.Set("User-Agent", evilUA)
if realIP != "" {
r.Header.Set("X-Real-IP", realIP)
}
w := httptest.NewRecorder()
handler().ServeHTTP(w, r)
return w
}
func TestClientIPTrustsOnlyLoopback(t *testing.T) {
w := get(t, "i.z.gd", "/", "127.0.0.1:5000", "192.0.2.7")
if !strings.HasPrefix(w.Body.String(), "192.0.2.7\n") {
t.Fatalf("loopback proxy: got %q", w.Body.String())
}
w = get(t, "i.z.gd", "/", "198.51.100.9:5000", "192.0.2.7")
if !strings.HasPrefix(w.Body.String(), "198.51.100.9\n") {
t.Fatalf("spoofed header honoured: got %q", w.Body.String())
}
}
func TestPlainTextIsNotHTML(t *testing.T) {
w := get(t, "i.z.gd", "/", "127.0.0.1:1", "192.0.2.7")
if ct := w.Header().Get("Content-Type"); !strings.HasPrefix(ct, "text/plain") {
t.Fatalf("content-type %q", ct)
}
if lines := strings.Split(w.Body.String(), "\n"); len(lines) != 4 || lines[2] != evilUA {
t.Fatalf("body %q", w.Body.String())
}
}
func TestNiceEscapesUserAgent(t *testing.T) {
w := get(t, "i.z.gd", "/?nice", "127.0.0.1:1", "192.0.2.7")
b := w.Body.String()
if strings.Contains(b, "<script>") || !strings.Contains(b, "&lt;script&gt;") {
t.Fatalf("UA not escaped: %s", b)
}
if !strings.Contains(b, "192.0.2.7<br><br>") {
t.Fatalf("layout changed: %s", b)
}
}
func TestOutputs(t *testing.T) {
cases := map[string][]string{
"/?output=json": {"application/json", `"ip":"192.0.2.7"`, "\"user-agent\":\"Test/1.0 (a,b) \\u003cscript\\u003e"},
"/?output=xml": {"text/xml", "<ip>192.0.2.7</ip>", "&lt;script&gt;"},
"/?output=csv": {"text/csv", `192.0.2.7,`, `"Test/1.0 (a,b) <script>alert(1)</script>"`},
"/?output=txt": {"text/plain", "192.0.2.7\n"},
"/": {"text/plain", "192.0.2.7\n"},
"/index.php": {"text/plain", "192.0.2.7\n"},
"/?output=html": {"text/html", "192.0.2.7<br><br>"},
}
for target, want := range cases {
w := get(t, "i.z.gd", target, "127.0.0.1:1", "192.0.2.7")
if ct := w.Header().Get("Content-Type"); !strings.HasPrefix(ct, want[0]) {
t.Errorf("%s: content-type %q", target, ct)
}
for _, s := range want[1:] {
if !strings.Contains(w.Body.String(), s) {
t.Errorf("%s: missing %q in %q", target, s, w.Body.String())
}
}
}
}
func TestUnknownPathsAndHosts(t *testing.T) {
for _, c := range [][2]string{{"i.z.gd", "/foo"}, {"i.z.gd", "/.env"}} {
if w := get(t, c[0], c[1], "127.0.0.1:1", ""); w.Code != http.StatusNotFound {
t.Errorf("%v: got %d", c, w.Code)
}
}
}