Finish the Lock and Unlock feature for webdav.
This commit is contained in:
parent
ed0aa017db
commit
76c763b84d
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user