Commit 72bfdce4 authored by Nigel Tao's avatar Nigel Tao

webdav: make properties belong to the File(System), not a PropSystem.

This makes properties consistent even after a COPY, MOVE or DELETE.

A follow-up CL will eliminate the concept of a configurable PropSystem
entirely.

See discussion at
https://groups.google.com/forum/#!topic/golang-dev/2_LiN6sf93A

Litmust test before/after:
<- summary for `props': of 30 tests run: 28 passed, 2 failed. 93.3%
<- summary for `props': of 30 tests run: 29 passed, 1 failed. 96.7%

Change-Id: I9bd09d9fb73e40f87306eaec2679945874af023d
Reviewed-on: https://go-review.googlesource.com/10241Reviewed-by: 's avatarNigel Tao <nigeltao@golang.org>
parent 5273a78d
......@@ -5,6 +5,7 @@
package webdav
import (
"encoding/xml"
"io"
"net/http"
"os"
......@@ -44,6 +45,9 @@ type FileSystem interface {
// A File is returned by a FileSystem's OpenFile method and can be served by a
// Handler.
//
// A File may optionally implement the DeadPropsHolder interface, if it can
// load and save dead properties.
type File interface {
http.File
io.Writer
......@@ -401,10 +405,11 @@ type memFSNode struct {
// children is protected by memFS.mu.
children map[string]*memFSNode
mu sync.Mutex
data []byte
mode os.FileMode
modTime time.Time
mu sync.Mutex
data []byte
mode os.FileMode
modTime time.Time
deadProps map[xml.Name]Property
}
func (n *memFSNode) stat(name string) *memFileInfo {
......@@ -418,6 +423,39 @@ func (n *memFSNode) stat(name string) *memFileInfo {
}
}
func (n *memFSNode) DeadProps() map[xml.Name]Property {
n.mu.Lock()
defer n.mu.Unlock()
if len(n.deadProps) == 0 {
return nil
}
ret := make(map[xml.Name]Property, len(n.deadProps))
for k, v := range n.deadProps {
ret[k] = v
}
return ret
}
func (n *memFSNode) Patch(patches []Proppatch) ([]Propstat, error) {
n.mu.Lock()
defer n.mu.Unlock()
pstat := Propstat{Status: http.StatusOK}
for _, patch := range patches {
for _, p := range patch.Props {
pstat.Props = append(pstat.Props, Property{XMLName: p.XMLName})
if patch.Remove {
delete(n.deadProps, p.XMLName)
continue
}
if n.deadProps == nil {
n.deadProps = map[xml.Name]Property{}
}
n.deadProps[p.XMLName] = p
}
}
return []Propstat{pstat}, nil
}
type memFileInfo struct {
name string
size int64
......@@ -443,6 +481,12 @@ type memFile struct {
pos int
}
// A *memFile implements the optional DeadPropsHolder interface.
var _ DeadPropsHolder = (*memFile)(nil)
func (f *memFile) DeadProps() map[xml.Name]Property { return f.n.DeadProps() }
func (f *memFile) Patch(patches []Proppatch) ([]Propstat, error) { return f.n.Patch(patches) }
func (f *memFile) Close() error {
return nil
}
......
......@@ -37,7 +37,7 @@ func main() {
http.Handle("/", &webdav.Handler{
FileSystem: fs,
LockSystem: ls,
PropSystem: webdav.NewMemPS(fs, ls, webdav.ReadWrite),
PropSystem: webdav.NewMemPS(fs, ls),
Logger: func(r *http.Request, err error) {
litmus := r.Header.Get("X-Litmus")
if len(litmus) > 19 {
......
This diff is collapsed.
......@@ -8,6 +8,7 @@ import (
"encoding/xml"
"fmt"
"net/http"
"os"
"reflect"
"sort"
"testing"
......@@ -49,10 +50,10 @@ func TestMemPS(t *testing.T) {
}
testCases := []struct {
desc string
mutability Mutability
buildfs []string
propOp []propOp
desc string
noDeadProps bool
buildfs []string
propOp []propOp
}{{
desc: "propname",
buildfs: []string{"mkdir /dir", "touch /file"},
......@@ -238,9 +239,9 @@ func TestMemPS(t *testing.T) {
}},
}},
}, {
desc: "proppatch property on read-only property system",
buildfs: []string{"mkdir /dir"},
mutability: ReadOnly,
desc: "proppatch property on no-dead-properties file system",
buildfs: []string{"mkdir /dir"},
noDeadProps: true,
propOp: []propOp{{
op: "proppatch",
name: "/dir",
......@@ -264,16 +265,16 @@ func TestMemPS(t *testing.T) {
}},
}},
wantPropstats: []Propstat{{
Status: http.StatusForbidden,
Status: http.StatusForbidden,
XMLError: `<error xmlns="DAV:"><cannot-modify-protected-property/></error>`,
Props: []Property{{
XMLName: xml.Name{Space: "DAV:", Local: "getetag"},
}},
}},
}},
}, {
desc: "proppatch dead property",
buildfs: []string{"mkdir /dir"},
mutability: ReadWrite,
desc: "proppatch dead property",
buildfs: []string{"mkdir /dir"},
propOp: []propOp{{
op: "proppatch",
name: "/dir",
......@@ -302,9 +303,8 @@ func TestMemPS(t *testing.T) {
}},
}},
}, {
desc: "proppatch dead property with failed dependency",
buildfs: []string{"mkdir /dir"},
mutability: ReadWrite,
desc: "proppatch dead property with failed dependency",
buildfs: []string{"mkdir /dir"},
propOp: []propOp{{
op: "proppatch",
name: "/dir",
......@@ -320,7 +320,8 @@ func TestMemPS(t *testing.T) {
}},
}},
wantPropstats: []Propstat{{
Status: http.StatusForbidden,
Status: http.StatusForbidden,
XMLError: `<error xmlns="DAV:"><cannot-modify-protected-property/></error>`,
Props: []Property{{
XMLName: xml.Name{Space: "DAV:", Local: "displayname"},
}},
......@@ -342,9 +343,8 @@ func TestMemPS(t *testing.T) {
}},
}},
}, {
desc: "proppatch remove dead property",
buildfs: []string{"mkdir /dir"},
mutability: ReadWrite,
desc: "proppatch remove dead property",
buildfs: []string{"mkdir /dir"},
propOp: []propOp{{
op: "proppatch",
name: "/dir",
......@@ -418,9 +418,8 @@ func TestMemPS(t *testing.T) {
}},
}},
}, {
desc: "propname with dead property",
buildfs: []string{"touch /file"},
mutability: ReadWrite,
desc: "propname with dead property",
buildfs: []string{"touch /file"},
propOp: []propOp{{
op: "proppatch",
name: "/file",
......@@ -450,9 +449,8 @@ func TestMemPS(t *testing.T) {
},
}},
}, {
desc: "proppatch remove unknown dead property",
buildfs: []string{"mkdir /dir"},
mutability: ReadWrite,
desc: "proppatch remove unknown dead property",
buildfs: []string{"mkdir /dir"},
propOp: []propOp{{
op: "proppatch",
name: "/dir",
......@@ -490,8 +488,11 @@ func TestMemPS(t *testing.T) {
if err != nil {
t.Fatalf("%s: cannot create test filesystem: %v", tc.desc, err)
}
if tc.noDeadProps {
fs = noDeadPropsFS{fs}
}
ls := NewMemLS()
ps := NewMemPS(fs, ls, tc.mutability)
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 {
......@@ -551,36 +552,43 @@ func cmpXMLName(a, b xml.Name) bool {
type byXMLName []xml.Name
func (b byXMLName) Len() int {
return len(b)
}
func (b byXMLName) Swap(i, j int) {
b[i], b[j] = b[j], b[i]
}
func (b byXMLName) Less(i, j int) bool {
return cmpXMLName(b[i], b[j])
}
func (b byXMLName) Len() int { return len(b) }
func (b byXMLName) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
func (b byXMLName) Less(i, j int) bool { return cmpXMLName(b[i], b[j]) }
type byPropname []Property
func (b byPropname) Len() int {
return len(b)
}
func (b byPropname) Swap(i, j int) {
b[i], b[j] = b[j], b[i]
}
func (b byPropname) Less(i, j int) bool {
return cmpXMLName(b[i].XMLName, b[j].XMLName)
}
func (b byPropname) Len() int { return len(b) }
func (b byPropname) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
func (b byPropname) Less(i, j int) bool { return cmpXMLName(b[i].XMLName, b[j].XMLName) }
type byStatus []Propstat
func (b byStatus) Len() int {
return len(b)
func (b byStatus) Len() int { return len(b) }
func (b byStatus) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
func (b byStatus) Less(i, j int) bool { return b[i].Status < b[j].Status }
type noDeadPropsFS struct {
FileSystem
}
func (b byStatus) Swap(i, j int) {
b[i], b[j] = b[j], b[i]
func (fs noDeadPropsFS) OpenFile(name string, flag int, perm os.FileMode) (File, error) {
f, err := fs.FileSystem.OpenFile(name, flag, perm)
if err != nil {
return nil, err
}
return noDeadPropsFile{f}, nil
}
func (b byStatus) Less(i, j int) bool {
return b[i].Status < b[j].Status
// noDeadPropsFile wraps a File but strips any optional DeadPropsHolder methods
// provided by the underlying File implementation.
type noDeadPropsFile struct {
f File
}
func (f noDeadPropsFile) Close() error { return f.f.Close() }
func (f noDeadPropsFile) Read(p []byte) (int, error) { return f.f.Read(p) }
func (f noDeadPropsFile) Readdir(count int) ([]os.FileInfo, error) { return f.f.Readdir(count) }
func (f noDeadPropsFile) Seek(off int64, whence int) (int64, error) { return f.f.Seek(off, whence) }
func (f noDeadPropsFile) Stat() (os.FileInfo, error) { return f.f.Stat() }
func (f noDeadPropsFile) Write(p []byte) (int, error) { return f.f.Write(p) }
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