返回 DeepSeek-Reasonix
workspace_media_security_test.go
根目录 / desktop / workspace_media_security_test.go
1 package main
2
3 import (
4 "net/http"
5 "net/http/httptest"
6 "os"
7 "path/filepath"
8 "runtime"
9 "strings"
10 "testing"
11 )
12
13 func TestMediaTokenHandlerRejectsFileReplacedAfterAuthorization(t *testing.T) {
14 orig, _ := os.Getwd()
15 defer os.Chdir(orig)
16
17 parent := t.TempDir()
18 workspace := filepath.Join(parent, "workspace")
19 if err := os.Mkdir(workspace, 0o755); err != nil {
20 t.Fatal(err)
21 }
22 if err := os.Chdir(workspace); err != nil {
23 t.Fatal(err)
24 }
25 if err := os.WriteFile("shot.png", []byte("authorized"), 0o644); err != nil {
26 t.Fatal(err)
27 }
28
29 app := NewApp()
30 preview := app.ReadFile("shot.png")
31 if preview.URL == "" {
32 t.Fatal("expected media token URL")
33 }
34 outside := filepath.Join(parent, "outside.png")
35 if err := os.WriteFile(outside, []byte("outside-secret"), 0o644); err != nil {
36 t.Fatal(err)
37 }
38 if err := os.Remove("shot.png"); err != nil {
39 t.Fatal(err)
40 }
41 if err := os.Symlink(outside, "shot.png"); err != nil {
42 if runtime.GOOS == "windows" {
43 t.Skipf("symlink unavailable: %v", err)
44 }
45 t.Fatal(err)
46 }
47
48 handler := app.workspaceMediaMiddleware()(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
49 t.Error("fallback handler should not be called")
50 }))
51 rec := httptest.NewRecorder()
52 handler.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, preview.URL, nil))
53 if rec.Code != http.StatusNotFound {
54 t.Fatalf("replaced token response = %d, want 404", rec.Code)
55 }
56 if strings.Contains(rec.Body.String(), "outside-secret") {
57 t.Fatal("replacement target escaped the authorized media identity")
58 }
59 }
60
61 func TestMarkdownMediaTokenRevalidatesInPlaceContent(t *testing.T) {
62 for _, tc := range []struct {
63 name string
64 replacement []byte
65 }{
66 {name: "pixel budget", replacement: markdownImageTestPNGConfig(10_000, 4_001)},
67 {name: "byte budget", replacement: make([]byte, remoteMarkdownImageMaxBytes+1)},
68 } {
69 t.Run(tc.name, func(t *testing.T) {
70 original, _ := os.Getwd()
71 defer os.Chdir(original)
72 workspace := t.TempDir()
73 if err := os.Chdir(workspace); err != nil {
74 t.Fatal(err)
75 }
76 if err := os.WriteFile("shot.png", markdownImageTestPNG, 0o644); err != nil {
77 t.Fatal(err)
78 }
79 before, err := os.Stat("shot.png")
80 if err != nil {
81 t.Fatal(err)
82 }
83
84 app := NewApp()
85 preview := app.ResolveMarkdownImageForTab("", "shot.png")
86 if preview.URL == "" || preview.ErrorCode != "" {
87 t.Fatalf("markdown image token = %+v", preview)
88 }
89 handler := app.workspaceMediaMiddleware()(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
90 t.Error("fallback handler should not be called")
91 }))
92 valid := httptest.NewRecorder()
93 handler.ServeHTTP(valid, httptest.NewRequest(http.MethodGet, preview.URL, nil))
94 if valid.Code != http.StatusOK || valid.Body.String() != string(markdownImageTestPNG) {
95 t.Fatalf("validated markdown image response = %d, body=%q", valid.Code, valid.Body.String())
96 }
97 if err := os.WriteFile("shot.png", tc.replacement, 0o644); err != nil {
98 t.Fatal(err)
99 }
100 after, err := os.Stat("shot.png")
101 if err != nil {
102 t.Fatal(err)
103 }
104 if !os.SameFile(before, after) {
105 t.Fatal("test replacement must preserve the authorized file identity")
106 }
107
108 rec := httptest.NewRecorder()
109 handler.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, preview.URL, nil))
110 if rec.Code != http.StatusRequestEntityTooLarge {
111 t.Fatalf("in-place replacement response = %d, want 413; body=%q", rec.Code, rec.Body.String())
112 }
113 })
114 }
115 }
116
117 func TestMarkdownMediaTokenBindsAuthorizedIdentity(t *testing.T) {
118 workspace := t.TempDir()
119 path := filepath.Join(workspace, "shot.png")
120 if err := os.WriteFile(path, markdownImageTestPNG, 0o644); err != nil {
121 t.Fatal(err)
122 }
123 authorized, err := os.Stat(path)
124 if err != nil {
125 t.Fatal(err)
126 }
127 replacementPath := filepath.Join(workspace, "replacement.png")
128 if err := os.WriteFile(replacementPath, markdownImageTestPNGConfig(10_000, 4_001), 0o644); err != nil {
129 t.Fatal(err)
130 }
131 replacement, err := os.Stat(replacementPath)
132 if err != nil {
133 t.Fatal(err)
134 }
135 if os.SameFile(authorized, replacement) {
136 t.Fatal("test replacement must have a different file identity")
137 }
138 if err := os.Remove(path); err != nil {
139 t.Fatal(err)
140 }
141 if err := os.Rename(replacementPath, path); err != nil {
142 t.Fatal(err)
143 }
144
145 app := NewApp()
146 token := app.ensureMediaTokenStore().createMarkdownImage(path, "shot.png", "image/png", authorized)
147 handler := app.workspaceMediaMiddleware()(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
148 t.Error("fallback handler should not be called")
149 }))
150 rec := httptest.NewRecorder()
151 handler.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/__reasonix_workspace_media/"+token+"/shot.png", nil))
152 if rec.Code != http.StatusNotFound {
153 t.Fatalf("replacement identity response = %d, want 404", rec.Code)
154 }
155 }
156
157 func TestMediaTokenHandlerSelectsPercentFilenameExactly(t *testing.T) {
158 original, _ := os.Getwd()
159 defer os.Chdir(original)
160 dir := t.TempDir()
161 if err := os.Chdir(dir); err != nil {
162 t.Fatal(err)
163 }
164 files := map[string]string{
165 "raw%20name.pdf": "literal-percent",
166 "raw name.pdf": "space",
167 "100%.pdf": "percent-sign",
168 "中文#.pdf": "unicode-hash",
169 }
170 app := NewApp()
171 handler := app.workspaceMediaMiddleware()(http.NotFoundHandler())
172 for name, body := range files {
173 if err := os.WriteFile(name, []byte(body), 0o644); err != nil {
174 t.Fatal(err)
175 }
176 preview := app.ReadFile(name)
177 if preview.Err != "" || preview.URL == "" {
178 t.Fatalf("preview %q = %+v", name, preview)
179 }
180 recorder := httptest.NewRecorder()
181 handler.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, preview.URL, nil))
182 if recorder.Code != http.StatusOK || recorder.Body.String() != body {
183 t.Fatalf("GET %q = %d %q, want exact %q", preview.URL, recorder.Code, recorder.Body.String(), body)
184 }
185 }
186 }
187
187 lines GO