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.
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")
27
// Condition can match a WebDAV resource, based on a token or ETag.
28
// Exactly one of Token and ETag should be non-empty.
29
type Condition struct {
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.
38
type 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.
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.
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)
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.
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.
63
// See http://www.webdav.org/specs/rfc4918.html#rfc.section.9.10.6 for
64
// when to use each error.
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)
71
// Refresh refreshes the lock with the given token.
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.
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)
82
// Unlock unlocks the lock with the given token.
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.
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
96
// LockDetails are a lock's metadata.
97
type 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.
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.
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
109
// ZeroDepth is whether the lock has zero depth. If it does not have zero
110
// depth, it has infinite depth.
114
// NewMemLS returns a new in-memory LockSystem.
115
func NewMemLS() LockSystem {
117
byName: make(map[string]*memLSNode),
118
byToken: make(map[string]*memLSNode),
119
gen: uint64(time.Now().Unix()),
125
byName map[string]*memLSNode
126
byToken map[string]*memLSNode
128
// byExpiry only contains those nodes whose LockDetails have a finite
129
// Duration and are yet to expire.
133
func (m *memLS) nextToken() string {
135
return strconv.FormatUint(m.gen, 10)
138
func (m *memLS) collectExpiredNodes(now time.Time) {
139
for len(m.byExpiry) > 0 {
140
if now.Before(m.byExpiry[0].expiry) {
143
m.remove(m.byExpiry[0])
147
func (m *memLS) Confirm(now time.Time, name0, name1 string, conditions ...Condition) (func(), error) {
150
m.collectExpiredNodes(now)
152
var n0, n1 *memLSNode
154
if n0 = m.lookup(slashClean(name0), conditions...); n0 == nil {
155
return nil, ErrConfirmationFailed
159
if n1 = m.lookup(slashClean(name1), conditions...); n1 == nil {
160
return nil, ErrConfirmationFailed
164
// Don't hold the same node twice.
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.
191
// n may be a parent of the named resource, if n is an infinite depth lock.
192
func (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 {
199
if name == n.details.Root {
202
if n.details.ZeroDepth {
205
if n.details.Root == "/" || strings.HasPrefix(name, n.details.Root+"/") {
212
func (m *memLS) hold(n *memLSNode) {
214
panic("webdav: memLS inconsistent held state")
217
if n.details.Duration >= 0 && n.byExpiryIndex >= 0 {
218
heap.Remove(&m.byExpiry, n.byExpiryIndex)
222
func (m *memLS) unhold(n *memLSNode) {
224
panic("webdav: memLS inconsistent held state")
227
if n.details.Duration >= 0 {
228
heap.Push(&m.byExpiry, n)
232
func (m *memLS) Create(now time.Time, details LockDetails) (string, error) {
235
m.collectExpiredNodes(now)
236
details.Root = slashClean(details.Root)
238
if !m.canCreate(details.Root, details.ZeroDepth) {
241
n := m.create(details.Root)
242
n.token = m.nextToken()
243
m.byToken[n.token] = n
245
if n.details.Duration >= 0 {
246
n.expiry = now.Add(n.details.Duration)
247
heap.Push(&m.byExpiry, n)
252
func (m *memLS) Refresh(now time.Time, token string, duration time.Duration) (LockDetails, error) {
255
m.collectExpiredNodes(now)
257
n := m.byToken[token]
259
return LockDetails{}, ErrNoSuchLock
262
return LockDetails{}, ErrLocked
264
if n.byExpiryIndex >= 0 {
265
heap.Remove(&m.byExpiry, n.byExpiryIndex)
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)
272
return n.details, nil
275
func (m *memLS) Unlock(now time.Time, token string) error {
278
m.collectExpiredNodes(now)
280
n := m.byToken[token]
291
func (m *memLS) canCreate(name string, zeroDepth bool) bool {
292
return walkToRoot(name, func(name0 string, first bool) bool {
299
// The target node is already locked.
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.
307
} else if n.token != "" && !n.details.ZeroDepth {
308
// An ancestor of the target node is locked with infinite depth.
315
func (m *memLS) create(name string) (ret *memLSNode) {
316
walkToRoot(name, func(name0 string, first bool) bool {
320
details: LockDetails{
336
func (m *memLS) remove(n *memLSNode) {
337
delete(m.byToken, n.token)
339
walkToRoot(n.details.Root, func(name0 string, first bool) bool {
343
delete(m.byName, name0)
347
if n.byExpiryIndex >= 0 {
348
heap.Remove(&m.byExpiry, n.byExpiryIndex)
352
func walkToRoot(name string, f func(name0 string, first bool) bool) bool {
353
for first := true; ; first = false {
360
name = name[:strings.LastIndex(name, "/")]
368
type 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.
372
// token is the unique identifier for this node's lock. An empty token means that
373
// this node is not explicitly locked.
375
// refCount is the number of self-or-descendent nodes that are explicitly locked.
377
// expiry is when this node's lock expires.
379
// byExpiryIndex is the index of this node in memLS.byExpiry. It is -1
380
// if this node does not expire, or has expired.
382
// held is whether this node's lock is actively held by a Confirm call.
386
type byExpiry []*memLSNode
388
func (b *byExpiry) Len() int {
392
func (b *byExpiry) Less(i, j int) bool {
393
return (*b)[i].expiry.Before((*b)[j].expiry)
396
func (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
402
func (b *byExpiry) Push(x interface{}) {
404
n.byExpiryIndex = len(*b)
408
func (b *byExpiry) Pop() interface{} {
417
const infiniteTimeout = -1
419
// parseTimeout parses the Timeout HTTP header, as per section 10.7. If s is
420
// empty, an infiniteTimeout is returned.
421
func parseTimeout(s string) (time.Duration, error) {
423
return infiniteTimeout, nil
425
if i := strings.IndexByte(s, ','); i >= 0 {
428
s = strings.TrimSpace(s)
430
return infiniteTimeout, nil
432
const pre = "Second-"
433
if !strings.HasPrefix(s, pre) {
434
return 0, errInvalidTimeout
437
if s == "" || s[0] < '0' || '9' < s[0] {
438
return 0, errInvalidTimeout
440
n, err := strconv.ParseInt(s, 10, 64)
441
if err != nil || 1<<32-1 < n {
442
return 0, errInvalidTimeout
444
return time.Duration(n) * time.Second, nil