初始化蓝鸟文件快递柜源码与部署文档

This commit is contained in:
lhc committed 2026-10-05 23:41:07 +08:00
commit 5643d22242
70 files changed
+18669

No files matched your search

+227
View File
@@ -0,0 +1,227 @@
package app
import (
"bytes"
"encoding/json"
"net/http/cookiejar"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"strings"
"testing"
"github.com/pelletier/go-toml/v2"
)
func TestMultipleAccessOriginsAndProtocolCookies(t *testing.T) {
c := fixture(t, true)
c.a.cfg.PublicURL = "https://files.example.com"
c.a.cfg.AccessURLs = []string{"http://files.example.com", "http://192.168.1.210:8080"}
c.a.cfg.SecureCookies = true
c.a.cfg.AllowLANHTTP = true
c.a.cfg.Listen = "192.168.1.210:8080"
if err := c.a.cfg.Validate(); err != nil {
t.Fatal(err)
}
jar, _ := cookiejar.New(nil)
request := func(base, method, path string, body any, hint bool) *httptest.ResponseRecorder {
var data []byte
if body != nil {
switch v := body.(type) {
case []byte:
data = v
default:
data, _ = json.Marshal(v)
}
}
r := httptest.NewRequest(method, base+"/api/v1"+path, bytes.NewReader(data))
r.TLS = nil // Real HTTPS is terminated on a separate proxy, which rewrites Host.
r.Host = "192.168.1.210:8080"
if base == "https://files.example.com" {
r.RemoteAddr = "192.168.1.60:1000"
} else if base == "http://files.example.com" {
r.RemoteAddr = "192.168.1.61:1000"
} else {
r.RemoteAddr = "192.168.1.62:1000"
}
if method != "GET" {
r.Header.Set("Origin", base)
}
if hint {
r.Header.Set("X-Bluebird-Origin", base)
}
u, _ := url.Parse(base + "/api/v1" + path)
for _, cookie := range jar.Cookies(u) {
r.AddCookie(cookie)
}
w := httptest.NewRecorder()
c.h.ServeHTTP(w, r)
jar.SetCookies(u, w.Result().Cookies())
return w
}
for _, base := range c.a.cfg.accessOrigins() {
w := request(base, "GET", "/policy", nil, true)
expect(t, w, 200)
var policy map[string]any
json.Unmarshal(w.Body.Bytes(), &policy)
if policy["public_url"] != base {
t.Fatal("policy forced a different origin")
}
cookies := w.Result().Cookies()
if len(cookies) != 1 {
t.Fatal("visitor cookie not returned")
}
https := strings.HasPrefix(base, "https:")
if cookies[0].Secure != https {
t.Fatalf("incorrect Secure flag for %s", base)
}
want := "bb_visitor"
if !https {
want += "_http"
}
if cookies[0].Name != want {
t.Fatal("HTTP can overwrite HTTPS cookie")
}
w = request(base, "POST", "/admin/login", map[string]string{"username": "admin", "password": "test-password"}, true)
expect(t, w, 200)
if w.Result().Cookies()[0].Secure != https {
t.Fatal("admin cookie protocol incorrect")
}
expect(t, request(base, "GET", "/admin/stats", nil, true), 200)
}
// HTTPS and HTTP on the same public hostname retain independent sessions.
u, _ := url.Parse("http://files.example.com/api/v1/admin/stats")
for _, cookie := range jar.Cookies(u) {
if cookie.Name == "bb_admin" {
t.Fatal("HTTPS credential sent over HTTP")
}
}
payload := []byte("available with failed public DNS")
w := request("http://192.168.1.210:8080", "POST", "/uploads", Manifest{Files: []ManifestFile{{Name: "offline.txt", Size: int64(len(payload))}}, Days: 7, Downloads: 8, Renew: true}, true)
expect(t, w, 201)
var parcel struct {
ID string
Files []struct{ ID string }
}
json.Unmarshal(w.Body.Bytes(), &parcel)
expect(t, request("http://192.168.1.210:8080", "PUT", "/uploads/"+parcel.ID+"/files/"+parcel.Files[0].ID, payload, true), 200)
w = request("http://192.168.1.210:8080", "POST", "/uploads/"+parcel.ID+"/publish", map[string]any{}, true)
expect(t, w, 200)
var share struct {
Code, URL string
URLs []map[string]string
}
json.Unmarshal(w.Body.Bytes(), &share)
if !strings.HasPrefix(share.URL, "http://192.168.1.210:8080/") || len(share.URLs) != 3 {
t.Fatal("share did not offer independent LAN/public URLs")
}
for _, base := range c.a.cfg.accessOrigins() {
expect(t, request(base, "POST", "/pickup/resolve", map[string]string{"code": share.Code}, true), 200)
// Navigation downloads have neither Origin nor the frontend access hint.
w = request(base, "GET", "/pickup/files/"+parcel.Files[0].ID+"/download", nil, false)
expect(t, w, 200)
if !bytes.Equal(w.Body.Bytes(), payload) {
t.Fatal("cross-entry download failed")
}
}
// The same hostname can hold different pickup grants on HTTP and HTTPS.
other := []byte("a different public HTTP parcel")
w = request("http://files.example.com", "POST", "/uploads", Manifest{Files: []ManifestFile{{Name: "other.txt", Size: int64(len(other))}}, Days: 7, Downloads: 4}, true)
expect(t, w, 201)
var second struct {
ID string
Files []struct{ ID string }
}
json.Unmarshal(w.Body.Bytes(), &second)
expect(t, request("http://files.example.com", "PUT", "/uploads/"+second.ID+"/files/"+second.Files[0].ID, other, true), 200)
w = request("http://files.example.com", "POST", "/uploads/"+second.ID+"/publish", map[string]any{}, true)
expect(t, w, 200)
var otherShare struct{ Code string }
json.Unmarshal(w.Body.Bytes(), &otherShare)
expect(t, request("https://files.example.com", "POST", "/pickup/resolve", map[string]string{"code": share.Code}, true), 200)
expect(t, request("http://files.example.com", "POST", "/pickup/resolve", map[string]string{"code": otherShare.Code}, true), 200)
w = request("https://files.example.com", "GET", "/pickup/files/"+parcel.Files[0].ID+"/download", nil, false)
expect(t, w, 200)
if !bytes.Equal(w.Body.Bytes(), payload) {
t.Fatal("HTTP grant shadowed the HTTPS grant")
}
w = request("http://files.example.com", "GET", "/pickup/files/"+second.Files[0].ID+"/download", nil, false)
expect(t, w, 200)
if !bytes.Equal(w.Body.Bytes(), other) {
t.Fatal("HTTPS grant shadowed the HTTP grant")
}
for _, header := range []struct{ origin, hint string }{{"https://evil.example", ""}, {"", "http://attacker.example"}, {"https://files.example.com", "http://files.example.com"}, {"http://192.168.1.211:8080", ""}} {
r := httptest.NewRequest("POST", "http://192.168.1.210:8080/api/v1/admin/login", strings.NewReader(`{"username":"admin","password":"test-password"}`))
r.Header.Set("Origin", header.origin)
r.Header.Set("X-Bluebird-Origin", header.hint)
out := httptest.NewRecorder()
c.h.ServeHTTP(out, r)
expect(t, out, 403)
}
// Neither Host nor forwarded headers can inject a foreign share URL.
r := httptest.NewRequest("GET", "http://attacker.example/api/v1/policy", nil)
r.RemoteAddr = "203.0.113.4:4000"
r.Header.Set("X-Forwarded-Host", "evil.example")
r.Header.Set("X-Forwarded-Proto", "http")
out := httptest.NewRecorder()
c.h.ServeHTTP(out, r)
expect(t, out, 200)
if strings.Contains(out.Body.String(), "evil.example") || strings.Contains(out.Body.String(), "attacker.example") {
t.Fatal("untrusted forwarding/Host used as site URL")
}
}
func TestAccessConfigurationValidationAndAtomicUpdate(t *testing.T) {
cfg := DefaultConfig()
cfg.PublicURL = "https://files.example.com"
cfg.SecureCookies = true
cfg.AccessURLs = []string{"http://files.example.com", "http://192.168.1.210:8080"}
if cfg.Validate() == nil {
t.Fatal("private HTTP accepted without explicit opt-in")
}
cfg.AllowLANHTTP = true
if err := cfg.Validate(); err != nil {
t.Fatal(err)
}
for _, bad := range []string{"null", "https://", "https://:443", "http://user:pass@files.example.com", "http://files.example.com/path", "http://files.example.com?x=1", "ftp://files.example.com", "https://files.example.com:70000"} {
cfg.AccessURLs = []string{bad}
if cfg.Validate() == nil {
t.Fatalf("accepted bad origin %q", bad)
}
}
cfg.AccessURLs = nil
directory := t.TempDir()
path := filepath.Join(directory, "config.toml")
original, _ := toml.Marshal(cfg)
if err := os.WriteFile(path, original, 0640); err != nil {
t.Fatal(err)
}
if err := UpdateAccessConfig(path, "192.168.1.210:8080", []string{"http://files.example.com", "http://192.168.1.210:8080"}, true); err != nil {
t.Fatal(err)
}
saved, err := LoadConfig(path)
if err != nil {
t.Fatal(err)
}
if len(saved.AccessURLs) != 2 || saved.PublicURL != cfg.PublicURL || saved.Encryption != cfg.Encryption || saved.FileBudget != cfg.FileBudget || saved.Listen != "192.168.1.210:8080" {
t.Fatal("access update reset existing settings")
}
if _, err = os.Stat(filepath.Join(directory, "var")); !os.IsNotExist(err) {
t.Fatal("access configuration opened runtime data")
}
updated, _ := os.ReadFile(path)
if err = UpdateAccessConfig(path, "", []string{"http://evil.example/path"}, false); err == nil {
t.Fatal("invalid access update accepted")
}
intact, _ := os.ReadFile(path)
if !bytes.Equal(updated, intact) {
t.Fatal("failed update changed live configuration")
}
info, _ := os.Stat(path)
if info.Mode().Perm() != 0640 {
t.Fatal("configuration permissions changed")
}
if _, _, err = LANListen("http://files.example.com:8080"); err == nil {
t.Fatal("LAN flag accepted DNS-dependent domain")
}
}