446 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			446 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
|  | // Copyright 2014 The Go Authors. All rights reserved. | ||
|  | // Use of this source code is governed by a BSD-style | ||
|  | // license that can be found in the LICENSE file. | ||
|  | 
 | ||
|  | package webdav | ||
|  | 
 | ||
|  | import ( | ||
|  | 	"container/heap" | ||
|  | 	"errors" | ||
|  | 	"strconv" | ||
|  | 	"strings" | ||
|  | 	"sync" | ||
|  | 	"time" | ||
|  | ) | ||
|  | 
 | ||
|  | var ( | ||
|  | 	// ErrConfirmationFailed is returned by a LockSystem's Confirm method. | ||
|  | 	ErrConfirmationFailed = errors.New("webdav: confirmation failed") | ||
|  | 	// ErrForbidden is returned by a LockSystem's Unlock method. | ||
|  | 	ErrForbidden = errors.New("webdav: forbidden") | ||
|  | 	// ErrLocked is returned by a LockSystem's Create, Refresh and Unlock methods. | ||
|  | 	ErrLocked = errors.New("webdav: locked") | ||
|  | 	// ErrNoSuchLock is returned by a LockSystem's Refresh and Unlock methods. | ||
|  | 	ErrNoSuchLock = errors.New("webdav: no such lock") | ||
|  | ) | ||
|  | 
 | ||
|  | // Condition can match a WebDAV resource, based on a token or ETag. | ||
|  | // Exactly one of Token and ETag should be non-empty. | ||
|  | type Condition struct { | ||
|  | 	Not   bool | ||
|  | 	Token string | ||
|  | 	ETag  string | ||
|  | } | ||
|  | 
 | ||
|  | // LockSystem manages access to a collection of named resources. The elements | ||
|  | // in a lock name are separated by slash ('/', U+002F) characters, regardless | ||
|  | // of host operating system convention. | ||
|  | type LockSystem interface { | ||
|  | 	// Confirm confirms that the caller can claim all of the locks specified by | ||
|  | 	// the given conditions, and that holding the union of all of those locks | ||
|  | 	// gives exclusive access to all of the named resources. Up to two resources | ||
|  | 	// can be named. Empty names are ignored. | ||
|  | 	// | ||
|  | 	// Exactly one of release and err will be non-nil. If release is non-nil, | ||
|  | 	// all of the requested locks are held until release is called. Calling | ||
|  | 	// release does not unlock the lock, in the WebDAV UNLOCK sense, but once | ||
|  | 	// Confirm has confirmed that a lock claim is valid, that lock cannot be | ||
|  | 	// Confirmed again until it has been released. | ||
|  | 	// | ||
|  | 	// If Confirm returns ErrConfirmationFailed then the Handler will continue | ||
|  | 	// to try any other set of locks presented (a WebDAV HTTP request can | ||
|  | 	// present more than one set of locks). If it returns any other non-nil | ||
|  | 	// error, the Handler will write a "500 Internal Server Error" HTTP status. | ||
|  | 	Confirm(now time.Time, name0, name1 string, conditions ...Condition) (release func(), err error) | ||
|  | 
 | ||
|  | 	// Create creates a lock with the given depth, duration, owner and root | ||
|  | 	// (name). The depth will either be negative (meaning infinite) or zero. | ||
|  | 	// | ||
|  | 	// If Create returns ErrLocked then the Handler will write a "423 Locked" | ||
|  | 	// HTTP status. If it returns any other non-nil error, the Handler will | ||
|  | 	// write a "500 Internal Server Error" HTTP status. | ||
|  | 	// | ||
|  | 	// See http://www.webdav.org/specs/rfc4918.html#rfc.section.9.10.6 for | ||
|  | 	// when to use each error. | ||
|  | 	// | ||
|  | 	// The token returned identifies the created lock. It should be an absolute | ||
|  | 	// URI as defined by RFC 3986, Section 4.3. In particular, it should not | ||
|  | 	// contain whitespace. | ||
|  | 	Create(now time.Time, details LockDetails) (token string, err error) | ||
|  | 
 | ||
|  | 	// Refresh refreshes the lock with the given token. | ||
|  | 	// | ||
|  | 	// If Refresh returns ErrLocked then the Handler will write a "423 Locked" | ||
|  | 	// HTTP Status. If Refresh returns ErrNoSuchLock then the Handler will write | ||
|  | 	// a "412 Precondition Failed" HTTP Status. If it returns any other non-nil | ||
|  | 	// error, the Handler will write a "500 Internal Server Error" HTTP status. | ||
|  | 	// | ||
|  | 	// See http://www.webdav.org/specs/rfc4918.html#rfc.section.9.10.6 for | ||
|  | 	// when to use each error. | ||
|  | 	Refresh(now time.Time, token string, duration time.Duration) (LockDetails, error) | ||
|  | 
 | ||
|  | 	// Unlock unlocks the lock with the given token. | ||
|  | 	// | ||
|  | 	// If Unlock returns ErrForbidden then the Handler will write a "403 | ||
|  | 	// Forbidden" HTTP Status. If Unlock returns ErrLocked then the Handler | ||
|  | 	// will write a "423 Locked" HTTP status. If Unlock returns ErrNoSuchLock | ||
|  | 	// then the Handler will write a "409 Conflict" HTTP Status. If it returns | ||
|  | 	// any other non-nil error, the Handler will write a "500 Internal Server | ||
|  | 	// Error" HTTP status. | ||
|  | 	// | ||
|  | 	// See http://www.webdav.org/specs/rfc4918.html#rfc.section.9.11.1 for | ||
|  | 	// when to use each error. | ||
|  | 	Unlock(now time.Time, token string) error | ||
|  | } | ||
|  | 
 | ||
