Commit 67f25490 authored by Nigel Tao's avatar Nigel Tao

webdav: implement parseDepth; restrict LockDetails' depth to only zero

or infinity.

Change-Id: I3e3fba8e05a9c6c3db2240311a0813961db81849
Reviewed-on: https://go-review.googlesource.com/2535Reviewed-by: 's avatarDave Cheney <dave@cheney.net>
parent 46077d3c
...@@ -103,13 +103,6 @@ type LockDetails struct { ...@@ -103,13 +103,6 @@ type LockDetails struct {
// Root is the root resource name being locked. For a zero-depth lock, the // Root is the root resource name being locked. For a zero-depth lock, the
// root is the only resource being locked. // root is the only resource being locked.
Root string Root string
// Depth is the lock depth. A negative depth means infinite.
//
// TODO: should depth be restricted to just "0 or infinite" (i.e. change
// this field to "Recursive bool") or just "0 or 1 or infinite"? Is
// validating that the responsibility of the Handler or the LockSystem
// implementations?
Depth int
// Duration is the lock timeout. A negative duration means infinite. // Duration is the lock timeout. A negative duration means infinite.
Duration time.Duration Duration time.Duration
// OwnerXML is the verbatim <owner> XML given in a LOCK HTTP request. // OwnerXML is the verbatim <owner> XML given in a LOCK HTTP request.
...@@ -118,6 +111,9 @@ type LockDetails struct { ...@@ -118,6 +111,9 @@ type LockDetails struct {
// Does the OwnerXML field need to have more structure? See // Does the OwnerXML field need to have more structure? See
// https://codereview.appspot.com/175140043/#msg2 // https://codereview.appspot.com/175140043/#msg2
OwnerXML string OwnerXML string
// ZeroDepth is whether the lock has zero depth. If it does not have zero
// depth, it has infinite depth.
ZeroDepth bool
} }
// NewMemLS returns a new in-memory LockSystem. // NewMemLS returns a new in-memory LockSystem.
...@@ -161,7 +157,7 @@ func (m *memLS) Create(now time.Time, details LockDetails) (string, error) { ...@@ -161,7 +157,7 @@ func (m *memLS) Create(now time.Time, details LockDetails) (string, error) {
m.collectExpiredNodes(now) m.collectExpiredNodes(now)
name := path.Clean("/" + details.Root) name := path.Clean("/" + details.Root)
if !m.canCreate(name, details.Depth) { if !m.canCreate(name, details.ZeroDepth) {
return "", ErrLocked return "", ErrLocked
} }
n := m.create(name) n := m.create(name)
...@@ -205,7 +201,7 @@ func (m *memLS) Unlock(now time.Time, token string) error { ...@@ -205,7 +201,7 @@ func (m *memLS) Unlock(now time.Time, token string) error {
return nil return nil
} }
func (m *memLS) canCreate(name string, depth int) bool { func (m *memLS) canCreate(name string, zeroDepth bool) bool {
return walkToRoot(name, func(name0 string, first bool) bool { return walkToRoot(name, func(name0 string, first bool) bool {
n := m.byName[name0] n := m.byName[name0]
if n == nil { if n == nil {
...@@ -216,12 +212,12 @@ func (m *memLS) canCreate(name string, depth int) bool { ...@@ -216,12 +212,12 @@ func (m *memLS) canCreate(name string, depth int) bool {
// The target node is already locked. // The target node is already locked.
return false return false
} }
if depth < 0 { if !zeroDepth {
// The requested lock depth is infinite, and the fact that n exists // The requested lock depth is infinite, and the fact that n exists
// (n != nil) means that a descendent of the target node is locked. // (n != nil) means that a descendent of the target node is locked.
return false return false
} }
} else if n.token != "" && n.details.Depth < 0 { } else if n.token != "" && !n.details.ZeroDepth {
// An ancestor of the target node is locked with infinite depth. // An ancestor of the target node is locked with infinite depth.
return false return false
} }
......
...@@ -78,12 +78,12 @@ var lockTestNames = []string{ ...@@ -78,12 +78,12 @@ var lockTestNames = []string{
"/z/_/z", "/z/_/z",
} }
func lockTestDepth(name string) int { func lockTestZeroDepth(name string) bool {
switch name[len(name)-1] { switch name[len(name)-1] {
case 'i': case 'i':
return -1 return false
case 'z': case 'z':
return 0 return true
} }
panic(fmt.Sprintf("lock name %q did not end with 'i' or 'z'", name)) panic(fmt.Sprintf("lock name %q did not end with 'i' or 'z'", name))
} }
...@@ -94,16 +94,16 @@ func TestMemLSCanCreate(t *testing.T) { ...@@ -94,16 +94,16 @@ func TestMemLSCanCreate(t *testing.T) {
for _, name := range lockTestNames { for _, name := range lockTestNames {
_, err := m.Create(now, LockDetails{ _, err := m.Create(now, LockDetails{
Depth: lockTestDepth(name),
Duration: -1,
Root: name, Root: name,
Duration: -1,
ZeroDepth: lockTestZeroDepth(name),
}) })
if err != nil { if err != nil {
t.Fatalf("creating lock for %q: %v", name, err) t.Fatalf("creating lock for %q: %v", name, err)
} }
} }
wantCanCreate := func(name string, depth int) bool { wantCanCreate := func(name string, zeroDepth bool) bool {
for _, n := range lockTestNames { for _, n := range lockTestNames {
switch { switch {
case n == name: case n == name:
...@@ -112,7 +112,7 @@ func TestMemLSCanCreate(t *testing.T) { ...@@ -112,7 +112,7 @@ func TestMemLSCanCreate(t *testing.T) {
case strings.HasPrefix(n, name): case strings.HasPrefix(n, name):
// An existing lock would be a child of the proposed lock, // An existing lock would be a child of the proposed lock,
// which conflicts if the proposed lock has infinite depth. // which conflicts if the proposed lock has infinite depth.
if depth < 0 { if !zeroDepth {
return false return false
} }
case strings.HasPrefix(name, n): case strings.HasPrefix(name, n):
...@@ -128,11 +128,11 @@ func TestMemLSCanCreate(t *testing.T) { ...@@ -128,11 +128,11 @@ func TestMemLSCanCreate(t *testing.T) {
var check func(int, string) var check func(int, string)
check = func(recursion int, name string) { check = func(recursion int, name string) {
for _, depth := range []int{-1, 0} { for _, zeroDepth := range []bool{false, true} {
got := m.canCreate(name, depth) got := m.canCreate(name, zeroDepth)
want := wantCanCreate(name, depth) want := wantCanCreate(name, zeroDepth)
if got != want { if got != want {
t.Errorf("canCreate name=%q depth=%d: got %t, want %t", name, depth, got, want) t.Errorf("canCreate name=%q zeroDepth=%d: got %t, want %t", name, zeroDepth, got, want)
} }
} }
if recursion == 6 { if recursion == 6 {
...@@ -162,9 +162,9 @@ func TestMemLSCreateUnlock(t *testing.T) { ...@@ -162,9 +162,9 @@ func TestMemLSCreateUnlock(t *testing.T) {
tokens[name] = "" tokens[name] = ""
} else { } else {
token, err := m.Create(now, LockDetails{ token, err := m.Create(now, LockDetails{
Depth: lockTestDepth(name),
Duration: -1,
Root: name, Root: name,
Duration: -1,
ZeroDepth: lockTestZeroDepth(name),
}) })
if err != nil { if err != nil {
t.Fatalf("iteration #%d: create %q: %v", i, name, err) t.Fatalf("iteration #%d: create %q: %v", i, name, err)
......
...@@ -38,6 +38,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { ...@@ -38,6 +38,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
status, err = http.StatusInternalServerError, errNoLockSystem status, err = http.StatusInternalServerError, errNoLockSystem
} else { } else {
// TODO: COPY, MOVE, PROPFIND, PROPPATCH methods. // TODO: COPY, MOVE, PROPFIND, PROPPATCH methods.
// MOVE needs to enforce its Depth constraint. See the parseDepth comment.
switch r.Method { switch r.Method {
case "OPTIONS": case "OPTIONS":
status, err = h.handleOptions(w, r) status, err = h.handleOptions(w, r)
...@@ -217,20 +218,22 @@ func (h *Handler) handleLock(w http.ResponseWriter, r *http.Request) (retStatus ...@@ -217,20 +218,22 @@ func (h *Handler) handleLock(w http.ResponseWriter, r *http.Request) (retStatus
} }
} else { } else {
depth, err := parseDepth(r.Header.Get("Depth")) // Section 9.10.3 says that "If no Depth header is submitted on a LOCK request,
if err != nil { // then the request MUST act as if a "Depth:infinity" had been submitted."
return http.StatusBadRequest, err depth := infiniteDepth
} if hdr := r.Header.Get("Depth"); hdr != "" {
if depth > 0 { depth = parseDepth(hdr)
if depth != 0 && depth != infiniteDepth {
// Section 9.10.3 says that "Values other than 0 or infinity must not be // Section 9.10.3 says that "Values other than 0 or infinity must not be
// used with the Depth header on a LOCK method". // used with the Depth header on a LOCK method".
return http.StatusBadRequest, errInvalidDepth return http.StatusBadRequest, errInvalidDepth
} }
}
ld = LockDetails{ ld = LockDetails{
Depth: depth, Root: r.URL.Path,
Duration: duration, Duration: duration,
OwnerXML: li.Owner.InnerXML, OwnerXML: li.Owner.InnerXML,
Root: r.URL.Path, ZeroDepth: depth == 0,
} }
token, err = h.LockSystem.Create(now, ld) token, err = h.LockSystem.Create(now, ld)
if err != nil { if err != nil {
...@@ -288,9 +291,29 @@ func (h *Handler) handleUnlock(w http.ResponseWriter, r *http.Request) (status i ...@@ -288,9 +291,29 @@ func (h *Handler) handleUnlock(w http.ResponseWriter, r *http.Request) (status i
} }
} }
func parseDepth(s string) (int, error) { const (
// TODO: implement. infiniteDepth = -1
return -1, nil invalidDepth = -2
)
// parseDepth maps the strings "0", "1" and "infinity" to 0, 1 and
// infiniteDepth. Parsing any other string returns invalidDepth.
//
// Different WebDAV methods have further constraints on valid depths:
// - PROPFIND has no further restrictions, as per section 9.1.
// - MOVE accepts only "infinity", as per section 9.2.2.
// - LOCK accepts only "0" or "infinity", as per section 9.10.3.
// These constraints are enforced by the handleXxx methods.
func parseDepth(s string) int {
switch s {
case "0":
return 0
case "1":
return 1
case "infinity":
return infiniteDepth
}
return invalidDepth
} }
func parseTimeout(s string) (time.Duration, error) { func parseTimeout(s string) (time.Duration, error) {
......
...@@ -13,7 +13,6 @@ import ( ...@@ -13,7 +13,6 @@ import (
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"strconv"
"time" "time"
) )
...@@ -65,8 +64,8 @@ func (c *countingReader) Read(p []byte) (int, error) { ...@@ -65,8 +64,8 @@ func (c *countingReader) Read(p []byte) (int, error) {
func writeLockInfo(w io.Writer, token string, ld LockDetails) (int, error) { func writeLockInfo(w io.Writer, token string, ld LockDetails) (int, error) {
depth := "infinity" depth := "infinity"
if d := ld.Depth; d >= 0 { if ld.ZeroDepth {
depth = strconv.Itoa(d) depth = "0"
} }
timeout := ld.Duration / time.Second timeout := ld.Duration / time.Second
return fmt.Fprintf(w, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"+ return fmt.Fprintf(w, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"+
......
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