golang.org/x/net@v0.25.1-0.20240516223405-c87a5b62e243/webdav/lock.go (about) 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 5 package webdav 6 7 import ( 8 "container/heap" 9 "errors" 10 "strconv" 11 "strings" 12 "sync" 13 "time" 14 ) 15 16 var ( 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. 29 type 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. 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. 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. 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. 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. 115 func 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 123 type 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 133 func (m *memLS) nextToken() string { 134 m.gen++ 135 return strconv.FormatUint(m.gen, 10) 136 } 137 138 func (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 147 func (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. 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 { 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 212 func (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 222 func (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 232 func (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 252 func (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 275 func (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 291 func (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 315 func (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 336 func (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 352 func 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 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. 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 386 type byExpiry []*memLSNode 387 388 func (b *byExpiry) Len() int { 389 return len(*b) 390 } 391 392 func (b *byExpiry) Less(i, j int) bool { 393 return (*b)[i].expiry.Before((*b)[j].expiry) 394 } 395 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 400 } 401 402 func (b *byExpiry) Push(x interface{}) { 403 n := x.(*memLSNode) 404 n.byExpiryIndex = len(*b) 405 *b = append(*b, n) 406 } 407 408 func (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 417 const infiniteTimeout = -1 418 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) { 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 }