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  }