diff --git a/README.md b/README.md index 97c8b99..c4410e5 100644 --- a/README.md +++ b/README.md @@ -606,3 +606,4 @@ Windows # 鸣谢 本项目大量借鉴了以下相关项目的功能&成果 > [tickstep/cloudpan189-go](https://github.com/tickstep/cloudpan189-go) +> [hacdias/webdav](https://github.com/hacdias/webdav) \ No newline at end of file diff --git a/go.mod b/go.mod index cc1680a..cf034a7 100644 --- a/go.mod +++ b/go.mod @@ -10,11 +10,12 @@ require ( github.com/oleiade/lane v0.0.0-20160817071224-3053869314bb github.com/olekukonko/tablewriter v0.0.2-0.20190618033246-cc27d85e17ce github.com/peterh/liner v1.2.1 - github.com/tickstep/bolt v1.3.3 github.com/tickstep/aliyunpan-api v0.0.3 + github.com/tickstep/bolt v1.3.3 github.com/tickstep/library-go v0.0.5 github.com/urfave/cli v1.21.1-0.20190817182405-23c83030263f - golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 // indirect + golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a + golang.org/x/net v0.0.0-20210614182718-04defd469f4e ) //replace github.com/tickstep/bolt => /Users/tickstep/Documents/Workspace/go/projects/bolt diff --git a/go.sum b/go.sum index c8c8b10..aa125cc 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,7 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/GeertJohan/go.incremental v1.0.0 h1:7AH+pY1XUgQE4Y1HcXYaMqAI0m9yrFqo/jt0CW30vsg= github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0= @@ -17,6 +18,9 @@ github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 h1:PJPDf8OUfOK1bb/NeTKd4f1QXZItOX389VN3B6qC8ro= github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= @@ -39,6 +43,7 @@ github.com/peterh/liner v1.1.1-0.20190305032635-6f820f8f90ce h1:Lz+a/i+oS4A7tb6J github.com/peterh/liner v1.1.1-0.20190305032635-6f820f8f90ce/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= github.com/peterh/liner v1.2.1 h1:O4BlKaq/LWu6VRWmol4ByWfzx6MfXc5Op5HETyIy5yg= github.com/peterh/liner v1.2.1/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= @@ -50,8 +55,10 @@ github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd/go.mod h1:TrYk7fJV github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/tickstep/aliyunpan-api v0.0.2 h1:aXChXeb1vmpINqV2qf60Bi3SGKgZPdmV7D8Eymb29Yw= github.com/tickstep/aliyunpan-api v0.0.2/go.mod h1:EdciagdVeAHfqvzStVEVp1UVtySaq1DFd5RSucDfKWY= github.com/tickstep/aliyunpan-api v0.0.3 h1:DBxRIjRUuebIjAgAPtS7rYZ4tafzOEF6J2949V2FeDY= @@ -85,19 +92,45 @@ github.com/xujiajun/nutsdb v0.5.1-0.20200830145825-432dd3d0c873 h1:pgTLmYRrfy9lG github.com/xujiajun/nutsdb v0.5.1-0.20200830145825-432dd3d0c873/go.mod h1:Q8FXi2zeQRluPpUl/CKQ6J7u/9gcI02J6cZp3owFLyA= github.com/xujiajun/utils v0.0.0-20190123093513-8bf096c4f53b h1:jKG9OiL4T4xQN3IUrhUpc1tG+HfDXppkgVcrAiiaI/0= github.com/xujiajun/utils v0.0.0-20190123093513-8bf096c4f53b/go.mod h1:AZd87GYJlUzl82Yab2kTjx1EyXSQCAfZDhpTo1SQC4k= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/webdav/dir.go b/internal/webdav/dir.go new file mode 100644 index 0000000..9593ec7 --- /dev/null +++ b/internal/webdav/dir.go @@ -0,0 +1,83 @@ +package webdav + +import ( + "context" + "mime" + "os" + "path" + + "golang.org/x/net/webdav" +) + +// NoSniffFileInfo wraps any generic FileInfo interface and bypasses mime type sniffing. +type NoSniffFileInfo struct { + os.FileInfo +} + +func (w NoSniffFileInfo) ContentType(ctx context.Context) (contentType string, err error) { + if mimeType := mime.TypeByExtension(path.Ext(w.FileInfo.Name())); mimeType != "" { + // We can figure out the mime from the extension. + return mimeType, nil + } else { + // We can't figure out the mime type without sniffing, call it an octet stream. + return "application/octet-stream", nil + } +} + +type WebDavDir struct { + webdav.Dir + NoSniff bool +} + +func (d WebDavDir) Stat(ctx context.Context, name string) (os.FileInfo, error) { + // Skip wrapping if NoSniff is off + if !d.NoSniff { + return d.Dir.Stat(ctx, name) + } + + info, err := d.Dir.Stat(ctx, name) + if err != nil { + return nil, err + } + + return NoSniffFileInfo{info}, nil +} + +func (d WebDavDir) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (webdav.File, error) { + // Skip wrapping if NoSniff is off + if !d.NoSniff { + return d.Dir.OpenFile(ctx, name, flag, perm) + } + + file, err := d.Dir.OpenFile(ctx, name, flag, perm) + if err != nil { + return nil, err + } + + return WebDavFile{File: file}, nil +} + +type WebDavFile struct { + webdav.File +} + +func (f WebDavFile) Stat() (os.FileInfo, error) { + info, err := f.File.Stat() + if err != nil { + return nil, err + } + + return NoSniffFileInfo{info}, nil +} + +func (f WebDavFile) Readdir(count int) (fis []os.FileInfo, err error) { + fis, err = f.File.Readdir(count) + if err != nil { + return nil, err + } + + for i := range fis { + fis[i] = NoSniffFileInfo{fis[i]} + } + return fis, nil +} diff --git a/internal/webdav/user.go b/internal/webdav/user.go new file mode 100644 index 0000000..481179f --- /dev/null +++ b/internal/webdav/user.go @@ -0,0 +1,50 @@ +package webdav + +import ( + "regexp" + "strings" + + "golang.org/x/net/webdav" +) + +// Rule is a dissalow/allow rule. +type Rule struct { + Regex bool + Allow bool + Modify bool + Path string + Regexp *regexp.Regexp +} + +// User contains the settings of each user. +type User struct { + Username string + Password string + Scope string + Modify bool + Rules []*Rule + Handler *webdav.Handler +} + +// Allowed checks if the user has permission to access a directory/file +func (u User) Allowed(url string, noModification bool) bool { + var rule *Rule + i := len(u.Rules) - 1 + + for i >= 0 { + rule = u.Rules[i] + + isAllowed := rule.Allow && (noModification || rule.Modify) + if rule.Regex { + if rule.Regexp.MatchString(url) { + return isAllowed + } + } else if strings.HasPrefix(url, rule.Path) { + return isAllowed + } + + i-- + } + + return noModification || u.Modify +} diff --git a/internal/webdav/utils.go b/internal/webdav/utils.go new file mode 100644 index 0000000..fa719d5 --- /dev/null +++ b/internal/webdav/utils.go @@ -0,0 +1,25 @@ +package webdav + +import ( + "strings" + + "golang.org/x/crypto/bcrypt" +) + +func checkPassword(saved, input string) bool { + if strings.HasPrefix(saved, "{bcrypt}") { + savedPassword := strings.TrimPrefix(saved, "{bcrypt}") + return bcrypt.CompareHashAndPassword([]byte(savedPassword), []byte(input)) == nil + } + + return saved == input +} + +func isAllowedHost(allowedHosts []string, origin string) bool { + for _, host := range allowedHosts { + if host == origin { + return true + } + } + return false +} diff --git a/internal/webdav/webdav.go b/internal/webdav/webdav.go new file mode 100644 index 0000000..8684f74 --- /dev/null +++ b/internal/webdav/webdav.go @@ -0,0 +1,175 @@ +package webdav + +import ( + "context" + "github.com/tickstep/library-go/logger" + "net/http" + "strings" +) + +// CorsCfg is the CORS config. +type CorsCfg struct { + Enabled bool + Credentials bool + AllowedHeaders []string + AllowedHosts []string + AllowedMethods []string + ExposedHeaders []string +} + +// Config is the configuration of a WebDAV instance. +type Config struct { + *User + Auth bool + NoSniff bool + Cors CorsCfg + Users map[string]*User + LogFormat string +} + +// ServeHTTP determines if the request is for this plugin, and if all prerequisites are met. +func (c *Config) ServeHTTP(w http.ResponseWriter, r *http.Request) { + u := c.User + requestOrigin := r.Header.Get("Origin") + + // Add CORS headers before any operation so even on a 401 unauthorized status, CORS will work. + if c.Cors.Enabled && requestOrigin != "" { + headers := w.Header() + + allowedHeaders := strings.Join(c.Cors.AllowedHeaders, ", ") + allowedMethods := strings.Join(c.Cors.AllowedMethods, ", ") + exposedHeaders := strings.Join(c.Cors.ExposedHeaders, ", ") + + allowAllHosts := len(c.Cors.AllowedHosts) == 1 && c.Cors.AllowedHosts[0] == "*" + allowedHost := isAllowedHost(c.Cors.AllowedHosts, requestOrigin) + + if allowAllHosts { + headers.Set("Access-Control-Allow-Origin", "*") + } else if allowedHost { + headers.Set("Access-Control-Allow-Origin", requestOrigin) + } + + if allowAllHosts || allowedHost { + headers.Set("Access-Control-Allow-Headers", allowedHeaders) + headers.Set("Access-Control-Allow-Methods", allowedMethods) + + if c.Cors.Credentials { + headers.Set("Access-Control-Allow-Credentials", "true") + } + + if len(c.Cors.ExposedHeaders) > 0 { + headers.Set("Access-Control-Expose-Headers", exposedHeaders) + } + } + } + + if r.Method == "OPTIONS" && c.Cors.Enabled && requestOrigin != "" { + return + } + + // Authentication + if c.Auth { + w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`) + + // Gets the correct user for this request. + username, password, ok := r.BasicAuth() + logger.Verboseln("login attempt", "username = " + username, "remote_address = " + r.RemoteAddr) + if !ok { + http.Error(w, "Not authorized", 401) + return + } + + user, ok := c.Users[username] + if !ok { + http.Error(w, "Not authorized", 401) + return + } + + if !checkPassword(user.Password, password) { + logger.Verboseln("invalid password", "username = " + username, "remote_address = " + r.RemoteAddr) + http.Error(w, "Not authorized", 401) + return + } + + u = user + logger.Verboseln("user authorized", "username = " + username) + } else { + // Even if Auth is disabled, we might want to get + // the user from the Basic Auth header. Useful for Caddy + // plugin implementation. + username, _, ok := r.BasicAuth() + if ok { + if user, ok := c.Users[username]; ok { + u = user + } + } + } + + // Checks for user permissions relatively to this PATH. + noModification := r.Method == "GET" || + r.Method == "HEAD" || + r.Method == "OPTIONS" || + r.Method == "PROPFIND" || + r.Method == "PUT" || + r.Method == "LOCK" || + r.Method == "UNLOCK" || + r.Method == "MOVE" || + r.Method == "DELETE" + + if !u.Allowed(r.URL.Path, noModification) { + w.WriteHeader(http.StatusForbidden) + return + } + + if r.Method == "HEAD" { + w = newResponseWriterNoBody(w) + } + + // Excerpt from RFC4918, section 9.4: + // + // GET, when applied to a collection, may return the contents of an + // "index.html" resource, a human-readable view of the contents of + // the collection, or something else altogether. + // + // Get, when applied to collection, will return the same as PROPFIND method. + if r.Method == "GET" && strings.HasPrefix(r.URL.Path, u.Handler.Prefix) { + info, err := u.Handler.FileSystem.Stat(context.TODO(), strings.TrimPrefix(r.URL.Path, u.Handler.Prefix)) + if err == nil && info.IsDir() { + r.Method = "PROPFIND" + + if r.Header.Get("Depth") == "" { + r.Header.Add("Depth", "1") + } + } + } + + // Runs the WebDAV. + //u.Handler.LockSystem = webdav.NewMemLS() + u.Handler.ServeHTTP(w, r) +} + +// responseWriterNoBody is a wrapper used to suprress the body of the response +// to a request. Mainly used for HEAD requests. +type responseWriterNoBody struct { + http.ResponseWriter +} + +// newResponseWriterNoBody creates a new responseWriterNoBody. +func newResponseWriterNoBody(w http.ResponseWriter) *responseWriterNoBody { + return &responseWriterNoBody{w} +} + +// Header executes the Header method from the http.ResponseWriter. +func (w responseWriterNoBody) Header() http.Header { + return w.ResponseWriter.Header() +} + +// Write suprresses the body. +func (w responseWriterNoBody) Write(data []byte) (int, error) { + return 0, nil +} + +// WriteHeader writes the header to the http.ResponseWriter. +func (w responseWriterNoBody) WriteHeader(statusCode int) { + w.ResponseWriter.WriteHeader(statusCode) +} diff --git a/internal/webdav/webdav_config.go b/internal/webdav/webdav_config.go new file mode 100644 index 0000000..771ae7c --- /dev/null +++ b/internal/webdav/webdav_config.go @@ -0,0 +1,66 @@ +package webdav + +import ( + "github.com/tickstep/library-go/logger" + "golang.org/x/net/webdav" + "log" + "net" + "net/http" + "strconv" +) + +type WebdavUser struct { + Username string `json:"username"` + Password string `json:"password"` + Scope string `json:"scope"` +} + +type WebdavConfig struct { + // 指定Webdav使用哪个账号的云盘资源 + PanUserId string `json:"panUserId"` + + Address string `json:"address"` + Port int `json:"port"` + Prefix string `json:"prefix"` + Users []WebdavUser `json:"users"` +} + +func (w *WebdavConfig) StartServer() { + users := map[string]*User{} + for _,u := range w.Users { + users[u.Username] = &User{ + Username: u.Username, + Password: u.Password, + Scope: u.Scope, + Modify: true, + Rules: nil, + Handler: &webdav.Handler{ + Prefix: w.Prefix, + FileSystem: WebDavDir{ + Dir: webdav.Dir(u.Scope), + NoSniff: false, + }, + LockSystem: webdav.NewMemLS(), + }, + } + } + cfg := &Config{ + Auth: true, + NoSniff: false, + Cors: CorsCfg{ + Enabled: false, + Credentials: false, + }, + Users: users, + LogFormat: "", + } + + listener, err := net.Listen("tcp", w.Address + ":" + strconv.Itoa(w.Port)) + if err != nil { + log.Fatal(err) + } + if err := http.Serve(listener, cfg); err != nil { + logger.Verboseln("shutting server", err) + } +} + diff --git a/main.go b/main.go index a783831..bc4b617 100644 --- a/main.go +++ b/main.go @@ -17,6 +17,7 @@ import ( "fmt" "github.com/tickstep/aliyunpan-api/aliyunpan" "github.com/tickstep/aliyunpan/cmder" + "github.com/tickstep/aliyunpan/internal/webdav" "os" "path" "path/filepath" @@ -519,35 +520,49 @@ func main() { }, // 调试用 debug - //{ - // Name: "debug", - // Aliases: []string{"dg"}, - // Usage: "开发调试用", - // Description: "", - // Category: "debug", - // Before: cmder.ReloadConfigFunc, - // Action: func(c *cli.Context) error { - // os.Setenv(config.EnvVerbose, c.String("verbose")) - // fmt.Println("显示调试日志", logger.IsVerbose) - // - // user := config.Config.ActiveUser() - // fdl,_ := user.CacheFilesDirectoriesList("/tmp") - // fmt.Println(fdl) - // return nil - // }, - // Flags: []cli.Flag{ - // cli.StringFlag{ - // Name: "param", - // Usage: "参数", - // }, - // cli.BoolFlag{ - // Name: "verbose", - // Destination: &logger.IsVerbose, - // EnvVar: config.EnvVerbose, - // Usage: "显示调试信息", - // }, - // }, - //}, + { + Name: "debug", + Aliases: []string{"dg"}, + Usage: "开发调试用", + Description: "", + Category: "debug", + Before: cmder.ReloadConfigFunc, + Action: func(c *cli.Context) error { + os.Setenv(config.EnvVerbose, c.String("verbose")) + fmt.Println("显示调试日志", logger.IsVerbose) + + //user := config.Config.ActiveUser() + //fdl,_ := user.CacheFilesDirectoriesList("/tmp") + //fmt.Println(fdl) + + // webdav + webdav := &webdav.WebdavConfig{ + PanUserId: "", + Address: "0.0.0.0", + Port: 23077, + Prefix: "/", + Users: []webdav.WebdavUser{{ + Username: "admin", + Password: "admin", + Scope: "D:/smb/feny/pyprojects/comic", + }}, + } + webdav.StartServer() + return nil + }, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "param", + Usage: "参数", + }, + cli.BoolFlag{ + Name: "verbose", + Destination: &logger.IsVerbose, + EnvVar: config.EnvVerbose, + Usage: "显示调试信息", + }, + }, + }, } sort.Sort(cli.FlagsByName(app.Flags))