Commit ad9eb390 authored by Nigel Tao's avatar Nigel Tao

webdav: delete the PropSystem and MemPS types.

Change-Id: I569993723942f71599411a25ff31e97c1bc8875c
Reviewed-on: https://go-review.googlesource.com/10305Reviewed-by: 's avatarRobert Stepanek <robert.stepanek@gmail.com>
Reviewed-by: 's avatarNigel Tao <nigeltao@golang.org>
parent 7212a080
......@@ -32,12 +32,9 @@ var port = flag.Int("port", 9999, "server port")
func main() {
flag.Parse()
log.SetFlags(0)
fs := webdav.NewMemFS()
ls := webdav.NewMemLS()
h := &webdav.Handler{
FileSystem: fs,
LockSystem: ls,
PropSystem: webdav.NewMemPS(fs, ls),
FileSystem: webdav.NewMemFS(),
LockSystem: webdav.NewMemLS(),
Logger: func(r *http.Request, err error) {
litmus := r.Header.Get("X-Litmus")
if len(litmus) > 19 {
......
......@@ -15,50 +15,6 @@ import (
"strconv"
)
// TODO(nigeltao): eliminate the concept of a configurable PropSystem, and the
// MemPS implementation. Properties are now the responsibility of a File
// implementation, not a PropSystem implementation.
// PropSystem manages the properties of named resources. It allows finding
// and setting properties as defined in RFC 4918.
//
// The elements in a resource name are separated by slash ('/', U+002F)
// characters, regardless of host operating system convention.
type PropSystem interface {
// Find returns the status of properties named propnames for resource name.
//
// Each Propstat must have a unique status and each property name must
// only be part of one Propstat element.
Find(name string, propnames []xml.Name) ([]Propstat, error)
// TODO(nigeltao) merge Find and Allprop?
// Allprop returns the properties defined for resource name and the
// properties named in include. The returned Propstats are handled
// as in Find.
//
// Note that RFC 4918 defines 'allprop' to return the DAV: properties
// defined within the RFC plus dead properties. Other live properties
// should only be returned if they are named in 'include'.
//
// See http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND
Allprop(name string, include []xml.Name) ([]Propstat, error)
// Propnames returns the property names defined for resource name.
Propnames(name string) ([]xml.Name, error)
// Patch patches the properties of resource name.
//
// If all patches can be applied without conflict, Patch returns a slice
// of length one and a Propstat element of status 200, naming all patched
// properties. In case of conflict, Patch returns an arbitrary long slice
// and no Propstat element must have status 200. In either case, properties
// in Propstat must not have values.
//
// Note that the WebDAV RFC requires either all patches to succeed or none.
Patch(name string, patches []Proppatch) ([]Propstat, error)
}
// Proppatch describes a property update instruction as defined in RFC 4918.
// See http://www.webdav.org/specs/rfc4918.html#METHOD_PROPPATCH
type Proppatch struct {
......@@ -140,43 +96,28 @@ type DeadPropsHolder interface {
Patch([]Proppatch) ([]Propstat, error)
}
// memPS implements an in-memory PropSystem. It supports all of the mandatory
// live properties of RFC 4918.
type memPS struct {
fs FileSystem
ls LockSystem
}
// NewMemPS returns a new in-memory PropSystem implementation.
func NewMemPS(fs FileSystem, ls LockSystem) PropSystem {
return &memPS{
fs: fs,
ls: ls,
}
}
// liveProps contains all supported, protected DAV: properties.
var liveProps = map[xml.Name]struct {
// findFn implements the propfind function of this property. If nil,
// it indicates a hidden property.
findFn func(*memPS, string, os.FileInfo) (string, error)
findFn func(FileSystem, LockSystem, string, os.FileInfo) (string, error)
// dir is true if the property applies to directories.
dir bool
}{
xml.Name{Space: "DAV:", Local: "resourcetype"}: {
findFn: (*memPS).findResourceType,
findFn: findResourceType,
dir: true,
},
xml.Name{Space: "DAV:", Local: "displayname"}: {
findFn: (*memPS).findDisplayName,
findFn: findDisplayName,
dir: true,
},
xml.Name{Space: "DAV:", Local: "getcontentlength"}: {
findFn: (*memPS).findContentLength,
findFn: findContentLength,
dir: true,
},
xml.Name{Space: "DAV:", Local: "getlastmodified"}: {
findFn: (*memPS).findLastModified,
findFn: findLastModified,
dir: true,
},
xml.Name{Space: "DAV:", Local: "creationdate"}: {
......@@ -188,15 +129,15 @@ var liveProps = map[xml.Name]struct {
dir: true,
},
xml.Name{Space: "DAV:", Local: "getcontenttype"}: {
findFn: (*memPS).findContentType,
findFn: findContentType,
dir: true,
},
xml.Name{Space: "DAV:", Local: "getetag"}: {
findFn: (*memPS).findETag,
// memPS implements ETag as the concatenated hex values of a file's
findFn: findETag,
// findETag implements ETag as the concatenated hex values of a file's
// modification time and size. This is not a reliable synchronization
// mechanism for directories, so we do not advertise getetag for
// DAV collections.
// mechanism for directories, so we do not advertise getetag for DAV
// collections.
dir: false,
},
......@@ -205,8 +146,14 @@ var liveProps = map[xml.Name]struct {
xml.Name{Space: "DAV:", Local: "supportedlock"}: {},
}
func (ps *memPS) Find(name string, propnames []xml.Name) ([]Propstat, error) {
f, err := ps.fs.OpenFile(name, os.O_RDONLY, 0)
// TODO(nigeltao) merge props and allprop?
// Props returns the status of the properties named pnames for resource name.
//
// Each Propstat has a unique status and each property name will only be part
// of one Propstat element.
func props(fs FileSystem, ls LockSystem, name string, pnames []xml.Name) ([]Propstat, error) {
f, err := fs.OpenFile(name, os.O_RDONLY, 0)
if err != nil {
return nil, err
}
......@@ -224,7 +171,7 @@ func (ps *memPS) Find(name string, propnames []xml.Name) ([]Propstat, error) {
pstatOK := Propstat{Status: http.StatusOK}
pstatNotFound := Propstat{Status: http.StatusNotFound}
for _, pn := range propnames {
for _, pn := range pnames {
// If this file has dead properties, check if they contain pn.
if dp, ok := deadProps[pn]; ok {
pstatOK.Props = append(pstatOK.Props, dp)
......@@ -232,7 +179,7 @@ func (ps *memPS) Find(name string, propnames []xml.Name) ([]Propstat, error) {
}
// Otherwise, it must either be a live property or we don't know it.
if prop := liveProps[pn]; prop.findFn != nil && (prop.dir || !isDir) {
innerXML, err := prop.findFn(ps, name, fi)
innerXML, err := prop.findFn(fs, ls, name, fi)
if err != nil {
return nil, err
}
......@@ -249,8 +196,9 @@ func (ps *memPS) Find(name string, propnames []xml.Name) ([]Propstat, error) {
return makePropstats(pstatOK, pstatNotFound), nil
}
func (ps *memPS) Propnames(name string) ([]xml.Name, error) {
f, err := ps.fs.OpenFile(name, os.O_RDONLY, 0)
// Propnames returns the property names defined for resource name.
func propnames(fs FileSystem, ls LockSystem, name string) ([]xml.Name, error) {
f, err := fs.OpenFile(name, os.O_RDONLY, 0)
if err != nil {
return nil, err
}
......@@ -266,37 +214,47 @@ func (ps *memPS) Propnames(name string) ([]xml.Name, error) {
deadProps = dph.DeadProps()
}
propnames := make([]xml.Name, 0, len(liveProps)+len(deadProps))
pnames := make([]xml.Name, 0, len(liveProps)+len(deadProps))
for pn, prop := range liveProps {
if prop.findFn != nil && (prop.dir || !isDir) {
propnames = append(propnames, pn)
pnames = append(pnames, pn)
}
}
for pn := range deadProps {
propnames = append(propnames, pn)
pnames = append(pnames, pn)
}
return propnames, nil
return pnames, nil
}
func (ps *memPS) Allprop(name string, include []xml.Name) ([]Propstat, error) {
propnames, err := ps.Propnames(name)
// Allprop returns the properties defined for resource name and the properties
// named in include.
//
// Note that RFC 4918 defines 'allprop' to return the DAV: properties defined
// within the RFC plus dead properties. Other live properties should only be
// returned if they are named in 'include'.
//
// See http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND
func allprop(fs FileSystem, ls LockSystem, name string, include []xml.Name) ([]Propstat, error) {
pnames, err := propnames(fs, ls, name)
if err != nil {
return nil, err
}
// Add names from include if they are not already covered in propnames.
// Add names from include if they are not already covered in pnames.
nameset := make(map[xml.Name]bool)
for _, pn := range propnames {
for _, pn := range pnames {
nameset[pn] = true
}
for _, pn := range include {
if !nameset[pn] {
propnames = append(propnames, pn)
pnames = append(pnames, pn)
}
}
return ps.Find(name, propnames)
return props(fs, ls, name, pnames)
}
func (ps *memPS) Patch(name string, patches []Proppatch) ([]Propstat, error) {
// Patch patches the properties of resource name. The return values are
// constrained in the same manner as DeadPropsHolder.Patch.
func patch(fs FileSystem, ls LockSystem, name string, patches []Proppatch) ([]Propstat, error) {
conflict := false
loop:
for _, patch := range patches {
......@@ -327,7 +285,7 @@ loop:
return makePropstats(pstatForbidden, pstatFailedDep), nil
}
f, err := ps.fs.OpenFile(name, os.O_RDWR, 0)
f, err := fs.OpenFile(name, os.O_RDWR, 0)
if err != nil {
return nil, err
}
......@@ -358,14 +316,14 @@ loop:
return []Propstat{pstat}, nil
}
func (ps *memPS) findResourceType(name string, fi os.FileInfo) (string, error) {
func findResourceType(fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) {
if fi.IsDir() {
return `<collection xmlns="DAV:"/>`, nil
}
return "", nil
}
func (ps *memPS) findDisplayName(name string, fi os.FileInfo) (string, error) {
func findDisplayName(fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) {
if slashClean(name) == "/" {
// Hide the real name of a possibly prefixed root directory.
return "", nil
......@@ -373,16 +331,16 @@ func (ps *memPS) findDisplayName(name string, fi os.FileInfo) (string, error) {
return fi.Name(), nil
}
func (ps *memPS) findContentLength(name string, fi os.FileInfo) (string, error) {
func findContentLength(fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) {
return strconv.FormatInt(fi.Size(), 10), nil
}
func (ps *memPS) findLastModified(name string, fi os.FileInfo) (string, error) {
func findLastModified(fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) {
return fi.ModTime().Format(http.TimeFormat), nil
}
func (ps *memPS) findContentType(name string, fi os.FileInfo) (string, error) {
f, err := ps.fs.OpenFile(name, os.O_RDONLY, 0)
func findContentType(fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) {
f, err := fs.OpenFile(name, os.O_RDONLY, 0)
if err != nil {
return "", err
}
......@@ -400,7 +358,7 @@ func (ps *memPS) findContentType(name string, fi os.FileInfo) (string, error) {
return ctype, err
}
func (ps *memPS) findETag(name string, fi os.FileInfo) (string, error) {
func findETag(fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) {
return detectETag(fi), nil
}
......
......@@ -43,9 +43,9 @@ func TestMemPS(t *testing.T) {
type propOp struct {
op string
name string
propnames []xml.Name
pnames []xml.Name
patches []Proppatch
wantNames []xml.Name
wantPnames []xml.Name
wantPropstats []Propstat
}
......@@ -60,7 +60,7 @@ func TestMemPS(t *testing.T) {
propOp: []propOp{{
op: "propname",
name: "/dir",
wantNames: []xml.Name{
wantPnames: []xml.Name{
xml.Name{Space: "DAV:", Local: "resourcetype"},
xml.Name{Space: "DAV:", Local: "displayname"},
xml.Name{Space: "DAV:", Local: "getcontentlength"},
......@@ -70,7 +70,7 @@ func TestMemPS(t *testing.T) {
}, {
op: "propname",
name: "/file",
wantNames: []xml.Name{
wantPnames: []xml.Name{
xml.Name{Space: "DAV:", Local: "resourcetype"},
xml.Name{Space: "DAV:", Local: "displayname"},
xml.Name{Space: "DAV:", Local: "getcontentlength"},
......@@ -132,7 +132,7 @@ func TestMemPS(t *testing.T) {
}, {
op: "allprop",
name: "/file",
propnames: []xml.Name{
pnames: []xml.Name{
{"DAV:", "resourcetype"},
{"foo", "bar"},
},
......@@ -167,9 +167,9 @@ func TestMemPS(t *testing.T) {
desc: "propfind DAV:resourcetype",
buildfs: []string{"mkdir /dir", "touch /file"},
propOp: []propOp{{
op: "propfind",
name: "/dir",
propnames: []xml.Name{{"DAV:", "resourcetype"}},
op: "propfind",
name: "/dir",
pnames: []xml.Name{{"DAV:", "resourcetype"}},
wantPropstats: []Propstat{{
Status: http.StatusOK,
Props: []Property{{
......@@ -178,9 +178,9 @@ func TestMemPS(t *testing.T) {
}},
}},
}, {
op: "propfind",
name: "/file",
propnames: []xml.Name{{"DAV:", "resourcetype"}},
op: "propfind",
name: "/file",
pnames: []xml.Name{{"DAV:", "resourcetype"}},
wantPropstats: []Propstat{{
Status: http.StatusOK,
Props: []Property{{
......@@ -193,9 +193,9 @@ func TestMemPS(t *testing.T) {
desc: "propfind unsupported DAV properties",
buildfs: []string{"mkdir /dir"},
propOp: []propOp{{
op: "propfind",
name: "/dir",
propnames: []xml.Name{{"DAV:", "getcontentlanguage"}},
op: "propfind",
name: "/dir",
pnames: []xml.Name{{"DAV:", "getcontentlanguage"}},
wantPropstats: []Propstat{{
Status: http.StatusNotFound,
Props: []Property{{
......@@ -203,9 +203,9 @@ func TestMemPS(t *testing.T) {
}},
}},
}, {
op: "propfind",
name: "/dir",
propnames: []xml.Name{{"DAV:", "creationdate"}},
op: "propfind",
name: "/dir",
pnames: []xml.Name{{"DAV:", "creationdate"}},
wantPropstats: []Propstat{{
Status: http.StatusNotFound,
Props: []Property{{
......@@ -217,9 +217,9 @@ func TestMemPS(t *testing.T) {
desc: "propfind getetag for files but not for directories",
buildfs: []string{"mkdir /dir", "touch /file"},
propOp: []propOp{{
op: "propfind",
name: "/dir",
propnames: []xml.Name{{"DAV:", "getetag"}},
op: "propfind",
name: "/dir",
pnames: []xml.Name{{"DAV:", "getetag"}},
wantPropstats: []Propstat{{
Status: http.StatusNotFound,
Props: []Property{{
......@@ -227,9 +227,9 @@ func TestMemPS(t *testing.T) {
}},
}},
}, {
op: "propfind",
name: "/file",
propnames: []xml.Name{{"DAV:", "getetag"}},
op: "propfind",
name: "/file",
pnames: []xml.Name{{"DAV:", "getetag"}},
wantPropstats: []Propstat{{
Status: http.StatusOK,
Props: []Property{{
......@@ -291,9 +291,9 @@ func TestMemPS(t *testing.T) {
}},
}},
}, {
op: "propfind",
name: "/dir",
propnames: []xml.Name{{Space: "foo", Local: "bar"}},
op: "propfind",
name: "/dir",
pnames: []xml.Name{{Space: "foo", Local: "bar"}},
wantPropstats: []Propstat{{
Status: http.StatusOK,
Props: []Property{{
......@@ -332,9 +332,9 @@ func TestMemPS(t *testing.T) {
}},
}},
}, {
op: "propfind",
name: "/dir",
propnames: []xml.Name{{Space: "foo", Local: "bar"}},
op: "propfind",
name: "/dir",
pnames: []xml.Name{{Space: "foo", Local: "bar"}},
wantPropstats: []Propstat{{
Status: http.StatusNotFound,
Props: []Property{{
......@@ -368,7 +368,7 @@ func TestMemPS(t *testing.T) {
}, {
op: "propfind",
name: "/dir",
propnames: []xml.Name{
pnames: []xml.Name{
{Space: "foo", Local: "bar"},
{Space: "spam", Local: "ham"},
},
......@@ -400,7 +400,7 @@ func TestMemPS(t *testing.T) {
}, {
op: "propfind",
name: "/dir",
propnames: []xml.Name{
pnames: []xml.Name{
{Space: "foo", Local: "bar"},
{Space: "spam", Local: "ham"},
},
......@@ -438,7 +438,7 @@ func TestMemPS(t *testing.T) {
}, {
op: "propname",
name: "/file",
wantNames: []xml.Name{
wantPnames: []xml.Name{
xml.Name{Space: "DAV:", Local: "resourcetype"},
xml.Name{Space: "DAV:", Local: "displayname"},
xml.Name{Space: "DAV:", Local: "getcontentlength"},
......@@ -471,9 +471,9 @@ func TestMemPS(t *testing.T) {
desc: "bad: propfind unknown property",
buildfs: []string{"mkdir /dir"},
propOp: []propOp{{
op: "propfind",
name: "/dir",
propnames: []xml.Name{{"foo:", "bar"}},
op: "propfind",
name: "/dir",
pnames: []xml.Name{{"foo:", "bar"}},
wantPropstats: []Propstat{{
Status: http.StatusNotFound,
Props: []Property{{
......@@ -492,7 +492,6 @@ func TestMemPS(t *testing.T) {
fs = noDeadPropsFS{fs}
}
ls := NewMemLS()
ps := NewMemPS(fs, ls)
for _, op := range tc.propOp {
desc := fmt.Sprintf("%s: %s %s", tc.desc, op.op, op.name)
if err = calcProps(op.name, fs, op.wantPropstats); err != nil {
......@@ -503,23 +502,23 @@ func TestMemPS(t *testing.T) {
var propstats []Propstat
switch op.op {
case "propname":
names, err := ps.Propnames(op.name)
pnames, err := propnames(fs, ls, op.name)
if err != nil {
t.Errorf("%s: got error %v, want nil", desc, err)
continue
}
sort.Sort(byXMLName(names))
sort.Sort(byXMLName(op.wantNames))
if !reflect.DeepEqual(names, op.wantNames) {
t.Errorf("%s: names\ngot %q\nwant %q", desc, names, op.wantNames)
sort.Sort(byXMLName(pnames))
sort.Sort(byXMLName(op.wantPnames))
if !reflect.DeepEqual(pnames, op.wantPnames) {
t.Errorf("%s: pnames\ngot %q\nwant %q", desc, pnames, op.wantPnames)
}
continue
case "allprop":
propstats, err = ps.Allprop(op.name, op.propnames)
propstats, err = allprop(fs, ls, op.name, op.pnames)
case "propfind":
propstats, err = ps.Find(op.name, op.propnames)
propstats, err = props(fs, ls, op.name, op.pnames)
case "proppatch":
propstats, err = ps.Patch(op.name, op.patches)
propstats, err = patch(fs, ls, op.name, op.patches)
default:
t.Fatalf("%s: %s not implemented", desc, op.op)
}
......
......@@ -5,8 +5,6 @@
// Package webdav etc etc TODO.
package webdav // import "golang.org/x/net/webdav"
// TODO: ETag, properties.
import (
"encoding/xml"
"errors"
......@@ -45,8 +43,6 @@ type Handler struct {
FileSystem FileSystem
// LockSystem is the lock management system.
LockSystem LockSystem
// PropSystem is the property management system.
PropSystem PropSystem
// Logger is an optional error logger. If non-nil, it will be called
// for all HTTP requests.
Logger func(*http.Request, error)
......@@ -58,8 +54,6 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
status, err = http.StatusInternalServerError, errNoFileSystem
} else if h.LockSystem == nil {
status, err = http.StatusInternalServerError, errNoLockSystem
} else if h.PropSystem == nil {
status, err = http.StatusInternalServerError, errNoPropSystem
} else {
switch r.Method {
case "OPTIONS":
......@@ -209,7 +203,7 @@ func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request) (sta
if err != nil {
return http.StatusNotFound, err
}
pstats, err := h.PropSystem.Find(r.URL.Path, []xml.Name{
pstats, err := props(h.FileSystem, h.LockSystem, r.URL.Path, []xml.Name{
{Space: "DAV:", Local: "getetag"},
{Space: "DAV:", Local: "getcontenttype"},
})
......@@ -264,7 +258,7 @@ func (h *Handler) handlePut(w http.ResponseWriter, r *http.Request) (status int,
if closeErr != nil {
return http.StatusMethodNotAllowed, closeErr
}
pstats, err := h.PropSystem.Find(r.URL.Path, []xml.Name{
pstats, err := props(h.FileSystem, h.LockSystem, r.URL.Path, []xml.Name{
{Space: "DAV:", Local: "getetag"},
})
if err != nil {
......@@ -502,19 +496,19 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) (status
}
var pstats []Propstat
if pf.Propname != nil {
propnames, err := h.PropSystem.Propnames(path)
pnames, err := propnames(h.FileSystem, h.LockSystem, path)
if err != nil {
return err
}
pstat := Propstat{Status: http.StatusOK}
for _, xmlname := range propnames {
for _, xmlname := range pnames {
pstat.Props = append(pstat.Props, Property{XMLName: xmlname})
}
pstats = append(pstats, pstat)
} else if pf.Allprop != nil {
pstats, err = h.PropSystem.Allprop(path, pf.Prop)
pstats, err = allprop(h.FileSystem, h.LockSystem, path, pf.Prop)
} else {
pstats, err = h.PropSystem.Find(path, pf.Prop)
pstats, err = props(h.FileSystem, h.LockSystem, path, pf.Prop)
}
if err != nil {
return err
......@@ -550,7 +544,7 @@ func (h *Handler) handleProppatch(w http.ResponseWriter, r *http.Request) (statu
if err != nil {
return status, err
}
pstats, err := h.PropSystem.Patch(r.URL.Path, patches)
pstats, err := patch(h.FileSystem, h.LockSystem, r.URL.Path, patches)
if err != nil {
return http.StatusInternalServerError, err
}
......@@ -671,7 +665,6 @@ var (
errInvalidTimeout = errors.New("webdav: invalid timeout")
errNoFileSystem = errors.New("webdav: no file system")
errNoLockSystem = errors.New("webdav: no lock system")
errNoPropSystem = errors.New("webdav: no property system")
errNotADirectory = errors.New("webdav: not a directory")
errRecursionTooDeep = errors.New("webdav: recursion too deep")
errUnsupportedLockInfo = errors.New("webdav: unsupported lock info")
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment