From 0133b7e73ed58ce79e919429d8bbf4fccfa3aaa6 Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Sat, 5 Jan 2019 09:41:09 +0000 Subject: [PATCH] feat: some more updates License: MIT Signed-off-by: Henrique Dias --- files/file.go | 33 ++++++++++++++++++---------- frontend | 2 +- http/http.go | 8 ++++--- http/public.go | 50 +++++++++++++++++++++++++++++++++++++++++++ http/raw.go | 15 ++++++++++--- http/resource.go | 8 ++++++- http/share.go | 36 ++++++++++++++----------------- http/utils.go | 6 ++++-- share/share.go | 10 ++++----- share/storage.go | 43 +++++++++++++++++++++++++++++++------ storage/bolt/share.go | 10 ++++----- 11 files changed, 162 insertions(+), 59 deletions(-) create mode 100644 http/public.go diff --git a/files/file.go b/files/file.go index aa8d1910..6dadead3 100644 --- a/files/file.go +++ b/files/file.go @@ -38,22 +38,31 @@ type FileInfo struct { Checksums map[string]string `json:"checksums,omitempty"` } +// FileOptions are the options when getting a file info. +type FileOptions struct { + Fs afero.Fs + Path string + Modify bool + Expand bool + Checker rules.Checker +} + // NewFileInfo creates a File object from a path and a given user. This File // object will be automatically filled depending on if it is a directory // or a file. If it's a video file, it will also detect any subtitles. -func NewFileInfo(fs afero.Fs, path string, modify bool, checker rules.Checker) (*FileInfo, error) { - if !checker.Check(path) { +func NewFileInfo(opts FileOptions) (*FileInfo, error) { + if !opts.Checker.Check(opts.Path) { return nil, os.ErrPermission } - info, err := fs.Stat(path) + info, err := opts.Fs.Stat(opts.Path) if err != nil { return nil, err } file := &FileInfo{ - Fs: fs, - Path: path, + Fs: opts.Fs, + Path: opts.Path, Name: info.Name(), ModTime: info.ModTime(), Mode: info.Mode(), @@ -62,13 +71,15 @@ func NewFileInfo(fs afero.Fs, path string, modify bool, checker rules.Checker) ( Extension: filepath.Ext(info.Name()), } - if file.IsDir { - return file, file.readListing(checker) - } + if opts.Expand { + if file.IsDir { + return file, file.readListing(opts.Checker) + } - err = file.detectType(modify) - if err != nil { - return nil, err + err = file.detectType(opts.Modify) + if err != nil { + return nil, err + } } return file, err diff --git a/frontend b/frontend index a7102977..a53f11bb 160000 --- a/frontend +++ b/frontend @@ -1 +1 @@ -Subproject commit a71029771582dbc85baa5a4eb984d800311b01c5 +Subproject commit a53f11bb786b409a1320866113276acbddbf97ac diff --git a/http/http.go b/http/http.go index cbdf079d..6384ff67 100644 --- a/http/http.go +++ b/http/http.go @@ -39,7 +39,7 @@ func NewHandler(storage *storage.Storage) (http.Handler, error) { api.PathPrefix("/resources").Handler(handle(resourcePostPutHandler, "/api/resources", storage)).Methods("PUT") api.PathPrefix("/resources").Handler(handle(resourcePatchHandler, "/api/resources", storage)).Methods("PATCH") - api.PathPrefix("/share").Handler(handle(shareGetHandler, "/api/share", storage)).Methods("GET") + api.PathPrefix("/share").Handler(handle(shareGetsHandler, "/api/share", storage)).Methods("GET") api.PathPrefix("/share").Handler(handle(sharePostHandler, "/api/share", storage)).Methods("POST") api.PathPrefix("/share").Handler(handle(shareDeleteHandler, "/api/share", storage)).Methods("DELETE") @@ -49,8 +49,10 @@ func NewHandler(storage *storage.Storage) (http.Handler, error) { api.PathPrefix("/raw").Handler(handle(rawHandler, "/api/raw", storage)).Methods("GET") api.PathPrefix("/command").Handler(handle(commandsHandler, "/api/command", storage)).Methods("GET") api.PathPrefix("/search").Handler(handle(searchHandler, "/api/search", storage)).Methods("GET") + + public := api.PathPrefix("/public").Subrouter() + public.PathPrefix("/dl").Handler(handle(publicDlHandler, "/api/public/dl/", storage)).Methods("GET") + public.PathPrefix("/share").Handler(handle(publicShareHandler, "/api/public/share/", storage)).Methods("GET") return r, nil } - - diff --git a/http/public.go b/http/public.go new file mode 100644 index 00000000..a3319bca --- /dev/null +++ b/http/public.go @@ -0,0 +1,50 @@ +package http + +import ( + "net/http" + + "github.com/filebrowser/filebrowser/files" +) + +var withHashFile = func(fn handleFunc) handleFunc { + return func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { + link, err := d.store.Share.GetByHash(r.URL.Path) + if err != nil { + return errToStatus(err), err + } + + user, err := d.store.Users.Get(link.UserID) + if err != nil { + return errToStatus(err), err + } + + d.user = user + + file, err := files.NewFileInfo(files.FileOptions{ + Fs: d.user.Fs, + Path: link.Path, + Modify: d.user.Perm.Modify, + Expand: false, + Checker: d, + }) + if err != nil { + return errToStatus(err), err + } + + d.raw = file + return fn(w, r, d) + } +} + +var publicShareHandler = withHashFile(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { + return renderJSON(w, r, d.raw) +}) + +var publicDlHandler = withHashFile(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { + file := d.raw.(*files.FileInfo) + if !file.IsDir { + return rawFileHandler(w, r, file) + } + + return rawDirHandler(w, r, d, file) +}) diff --git a/http/raw.go b/http/raw.go index 76bbb7f3..cbabda1b 100644 --- a/http/raw.go +++ b/http/raw.go @@ -2,6 +2,7 @@ package http import ( "errors" + "fmt" "net/http" "net/url" "path/filepath" @@ -60,7 +61,13 @@ var rawHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) return http.StatusAccepted, nil } - file, err := files.NewFileInfo(d.user.Fs, r.URL.Path, d.user.Perm.Modify, d) + file, err := files.NewFileInfo(files.FileOptions{ + Fs: d.user.Fs, + Path: r.URL.Path, + Modify: d.user.Perm.Modify, + Expand: false, + Checker: d, + }) if err != nil { return errToStatus(err), err } @@ -73,6 +80,9 @@ var rawHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) }) func addFile(ar archiver.Writer, d *data, path string) error { + // Checks are always done with paths with "/" as path separator. + path = strings.Replace(path, "\\", "/", -1) + fmt.Println(path) if !d.Check(path) { return nil } @@ -82,7 +92,6 @@ func addFile(ar archiver.Writer, d *data, path string) error { return err } - // open the file file, err := d.user.Fs.Open(path) if err != nil { return err @@ -92,7 +101,7 @@ func addFile(ar archiver.Writer, d *data, path string) error { err = ar.Write(archiver.File{ FileInfo: archiver.FileInfo{ FileInfo: info, - CustomName: path, + CustomName: strings.TrimPrefix(path, "/"), }, ReadCloser: file, }) diff --git a/http/resource.go b/http/resource.go index 9ebfffc3..afefc42e 100644 --- a/http/resource.go +++ b/http/resource.go @@ -16,7 +16,13 @@ import ( ) var resourceGetHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { - file, err := files.NewFileInfo(d.user.Fs, r.URL.Path, d.user.Perm.Modify, d) + file, err := files.NewFileInfo(files.FileOptions{ + Fs: d.user.Fs, + Path: r.URL.Path, + Modify: d.user.Perm.Modify, + Expand: true, + Checker: d, + }) if err != nil { return errToStatus(err), err } diff --git a/http/share.go b/http/share.go index 91ba5a27..ae018762 100644 --- a/http/share.go +++ b/http/share.go @@ -22,8 +22,8 @@ func withPermShare(fn handleFunc) handleFunc { }) } -var shareGetHandler = withPermShare(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { - s, err := d.store.Share.Gets(r.URL.Path) +var shareGetsHandler = withPermShare(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { + s, err := d.store.Share.Gets(r.URL.Path, d.user.ID) if err == errors.ErrNotExist { return renderJSON(w, r, []*share.Link{}) } @@ -32,13 +32,6 @@ var shareGetHandler = withPermShare(func(w http.ResponseWriter, r *http.Request, return http.StatusInternalServerError, err } - for i, link := range s { - if link.Expires && link.ExpireDate.Before(time.Now()) { - d.store.Share.Delete(link.Hash) - s = append(s[:i], s[i+1:]...) - } - } - return renderJSON(w, r, s) }) @@ -56,12 +49,12 @@ var shareDeleteHandler = withPermShare(func(w http.ResponseWriter, r *http.Reque var sharePostHandler = withPermShare(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { var s *share.Link - expire := r.URL.Query().Get("expires") + rawExpire := r.URL.Query().Get("expires") unit := r.URL.Query().Get("unit") - if expire == "" { + if rawExpire == "" { var err error - s, err = d.store.Share.GetPermanent(r.URL.Path) + s, err = d.store.Share.GetPermanent(r.URL.Path, d.user.ID) if err == nil { w.Write([]byte(d.settings.BaseURL + "/share/" + s.Hash)) return 0, nil @@ -76,14 +69,10 @@ var sharePostHandler = withPermShare(func(w http.ResponseWriter, r *http.Request str := base64.URLEncoding.EncodeToString(bytes) - s = &share.Link{ - Path: r.URL.Path, - Hash: str, - Expires: expire != "", - } + var expire int64 = 0 - if expire != "" { - num, err := strconv.Atoi(expire) + if rawExpire != "" { + num, err := strconv.Atoi(rawExpire) if err != nil { return http.StatusInternalServerError, err } @@ -100,7 +89,14 @@ var sharePostHandler = withPermShare(func(w http.ResponseWriter, r *http.Request add = time.Hour * time.Duration(num) } - s.ExpireDate = time.Now().Add(add) + expire = time.Now().Add(add).Unix() + } + + s = &share.Link{ + Path: r.URL.Path, + Hash: str, + Expire: expire, + UserID: d.user.ID, } if err := d.store.Share.Save(s); err != nil { diff --git a/http/utils.go b/http/utils.go index 1ea3c387..07f04c03 100644 --- a/http/utils.go +++ b/http/utils.go @@ -4,6 +4,8 @@ import ( "encoding/json" "net/http" "os" + + "github.com/filebrowser/filebrowser/errors" ) func renderJSON(w http.ResponseWriter, r *http.Request, data interface{}) (int, error) { @@ -27,9 +29,9 @@ func errToStatus(err error) int { return http.StatusOK case os.IsPermission(err): return http.StatusForbidden - case os.IsNotExist(err): + case os.IsNotExist(err), err == errors.ErrNotExist: return http.StatusNotFound - case os.IsExist(err): + case os.IsExist(err), err == errors.ErrExist: return http.StatusConflict default: return http.StatusInternalServerError diff --git a/share/share.go b/share/share.go index 7e4add87..fb4329f1 100644 --- a/share/share.go +++ b/share/share.go @@ -1,11 +1,9 @@ package share -import "time" - // Link is the information needed to build a shareable link. type Link struct { - Hash string `json:"hash" storm:"id,index"` - Path string `json:"path" storm:"index"` - Expires bool `json:"expires"` - ExpireDate time.Time `json:"expireDate"` + Hash string `json:"hash" storm:"id,index"` + Path string `json:"path" storm:"index"` + UserID uint `json:"userID"` + Expire int64 `json:"expire"` } diff --git a/share/storage.go b/share/storage.go index 50a97ccc..563d5673 100644 --- a/share/storage.go +++ b/share/storage.go @@ -1,10 +1,16 @@ package share +import ( + "time" + + "github.com/filebrowser/filebrowser/errors" +) + // StorageBackend is the interface to implement for a share storage. type StorageBackend interface { GetByHash(hash string) (*Link, error) - GetPermanent(path string) (*Link, error) - Gets(path string) ([]*Link, error) + GetPermanent(path string, id uint) (*Link, error) + Gets(path string, id uint) ([]*Link, error) Save(s *Link) error Delete(hash string) error } @@ -21,17 +27,40 @@ func NewStorage(back StorageBackend) *Storage { // GetByHash wraps a StorageBackend.GetByHash. func (s *Storage) GetByHash(hash string) (*Link, error) { - return s.back.GetByHash(hash) + link, err := s.back.GetByHash(hash) + if err != nil { + return nil, err + } + + if link.Expire != 0 && link.Expire <= time.Now().Unix() { + s.Delete(link.Hash) + return nil, errors.ErrNotExist + } + + return link, nil } // GetPermanent wraps a StorageBackend.GetPermanent -func (s *Storage) GetPermanent(path string) (*Link, error) { - return s.back.GetPermanent(path) +func (s *Storage) GetPermanent(path string, id uint) (*Link, error) { + return s.back.GetPermanent(path, id) } // Gets wraps a StorageBackend.Gets -func (s *Storage) Gets(path string) ([]*Link, error) { - return s.back.Gets(path) +func (s *Storage) Gets(path string, id uint) ([]*Link, error) { + links, err := s.back.Gets(path, id) + + if err != nil { + return nil, err + } + + for i, link := range links { + if link.Expire != 0 && link.Expire <= time.Now().Unix() { + s.Delete(link.Hash) + links = append(links[:i], links[i+1:]...) + } + } + + return links, nil } // Save wraps a StorageBackend.Save diff --git a/storage/bolt/share.go b/storage/bolt/share.go index 70cf7e22..f3196034 100644 --- a/storage/bolt/share.go +++ b/storage/bolt/share.go @@ -21,19 +21,19 @@ func (s shareBackend) GetByHash(hash string) (*share.Link, error) { return &v, err } -func (s shareBackend) GetPermanent(path string) (*share.Link, error) { +func (s shareBackend) GetPermanent(path string, id uint) (*share.Link, error) { var v share.Link - err := s.db.Select(q.Eq("Path", path), q.Eq("Expires", false)).First(&v) + err := s.db.Select(q.Eq("Path", path), q.Eq("Expire", 0), q.Eq("UserID", id)).First(&v) if err == storm.ErrNotFound { return nil, errors.ErrNotExist } - + return &v, err } -func (s shareBackend) Gets(hash string) ([]*share.Link, error) { +func (s shareBackend) Gets(path string, id uint) ([]*share.Link, error) { var v []*share.Link - err := s.db.Find("Path", hash, &v) + err := s.db.Select(q.Eq("Path", path), q.Eq("UserID", id)).Find(&v) if err == storm.ErrNotFound { return v, errors.ErrNotExist }