109 lines
3.6 KiB
Go
109 lines
3.6 KiB
Go
package main
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"testing/fstest"
|
|
)
|
|
|
|
func newTestPublicApp(t *testing.T, downloads string) (*publicApp, http.Handler) {
|
|
t.Helper()
|
|
web := fstest.MapFS{
|
|
"app.html": &fstest.MapFile{Data: []byte("public app")},
|
|
"app.js": &fstest.MapFile{Data: []byte("js")},
|
|
"styles.css": &fstest.MapFile{Data: []byte("css")},
|
|
}
|
|
app, err := newPublicApp("https://lrc.flechazo.xin", downloads, web, fakeQQ{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return app, app.routes()
|
|
}
|
|
|
|
func publicRequest(method, path, body string) *http.Request {
|
|
request := httptest.NewRequest(method, "https://lrc.flechazo.xin"+path, strings.NewReader(body))
|
|
request.Header.Set("Origin", "https://lrc.flechazo.xin")
|
|
request.Header.Set("Content-Type", "application/json")
|
|
request.Header.Set("X-Real-IP", "203.0.113.7")
|
|
return request
|
|
}
|
|
|
|
func TestPublicPageNeedsNoSession(t *testing.T) {
|
|
_, handler := newTestPublicApp(t, "")
|
|
w := httptest.NewRecorder()
|
|
handler.ServeHTTP(w, publicRequest(http.MethodGet, "/", ""))
|
|
if w.Code != http.StatusOK || w.Body.String() != "public app" {
|
|
t.Fatalf("status=%d body=%q", w.Code, w.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestPublicAPIRejectsWrongOrigin(t *testing.T) {
|
|
_, handler := newTestPublicApp(t, "")
|
|
request := publicRequest(http.MethodPost, "/api/search", `{"keyword":"test","limit":2}`)
|
|
request.Header.Set("Origin", "https://attacker.example")
|
|
w := httptest.NewRecorder()
|
|
handler.ServeHTTP(w, request)
|
|
if w.Code != http.StatusForbidden {
|
|
t.Fatalf("expected 403, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestPublicSearchWorksWithoutCookie(t *testing.T) {
|
|
_, handler := newTestPublicApp(t, "")
|
|
w := httptest.NewRecorder()
|
|
handler.ServeHTTP(w, publicRequest(http.MethodPost, "/api/search", `{"keyword":"测试","limit":2}`))
|
|
if w.Code != http.StatusOK || !strings.Contains(w.Body.String(), "测试") {
|
|
t.Fatalf("status=%d body=%s", w.Code, w.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestPublicPerClientRateLimit(t *testing.T) {
|
|
_, handler := newTestPublicApp(t, "")
|
|
for attempt := 1; attempt <= publicClientLimit+1; attempt++ {
|
|
w := httptest.NewRecorder()
|
|
handler.ServeHTTP(w, publicRequest(http.MethodPost, "/api/search", `{"keyword":"test","limit":1}`))
|
|
if attempt <= publicClientLimit && w.Code != http.StatusOK {
|
|
t.Fatalf("attempt %d unexpectedly returned %d", attempt, w.Code)
|
|
}
|
|
if attempt == publicClientLimit+1 && w.Code != http.StatusTooManyRequests {
|
|
t.Fatalf("expected final attempt to return 429, got %d", w.Code)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPublicDownloadWhitelistAndRange(t *testing.T) {
|
|
directory := t.TempDir()
|
|
name := "lrc-local-windows-amd64.exe"
|
|
if err := os.WriteFile(filepath.Join(directory, name), []byte("0123456789"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_, handler := newTestPublicApp(t, directory)
|
|
request := publicRequest(http.MethodGet, "/download/"+name, "")
|
|
request.Header.Set("Range", "bytes=2-5")
|
|
w := httptest.NewRecorder()
|
|
handler.ServeHTTP(w, request)
|
|
if w.Code != http.StatusPartialContent || w.Body.String() != "2345" {
|
|
t.Fatalf("status=%d body=%q", w.Code, w.Body.String())
|
|
}
|
|
if !strings.Contains(w.Header().Get("Content-Disposition"), name) {
|
|
t.Fatal("download disposition is missing")
|
|
}
|
|
|
|
w = httptest.NewRecorder()
|
|
handler.ServeHTTP(w, publicRequest(http.MethodGet, "/download/not-allowed", ""))
|
|
if w.Code != http.StatusNotFound {
|
|
t.Fatalf("unexpected non-whitelist status %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestPublicOriginConfigurationMustUseHTTPS(t *testing.T) {
|
|
_, err := newPublicApp("http://lrc.flechazo.xin", "", fstest.MapFS{}, fakeQQ{})
|
|
if err == nil {
|
|
t.Fatal("expected insecure public origin to be rejected")
|
|
}
|
|
}
|