package webdav import ( "context" "net/http" "regexp" "strings" "golang.org/x/net/webdav" ) // Config is the configuration of a WebDAV instance. type Config struct { *User 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.BasicAuth() if ok { if user, ok := c.Users[username]; ok { u = user } } // 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" if r.Header.Get("Depth") == "" { r.Header.Add("Depth", "1") } } } // 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) }