|  | // LockDetails are a lock's metadata. | ||
|  | type LockDetails struct { | ||
|  | 	// Root is the root resource name being locked. For a zero-depth lock, the | ||
|  | 	// root is the only resource being locked. | ||
|  | 	Root string | ||
|  | 	// Duration is the lock timeout. A negative duration means infinite. | ||
|  | 	Duration time.Duration | ||
|  | 	// OwnerXML is the verbatim <owner> XML given in a LOCK HTTP request. | ||
|  | 	// | ||
|  | 	// TODO: does the "verbatim" nature play well with XML namespaces? | ||
|  | 	// Does the OwnerXML field need to have more structure? See | ||
|  | 	// https://codereview.appspot.com/175140043/#msg2 | ||
|  | 	OwnerXML string | ||
|  | 	// ZeroDepth is whether the lock has zero depth. If it does not have zero | ||
|  | 	// depth, it has infinite depth. | ||
|  | 	ZeroDepth bool | ||
|  | } | ||
|  | 
 | ||
|  | // NewMemLS returns a new in-memory LockSystem. | ||
|  | func NewMemLS() LockSystem { | ||
|  | 	return &memLS{ | ||
|  | 		byName:  make(map[string]*memLSNode), | ||
|  | 		byToken: make(map[string]*memLSNode), | ||
|  | 		gen:     uint64(time.Now().Unix()), | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | type memLS struct { | ||
|  | 	mu      sync.Mutex | ||
|  | 	byName  map[string]*memLSNode | ||
|  | 	byToken map[string]*memLSNode | ||
|  | 	gen     uint64 | ||
|  | 	// byExpiry only contains those nodes whose LockDetails have a finite | ||
|  | 	// Duration and are yet to expire. | ||
|  | 	byExpiry byExpiry | ||
|  | } | ||
|  | 
 | ||
|  | func (m *memLS) nextToken() string { | ||
|  | 	m.gen++ | ||
|  | 	return strconv.FormatUint(m.gen, 10) | ||
|  | } | ||
|  | 
 | ||
|  | func (m *memLS) collectExpiredNodes(now time.Time) { | ||
|  | 	for len(m.byExpiry) > 0 { | ||
|  | 		if now.Before(m.byExpiry[0].expiry) { | ||
|  | 			break | ||
|  | 		} | ||
|  | 		m.remove(m.byExpiry[0]) | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | func (m *memLS) Confirm(now time.Time, name0, name1 string, conditions ...Condition) (func(), error) { | ||
|  | 	m.mu.Lock() | ||
|  | 	defer m.mu.Unlock() | ||
|  | 	m.collectExpiredNodes(now) | ||
|  | 
 | ||
|  | 	var n0, n1 *memLSNode | ||
|  | 	if name0 != "" { | ||
|  | 		if n0 = m.lookup(slashClean(name0), conditions...); n0 == nil { | ||
|  | 			return nil, ErrConfirmationFailed | ||
|  | 		} | ||
|  | 	} | ||
|  | 	if name1 != "" { | ||
|  | 		if n1 = m.lookup(slashClean(name1), conditions...); n1 == nil { | ||
|  | 			return nil, ErrConfirmationFailed | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// Don't hold the same node twice. | ||
|  | 	if n1 == n0 { | ||
|  | 		n1 = nil | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if n0 != nil { | ||
|  | 		m.hold(n0) | ||
|  | 	} | ||
|  | 	if n1 != nil { | ||
|  | 		m.hold(n1) | ||
|  | 	} | ||
|  | 	return func() { | ||
|  | 		m.mu.Lock() | ||
|  | 		defer m.mu.Unlock() | ||
|  | 		if n1 != nil { | ||
|  | 			m.unhold(n1) | ||
|  | 		} | ||
|  | 		if n0 != nil { | ||
|  | 			m.unhold(n0) | ||
|  | 		} | ||
|  | 	}, nil | ||
|  | } | ||
|  | 
 | ||
|  | // lookup returns the node n that locks the named resource, provided that n | ||
|  | // matches at least one of the given conditions and that lock isn't held by | ||
|  | // another party. Otherwise, it returns nil. | ||
|  | // | ||
|  | // n may be a parent of the named resource, if n is an infinite depth lock. | ||
|  | func (m *memLS) lookup(name string, conditions ...Condition) (n *memLSNode) { | ||
|  | 	// TODO: support Condition.Not and Condition.ETag. | ||
|  | 	for _, c := range conditions { | ||
|  | 		n = m.byToken[c.Token] | ||
|  | 		if n == nil || n.held { | ||
|  | 			continue | ||
|  | 		} | ||
|  | 		if name == n.details.Root { | ||
|  | 			return n | ||
|  | 		} | ||
|  | 		if n.details.ZeroDepth { | ||
|  | 			continue | ||
|  | 		} | ||
|  | 		if n.details.Root == "/" || strings.HasPrefix(name, n.details.Root+"/") { | ||
|  | 			return n | ||
|  | 		} | ||
|  | 	} | ||
|  | 	return nil | ||
|  | } | ||
|  | 
 | ||
|  | func (m *memLS) hold(n *memLSNode) { | ||
|  | 	if n.held { | ||
|  | 		panic("webdav: memLS inconsistent held state") | ||
|  | 	} | ||
|  | 	n.held = true | ||
|  | 	if n.details.Duration >= 0 && n.byExpiryIndex >= 0 { | ||
|  | 		heap.Remove(&m.byExpiry, n.byExpiryIndex) | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | func (m *memLS) unhold(n *memLSNode) { | ||
|  | 	if !n.held { | ||
|  | 		panic("webdav: memLS inconsistent held state") | ||
|  | 	} | ||
|  | 	n.held = false | ||
|  | 	if n.details.Duration >= 0 { | ||
|  | 		heap.Push(&m.byExpiry, n) | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | func (m *memLS) Create(now time.Time, details LockDetails) (string, error) { | ||
|  | 	m.mu.Lock() | ||
|  | 	defer m.mu.Unlock() | ||
|  | 	m.collectExpiredNodes(now) | ||
|  | 	details.Root = slashClean(details.Root) | ||
|  | 
 | ||
|  | 	if !m.canCreate(details.Root, details.ZeroDepth) { | ||
|  | 		return "", ErrLocked | ||
|  | 	} | ||
|  | 	n := m.create(details.Root) | ||
|  | 	n.token = m.nextToken() | ||
|  | 	m.byToken[n.token] = n | ||
|  | 	n.details = details | ||
|  | 	if n.details.Duration >= 0 { | ||
|  | 		n.expiry = now.Add(n.details.Duration) | ||
|  | 		heap.Push(&m.byExpiry, n) | ||
|  | 	} | ||
|  | 	return n.token, nil | ||
|  | } | ||
|  | 
 | ||
|  | func (m *memLS) Refresh(now time.Time, token string, duration time.Duration) (LockDetails, error) { | ||
|  | 	m.mu.Lock() | ||
|  | 	defer m.mu.Unlock() | ||
|  | 	m.collectExpiredNodes(now) | ||
|  | 
 | ||
|  | 	n := m.byToken[token] | ||
|  | 	if n == nil { | ||
|  | 		return LockDetails{}, ErrNoSuchLock | ||
|  | 	} | ||
|  | 	if n.held { | ||
|  | 		return LockDetails{}, ErrLocked | ||
|  | 	} | ||
|  | 	if n.byExpiryIndex >= 0 { | ||
|  | 		heap.Remove(&m.byExpiry, n.byExpiryIndex) | ||
|  | 	} | ||
|  | 	n.details.Duration = duration | ||
|  | 	if n.details.Duration >= 0 { | ||
|  | 		n.expiry = now.Add(n.details.Duration) | ||
|  | 		heap.Push(&m.byExpiry, n) | ||
|  | 	} | ||
|  | 	return n.details, nil | ||
|  | } | ||
|  | 
 | ||
|  | func (m *memLS) Unlock(now time.Time, token string) error { | ||
|  | 	m.mu.Lock() | ||
|  | 	defer m.mu.Unlock() | ||
|  | 	m.collectExpiredNodes(now) | ||
|  | 
 | ||
|  | 	n := m.byToken[token] | ||
|  | 	if n == nil { | ||
|  | 		return ErrNoSuchLock | ||
|  | 	} | ||
|  | 	if n.held { | ||
|  | 		return ErrLocked | ||
|  | 	} | ||
|  | 	m.remove(n) | ||
|  | 	return nil | ||
|  | } | ||
|  | 
 | ||
|  | func (m *memLS) canCreate(name string, zeroDepth bool) bool { | ||
|  | 	return walkToRoot(name, func(name0 string, first bool) bool { | ||
|  | 		n := m.byName[name0] | ||
|  | 		if n == nil { | ||
|  | 			return true | ||
|  | 		} | ||
|  | 		if first { | ||
|  | 			if n.token != "" { | ||
|  | 				// The target node is already locked. | ||
|  | 				return false | ||
|  | 			} | ||
|  | 			if !zeroDepth { | ||
|  | 				// The requested lock depth is infinite, and the fact that n exists | ||
|  | 				// (n != nil) means that a descendent of the target node is locked. | ||
|  | 				return false | ||
|  | 			} | ||
|  | 		} else if n.token != "" && !n.details.ZeroDepth { | ||
|  | 			// An ancestor of the target node is locked with infinite depth. | ||
|  | 			return false | ||
|  | 		} | ||
|  | 		return true | ||
|  | 	}) | ||
|  | } | ||
|  | 
 | ||
|  | func (m *memLS) create(name string) (ret *memLSNode) { | ||
|  | 	walkToRoot(name, func(name0 string, first bool) bool { | ||
|  | 		n := m.byName[name0] | ||
|  | 		if n == nil { | ||
|  | 			n = &memLSNode{ | ||
|  | 				details: LockDetails{ | ||
|  | 					Root: name0, | ||
|  | 				}, | ||
|  | 				byExpiryIndex: -1, | ||
|  | 			} | ||
|  | 			m.byName[name0] = n | ||
|  | 		} | ||
|  | 		n.refCount++ | ||
|  | 		if first { | ||
|  | 			ret = n | ||
|  | 		} | ||
|  | 		return true | ||
|  | 	}) | ||
|  | 	return ret | ||
|  | } | ||
|  | 
 | ||
|  | func (m *memLS) remove(n *memLSNode) { | ||
|  | 	delete(m.byToken, n.token) | ||
|  | 	n.token = "" | ||
|  | 	walkToRoot(n.details.Root, func(name0 string, first bool) bool { | ||
|  | 		x := m.byName[name0] | ||
|  | 		x.refCount-- | ||
|  | 		if x.refCount == 0 { | ||
|  | 			delete(m.byName, name0) | ||
|  | 		} | ||
|  | 		return true | ||
|  | 	}) | ||
|  | 	if n.byExpiryIndex >= 0 { | ||
|  | 		heap.Remove(&m.byExpiry, n.byExpiryIndex) | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | func walkToRoot(name string, f func(name0 string, first bool) bool) bool { | ||
|  | 	for first := true; ; first = false { | ||
|  | 		if !f(name, first) { | ||
|  | 			return false | ||
|  | 		} | ||
|  | 		if name == "/" { | ||
|  | 			break | ||
|  | 		} | ||
|  | 		name = name[:strings.LastIndex(name, "/")] | ||
|  | 		if name == "" { | ||
|  | 			name = "/" | ||
|  | 		} | ||
|  | 	} | ||
|  | 	return true | ||
|  | } | ||
|  | 
 | ||
|  | type memLSNode struct { | ||
|  | 	// details are the lock metadata. Even if this node's name is not explicitly locked, | ||
|  | 	// details.Root will still equal the node's name. | ||
|  | 	details LockDetails | ||
|  | 	// token is the unique identifier for this node's lock. An empty token means that | ||
|  | 	// this node is not explicitly locked. | ||
|  | 	token string | ||
|  | 	// refCount is the number of self-or-descendent nodes that are explicitly locked. | ||
|  | 	refCount int | ||
|  | 	// expiry is when this node's lock expires. | ||
|  | 	expiry time.Time | ||
|  | 	// byExpiryIndex is the index of this node in memLS.byExpiry. It is -1 | ||
|  | 	// if this node does not expire, or has expired. | ||
|  | 	byExpiryIndex int | ||
|  | 	// held is whether this node's lock is actively held by a Confirm call. | ||
|  | 	held bool | ||
|  | } | ||
|  | 
 | ||
|  | type byExpiry []*memLSNode | ||
|  | 
 | ||
|  | func (b *byExpiry) Len() int { | ||
|  | 	return len(*b) | ||
|  | } | ||
|  | 
 | ||
|  | func (b *byExpiry) Less(i, j int) bool { | ||
|  | 	return (*b)[i].expiry.Before((*b)[j].expiry) | ||
|  | } | ||
|  | 
 | ||
|  | func (b *byExpiry) Swap(i, j int) { | ||
|  | 	(*b)[i], (*b)[j] = (*b)[j], (*b)[i] | ||
|  | 	(*b)[i].byExpiryIndex = i | ||
|  | 	(*b)[j].byExpiryIndex = j | ||
|  | } | ||
|  | 
 | ||
|  | func (b *byExpiry) Push(x interface{}) { | ||
|  | 	n := x.(*memLSNode) | ||
|  | 	n.byExpiryIndex = len(*b) | ||
|  | 	*b = append(*b, n) | ||
|  | } | ||
|  | 
 | ||
|  | func (b *byExpiry) Pop() interface{} { | ||
|  | 	i := len(*b) - 1 | ||
|  | 	n := (*b)[i] | ||
|  | 	(*b)[i] = nil | ||
|  | 	n.byExpiryIndex = -1 | ||
|  | 	*b = (*b)[:i] | ||
|  | 	return n | ||
|  | } | ||
|  | 
 | ||
|  | const infiniteTimeout = -1 | ||
|  | 
 | ||
|  | // parseTimeout parses the Timeout HTTP header, as per section 10.7. If s is | ||
|  | // empty, an infiniteTimeout is returned. | ||
|  | func parseTimeout(s string) (time.Duration, error) { | ||
|  | 	if s == "" { | ||
|  | 		return infiniteTimeout, nil | ||
|  | 	} | ||
|  | 	if i := strings.IndexByte(s, ','); i >= 0 { | ||
|  | 		s = s[:i] | ||
|  | 	} | ||
|  | 	s = strings.TrimSpace(s) | ||
|  | 	if s == "Infinite" { | ||
|  | 		return infiniteTimeout, nil | ||
|  | 	} | ||
|  | 	const pre = "Second-" | ||
|  | 	if !strings.HasPrefix(s, pre) { | ||
|  | 		return 0, errInvalidTimeout | ||
|  | 	} | ||
|  | 	s = s[len(pre):] | ||
|  | 	if s == "" || s[0] < '0' || '9' < s[0] { | ||
|  | 		return 0, errInvalidTimeout | ||
|  | 	} | ||
|  | 	n, err := strconv.ParseInt(s, 10, 64) | ||
|  | 	if err != nil || 1<<32-1 < n { | ||
|  | 		return 0, errInvalidTimeout | ||
|  | 	} | ||
|  | 	return time.Duration(n) * time.Second, nil | ||
|  | } |