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>
282 lines
5.5 KiB
Go
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
|
|
}
|