diff --git a/internal/webdav/serve_resource.go b/internal/webdav/serve_resource.go index 387c8d2b..b7ec3086 100644 --- a/internal/webdav/serve_resource.go +++ b/internal/webdav/serve_resource.go @@ -6,11 +6,53 @@ import ( "io" "net/http" "net/textproto" + "net/url" + "sort" "strconv" "strings" "time" ) +var htmlReplacer = strings.NewReplacer( + "&", "&", + "<", "<", + ">", ">", + // """ is shorter than """. + `"`, """, + // "'" is shorter than "'" and apos was not in HTML until HTML5. + "'", "'", +) + +func serveCollection(w http.ResponseWriter, r *http.Request, ri ResourceInfo) { + if checkIfModifiedSince(r, ri) == condFalse { + writeNotModified(w) + return + } + w.Header().Set("Last-Modified", ri.ModTime().Format(http.TimeFormat)) + + files, err := ri.Readdir(r.Context()) + if err != nil { + http.Error(w, "Error reading directory", http.StatusInternalServerError) + return + } + sort.Slice(files, func(i, j int) bool { return files[i].Name() < files[j].Name() }) + + w.Header().Set("Content-Type", "text/html; charset=utf-8") + fmt.Fprintf(w, "
\n")
+ for _, f := range files {
+ name := f.Name()
+ if f.IsDir() {
+ 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, "%s\n", url.String(), htmlReplacer.Replace(name))
+ }
+ fmt.Fprintf(w, "\n")
+}
+
func serveResource(w http.ResponseWriter, r *http.Request, ri ResourceInfo) {
w.Header().Set("Etag", ri.ETag())
w.Header().Set("Last-Modified", ri.ModTime().Format(http.TimeFormat))
@@ -52,6 +94,11 @@ func serveResource(w http.ResponseWriter, r *http.Request, ri ResourceInfo) {
reader, err = ri.OpenRead(r.Context(), 0, -1)
}
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
w.Header().Set("Accept-Ranges", "bytes")
w.Header().Set("Content-Length", strconv.FormatInt(sendSize, 10))
w.WriteHeader(code)
@@ -70,13 +117,6 @@ func (r httpRange) contentRange(size int64) string {
return fmt.Sprintf("bytes %d-%d/%d", r.start, r.start+r.length-1, size)
}
-func (r httpRange) mimeHeader(contentType string, size int64) textproto.MIMEHeader {
- return textproto.MIMEHeader{
- "Content-Range": {r.contentRange(size)},
- "Content-Type": {contentType},
- }
-}
-
// parseRange parses a Range header string as per RFC 7233.
// errNoOverlap is returned if none of the ranges overlap.
func parseRange(s string, size int64) ([]httpRange, error) {
diff --git a/internal/webdav/webdav.go b/internal/webdav/webdav.go
index 2587706a..6a63f745 100644
--- a/internal/webdav/webdav.go
+++ b/internal/webdav/webdav.go
@@ -204,10 +204,11 @@ func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request) (sta
return http.StatusNotFound, err
}
if fi.IsDir() {
- return http.StatusMethodNotAllowed, nil
+ serveCollection(w, r, fi)
+ } else {
+ serveResource(w, r, fi)
}
- serveResource(w, r, fi)
return 0, nil
}