Skip to content

Commit 67f2549

Browse files
committed
webdav: implement parseDepth; restrict LockDetails' depth to only zero
or infinity. Change-Id: I3e3fba8e05a9c6c3db2240311a0813961db81849 Reviewed-on: https://go-review.googlesource.com/2535 Reviewed-by: Dave Cheney <dave@cheney.net>
1 parent 46077d3 commit 67f2549

File tree

4 files changed

+62
-44
lines changed

4 files changed

+62
-44
lines changed

webdav/lock.go

+7-11
Original file line numberDiff line numberDiff line change
@@ -103,13 +103,6 @@ type LockDetails struct {
103103
// Root is the root resource name being locked. For a zero-depth lock, the
104104
// root is the only resource being locked.
105105
Root string
106-
// Depth is the lock depth. A negative depth means infinite.
107-
//
108-
// TODO: should depth be restricted to just "0 or infinite" (i.e. change
109-
// this field to "Recursive bool") or just "0 or 1 or infinite"? Is
110-
// validating that the responsibility of the Handler or the LockSystem
111-
// implementations?
112-
Depth int
113106
// Duration is the lock timeout. A negative duration means infinite.
114107
Duration time.Duration
115108
// OwnerXML is the verbatim <owner> XML given in a LOCK HTTP request.
@@ -118,6 +111,9 @@ type LockDetails struct {
118111
// Does the OwnerXML field need to have more structure? See
119112
// https://codereview.appspot.com/175140043/#msg2
120113
OwnerXML string
114+
// ZeroDepth is whether the lock has zero depth. If it does not have zero
115+
// depth, it has infinite depth.
116+
ZeroDepth bool
121117
}
122118

123119
// NewMemLS returns a new in-memory LockSystem.
@@ -161,7 +157,7 @@ func (m *memLS) Create(now time.Time, details LockDetails) (string, error) {
161157
m.collectExpiredNodes(now)
162158
name := path.Clean("/" + details.Root)
163159

164-
if !m.canCreate(name, details.Depth) {
160+
if !m.canCreate(name, details.ZeroDepth) {
165161
return "", ErrLocked
166162
}
167163
n := m.create(name)
@@ -205,7 +201,7 @@ func (m *memLS) Unlock(now time.Time, token string) error {
205201
return nil
206202
}
207203

208-
func (m *memLS) canCreate(name string, depth int) bool {
204+
func (m *memLS) canCreate(name string, zeroDepth bool) bool {
209205
return walkToRoot(name, func(name0 string, first bool) bool {
210206
n := m.byName[name0]
211207
if n == nil {
@@ -216,12 +212,12 @@ func (m *memLS) canCreate(name string, depth int) bool {
216212
// The target node is already locked.
217213
return false
218214
}
219-
if depth < 0 {
215+
if !zeroDepth {
220216
// The requested lock depth is infinite, and the fact that n exists
221217
// (n != nil) means that a descendent of the target node is locked.
222218
return false
223219
}
224-
} else if n.token != "" && n.details.Depth < 0 {
220+
} else if n.token != "" && !n.details.ZeroDepth {
225221
// An ancestor of the target node is locked with infinite depth.
226222
return false
227223
}

webdav/lock_test.go

+15-15
Original file line numberDiff line numberDiff line change
@@ -78,12 +78,12 @@ var lockTestNames = []string{
7878
"/z/_/z",
7979
}
8080

81-
func lockTestDepth(name string) int {
81+
func lockTestZeroDepth(name string) bool {
8282
switch name[len(name)-1] {
8383
case 'i':
84-
return -1
84+
return false
8585
case 'z':
86-
return 0
86+
return true
8787
}
8888
panic(fmt.Sprintf("lock name %q did not end with 'i' or 'z'", name))
8989
}
@@ -94,16 +94,16 @@ func TestMemLSCanCreate(t *testing.T) {
9494

9595
for _, name := range lockTestNames {
9696
_, err := m.Create(now, LockDetails{
97-
Depth: lockTestDepth(name),
98-
Duration: -1,
99-
Root: name,
97+
Root: name,
98+
Duration: -1,
99+
ZeroDepth: lockTestZeroDepth(name),
100100
})
101101
if err != nil {
102102
t.Fatalf("creating lock for %q: %v", name, err)
103103
}
104104
}
105105

106-
wantCanCreate := func(name string, depth int) bool {
106+
wantCanCreate := func(name string, zeroDepth bool) bool {
107107
for _, n := range lockTestNames {
108108
switch {
109109
case n == name:
@@ -112,7 +112,7 @@ func TestMemLSCanCreate(t *testing.T) {
112112
case strings.HasPrefix(n, name):
113113
// An existing lock would be a child of the proposed lock,
114114
// which conflicts if the proposed lock has infinite depth.
115-
if depth < 0 {
115+
if !zeroDepth {
116116
return false
117117
}
118118
case strings.HasPrefix(name, n):
@@ -128,11 +128,11 @@ func TestMemLSCanCreate(t *testing.T) {
128128

129129
var check func(int, string)
130130
check = func(recursion int, name string) {
131-
for _, depth := range []int{-1, 0} {
132-
got := m.canCreate(name, depth)
133-
want := wantCanCreate(name, depth)
131+
for _, zeroDepth := range []bool{false, true} {
132+
got := m.canCreate(name, zeroDepth)
133+
want := wantCanCreate(name, zeroDepth)
134134
if got != want {
135-
t.Errorf("canCreate name=%q depth=%d: got %t, want %t", name, depth, got, want)
135+
t.Errorf("canCreate name=%q zeroDepth=%d: got %t, want %t", name, zeroDepth, got, want)
136136
}
137137
}
138138
if recursion == 6 {
@@ -162,9 +162,9 @@ func TestMemLSCreateUnlock(t *testing.T) {
162162
tokens[name] = ""
163163
} else {
164164
token, err := m.Create(now, LockDetails{
165-
Depth: lockTestDepth(name),
166-
Duration: -1,
167-
Root: name,
165+
Root: name,
166+
Duration: -1,
167+
ZeroDepth: lockTestZeroDepth(name),
168168
})
169169
if err != nil {
170170
t.Fatalf("iteration #%d: create %q: %v", i, name, err)

webdav/webdav.go

+38-15
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
3838
status, err = http.StatusInternalServerError, errNoLockSystem
3939
} else {
4040
// TODO: COPY, MOVE, PROPFIND, PROPPATCH methods.
41+
// MOVE needs to enforce its Depth constraint. See the parseDepth comment.
4142
switch r.Method {
4243
case "OPTIONS":
4344
status, err = h.handleOptions(w, r)
@@ -217,20 +218,22 @@ func (h *Handler) handleLock(w http.ResponseWriter, r *http.Request) (retStatus
217218
}
218219

219220
} else {
220-
depth, err := parseDepth(r.Header.Get("Depth"))
221-
if err != nil {
222-
return http.StatusBadRequest, err
223-
}
224-
if depth > 0 {
225-
// Section 9.10.3 says that "Values other than 0 or infinity must not be
226-
// used with the Depth header on a LOCK method".
227-
return http.StatusBadRequest, errInvalidDepth
221+
// Section 9.10.3 says that "If no Depth header is submitted on a LOCK request,
222+
// then the request MUST act as if a "Depth:infinity" had been submitted."
223+
depth := infiniteDepth
224+
if hdr := r.Header.Get("Depth"); hdr != "" {
225+
depth = parseDepth(hdr)
226+
if depth != 0 && depth != infiniteDepth {
227+
// Section 9.10.3 says that "Values other than 0 or infinity must not be
228+
// used with the Depth header on a LOCK method".
229+
return http.StatusBadRequest, errInvalidDepth
230+
}
228231
}
229232
ld = LockDetails{
230-
Depth: depth,
231-
Duration: duration,
232-
OwnerXML: li.Owner.InnerXML,
233-
Root: r.URL.Path,
233+
Root: r.URL.Path,
234+
Duration: duration,
235+
OwnerXML: li.Owner.InnerXML,
236+
ZeroDepth: depth == 0,
234237
}
235238
token, err = h.LockSystem.Create(now, ld)
236239
if err != nil {
@@ -288,9 +291,29 @@ func (h *Handler) handleUnlock(w http.ResponseWriter, r *http.Request) (status i
288291
}
289292
}
290293

291-
func parseDepth(s string) (int, error) {
292-
// TODO: implement.
293-
return -1, nil
294+
const (
295+
infiniteDepth = -1
296+
invalidDepth = -2
297+
)
298+
299+
// parseDepth maps the strings "0", "1" and "infinity" to 0, 1 and
300+
// infiniteDepth. Parsing any other string returns invalidDepth.
301+
//
302+
// Different WebDAV methods have further constraints on valid depths:
303+
// - PROPFIND has no further restrictions, as per section 9.1.
304+
// - MOVE accepts only "infinity", as per section 9.2.2.
305+
// - LOCK accepts only "0" or "infinity", as per section 9.10.3.
306+
// These constraints are enforced by the handleXxx methods.
307+
func parseDepth(s string) int {
308+
switch s {
309+
case "0":
310+
return 0
311+
case "1":
312+
return 1
313+
case "infinity":
314+
return infiniteDepth
315+
}
316+
return invalidDepth
294317
}
295318

296319
func parseTimeout(s string) (time.Duration, error) {

webdav/xml.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import (
1313
"fmt"
1414
"io"
1515
"net/http"
16-
"strconv"
1716
"time"
1817
)
1918

@@ -65,8 +64,8 @@ func (c *countingReader) Read(p []byte) (int, error) {
6564

6665
func writeLockInfo(w io.Writer, token string, ld LockDetails) (int, error) {
6766
depth := "infinity"
68-
if d := ld.Depth; d >= 0 {
69-
depth = strconv.Itoa(d)
67+
if ld.ZeroDepth {
68+
depth = "0"
7069
}
7170
timeout := ld.Duration / time.Second
7271
return fmt.Fprintf(w, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"+

0 commit comments

Comments
 (0)