f21fa5b5e8
Commilitia Drop:自托管的跨设备剪贴板同步与点对点文件传输。 - 后端 Go(chi / SQLite WAL / SSE Hub / WebRTC signaling + 状态机 / Relay ring buffer),编译进单个 distroless 镜像(前端 go:embed)。 - 前端 React + TanStack Router + Zustand,自实现 SSE + WebRTC P2P,NAT 受阻时回退服务端中继;聚珍(Juzhen)CJK 综合排版。 - 桌面端 Wails v2(macOS / Windows),瘦客户端复用 web。 - 鉴权 OIDC PKCE(自建 Casdoor 等),refresh_token 信封加密存系统密钥库;iOS Shortcut 用 HS256 scoped token。 架构文档与变更记录见 docs 分支(PROJECT_BRIEF / FRONTEND_DESIGN / CHANGELOG)。 本次为公开发布初始提交:完整开发历史(含部署细节)留存于私有归档,公开仓库自此干净起步。
110 lines
4.0 KiB
Go
110 lines
4.0 KiB
Go
package platform
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
const htmlDoc = "<html><head><title>cdrop</title></head><body></body></html>"
|
|
|
|
func htmlHandler() http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
w.WriteHeader(http.StatusOK)
|
|
_, _ = w.Write([]byte(htmlDoc))
|
|
})
|
|
}
|
|
|
|
func TestSessionInjectIntoDocument(t *testing.T) {
|
|
session := &LoginResult{
|
|
AccessToken: "acc-token",
|
|
RefreshToken: "ref-token",
|
|
User: UserInfo{ID: "u-1", Name: "alice"},
|
|
}
|
|
h := SessionInjectMiddleware(session, "my-host", "", "macos")(htmlHandler())
|
|
|
|
rec := httptest.NewRecorder()
|
|
h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/", nil))
|
|
body := rec.Body.String()
|
|
|
|
if !strings.Contains(body, "window.__CDROP_BOOT__") {
|
|
t.Fatalf("boot global not injected:\n%s", body)
|
|
}
|
|
if !strings.Contains(body, `"access_token":"acc-token"`) {
|
|
t.Errorf("access token not in injected payload:\n%s", body)
|
|
}
|
|
// Credential policy: the refresh_token must NEVER reach the page.
|
|
if strings.Contains(body, "ref-token") || strings.Contains(body, "refresh_token") {
|
|
t.Errorf("refresh_token must not be injected into the page:\n%s", body)
|
|
}
|
|
if !strings.Contains(body, `"device_name":"my-host"`) {
|
|
t.Errorf("device name not in injected payload:\n%s", body)
|
|
}
|
|
if !strings.Contains(body, `"device_type":"macos"`) {
|
|
t.Errorf("device type not in injected payload:\n%s", body)
|
|
}
|
|
// Injected before </head> so it runs before the deferred module bundle.
|
|
if strings.Index(body, "__CDROP_BOOT__") > strings.Index(body, "</head>") {
|
|
t.Error("injection must precede </head>")
|
|
}
|
|
}
|
|
|
|
func TestSessionInjectPassesThroughNonDocument(t *testing.T) {
|
|
apiBody := "streamed-api-body"
|
|
next := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
_, _ = w.Write([]byte(apiBody))
|
|
})
|
|
h := SessionInjectMiddleware(&LoginResult{AccessToken: "x"}, "h", "", "")(next)
|
|
|
|
rec := httptest.NewRecorder()
|
|
h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/api/hub/events", nil))
|
|
if rec.Body.String() != apiBody {
|
|
t.Errorf("non-document request must pass through unchanged, got %q", rec.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestSessionInjectNilSessionStillInjectsDeviceBoot(t *testing.T) {
|
|
// Logged out, the boot state is still injected — the page needs the device
|
|
// name / api_base / device_type before login — but with a null session, and
|
|
// no token of any kind leaks.
|
|
h := SessionInjectMiddleware(nil, "my-host", "http://127.0.0.1:5000", "windows")(htmlHandler())
|
|
rec := httptest.NewRecorder()
|
|
h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/", nil))
|
|
body := rec.Body.String()
|
|
|
|
if !strings.Contains(body, "window.__CDROP_BOOT__") {
|
|
t.Fatalf("boot global must be injected even when logged out:\n%s", body)
|
|
}
|
|
if !strings.Contains(body, `"session":null`) {
|
|
t.Errorf("logged-out boot must carry a null session:\n%s", body)
|
|
}
|
|
if !strings.Contains(body, `"device_name":"my-host"`) ||
|
|
!strings.Contains(body, `"device_type":"windows"`) ||
|
|
!strings.Contains(body, `"api_base":"http://127.0.0.1:5000"`) {
|
|
t.Errorf("device boot fields not injected:\n%s", body)
|
|
}
|
|
if strings.Contains(body, "access_token") {
|
|
t.Errorf("no token must appear when logged out:\n%s", body)
|
|
}
|
|
}
|
|
|
|
func TestSessionInjectEscapesScriptBreakout(t *testing.T) {
|
|
// A display name containing </script> must not break out of the inline tag.
|
|
session := &LoginResult{AccessToken: "acc", User: UserInfo{Name: "</script><b>x"}}
|
|
h := SessionInjectMiddleware(session, "h", "", "")(htmlHandler())
|
|
rec := httptest.NewRecorder()
|
|
h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/", nil))
|
|
body := rec.Body.String()
|
|
|
|
// The literal closing tag from the name must NOT appear verbatim; encoding/json
|
|
// HTML-escapes < / > to < / > by default.
|
|
if strings.Contains(body, "</script><b>x") {
|
|
t.Errorf("script breakout not escaped:\n%s", body)
|
|
}
|
|
if !strings.Contains(body, `</script>`) {
|
|
t.Errorf("expected HTML-escaped name in payload:\n%s", body)
|
|
}
|
|
}
|