From fc3030339fda129a120863de5f32c4ff5075336a Mon Sep 17 00:00:00 2001 From: Daniel Heath Date: Sun, 1 Apr 2018 11:31:26 +1000 Subject: [PATCH] Add "/uploads" to list all uploaded files --- handlers.go | 59 +++++++++++++++++++++++++++++++------------- page.go | 53 +++++++++++++++++++++++++++++++++++---- templates/index.tmpl | 17 ++++++++++--- utils.go | 21 ++++++++++++++++ 4 files changed, 125 insertions(+), 25 deletions(-) diff --git a/handlers.go b/handlers.go index e9ecfce..0151984 100755 --- a/handlers.go +++ b/handlers.go @@ -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)), diff --git a/page.go b/page.go index 0300c44..1f265bd 100755 --- a/page.go +++ b/page.go @@ -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 diff --git a/templates/index.tmpl b/templates/index.tmpl index 1a17730..31ff74b 100755 --- a/templates/index.tmpl +++ b/templates/index.tmpl @@ -202,18 +202,29 @@ {{ if .DirectoryPage }} + {{ $upload := .UploadPage }} + {{ if not $upload }} + {{ end }} {{range .DirectoryEntries}} - - + + + {{ if not $upload }} - + {{ end }} + {{ end }}
Document Current sizeNum EditsLast Edited
{{ .Name }}{{.Length}} + {{ if $upload }} + {{ sniffContentType .Name }} + {{ else }} + {{ .Name }} + {{ end }} + {{.Size}}{{.Numchanges}}{{.LastEditTime}}{{.ModTime.Format "Mon Jan 2 15:04:05 MST 2006" }}
diff --git a/utils.go b/utils.go index 5f33e2e..5f50635 100644 --- a/utils.go +++ b/utils.go @@ -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)