package gzev import ( "bytes" "context" "errors" "mime/multipart" "net/http/httptest" "os" "path/filepath" "strings" "testing" ) func uploadRequest(t *testing.T, h *UploadHandler, origin, code string) *httptest.ResponseRecorder { t.Helper() var body bytes.Buffer form := multipart.NewWriter(&body) form.WriteField("csrf", h.csrf) form.WriteField("token", "new-session") form.WriteField("recovery_code", code) form.Close() req := httptest.NewRequest("POST", "https://vehicle.d.lnoneos.com/gzev/session/", &body) req.Header.Set("Content-Type", form.FormDataContentType()) req.Header.Set("Origin", origin) rec := httptest.NewRecorder() h.ServeHTTP(rec, req) return rec } func TestPublicUploadRequiresCodeAndConsumesIt(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "session.json") h, _ := NewUploadHandler(path, "expected") h.PublicOrigin = "https://vehicle.d.lnoneos.com" h.RecoveryPath = path + ".recovery.json" calls := 0 h.Verify = func(_ context.Context, s Session, account string) error { calls++ if account != "expected" || s.Authorization != "Bearer new-session" { t.Fatal("wrong account or session") } return nil } if r := uploadRequest(t, h, h.PublicOrigin, ""); r.Code != 422 || calls != 0 { t.Fatal("missing code reached verification") } code, err := EnsureRecoveryCode(h.RecoveryPath) if err != nil { t.Fatal(err) } for _, origin := range []string{"https://attacker.invalid", "null", ""} { if r := uploadRequest(t, h, origin, code); r.Code != 403 || calls != 0 { t.Fatalf("untrusted origin %q accepted", origin) } } if r := uploadRequest(t, h, h.PublicOrigin, code); r.Code != 200 { t.Fatalf("upload failed: %d %s", r.Code, r.Body.String()) } session, err := ReadSession(path) if err != nil || session.Authorization != "Bearer new-session" { t.Fatal("session was not securely saved") } if r := uploadRequest(t, h, h.PublicOrigin, code); r.Code != 422 || calls != 1 { t.Fatal("reused code accepted") } } func TestInvalidSessionDoesNotOverwriteOrConsume(t *testing.T) { path := filepath.Join(t.TempDir(), "session.json") os.WriteFile(path, []byte("original"), 0600) h, _ := NewUploadHandler(path, "expected") h.PublicOrigin = "https://vehicle.d.lnoneos.com" h.RecoveryPath = path + ".recovery.json" h.Verify = func(context.Context, Session, string) error { return errors.New("expired credential") } code, _ := EnsureRecoveryCode(h.RecoveryPath) r := uploadRequest(t, h, h.PublicOrigin, code) if r.Code != 422 { t.Fatal(r.Code) } b, _ := os.ReadFile(path) if string(b) != "original" { t.Fatal("overwrote valid previous file") } if _, err := os.Stat(h.RecoveryPath); err != nil { t.Fatal("failed upload consumed recovery code") } if strings.Contains(r.Body.String(), "new-session") { t.Fatal("response leaked token") } } func TestUploadRejectsUntrustedHostAndHidesToken(t *testing.T) { h, _ := NewUploadHandler(filepath.Join(t.TempDir(), "session.json"), "expected") r := httptest.NewRecorder() h.ServeHTTP(r, httptest.NewRequest("GET", "http://attacker.invalid/", nil)) if r.Code != 403 { t.Fatal(r.Code) } r = httptest.NewRecorder() h.ServeHTTP(r, httptest.NewRequest("GET", "http://127.0.0.1:20312/", nil)) if r.Code != 200 || r.Header().Get("Cache-Control") != "no-store" || r.Header().Get("Referrer-Policy") != "same-origin" || !strings.Contains(r.Body.String(), "验证并更新") { t.Fatal("upload UI unavailable") } }