LEFT | RIGHT |
(no file at all) | |
1 // Copyright 2014 The Go Authors. All rights reserved. | 1 // Copyright 2014 The Go Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style | 2 // Use of this source code is governed by a BSD-style |
3 // license that can be found in the LICENSE file. | 3 // license that can be found in the LICENSE file. |
4 | 4 |
5 package webdav | 5 package webdav |
6 | 6 |
7 import ( | 7 import ( |
8 "errors" | 8 "errors" |
9 » "io" | 9 » "path" |
| 10 » "strconv" |
| 11 » "strings" |
| 12 » "sync" |
10 "time" | 13 "time" |
11 ) | 14 ) |
12 | 15 |
13 var ( | 16 var ( |
| 17 // ErrConfirmationFailed is returned by a LockSystem's Confirm method. |
14 ErrConfirmationFailed = errors.New("webdav: confirmation failed") | 18 ErrConfirmationFailed = errors.New("webdav: confirmation failed") |
15 » ErrForbidden = errors.New("webdav: forbidden") | 19 » // ErrForbidden is returned by a LockSystem's Unlock method. |
16 » ErrNoSuchLock = errors.New("webdav: no such lock") | 20 » ErrForbidden = errors.New("webdav: forbidden") |
| 21 » // ErrLocked is returned by a LockSystem's Create, Refresh and Unlock me
thods. |
| 22 » ErrLocked = errors.New("webdav: locked") |
| 23 » // ErrNoSuchLock is returned by a LockSystem's Refresh and Unlock method
s. |
| 24 » ErrNoSuchLock = errors.New("webdav: no such lock") |
17 ) | 25 ) |
18 | 26 |
19 // Condition can match a WebDAV resource, based on a token or ETag. | 27 // Condition can match a WebDAV resource, based on a token or ETag. |
20 // Exactly one of Token and ETag should be non-empty. | 28 // Exactly one of Token and ETag should be non-empty. |
21 type Condition struct { | 29 type Condition struct { |
22 Not bool | 30 Not bool |
23 Token string | 31 Token string |
24 ETag string | 32 ETag string |
25 } | 33 } |
26 | 34 |
| 35 // Releaser releases previously confirmed lock claims. |
| 36 // |
| 37 // Calling Release does not unlock the lock, in the WebDAV UNLOCK sense, but |
| 38 // once LockSystem.Confirm has confirmed that a lock claim is valid, that lock |
| 39 // cannot be Confirmed again until it has been Released. |
| 40 type Releaser interface { |
| 41 Release() |
| 42 } |
| 43 |
| 44 // LockSystem manages access to a collection of named resources. The elements |
| 45 // in a lock name are separated by slash ('/', U+002F) characters, regardless |
| 46 // of host operating system convention. |
27 type LockSystem interface { | 47 type LockSystem interface { |
28 » // TODO: comment that the conditions should be ANDed together. | 48 » // Confirm confirms that the caller can claim all of the locks specified
by |
29 » Confirm(path string, conditions ...Condition) (c io.Closer, err error) | 49 » // the given conditions, and that holding the union of all of those lock
s |
30 » // TODO: comment that token should be an absolute URI as defined by RFC
3986, | 50 » // gives exclusive access to the named resource. |
31 » // Section 4.3. In particular, it should not contain whitespace. | 51 » // |
32 » Create(path string, now time.Time, ld LockDetails) (token string, c io.C
loser, err error) | 52 » // Exactly one of r and err will be non-nil. If r is non-nil, all of the |
33 » Refresh(token string, now time.Time, duration time.Duration) (ld LockDet
ails, c io.Closer, err error) | 53 » // requested locks are held until r.Release is called. |
34 » Unlock(token string) error | 54 » // |
35 } | 55 » // If Confirm returns ErrConfirmationFailed then the Handler will contin
ue |
36 | 56 » // to try any other set of locks presented (a WebDAV HTTP request can |
| 57 » // present more than one set of locks). If it returns any other non-nil |
| 58 » // error, the Handler will write a "500 Internal Server Error" HTTP stat
us. |
| 59 » Confirm(now time.Time, name string, conditions ...Condition) (r Releaser
, err error) |
| 60 |
| 61 » // Create creates a lock with the given depth, duration, owner and root |
| 62 » // (name). The depth will either be negative (meaning infinite) or zero. |
| 63 » // |
| 64 » // If Create returns ErrLocked then the Handler will write a "423 Locked
" |
| 65 » // HTTP status. If it returns any other non-nil error, the Handler will |
| 66 » // write a "500 Internal Server Error" HTTP status. |
| 67 » // |
| 68 » // See http://www.webdav.org/specs/rfc4918.html#rfc.section.9.10.6 for |
| 69 » // when to use each error. |
| 70 » // |
| 71 » // The token returned identifies the created lock. It should be an absol
ute |
| 72 » // URI as defined by RFC 3986, Section 4.3. In particular, it should not |
| 73 » // contain whitespace. |
| 74 » Create(now time.Time, details LockDetails) (token string, err error) |
| 75 |
| 76 » // Refresh refreshes the lock with the given token. |
| 77 » // |
| 78 » // If Refresh returns ErrLocked then the Handler will write a "423 Locke
d" |
| 79 » // HTTP Status. If Refresh returns ErrNoSuchLock then the Handler will w
rite |
| 80 » // a "412 Precondition Failed" HTTP Status. If it returns any other non-
nil |
| 81 » // error, the Handler will write a "500 Internal Server Error" HTTP stat
us. |
| 82 » // |
| 83 » // See http://www.webdav.org/specs/rfc4918.html#rfc.section.9.10.6 for |
| 84 » // when to use each error. |
| 85 » Refresh(now time.Time, token string, duration time.Duration) (details Lo
ckDetails, err error) |
| 86 |
| 87 » // Unlock unlocks the lock with the given token. |
| 88 » // |
| 89 » // If Unlock returns ErrForbidden then the Handler will write a "403 |
| 90 » // Forbidden" HTTP Status. If Unlock returns ErrLocked then the Handler |
| 91 » // will write a "423 Locked" HTTP status. If Unlock returns ErrNoSuchLoc
k |
| 92 » // then the Handler will write a "409 Conflict" HTTP Status. If it retur
ns |
| 93 » // any other non-nil error, the Handler will write a "500 Internal Serve
r |
| 94 » // Error" HTTP status. |
| 95 » // |
| 96 » // See http://www.webdav.org/specs/rfc4918.html#rfc.section.9.11.1 for |
| 97 » // when to use each error. |
| 98 » Unlock(now time.Time, token string) error |
| 99 } |
| 100 |
| 101 // LockDetails are a lock's metadata. |
37 type LockDetails struct { | 102 type LockDetails struct { |
38 » Depth int // Negative means infinite depth. | 103 » // Depth is the lock depth. A negative means infinite. |
39 » Duration time.Duration // Negative means unlimited duration. | 104 » Depth int |
40 » OwnerXML string // Verbatim XML. | 105 » // Duration is the lock timeout. A negative duration means infinite. |
41 » Path string | 106 » Duration time.Duration |
42 } | 107 » // OwnerXML is the verbatim <owner> XML given in a LOCK HTTP request. |
43 | 108 » OwnerXML string |
44 // TODO: a MemLS implementation. | 109 » // Root is the root resource name being locked. For a zero-depth lock, t
he |
| 110 » // root is the only resource being locked. |
| 111 » Root string |
| 112 } |
| 113 |
| 114 // NewMemLS returns a new in-memory LockSystem. |
| 115 func NewMemLS() LockSystem { |
| 116 » return &memLS{ |
| 117 » » byName: make(map[string]*memLSNode), |
| 118 » » byToken: make(map[string]*memLSNode), |
| 119 » } |
| 120 } |
| 121 |
| 122 type memLS struct { |
| 123 » mu sync.Mutex |
| 124 » byName map[string]*memLSNode |
| 125 » byToken map[string]*memLSNode |
| 126 » gen uint64 |
| 127 } |
| 128 |
| 129 func (m *memLS) nextToken() string { |
| 130 » // TODO: use a better token generator. |
| 131 » m.gen++ |
| 132 » return strconv.FormatUint(m.gen, 10) |
| 133 } |
| 134 |
| 135 func (m *memLS) collectExpiredNodes(now time.Time) { |
| 136 » // TODO: implement. |
| 137 } |
| 138 |
| 139 func (m *memLS) Confirm(now time.Time, name string, conditions ...Condition) (Re
leaser, error) { |
| 140 » m.mu.Lock() |
| 141 » defer m.mu.Unlock() |
| 142 » m.collectExpiredNodes(now) |
| 143 » name = path.Clean("/" + name) |
| 144 |
| 145 » // TODO: touch n.held. |
| 146 » panic("todo") |
| 147 } |
| 148 |
| 149 func (m *memLS) Create(now time.Time, details LockDetails) (string, error) { |
| 150 » m.mu.Lock() |
| 151 » defer m.mu.Unlock() |
| 152 » m.collectExpiredNodes(now) |
| 153 » name := path.Clean("/" + details.Root) |
| 154 |
| 155 » if !m.canCreate(name, details.Depth) { |
| 156 » » return "", ErrLocked |
| 157 » } |
| 158 » n := m.create(name) |
| 159 » n.token = m.nextToken() |
| 160 » m.byToken[n.token] = n |
| 161 » n.details = details |
| 162 » // TODO: set n.expiry. |
| 163 » return n.token, nil |
| 164 } |
| 165 |
| 166 func (m *memLS) Refresh(now time.Time, token string, duration time.Duration) (Lo
ckDetails, error) { |
| 167 » m.mu.Lock() |
| 168 » defer m.mu.Unlock() |
| 169 » m.collectExpiredNodes(now) |
| 170 |
| 171 » n := m.byToken[token] |
| 172 » if n == nil { |
| 173 » » return LockDetails{}, ErrNoSuchLock |
| 174 » } |
| 175 » if n.held { |
| 176 » » return LockDetails{}, ErrLocked |
| 177 » } |
| 178 » n.details.Duration = duration |
| 179 » // TODO: update n.expiry. |
| 180 » return n.details, nil |
| 181 } |
| 182 |
| 183 func (m *memLS) Unlock(now time.Time, token string) error { |
| 184 » m.mu.Lock() |
| 185 » defer m.mu.Unlock() |
| 186 » m.collectExpiredNodes(now) |
| 187 |
| 188 » n := m.byToken[token] |
| 189 » if n == nil { |
| 190 » » return ErrNoSuchLock |
| 191 » } |
| 192 » if n.held { |
| 193 » » return ErrLocked |
| 194 » } |
| 195 » m.remove(n) |
| 196 » return nil |
| 197 } |
| 198 |
| 199 func (m *memLS) canCreate(name string, depth int) bool { |
| 200 » return walkToRoot(name, func(name0 string, first bool) bool { |
| 201 » » n := m.byName[name0] |
| 202 » » if n == nil { |
| 203 » » » return true |
| 204 » » } |
| 205 » » if first { |
| 206 » » » if n.token != "" { |
| 207 » » » » // The target node is already locked. |
| 208 » » » » return false |
| 209 » » » } |
| 210 » » » if depth < 0 { |
| 211 » » » » // The requested lock depth is infinite, and the
fact that n exists |
| 212 » » » » // (n != nil) means that a descendent of the tar
get node is locked. |
| 213 » » » » return false |
| 214 » » » } |
| 215 » » } else if n.token != "" && n.details.Depth < 0 { |
| 216 » » » // An ancestor of the target node is locked with infinit
e depth. |
| 217 » » » return false |
| 218 » » } |
| 219 » » return true |
| 220 » }) |
| 221 } |
| 222 |
| 223 func (m *memLS) create(name string) (ret *memLSNode) { |
| 224 » walkToRoot(name, func(name0 string, first bool) bool { |
| 225 » » n := m.byName[name0] |
| 226 » » if n == nil { |
| 227 » » » n = &memLSNode{ |
| 228 » » » » details: LockDetails{ |
| 229 » » » » » Root: name0, |
| 230 » » » » }, |
| 231 » » » } |
| 232 » » » m.byName[name0] = n |
| 233 » » } |
| 234 » » n.refCount++ |
| 235 » » if first { |
| 236 » » » ret = n |
| 237 » » } |
| 238 » » return true |
| 239 » }) |
| 240 » return ret |
| 241 } |
| 242 |
| 243 func (m *memLS) remove(n *memLSNode) { |
| 244 » delete(m.byToken, n.token) |
| 245 » n.token = "" |
| 246 » walkToRoot(n.details.Root, func(name0 string, first bool) bool { |
| 247 » » x := m.byName[name0] |
| 248 » » x.refCount-- |
| 249 » » if x.refCount == 0 { |
| 250 » » » delete(m.byName, name0) |
| 251 » » } |
| 252 » » return true |
| 253 » }) |
| 254 } |
| 255 |
| 256 func walkToRoot(name string, f func(name0 string, first bool) bool) bool { |
| 257 » for first := true; ; first = false { |
| 258 » » if !f(name, first) { |
| 259 » » » return false |
| 260 » » } |
| 261 » » if name == "/" { |
| 262 » » » break |
| 263 » » } |
| 264 » » name = name[:strings.LastIndex(name, "/")] |
| 265 » » if name == "" { |
| 266 » » » name = "/" |
| 267 » » } |
| 268 » } |
| 269 » return true |
| 270 } |
| 271 |
| 272 type memLSNode struct { |
| 273 » // details are the lock metadata. Even if this node's name is not explic
itly locked, |
| 274 » // details.Root will still equal the node's name. |
| 275 » details LockDetails |
| 276 » // token is the unique identifier for this node's lock. An empty token m
eans that |
| 277 » // this node is not explicitly locked. |
| 278 » token string |
| 279 » // refCount is the number of self-or-descendent nodes that are explicitl
y locked. |
| 280 » refCount int |
| 281 » // expiry is when this node's lock expires. |
| 282 » expiry time.Time |
| 283 » // held is whether this node's lock is actively held by a Confirm call. |
| 284 » held bool |
| 285 } |
LEFT | RIGHT |