1// Copyright 2014 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package webdav
6
7import (
8	"container/heap"
9	"errors"
10	"strconv"
11	"strings"
12	"sync"
13	"time"
14)
15
16var (
17	// ErrConfirmationFailed is returned by a LockSystem's Confirm method.
18	ErrConfirmationFailed = errors.New("webdav: confirmation failed")
19	// ErrForbidden is returned by a LockSystem's Unlock method.
20	ErrForbidden = errors.New("webdav: forbidden")
21	// ErrLocked is returned by a LockSystem's Create, Refresh and Unlock methods.
22	ErrLocked = errors.New("webdav: locked")
23	// ErrNoSuchLock is returned by a LockSystem's Refresh and Unlock methods.
24	ErrNoSuchLock = errors.New("webdav: no such lock")
25)
26
27// Condition can match a WebDAV resource, based on a token or ETag.
28// Exactly one of Token and ETag should be non-empty.
29type Condition struct {
30	Not   bool
31	Token string
32	ETag  string
33}
34
35// LockSystem manages access to a collection of named resources. The elements
36// in a lock name are separated by slash ('/', U+002F) characters, regardless
37// of host operating system convention.
38type LockSystem interface {
39	// Confirm confirms that the caller can claim all of the locks specified by
40	// the given conditions, and that holding the union of all of those locks
41	// gives exclusive access to all of the named resources. Up to two resources
42	// can be named. Empty names are ignored.
43	//
44	// Exactly one of release and err will be non-nil. If release is non-nil,
45	// all of the requested locks are held until release is called. Calling
46	// release does not unlock the lock, in the WebDAV UNLOCK sense, but once
47	// Confirm has confirmed that a lock claim is valid, that lock cannot be
48	// Confirmed again until it has been released.
49	//
50	// If Confirm returns ErrConfirmationFailed then the Handler will continue
51	// to try any other set of locks presented (a WebDAV HTTP request can
52	// present more than one set of locks). If it returns any other non-nil
53	// error, the Handler will write a "500 Internal Server Error" HTTP status.
54	Confirm(now time.Time, name0, name1 string, conditions ...Condition) (release func(), err error)
55
56	// Create creates a lock with the given depth, duration, owner and root
57	// (name). The depth will either be negative (meaning infinite) or zero.
58	//
59	// If Create returns ErrLocked then the Handler will write a "423 Locked"
60	// HTTP status. If it returns any other non-nil error, the Handler will
61	// write a "500 Internal Server Error" HTTP status.
62	//
63	// See http://www.webdav.org/specs/rfc4918.html#rfc.section.9.10.6 for
64	// when to use each error.
65	//
66	// The token returned identifies the created lock. It should be an absolute
67	// URI as defined by RFC 3986, Section 4.3. In particular, it should not
68	// contain whitespace.
69	Create(now time.Time, details LockDetails) (token string, err error)
70
71	// Refresh refreshes the lock with the given token.
72	//
73	// If Refresh returns ErrLocked then the Handler will write a "423 Locked"
74	// HTTP Status. If Refresh returns ErrNoSuchLock then the Handler will write
75	// a "412 Precondition Failed" HTTP Status. If it returns any other non-nil
76	// error, the Handler will write a "500 Internal Server Error" HTTP status.
77	//
78	// See http://www.webdav.org/specs/rfc4918.html#rfc.section.9.10.6 for
79	// when to use each error.
80	Refresh(now time.Time, token string, duration time.Duration) (LockDetails, error)
81
82	// Unlock unlocks the lock with the given token.
83	//
84	// If Unlock returns ErrForbidden then the Handler will write a "403
85	// Forbidden" HTTP Status. If Unlock returns ErrLocked then the Handler
86	// will write a "423 Locked" HTTP status. If Unlock returns ErrNoSuchLock
87	// then the Handler will write a "409 Conflict" HTTP Status. If it returns
88	// any other non-nil error, the Handler will write a "500 Internal Server
89	// Error" HTTP status.
90	//
91	// See http://www.webdav.org/specs/rfc4918.html#rfc.section.9.11.1 for
92	// when to use each error.
93	Unlock(now time.Time, token string) error
94}
95
96// LockDetails are a lock's metadata.
97type LockDetails struct {
98	// Root is the root resource name being locked. For a zero-depth lock, the
99	// root is the only resource being locked.
100	Root string
101	// Duration is the lock timeout. A negative duration means infinite.
102	Duration time.Duration
103	// OwnerXML is the verbatim <owner> XML given in a LOCK HTTP request.
104	//
105	// TODO: does the "verbatim" nature play well with XML namespaces?
106	// Does the OwnerXML field need to have more structure? See
107	// https://codereview.appspot.com/175140043/#msg2
108	OwnerXML string
109	// ZeroDepth is whether the lock has zero depth. If it does not have zero
110	// depth, it has infinite depth.
111	ZeroDepth bool
112}
113
114// NewMemLS returns a new in-memory LockSystem.
115func NewMemLS() LockSystem {
116	return &memLS{
117		byName:  make(map[string]*memLSNode),
118		byToken: make(map[string]*memLSNode),
119		gen:     uint64(time.Now().Unix()),
120	}
121}
122
123type memLS struct {
124	mu      sync.Mutex
125	byName  map[string]*memLSNode
126	byToken map[string]*memLSNode
127	gen     uint64
128	// byExpiry only contains those nodes whose LockDetails have a finite
129	// Duration and are yet to expire.
130	byExpiry byExpiry
131}
132
133func (m *memLS) nextToken() string {
134	m.gen++
135	return strconv.FormatUint(m.gen, 10)
136}
137
138func (m *memLS) collectExpiredNodes(now time.Time) {
139	for len(m.byExpiry) > 0 {
140		if now.Before(m.byExpiry[0].expiry) {
141			break
142		}
143		m.remove(m.byExpiry[0])
144	}
145}
146
147func (m *memLS) Confirm(now time.Time, name0, name1 string, conditions ...Condition) (func(), error) {
148	m.mu.Lock()
149	defer m.mu.Unlock()
150	m.collectExpiredNodes(now)
151
152	var n0, n1 *memLSNode
153	if name0 != "" {
154		if n0 = m.lookup(slashClean(name0), conditions...); n0 == nil {
155			return nil, ErrConfirmationFailed
156		}
157	}
158	if name1 != "" {
159		if n1 = m.lookup(slashClean(name1), conditions...); n1 == nil {
160			return nil, ErrConfirmationFailed
161		}
162	}
163
164	// Don't hold the same node twice.
165	if n1 == n0 {
166		n1 = nil
167	}
168
169	if n0 != nil {
170		m.hold(n0)
171	}
172	if n1 != nil {
173		m.hold(n1)
174	}
175	return func() {
176		m.mu.Lock()
177		defer m.mu.Unlock()
178		if n1 != nil {
179			m.unhold(n1)
180		}
181		if n0 != nil {
182			m.unhold(n0)
183		}
184	}, nil
185}
186
187// lookup returns the node n that locks the named resource, provided that n
188// matches at least one of the given conditions and that lock isn't held by
189// another party. Otherwise, it returns nil.
190//
191// n may be a parent of the named resource, if n is an infinite depth lock.
192func (m *memLS) lookup(name string, conditions ...Condition) (n *memLSNode) {
193	// TODO: support Condition.Not and Condition.ETag.
194	for _, c := range conditions {
195		n = m.byToken[c.Token]
196		if n == nil || n.held {
197			continue
198		}
199		if name == n.details.Root {
200			return n
201		}
202		if n.details.ZeroDepth {
203			continue
204		}
205		if n.details.Root == "/" || strings.HasPrefix(name, n.details.Root+"/") {
206			return n
207		}
208	}
209	return nil
210}
211
212func (m *memLS) hold(n *memLSNode) {
213	if n.held {
214		panic("webdav: memLS inconsistent held state")
215	}
216	n.held = true
217	if n.details.Duration >= 0 && n.byExpiryIndex >= 0 {
218		heap.Remove(&m.byExpiry, n.byExpiryIndex)
219	}
220}
221
222func (m *memLS) unhold(n *memLSNode) {
223	if !n.held {
224		panic("webdav: memLS inconsistent held state")
225	}
226	n.held = false
227	if n.details.Duration >= 0 {
228		heap.Push(&m.byExpiry, n)
229	}
230}
231
232func (m *memLS) Create(now time.Time, details LockDetails) (string, error) {
233	m.mu.Lock()
234	defer m.mu.Unlock()
235	m.collectExpiredNodes(now)
236	details.Root = slashClean(details.Root)
237
238	if !m.canCreate(details.Root, details.ZeroDepth) {
239		return "", ErrLocked
240	}
241	n := m.create(details.Root)
242	n.token = m.nextToken()
243	m.byToken[n.token] = n
244	n.details = details
245	if n.details.Duration >= 0 {
246		n.expiry = now.Add(n.details.Duration)
247		heap.Push(&m.byExpiry, n)
248	}
249	return n.token, nil
250}
251
252func (m *memLS) Refresh(now time.Time, token string, duration time.Duration) (LockDetails, error) {
253	m.mu.Lock()
254	defer m.mu.Unlock()
255	m.collectExpiredNodes(now)
256
257	n := m.byToken[token]
258	if n == nil {
259		return LockDetails{}, ErrNoSuchLock
260	}
261	if n.held {
262		return LockDetails{}, ErrLocked
263	}
264	if n.byExpiryIndex >= 0 {
265		heap.Remove(&m.byExpiry, n.byExpiryIndex)
266	}
267	n.details.Duration = duration
268	if n.details.Duration >= 0 {
269		n.expiry = now.Add(n.details.Duration)
270		heap.Push(&m.byExpiry, n)
271	}
272	return n.details, nil
273}
274
275func (m *memLS) Unlock(now time.Time, token string) error {
276	m.mu.Lock()
277	defer m.mu.Unlock()
278	m.collectExpiredNodes(now)
279
280	n := m.byToken[token]
281	if n == nil {
282		return ErrNoSuchLock
283	}
284	if n.held {
285		return ErrLocked
286	}
287	m.remove(n)
288	return nil
289}
290
291func (m *memLS) canCreate(name string, zeroDepth bool) bool {
292	return walkToRoot(name, func(name0 string, first bool) bool {
293		n := m.byName[name0]
294		if n == nil {
295			return true
296		}
297		if first {
298			if n.token != "" {
299				// The target node is already locked.
300				return false
301			}
302			if !zeroDepth {
303				// The requested lock depth is infinite, and the fact that n exists
304				// (n != nil) means that a descendent of the target node is locked.
305				return false
306			}
307		} else if n.token != "" && !n.details.ZeroDepth {
308			// An ancestor of the target node is locked with infinite depth.
309			return false
310		}
311		return true
312	})
313}
314
315func (m *memLS) create(name string) (ret *memLSNode) {
316	walkToRoot(name, func(name0 string, first bool) bool {
317		n := m.byName[name0]
318		if n == nil {
319			n = &memLSNode{
320				details: LockDetails{
321					Root: name0,
322				},
323				byExpiryIndex: -1,
324			}
325			m.byName[name0] = n
326		}
327		n.refCount++
328		if first {
329			ret = n
330		}
331		return true
332	})
333	return ret
334}
335
336func (m *memLS) remove(n *memLSNode) {
337	delete(m.byToken, n.token)
338	n.token = ""
339	walkToRoot(n.details.Root, func(name0 string, first bool) bool {
340		x := m.byName[name0]
341		x.refCount--
342		if x.refCount == 0 {
343			delete(m.byName, name0)
344		}
345		return true
346	})
347	if n.byExpiryIndex >= 0 {
348		heap.Remove(&m.byExpiry, n.byExpiryIndex)
349	}
350}
351
352func walkToRoot(name string, f func(name0 string, first bool) bool) bool {
353	for first := true; ; first = false {
354		if !f(name, first) {
355			return false
356		}
357		if name == "/" {
358			break
359		}
360		name = name[:strings.LastIndex(name, "/")]
361		if name == "" {
362			name = "/"
363		}
364	}
365	return true
366}
367
368type memLSNode struct {
369	// details are the lock metadata. Even if this node's name is not explicitly locked,
370	// details.Root will still equal the node's name.
371	details LockDetails
372	// token is the unique identifier for this node's lock. An empty token means that
373	// this node is not explicitly locked.
374	token string
375	// refCount is the number of self-or-descendent nodes that are explicitly locked.
376	refCount int
377	// expiry is when this node's lock expires.
378	expiry time.Time
379	// byExpiryIndex is the index of this node in memLS.byExpiry. It is -1
380	// if this node does not expire, or has expired.
381	byExpiryIndex int
382	// held is whether this node's lock is actively held by a Confirm call.
383	held bool
384}
385
386type byExpiry []*memLSNode
387
388func (b *byExpiry) Len() int {
389	return len(*b)
390}
391
392func (b *byExpiry) Less(i, j int) bool {
393	return (*b)[i].expiry.Before((*b)[j].expiry)
394}
395
396func (b *byExpiry) Swap(i, j int) {
397	(*b)[i], (*b)[j] = (*b)[j], (*b)[i]
398	(*b)[i].byExpiryIndex = i
399	(*b)[j].byExpiryIndex = j
400}
401
402func (b *byExpiry) Push(x interface{}) {
403	n := x.(*memLSNode)
404	n.byExpiryIndex = len(*b)
405	*b = append(*b, n)
406}
407
408func (b *byExpiry) Pop() interface{} {
409	i := len(*b) - 1
410	n := (*b)[i]
411	(*b)[i] = nil
412	n.byExpiryIndex = -1
413	*b = (*b)[:i]
414	return n
415}
416
417const infiniteTimeout = -1
418
419// parseTimeout parses the Timeout HTTP header, as per section 10.7. If s is
420// empty, an infiniteTimeout is returned.
421func parseTimeout(s string) (time.Duration, error) {
422	if s == "" {
423		return infiniteTimeout, nil
424	}
425	if i := strings.IndexByte(s, ','); i >= 0 {
426		s = s[:i]
427	}
428	s = strings.TrimSpace(s)
429	if s == "Infinite" {
430		return infiniteTimeout, nil
431	}
432	const pre = "Second-"
433	if !strings.HasPrefix(s, pre) {
434		return 0, errInvalidTimeout
435	}
436	s = s[len(pre):]
437	if s == "" || s[0] < '0' || '9' < s[0] {
438		return 0, errInvalidTimeout
439	}
440	n, err := strconv.ParseInt(s, 10, 64)
441	if err != nil || 1<<32-1 < n {
442		return 0, errInvalidTimeout
443	}
444	return time.Duration(n) * time.Second, nil
445}
446