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.
|
||||
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.
|
||||
|
@ -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) {
|
||||
|
@ -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,
|
||||
}}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user