879 lines
24 KiB
Go
879 lines
24 KiB
Go
package rest
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/eyebluecn/tank/code/core"
|
|
"github.com/eyebluecn/tank/code/tool/dav"
|
|
"github.com/eyebluecn/tank/code/tool/dav/xml"
|
|
"github.com/eyebluecn/tank/code/tool/result"
|
|
"github.com/eyebluecn/tank/code/tool/util"
|
|
"github.com/eyebluecn/tank/code/tool/webdav"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/url"
|
|
"path"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
/**
|
|
*
|
|
* WebDav document
|
|
* https://tools.ietf.org/html/rfc4918
|
|
* refer: golang.org/x/net/webdav
|
|
* test machine: http://www.webdav.org/neon/litmus/
|
|
*/
|
|
//@Service
|
|
type DavService struct {
|
|
BaseBean
|
|
matterDao *MatterDao
|
|
matterService *MatterService
|
|
lockSystem webdav.LockSystem
|
|
}
|
|
|
|
func (this *DavService) Init() {
|
|
this.BaseBean.Init()
|
|
|
|
b := core.CONTEXT.GetBean(this.matterDao)
|
|
if b, ok := b.(*MatterDao); ok {
|
|
this.matterDao = b
|
|
}
|
|
|
|
b = core.CONTEXT.GetBean(this.matterService)
|
|
if b, ok := b.(*MatterService); ok {
|
|
this.matterService = b
|
|
}
|
|
|
|
// init the webdav lock system.
|
|
this.lockSystem = webdav.NewMemLS()
|
|
}
|
|
|
|
//get the depth in header. Not support infinity yet.
|
|
func (this *DavService) ParseDepth(request *http.Request) int {
|
|
|
|
depth := 1
|
|
if hdr := request.Header.Get("Depth"); hdr != "" {
|
|
switch hdr {
|
|
case "0":
|
|
return 0
|
|
case "1":
|
|
return 1
|
|
case "infinity":
|
|
return 1
|
|
}
|
|
} else {
|
|
panic(result.BadRequest("Header Depth cannot be null"))
|
|
}
|
|
return depth
|
|
}
|
|
|
|
func (this *DavService) makePropstatResponse(href string, pstats []dav.Propstat) *dav.Response {
|
|
resp := dav.Response{
|
|
Href: []string{(&url.URL{Path: href}).EscapedPath()},
|
|
Propstat: make([]dav.SubPropstat, 0, len(pstats)),
|
|
}
|
|
for _, p := range pstats {
|
|
var xmlErr *dav.XmlError
|
|
if p.XMLError != "" {
|
|
xmlErr = &dav.XmlError{InnerXML: []byte(p.XMLError)}
|
|
}
|
|
resp.Propstat = append(resp.Propstat, dav.SubPropstat{
|
|
Status: fmt.Sprintf("HTTP/1.1 %d %s", p.Status, dav.StatusText(p.Status)),
|
|
Prop: p.Props,
|
|
ResponseDescription: p.ResponseDescription,
|
|
Error: xmlErr,
|
|
})
|
|
}
|
|
return &resp
|
|
}
|
|
|
|
//fetch a matter's []dav.Propstat
|
|
func (this *DavService) PropstatsFromXmlNames(user *User, matter *Matter, xmlNames []xml.Name) []dav.Propstat {
|
|
|
|
propstats := make([]dav.Propstat, 0)
|
|
|
|
var okProperties []dav.Property
|
|
var notFoundProperties []dav.Property
|
|
|
|
for _, xmlName := range xmlNames {
|
|
//TODO: deadprops not implement yet.
|
|
|
|
// Otherwise, it must either be a live property or we don't know it.
|
|
if liveProp := LivePropMap[xmlName]; liveProp.findFn != nil && (liveProp.dir || !matter.Dir) {
|
|
innerXML := liveProp.findFn(user, matter)
|
|
|
|
okProperties = append(okProperties, dav.Property{
|
|
XMLName: xmlName,
|
|
InnerXML: []byte(innerXML),
|
|
})
|
|
} else {
|
|
this.logger.Info("handle props %s %s.", matter.Path, xmlName.Local)
|
|
|
|
propMap := matter.FetchPropMap()
|
|
if value, isPresent := propMap[xmlName.Local]; isPresent {
|
|
okProperties = append(okProperties, dav.Property{
|
|
XMLName: xmlName,
|
|
InnerXML: []byte(value),
|
|
})
|
|
} else {
|
|
|
|
//only accept Space not null.
|
|
if xmlName.Space != "" {
|
|
|
|
//collect not found props
|
|
notFoundProperties = append(notFoundProperties, dav.Property{
|
|
XMLName: xmlName,
|
|
InnerXML: []byte(""),
|
|
})
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(okProperties) == 0 && len(notFoundProperties) == 0 {
|
|
panic(result.BadRequest("cannot parse request properties"))
|
|
}
|
|
|
|
if len(okProperties) != 0 {
|
|
okPropstat := dav.Propstat{Status: http.StatusOK, Props: okProperties}
|
|
propstats = append(propstats, okPropstat)
|
|
}
|
|
|
|
if len(notFoundProperties) != 0 {
|
|
notFoundPropstat := dav.Propstat{Status: http.StatusNotFound, Props: notFoundProperties}
|
|
propstats = append(propstats, notFoundPropstat)
|
|
}
|
|
|
|
return propstats
|
|
|
|
}
|
|
|
|
func (this *DavService) AllPropXmlNames(matter *Matter) []xml.Name {
|
|
|
|
pnames := make([]xml.Name, 0)
|
|
for pn, prop := range LivePropMap {
|
|
if prop.findFn != nil && (prop.dir || !matter.Dir) {
|
|
pnames = append(pnames, pn)
|
|
}
|
|
}
|
|
|
|
return pnames
|
|
}
|
|
|
|
func (this *DavService) Propstats(user *User, matter *Matter, propfind *dav.Propfind) []dav.Propstat {
|
|
|
|
propstats := make([]dav.Propstat, 0)
|
|
if propfind.Propname != nil {
|
|
panic(result.BadRequest("TODO: propfind.Propname != nil "))
|
|
} else if propfind.Allprop != nil {
|
|
|
|
//TODO: if include other things. add to it.
|
|
xmlNames := this.AllPropXmlNames(matter)
|
|
|
|
propstats = this.PropstatsFromXmlNames(user, matter, xmlNames)
|
|
|
|
} else {
|
|
propstats = this.PropstatsFromXmlNames(user, matter, propfind.Prop)
|
|
}
|
|
|
|
return propstats
|
|
|
|
}
|
|
|
|
//list the directory.
|
|
func (this *DavService) HandlePropfind(writer http.ResponseWriter, request *http.Request, user *User, subPath string) {
|
|
|
|
fmt.Printf("PROPFIND %s\n", subPath)
|
|
|
|
// read depth
|
|
depth := this.ParseDepth(request)
|
|
|
|
propfind := dav.ReadPropfind(request.Body)
|
|
|
|
//find the matter, if subPath is null, means the root directory.
|
|
matter := this.matterDao.CheckWithRootByPath(subPath, user)
|
|
|
|
var matters []*Matter
|
|
if depth == 0 {
|
|
matters = []*Matter{matter}
|
|
} else {
|
|
// len(matters) == 0 means empty directory
|
|
matters = this.matterDao.FindByPuuidAndUserUuidAndDeleted(matter.Uuid, user.Uuid, FALSE, nil)
|
|
|
|
//add this matter to head.
|
|
matters = append([]*Matter{matter}, matters...)
|
|
}
|
|
|
|
//prepare a multiStatusWriter.
|
|
multiStatusWriter := &dav.MultiStatusWriter{Writer: writer}
|
|
|
|
for _, matter := range matters {
|
|
|
|
fmt.Printf("handle Matter %s\n", matter.Path)
|
|
|
|
propstats := this.Propstats(user, matter, propfind)
|
|
visitPath := fmt.Sprintf("%s%s", WEBDAV_PREFIX, matter.Path)
|
|
response := this.makePropstatResponse(visitPath, propstats)
|
|
|
|
err := multiStatusWriter.Write(response)
|
|
this.PanicError(err)
|
|
}
|
|
|
|
err := multiStatusWriter.Close()
|
|
this.PanicError(err)
|
|
|
|
fmt.Printf("%v %v \n", subPath, propfind.Prop)
|
|
|
|
}
|
|
|
|
//change the file's property
|
|
func (this *DavService) HandleProppatch(writer http.ResponseWriter, request *http.Request, user *User, subPath string) {
|
|
|
|
fmt.Printf("PROPPATCH %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()
|
|
}
|
|
|
|
matter := this.matterDao.checkByUserUuidAndPath(user.Uuid, subPath)
|
|
|
|
patches, status, err := webdav.ReadProppatch(request.Body)
|
|
this.PanicError(err)
|
|
|
|
fmt.Println("status:%v", status)
|
|
|
|
//prepare a multiStatusWriter.
|
|
multiStatusWriter := &dav.MultiStatusWriter{Writer: writer}
|
|
|
|
propstats := make([]dav.Propstat, 0)
|
|
propMap := matter.FetchPropMap()
|
|
for _, patch := range patches {
|
|
propStat := dav.Propstat{Status: http.StatusOK}
|
|
if patch.Remove {
|
|
|
|
if len(patch.Props) > 0 {
|
|
property := patch.Props[0]
|
|
if _, isPresent := propMap[property.XMLName.Local]; isPresent {
|
|
//delete the prop.
|
|
delete(propMap, property.XMLName.Local)
|
|
}
|
|
}
|
|
} else {
|
|
for _, prop := range patch.Props {
|
|
propMap[prop.XMLName.Local] = string(prop.InnerXML)
|
|
|
|
propStat.Props = append(propStat.Props, dav.Property{XMLName: xml.Name{Space: prop.XMLName.Space, Local: prop.XMLName.Local}})
|
|
|
|
}
|
|
}
|
|
|
|
propstats = append(propstats, propStat)
|
|
|
|
}
|
|
matter.SetPropMap(propMap)
|
|
// update the matter
|
|
this.matterDao.Save(matter)
|
|
|
|
visitPath := fmt.Sprintf("%s%s", WEBDAV_PREFIX, matter.Path)
|
|
response := this.makePropstatResponse(visitPath, propstats)
|
|
|
|
err1 := multiStatusWriter.Write(response)
|
|
this.PanicError(err1)
|
|
|
|
err2 := multiStatusWriter.Close()
|
|
this.PanicError(err2)
|
|
|
|
}
|
|
|
|
//handle download
|
|
func (this *DavService) HandleGetHeadPost(writer http.ResponseWriter, request *http.Request, user *User, subPath string) {
|
|
|
|
fmt.Printf("GET %s\n", subPath)
|
|
|
|
matter := this.matterDao.CheckWithRootByPath(subPath, user)
|
|
|
|
//if this is a Directory, it means Propfind
|
|
if matter.Dir {
|
|
this.HandlePropfind(writer, request, user, subPath)
|
|
return
|
|
}
|
|
|
|
//download a file.
|
|
this.matterService.DownloadFile(writer, request, matter.AbsolutePath(), matter.Name, false)
|
|
|
|
}
|
|
|
|
//upload a file
|
|
func (this *DavService) HandlePut(writer http.ResponseWriter, request *http.Request, user *User, subPath string) {
|
|
|
|
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)
|
|
|
|
dirMatter := this.matterDao.CheckWithRootByPath(dirPath, user)
|
|
|
|
//if exist delete it.
|
|
srcMatter := this.matterDao.findByUserUuidAndPath(user.Uuid, subPath)
|
|
if srcMatter != nil {
|
|
this.matterService.AtomicDelete(request, srcMatter, user)
|
|
}
|
|
|
|
this.matterService.Upload(request, request.Body, user, dirMatter, filename, true)
|
|
|
|
//set the status code 201
|
|
writer.WriteHeader(http.StatusCreated)
|
|
|
|
}
|
|
|
|
//delete file
|
|
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(r, matter, user)
|
|
}
|
|
|
|
//crate a directory
|
|
func (this *DavService) HandleMkcol(writer http.ResponseWriter, request *http.Request, user *User, subPath string) {
|
|
|
|
fmt.Printf("MKCOL %s\n", subPath)
|
|
|
|
//the body of MKCOL request MUST be empty. (RFC2518:8.3.1)
|
|
bodyBytes, err := ioutil.ReadAll(request.Body)
|
|
if err != nil {
|
|
fmt.Println("occur error when reading body: " + err.Error())
|
|
} else {
|
|
if len(bodyBytes) != 0 {
|
|
//throw conflict error
|
|
panic(result.CustomWebResult(result.UNSUPPORTED_MEDIA_TYPE, fmt.Sprintf("%s MKCOL should NO body", subPath)))
|
|
}
|
|
}
|
|
|
|
thisDirName := util.GetFilenameOfPath(subPath)
|
|
dirPath := util.GetDirOfPath(subPath)
|
|
|
|
dirMatter := this.matterDao.FindWithRootByPath(dirPath, user)
|
|
if dirMatter == nil {
|
|
//throw conflict error
|
|
panic(result.CustomWebResult(result.CONFLICT, fmt.Sprintf("%s not exist", dirPath)))
|
|
}
|
|
|
|
//check whether col exists. (RFC2518:8.3.1)
|
|
dbMatter := this.matterDao.FindByUserUuidAndPuuidAndDirAndName(user.Uuid, dirMatter.Uuid, TRUE, thisDirName)
|
|
if dbMatter != nil {
|
|
panic(result.CustomWebResult(result.METHOD_NOT_ALLOWED, fmt.Sprintf("%s already exists", dirPath)))
|
|
}
|
|
|
|
//check whether file exists. (RFC2518:8.3.1)
|
|
fileMatter := this.matterDao.FindByUserUuidAndPuuidAndDirAndName(user.Uuid, dirMatter.Uuid, FALSE, thisDirName)
|
|
if fileMatter != nil {
|
|
panic(result.CustomWebResult(result.METHOD_NOT_ALLOWED, fmt.Sprintf("%s file already exists", dirPath)))
|
|
}
|
|
|
|
this.matterService.AtomicCreateDirectory(request, dirMatter, thisDirName, user)
|
|
|
|
}
|
|
|
|
//cors options
|
|
func (this *DavService) HandleOptions(w http.ResponseWriter, r *http.Request, user *User, subPath string) {
|
|
|
|
fmt.Printf("OPTIONS %s\n", subPath)
|
|
|
|
matter := this.matterDao.CheckWithRootByPath(subPath, user)
|
|
|
|
allow := "OPTIONS, LOCK, PUT, MKCOL"
|
|
if matter.Dir {
|
|
allow = "OPTIONS, LOCK, DELETE, PROPPATCH, COPY, MOVE, UNLOCK, PROPFIND"
|
|
} else {
|
|
allow = "OPTIONS, LOCK, GET, HEAD, POST, DELETE, PROPPATCH, COPY, MOVE, UNLOCK, PROPFIND, PUT"
|
|
}
|
|
|
|
w.Header().Set("Allow", allow)
|
|
// http://www.webdav.org/specs/rfc4918.html#dav.compliance.classes
|
|
w.Header().Set("DAV", "1, 2")
|
|
// http://msdn.microsoft.com/en-au/library/cc250217.aspx
|
|
w.Header().Set("MS-Author-Via", "DAV")
|
|
|
|
}
|
|
|
|
//prepare for moving or copying
|
|
func (this *DavService) prepareMoveCopy(
|
|
writer http.ResponseWriter,
|
|
request *http.Request,
|
|
user *User, subPath string) (
|
|
srcMatter *Matter,
|
|
destDirMatter *Matter,
|
|
srcDirPath string,
|
|
destinationDirPath string,
|
|
destinationName string,
|
|
overwrite bool) {
|
|
|
|
//parse the destination.
|
|
destinationStr := request.Header.Get("Destination")
|
|
|
|
//parse Overwrite。
|
|
overwriteStr := request.Header.Get("Overwrite")
|
|
|
|
//destination path with prefix
|
|
var fullDestinationPath string
|
|
//destination path without prefix
|
|
var destinationPath string
|
|
|
|
if destinationStr == "" {
|
|
panic(result.BadRequest("Header Destination cannot be null"))
|
|
}
|
|
|
|
//if rename. not start with http
|
|
if strings.HasPrefix(destinationStr, WEBDAV_PREFIX) {
|
|
fullDestinationPath = destinationStr
|
|
} else {
|
|
destinationUrl, err := url.Parse(destinationStr)
|
|
this.PanicError(err)
|
|
if destinationUrl.Host != request.Host {
|
|
panic(result.BadRequest("Destination Host not the same. %s %s != %s", destinationStr, destinationUrl.Host, request.Host))
|
|
}
|
|
fullDestinationPath = destinationUrl.Path
|
|
}
|
|
|
|
//clean the relative path. eg. /a/b/../ => /a/
|
|
fullDestinationPath = path.Clean(fullDestinationPath)
|
|
|
|
//clean the prefix
|
|
pattern := fmt.Sprintf(`^%s(.*)$`, WEBDAV_PREFIX)
|
|
reg := regexp.MustCompile(pattern)
|
|
strs := reg.FindStringSubmatch(fullDestinationPath)
|
|
if len(strs) == 2 {
|
|
destinationPath = strs[1]
|
|
} else {
|
|
panic(result.BadRequest("destination prefix must be %s", WEBDAV_PREFIX))
|
|
}
|
|
|
|
destinationName = util.GetFilenameOfPath(destinationPath)
|
|
destinationDirPath = util.GetDirOfPath(destinationPath)
|
|
srcDirPath = util.GetDirOfPath(subPath)
|
|
|
|
overwrite = false
|
|
if overwriteStr == "T" {
|
|
overwrite = true
|
|
}
|
|
|
|
//if not change return.
|
|
if destinationPath == subPath {
|
|
return
|
|
}
|
|
|
|
//source matter
|
|
srcMatter = this.matterDao.CheckWithRootByPath(subPath, user)
|
|
|
|
//if source matter is root.
|
|
if srcMatter.Uuid == MATTER_ROOT {
|
|
panic(result.BadRequest("you cannot move the root directory"))
|
|
}
|
|
|
|
destDirMatter = this.matterDao.FindWithRootByPath(destinationDirPath, user)
|
|
if destDirMatter == nil {
|
|
//throw conflict error
|
|
panic(result.CustomWebResult(result.CONFLICT, fmt.Sprintf("%s not exist", destinationDirPath)))
|
|
}
|
|
|
|
return srcMatter, destDirMatter, srcDirPath, destinationDirPath, destinationName, overwrite
|
|
|
|
}
|
|
|
|
//move or rename.
|
|
func (this *DavService) HandleMove(writer http.ResponseWriter, request *http.Request, user *User, subPath string) {
|
|
|
|
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
|
|
if destinationDirPath == srcDirPath {
|
|
//if destination path not change. it means rename.
|
|
this.matterService.AtomicRename(request, srcMatter, destinationName, overwrite, user)
|
|
} else {
|
|
this.matterService.AtomicMove(request, srcMatter, destDirMatter, overwrite, user)
|
|
}
|
|
|
|
this.logger.Info("finish moving %s => %s", subPath, destDirMatter.Path)
|
|
|
|
if overwrite {
|
|
//overwrite old. set the status code 204
|
|
writer.WriteHeader(http.StatusNoContent)
|
|
} else {
|
|
//copy new. set the status code 201
|
|
writer.WriteHeader(http.StatusCreated)
|
|
}
|
|
}
|
|
|
|
//copy file/directory
|
|
func (this *DavService) HandleCopy(writer http.ResponseWriter, request *http.Request, user *User, subPath string) {
|
|
|
|
fmt.Printf("COPY %s\n", 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
|
|
this.matterService.AtomicCopy(request, srcMatter, destDirMatter, destinationName, overwrite, user)
|
|
|
|
this.logger.Info("finish copying %s => %s", subPath, destDirMatter.Path)
|
|
|
|
if overwrite {
|
|
//overwrite old. set the status code 204
|
|
writer.WriteHeader(http.StatusNoContent)
|
|
} else {
|
|
//copy new. set the status code 201
|
|
writer.WriteHeader(http.StatusCreated)
|
|
}
|
|
|
|
}
|
|
|
|
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) {
|
|
|
|
duration, err := webdav.ParseTimeout(r.Header.Get("Timeout"))
|
|
if err != nil {
|
|
panic(result.BadRequest(err.Error()))
|
|
}
|
|
li, status, err := webdav.ReadLockInfo(r.Body)
|
|
if err != nil {
|
|
panic(result.BadRequest(fmt.Sprintf("error:%s, status=%d", err.Error(), status)))
|
|
}
|
|
|
|
token, ld, now, created := "", webdav.LockDetails{}, time.Now(), false
|
|
if li == (webdav.LockInfo{}) {
|
|
// An empty LockInfo means to refresh the lock.
|
|
ih, ok := webdav.ParseIfHeader(r.Header.Get("If"))
|
|
if !ok {
|
|
panic(result.BadRequest(webdav.ErrInvalidIfHeader.Error()))
|
|
}
|
|
if len(ih.Lists) == 1 && len(ih.Lists[0].Conditions) == 1 {
|
|
token = ih.Lists[0].Conditions[0].Token
|
|
}
|
|
if token == "" {
|
|
panic(result.BadRequest(webdav.ErrInvalidLockToken.Error()))
|
|
}
|
|
ld, err = this.lockSystem.Refresh(now, token, duration)
|
|
if err != nil {
|
|
if err == webdav.ErrNoSuchLock {
|
|
panic(result.StatusCodeWebResult(http.StatusPreconditionFailed, err.Error()))
|
|
}
|
|
panic(result.StatusCodeWebResult(http.StatusInternalServerError, err.Error()))
|
|
}
|
|
|
|
} else {
|
|
// Section 9.10.3 says that "If no Depth header is submitted on a LOCK request,
|
|
// then the request MUST act as if a "Depth:infinity" had been submitted."
|
|
depth := webdav.InfiniteDepth
|
|
if hdr := r.Header.Get("Depth"); hdr != "" {
|
|
depth = webdav.ParseDepth(hdr)
|
|
if depth != 0 && depth != webdav.InfiniteDepth {
|
|
// Section 9.10.3 says that "Values other than 0 or infinity must not be
|
|
// used with the Depth header on a LOCK method".
|
|
panic(result.StatusCodeWebResult(http.StatusBadRequest, webdav.ErrInvalidDepth.Error()))
|
|
}
|
|
}
|
|
|
|
reqPath, status, err := this.stripPrefix(r.URL.Path)
|
|
if err != nil {
|
|
panic(result.StatusCodeWebResult(status, err.Error()))
|
|
}
|
|
|
|
ld = webdav.LockDetails{
|
|
Root: reqPath,
|
|
Duration: duration,
|
|
OwnerXML: li.Owner.InnerXML,
|
|
ZeroDepth: depth == 0,
|
|
}
|
|
token, err = this.lockSystem.Create(now, ld)
|
|
if err != nil {
|
|
if err == webdav.ErrLocked {
|
|
panic(result.StatusCodeWebResult(http.StatusLocked, err.Error()))
|
|
}
|
|
panic(result.StatusCodeWebResult(http.StatusInternalServerError, err.Error()))
|
|
}
|
|
defer func() {
|
|
//when error occur, rollback.
|
|
//this.lockSystem.Unlock(now, token)
|
|
}()
|
|
|
|
// Create the resource if it didn't previously exist.
|
|
// ctx := r.Context()
|
|
//if _, err := this.FileSystem.Stat(ctx, subPath); err != nil {
|
|
// f, err := h.FileSystem.OpenFile(ctx, reqPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
|
|
// if err != nil {
|
|
// // TODO: detect missing intermediate dirs and return http.StatusConflict?
|
|
// return http.StatusInternalServerError, err
|
|
// }
|
|
// f.Close()
|
|
// created = true
|
|
//}
|
|
|
|
// http://www.webdav.org/specs/rfc4918.html#HEADER_Lock-Token says that the
|
|
// Lock-Token value is a Coded-URL. We add angle brackets.
|
|
w.Header().Set("Lock-Token", "<"+token+">")
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/xml; charset=utf-8")
|
|
if created {
|
|
// This is "w.WriteHeader(http.StatusCreated)" and not "return
|
|
// http.StatusCreated, nil" because we write our own (XML) response to w
|
|
// and Handler.ServeHTTP would otherwise write "Created".
|
|
w.WriteHeader(http.StatusCreated)
|
|
}
|
|
_, _ = webdav.WriteLockInfo(w, token, ld)
|
|
|
|
}
|
|
|
|
//unlock
|
|
func (this *DavService) HandleUnlock(w http.ResponseWriter, r *http.Request, user *User, subPath string) {
|
|
|
|
// 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.
|
|
func (this *DavService) HandleDav(writer http.ResponseWriter, request *http.Request, user *User, subPath string) {
|
|
|
|
method := request.Method
|
|
if method == "OPTIONS" {
|
|
|
|
//cors option
|
|
this.HandleOptions(writer, request, user, subPath)
|
|
|
|
} else if method == "GET" || method == "HEAD" || method == "POST" {
|
|
|
|
//get the detail of file. download
|
|
this.HandleGetHeadPost(writer, request, user, subPath)
|
|
|
|
} else if method == "DELETE" {
|
|
|
|
//delete file
|
|
this.HandleDelete(writer, request, user, subPath)
|
|
|
|
} else if method == "PUT" {
|
|
|
|
//upload file
|
|
this.HandlePut(writer, request, user, subPath)
|
|
|
|
} else if method == "MKCOL" {
|
|
|
|
//crate directory
|
|
this.HandleMkcol(writer, request, user, subPath)
|
|
|
|
} else if method == "COPY" {
|
|
|
|
//copy file/directory
|
|
this.HandleCopy(writer, request, user, subPath)
|
|
|
|
} else if method == "MOVE" {
|
|
|
|
//move/rename a file or directory
|
|
this.HandleMove(writer, request, user, subPath)
|
|
|
|
} else if method == "LOCK" {
|
|
|
|
//lock
|
|
this.HandleLock(writer, request, user, subPath)
|
|
|
|
} else if method == "UNLOCK" {
|
|
|
|
//unlock
|
|
this.HandleUnlock(writer, request, user, subPath)
|
|
|
|
} else if method == "PROPFIND" {
|
|
|
|
//list a directory
|
|
this.HandlePropfind(writer, request, user, subPath)
|
|
|
|
} else if method == "PROPPATCH" {
|
|
|
|
//change file's property.
|
|
this.HandleProppatch(writer, request, user, subPath)
|
|
|
|
} else {
|
|
|
|
panic(result.BadRequest("not support %s yet.", method))
|
|
|
|
}
|
|
|
|
}
|