120 lines
2.9 KiB
Go
120 lines
2.9 KiB
Go
package webdav
|
|
|
|
import (
|
|
"context"
|
|
"log"
|
|
"net/http"
|
|
)
|
|
|
|
// Config is the configuration of a WebDAV instance.
|
|
type Config struct {
|
|
*User
|
|
Auth bool
|
|
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
|
|
|
|
if c.Auth {
|
|
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
|
|
|
// Gets the correct user for this request.
|
|
username, password, ok := r.BasicAuth()
|
|
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) {
|
|
log.Println("Wrong Password for user", username)
|
|
http.Error(w, "Not authorized", 401)
|
|
return
|
|
}
|
|
|
|
u = user
|
|
} 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.
|
|
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)
|
|
}
|
|
|
|
// 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)
|
|
}
|