Skip to content

Commit

Permalink
cmd/webdav: support PROPPATCH method (#5041)
Browse files Browse the repository at this point in the history
  • Loading branch information
zhijian-pro authored Aug 5, 2024
1 parent cba7747 commit a300cae
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 20 deletions.
19 changes: 12 additions & 7 deletions cmd/webdav.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ func cmdWebDav() *cli.Command {
Name: "disallowList",
Usage: "disallow list a directory",
},
&cli.BoolFlag{
Name: "enable-proppatch",
Usage: "enable proppatch method support",
},
&cli.StringFlag{
Name: "log",
Usage: "path for WebDAV log",
Expand Down Expand Up @@ -83,13 +87,14 @@ func webdav(c *cli.Context) error {
listenAddr := c.Args().Get(1)
_, jfs := initForSvc(c, "webdav", metaUrl, listenAddr)
fs.StartHTTPServer(jfs, fs.WebdavConfig{
Addr: listenAddr,
DisallowList: c.Bool("disallowList"),
EnableGzip: c.Bool("gzip"),
Username: os.Getenv("WEBDAV_USER"),
Password: os.Getenv("WEBDAV_PASSWORD"),
CertFile: c.String("cert-file"),
KeyFile: c.String("key-file"),
Addr: listenAddr,
DisallowList: c.Bool("disallowList"),
EnableGzip: c.Bool("gzip"),
Username: os.Getenv("WEBDAV_USER"),
Password: os.Getenv("WEBDAV_PASSWORD"),
CertFile: c.String("cert-file"),
KeyFile: c.String("key-file"),
EnableProppatch: c.Bool("enable-proppatch"),
})
return jfs.Meta().CloseSession()
}
139 changes: 127 additions & 12 deletions pkg/fs/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ package fs
import (
"compress/gzip"
"context"
"encoding/json"
"encoding/xml"
"errors"
"io"
"net/http"
"os"
Expand Down Expand Up @@ -82,9 +85,10 @@ func econv(err error) error {
}

type webdavFS struct {
ctx meta.Context
fs *FileSystem
umask uint16
ctx meta.Context
fs *FileSystem
umask uint16
config WebdavConfig
}

func (hfs *webdavFS) Mkdir(ctx context.Context, name string, perm os.FileMode) error {
Expand Down Expand Up @@ -117,7 +121,7 @@ func (hfs *webdavFS) OpenFile(ctx context.Context, name string, flag int, perm o
return nil, err
}
}
return &davFile{f}, econv(err)
return &davFile{f, hfs.ctx, hfs.fs, hfs.config}, econv(err)
}

func (hfs *webdavFS) RemoveAll(ctx context.Context, name string) error {
Expand All @@ -135,6 +139,80 @@ func (hfs *webdavFS) Stat(ctx context.Context, name string) (os.FileInfo, error)

type davFile struct {
*File
mctx meta.Context
fs *FileSystem
config WebdavConfig
}

const webdavDeadProps = "webdav-dead-props"

type localProperty struct {
N xml.Name `json:"name"`
P webdav.Property `json:"property"`
}

func (f *davFile) DeadProps() (map[xml.Name]webdav.Property, error) {
if !f.config.EnableProppatch {
return nil, nil
}
result, err := f.fs.GetXattr(f.mctx, f.path, webdavDeadProps)
if err != 0 {
if errors.Is(err, meta.ENOATTR) {
return nil, nil
}
return nil, econv(err)
}

var lProperty []localProperty
if err := json.Unmarshal(result, &lProperty); err != nil {
return nil, econv(err)
}
var property = make(map[xml.Name]webdav.Property)
for _, p := range lProperty {
property[p.N] = p.P
}
return property, nil
}

func (f *davFile) Patch(patches []webdav.Proppatch) ([]webdav.Propstat, error) {
if !f.config.EnableProppatch {
return nil, nil
}
pstat := webdav.Propstat{Status: http.StatusOK}
deadProps, err := f.DeadProps()
if err != nil {
return nil, err
}
for _, patch := range patches {
for _, p := range patch.Props {
pstat.Props = append(pstat.Props, webdav.Property{XMLName: p.XMLName})
if patch.Remove && deadProps != nil {
delete(deadProps, p.XMLName)
continue
}
if deadProps == nil {
deadProps = map[xml.Name]webdav.Property{}
}
deadProps[p.XMLName] = p
}
}

if deadProps != nil {
var property []localProperty
for name, p := range deadProps {
property = append(property, localProperty{N: name, P: p})
}

jsonData, err := json.Marshal(&property)
if err != nil {
return nil, err
}
errno := f.fs.SetXattr(f.mctx, f.path, webdavDeadProps, jsonData, 0)
if errno != 0 {
return nil, econv(errno)
}
}
return []webdav.Propstat{pstat}, nil
}

func (f *davFile) Seek(offset int64, whence int) (int64, error) {
Expand Down Expand Up @@ -166,13 +244,14 @@ func (f *davFile) Close() error {
}

type WebdavConfig struct {
Addr string
DisallowList bool
EnableGzip bool
Username string
Password string
CertFile string
KeyFile string
Addr string
DisallowList bool
EnableProppatch bool
EnableGzip bool
Username string
Password string
CertFile string
KeyFile string
}

type indexHandler struct {
Expand Down Expand Up @@ -216,12 +295,48 @@ func (h *indexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
}
}

// The next line would normally be:
// http.Handle("/", h)
// but we wrap that HTTP handler h to cater for a special case.
//
// The propfind_invalid2 litmus test case expects an empty namespace prefix
// declaration to be an error. The FAQ in the webdav litmus test says:
//
// "What does the "propfind_invalid2" test check for?...
//
// If a request was sent with an XML body which included an empty namespace
// prefix declaration (xmlns:ns1=""), then the server must reject that with
// a "400 Bad Request" response, as it is invalid according to the XML
// Namespace specification."
//
// On the other hand, the Go standard library's encoding/xml package
// accepts an empty xmlns namespace, as per the discussion at
// https://github.com/golang/go/issues/8068
//
// Empty namespaces seem disallowed in the second (2006) edition of the XML
// standard, but allowed in a later edition. The grammar differs between
// http://www.w3.org/TR/2006/REC-xml-names-20060816/#ns-decl and
// http://www.w3.org/TR/REC-xml-names/#dt-prefix
//
// Thus, we assume that the propfind_invalid2 test is obsolete, and
// hard-code the 400 Bad Request response that the test expects.
if r.Header.Get("X-Litmus") == "props: 3 (propfind_invalid2)" {
http.Error(w, "400 Bad Request", http.StatusBadRequest)
return
}

if !h.EnableProppatch && r.Method == "PROPPATCH" {
http.Error(w, "The PROPPATCH method is not currently enabled,please add the --enable-proppatch parameter and run it again", http.StatusNotImplemented)
return
}

h.Handler.ServeHTTP(w, r)
}

func StartHTTPServer(fs *FileSystem, config WebdavConfig) {
ctx := meta.NewContext(uint32(os.Getpid()), uint32(os.Getuid()), []uint32{uint32(os.Getgid())})
hfs := &webdavFS{ctx, fs, uint16(utils.GetUmask())}
hfs := &webdavFS{ctx, fs, uint16(utils.GetUmask()), config}
srv := &webdav.Handler{
FileSystem: hfs,
LockSystem: webdav.NewMemLS(),
Expand Down
2 changes: 1 addition & 1 deletion pkg/fs/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import (

func TestWebdav(t *testing.T) {
jfs := createTestFS(t)
webdavFS := &webdavFS{meta.NewContext(uint32(os.Getpid()), uint32(os.Getuid()), []uint32{uint32(os.Getgid())}), jfs, uint16(utils.GetUmask())}
webdavFS := &webdavFS{meta.NewContext(uint32(os.Getpid()), uint32(os.Getuid()), []uint32{uint32(os.Getgid())}), jfs, uint16(utils.GetUmask()), WebdavConfig{EnableProppatch: true}}
ctx := context.Background()
_, err := webdavFS.Stat(ctx, "/")
if err != nil {
Expand Down

0 comments on commit a300cae

Please sign in to comment.