Files
book-comic-library/backend/cmd/webui/handlers/libraries_test.go
T

91 lines
3.0 KiB
Go

package handlers_test
import (
"bytes"
"encoding/json"
"mime/multipart"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
)
func TestLibraryCreateListUpload(t *testing.T) {
_, _, h, booksDir := setupAPI(t)
tok := adminToken(t, h)
root := filepath.Join(booksDir, "comics") // 服务端自动拼接 BooksDir/<清洗后的库名>
w := do(h, "POST", "/api/libraries", tok, map[string]string{"name": "comics"})
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)
if lib["root_path"] != root {
t.Fatalf("root_path want %q got %v", root, lib["root_path"])
}
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 仍在 booksDir 内
w = do(h, "POST", "/api/libraries", tok, map[string]string{"name": "../../etc/passwd"})
if w.Code != 201 || !strings.Contains(w.Body.String(), filepath.Join(booksDir, "passwd")) {
t.Fatalf("traversal name want sanitized 201 got %d %s", w.Code, w.Body)
}
// "." / ".." 清洗后非法 → 400
w = do(h, "POST", "/api/libraries", tok, map[string]string{"name": ".."})
if w.Code != 400 {
t.Fatalf("'..' want 400 got %d", w.Code)
}
// 重名(同 root)→ 409
w = do(h, "POST", "/api/libraries", tok, map[string]string{"name": "comics"})
if w.Code != 409 {
t.Fatalf("dup want 409 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
}