From 4ad8aa6a64f7bf6ffc32f746d10f627868f69ea3 Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Thu, 3 Aug 2017 08:18:26 +0100 Subject: [PATCH] Init --- cmd/webdav/main.go | 1 + webdav.go | 135 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+) create mode 100644 cmd/webdav/main.go create mode 100644 webdav.go diff --git a/cmd/webdav/main.go b/cmd/webdav/main.go new file mode 100644 index 0000000..06ab7d0 --- /dev/null +++ b/cmd/webdav/main.go @@ -0,0 +1 @@ +package main diff --git a/webdav.go b/webdav.go new file mode 100644 index 0000000..a31d832 --- /dev/null +++ b/webdav.go @@ -0,0 +1,135 @@ +package webdav + +import ( + "context" + "net/http" + "regexp" + "strings" + + "github.com/mholt/caddy/caddyhttp/httpserver" + + "golang.org/x/net/webdav" +) + +// Config is the configuration of a WebDAV instance. +type Config struct { + *User + BaseURL string + Users map[string]*User +} + +// 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 + + // Gets the correct user for this request. + username, ok := r.Context().Value(httpserver.RemoteUserCtxKey).(string) + if ok { + if user, ok := c.Users[username]; ok { + u = user + } + } + + // Remove the BaseURL from the url path. + r.URL.Path = strings.TrimPrefix(r.URL.Path, c.BaseURL) + + // Checks for user permissions relatively to this PATH. + if !u.Allowed(r.URL.Path) { + w.WriteHeader(http.StatusForbidden) + return + } + + if r.Method == "HEAD" { + 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 + // "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" { + info, err := u.Handler.FileSystem.Stat(context.TODO(), r.URL.Path) + if err == nil && info.IsDir() { + r.Method = "PROPFIND" + } + } + + // Runs the WebDAV. + u.Handler.ServeHTTP(w, r) +} + +// Rule is a dissalow/allow rule. +type Rule struct { + Regex bool + Allow bool + Path string + Regexp *regexp.Regexp +} + +// User contains the settings of each user. +type User struct { + 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) bool { + var rule *Rule + i := len(u.Rules) - 1 + + for i >= 0 { + rule = u.Rules[i] + + if rule.Regex { + if rule.Regexp.MatchString(url) { + return rule.Allow + } + } else if strings.HasPrefix(url, rule.Path) { + return rule.Allow + } + + i-- + } + + return true +} + +// 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) +}