@@ -134,3 +134,108 @@ func TestUpload_ValidFilename_Succeeds(t *testing.T) {
134134 require .NoError (t , err )
135135 require .Equal (t , "hello" , string (got ))
136136}
137+
138+ // GHSA-966r-mw4j-rv64: HTTP PUT opens the target with O_TRUNC, so overwriting an
139+ // existing file destroys its contents. Under --no-delete that must be blocked and
140+ // the file left intact.
141+ func TestPut_NoDelete_BlocksOverwrite (t * testing.T ) {
142+ webroot := t .TempDir ()
143+ fs , cleanup := newTestFileServer (t , webroot )
144+ defer cleanup ()
145+ fs .NoDelete = true
146+ require .NoError (t , os .WriteFile (filepath .Join (webroot , "victim.txt" ), []byte ("VICTIM" ), 0644 ))
147+
148+ r := httptest .NewRequest (http .MethodPut , "/victim.txt" , bytes .NewBufferString ("CLOBBERED" ))
149+ r .Header .Set ("X-CSRF-Token" , "test-csrf" )
150+ w := httptest .NewRecorder ()
151+
152+ fs .put (w , r )
153+
154+ require .Equal (t , http .StatusForbidden , w .Code )
155+ got , err := os .ReadFile (filepath .Join (webroot , "victim.txt" ))
156+ require .NoError (t , err )
157+ require .Equal (t , "VICTIM" , string (got ), "existing file must not be truncated/overwritten" )
158+ }
159+
160+ // --upload-only likewise forbids destroying existing content via PUT overwrite.
161+ func TestPut_UploadOnly_BlocksOverwrite (t * testing.T ) {
162+ webroot := t .TempDir ()
163+ fs , cleanup := newTestFileServer (t , webroot )
164+ defer cleanup ()
165+ fs .UploadOnly = true
166+ require .NoError (t , os .WriteFile (filepath .Join (webroot , "victim.txt" ), []byte ("VICTIM" ), 0644 ))
167+
168+ r := httptest .NewRequest (http .MethodPut , "/victim.txt" , bytes .NewBufferString ("CLOBBERED" ))
169+ r .Header .Set ("X-CSRF-Token" , "test-csrf" )
170+ w := httptest .NewRecorder ()
171+
172+ fs .put (w , r )
173+
174+ require .Equal (t , http .StatusForbidden , w .Code )
175+ got , err := os .ReadFile (filepath .Join (webroot , "victim.txt" ))
176+ require .NoError (t , err )
177+ require .Equal (t , "VICTIM" , string (got ))
178+ }
179+
180+ // Creating a new file via PUT destroys nothing and must stay allowed under
181+ // --no-delete. Confirms the overwrite guard does not over-block fresh writes.
182+ func TestPut_NoDelete_AllowsNewFile (t * testing.T ) {
183+ webroot := t .TempDir ()
184+ fs , cleanup := newTestFileServer (t , webroot )
185+ defer cleanup ()
186+ fs .NoDelete = true
187+
188+ r := httptest .NewRequest (http .MethodPut , "/fresh.txt" , bytes .NewBufferString ("NEW" ))
189+ r .Header .Set ("X-CSRF-Token" , "test-csrf" )
190+ w := httptest .NewRecorder ()
191+
192+ fs .put (w , r )
193+
194+ got , err := os .ReadFile (filepath .Join (webroot , "fresh.txt" ))
195+ require .NoError (t , err )
196+ require .Equal (t , "NEW" , string (got ))
197+ }
198+
199+ // The multipart upload path renames the temp file over any existing same-named
200+ // file, clobbering it. Under --no-delete that overwrite must be skipped and the
201+ // existing file left intact.
202+ func TestUpload_NoDelete_BlocksOverwrite (t * testing.T ) {
203+ webroot := t .TempDir ()
204+ fs , cleanup := newTestFileServer (t , webroot )
205+ defer cleanup ()
206+ fs .NoDelete = true
207+ require .NoError (t , os .WriteFile (filepath .Join (webroot , "victim.txt" ), []byte ("VICTIM" ), 0644 ))
208+
209+ body , ctype := multipartUpload (t , "victim.txt" , "CLOBBERED" )
210+ r := httptest .NewRequest (http .MethodPost , "/upload" , body )
211+ r .Header .Set ("Content-Type" , ctype )
212+ r .Header .Set ("X-CSRF-Token" , "test-csrf" )
213+ w := httptest .NewRecorder ()
214+
215+ fs .upload (w , r )
216+
217+ got , err := os .ReadFile (filepath .Join (webroot , "victim.txt" ))
218+ require .NoError (t , err )
219+ require .Equal (t , "VICTIM" , string (got ), "existing file must not be clobbered by upload" )
220+ }
221+
222+ // A new-named upload must still succeed under --no-delete.
223+ func TestUpload_NoDelete_AllowsNewFile (t * testing.T ) {
224+ webroot := t .TempDir ()
225+ fs , cleanup := newTestFileServer (t , webroot )
226+ defer cleanup ()
227+ fs .NoDelete = true
228+
229+ body , ctype := multipartUpload (t , "fresh.txt" , "NEW" )
230+ r := httptest .NewRequest (http .MethodPost , "/upload" , body )
231+ r .Header .Set ("Content-Type" , ctype )
232+ r .Header .Set ("X-CSRF-Token" , "test-csrf" )
233+ w := httptest .NewRecorder ()
234+
235+ fs .upload (w , r )
236+
237+ require .Equal (t , http .StatusSeeOther , w .Code )
238+ got , err := os .ReadFile (filepath .Join (webroot , "fresh.txt" ))
239+ require .NoError (t , err )
240+ require .Equal (t , "NEW" , string (got ))
241+ }
0 commit comments