Compare commits

...
5 Commits
8 changed files with 135 additions and 33 deletions
+2 -4
View File
@@ -22,10 +22,8 @@ jobs:
- name: Checkout code
uses: actions/checkout@v2
# So GoReleaser can generate the changelog properly
- name: Unshallowify the repo clone
run: git fetch --prune --unshallow
with:
fetch-depth: 0
# 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
+1 -1
View File
@@ -53,7 +53,7 @@ release:
github:
owner: hacdias
name: webdav
draft: true
draft: false
prerelease: auto
dockers:
+2
View File
@@ -57,6 +57,8 @@ users:
- regex: false
allow: false
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.
+23 -8
View File
@@ -14,7 +14,7 @@ import (
"golang.org/x/net/webdav"
)
func parseRules(raw []interface{}) []*lib.Rule {
func parseRules(raw []interface{}, defaultModify bool) []*lib.Rule {
rules := []*lib.Rule{}
for _, v := range raw {
@@ -22,6 +22,7 @@ func parseRules(raw []interface{}) []*lib.Rule {
rule := &lib.Rule{
Regex: false,
Allow: false,
Modify: defaultModify,
Path: "",
}
@@ -33,6 +34,13 @@ func parseRules(raw []interface{}) []*lib.Rule {
rule.Allow = allow
}
if modify, ok := r["modify"].(bool); ok {
rule.Modify = modify
if modify {
rule.Allow = true
}
}
path, ok := r["path"].(string)
if !ok {
continue
@@ -110,12 +118,15 @@ func parseUsers(raw []interface{}, c *lib.Config) {
}
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{
Prefix: c.User.Handler.Prefix,
FileSystem: webdav.Dir(user.Scope),
Prefix: c.User.Handler.Prefix,
FileSystem: lib.WebDavDir{
Dir: webdav.Dir(user.Scope),
NoSniff: c.NoSniff,
},
LockSystem: webdav.NewMemLS(),
}
@@ -171,12 +182,16 @@ func readConfig(flags *pflag.FlagSet) *lib.Config {
Modify: getOptB(flags, "modify"),
Rules: []*lib.Rule{},
Handler: &webdav.Handler{
Prefix: getOpt(flags, "prefix"),
FileSystem: webdav.Dir(getOpt(flags, "scope")),
Prefix: getOpt(flags, "prefix"),
FileSystem: lib.WebDavDir{
Dir: webdav.Dir(getOpt(flags, "scope")),
NoSniff: getOptB(flags, "nosniff"),
},
LockSystem: webdav.NewMemLS(),
},
},
Auth: getOptB(flags, "auth"),
Auth: getOptB(flags, "auth"),
NoSniff: getOptB(flags, "nosniff"),
Cors: lib.CorsCfg{
Enabled: false,
Credentials: false,
@@ -186,7 +201,7 @@ func readConfig(flags *pflag.FlagSet) *lib.Config {
rawRules := v.Get("rules")
if rules, ok := rawRules.([]interface{}); ok {
cfg.User.Rules = parseRules(rules)
cfg.User.Rules = parseRules(rules, cfg.User.Modify)
}
rawUsers := v.Get("users")
+11 -3
View File
@@ -53,9 +53,17 @@ set WD_CERT.`,
cfg := readConfig(flags)
// Builds the address and a listener.
laddr := getOpt(flags, "address") + ":" + getOpt(flags, "port")
listener, err := net.Listen("tcp", laddr)
// Build address and listener
laddr := getOpt(flags, "address")
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 {
log.Fatal(err)
}
+83
View File
@@ -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
View File
@@ -11,6 +11,7 @@ import (
type Rule struct {
Regex bool
Allow bool
Modify bool
Path string
Regexp *regexp.Regexp
}
@@ -26,23 +27,24 @@ type User struct {
}
// 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
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 rule.Allow
return isAllowed
}
} else if strings.HasPrefix(url, rule.Path) {
return rule.Allow
return isAllowed
}
i--
}
return true
return noModification || u.Modify
}
+7 -13
View File
@@ -20,9 +20,10 @@ type CorsCfg struct {
// Config is the configuration of a WebDAV instance.
type Config struct {
*User
Auth bool
Cors CorsCfg
Users map[string]*User
Auth bool
NoSniff bool
Cors CorsCfg
Users map[string]*User
}
// 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.
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)
return
}
@@ -111,15 +114,6 @@ func (c *Config) ServeHTTP(w http.ResponseWriter, r *http.Request) {
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:
//
// GET, when applied to a collection, may return the contents of an