Add "/uploads" to list all uploaded files

This commit is contained in:
Daniel Heath 2018-04-01 11:31:26 +10:00
parent df406ec71b
commit fc3030339f
4 changed files with 125 additions and 25 deletions

View File

@ -57,6 +57,10 @@ func serve(
router := gin.Default()
router.SetFuncMap(template.FuncMap{
"sniffContentType": sniffContentType,
})
if hotTemplateReloading {
router.LoadHTMLGlob("templates/*.tmpl")
} else {
@ -307,23 +311,34 @@ func handlePageRequest(c *gin.Context) {
c.Data(http.StatusOK, contentType(filename), data)
return
} else if page == "uploads" {
pathname := path.Join(pathToData, command[1:]+".upload")
if allowInsecureHtml {
c.Header(
"Content-Disposition",
`inline; filename="`+c.DefaultQuery("filename", "upload")+`"`,
)
if len(command) == 0 || command == "/" || command == "/edit" {
if !allowFileUploads {
c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("Uploads are disabled on this server"))
return
}
} else {
// Prevent malicious html uploads by forcing type to plaintext and 'download-instead-of-view'
c.Header("Content-Type", "text/plain")
c.Header(
"Content-Disposition",
`attachment; filename="`+c.DefaultQuery("filename", "upload")+`"`,
)
command = command[1:]
if !strings.HasSuffix(command, ".upload") {
command = command + ".upload"
}
pathname := path.Join(pathToData, command)
if allowInsecureHtml {
c.Header(
"Content-Disposition",
`inline; filename="`+c.DefaultQuery("filename", "upload")+`"`,
)
} else {
// Prevent malicious html uploads by forcing type to plaintext and 'download-instead-of-view'
c.Header("Content-Type", "text/plain")
c.Header(
"Content-Disposition",
`attachment; filename="`+c.DefaultQuery("filename", "upload")+`"`,
)
}
c.File(pathname)
return
}
c.File(pathname)
return
}
p := Open(page)
@ -410,11 +425,20 @@ func handlePageRequest(c *gin.Context) {
return
}
var DirectoryEntries []DirectoryEntry
var DirectoryEntries []os.FileInfo
if page == "ls" {
command = "/view"
DirectoryEntries = DirectoryList()
}
if page == "uploads" {
command = "/view"
var err error
DirectoryEntries, err = UploadList()
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
}
// swap out /view for /read if it is published
if p.IsPublished {
@ -432,7 +456,8 @@ func handlePageRequest(c *gin.Context) {
command[0:2] != "/l" &&
command[0:2] != "/r" &&
command[0:2] != "/h",
"DirectoryPage": page == "ls",
"DirectoryPage": page == "ls" || page == "uploads",
"UploadPage": page == "uploads",
"DirectoryEntries": DirectoryEntries,
"Page": page,
"RenderedPage": template.HTML([]byte(rawHTML)),

53
page.go
View File

@ -54,7 +54,7 @@ func Open(name string) (p *Page) {
}
type DirectoryEntry struct {
Name string
Path string
Length int
Numchanges int
LastEdited time.Time
@ -64,23 +64,66 @@ func (d DirectoryEntry) LastEditTime() string {
return d.LastEdited.Format("Mon Jan 2 15:04:05 MST 2006")
}
func DirectoryList() []DirectoryEntry {
func (d DirectoryEntry) Name() string {
return d.Path
}
func (d DirectoryEntry) Size() int64 {
return int64(d.Length)
}
func (d DirectoryEntry) Mode() os.FileMode {
return os.ModePerm
}
func (d DirectoryEntry) ModTime() time.Time {
return d.LastEdited
}
func (d DirectoryEntry) IsDir() bool {
return false
}
func (d DirectoryEntry) Sys() interface{} {
return nil
}
func DirectoryList() []os.FileInfo {
files, _ := ioutil.ReadDir(pathToData)
entries := make([]DirectoryEntry, len(files))
entries := make([]os.FileInfo, len(files))
for i, f := range files {
name := DecodeFileName(f.Name())
p := Open(name)
entries[i] = DirectoryEntry{
Name: name,
Path: name,
Length: len(p.Text.GetCurrent()),
Numchanges: p.Text.NumEdits(),
LastEdited: time.Unix(p.Text.LastEditTime()/1000000000, 0),
}
}
sort.Slice(entries, func(i, j int) bool { return entries[i].LastEdited.After(entries[j].LastEdited) })
sort.Slice(entries, func(i, j int) bool { return entries[i].ModTime().After(entries[j].ModTime()) })
return entries
}
type UploadEntry struct {
os.FileInfo
}
func UploadList() ([]os.FileInfo, error) {
paths, err := filepath.Glob(path.Join(pathToData, "sha256*"))
if err != nil {
return nil, err
}
result := make([]os.FileInfo, len(paths))
for i := range paths {
result[i], err = os.Stat(paths[i])
if err != nil {
return result, err
}
}
return result, nil
}
func DecodeFileName(s string) string {
s2, _ := decodeFromBase32(strings.Split(s, ".")[0])
return s2

View File

@ -202,18 +202,29 @@
{{ if .DirectoryPage }}
<table style="width:100%">
{{ $upload := .UploadPage }}
<tr>
<th>Document</th>
<th>Current size</th>
{{ if not $upload }}
<th>Num Edits</th>
{{ end }}
<th>Last Edited</th>
</tr>
{{range .DirectoryEntries}}
<tr>
<td><a href="/{{ .Name }}/view">{{ .Name }}</a></td>
<td>{{.Length}}</td>
<td>
{{ if $upload }}
<a href="/uploads/{{ .Name }}">{{ sniffContentType .Name }}</a>
{{ else }}
<a href="/{{ .Name }}/view">{{ .Name }}</a>
{{ end }}
</td>
<td>{{.Size}}</td>
{{ if not $upload }}
<td>{{.Numchanges}}</td>
<td>{{.LastEditTime}}</td>
{{ end }}
<td>{{.ModTime.Format "Mon Jan 2 15:04:05 MST 2006" }}</td>
</tr>
{{ end }}
</table>

View File

@ -6,7 +6,9 @@ import (
"encoding/hex"
"math/rand"
"net"
"net/http"
"os"
"path"
"strings"
"time"
@ -92,6 +94,25 @@ func contentType(filename string) string {
return "text/html"
}
func sniffContentType(name string) (string, error) {
file, err := os.Open(path.Join(pathToData, name))
if err != nil {
return "", err
}
defer file.Close()
// Only the first 512 bytes are used to sniff the content type.
buffer := make([]byte, 512)
_, err = file.Read(buffer)
if err != nil {
return "", err
}
// Always returns a valid content-type and "application/octet-stream" if no others seemed to match.
return http.DetectContentType(buffer), nil
}
func timeTrack(start time.Time, name string) {
elapsed := time.Since(start)
log.Debug("%s took %s", name, elapsed)