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

53
page.go
View File

@ -54,7 +54,7 @@ func Open(name string) (p *Page) {
} }
type DirectoryEntry struct { type DirectoryEntry struct {
Name string Path string
Length int Length int
Numchanges int Numchanges int
LastEdited time.Time LastEdited time.Time
@ -64,23 +64,66 @@ func (d DirectoryEntry) LastEditTime() string {
return d.LastEdited.Format("Mon Jan 2 15:04:05 MST 2006") 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) files, _ := ioutil.ReadDir(pathToData)
entries := make([]DirectoryEntry, len(files)) entries := make([]os.FileInfo, len(files))
for i, f := range files { for i, f := range files {
name := DecodeFileName(f.Name()) name := DecodeFileName(f.Name())
p := Open(name) p := Open(name)
entries[i] = DirectoryEntry{ entries[i] = DirectoryEntry{
Name: name, Path: name,
Length: len(p.Text.GetCurrent()), Length: len(p.Text.GetCurrent()),
Numchanges: p.Text.NumEdits(), Numchanges: p.Text.NumEdits(),
LastEdited: time.Unix(p.Text.LastEditTime()/1000000000, 0), 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 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 { func DecodeFileName(s string) string {
s2, _ := decodeFromBase32(strings.Split(s, ".")[0]) s2, _ := decodeFromBase32(strings.Split(s, ".")[0])
return s2 return s2

View File

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

View File

@ -6,7 +6,9 @@ import (
"encoding/hex" "encoding/hex"
"math/rand" "math/rand"
"net" "net"
"net/http"
"os" "os"
"path"
"strings" "strings"
"time" "time"
@ -92,6 +94,25 @@ func contentType(filename string) string {
return "text/html" 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) { func timeTrack(start time.Time, name string) {
elapsed := time.Since(start) elapsed := time.Since(start)
log.Debug("%s took %s", name, elapsed) log.Debug("%s took %s", name, elapsed)