package office import ( "crypto/hmac" "crypto/sha256" "encoding/base64" "encoding/json" "strings" "testing" ) // Die drei Stellen, an denen ein Fehler **still** wäre: eine Signatur, die // nicht stimmt (der Server sagt nur „-8"), eine Fehlerseite, die als PDF // abgelegt wird, und eine Ergebnisadresse, die woanders hinzeigt. func TestSignJWTIsVerifiable(t *testing.T) { const secret = "geheimnis-mit-mindestens-32-zeichen!!" token := signJWT(secret, map[string]any{"outputtype": "pdf", "key": "abc"}) parts := strings.Split(token, ".") if len(parts) != 3 { t.Fatalf("Token hat %d Teile, erwartet 3", len(parts)) } mac := hmac.New(sha256.New, []byte(secret)) mac.Write([]byte(parts[0] + "." + parts[1])) want := base64.RawURLEncoding.EncodeToString(mac.Sum(nil)) if parts[2] != want { t.Errorf("Signatur stimmt nicht:\n ist: %s\n soll: %s", parts[2], want) } head, err := base64.RawURLEncoding.DecodeString(parts[0]) if err != nil { t.Fatalf("Kopf nicht dekodierbar: %v", err) } var h map[string]string if err := json.Unmarshal(head, &h); err != nil { t.Fatalf("Kopf ist kein JSON: %v", err) } if h["alg"] != "HS256" { t.Errorf("alg = %q, erwartet HS256", h["alg"]) } body, _ := base64.RawURLEncoding.DecodeString(parts[1]) var claims map[string]any if err := json.Unmarshal(body, &claims); err != nil { t.Fatalf("Nutzlast ist kein JSON: %v", err) } if claims["key"] != "abc" || claims["outputtype"] != "pdf" { t.Errorf("Nutzlast fehlt oder ist verändert: %v", claims) } // Ohne Ablauf gälte ein abgefangenes Token für immer. if _, ok := claims["exp"]; !ok { t.Error("exp fehlt – das Token liefe nie ab") } // Die eigene Signatur darf nicht Teil der signierten Nutzlast sein. if _, ok := claims["token"]; ok { t.Error("die Signatur signiert sich selbst mit") } } func TestReadPDFRejectsNonPDF(t *testing.T) { cases := map[string]string{ "Fehlerseite": "500 Internal Server Error", "leer": "", "JSON-Fehler": `{"error":-4}`, "fast ein PDF": "%PD", } for name, body := range cases { if _, err := readPDF(strings.NewReader(body)); err == nil { t.Errorf("%s wurde als PDF angenommen", name) } } // Ein echtes PDF muss durchkommen – sonst prüft der Test nichts. if _, err := readPDF(strings.NewReader("%PDF-1.7\nInhalt")); err != nil { t.Errorf("echtes PDF abgelehnt: %v", err) } } func TestSameHostAsServerRefusesForeignAddresses(t *testing.T) { c := &Client{baseURL: "http://office:80"} // Die Ergebnisadresse kommt aus der Antwort eines anderen Servers. Genau // hier würde aus einer Umwandlung ein Zugriff auf einen fremden Dienst. foreign := []string{ "http://169.254.169.254/latest/meta-data/", "http://localhost:8080/api/v1/admin/users", "http://office.evil.example.com/x.pdf", "file:///etc/passwd", "://kaputt", } for _, raw := range foreign { if err := c.sameHostAsServer(raw); err == nil { t.Errorf("fremde Adresse angenommen: %s", raw) } } for _, ok := range []string{ "http://office/cache/files/x.pdf", "http://office:80/cache/files/x.pdf", "http://OFFICE/cache/files/x.pdf", // Groß-/Kleinschreibung zählt nicht } { if err := c.sameHostAsServer(ok); err != nil { t.Errorf("eigene Adresse abgelehnt: %s (%v)", ok, err) } } } func TestSanitizeFilename(t *testing.T) { cases := map[string]string{ `Rechnung "2026".docx`: "Rechnung 2026", // Zeilenumbrüche raus – sie könnten eine zweite Kopfzeile erfinden. "kaputt\r\nInjected: x.odt": "kaputtInjected: x", ".odt": ".odt", "": "document", } for in, want := range cases { if got := sanitizeFilename(in); got != want { t.Errorf("sanitizeFilename(%q) = %q, erwartet %q", in, got, want) } } }