add basic file upload, delete
This commit is contained in:
parent
b530a492ba
commit
6f663500cc
12 changed files with 191 additions and 47 deletions
|
@ -1,7 +1,9 @@
|
||||||
package archetype
|
package archetype
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -33,9 +35,9 @@ type FileManager interface {
|
||||||
// ListTree() FileListing
|
// ListTree() FileListing
|
||||||
ListSubTree(root string) FileListing
|
ListSubTree(root string) FileListing
|
||||||
GetFileData(slug string) FileData
|
GetFileData(slug string) FileData
|
||||||
// AddFile(path string, file multipart.FileHeader) error
|
AddFile(path string, req *http.Request) error
|
||||||
// MkDir(path string) error
|
// MkDir(path string) error
|
||||||
// Remove(path string) error
|
Remove(path string) error
|
||||||
// Rename(old, new string) error
|
// Rename(old, new string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,3 +122,50 @@ func (self *SimpleFileManager) GetFileData(slug string) FileData {
|
||||||
IsDir: fileInfo.IsDir(),
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -86,3 +86,12 @@ func WithFileData(next http.Handler, fileManager core.FileManager) http.Handler
|
||||||
|
|
||||||
return http.HandlerFunc(handlerFunc)
|
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)
|
||||||
|
}
|
||||||
|
|
57
nirvash.go
57
nirvash.go
|
@ -244,14 +244,63 @@ func main() {
|
||||||
http.MethodGet,
|
http.MethodGet,
|
||||||
udb,
|
udb,
|
||||||
"/login")))
|
"/login")))
|
||||||
// file upload GET contains form for file upload
|
|
||||||
|
rtr.Post(
|
||||||
|
`/file-delete/(?P<Slug>.*)`,
|
||||||
|
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<Slug>.*)`,
|
||||||
|
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<Slug>.*)`,
|
||||||
|
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
|
// file upload POST performs the action of creating/overwriting
|
||||||
// add directory GET contains the form for directory creation
|
// add directory GET contains the form for directory creation
|
||||||
// add directory POST performs the action of creating directory
|
// 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
|
// 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
|
// move-do POST moves the file when finalized in move-choose
|
||||||
|
|
||||||
http.ListenAndServe(":8080", rtr)
|
http.ListenAndServe(":8080", rtr)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
||||||
<svg
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
version="1.0"
|
|
||||||
width="100"
|
|
||||||
height="100"
|
|
||||||
id="svg8978">
|
|
||||||
<defs
|
|
||||||
id="defs8980" />
|
|
||||||
<g
|
|
||||||
id="layer1">
|
|
||||||
<path
|
|
||||||
d="M 6.3895625,6.4195626 C 93.580437,93.610437 93.580437,93.610437 93.580437,93.610437"
|
|
||||||
style="fill:none;fill-rule:evenodd;stroke:#D80F0F;stroke-width:18.05195999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
||||||
id="path8986" />
|
|
||||||
<path
|
|
||||||
d="M 6.3894001,93.6106 C 93.830213,6.4194003 93.830213,6.4194003 93.830213,6.4194003"
|
|
||||||
style="fill:none;fill-rule:evenodd;stroke:#D80F0F;stroke-width:17.80202103;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
||||||
id="path8988" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 954 B |
|
@ -1,6 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500">
|
|
||||||
<path stroke="#177355" stroke-width="55" fill="none"
|
|
||||||
stroke-linecap="round" stroke-linejoin="round"
|
|
||||||
d="m249,30a220,220 0 1,0 2,0zm-10,75 140,145-140,145M110,250H350"/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 279 B |
|
@ -180,7 +180,7 @@ h2 {
|
||||||
position: relative;
|
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;
|
display: block;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
width: 80%;
|
width: 80%;
|
||||||
|
@ -202,7 +202,7 @@ form.editor label, form.build label, .danger-zone label, form.configurator label
|
||||||
text-transform: uppercase;
|
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;
|
display: block;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
margin-top: 0.2em;
|
margin-top: 0.2em;
|
||||||
|
@ -216,14 +216,22 @@ form.editor input, form.build input, form.editor textarea, form.configurator inp
|
||||||
box-sizing: border-box;
|
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"] {
|
form.editor input[type="text"], form.configurator input[type="text"], form.configurator input[type="number"], form.build input[type="text"] {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
form.editor input.slug-input {
|
form.editor input.slug-input {
|
||||||
font-size: 100%;
|
font-size: 100%;
|
||||||
}
|
}
|
||||||
|
@ -236,7 +244,7 @@ form input:focus, form textarea:focus {
|
||||||
border: 2px solid cyan;
|
border: 2px solid cyan;
|
||||||
}
|
}
|
||||||
|
|
||||||
form.editor, form.editor.danger-zone {
|
form.editor, .wide {
|
||||||
max-width: 80em;
|
max-width: 80em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -257,7 +265,7 @@ form.configurator input, form.configurator textarea {
|
||||||
font-size: 125%;
|
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-left: auto;
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
font-size: 150%;
|
font-size: 150%;
|
||||||
|
@ -265,7 +273,7 @@ form.editor input[type="submit"], form.build input[type="submit"], .danger-zone
|
||||||
transition: background 1s, color 1s;
|
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;
|
background: lightgray;
|
||||||
color: black;
|
color: black;
|
||||||
}
|
}
|
||||||
|
@ -300,7 +308,17 @@ form input[readonly] {
|
||||||
border-bottom: solid 2px crimson;
|
border-bottom: solid 2px crimson;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.file-list li a {
|
||||||
|
display: inline-block;
|
||||||
|
max-width: calc(100% - 32px);
|
||||||
|
}
|
||||||
|
|
||||||
.file-actions-icon {
|
.file-actions-icon {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
max-height: 16px;
|
max-height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="file"] {
|
||||||
|
display: none;
|
||||||
}
|
}
|
|
@ -32,7 +32,7 @@
|
||||||
<input type="submit" value="Save"/>
|
<input type="submit" value="Save"/>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<details class="danger-zone"><summary>Danger Zone</summary>
|
<details class="danger-zone wide"><summary>Danger Zone</summary>
|
||||||
<form class="eraser" method="POST" action="/delete/{{$slug}}">
|
<form class="eraser" method="POST" action="/delete/{{$slug}}">
|
||||||
<input hidden name="csrfToken" value="{{$csrfToken}}"/>
|
<input hidden name="csrfToken" value="{{$csrfToken}}"/>
|
||||||
<label>I want to delete this page
|
<label>I want to delete this page
|
||||||
|
|
|
@ -17,15 +17,15 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
<div class="action-panel">
|
<div class="action-panel">
|
||||||
<form class="file-move" method="POST" action="/move-select{{($file).Path}}">
|
<form class="file-move" method="POST" action="/move-select/{{($file).Path}}">
|
||||||
<span>/{{($file).Path}}</span>
|
<span>/{{($file).Path}}</span>
|
||||||
<input hidden name="csrfToken" value="{{$csrfToken}}"/>
|
<input hidden name="csrfToken" value="{{$csrfToken}}"/>
|
||||||
<input type="submit" value="Move/Rename"/>
|
<input type="submit" value="Move/Rename"/>
|
||||||
</form>
|
</form>
|
||||||
<details class="danger-zone"><summary>Danger Zone</summary>
|
<details class="danger-zone"><summary>Danger Zone</summary>
|
||||||
<form class="file-delete" method="POST" action="/file-delete{{($file).Path}}">
|
<form class="file-delete" method="POST" action="/file-delete/{{($file).Path}}">
|
||||||
<input hidden name="csrfToken" value="{{$csrfToken}}"/>
|
<input hidden name="csrfToken" value="{{$csrfToken}}"/>
|
||||||
<label>I want to delete this file
|
<label>I want to delete this {{if ($file).IsDir }} diretory and everything under it {{ else }} file {{ end }}
|
||||||
<input type="checkbox" required/><br/>
|
<input type="checkbox" required/><br/>
|
||||||
</label>
|
</label>
|
||||||
<label>Yes, I'm sure!
|
<label>Yes, I'm sure!
|
||||||
|
|
14
templates/file_delete.html
Normal file
14
templates/file_delete.html
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{{ $slug := ((.Context).Value "params").Slug }}
|
||||||
|
{{ $deleteErr := ((.Context).Value "file-manager").Remove $slug }}
|
||||||
|
|
||||||
|
{{ template "header" . }}
|
||||||
|
|
||||||
|
{{ if $deleteErr }}
|
||||||
|
<h2>File Deletion Error</h2>
|
||||||
|
<span class="adapter-error">There was an error deleting the file: {{ ($deleteErr).Error }}</span>
|
||||||
|
{{ else }}
|
||||||
|
<h2>File Deleted</h2>
|
||||||
|
<span class="adapter-success">Static file '{{ $slug }}' was deleted</span>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ template "footer" . }}
|
|
@ -17,7 +17,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="page-list">
|
<div class="page-list">
|
||||||
<ul>
|
<ul class="file-list">
|
||||||
{{ if ($fileList).Up }}
|
{{ if ($fileList).Up }}
|
||||||
<li><a href="/static-mgr{{$fileList.Up}}">..</a></li>
|
<li><a href="/static-mgr{{$fileList.Up}}">..</a></li>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
20
templates/file_upload.html
Normal file
20
templates/file_upload.html
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{{ $slug := ((.Context).Value "params").Slug }}
|
||||||
|
{{ $csrfToken := (.Context).Value "csrfToken" }}
|
||||||
|
|
||||||
|
{{ template "header" . }}
|
||||||
|
|
||||||
|
<h2>File Upload</h2>
|
||||||
|
|
||||||
|
<form class="uploader" enctype="multipart/form-data" method="POST" action="/upload-process/{{$slug}}">
|
||||||
|
<input hidden type="text" name="csrfToken" value="{{$csrfToken}}"/>
|
||||||
|
|
||||||
|
<span>Uploading file to /{{$slug}}</span><br/>
|
||||||
|
<span class="upload-warning">(file with the same name as your upload will be overwritten!)</span><br/>
|
||||||
|
|
||||||
|
<label>Select File<br/>
|
||||||
|
<input required type="file" name="file"/>
|
||||||
|
</label>
|
||||||
|
<input type="submit" value="Upload"/>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{{ template "footer" . }}
|
14
templates/file_upload_process.html
Normal file
14
templates/file_upload_process.html
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{{ $slug := ((.Context).Value "params").Slug }}
|
||||||
|
{{ $uploadError := ((.Context).Value "file-manager").AddFile $slug . }}
|
||||||
|
|
||||||
|
{{ template "header" . }}
|
||||||
|
|
||||||
|
{{ if $uploadError }}
|
||||||
|
<h2>Upload Error</h2>
|
||||||
|
<span class="adapter-error">{{($uploadError).Error}}</span>
|
||||||
|
{{ else }}
|
||||||
|
<h2>Upload Successful</h2>
|
||||||
|
<span class="adapter-success">The file has been uploaded successfuly</span>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ template "footer" . }}
|
Loading…
Reference in a new issue