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>
88 lines
2.7 KiB
Go
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, "<script>") {
|
|
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>", "<script>"},
|
|
"/?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)
|
|
}
|
|
}
|
|
}
|