Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/fetch_upstream.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
ref: master # set the branch to merge to
fetch-depth: 0
Expand Down
119 changes: 116 additions & 3 deletions cmd/serve/webdav/webdav.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ package webdav

import (
"context"
"encoding/xml"
"errors"
"fmt"
"net/http"
"os"
"strconv"
"strings"
"time"

Expand Down Expand Up @@ -255,6 +257,52 @@ func (w *WebDAV) auth(user, pass string) (value interface{}, err error) {
return VFS, err
}

type webdavRW struct {
http.ResponseWriter
status int
}

func (rw *webdavRW) WriteHeader(statusCode int) {
rw.status = statusCode
rw.ResponseWriter.WriteHeader(statusCode)
}

func (rw *webdavRW) isSuccessfull() bool {
return rw.status == 0 || (rw.status >= 200 && rw.status <= 299)
}

func (w *WebDAV) postprocess(r *http.Request, remote string) {
// set modtime from requests, don't write to client because status is already written
switch r.Method {
case "COPY", "MOVE", "PUT":
VFS, err := w.getVFS(r.Context())
if err != nil {
fs.Errorf(nil, "Failed to get VFS: %v", err)
return
}

// Get the node
node, err := VFS.Stat(remote)
if err != nil {
fs.Errorf(nil, "Failed to stat node: %v", err)
return
}

mh := r.Header.Get("X-OC-Mtime")
if mh != "" {
modtimeUnix, err := strconv.ParseInt(mh, 10, 64)
if err == nil {
err = node.SetModTime(time.Unix(modtimeUnix, 0))
if err != nil {
fs.Errorf(nil, "Failed to set modtime: %v", err)
}
} else {
fs.Errorf(nil, "Failed to parse modtime: %v", err)
}
}
}
}

func (w *WebDAV) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
urlPath := r.URL.Path
isDir := strings.HasSuffix(urlPath, "/")
Expand All @@ -266,7 +314,12 @@ func (w *WebDAV) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
// Add URL Prefix back to path since webdavhandler needs to
// return absolute references.
r.URL.Path = w.opt.HTTP.BaseURL + r.URL.Path
w.webdavhandler.ServeHTTP(rw, r)
wrw := &webdavRW{ResponseWriter: rw}
w.webdavhandler.ServeHTTP(wrw, r)

if wrw.isSuccessfull() {
w.postprocess(r, remote)
}
}

// serveDir serves a directory index at dirRemote
Expand Down Expand Up @@ -356,7 +409,7 @@ func (w *WebDAV) OpenFile(ctx context.Context, name string, flags int, perm os.F
if err != nil {
return nil, err
}
return Handle{Handle: f, w: w}, nil
return Handle{Handle: f, w: w, ctx: ctx}, nil
}

// RemoveAll removes a file or a directory and its contents
Expand Down Expand Up @@ -404,7 +457,8 @@ func (w *WebDAV) Stat(ctx context.Context, name string) (fi os.FileInfo, err err
// Handle represents an open file
type Handle struct {
vfs.Handle
w *WebDAV
w *WebDAV
ctx context.Context
}

// Readdir reads directory entries from the handle
Expand All @@ -429,6 +483,65 @@ func (h Handle) Stat() (fi os.FileInfo, err error) {
return FileInfo{FileInfo: fi, w: h.w}, nil
}

// DeadProps returns extra properties about the handle
func (h Handle) DeadProps() (map[xml.Name]webdav.Property, error) {
var (
xmlName xml.Name
property webdav.Property
properties = make(map[xml.Name]webdav.Property)
)
if h.w.opt.HashType != hash.None {
entry := h.Handle.Node().DirEntry()
if o, ok := entry.(fs.Object); ok {
hash, err := o.Hash(h.ctx, h.w.opt.HashType)
if err == nil {
xmlName.Space = "http://owncloud.org/ns"
xmlName.Local = "checksums"
property.XMLName = xmlName
property.InnerXML = append(property.InnerXML, "<checksum xmlns=\"http://owncloud.org/ns\">"...)
property.InnerXML = append(property.InnerXML, strings.ToUpper(h.w.opt.HashType.String())...)
property.InnerXML = append(property.InnerXML, ':')
property.InnerXML = append(property.InnerXML, hash...)
property.InnerXML = append(property.InnerXML, "</checksum>"...)
properties[xmlName] = property
} else {
fs.Errorf(nil, "failed to calculate hash: %v", err)
}
}
}

xmlName.Space = "DAV:"
xmlName.Local = "lastmodified"
property.XMLName = xmlName
property.InnerXML = strconv.AppendInt(nil, h.Handle.Node().ModTime().Unix(), 10)
properties[xmlName] = property

return properties, nil
}

// Patch changes modtime of the underlying resources, it returns ok for all properties, the error is from setModtime if any
// FIXME does not check for invalid property and SetModTime error
func (h Handle) Patch(proppatches []webdav.Proppatch) ([]webdav.Propstat, error) {
var (
stat webdav.Propstat
err error
)
stat.Status = http.StatusOK
for _, patch := range proppatches {
for _, prop := range patch.Props {
stat.Props = append(stat.Props, webdav.Property{XMLName: prop.XMLName})
if prop.XMLName.Space == "DAV:" && prop.XMLName.Local == "lastmodified" {
var modtimeUnix int64
modtimeUnix, err = strconv.ParseInt(string(prop.InnerXML), 10, 64)
if err == nil {
err = h.Handle.Node().SetModTime(time.Unix(modtimeUnix, 0))
}
}
}
}
return []webdav.Propstat{stat}, err
}

// FileInfo represents info about a file satisfying os.FileInfo and
// also some additional interfaces for webdav for ETag and ContentType
type FileInfo struct {
Expand Down
2 changes: 1 addition & 1 deletion cmd/serve/webdav/webdav_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func TestWebDav(t *testing.T) {
// Config for the backend we'll use to connect to the server
config := configmap.Simple{
"type": "webdav",
"vendor": "other",
"vendor": "owncloud",
"url": w.Server.URLs()[0],
"user": testUser,
"pass": obscure.MustObscure(testPass),
Expand Down