Umbau-Projekt (SSR und Rest)
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestSafeReturn deckt die Open-Redirect-Abwehr ab. Jede hier durchgelassene
|
||||
// Eingabe landet später in einem Location-Header -- die abgelehnten Fälle sind
|
||||
// deshalb wichtiger als die erlaubten.
|
||||
func TestSafeReturn(t *testing.T) {
|
||||
const fallback = "/"
|
||||
|
||||
erlaubt := []string{
|
||||
"/",
|
||||
"/u/alice",
|
||||
"/u/alice?page=3",
|
||||
"/?page=2#e123",
|
||||
"/u/alice?q=%22katzen%22&page=1#e42",
|
||||
"/e/123",
|
||||
}
|
||||
for _, v := range erlaubt {
|
||||
if got := safeReturn(v, fallback); got != v {
|
||||
t.Errorf("safeReturn(%q) = %q, erwartet unverändert", v, got)
|
||||
}
|
||||
}
|
||||
|
||||
abgelehnt := map[string]string{
|
||||
"leer": "",
|
||||
"protokoll-relativ": "//evil.example/x",
|
||||
"drei slashes": "///evil.example",
|
||||
"backslash-variante": `/\evil.example`,
|
||||
"absolute https": "https://evil.example",
|
||||
"absolute http": "http://evil.example",
|
||||
"javascript-schema": "javascript:alert(1)",
|
||||
"data-schema": "data:text/html,<script>",
|
||||
"relativ ohne slash": "u/alice",
|
||||
"header-injection CR": "/x\r\nSet-Cookie: a=b",
|
||||
"header-injection LF": "/x\nSet-Cookie: a=b",
|
||||
"nullbyte": "/x\x00y",
|
||||
"zu lang": "/" + strings.Repeat("a", 512),
|
||||
"backslash am anfang": `\/evil.example`,
|
||||
"leerzeichen davor": " /u/alice",
|
||||
}
|
||||
for name, v := range abgelehnt {
|
||||
if got := safeReturn(v, fallback); got != fallback {
|
||||
t.Errorf("%s: safeReturn(%q) = %q, erwartet Fallback %q", name, v, got, fallback)
|
||||
}
|
||||
}
|
||||
|
||||
// Der Fallback wird durchgereicht, nicht hartkodiert.
|
||||
if got := safeReturn("//evil.example", "/u/bob"); got != "/u/bob" {
|
||||
t.Errorf("eigener Fallback: bekam %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestReturnTo prüft die beiden Quellen: bei GET die aufgerufene URL (damit
|
||||
// Filter und Seitenzahl automatisch mitreisen), bei POST das mitgeschickte
|
||||
// Feld (damit ein neu gerendertes Formular sein Ziel behält).
|
||||
func TestReturnTo(t *testing.T) {
|
||||
get := httptest.NewRequest("GET", "/u/alice?page=3&q=katzen", nil)
|
||||
if got, want := returnTo(get), "/u/alice?page=3&q=katzen"; got != want {
|
||||
t.Errorf("GET: returnTo = %q, erwartet %q", got, want)
|
||||
}
|
||||
|
||||
// Eine bösartige URL kann per GET gar nicht erst entstehen (der Pfad kommt
|
||||
// vom Router), aber die Prüfung muss trotzdem greifen.
|
||||
odd := httptest.NewRequest("GET", "http://example.com//evil.example", nil)
|
||||
if got := returnTo(odd); got != "/" {
|
||||
t.Errorf("GET mit // : returnTo = %q, erwartet /", got)
|
||||
}
|
||||
|
||||
post := httptest.NewRequest("POST", "/entry/1/vote",
|
||||
strings.NewReader("return_to=%2Fu%2Falice%3Fpage%3D3%23e42&mode=left"))
|
||||
post.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
if got, want := returnTo(post), "/u/alice?page=3#e42"; got != want {
|
||||
t.Errorf("POST: returnTo = %q, erwartet %q", got, want)
|
||||
}
|
||||
|
||||
evil := httptest.NewRequest("POST", "/entry/1/vote",
|
||||
strings.NewReader("return_to=https%3A%2F%2Fevil.example"))
|
||||
evil.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
if got := returnTo(evil); got != "/" {
|
||||
t.Errorf("POST mit fremder URL: returnTo = %q, erwartet /", got)
|
||||
}
|
||||
|
||||
// Ohne Feld: Fallback, nicht die POST-Ziel-URL.
|
||||
bare := httptest.NewRequest("POST", "/entry/1/vote", nil)
|
||||
if got := returnTo(bare); got != "/" {
|
||||
t.Errorf("POST ohne return_to: returnTo = %q, erwartet /", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRedirectBack stellt sicher, dass die Umleitung nach einer Aktion ein 303
|
||||
// ist (GET auf das Ziel, Reload wiederholt die Aktion nicht) und dass ein
|
||||
// manipuliertes return_to nicht im Location-Header landet.
|
||||
func TestRedirectBack(t *testing.T) {
|
||||
post := func(body string) *httptest.ResponseRecorder {
|
||||
r := httptest.NewRequest("POST", "/entry/1/vote", strings.NewReader(body))
|
||||
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
w := httptest.NewRecorder()
|
||||
redirectBack(w, r, "/")
|
||||
return w
|
||||
}
|
||||
|
||||
w := post("return_to=%2Fu%2Falice%23e42")
|
||||
if w.Code != 303 {
|
||||
t.Errorf("Status = %d, erwartet 303", w.Code)
|
||||
}
|
||||
if got, want := w.Header().Get("Location"), "/u/alice#e42"; got != want {
|
||||
t.Errorf("Location = %q, erwartet %q", got, want)
|
||||
}
|
||||
|
||||
w = post("return_to=%2F%2Fevil.example")
|
||||
if got := w.Header().Get("Location"); got != "/" {
|
||||
t.Errorf("manipuliertes Ziel: Location = %q, erwartet /", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user