Skip to content

Commit

Permalink
Add "/uploads" to list all uploaded files
Browse files Browse the repository at this point in the history
  • Loading branch information
DanielHeath committed Apr 1, 2018
1 parent df406ec commit fc30303
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 25 deletions.
59 changes: 42 additions & 17 deletions handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ func serve(

router := gin.Default()

router.SetFuncMap(template.FuncMap{
"sniffContentType": sniffContentType,
})

if hotTemplateReloading {
router.LoadHTMLGlob("templates/*.tmpl")
} else {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand All @@ -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)),
Expand Down
53 changes: 48 additions & 5 deletions page.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func Open(name string) (p *Page) {
}

type DirectoryEntry struct {
Name string
Path string
Length int
Numchanges int
LastEdited time.Time
Expand All @@ -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
Expand Down
17 changes: 14 additions & 3 deletions templates/index.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
21 changes: 21 additions & 0 deletions utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import (
"encoding/hex"
"math/rand"
"net"
"net/http"
"os"
"path"
"strings"
"time"

Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit fc30303

Please sign in to comment.