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