diff --git a/archetype/fileManager.go b/archetype/fileManager.go index 428bd7e..46a5538 100644 --- a/archetype/fileManager.go +++ b/archetype/fileManager.go @@ -1,7 +1,9 @@ package archetype import ( + "errors" "io/ioutil" + "net/http" "os" "path/filepath" "strings" @@ -33,9 +35,9 @@ type FileManager interface { // ListTree() FileListing ListSubTree(root string) FileListing GetFileData(slug string) FileData - // AddFile(path string, file multipart.FileHeader) error + AddFile(path string, req *http.Request) error // MkDir(path string) error - // Remove(path string) error + Remove(path string) error // Rename(old, new string) error } @@ -120,3 +122,50 @@ func (self *SimpleFileManager) GetFileData(slug string) FileData { IsDir: fileInfo.IsDir(), } } + +func (self *SimpleFileManager) Remove(slug string) error { + fullPath := filepath.Join(self.Root, slug) + _, err := os.Stat(fullPath) + + if err != nil { + return err + } + if !strings.HasPrefix(fullPath, self.Root) { + return errors.New("You cannot escape!") + } + + return os.RemoveAll(fullPath) +} + +func (self *SimpleFileManager) AddFile(path string, req *http.Request) error { + fullPath := filepath.Join(self.Root, path) + _, err := os.Stat(fullPath) + if err != nil { + return err + } + + if !strings.HasPrefix(fullPath, filepath.Clean(self.Root)) { + return errors.New("You cannot escape!") + } + + req.ParseMultipartForm(250 << 20) + file, header, err := req.FormFile("file") + if err != nil { + return err + } + + fileData, err := ioutil.ReadAll(file) + if err != nil { + return err + } + + destPath := filepath.Join(fullPath, header.Filename) + dest, err := os.Create(destPath) + if err != nil { + return err + } + defer dest.Close() + + dest.Write(fileData) + return nil +} diff --git a/lfo/middleware.go b/lfo/middleware.go index 847a114..8747eb5 100644 --- a/lfo/middleware.go +++ b/lfo/middleware.go @@ -86,3 +86,12 @@ func WithFileData(next http.Handler, fileManager core.FileManager) http.Handler return http.HandlerFunc(handlerFunc) } + +func PrepareForUpload(next http.Handler) http.Handler { + handlerFunc := func(w http.ResponseWriter, req *http.Request) { + req.ParseMultipartForm(250 << 20) + next.ServeHTTP(w, req) + } + + return http.HandlerFunc(handlerFunc) +} diff --git a/nirvash.go b/nirvash.go index c8fa483..a54eb05 100644 --- a/nirvash.go +++ b/nirvash.go @@ -244,14 +244,63 @@ func main() { http.MethodGet, udb, "/login"))) - // file upload GET contains form for file upload + + rtr.Post( + `/file-delete/(?P.*)`, + Defend( + Protected( + WithFileManager( + renderer.Template( + pathConcat(templateRoot, "file_delete.html"), + pathConcat(templateRoot, "header.html"), + pathConcat(templateRoot, "footer.html")), + fileManager), + http.MethodGet, + udb, + "/login"), + udb, + "/")) + + rtr.Get( + `/upload/(?P.*)`, + Fortify( + Protected( + WithFileManager( + renderer.Template( + pathConcat(templateRoot, "file_upload.html"), + pathConcat(templateRoot, "header.html"), + pathConcat(templateRoot, "footer.html")), + fileManager), + http.MethodGet, + udb, + "/login"))) + + rtr.Post( + `/upload-process/(?P.*)`, + PrepareForUpload( + Defend( + Protected( + WithFileManager( + renderer.Template( + pathConcat(templateRoot, "file_upload_process.html"), + pathConcat(templateRoot, "header.html"), + pathConcat(templateRoot, "footer.html")), + fileManager), + http.MethodGet, + udb, + "/login"), + udb, + "/"))) + + // TODO: + // file upload GET contains form for file upload in current directory // file upload POST performs the action of creating/overwriting // add directory GET contains the form for directory creation // add directory POST performs the action of creating directory - // delete GET contains the form for confirming deletion - // delete POST performs the action of deleting - // move GET (not required?) // move-choose POST uses a form to navigate through the file tree + // - to use links to navigate, destination location uses the slug, + // - and file to move uses the POST parameters // move-do POST moves the file when finalized in move-choose + http.ListenAndServe(":8080", rtr) } diff --git a/static/delete.svg b/static/delete.svg deleted file mode 100644 index 4bbe3af..0000000 --- a/static/delete.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - diff --git a/static/move.svg b/static/move.svg deleted file mode 100644 index 37e55f5..0000000 --- a/static/move.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/static/style.css b/static/style.css index 374b08e..5811059 100644 --- a/static/style.css +++ b/static/style.css @@ -180,7 +180,7 @@ h2 { position: relative; } -.page-list, form.editor, form.build, form.configurator, span.adapter-error, span.adapter-success, .file-move, .danger-zone { +.page-list, form.editor, form.build, form.configurator, span.adapter-error, span.adapter-success, .file-move, .danger-zone, .uploader { display: block; overflow-x: hidden; width: 80%; @@ -202,7 +202,7 @@ form.editor label, form.build label, .danger-zone label, form.configurator label text-transform: uppercase; } -form.editor input, form.build input, form.editor textarea, form.configurator input, form.configurator textarea, .danger-zone input[type="submit"], .file-move input[type="submit"] { +form.editor input, form.build input, form.editor textarea, form.configurator input, form.configurator textarea, .danger-zone input[type="submit"], .file-move input[type="submit"], .uploader label, .uploader input[type="submit"] { display: block; margin: 0; margin-top: 0.2em; @@ -216,14 +216,22 @@ form.editor input, form.build input, form.editor textarea, form.configurator inp box-sizing: border-box; } +.uploader label { + text-transform: uppercase; + display: inline-block; + transition: background 1s, color 1s; +} + +.upload-warning { + border-bottom: 2px solid crimson; +} + form.editor input[type="text"], form.configurator input[type="text"], form.configurator input[type="number"], form.build input[type="text"] { margin: 0; width: 100%; } - - form.editor input.slug-input { font-size: 100%; } @@ -236,7 +244,7 @@ form input:focus, form textarea:focus { border: 2px solid cyan; } -form.editor, form.editor.danger-zone { +form.editor, .wide { max-width: 80em; } @@ -257,7 +265,7 @@ form.configurator input, form.configurator textarea { font-size: 125%; } -form.editor input[type="submit"], form.build input[type="submit"], .danger-zone input[type="submit"], form.configurator input[type="submit"], .file-move input[type="submit"] { +form.editor input[type="submit"], form.build input[type="submit"], .danger-zone input[type="submit"], form.configurator input[type="submit"], .file-move input[type="submit"], .uploader input[type="submit"] { margin-left: auto; margin-right: 0; font-size: 150%; @@ -265,7 +273,7 @@ form.editor input[type="submit"], form.build input[type="submit"], .danger-zone transition: background 1s, color 1s; } -form.editor input[type="submit"]:hover,form.build input[type="submit"]:hover, .danger-zone input[type="submit"]:hover, form.configurator input[type="submit"]:hover, .file-move input[type="submit"]:hover { +form.editor input[type="submit"]:hover,form.build input[type="submit"]:hover, .danger-zone input[type="submit"]:hover, form.configurator input[type="submit"]:hover, .file-move input[type="submit"]:hover, .uploader input[type="submit"]:hover, .uploader label:hover { background: lightgray; color: black; } @@ -300,7 +308,17 @@ form input[readonly] { border-bottom: solid 2px crimson; } +.file-list li a { + display: inline-block; + max-width: calc(100% - 32px); +} + .file-actions-icon { display: inline-block; max-height: 16px; + width: 16px; +} + +input[type="file"] { + display: none; } \ No newline at end of file diff --git a/templates/cms_edit.html b/templates/cms_edit.html index 30ea57f..8d06f6f 100644 --- a/templates/cms_edit.html +++ b/templates/cms_edit.html @@ -32,7 +32,7 @@ -
Danger Zone +
Danger Zone