mirror of
https://github.com/m1k1o/neko.git
synced 2026-11-03 00:11:48 -06:00
feat(filetransfer): allow file deletion and add user delete config option (#683)
* feat(filetransfer): allow file deletion and add user delete config option * fix(client): make user_delete required boolean in payload * lint. * feat(filetransfer): add confirmation dialog for file deletion. * feat(filetransfer): add delete file confirmation messages in multiple languages * lint. --------- Co-authored-by: Miroslav Šedivý <sedivy.miro@gmail.com>
This commit is contained in:
committed by
GitHub
parent
30d8e1c595
commit
d6a4200b36
@@ -388,6 +388,34 @@ func (h *LegacyHandler) Route(r types.Router) {
|
||||
return err
|
||||
})
|
||||
|
||||
r.Delete("/file", func(w http.ResponseWriter, r *http.Request) error {
|
||||
if h.isBanned(r) {
|
||||
return utils.HttpForbidden("banned ip")
|
||||
}
|
||||
|
||||
s := h.newSession(r)
|
||||
|
||||
// create a new session
|
||||
username := r.URL.Query().Get("usr")
|
||||
password := r.URL.Query().Get("pwd")
|
||||
err := s.create(username, password)
|
||||
if err != nil {
|
||||
return utils.HttpForbidden(err.Error())
|
||||
}
|
||||
defer s.destroy()
|
||||
|
||||
filename := r.URL.Query().Get("filename")
|
||||
|
||||
body, _, err := s.req(http.MethodDelete, "/api/filetransfer?filename="+url.QueryEscape(filename), r.Header, nil)
|
||||
if err != nil {
|
||||
return utils.HttpInternalServerError().WithInternalErr(err)
|
||||
}
|
||||
|
||||
// copy the body to the response writer
|
||||
_, err = io.Copy(w, body)
|
||||
return err
|
||||
})
|
||||
|
||||
r.Get("/health", func(w http.ResponseWriter, r *http.Request) error {
|
||||
_, err := w.Write([]byte("true"))
|
||||
return err
|
||||
|
||||
@@ -113,6 +113,7 @@ type FileTransferList struct {
|
||||
Cwd string `json:"cwd"`
|
||||
UserDownload bool `json:"user_download"`
|
||||
UserUpload bool `json:"user_upload"`
|
||||
UserDelete bool `json:"user_delete"`
|
||||
Files []types.FileListItem `json:"files"`
|
||||
}
|
||||
|
||||
|
||||
@@ -611,6 +611,7 @@ func (s *session) wsToClient(msg []byte) error {
|
||||
Cwd: request.RootDir,
|
||||
UserDownload: request.UserDownload,
|
||||
UserUpload: request.UserUpload,
|
||||
UserDelete: request.UserDelete,
|
||||
Files: files,
|
||||
})
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ type Config struct {
|
||||
RefreshInterval time.Duration
|
||||
UserDownload bool
|
||||
UserUpload bool
|
||||
UserDelete bool
|
||||
}
|
||||
|
||||
func (Config) Init(cmd *cobra.Command) error {
|
||||
@@ -42,6 +43,11 @@ func (Config) Init(cmd *cobra.Command) error {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.PersistentFlags().Bool("filetransfer.user_delete", false, "allow non-admin users to delete files")
|
||||
if err := viper.BindPFlag("filetransfer.user_delete", cmd.PersistentFlags().Lookup("filetransfer.user_delete")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// v2 config
|
||||
|
||||
cmd.PersistentFlags().Bool("file_transfer_enabled", false, "enable file transfer feature")
|
||||
@@ -64,6 +70,7 @@ func (s *Config) Set() {
|
||||
s.RefreshInterval = viper.GetDuration("filetransfer.refresh_interval")
|
||||
s.UserDownload = viper.GetBool("filetransfer.user_download")
|
||||
s.UserUpload = viper.GetBool("filetransfer.user_upload")
|
||||
s.UserDelete = viper.GetBool("filetransfer.user_delete")
|
||||
|
||||
// v2 config
|
||||
|
||||
|
||||
@@ -107,6 +107,7 @@ func (m *Manager) broadcastUpdate() {
|
||||
RootDir: m.config.RootDir,
|
||||
UserDownload: m.config.UserDownload,
|
||||
UserUpload: m.config.UserUpload,
|
||||
UserDelete: m.config.UserDelete,
|
||||
Files: fileList,
|
||||
})
|
||||
}
|
||||
@@ -121,6 +122,7 @@ func (m *Manager) sendUpdate(session types.Session) {
|
||||
RootDir: m.config.RootDir,
|
||||
UserDownload: m.config.UserDownload,
|
||||
UserUpload: m.config.UserUpload,
|
||||
UserDelete: m.config.UserDelete,
|
||||
Files: fileList,
|
||||
})
|
||||
}
|
||||
@@ -206,6 +208,59 @@ func (m *Manager) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) deleteFileHandler(w http.ResponseWriter, r *http.Request) error {
|
||||
session, ok := auth.GetSession(r)
|
||||
if !ok {
|
||||
return utils.HttpUnauthorized("session not found")
|
||||
}
|
||||
|
||||
enabled, err := m.isEnabledForSession(session)
|
||||
if err != nil {
|
||||
return utils.HttpInternalServerError().
|
||||
WithInternalErr(err).
|
||||
Msg("error checking file transfer permissions")
|
||||
}
|
||||
|
||||
if !enabled {
|
||||
return utils.HttpForbidden("file transfer is disabled")
|
||||
}
|
||||
|
||||
if !session.Profile().IsAdmin && !m.config.UserDelete {
|
||||
return utils.HttpForbidden("file delete is not allowed for non-admin users")
|
||||
}
|
||||
|
||||
filename := r.URL.Query().Get("filename")
|
||||
badChars, err := regexp.MatchString(`(?m)\.\.(?:\/|$)`, filename)
|
||||
if filename == "" || badChars || err != nil {
|
||||
return utils.HttpBadRequest().
|
||||
WithInternalErr(err).
|
||||
Msg("bad filename")
|
||||
}
|
||||
|
||||
filename = filepath.Clean(filename)
|
||||
filename = filepath.Base(filename)
|
||||
filePath := filepath.Join(m.config.RootDir, filename)
|
||||
|
||||
if err := os.Remove(filePath); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return utils.HttpNotFound("file not found")
|
||||
}
|
||||
return utils.HttpInternalServerError().
|
||||
WithInternalErr(err).
|
||||
Msg("error deleting file")
|
||||
}
|
||||
|
||||
err, changed := m.refresh()
|
||||
if err != nil {
|
||||
m.logger.Err(err).Msg("unable to refresh file list after delete")
|
||||
}
|
||||
if changed {
|
||||
m.broadcastUpdate()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) Shutdown() error {
|
||||
close(m.shutdown)
|
||||
return nil
|
||||
@@ -214,6 +269,7 @@ func (m *Manager) Shutdown() error {
|
||||
func (m *Manager) Route(r types.Router) {
|
||||
r.Get("/", m.downloadFileHandler)
|
||||
r.Post("/", m.uploadFileHandler)
|
||||
r.Delete("/", m.deleteFileHandler)
|
||||
}
|
||||
|
||||
func (m *Manager) WebSocketHandler(session types.Session, msg types.WebSocketMessage) bool {
|
||||
|
||||
@@ -15,6 +15,7 @@ type Message struct {
|
||||
RootDir string `json:"root_dir"`
|
||||
UserDownload bool `json:"user_download"`
|
||||
UserUpload bool `json:"user_upload"`
|
||||
UserDelete bool `json:"user_delete"`
|
||||
Files []Item `json:"files"`
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user