mirror of
https://github.com/hacdias/webdav.git
synced 2026-09-23 19:51:53 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
099479a894 | ||
|
|
8cd6d0a585 | ||
|
|
fca4e54839 | ||
|
|
da6dd253d5 |
@@ -22,10 +22,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
# So GoReleaser can generate the changelog properly
|
fetch-depth: 0
|
||||||
- name: Unshallowify the repo clone
|
|
||||||
run: git fetch --prune --unshallow
|
|
||||||
|
|
||||||
# https://github.community/t5/GitHub-Actions/How-to-get-just-the-tag-name/m-p/32167/highlight/true#M1027
|
# https://github.community/t5/GitHub-Actions/How-to-get-just-the-tag-name/m-p/32167/highlight/true#M1027
|
||||||
- name: Print Go version and environment
|
- name: Print Go version and environment
|
||||||
|
|||||||
@@ -57,6 +57,8 @@ users:
|
|||||||
- regex: false
|
- regex: false
|
||||||
allow: false
|
allow: false
|
||||||
path: /some/file
|
path: /some/file
|
||||||
|
- path: /public/access/
|
||||||
|
modify: true
|
||||||
```
|
```
|
||||||
|
|
||||||
There are more ways to customize how you run WebDAV through flags and environment variables. Please run `webdav --help` for more information on that.
|
There are more ways to customize how you run WebDAV through flags and environment variables. Please run `webdav --help` for more information on that.
|
||||||
|
|||||||
+23
-8
@@ -14,7 +14,7 @@ import (
|
|||||||
"golang.org/x/net/webdav"
|
"golang.org/x/net/webdav"
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseRules(raw []interface{}) []*lib.Rule {
|
func parseRules(raw []interface{}, defaultModify bool) []*lib.Rule {
|
||||||
rules := []*lib.Rule{}
|
rules := []*lib.Rule{}
|
||||||
|
|
||||||
for _, v := range raw {
|
for _, v := range raw {
|
||||||
@@ -22,6 +22,7 @@ func parseRules(raw []interface{}) []*lib.Rule {
|
|||||||
rule := &lib.Rule{
|
rule := &lib.Rule{
|
||||||
Regex: false,
|
Regex: false,
|
||||||
Allow: false,
|
Allow: false,
|
||||||
|
Modify: defaultModify,
|
||||||
Path: "",
|
Path: "",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,6 +34,13 @@ func parseRules(raw []interface{}) []*lib.Rule {
|
|||||||
rule.Allow = allow
|
rule.Allow = allow
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if modify, ok := r["modify"].(bool); ok {
|
||||||
|
rule.Modify = modify
|
||||||
|
if modify {
|
||||||
|
rule.Allow = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
path, ok := r["path"].(string)
|
path, ok := r["path"].(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
@@ -110,12 +118,15 @@ func parseUsers(raw []interface{}, c *lib.Config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if rules, ok := u["rules"].([]interface{}); ok {
|
if rules, ok := u["rules"].([]interface{}); ok {
|
||||||
user.Rules = parseRules(rules)
|
user.Rules = append(c.User.Rules, parseRules(rules, user.Modify)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
user.Handler = &webdav.Handler{
|
user.Handler = &webdav.Handler{
|
||||||
Prefix: c.User.Handler.Prefix,
|
Prefix: c.User.Handler.Prefix,
|
||||||
FileSystem: webdav.Dir(user.Scope),
|
FileSystem: lib.WebDavDir{
|
||||||
|
Dir: webdav.Dir(user.Scope),
|
||||||
|
NoSniff: c.NoSniff,
|
||||||
|
},
|
||||||
LockSystem: webdav.NewMemLS(),
|
LockSystem: webdav.NewMemLS(),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,12 +182,16 @@ func readConfig(flags *pflag.FlagSet) *lib.Config {
|
|||||||
Modify: getOptB(flags, "modify"),
|
Modify: getOptB(flags, "modify"),
|
||||||
Rules: []*lib.Rule{},
|
Rules: []*lib.Rule{},
|
||||||
Handler: &webdav.Handler{
|
Handler: &webdav.Handler{
|
||||||
Prefix: getOpt(flags, "prefix"),
|
Prefix: getOpt(flags, "prefix"),
|
||||||
FileSystem: webdav.Dir(getOpt(flags, "scope")),
|
FileSystem: lib.WebDavDir{
|
||||||
|
Dir: webdav.Dir(getOpt(flags, "scope")),
|
||||||
|
NoSniff: getOptB(flags, "nosniff"),
|
||||||
|
},
|
||||||
LockSystem: webdav.NewMemLS(),
|
LockSystem: webdav.NewMemLS(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Auth: getOptB(flags, "auth"),
|
Auth: getOptB(flags, "auth"),
|
||||||
|
NoSniff: getOptB(flags, "nosniff"),
|
||||||
Cors: lib.CorsCfg{
|
Cors: lib.CorsCfg{
|
||||||
Enabled: false,
|
Enabled: false,
|
||||||
Credentials: false,
|
Credentials: false,
|
||||||
@@ -186,7 +201,7 @@ func readConfig(flags *pflag.FlagSet) *lib.Config {
|
|||||||
|
|
||||||
rawRules := v.Get("rules")
|
rawRules := v.Get("rules")
|
||||||
if rules, ok := rawRules.([]interface{}); ok {
|
if rules, ok := rawRules.([]interface{}); ok {
|
||||||
cfg.User.Rules = parseRules(rules)
|
cfg.User.Rules = parseRules(rules, cfg.User.Modify)
|
||||||
}
|
}
|
||||||
|
|
||||||
rawUsers := v.Get("users")
|
rawUsers := v.Get("users")
|
||||||
|
|||||||
+11
-3
@@ -53,9 +53,17 @@ set WD_CERT.`,
|
|||||||
|
|
||||||
cfg := readConfig(flags)
|
cfg := readConfig(flags)
|
||||||
|
|
||||||
// Builds the address and a listener.
|
// Build address and listener
|
||||||
laddr := getOpt(flags, "address") + ":" + getOpt(flags, "port")
|
laddr := getOpt(flags, "address")
|
||||||
listener, err := net.Listen("tcp", laddr)
|
var lnet string
|
||||||
|
if strings.HasPrefix(laddr, "unix:") {
|
||||||
|
laddr = laddr[5:]
|
||||||
|
lnet = "unix"
|
||||||
|
} else {
|
||||||
|
laddr = laddr + ":" + getOpt(flags, "port")
|
||||||
|
lnet = "tcp"
|
||||||
|
}
|
||||||
|
listener, err := net.Listen(lnet, laddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
+83
@@ -0,0 +1,83 @@
|
|||||||
|
package lib
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
+6
-4
@@ -11,6 +11,7 @@ import (
|
|||||||
type Rule struct {
|
type Rule struct {
|
||||||
Regex bool
|
Regex bool
|
||||||
Allow bool
|
Allow bool
|
||||||
|
Modify bool
|
||||||
Path string
|
Path string
|
||||||
Regexp *regexp.Regexp
|
Regexp *regexp.Regexp
|
||||||
}
|
}
|
||||||
@@ -26,23 +27,24 @@ type User struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Allowed checks if the user has permission to access a directory/file
|
// Allowed checks if the user has permission to access a directory/file
|
||||||
func (u User) Allowed(url string) bool {
|
func (u User) Allowed(url string, noModification bool) bool {
|
||||||
var rule *Rule
|
var rule *Rule
|
||||||
i := len(u.Rules) - 1
|
i := len(u.Rules) - 1
|
||||||
|
|
||||||
for i >= 0 {
|
for i >= 0 {
|
||||||
rule = u.Rules[i]
|
rule = u.Rules[i]
|
||||||
|
|
||||||
|
isAllowed := rule.Allow && (noModification || rule.Modify)
|
||||||
if rule.Regex {
|
if rule.Regex {
|
||||||
if rule.Regexp.MatchString(url) {
|
if rule.Regexp.MatchString(url) {
|
||||||
return rule.Allow
|
return isAllowed
|
||||||
}
|
}
|
||||||
} else if strings.HasPrefix(url, rule.Path) {
|
} else if strings.HasPrefix(url, rule.Path) {
|
||||||
return rule.Allow
|
return isAllowed
|
||||||
}
|
}
|
||||||
|
|
||||||
i--
|
i--
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return noModification || u.Modify
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-13
@@ -20,9 +20,10 @@ type CorsCfg struct {
|
|||||||
// Config is the configuration of a WebDAV instance.
|
// Config is the configuration of a WebDAV instance.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
*User
|
*User
|
||||||
Auth bool
|
Auth bool
|
||||||
Cors CorsCfg
|
NoSniff bool
|
||||||
Users map[string]*User
|
Cors CorsCfg
|
||||||
|
Users map[string]*User
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServeHTTP determines if the request is for this plugin, and if all prerequisites are met.
|
// ServeHTTP determines if the request is for this plugin, and if all prerequisites are met.
|
||||||
@@ -102,7 +103,9 @@ func (c *Config) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Checks for user permissions relatively to this PATH.
|
// Checks for user permissions relatively to this PATH.
|
||||||
if !u.Allowed(r.URL.Path) {
|
noModification := r.Method == "GET" || r.Method == "HEAD" ||
|
||||||
|
r.Method == "OPTIONS" || r.Method == "PROPFIND"
|
||||||
|
if !u.Allowed(r.URL.Path, noModification) {
|
||||||
w.WriteHeader(http.StatusForbidden)
|
w.WriteHeader(http.StatusForbidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -111,15 +114,6 @@ func (c *Config) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
w = newResponseWriterNoBody(w)
|
w = newResponseWriterNoBody(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this request modified the files and the user doesn't have permission
|
|
||||||
// to do so, return forbidden.
|
|
||||||
if (r.Method == "PUT" || r.Method == "POST" || r.Method == "MKCOL" ||
|
|
||||||
r.Method == "DELETE" || r.Method == "COPY" || r.Method == "MOVE") &&
|
|
||||||
!u.Modify {
|
|
||||||
w.WriteHeader(http.StatusForbidden)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Excerpt from RFC4918, section 9.4:
|
// Excerpt from RFC4918, section 9.4:
|
||||||
//
|
//
|
||||||
// GET, when applied to a collection, may return the contents of an
|
// GET, when applied to a collection, may return the contents of an
|
||||||
|
|||||||
Reference in New Issue
Block a user