529 lines
14 KiB
Go
529 lines
14 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"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/url"
|
|
"path"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
/**
|
|
*
|
|
* 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
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
}
|
|
|
|
//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 properties []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)
|
|
|
|
properties = append(properties, dav.Property{
|
|
XMLName: xmlName,
|
|
InnerXML: []byte(innerXML),
|
|
})
|
|
} else {
|
|
this.logger.Info("%s %s cannot finish.", matter.Path, xmlName.Local)
|
|
}
|
|
}
|
|
|
|
if len(properties) == 0 {
|
|
panic(result.BadRequest("cannot parse request properties"))
|
|
}
|
|
|
|
okPropstat := dav.Propstat{Status: http.StatusOK, Props: properties}
|
|
|
|
propstats = append(propstats, okPropstat)
|
|
|
|
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.FindByPuuidAndUserUuid(matter.Uuid, user.Uuid, 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)
|
|
|
|
}
|
|
|
|
//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)
|
|
|
|
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(writer http.ResponseWriter, request *http.Request, user *User, subPath string) {
|
|
|
|
fmt.Printf("DELETE %s\n", subPath)
|
|
|
|
matter := this.matterDao.CheckWithRootByPath(subPath, user)
|
|
|
|
this.matterService.AtomicDelete(request, 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)
|
|
|
|
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)
|
|
|
|
//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)
|
|
}
|
|
|
|
}
|
|
|
|
//lock.
|
|
func (this *DavService) HandleLock(writer http.ResponseWriter, request *http.Request, user *User, subPath string) {
|
|
|
|
panic(result.BadRequest("not support LOCK yet."))
|
|
}
|
|
|
|
//unlock
|
|
func (this *DavService) HandleUnlock(writer http.ResponseWriter, request *http.Request, user *User, subPath string) {
|
|
|
|
panic(result.BadRequest("not support UNLOCK yet."))
|
|
}
|
|
|
|
//change the file's property
|
|
func (this *DavService) HandleProppatch(writer http.ResponseWriter, request *http.Request, user *User, subPath string) {
|
|
|
|
panic(result.BadRequest("not support PROPPATCH yet."))
|
|
}
|
|
|
|
//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))
|
|
|
|
}
|
|
|
|
}
|