diff --git a/context.go b/context.go index 5174033eb3..368dc7193e 100644 --- a/context.go +++ b/context.go @@ -728,11 +728,17 @@ func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string, perm mode = perm[0] } dir := filepath.Dir(dst) + // Check if directory exists to only chmod newly created directories + _, statErr := os.Stat(dir) + dirExists := !os.IsNotExist(statErr) if err = os.MkdirAll(dir, mode); err != nil { return err } - if err = os.Chmod(dir, mode); err != nil { - return err + // Only chmod if directory was newly created (fixes #4622) + if !dirExists { + if err = os.Chmod(dir, mode); err != nil { + return err + } } out, err := os.Create(dst) diff --git a/context_test.go b/context_test.go index ef60379d77..2a562c73e3 100644 --- a/context_test.go +++ b/context_test.go @@ -246,14 +246,19 @@ func TestSaveUploadedFileWithPermission(t *testing.T) { f, err := c.FormFile("file") require.NoError(t, err) assert.Equal(t, "permission_test", f.Filename) + + // Save into a not-yet-existing subdirectory so the requested mode is + // applied by MkdirAll/Chmod and the assertion is independent of the + // test runner's working-directory permissions. + parent := t.TempDir() + newDir := filepath.Join(parent, "perm_subdir") + dst := filepath.Join(newDir, "permission_test") + var mode fs.FileMode = 0o755 - require.NoError(t, c.SaveUploadedFile(f, "permission_test", mode)) - t.Cleanup(func() { - assert.NoError(t, os.Remove("permission_test")) - }) - info, err := os.Stat(filepath.Dir("permission_test")) + require.NoError(t, c.SaveUploadedFile(f, dst, mode)) + info, err := os.Stat(newDir) require.NoError(t, err) - assert.Equal(t, info.Mode().Perm(), mode) + assert.Equal(t, mode, info.Mode().Perm()) } func TestSaveUploadedFileWithPermissionFailed(t *testing.T) { @@ -274,6 +279,39 @@ func TestSaveUploadedFileWithPermissionFailed(t *testing.T) { require.Error(t, c.SaveUploadedFile(f, "test/permission_test", mode)) } +// TestSaveUploadedFileExistingDir verifies that SaveUploadedFile does not +// alter permissions on a pre-existing target directory (regression test for +// #4622, where chmod on a system dir like /tmp would fail). +func TestSaveUploadedFileExistingDir(t *testing.T) { + buf := new(bytes.Buffer) + mw := multipart.NewWriter(buf) + w, err := mw.CreateFormFile("file", "existing_dir_test") + require.NoError(t, err) + _, err = w.Write([]byte("existing_dir_test")) + require.NoError(t, err) + mw.Close() + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest(http.MethodPost, "/", buf) + c.Request.Header.Set("Content-Type", mw.FormDataContentType()) + f, err := c.FormFile("file") + require.NoError(t, err) + + // Create a target directory with a non-default permission and + // confirm SaveUploadedFile leaves the existing perm untouched. + dir := t.TempDir() + var existingPerm fs.FileMode = 0o700 + require.NoError(t, os.Chmod(dir, existingPerm)) + + dst := filepath.Join(dir, "existing_dir_test") + var mode fs.FileMode = 0o755 + require.NoError(t, c.SaveUploadedFile(f, dst, mode)) + + info, err := os.Stat(dir) + require.NoError(t, err) + assert.Equal(t, existingPerm, info.Mode().Perm(), + "existing directory permissions must not be modified") +} + func TestContextReset(t *testing.T) { router := New() c := router.allocateContext(0)