Finish the Lock and Unlock feature for webdav.
This commit is contained in:
		| @ -185,11 +185,6 @@ func (this *DavService) Propstats(user *User, matter *Matter, propfind *dav.Prop | |||||||
| //list the directory. | //list the directory. | ||||||
| func (this *DavService) HandlePropfind(writer http.ResponseWriter, request *http.Request, user *User, subPath string) { | func (this *DavService) HandlePropfind(writer http.ResponseWriter, request *http.Request, user *User, subPath string) { | ||||||
|  |  | ||||||
| 	xLimits := request.Header.Get("X-Litmus") |  | ||||||
| 	if xLimits == "props: 3 (propfind_invalid2)" { |  | ||||||
| 		fmt.Println("stop here!") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	fmt.Printf("PROPFIND %s\n", subPath) | 	fmt.Printf("PROPFIND %s\n", subPath) | ||||||
|  |  | ||||||
| 	// read depth | 	// read depth | ||||||
| @ -238,9 +233,17 @@ func (this *DavService) HandleProppatch(writer http.ResponseWriter, request *htt | |||||||
|  |  | ||||||
| 	fmt.Printf("PROPPATCH %s\n", subPath) | 	fmt.Printf("PROPPATCH %s\n", subPath) | ||||||
|  |  | ||||||
| 	xLimits := request.Header.Get("X-Litmus") | 	// handle the lock feature. | ||||||
| 	if xLimits == "props: 17 (prophighunicode)" { | 	reqPath, status, err := this.stripPrefix(request.URL.Path) | ||||||
| 		fmt.Println("stop here!") | 	if err != nil { | ||||||
|  | 		panic(result.StatusCodeWebResult(status, err.Error())) | ||||||
|  | 	} | ||||||
|  | 	release, status, err := this.confirmLocks(request, reqPath, "") | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(result.StatusCodeWebResult(status, err.Error())) | ||||||
|  | 	} | ||||||
|  | 	if release != nil { | ||||||
|  | 		defer release() | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	matter := this.matterDao.checkByUserUuidAndPath(user.Uuid, subPath) | 	matter := this.matterDao.checkByUserUuidAndPath(user.Uuid, subPath) | ||||||
| @ -316,6 +319,23 @@ func (this *DavService) HandlePut(writer http.ResponseWriter, request *http.Requ | |||||||
|  |  | ||||||
| 	fmt.Printf("PUT %s\n", subPath) | 	fmt.Printf("PUT %s\n", subPath) | ||||||
|  |  | ||||||
|  | 	// handle the lock feature. | ||||||
|  | 	reqPath, status, err := this.stripPrefix(request.URL.Path) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(result.StatusCodeWebResult(status, err.Error())) | ||||||
|  | 	} | ||||||
|  | 	release, status, err := this.confirmLocks(request, reqPath, "") | ||||||
|  | 	if err != nil { | ||||||
|  |  | ||||||
|  | 		//if status == http.StatusLocked { | ||||||
|  | 		//	status = http.StatusPreconditionFailed | ||||||
|  | 		//} | ||||||
|  | 		panic(result.StatusCodeWebResult(status, err.Error())) | ||||||
|  | 	} | ||||||
|  | 	if release != nil { | ||||||
|  | 		defer release() | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	filename := util.GetFilenameOfPath(subPath) | 	filename := util.GetFilenameOfPath(subPath) | ||||||
| 	dirPath := util.GetDirOfPath(subPath) | 	dirPath := util.GetDirOfPath(subPath) | ||||||
|  |  | ||||||
| @ -335,13 +355,25 @@ func (this *DavService) HandlePut(writer http.ResponseWriter, request *http.Requ | |||||||
| } | } | ||||||
|  |  | ||||||
| //delete file | //delete file | ||||||
| func (this *DavService) HandleDelete(writer http.ResponseWriter, request *http.Request, user *User, subPath string) { | func (this *DavService) HandleDelete(w http.ResponseWriter, r *http.Request, user *User, subPath string) { | ||||||
|  |  | ||||||
| 	fmt.Printf("DELETE %s\n", subPath) | 	fmt.Printf("DELETE %s\n", subPath) | ||||||
|  |  | ||||||
|  | 	reqPath, status, err := this.stripPrefix(r.URL.Path) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(result.StatusCodeWebResult(status, err.Error())) | ||||||
|  | 	} | ||||||
|  | 	release, status, err := this.confirmLocks(r, reqPath, "") | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(result.StatusCodeWebResult(status, err.Error())) | ||||||
|  | 	} | ||||||
|  | 	if release != nil { | ||||||
|  | 		defer release() | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	matter := this.matterDao.CheckWithRootByPath(subPath, user) | 	matter := this.matterDao.CheckWithRootByPath(subPath, user) | ||||||
|  |  | ||||||
| 	this.matterService.AtomicDelete(request, matter, user) | 	this.matterService.AtomicDelete(r, matter, user) | ||||||
| } | } | ||||||
|  |  | ||||||
| //crate a directory | //crate a directory | ||||||
| @ -496,6 +528,19 @@ func (this *DavService) HandleMove(writer http.ResponseWriter, request *http.Req | |||||||
|  |  | ||||||
| 	fmt.Printf("MOVE %s\n", subPath) | 	fmt.Printf("MOVE %s\n", subPath) | ||||||
|  |  | ||||||
|  | 	// handle the lock feature. | ||||||
|  | 	reqPath, status, err := this.stripPrefix(request.URL.Path) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(result.StatusCodeWebResult(status, err.Error())) | ||||||
|  | 	} | ||||||
|  | 	release, status, err := this.confirmLocks(request, reqPath, "") | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(result.StatusCodeWebResult(status, err.Error())) | ||||||
|  | 	} | ||||||
|  | 	if release != nil { | ||||||
|  | 		defer release() | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	srcMatter, destDirMatter, srcDirPath, destinationDirPath, destinationName, overwrite := this.prepareMoveCopy(writer, request, user, subPath) | 	srcMatter, destDirMatter, srcDirPath, destinationDirPath, destinationName, overwrite := this.prepareMoveCopy(writer, request, user, subPath) | ||||||
|  |  | ||||||
| 	//move to the new directory | 	//move to the new directory | ||||||
| @ -524,6 +569,15 @@ func (this *DavService) HandleCopy(writer http.ResponseWriter, request *http.Req | |||||||
|  |  | ||||||
| 	srcMatter, destDirMatter, _, _, destinationName, overwrite := this.prepareMoveCopy(writer, request, user, subPath) | 	srcMatter, destDirMatter, _, _, destinationName, overwrite := this.prepareMoveCopy(writer, request, user, subPath) | ||||||
|  |  | ||||||
|  | 	// handle the lock feature. | ||||||
|  | 	release, status, err := this.confirmLocks(request, destDirMatter.Path+"/"+destinationName, "") | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(result.StatusCodeWebResult(status, err.Error())) | ||||||
|  | 	} | ||||||
|  | 	if release != nil { | ||||||
|  | 		defer release() | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	//copy to the new directory | 	//copy to the new directory | ||||||
| 	this.matterService.AtomicCopy(request, srcMatter, destDirMatter, destinationName, overwrite, user) | 	this.matterService.AtomicCopy(request, srcMatter, destDirMatter, destinationName, overwrite, user) | ||||||
|  |  | ||||||
| @ -539,14 +593,104 @@ func (this *DavService) HandleCopy(writer http.ResponseWriter, request *http.Req | |||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (h *DavService) stripPrefix(p string) (string, int, error) { | ||||||
|  | 	if r := strings.TrimPrefix(p, WEBDAV_PREFIX); len(r) < len(p) { | ||||||
|  | 		return r, http.StatusOK, nil | ||||||
|  | 	} | ||||||
|  | 	return p, http.StatusNotFound, webdav.ErrPrefixMismatch | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (h *DavService) lock(now time.Time, root string) (token string, status int, err error) { | ||||||
|  | 	token, err = h.lockSystem.Create(now, webdav.LockDetails{ | ||||||
|  | 		Root:      root, | ||||||
|  | 		Duration:  webdav.InfiniteTimeout, | ||||||
|  | 		ZeroDepth: true, | ||||||
|  | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		if err == webdav.ErrLocked { | ||||||
|  | 			return "", webdav.StatusLocked, err | ||||||
|  | 		} | ||||||
|  | 		return "", http.StatusInternalServerError, err | ||||||
|  | 	} | ||||||
|  | 	return token, 0, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (h *DavService) confirmLocks(r *http.Request, src, dst string) (release func(), status int, err error) { | ||||||
|  | 	hdr := r.Header.Get("If") | ||||||
|  | 	if hdr == "" { | ||||||
|  | 		// An empty If header means that the client hasn't previously created locks. | ||||||
|  | 		// Even if this client doesn't care about locks, we still need to check that | ||||||
|  | 		// the resources aren't locked by another client, so we create temporary | ||||||
|  | 		// locks that would conflict with another client's locks. These temporary | ||||||
|  | 		// locks are unlocked at the end of the HTTP request. | ||||||
|  | 		now, srcToken, dstToken := time.Now(), "", "" | ||||||
|  | 		if src != "" { | ||||||
|  | 			srcToken, status, err = h.lock(now, src) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, status, err | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		if dst != "" { | ||||||
|  | 			dstToken, status, err = h.lock(now, dst) | ||||||
|  | 			if err != nil { | ||||||
|  | 				if srcToken != "" { | ||||||
|  | 					h.lockSystem.Unlock(now, srcToken) | ||||||
|  | 				} | ||||||
|  | 				return nil, status, err | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return func() { | ||||||
|  | 			if dstToken != "" { | ||||||
|  | 				h.lockSystem.Unlock(now, dstToken) | ||||||
|  | 			} | ||||||
|  | 			if srcToken != "" { | ||||||
|  | 				h.lockSystem.Unlock(now, srcToken) | ||||||
|  | 			} | ||||||
|  | 		}, 0, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ih, ok := webdav.ParseIfHeader(hdr) | ||||||
|  | 	if !ok { | ||||||
|  | 		return nil, http.StatusBadRequest, webdav.ErrInvalidIfHeader | ||||||
|  | 	} | ||||||
|  | 	// ih is a disjunction (OR) of ifLists, so any IfList will do. | ||||||
|  | 	for _, l := range ih.Lists { | ||||||
|  | 		lsrc := l.ResourceTag | ||||||
|  | 		if lsrc == "" { | ||||||
|  | 			lsrc = src | ||||||
|  | 		} else { | ||||||
|  | 			u, err := url.Parse(lsrc) | ||||||
|  | 			if err != nil { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			if u.Host != r.Host { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			lsrc, status, err = h.stripPrefix(u.Path) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, status, err | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		release, err = h.lockSystem.Confirm(time.Now(), lsrc, dst, l.Conditions...) | ||||||
|  | 		if err == webdav.ErrConfirmationFailed { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, http.StatusInternalServerError, err | ||||||
|  | 		} | ||||||
|  | 		return release, 0, nil | ||||||
|  | 	} | ||||||
|  | 	// Section 10.4.1 says that "If this header is evaluated and all state lists | ||||||
|  | 	// fail, then the request must fail with a 412 (Precondition Failed) status." | ||||||
|  | 	// We follow the spec even though the cond_put_corrupt_token test case from | ||||||
|  | 	// the litmus test warns on seeing a 412 instead of a 423 (Locked). | ||||||
|  | 	return nil, http.StatusLocked, webdav.ErrLocked | ||||||
|  | } | ||||||
|  |  | ||||||
| //lock. | //lock. | ||||||
| func (this *DavService) HandleLock(w http.ResponseWriter, r *http.Request, user *User, subPath string) { | func (this *DavService) HandleLock(w http.ResponseWriter, r *http.Request, user *User, subPath string) { | ||||||
|  |  | ||||||
| 	xLimits := r.Header.Get("X-Litmus") |  | ||||||
| 	if xLimits == "locks: 6 (lock_excl)" { |  | ||||||
| 		fmt.Println("stop here!") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	duration, err := webdav.ParseTimeout(r.Header.Get("Timeout")) | 	duration, err := webdav.ParseTimeout(r.Header.Get("Timeout")) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(result.BadRequest(err.Error())) | 		panic(result.BadRequest(err.Error())) | ||||||
| @ -590,8 +734,13 @@ func (this *DavService) HandleLock(w http.ResponseWriter, r *http.Request, user | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		reqPath, status, err := this.stripPrefix(r.URL.Path) | ||||||
|  | 		if err != nil { | ||||||
|  | 			panic(result.StatusCodeWebResult(status, err.Error())) | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		ld = webdav.LockDetails{ | 		ld = webdav.LockDetails{ | ||||||
| 			Root:      subPath, | 			Root:      reqPath, | ||||||
| 			Duration:  duration, | 			Duration:  duration, | ||||||
| 			OwnerXML:  li.Owner.InnerXML, | 			OwnerXML:  li.Owner.InnerXML, | ||||||
| 			ZeroDepth: depth == 0, | 			ZeroDepth: depth == 0, | ||||||
| @ -637,9 +786,28 @@ func (this *DavService) HandleLock(w http.ResponseWriter, r *http.Request, user | |||||||
| } | } | ||||||
|  |  | ||||||
| //unlock | //unlock | ||||||
| func (this *DavService) HandleUnlock(writer http.ResponseWriter, request *http.Request, user *User, subPath string) { | func (this *DavService) HandleUnlock(w http.ResponseWriter, r *http.Request, user *User, subPath string) { | ||||||
|  |  | ||||||
| 	panic(result.BadRequest("not support UNLOCK yet.")) | 	// http://www.webdav.org/specs/rfc4918.html#HEADER_Lock-Token says that the | ||||||
|  | 	// Lock-Token value is a Coded-URL. We strip its angle brackets. | ||||||
|  | 	t := r.Header.Get("Lock-Token") | ||||||
|  | 	if len(t) < 2 || t[0] != '<' || t[len(t)-1] != '>' { | ||||||
|  | 		panic(result.StatusCodeWebResult(http.StatusBadRequest, webdav.ErrInvalidLockToken.Error())) | ||||||
|  | 	} | ||||||
|  | 	t = t[1 : len(t)-1] | ||||||
|  |  | ||||||
|  | 	switch err := this.lockSystem.Unlock(time.Now(), t); err { | ||||||
|  | 	case nil: | ||||||
|  | 		panic(result.StatusCodeWebResult(http.StatusNoContent, "")) | ||||||
|  | 	case webdav.ErrForbidden: | ||||||
|  | 		panic(result.StatusCodeWebResult(http.StatusForbidden, err.Error())) | ||||||
|  | 	case webdav.ErrLocked: | ||||||
|  | 		panic(result.StatusCodeWebResult(http.StatusLocked, err.Error())) | ||||||
|  | 	case webdav.ErrNoSuchLock: | ||||||
|  | 		panic(result.StatusCodeWebResult(http.StatusConflict, err.Error())) | ||||||
|  | 	default: | ||||||
|  | 		panic(result.StatusCodeWebResult(http.StatusInternalServerError, err.Error())) | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| //hanle all the request. | //hanle all the request. | ||||||
|  | |||||||
| @ -414,20 +414,20 @@ func (b *byExpiry) Pop() interface{} { | |||||||
| 	return n | 	return n | ||||||
| } | } | ||||||
|  |  | ||||||
| const infiniteTimeout = -1 | const InfiniteTimeout = -1 | ||||||
|  |  | ||||||
| // ParseTimeout parses the Timeout HTTP header, as per section 10.7. If s is | // ParseTimeout parses the Timeout HTTP header, as per section 10.7. If s is | ||||||
| // empty, an infiniteTimeout is returned. | // empty, an InfiniteTimeout is returned. | ||||||
| func ParseTimeout(s string) (time.Duration, error) { | func ParseTimeout(s string) (time.Duration, error) { | ||||||
| 	if s == "" { | 	if s == "" { | ||||||
| 		return infiniteTimeout, nil | 		return InfiniteTimeout, nil | ||||||
| 	} | 	} | ||||||
| 	if i := strings.IndexByte(s, ','); i >= 0 { | 	if i := strings.IndexByte(s, ','); i >= 0 { | ||||||
| 		s = s[:i] | 		s = s[:i] | ||||||
| 	} | 	} | ||||||
| 	s = strings.TrimSpace(s) | 	s = strings.TrimSpace(s) | ||||||
| 	if s == "Infinite" { | 	if s == "Infinite" { | ||||||
| 		return infiniteTimeout, nil | 		return InfiniteTimeout, nil | ||||||
| 	} | 	} | ||||||
| 	const pre = "Second-" | 	const pre = "Second-" | ||||||
| 	if !strings.HasPrefix(s, pre) { | 	if !strings.HasPrefix(s, pre) { | ||||||
|  | |||||||
| @ -61,7 +61,7 @@ func TestWalkToRoot(t *testing.T) { | |||||||
| } | } | ||||||
|  |  | ||||||
| var lockTestDurations = []time.Duration{ | var lockTestDurations = []time.Duration{ | ||||||
| 	infiniteTimeout, // infiniteTimeout means to never expire. | 	InfiniteTimeout, // InfiniteTimeout means to never expire. | ||||||
| 	0,               // A zero duration means to expire immediately. | 	0,               // A zero duration means to expire immediately. | ||||||
| 	100 * time.Hour, // A very large duration will not expire in these tests. | 	100 * time.Hour, // A very large duration will not expire in these tests. | ||||||
| } | } | ||||||
| @ -102,7 +102,7 @@ func TestMemLSCanCreate(t *testing.T) { | |||||||
| 	for _, name := range lockTestNames { | 	for _, name := range lockTestNames { | ||||||
| 		_, err := m.Create(now, LockDetails{ | 		_, err := m.Create(now, LockDetails{ | ||||||
| 			Root:      name, | 			Root:      name, | ||||||
| 			Duration:  infiniteTimeout, | 			Duration:  InfiniteTimeout, | ||||||
| 			ZeroDepth: lockTestZeroDepth(name), | 			ZeroDepth: lockTestZeroDepth(name), | ||||||
| 		}) | 		}) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| @ -165,7 +165,7 @@ func TestMemLSLookup(t *testing.T) { | |||||||
| 	for _, name := range lockTestNames { | 	for _, name := range lockTestNames { | ||||||
| 		token, err := m.Create(now, LockDetails{ | 		token, err := m.Create(now, LockDetails{ | ||||||
| 			Root:      name, | 			Root:      name, | ||||||
| 			Duration:  infiniteTimeout, | 			Duration:  InfiniteTimeout, | ||||||
| 			ZeroDepth: lockTestZeroDepth(name), | 			ZeroDepth: lockTestZeroDepth(name), | ||||||
| 		}) | 		}) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| @ -209,7 +209,7 @@ func TestMemLSConfirm(t *testing.T) { | |||||||
| 	m := NewMemLS().(*MemLS) | 	m := NewMemLS().(*MemLS) | ||||||
| 	alice, err := m.Create(now, LockDetails{ | 	alice, err := m.Create(now, LockDetails{ | ||||||
| 		Root:      "/alice", | 		Root:      "/alice", | ||||||
| 		Duration:  infiniteTimeout, | 		Duration:  InfiniteTimeout, | ||||||
| 		ZeroDepth: false, | 		ZeroDepth: false, | ||||||
| 	}) | 	}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @ -218,7 +218,7 @@ func TestMemLSConfirm(t *testing.T) { | |||||||
|  |  | ||||||
| 	tweedle, err := m.Create(now, LockDetails{ | 	tweedle, err := m.Create(now, LockDetails{ | ||||||
| 		Root:      "/tweedle", | 		Root:      "/tweedle", | ||||||
| 		Duration:  infiniteTimeout, | 		Duration:  InfiniteTimeout, | ||||||
| 		ZeroDepth: false, | 		ZeroDepth: false, | ||||||
| 	}) | 	}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @ -644,11 +644,11 @@ func TestParseTimeout(t *testing.T) { | |||||||
| 		wantErr error | 		wantErr error | ||||||
| 	}{{ | 	}{{ | ||||||
| 		"", | 		"", | ||||||
| 		infiniteTimeout, | 		InfiniteTimeout, | ||||||
| 		nil, | 		nil, | ||||||
| 	}, { | 	}, { | ||||||
| 		"Infinite", | 		"Infinite", | ||||||
| 		infiniteTimeout, | 		InfiniteTimeout, | ||||||
| 		nil, | 		nil, | ||||||
| 	}, { | 	}, { | ||||||
| 		"Infinitesimal", | 		"Infinitesimal", | ||||||
| @ -722,7 +722,7 @@ func TestParseTimeout(t *testing.T) { | |||||||
| 		// The Go WebDAV package always supports infinite length locks, | 		// The Go WebDAV package always supports infinite length locks, | ||||||
| 		// and ignores the fallback after the comma. | 		// and ignores the fallback after the comma. | ||||||
| 		"Infinite, Second-4100000000", | 		"Infinite, Second-4100000000", | ||||||
| 		infiniteTimeout, | 		InfiniteTimeout, | ||||||
| 		nil, | 		nil, | ||||||
| 	}} | 	}} | ||||||
|  |  | ||||||
|  | |||||||
| @ -84,7 +84,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |||||||
| func (h *Handler) lock(now time.Time, root string) (token string, status int, err error) { | func (h *Handler) lock(now time.Time, root string) (token string, status int, err error) { | ||||||
| 	token, err = h.LockSystem.Create(now, LockDetails{ | 	token, err = h.LockSystem.Create(now, LockDetails{ | ||||||
| 		Root:      root, | 		Root:      root, | ||||||
| 		Duration:  infiniteTimeout, | 		Duration:  InfiniteTimeout, | ||||||
| 		ZeroDepth: true, | 		ZeroDepth: true, | ||||||
| 	}) | 	}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | |||||||
| @ -65,6 +65,7 @@ func ReadLockInfo(r io.Reader) (li LockInfo, status int, err error) { | |||||||
| 	// We only support exclusive (non-shared) write locks. In practice, these are | 	// We only support exclusive (non-shared) write locks. In practice, these are | ||||||
| 	// the only types of locks that seem to matter. | 	// the only types of locks that seem to matter. | ||||||
| 	if li.Exclusive == nil || li.Shared != nil || li.Write == nil { | 	if li.Exclusive == nil || li.Shared != nil || li.Write == nil { | ||||||
|  | 		// we should support Shared lock. | ||||||
| 		return LockInfo{}, http.StatusNotImplemented, ErrUnsupportedLockInfo | 		return LockInfo{}, http.StatusNotImplemented, ErrUnsupportedLockInfo | ||||||
| 	} | 	} | ||||||
| 	return li, 0, nil | 	return li, 0, nil | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user