diff --git a/http.go b/http.go index dabcca2..e01b133 100644 --- a/http.go +++ b/http.go @@ -269,6 +269,24 @@ func (rt *Router) routeServerListDirectory(w http.ResponseWriter, r *http.Reques json.NewEncoder(w).Encode(stats) } +// Writes a file to the system for the server. +func (rt *Router) routeServerWriteFile(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + s := rt.Servers.Get(ps.ByName("server")) + + p := r.URL.Query().Get("file") + defer r.Body.Close() + err := s.Filesystem.Writefile(p, r.Body) + + if err != nil { + zap.S().Errorw("failed to write file to directory", zap.String("server", s.Uuid), zap.String("path", p), zap.Error(err)) + + http.Error(w, "failed to write file to directory", http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusNoContent) +} + // Creates a new directory for the server. func (rt *Router) routeServerCreateDirectory(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { s := rt.Servers.Get(ps.ByName("server")) @@ -374,6 +392,7 @@ func (rt *Router) ConfigureRouter() *httprouter.Router { router.GET("/api/servers/:server/files/list-directory", rt.AuthenticateToken("s:files", rt.AuthenticateServer(rt.routeServerListDirectory))) router.PUT("/api/servers/:server/files/rename", rt.AuthenticateToken("s:files", rt.AuthenticateServer(rt.routeServerRenameFile))) router.POST("/api/servers/:server/files/copy", rt.AuthenticateToken("s:files", rt.AuthenticateServer(rt.routeServerCopyFile))) + router.POST("/api/servers/:server/files/write", rt.AuthenticateToken("s:files", rt.AuthenticateServer(rt.routeServerWriteFile))) router.POST("/api/servers/:server/files/create-directory", rt.AuthenticateToken("s:files", rt.AuthenticateServer(rt.routeServerCreateDirectory))) router.POST("/api/servers/:server/files/delete", rt.AuthenticateToken("s:files", rt.AuthenticateServer(rt.routeServerDeleteFile))) router.POST("/api/servers/:server/power", rt.AuthenticateToken("s:power", rt.AuthenticateServer(rt.routeServerPower))) diff --git a/server/filesystem.go b/server/filesystem.go index f1d5a2a..3259bfb 100644 --- a/server/filesystem.go +++ b/server/filesystem.go @@ -1,6 +1,7 @@ package server import ( + "bufio" "bytes" "encoding/json" "errors" @@ -196,6 +197,68 @@ func (fs *Filesystem) Readfile(p string) (io.Reader, error) { return bytes.NewReader(b), nil } +// Writes a file to the system. If the file does not already exist one will be created. +// +// @todo should probably have a write lock here so we don't write twice at once. +func (fs *Filesystem) Writefile(p string, r io.Reader) error { + cleaned, err := fs.SafePath(p) + if err != nil { + return err + } + + // If the file does not exist on the system already go ahead and create the pathway + // to it and an empty file. We'll then write to it later on after this completes. + if stat, err := os.Stat(cleaned); err != nil && os.IsNotExist(err) { + if err := os.MkdirAll(filepath.Dir(cleaned), 0755); err != nil { + return err + } + + if err := fs.Chown(filepath.Dir(cleaned)); err != nil { + return err + } + } else if err != nil { + return err + } else if stat.IsDir() { + return errors.New("cannot use a directory as a file for writing") + } + + // This will either create the file if it does not already exist, or open and + // truncate the existing file. + file, err := os.OpenFile(cleaned, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + return err + } + defer file.Close() + + // Create a new buffered writer that will write to the file we just opened + // and stream in the contents from the reader. + w := bufio.NewWriter(file) + + buf := make([]byte, 1024) + for { + n, err := r.Read(buf) + if err != nil && err != io.EOF { + return err + } + + if n == 0 { + break + } + + if _, err := w.Write(buf[:n]); err != nil { + return err + } + } + + if err := w.Flush(); err != nil { + return err + } + + // Finally, chown the file to ensure the permissions don't end up out-of-whack + // if we had just created it. + return fs.Chown(p) +} + // Defines the stat struct object. type Stat struct { Info os.FileInfo