package api import ( "bytes" "encoding/json" "mime/multipart" "net/http/httptest" "os" "path/filepath" "strings" "testing" ) func TestLibraryCreateListUpload(t *testing.T) { _, h := setupAPI(t) tok := adminToken(t, h) root := t.TempDir() w := do(h, "POST", "/api/libraries", tok, map[string]string{"name": "comics", "root_path": root}) if w.Code != 201 { t.Fatalf("create lib %d %s", w.Code, w.Body) } var lib map[string]any json.Unmarshal(w.Body.Bytes(), &lib) libID := itoa(lib["id"]) w = do(h, "GET", "/api/libraries", tok, nil) if !strings.Contains(w.Body.String(), `"comics"`) { t.Fatalf("list: %s", w.Body) } // 相对路径 root 必须 400(前缀校验的根) w = do(h, "POST", "/api/libraries", tok, map[string]string{"name": "x", "root_path": "relative/path"}) if w.Code != 400 { t.Fatalf("relative root want 400 got %d", w.Code) } // 上传:白名单 + 防穿越 + 原子落盘 body, mw := uploadBody("my 01.cbz", []byte("zipbytes")) req := httptest.NewRequest("POST", "/api/libraries/"+libID+"/upload", body) req.Header.Set("Content-Type", mw.FormDataContentType()) req.Header.Set("Authorization", "Bearer "+tok) ww := httptest.NewRecorder() h.ServeHTTP(ww, req) if ww.Code != 202 { t.Fatalf("upload %d %s", ww.Code, ww.Body) } if _, err := os.Stat(filepath.Join(root, "my 01.cbz")); err != nil { t.Fatal("uploaded file missing:", err) } body, mw = uploadBody("../../evil.cbz", []byte("x")) req = httptest.NewRequest("POST", "/api/libraries/"+libID+"/upload", body) req.Header.Set("Content-Type", mw.FormDataContentType()) req.Header.Set("Authorization", "Bearer "+tok) ww = httptest.NewRecorder() h.ServeHTTP(ww, req) if ww.Code != 202 { // 名字被清洗成 evil.cbz,落在 root 内 t.Fatalf("sanitize upload %d", ww.Code) } if _, err := os.Stat(filepath.Join(root, "evil.cbz")); err != nil { t.Fatal("evil upload not sanitized") } body, mw = uploadBody("virus.exe", []byte("x")) req = httptest.NewRequest("POST", "/api/libraries/"+libID+"/upload", body) req.Header.Set("Content-Type", mw.FormDataContentType()) req.Header.Set("Authorization", "Bearer "+tok) ww = httptest.NewRecorder() h.ServeHTTP(ww, req) if ww.Code != 400 { t.Fatalf("bad ext want 400 got %d", ww.Code) } } func uploadBody(filename string, content []byte) (*bytes.Buffer, *multipart.Writer) { buf := &bytes.Buffer{} mw := multipart.NewWriter(buf) fw, _ := mw.CreateFormFile("file", filename) fw.Write(content) mw.Close() return buf, mw }