Finish the Lock and Unlock feature for webdav.

This commit is contained in:
lishuang 2020-05-06 01:10:17 +08:00
parent ed0aa017db
commit 76c763b84d
5 changed files with 200 additions and 31 deletions

View File

@ -185,11 +185,6 @@ func (this *DavService) Propstats(user *User, matter *Matter, propfind *dav.Prop
//list the directory.
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)
// read depth
@ -238,9 +233,17 @@ func (this *DavService) HandleProppatch(writer http.ResponseWriter, request *htt
fmt.Printf("PROPPATCH %s\n", subPath)
xLimits := request.Header.Get("X-Litmus")
if xLimits == "props: 17 (prophighunicode)" {
fmt.Println("stop here!")
// 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()
}
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)
// 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)
dirPath := util.GetDirOfPath(subPath)
@ -335,13 +355,25 @@ func (this *DavService) HandlePut(writer http.ResponseWriter, request *http.Requ
}
//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)
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)
this.matterService.AtomicDelete(request, matter, user)
this.matterService.AtomicDelete(r, matter, user)
}
//crate a directory
@ -496,6 +528,19 @@ func (this *DavService) HandleMove(writer http.ResponseWriter, request *http.Req
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)
//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)
// 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
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.
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"))
if err != nil {
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{
Root: subPath,
Root: reqPath,
Duration: duration,
OwnerXML: li.Owner.InnerXML,
ZeroDepth: depth == 0,
@ -637,9 +786,28 @@ func (this *DavService) HandleLock(w http.ResponseWriter, r *http.Request, user
}
//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.

View File

@ -414,20 +414,20 @@ func (b *byExpiry) Pop() interface{} {
return n
}
const infiniteTimeout = -1
const InfiniteTimeout = -1
// 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) {
if s == "" {
return infiniteTimeout, nil
return InfiniteTimeout, nil
}
if i := strings.IndexByte(s, ','); i >= 0 {
s = s[:i]
}
s = strings.TrimSpace(s)
if s == "Infinite" {
return infiniteTimeout, nil
return InfiniteTimeout, nil
}
const pre = "Second-"
if !strings.HasPrefix(s, pre) {

View File

@ -61,7 +61,7 @@ func TestWalkToRoot(t *testing.T) {
}
var lockTestDurations = []time.Duration{
infiniteTimeout, // infiniteTimeout means to never expire.
InfiniteTimeout, // InfiniteTimeout means to never expire.
0, // A zero duration means to expire immediately.
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 {
_, err := m.Create(now, LockDetails{
Root: name,
Duration: infiniteTimeout,
Duration: InfiniteTimeout,
ZeroDepth: lockTestZeroDepth(name),
})
if err != nil {
@ -165,7 +165,7 @@ func TestMemLSLookup(t *testing.T) {
for _, name := range lockTestNames {
token, err := m.Create(now, LockDetails{
Root: name,
Duration: infiniteTimeout,
Duration: InfiniteTimeout,
ZeroDepth: lockTestZeroDepth(name),
})
if err != nil {
@ -209,7 +209,7 @@ func TestMemLSConfirm(t *testing.T) {
m := NewMemLS().(*MemLS)
alice, err := m.Create(now, LockDetails{
Root: "/alice",
Duration: infiniteTimeout,
Duration: InfiniteTimeout,
ZeroDepth: false,
})
if err != nil {
@ -218,7 +218,7 @@ func TestMemLSConfirm(t *testing.T) {
tweedle, err := m.Create(now, LockDetails{
Root: "/tweedle",
Duration: infiniteTimeout,
Duration: InfiniteTimeout,
ZeroDepth: false,
})
if err != nil {
@ -644,11 +644,11 @@ func TestParseTimeout(t *testing.T) {
wantErr error
}{{
"",
infiniteTimeout,
InfiniteTimeout,
nil,
}, {
"Infinite",
infiniteTimeout,
InfiniteTimeout,
nil,
}, {
"Infinitesimal",
@ -722,7 +722,7 @@ func TestParseTimeout(t *testing.T) {
// The Go WebDAV package always supports infinite length locks,
// and ignores the fallback after the comma.
"Infinite, Second-4100000000",
infiniteTimeout,
InfiniteTimeout,
nil,
}}

View File

@ -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) {
token, err = h.LockSystem.Create(now, LockDetails{
Root: root,
Duration: infiniteTimeout,
Duration: InfiniteTimeout,
ZeroDepth: true,
})
if err != nil {

View File

@ -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
// the only types of locks that seem to matter.
if li.Exclusive == nil || li.Shared != nil || li.Write == nil {
// we should support Shared lock.
return LockInfo{}, http.StatusNotImplemented, ErrUnsupportedLockInfo
}
return li, 0, nil