diff --git a/lib/handler.go b/lib/handler.go index bc572cc..e22b893 100644 --- a/lib/handler.go +++ b/lib/handler.go @@ -2,7 +2,6 @@ package lib import ( "net/http" - "net/url" "os" "strings" @@ -116,21 +115,16 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { zap.L().Info("user authorized", zap.String("username", username), zap.String("remote_address", remoteAddr)) } - // Cleanup destination header if it's present by stripping out the prefix - // and only keeping the path. - if destination := r.Header.Get("Destination"); destination != "" { - u, err := url.Parse(destination) - if err == nil { - destination = strings.TrimPrefix(u.Path, user.Prefix) - if !strings.HasPrefix(destination, "/") { - destination = "/" + destination - } - r.Header.Set("Destination", destination) - } + // Convert the HTTP request into an internal request type + req, err := newRequest(r, h.user.Prefix) + if err != nil { + zap.L().Info("invalid request path or destination", zap.Error(err)) + http.Error(w, "Invalid request path or destination", http.StatusBadRequest) + return } // Checks for user permissions relatively to this PATH. - allowed := user.Allowed(r, func(filename string) bool { + allowed := user.Allowed(req, func(filename string) bool { _, err := user.FileSystem.Stat(r.Context(), filename) return !os.IsNotExist(err) }) diff --git a/lib/permissions.go b/lib/permissions.go index aa70a7f..2870a35 100644 --- a/lib/permissions.go +++ b/lib/permissions.go @@ -3,7 +3,6 @@ package lib import ( "errors" "fmt" - "net/http" "path/filepath" "regexp" "strings" @@ -51,12 +50,12 @@ type UserPermissions struct { } // Allowed checks if the user has permission to access a directory/file -func (p UserPermissions) Allowed(r *http.Request, fileExists func(string) bool) bool { +func (p UserPermissions) Allowed(r *request, fileExists func(string) bool) bool { // For COPY and MOVE requests, we first check the permissions for the destination // path. As soon as a rule matches and does not allow the operation at the destination, // we fail immediately. If no rule matches, we check the global permissions. - if r.Method == "COPY" || r.Method == "MOVE" { - dst := r.Header.Get("Destination") + if r.method == "COPY" || r.method == "MOVE" { + dst := r.destination for i := len(p.Rules) - 1; i >= 0; i-- { if p.Rules[i].Matches(dst) { @@ -77,7 +76,7 @@ func (p UserPermissions) Allowed(r *http.Request, fileExists func(string) bool) // Go through rules beginning from the last one, and check the permissions at // the source. The first matched rule returns. for i := len(p.Rules) - 1; i >= 0; i-- { - if p.Rules[i].Matches(r.URL.Path) { + if p.Rules[i].Matches(r.path) { return p.Rules[i].Permissions.Allowed(r, fileExists) } } @@ -142,8 +141,8 @@ func (p *Permissions) UnmarshalText(data []byte) error { // Allowed returns whether this permission set has permissions to execute this // request in the source directory. This applies to all requests with all methods. -func (p Permissions) Allowed(r *http.Request, fileExists func(string) bool) bool { - switch r.Method { +func (p Permissions) Allowed(r *request, fileExists func(string) bool) bool { + switch r.method { case "GET", "HEAD", "OPTIONS", "POST", "PROPFIND": // Note: POST backend implementation just returns the same thing as GET. return p.Read @@ -152,7 +151,7 @@ func (p Permissions) Allowed(r *http.Request, fileExists func(string) bool) bool case "PROPPATCH": return p.Update case "PUT": - if fileExists(r.URL.Path) { + if fileExists(r.path) { return p.Update } else { return p.Create @@ -172,10 +171,10 @@ func (p Permissions) Allowed(r *http.Request, fileExists func(string) bool) bool // AllowedDestination returns whether this permissions set has permissions to execute this // request in the destination directory. This only applies for COPY and MOVE requests. -func (p Permissions) AllowedDestination(r *http.Request, fileExists func(string) bool) bool { - switch r.Method { +func (p Permissions) AllowedDestination(r *request, fileExists func(string) bool) bool { + switch r.method { case "COPY", "MOVE": - if fileExists(r.Header.Get("Destination")) { + if fileExists(r.destination) { return p.Update } else { return p.Create diff --git a/lib/request.go b/lib/request.go new file mode 100644 index 0000000..9468577 --- /dev/null +++ b/lib/request.go @@ -0,0 +1,57 @@ +package lib + +import ( + "errors" + "net/http" + "net/url" + "strings" +) + +type request struct { + method string + path string + destination string +} + +func newRequest(r *http.Request, prefix string) (*request, error) { + ctx := &request{ + method: r.Method, + } + + if destination := r.Header.Get("Destination"); destination != "" { + u, err := url.Parse(destination) + if err != nil { + return nil, errors.New("invalid destination header") + } + + if prefix != "" { + destination = strings.TrimPrefix(u.Path, prefix) + if len(destination) >= len(u.Path) { + return nil, errors.New("invalid url prefix") + } + } + + if !strings.HasPrefix(destination, "/") { + destination = "/" + destination + } + + ctx.destination = destination + } + + path := r.URL.Path + + if prefix != "" { + path = strings.TrimPrefix(r.URL.Path, prefix) + if len(path) >= len(r.URL.Path) { + return nil, errors.New("invalid url prefix") + } + } + + if !strings.HasPrefix(path, "/") { + path = "/" + path + } + + ctx.path = path + + return ctx, nil +}