serve http, serve webdav: Added a --template flag for user defined markup
This commit is contained in:
parent
dcf945ed58
commit
4362ca7bb9
10 changed files with 241 additions and 25 deletions
|
@ -131,9 +131,13 @@ func (s *server) serveDir(w http.ResponseWriter, r *http.Request, dirRemote stri
|
||||||
// Make the entries for display
|
// Make the entries for display
|
||||||
directory := serve.NewDirectory(dirRemote, s.HTMLTemplate)
|
directory := serve.NewDirectory(dirRemote, s.HTMLTemplate)
|
||||||
for _, node := range dirEntries {
|
for _, node := range dirEntries {
|
||||||
directory.AddEntry(node.Path(), node.IsDir())
|
directory.AddHTMLEntry(node.Path(), node.IsDir(), node.Size(), node.ModTime())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sortParm := r.URL.Query().Get("sort")
|
||||||
|
orderParm := r.URL.Query().Get("order")
|
||||||
|
directory.ProcessQueryParams(sortParm, orderParm)
|
||||||
|
|
||||||
directory.Serve(w, r)
|
directory.Serve(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
2
cmd/serve/http/testdata/golden/index.html
vendored
2
cmd/serve/http/testdata/golden/index.html
vendored
|
@ -6,8 +6,8 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Directory listing of /</h1>
|
<h1>Directory listing of /</h1>
|
||||||
<a href="one%25.txt">one%.txt</a><br />
|
|
||||||
<a href="three/">three/</a><br />
|
<a href="three/">three/</a><br />
|
||||||
|
<a href="one%25.txt">one%.txt</a><br />
|
||||||
<a href="two.txt">two.txt</a><br />
|
<a href="two.txt">two.txt</a><br />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -27,6 +27,7 @@ func AddFlagsPrefix(flagSet *pflag.FlagSet, prefix string, Opt *httplib.Options)
|
||||||
flags.StringVarP(flagSet, &Opt.BasicUser, prefix+"user", "", Opt.BasicUser, "User name for authentication.")
|
flags.StringVarP(flagSet, &Opt.BasicUser, prefix+"user", "", Opt.BasicUser, "User name for authentication.")
|
||||||
flags.StringVarP(flagSet, &Opt.BasicPass, prefix+"pass", "", Opt.BasicPass, "Password for authentication.")
|
flags.StringVarP(flagSet, &Opt.BasicPass, prefix+"pass", "", Opt.BasicPass, "Password for authentication.")
|
||||||
flags.StringVarP(flagSet, &Opt.BaseURL, prefix+"baseurl", "", Opt.BaseURL, "Prefix for URLs - leave blank for root.")
|
flags.StringVarP(flagSet, &Opt.BaseURL, prefix+"baseurl", "", Opt.BaseURL, "Prefix for URLs - leave blank for root.")
|
||||||
|
flags.StringVarP(flagSet, &Opt.Template, prefix+"template", "", Opt.Template, "User Specified Template.")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,27 @@ inserts leading and trailing "/" on --baseurl, so --baseurl "rclone",
|
||||||
--baseurl "/rclone" and --baseurl "/rclone/" are all treated
|
--baseurl "/rclone" and --baseurl "/rclone/" are all treated
|
||||||
identically.
|
identically.
|
||||||
|
|
||||||
|
--template allows a user to specify a custom markup template for http
|
||||||
|
and webdav serve functions. The server exports the following markup
|
||||||
|
to be used within the template to server pages:
|
||||||
|
.Name The full path of a file/directory.
|
||||||
|
.Title "Directory listing of .Name".
|
||||||
|
.Sort The current sort used. This is changble via ?sort= parameter
|
||||||
|
Sort Options: namedirfist,name,size,time (defailt namedirfirst)
|
||||||
|
.Order The current ordering used. This is changable via ?order= paramter
|
||||||
|
Order Options: asc,desc (default asc)
|
||||||
|
.Query Currently unused.
|
||||||
|
.Breacrumb Allows for creating a relative navigation
|
||||||
|
-- .Link The relative to the root link of the Text.
|
||||||
|
-- .Text The Name of the directory.
|
||||||
|
.Entries Information about a specific file/directory.
|
||||||
|
-- .URL The 'url' of an entry.
|
||||||
|
-- .Leaf Currently same as 'URL' but intended to be 'just' the name.
|
||||||
|
-- .IsDir Boolean for if an entry is a directory or not.
|
||||||
|
-- .Size Size in Bytes of the entry.
|
||||||
|
-- .ModTime The UTC timestamp of an entry.
|
||||||
|
|
||||||
|
|
||||||
#### Authentication
|
#### Authentication
|
||||||
|
|
||||||
By default this will serve files without needing a login.
|
By default this will serve files without needing a login.
|
||||||
|
@ -101,6 +122,7 @@ type Options struct {
|
||||||
BasicUser string // single username for basic auth if not using Htpasswd
|
BasicUser string // single username for basic auth if not using Htpasswd
|
||||||
BasicPass string // password for BasicUser
|
BasicPass string // password for BasicUser
|
||||||
Auth AuthFn `json:"-"` // custom Auth (not set by command line flags)
|
Auth AuthFn `json:"-"` // custom Auth (not set by command line flags)
|
||||||
|
Template string // User specified template
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthFn if used will be used to authenticate user, pass. If an error
|
// AuthFn if used will be used to authenticate user, pass. If an error
|
||||||
|
@ -281,7 +303,7 @@ func NewServer(handler http.Handler, opt *Options) *Server {
|
||||||
s.httpServer.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert
|
s.httpServer.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert
|
||||||
}
|
}
|
||||||
|
|
||||||
htmlTemplate, templateErr := data.GetTemplate()
|
htmlTemplate, templateErr := data.GetTemplate(s.Opt.Template)
|
||||||
if templateErr != nil {
|
if templateErr != nil {
|
||||||
log.Fatalf(templateErr.Error())
|
log.Fatalf(templateErr.Error())
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,11 +21,11 @@ var Assets = func() http.FileSystem {
|
||||||
fs := vfsgen۰FS{
|
fs := vfsgen۰FS{
|
||||||
"/": &vfsgen۰DirInfo{
|
"/": &vfsgen۰DirInfo{
|
||||||
name: "/",
|
name: "/",
|
||||||
modTime: time.Date(2018, 12, 16, 6, 54, 42, 894445775, time.UTC),
|
modTime: time.Date(2020, 5, 4, 15, 36, 2, 723307530, time.UTC),
|
||||||
},
|
},
|
||||||
"/index.html": &vfsgen۰CompressedFileInfo{
|
"/index.html": &vfsgen۰CompressedFileInfo{
|
||||||
name: "index.html",
|
name: "index.html",
|
||||||
modTime: time.Date(2018, 12, 16, 6, 54, 42, 790442328, time.UTC),
|
modTime: time.Date(2020, 5, 4, 15, 36, 2, 527302371, time.UTC),
|
||||||
uncompressedSize: 226,
|
uncompressedSize: 226,
|
||||||
|
|
||||||
compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x5c\x8f\x31\xcf\x83\x20\x10\x86\x77\x7e\xc5\x7d\xc4\xf5\x93\xb8\x35\x0d\xb0\xb4\x6e\x26\x6d\x1a\x3b\x74\x3c\xeb\x29\x24\x4a\x13\xa4\x43\x43\xf8\xef\x0d\xea\xd4\x09\xee\x79\xef\x9e\xcb\xc9\xbf\xf3\xe5\xd4\x3e\xae\x35\x98\x30\x4f\x9a\xc9\xfc\xc0\x84\x6e\x54\x9c\x1c\xcf\x80\xb0\xd7\x4c\xce\x14\x10\x9e\x06\xfd\x42\x41\xf1\x77\x18\xfe\x0f\x39\x0d\x36\x4c\xa4\x63\x84\xb2\xcd\x3f\x48\x49\x8a\x8d\x31\x29\xf6\xd1\xee\xd5\x7f\xb2\xa8\xfa\xe9\x33\x95\x66\x31\x82\x47\x37\x12\x14\x16\x8e\x0a\xca\xda\x05\x6f\x69\xc9\x39\x82\xf1\x34\x28\x1e\x23\x14\xb6\xbc\xdf\x1a\x48\x89\xeb\xad\x6a\x08\x87\xd5\x81\x5a\x76\x1e\xc4\x2a\x22\xd7\xaf\x6c\xdf\x27\xb6\x8b\xbe\x01\x00\x00\xff\xff\x92\x2e\x35\x75\xe2\x00\x00\x00"),
|
compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x5c\x8f\x31\xcf\x83\x20\x10\x86\x77\x7e\xc5\x7d\xc4\xf5\x93\xb8\x35\x0d\xb0\xb4\x6e\x26\x6d\x1a\x3b\x74\x3c\xeb\x29\x24\x4a\x13\xa4\x43\x43\xf8\xef\x0d\xea\xd4\x09\xee\x79\xef\x9e\xcb\xc9\xbf\xf3\xe5\xd4\x3e\xae\x35\x98\x30\x4f\x9a\xc9\xfc\xc0\x84\x6e\x54\x9c\x1c\xcf\x80\xb0\xd7\x4c\xce\x14\x10\x9e\x06\xfd\x42\x41\xf1\x77\x18\xfe\x0f\x39\x0d\x36\x4c\xa4\x63\x84\xb2\xcd\x3f\x48\x49\x8a\x8d\x31\x29\xf6\xd1\xee\xd5\x7f\xb2\xa8\xfa\xe9\x33\x95\x66\x31\x82\x47\x37\x12\x14\x16\x8e\x0a\xca\xda\x05\x6f\x69\xc9\x39\x82\xf1\x34\x28\x1e\x23\x14\xb6\xbc\xdf\x1a\x48\x89\xeb\xad\x6a\x08\x87\xd5\x81\x5a\x76\x1e\xc4\x2a\x22\xd7\xaf\x6c\xdf\x27\xb6\x8b\xbe\x01\x00\x00\xff\xff\x92\x2e\x35\x75\xe2\x00\x00\x00"),
|
||||||
|
|
|
@ -11,22 +11,33 @@ import (
|
||||||
"github.com/rclone/rclone/fs"
|
"github.com/rclone/rclone/fs"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetTemplate returns the HTML template for serving directories via HTTP
|
// GetTemplate returns the HTML template for serving directories via HTTP/Webdav
|
||||||
func GetTemplate() (tpl *template.Template, err error) {
|
func GetTemplate(tmpl string) (tpl *template.Template, err error) {
|
||||||
templateFile, err := Assets.Open("index.html")
|
var templateString string
|
||||||
if err != nil {
|
if tmpl == "" {
|
||||||
return nil, errors.Wrap(err, "get template open")
|
templateFile, err := Assets.Open("index.html")
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "get template open")
|
||||||
|
}
|
||||||
|
|
||||||
|
defer fs.CheckClose(templateFile, &err)
|
||||||
|
|
||||||
|
templateBytes, err := ioutil.ReadAll(templateFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "get template read")
|
||||||
|
}
|
||||||
|
|
||||||
|
templateString = string(templateBytes)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
templateFile, err := ioutil.ReadFile(tmpl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "get template open")
|
||||||
|
}
|
||||||
|
|
||||||
|
templateString = string(templateFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
defer fs.CheckClose(templateFile, &err)
|
|
||||||
|
|
||||||
templateBytes, err := ioutil.ReadAll(templateFile)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "get template read")
|
|
||||||
}
|
|
||||||
|
|
||||||
var templateString = string(templateBytes)
|
|
||||||
|
|
||||||
tpl, err = template.New("index").Parse(templateString)
|
tpl, err = template.New("index").Parse(templateString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "get template parse")
|
return nil, errors.Wrap(err, "get template parse")
|
||||||
|
|
|
@ -7,6 +7,9 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/rclone/rclone/fs"
|
"github.com/rclone/rclone/fs"
|
||||||
"github.com/rclone/rclone/fs/accounting"
|
"github.com/rclone/rclone/fs/accounting"
|
||||||
|
@ -15,26 +18,59 @@ import (
|
||||||
|
|
||||||
// DirEntry is a directory entry
|
// DirEntry is a directory entry
|
||||||
type DirEntry struct {
|
type DirEntry struct {
|
||||||
remote string
|
remote string
|
||||||
URL string
|
URL string
|
||||||
Leaf string
|
Leaf string
|
||||||
|
IsDir bool
|
||||||
|
Size int64
|
||||||
|
ModTime time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
// Directory represents a directory
|
// Directory represents a directory
|
||||||
type Directory struct {
|
type Directory struct {
|
||||||
DirRemote string
|
DirRemote string
|
||||||
Title string
|
Title string
|
||||||
|
Name string
|
||||||
Entries []DirEntry
|
Entries []DirEntry
|
||||||
Query string
|
Query string
|
||||||
HTMLTemplate *template.Template
|
HTMLTemplate *template.Template
|
||||||
|
Breadcrumb []Crumb
|
||||||
|
Sort string
|
||||||
|
Order string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crumb is a breadcrumb entry
|
||||||
|
type Crumb struct {
|
||||||
|
Link string
|
||||||
|
Text string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDirectory makes an empty Directory
|
// NewDirectory makes an empty Directory
|
||||||
func NewDirectory(dirRemote string, htmlTemplate *template.Template) *Directory {
|
func NewDirectory(dirRemote string, htmlTemplate *template.Template) *Directory {
|
||||||
|
var breadcrumb []Crumb
|
||||||
|
|
||||||
|
// skip trailing slash
|
||||||
|
lpath := "/" + dirRemote
|
||||||
|
if lpath[len(lpath)-1] == '/' {
|
||||||
|
lpath = lpath[:len(lpath)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(lpath, "/")
|
||||||
|
for i := range parts {
|
||||||
|
txt := parts[i]
|
||||||
|
if i == 0 && parts[i] == "" {
|
||||||
|
txt = "/"
|
||||||
|
}
|
||||||
|
lnk := strings.Repeat("../", len(parts)-i-1)
|
||||||
|
breadcrumb = append(breadcrumb, Crumb{Link: lnk, Text: txt})
|
||||||
|
}
|
||||||
|
|
||||||
d := &Directory{
|
d := &Directory{
|
||||||
DirRemote: dirRemote,
|
DirRemote: dirRemote,
|
||||||
Title: fmt.Sprintf("Directory listing of /%s", dirRemote),
|
Title: fmt.Sprintf("Directory listing of /%s", dirRemote),
|
||||||
|
Name: fmt.Sprintf("/%s", dirRemote),
|
||||||
HTMLTemplate: htmlTemplate,
|
HTMLTemplate: htmlTemplate,
|
||||||
|
Breadcrumb: breadcrumb,
|
||||||
}
|
}
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
|
@ -48,6 +84,27 @@ func (d *Directory) SetQuery(queryParams url.Values) *Directory {
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddHTMLEntry adds an entry to that directory
|
||||||
|
func (d *Directory) AddHTMLEntry(remote string, isDir bool, size int64, modTime time.Time) {
|
||||||
|
leaf := path.Base(remote)
|
||||||
|
if leaf == "." {
|
||||||
|
leaf = ""
|
||||||
|
}
|
||||||
|
urlRemote := leaf
|
||||||
|
if isDir {
|
||||||
|
leaf += "/"
|
||||||
|
urlRemote += "/"
|
||||||
|
}
|
||||||
|
d.Entries = append(d.Entries, DirEntry{
|
||||||
|
remote: remote,
|
||||||
|
URL: rest.URLPathEscape(urlRemote) + d.Query,
|
||||||
|
Leaf: leaf,
|
||||||
|
IsDir: isDir,
|
||||||
|
Size: size,
|
||||||
|
ModTime: modTime,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// AddEntry adds an entry to that directory
|
// AddEntry adds an entry to that directory
|
||||||
func (d *Directory) AddEntry(remote string, isDir bool) {
|
func (d *Directory) AddEntry(remote string, isDir bool) {
|
||||||
leaf := path.Base(remote)
|
leaf := path.Base(remote)
|
||||||
|
@ -75,6 +132,95 @@ func Error(what interface{}, w http.ResponseWriter, text string, err error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ProcessQueryParams takes and sorts/orders based on the request sort/order parameters and defailt is namedirfist/asc
|
||||||
|
func (d *Directory) ProcessQueryParams(sortParm string, orderParm string) *Directory {
|
||||||
|
d.Sort = sortParm
|
||||||
|
d.Order = orderParm
|
||||||
|
|
||||||
|
var toSort sort.Interface
|
||||||
|
|
||||||
|
switch d.Sort {
|
||||||
|
case sortByName:
|
||||||
|
toSort = byName(*d)
|
||||||
|
case sortByNameDirFirst:
|
||||||
|
toSort = byNameDirFirst(*d)
|
||||||
|
case sortBySize:
|
||||||
|
toSort = bySize(*d)
|
||||||
|
case sortByTime:
|
||||||
|
toSort = byTime(*d)
|
||||||
|
default:
|
||||||
|
toSort = byNameDirFirst(*d)
|
||||||
|
}
|
||||||
|
if d.Order == "desc" && toSort != nil {
|
||||||
|
toSort = sort.Reverse(toSort)
|
||||||
|
}
|
||||||
|
if toSort != nil {
|
||||||
|
sort.Sort(toSort)
|
||||||
|
}
|
||||||
|
|
||||||
|
return d
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type byName Directory
|
||||||
|
type byNameDirFirst Directory
|
||||||
|
type bySize Directory
|
||||||
|
type byTime Directory
|
||||||
|
|
||||||
|
func (d byName) Len() int { return len(d.Entries) }
|
||||||
|
func (d byName) Swap(i, j int) { d.Entries[i], d.Entries[j] = d.Entries[j], d.Entries[i] }
|
||||||
|
|
||||||
|
func (d byName) Less(i, j int) bool {
|
||||||
|
return strings.ToLower(d.Entries[i].Leaf) < strings.ToLower(d.Entries[j].Leaf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d byNameDirFirst) Len() int { return len(d.Entries) }
|
||||||
|
func (d byNameDirFirst) Swap(i, j int) { d.Entries[i], d.Entries[j] = d.Entries[j], d.Entries[i] }
|
||||||
|
|
||||||
|
func (d byNameDirFirst) Less(i, j int) bool {
|
||||||
|
// sort by name if both are dir or file
|
||||||
|
if d.Entries[i].IsDir == d.Entries[j].IsDir {
|
||||||
|
return strings.ToLower(d.Entries[i].Leaf) < strings.ToLower(d.Entries[j].Leaf)
|
||||||
|
}
|
||||||
|
// sort dir ahead of file
|
||||||
|
return d.Entries[i].IsDir
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d bySize) Len() int { return len(d.Entries) }
|
||||||
|
func (d bySize) Swap(i, j int) { d.Entries[i], d.Entries[j] = d.Entries[j], d.Entries[i] }
|
||||||
|
|
||||||
|
func (d bySize) Less(i, j int) bool {
|
||||||
|
const directoryOffset = -1 << 31 // = -math.MinInt32
|
||||||
|
|
||||||
|
iSize, jSize := d.Entries[i].Size, d.Entries[j].Size
|
||||||
|
|
||||||
|
// directory sizes depend on the file system; to
|
||||||
|
// provide a consistent experience, put them up front
|
||||||
|
// and sort them by name
|
||||||
|
if d.Entries[i].IsDir {
|
||||||
|
iSize = directoryOffset
|
||||||
|
}
|
||||||
|
if d.Entries[j].IsDir {
|
||||||
|
jSize = directoryOffset
|
||||||
|
}
|
||||||
|
if d.Entries[i].IsDir && d.Entries[j].IsDir {
|
||||||
|
return strings.ToLower(d.Entries[i].Leaf) < strings.ToLower(d.Entries[j].Leaf)
|
||||||
|
}
|
||||||
|
|
||||||
|
return iSize < jSize
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d byTime) Len() int { return len(d.Entries) }
|
||||||
|
func (d byTime) Swap(i, j int) { d.Entries[i], d.Entries[j] = d.Entries[j], d.Entries[i] }
|
||||||
|
func (d byTime) Less(i, j int) bool { return d.Entries[i].ModTime.Before(d.Entries[j].ModTime) }
|
||||||
|
|
||||||
|
const (
|
||||||
|
sortByName = "name"
|
||||||
|
sortByNameDirFirst = "namedirfirst"
|
||||||
|
sortBySize = "size"
|
||||||
|
sortByTime = "time"
|
||||||
|
)
|
||||||
|
|
||||||
// Serve serves a directory
|
// Serve serves a directory
|
||||||
func (d *Directory) Serve(w http.ResponseWriter, r *http.Request) {
|
func (d *Directory) Serve(w http.ResponseWriter, r *http.Request) {
|
||||||
// Account the transfer
|
// Account the transfer
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/rclone/rclone/cmd/serve/httplib/serve/data"
|
"github.com/rclone/rclone/cmd/serve/httplib/serve/data"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
@ -15,7 +16,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetTemplate(t *testing.T) *template.Template {
|
func GetTemplate(t *testing.T) *template.Template {
|
||||||
htmlTemplate, err := data.GetTemplate()
|
htmlTemplate, err := data.GetTemplate("")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return htmlTemplate
|
return htmlTemplate
|
||||||
}
|
}
|
||||||
|
@ -35,6 +36,32 @@ func TestSetQuery(t *testing.T) {
|
||||||
assert.Equal(t, "", d.Query)
|
assert.Equal(t, "", d.Query)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAddHTMLEntry(t *testing.T) {
|
||||||
|
var modtime = time.Now()
|
||||||
|
var d = NewDirectory("z", GetTemplate(t))
|
||||||
|
d.AddHTMLEntry("", true, 0, modtime)
|
||||||
|
d.AddHTMLEntry("dir", true, 0, modtime)
|
||||||
|
d.AddHTMLEntry("a/b/c/d.txt", false, 64, modtime)
|
||||||
|
d.AddHTMLEntry("a/b/c/colon:colon.txt", false, 64, modtime)
|
||||||
|
d.AddHTMLEntry("\"quotes\".txt", false, 64, modtime)
|
||||||
|
assert.Equal(t, []DirEntry{
|
||||||
|
{remote: "", URL: "/", Leaf: "/", IsDir: true, Size: 0, ModTime: modtime},
|
||||||
|
{remote: "dir", URL: "dir/", Leaf: "dir/", IsDir: true, Size: 0, ModTime: modtime},
|
||||||
|
{remote: "a/b/c/d.txt", URL: "d.txt", Leaf: "d.txt", IsDir: false, Size: 64, ModTime: modtime},
|
||||||
|
{remote: "a/b/c/colon:colon.txt", URL: "./colon:colon.txt", Leaf: "colon:colon.txt", IsDir: false, Size: 64, ModTime: modtime},
|
||||||
|
{remote: "\"quotes\".txt", URL: "%22quotes%22.txt", Leaf: "\"quotes\".txt", Size: 64, IsDir: false, ModTime: modtime},
|
||||||
|
}, d.Entries)
|
||||||
|
|
||||||
|
// Now test with a query parameter
|
||||||
|
d = NewDirectory("z", GetTemplate(t)).SetQuery(url.Values{"potato": []string{"42"}})
|
||||||
|
d.AddHTMLEntry("file", false, 64, modtime)
|
||||||
|
d.AddHTMLEntry("dir", true, 0, modtime)
|
||||||
|
assert.Equal(t, []DirEntry{
|
||||||
|
{remote: "file", URL: "file?potato=42", Leaf: "file", IsDir: false, Size: 64, ModTime: modtime},
|
||||||
|
{remote: "dir", URL: "dir/?potato=42", Leaf: "dir/", IsDir: true, Size: 0, ModTime: modtime},
|
||||||
|
}, d.Entries)
|
||||||
|
}
|
||||||
|
|
||||||
func TestAddEntry(t *testing.T) {
|
func TestAddEntry(t *testing.T) {
|
||||||
var d = NewDirectory("z", GetTemplate(t))
|
var d = NewDirectory("z", GetTemplate(t))
|
||||||
d.AddEntry("", true)
|
d.AddEntry("", true)
|
||||||
|
|
2
cmd/serve/webdav/testdata/golden/index.html
vendored
2
cmd/serve/webdav/testdata/golden/index.html
vendored
|
@ -6,8 +6,8 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Directory listing of /</h1>
|
<h1>Directory listing of /</h1>
|
||||||
<a href="one%25.txt">one%.txt</a><br />
|
|
||||||
<a href="three/">three/</a><br />
|
<a href="three/">three/</a><br />
|
||||||
|
<a href="one%25.txt">one%.txt</a><br />
|
||||||
<a href="two.txt">two.txt</a><br />
|
<a href="two.txt">two.txt</a><br />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -204,6 +204,7 @@ func (w *WebDAV) serveDir(rw http.ResponseWriter, r *http.Request, dirRemote str
|
||||||
}
|
}
|
||||||
dir := node.(*vfs.Dir)
|
dir := node.(*vfs.Dir)
|
||||||
dirEntries, err := dir.ReadDirAll()
|
dirEntries, err := dir.ReadDirAll()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
serve.Error(dirRemote, rw, "Failed to list directory", err)
|
serve.Error(dirRemote, rw, "Failed to list directory", err)
|
||||||
return
|
return
|
||||||
|
@ -212,9 +213,13 @@ func (w *WebDAV) serveDir(rw http.ResponseWriter, r *http.Request, dirRemote str
|
||||||
// Make the entries for display
|
// Make the entries for display
|
||||||
directory := serve.NewDirectory(dirRemote, w.HTMLTemplate)
|
directory := serve.NewDirectory(dirRemote, w.HTMLTemplate)
|
||||||
for _, node := range dirEntries {
|
for _, node := range dirEntries {
|
||||||
directory.AddEntry(node.Path(), node.IsDir())
|
directory.AddHTMLEntry(node.Path(), node.IsDir(), node.Size(), node.ModTime())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sortParm := r.URL.Query().Get("sort")
|
||||||
|
orderParm := r.URL.Query().Get("order")
|
||||||
|
directory.ProcessQueryParams(sortParm, orderParm)
|
||||||
|
|
||||||
directory.Serve(rw, r)
|
directory.Serve(rw, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue