webdav enable dir renader

v3
veypi 1 month ago
parent 2766dd99bd
commit a4bb05d60d

@ -0,0 +1,89 @@
//
// dir_render.go
// Copyright (C) 2024 veypi <i@veypi.com>
// 2024-10-18 19:35
// Distributed under terms of the GPL license.
//
package webdav
import (
"fmt"
"io/fs"
"net/http"
"net/url"
"sort"
"strings"
"github.com/veypi/utils/logv"
)
type anyDirs interface {
len() int
name(i int) string
isDir(i int) bool
}
type fileInfoDirs []fs.FileInfo
func (d fileInfoDirs) len() int { return len(d) }
func (d fileInfoDirs) isDir(i int) bool { return d[i].IsDir() }
func (d fileInfoDirs) name(i int) string { return d[i].Name() }
type dirEntryDirs []fs.DirEntry
func (d dirEntryDirs) len() int { return len(d) }
func (d dirEntryDirs) isDir(i int) bool { return d[i].IsDir() }
func (d dirEntryDirs) name(i int) string { return d[i].Name() }
func dirList(w http.ResponseWriter, r *http.Request, f File) {
// Prefer to use ReadDir instead of Readdir,
// because the former doesn't require calling
// Stat on every entry of a directory on Unix.
var dirs anyDirs
var err error
if d, ok := f.(fs.ReadDirFile); ok {
var list dirEntryDirs
list, err = d.ReadDir(-1)
dirs = list
} else {
var list fileInfoDirs
list, err = f.Readdir(-1)
dirs = list
}
if err != nil {
logv.Warn().Msgf("http: error reading directory: %v", err)
http.Error(w, "Error reading directory", http.StatusInternalServerError)
return
}
sort.Slice(dirs, func(i, j int) bool { return dirs.name(i) < dirs.name(j) })
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprintf(w, "<!doctype html>\n")
fmt.Fprintf(w, "<meta name=\"viewport\" content=\"width=device-width\">\n")
fmt.Fprintf(w, "<pre>\n")
for i, n := 0, dirs.len(); i < n; i++ {
name := dirs.name(i)
if dirs.isDir(i) {
name += "/"
}
// name may contain '?' or '#', which must be escaped to remain
// part of the URL path, and not indicate the start of a query
// string or fragment.
url := url.URL{Path: name}
fmt.Fprintf(w, "<a href=\"%s\">%s</a>\n", url.String(), htmlReplacer.Replace(name))
}
fmt.Fprintf(w, "</pre>\n")
}
var htmlReplacer = strings.NewReplacer(
"&", "&amp;",
"<", "&lt;",
">", "&gt;",
// "&#34;" is shorter than "&quot;".
`"`, "&#34;",
// "&#39;" is shorter than "&apos;" and apos was not in HTML until HTML5.
"'", "&#39;",
)

@ -22,6 +22,10 @@ func NewWebdav(p string) func(http.ResponseWriter, *http.Request) {
fs := &Handler{
FileSystem: Dir(p),
LockSystem: NewMemLS(),
EnableDirRender: true,
GenSubPathFunc: func(r *http.Request) string {
return ""
},
}
return fs.ServeHTTP
}
@ -29,6 +33,7 @@ func NewWebdav(p string) func(http.ResponseWriter, *http.Request) {
type Handler struct {
// Prefix is the URL path prefix to strip from WebDAV resource paths.
Prefix string
EnableDirRender bool
GenSubPathFunc func(*http.Request) string
// FileSystem is the virtual file system.
FileSystem FileSystem
@ -39,14 +44,22 @@ type Handler struct {
Logger func(*http.Request, error)
}
func (h *Handler) stripPrefix(p string) (string, int, error) {
if h.Prefix == "" {
return p, http.StatusOK, nil
func (h *Handler) stripPrefix(p string, r *http.Request) (np string, ns int, ne error) {
np = p
ns = http.StatusOK
if h.Prefix != "" {
if tmp := strings.TrimPrefix(p, h.Prefix); len(tmp) < len(p) {
np = tmp
} else {
return p, http.StatusNotFound, errPrefixMismatch
}
if r := strings.TrimPrefix(p, h.Prefix); len(r) < len(p) {
return r, http.StatusOK, nil
}
return p, http.StatusNotFound, errPrefixMismatch
if h.GenSubPathFunc != nil {
if tmp := h.GenSubPathFunc(r); tmp != "" && tmp != "/" {
np = tmp + np
}
}
return
}
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
@ -158,7 +171,7 @@ func (h *Handler) confirmLocks(r *http.Request, src, dst string) (release func()
if u.Host != r.Host {
continue
}
lsrc, status, err = h.stripPrefix(u.Path)
lsrc, status, err = h.stripPrefix(u.Path, r)
if err != nil {
return nil, status, err
}
@ -180,7 +193,7 @@ func (h *Handler) confirmLocks(r *http.Request, src, dst string) (release func()
}
func (h *Handler) handleOptions(w http.ResponseWriter, r *http.Request) (status int, err error) {
reqPath, status, err := h.stripPrefix(r.URL.Path)
reqPath, status, err := h.stripPrefix(r.URL.Path, r)
if err != nil {
return status, err
}
@ -202,7 +215,7 @@ func (h *Handler) handleOptions(w http.ResponseWriter, r *http.Request) (status
}
func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request) (status int, err error) {
reqPath, status, err := h.stripPrefix(r.URL.Path)
reqPath, status, err := h.stripPrefix(r.URL.Path, r)
if err != nil {
return status, err
}
@ -218,6 +231,10 @@ func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request) (sta
return http.StatusNotFound, err
}
if fi.IsDir() {
if h.EnableDirRender {
dirList(w, r, f)
return 0, nil
}
return http.StatusMethodNotAllowed, nil
}
etag, err := findETag(ctx, h.FileSystem, h.LockSystem, reqPath, fi)
@ -231,7 +248,7 @@ func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request) (sta
}
func (h *Handler) handleDelete(w http.ResponseWriter, r *http.Request) (status int, err error) {
reqPath, status, err := h.stripPrefix(r.URL.Path)
reqPath, status, err := h.stripPrefix(r.URL.Path, r)
if err != nil {
return status, err
}
@ -261,7 +278,7 @@ func (h *Handler) handleDelete(w http.ResponseWriter, r *http.Request) (status i
}
func (h *Handler) handlePut(w http.ResponseWriter, r *http.Request) (status int, err error) {
reqPath, status, err := h.stripPrefix(r.URL.Path)
reqPath, status, err := h.stripPrefix(r.URL.Path, r)
if err != nil {
return status, err
}
@ -303,7 +320,7 @@ func (h *Handler) handlePut(w http.ResponseWriter, r *http.Request) (status int,
}
func (h *Handler) handleMkcol(w http.ResponseWriter, r *http.Request) (status int, err error) {
reqPath, status, err := h.stripPrefix(r.URL.Path)
reqPath, status, err := h.stripPrefix(r.URL.Path, r)
if err != nil {
return status, err
}
@ -340,12 +357,12 @@ func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request) (status
return http.StatusBadGateway, errInvalidDestination
}
src, status, err := h.stripPrefix(r.URL.Path)
src, status, err := h.stripPrefix(r.URL.Path, r)
if err != nil {
return status, err
}
dst, status, err := h.stripPrefix(u.Path)
dst, status, err := h.stripPrefix(u.Path, r)
if err != nil {
return status, err
}
@ -446,7 +463,7 @@ func (h *Handler) handleLock(w http.ResponseWriter, r *http.Request) (retStatus
return http.StatusBadRequest, errInvalidDepth
}
}
reqPath, status, err := h.stripPrefix(r.URL.Path)
reqPath, status, err := h.stripPrefix(r.URL.Path, r)
if err != nil {
return status, err
}
@ -520,7 +537,7 @@ func (h *Handler) handleUnlock(w http.ResponseWriter, r *http.Request) (status i
}
func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) (status int, err error) {
reqPath, status, err := h.stripPrefix(r.URL.Path)
reqPath, status, err := h.stripPrefix(r.URL.Path, r)
if err != nil {
return status, err
}
@ -589,7 +606,7 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) (status
}
func (h *Handler) handleProppatch(w http.ResponseWriter, r *http.Request) (status int, err error) {
reqPath, status, err := h.stripPrefix(r.URL.Path)
reqPath, status, err := h.stripPrefix(r.URL.Path, r)
if err != nil {
return status, err
}

@ -16,7 +16,7 @@ import (
"strings"
"testing"
ixml "golang.org/x/net/webdav/internal/xml"
ixml "oa/builtin/webdav/internal/xml"
)
func TestReadLockInfo(t *testing.T) {

Loading…
Cancel
Save