filebrowser/http/resource.go
Henrique Dias 42227d9edd feat: many updates (see PR)
feat: add main command

feat: add todos

feat: add signup api

feat: do not repeat code

fix: user return

feat: work out static box

fix: setup static handlers

feat: add share types

License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>

feat: start static

License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>

feat: bring back more features

License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>

add

feat: readd more files

License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>

feat: add dockerignore

License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>

feat: gitignore

License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>

feat: readd submodule

License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>
2018-12-28 23:40:11 +00:00

282 lines
5.5 KiB
Go

package http
import (
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
"strings"
"github.com/filebrowser/filebrowser/types"
)
const apiResourcePrefix = "/api/resources"
func httpFsErr(err error) int {
switch {
case err == nil:
return http.StatusOK
case os.IsPermission(err):
return http.StatusForbidden
case os.IsNotExist(err):
return http.StatusNotFound
case os.IsExist(err):
return http.StatusConflict
default:
return http.StatusInternalServerError
}
}
func (e *Env) getResourceData(w http.ResponseWriter, r *http.Request, prefix string) (string, *types.User, bool) {
user, ok := e.getUser(w, r)
if !ok {
return "", nil, ok
}
path := strings.TrimPrefix(r.URL.Path, prefix)
path = strings.TrimSuffix(path, "/")
if path == "" {
path = "/"
}
if !user.IsAllowed(path) {
httpErr(w, http.StatusForbidden, nil)
return "", nil, false
}
return path, user, true
}
func (e *Env) resourceGetHandler(w http.ResponseWriter, r *http.Request) {
path, user, ok := e.getResourceData(w, r, apiResourcePrefix)
if !ok {
return
}
file, err := types.NewFileInfo(user, path)
if err != nil {
httpErr(w, httpFsErr(err), err)
return
}
if file.IsDir {
scope := "/"
if sort, order, err := handleSortOrder(w, r, scope); err == nil {
file.Listing.Sort = sort
file.Listing.Order = order
} else {
httpErr(w, http.StatusBadRequest, err)
return
}
file.Listing.ApplySort()
renderJSON(w, file)
return
}
if file.Type == "video" {
file.DetectSubtitles()
}
if !user.Perm.Modify && file.Type == "text" {
file.Type = "textImmutable"
}
if checksum := r.URL.Query().Get("checksum"); checksum != "" {
err = file.Checksum(checksum)
if err == types.ErrInvalidOption {
httpErr(w, http.StatusBadRequest, nil)
return
} else if err != nil {
httpErr(w, http.StatusInternalServerError, err)
return
}
// do not waste bandwidth if we just want the checksum
file.Content = ""
}
renderJSON(w, file)
}
func (e *Env) resourceDeleteHandler(w http.ResponseWriter, r *http.Request) {
path, user, ok := e.getResourceData(w, r, apiResourcePrefix)
if !ok {
return
}
if path == "/" || !user.Perm.Delete {
httpErr(w, http.StatusForbidden, nil)
return
}
err := e.Runner.Run(func() error {
return user.Fs.RemoveAll(path)
}, "delete", path, "", user)
if err != nil {
httpErr(w, httpFsErr(err), err)
return
}
w.WriteHeader(http.StatusOK)
}
func (e *Env) resourcePostPutHandler(w http.ResponseWriter, r *http.Request) {
path, user, ok := e.getResourceData(w, r, apiResourcePrefix)
if !ok {
return
}
if !user.Perm.Create && r.Method == http.MethodPost {
httpErr(w, http.StatusForbidden, nil)
return
}
if !user.Perm.Modify && r.Method == http.MethodPut {
httpErr(w, http.StatusForbidden, nil)
return
}
defer func() {
io.Copy(ioutil.Discard, r.Body)
}()
// For directories, only allow POST for creation.
if strings.HasSuffix(r.URL.Path, "/") {
if r.Method == http.MethodPut {
httpErr(w, http.StatusMethodNotAllowed, nil)
} else {
err := user.Fs.MkdirAll(path, 0775)
httpErr(w, httpFsErr(err), err)
}
return
}
if r.Method == http.MethodPost && r.URL.Query().Get("override") != "true" {
if _, err := user.Fs.Stat(path); err == nil {
httpErr(w, http.StatusConflict, nil)
return
}
}
err := e.Runner.Run(func() error {
file, err := user.Fs.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0775)
if err != nil {
return err
}
defer file.Close()
_, err = io.Copy(file, r.Body)
if err != nil {
return err
}
// Gets the info about the file.
info, err := file.Stat()
if err != nil {
return err
}
etag := fmt.Sprintf(`"%x%x"`, info.ModTime().UnixNano(), info.Size())
w.Header().Set("ETag", etag)
return nil
}, "upload", path, "", user)
if err != nil {
httpErr(w, httpFsErr(err), err)
return
}
httpErr(w, http.StatusOK, nil)
}
func (e *Env) resourcePatchHandler(w http.ResponseWriter, r *http.Request) {
src, user, ok := e.getResourceData(w, r, apiResourcePrefix)
if !ok {
return
}
dst := r.URL.Query().Get("destination")
action := r.URL.Query().Get("action")
dst, err := url.QueryUnescape(dst)
if err != nil {
httpErr(w, httpFsErr(err), err)
return
}
if dst == "/" || src == "/" {
httpErr(w, http.StatusForbidden, nil)
return
}
switch action {
case "copy":
if !user.Perm.Create {
httpErr(w, http.StatusForbidden, nil)
return
}
case "rename":
default:
action = "rename"
if !user.Perm.Rename {
httpErr(w, http.StatusForbidden, nil)
return
}
}
err = e.Runner.Run(func() error {
if action == "copy" {
// TODO: err = user.FileSystem.Copy(src, dst)
return nil
}
return user.Fs.Rename(src, dst)
}, "action", src, dst, user)
httpErr(w, httpFsErr(err), err)
}
func handleSortOrder(w http.ResponseWriter, r *http.Request, scope string) (sort string, order string, err error) {
sort = r.URL.Query().Get("sort")
order = r.URL.Query().Get("order")
switch sort {
case "":
sort = "name"
if sortCookie, sortErr := r.Cookie("sort"); sortErr == nil {
sort = sortCookie.Value
}
case "name", "size":
http.SetCookie(w, &http.Cookie{
Name: "sort",
Value: sort,
MaxAge: 31536000,
Path: scope,
Secure: r.TLS != nil,
})
}
switch order {
case "":
order = "asc"
if orderCookie, orderErr := r.Cookie("order"); orderErr == nil {
order = orderCookie.Value
}
case "asc", "desc":
http.SetCookie(w, &http.Cookie{
Name: "order",
Value: order,
MaxAge: 31536000,
Path: scope,
Secure: r.TLS != nil,
})
}
return
}