diff --git a/assets/index.html b/assets/index.html
index a0ad846d..c75d0c76 100644
--- a/assets/index.html
+++ b/assets/index.html
@@ -22,7 +22,7 @@
- <% for (var chunk of webpack.compilation.chunks) {
+ <% for (var chunk of webpack.chunks) {
for (var file of chunk.files) {
if (file.match(/\.(js|css)$/)) { %>
<% }}} %>
diff --git a/bolt/config.go b/bolt/config.go
new file mode 100644
index 00000000..6dcd0378
--- /dev/null
+++ b/bolt/config.go
@@ -0,0 +1,17 @@
+package bolt
+
+import (
+ "github.com/asdine/storm"
+)
+
+type ConfigStore struct {
+ DB *storm.DB
+}
+
+func (c ConfigStore) Get(name string, to interface{}) error {
+ return c.DB.Get("config", name, to)
+}
+
+func (c ConfigStore) Save(name string, from interface{}) error {
+ return c.DB.Set("config", name, from)
+}
diff --git a/bolt/share.go b/bolt/share.go
new file mode 100644
index 00000000..d66c08bb
--- /dev/null
+++ b/bolt/share.go
@@ -0,0 +1,36 @@
+package bolt
+
+import (
+ "github.com/asdine/storm"
+ fm "github.com/hacdias/filemanager"
+)
+
+type ShareStore struct {
+ DB *storm.DB
+}
+
+func (s ShareStore) Get(hash string) (*fm.ShareLink, error) {
+ var v *fm.ShareLink
+ err := s.DB.One("Hash", hash, &v)
+ return v, err
+}
+
+func (s ShareStore) GetByPath(hash string) ([]*fm.ShareLink, error) {
+ var v []*fm.ShareLink
+ err := s.DB.Find("Path", hash, &v)
+ return v, err
+}
+
+func (s ShareStore) Gets(hash string) ([]*fm.ShareLink, error) {
+ var v []*fm.ShareLink
+ err := s.DB.All(&v)
+ return v, err
+}
+
+func (s ShareStore) Save(l *fm.ShareLink) error {
+ return s.DB.Save(l)
+}
+
+func (s ShareStore) Delete(hash string) error {
+ return s.DB.DeleteStruct(&fm.ShareLink{Hash: hash})
+}
diff --git a/bolt/users.go b/bolt/users.go
new file mode 100644
index 00000000..880abfa1
--- /dev/null
+++ b/bolt/users.go
@@ -0,0 +1,55 @@
+package bolt
+
+import (
+ "reflect"
+
+ "github.com/asdine/storm"
+ fm "github.com/hacdias/filemanager"
+)
+
+type UsersStore struct {
+ DB *storm.DB
+}
+
+func (u UsersStore) Get(id int) (*fm.User, error) {
+ var us *fm.User
+ err := u.DB.One("ID", id, us)
+ if err == storm.ErrNotFound {
+ return nil, fm.ErrUserNotExist
+ }
+
+ if err != nil {
+ return nil, err
+ }
+
+ return &fm.User{}, nil
+}
+
+func (u UsersStore) Gets() ([]*fm.User, error) {
+ var us []*fm.User
+ err := u.DB.All(us)
+ return us, err
+}
+
+func (u UsersStore) Update(us *fm.User, fields ...string) error {
+ if len(fields) == 0 {
+ return u.Save(us)
+ }
+
+ for _, field := range fields {
+ val := reflect.ValueOf(us).Elem().FieldByName(field).Interface()
+ if err := u.DB.UpdateField(us, field, val); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (u UsersStore) Save(us *fm.User) error {
+ return u.DB.Save(us)
+}
+
+func (u UsersStore) Delete(id int) error {
+ return u.DB.DeleteStruct(&fm.User{ID: id})
+}
diff --git a/file.go b/file.go
index 8beec058..367cec66 100644
--- a/file.go
+++ b/file.go
@@ -24,12 +24,12 @@ import (
)
var (
- errInvalidOption = errors.New("Invalid option")
+ ErrInvalidOption = errors.New("Invalid option")
)
// File contains the information about a particular file or directory.
type File struct {
- // Indicates the Kind of view on the front-end (listing, editor or preview).
+ // Indicates the Kind of view on the front-end (Listing, editor or preview).
Kind string `json:"kind"`
// The name of the file.
Name string `json:"name"`
@@ -54,19 +54,19 @@ type File struct {
// Stores the content of a text file.
Content string `json:"content,omitempty"`
- *listing `json:",omitempty"`
+ *Listing `json:",omitempty"`
Metadata string `json:"metadata,omitempty"`
Language string `json:"language,omitempty"`
}
-// A listing is the context used to fill out a template.
-type listing struct {
+// A Listing is the context used to fill out a template.
+type Listing struct {
// The items (files and folders) in the path.
Items []*File `json:"items"`
- // The number of directories in the listing.
+ // The number of directories in the Listing.
NumDirs int `json:"numDirs"`
- // The number of files (items that aren't directories) in the listing.
+ // The number of files (items that aren't directories) in the Listing.
NumFiles int `json:"numFiles"`
// Which sorting order is used.
Sort string `json:"sort"`
@@ -166,7 +166,7 @@ func (i *File) GetListing(u *User, r *http.Request) error {
fileinfos = append(fileinfos, i)
}
- i.listing = &listing{
+ i.Listing = &Listing{
Items: fileinfos,
NumDirs: dirCount,
NumFiles: fileCount,
@@ -304,7 +304,7 @@ func (i File) Checksum(algo string) (string, error) {
case "sha512":
h = sha512.New()
default:
- return "", errInvalidOption
+ return "", ErrInvalidOption
}
_, err = io.Copy(h, file)
@@ -321,7 +321,7 @@ func (i File) CanBeEdited() bool {
}
// ApplySort applies the sort order using .Order and .Sort
-func (l listing) ApplySort() {
+func (l Listing) ApplySort() {
// Check '.Order' to know how to sort
if l.Order == "desc" {
switch l.Sort {
@@ -350,10 +350,10 @@ func (l listing) ApplySort() {
}
}
-// Implement sorting for listing
-type byName listing
-type bySize listing
-type byModified listing
+// Implement sorting for Listing
+type byName Listing
+type bySize Listing
+type byModified Listing
// By Name
func (l byName) Len() int {
diff --git a/filemanager.go b/filemanager.go
index b2489745..928a6922 100644
--- a/filemanager.go
+++ b/filemanager.go
@@ -61,6 +61,7 @@ import (
"os/exec"
"reflect"
"strings"
+ "time"
rice "github.com/GeertJohan/go.rice"
"github.com/asdine/storm"
@@ -71,17 +72,14 @@ import (
// FileManager is a file manager instance. It should be creating using the
// 'New' function and not directly.
type FileManager struct {
- // The BoltDB database for this instance.
- db *storm.DB
+ // Job cron.
+ Cron *cron.Cron
// The key used to sign the JWT tokens.
- key []byte
+ Key []byte
// The static assets.
- assets *rice.Box
-
- // Job cron.
- cron *cron.Cron
+ Assets *rice.Box
// PrefixURL is a part of the URL that is already trimmed from the request URL before it
// arrives to our handlers. It may be useful when using File Manager as a middleware
@@ -103,66 +101,42 @@ type FileManager struct {
// The Default User needed to build the New User page.
DefaultUser *User
- // Users is a map with the different configurations for each user.
- Users map[string]*User
-
// A map of events to a slice of commands.
Commands map[string][]string
Store *Store
}
-type Store struct {
- Users *UsersStore
-}
-
// Command is a command function.
type Command func(r *http.Request, m *FileManager, u *User) error
-/*
-
-// New creates a new File Manager instance. If 'database' file already
-// exists, it will load the users from there. Otherwise, a new user
-// will be created using the 'base' variable. The 'base' User should
-// not have the Password field hashed.
-func New(database string, base User) (*FileManager, error) {
+func (m *FileManager) Load() error {
// Creates a new File Manager instance with the Users
// map and Assets box.
- m := &FileManager{
- Users: map[string]*User{},
- cron: cron.New(),
- assets: rice.MustFindBox("./assets/dist"),
- }
-
- // Tries to open a database on the location provided. This
- // function will automatically create a new one if it doesn't
- // exist.
- db, err := storm.Open(database)
- if err != nil {
- return nil, err
- }
+ m.Assets = rice.MustFindBox("./assets/dist")
+ m.Cron = cron.New()
// Tries to get the encryption key from the database.
// If it doesn't exist, create a new one of 256 bits.
- err = db.Get("config", "key", &m.key)
- if err != nil && err == storm.ErrNotFound {
+ err := m.Store.Config.Get("key", &m.Key)
+ if err != nil && err == ErrNotExist {
var bytes []byte
- bytes, err = generateRandomBytes(64)
+ bytes, err = GenerateRandomBytes(64)
if err != nil {
- return nil, err
+ return err
}
- m.key = bytes
- err = db.Set("config", "key", m.key)
+ m.Key = bytes
+ err = m.Store.Config.Save("key", m.Key)
}
if err != nil {
- return nil, err
+ return err
}
// Tries to get the event commands from the database.
// If they don't exist, initialize them.
- err = db.Get("config", "commands", &m.Commands)
+ err = m.Store.Config.Get("commands", &m.Commands)
if err != nil && err == storm.ErrNotFound {
m.Commands = map[string][]string{
"before_save": {},
@@ -170,35 +144,29 @@ func New(database string, base User) (*FileManager, error) {
"before_publish": {},
"after_publish": {},
}
- err = db.Set("config", "commands", m.Commands)
+ err = m.Store.Config.Save("commands", m.Commands)
}
if err != nil {
- return nil, err
+ return err
}
- // Tries to fetch the users from the database and if there are
- // any, add them to the current File Manager instance.
- var users []User
- err = db.All(&users)
+ // Tries to fetch the users from the database.
+ users, err := m.Store.Users.Gets()
if err != nil {
- return nil, err
- }
-
- for i := range users {
- m.Users[users[i].Username] = &users[i]
+ return err
}
// If there are no users in the database, it creates a new one
// based on 'base' User that must be provided by the function caller.
if len(users) == 0 {
- u := base
+ u := *m.DefaultUser
u.Username = "admin"
// Hashes the password.
- u.Password, err = hashPassword("admin")
+ u.Password, err = HashPassword("admin")
if err != nil {
- return nil, err
+ return err
}
// The first user must be an administrator.
@@ -209,26 +177,19 @@ func New(database string, base User) (*FileManager, error) {
u.AllowPublish = true
// Saves the user to the database.
- if err := db.Save(&u); err != nil {
- return nil, err
+ if err := m.Store.Users.Save(&u); err != nil {
+ return err
}
-
- m.Users[u.Username] = &u
}
- // Attaches db to this File Manager instance.
- m.db = db
+ m.DefaultUser.Username = ""
+ m.DefaultUser.Password = ""
- // Create the default user, making a copy of the base.
- base.Username = ""
- base.Password = ""
- m.DefaultUser = &base
+ m.Cron.AddFunc("@hourly", m.ShareCleaner)
+ m.Cron.Start()
- m.cron.AddFunc("@hourly", m.shareCleaner)
- m.cron.Start()
-
- return m, nil
-} */
+ return nil
+}
// RootURL returns the actual URL where
// File Manager interface can be accessed.
@@ -291,24 +252,19 @@ func (m *FileManager) Attach(s StaticGen) error {
m.StaticGen = s
- // TODO: Save...
- /* err := m.db.Get("staticgen", "hugo", h)
- if err != nil && err == storm.ErrNotFound {
- err = m.db.Set("staticgen", "hugo", *h)
+ err = m.Store.Config.Get("staticgen_"+s.Name(), s)
+ if err == ErrNotExist {
+ return m.Store.Config.Save("staticgen_"+s.Name(), s)
}
- */
- return nil
+
+ return err
}
-/*
-
-// shareCleaner removes sharing links that are no longer active.
+// ShareCleaner removes sharing links that are no longer active.
// This function is set to run periodically.
-func (m FileManager) shareCleaner() {
- var links []shareLink
-
+func (m FileManager) ShareCleaner() {
// Get all links.
- err := m.db.All(&links)
+ links, err := m.Store.Share.Gets()
if err != nil {
log.Print(err)
return
@@ -317,13 +273,13 @@ func (m FileManager) shareCleaner() {
// Find the expired ones.
for i := range links {
if links[i].Expires && links[i].ExpireDate.Before(time.Now()) {
- err = m.db.DeleteStruct(&links[i])
+ err = m.Store.Share.Delete(links[i].Hash)
if err != nil {
log.Print(err)
}
}
}
-} */
+}
// Runner runs the commands for a certain event type.
func (m FileManager) Runner(event string, path string) error {
diff --git a/http/auth.go b/http/auth.go
index c5888e6e..ffade7ae 100644
--- a/http/auth.go
+++ b/http/auth.go
@@ -1,14 +1,11 @@
package http
import (
- "crypto/rand"
"encoding/json"
"net/http"
"strings"
"time"
- "golang.org/x/crypto/bcrypt"
-
jwt "github.com/dgrijalva/jwt-go"
"github.com/dgrijalva/jwt-go/request"
fm "github.com/hacdias/filemanager"
@@ -33,13 +30,13 @@ func authHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, er
}
// Checks if the user exists.
- u, ok := c.Users[cred.Username]
- if !ok {
+ u, err := c.Store.Users.Get(cred.ID)
+ if err != nil {
return http.StatusForbidden, nil
}
// Checks if the password is correct.
- if !checkPasswordHash(cred.Password, u.Password) {
+ if !fm.CheckPasswordHash(cred.Password, u.Password) {
return http.StatusForbidden, nil
}
@@ -86,7 +83,7 @@ func printToken(c *fm.Context, w http.ResponseWriter) (int, error) {
// Creates the token and signs it.
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
- signed, err := token.SignedString(c.key)
+ signed, err := token.SignedString(c.Key)
if err != nil {
return http.StatusInternalServerError, err
@@ -127,7 +124,7 @@ func validateAuth(c *fm.Context, r *http.Request) (bool, *fm.User) {
}
keyFunc := func(token *jwt.Token) (interface{}, error) {
- return c.key, nil
+ return c.Key, nil
}
var claims claims
token, err := request.ParseFromRequestWithClaims(r,
@@ -140,38 +137,11 @@ func validateAuth(c *fm.Context, r *http.Request) (bool, *fm.User) {
return false, nil
}
- u, ok := c.Users[claims.User.Username]
- if !ok {
+ u, err := c.Store.Users.Get(claims.User.ID)
+ if err != nil {
return false, nil
}
c.User = u
return true, u
}
-
-// hashPassword generates an hash from a password using bcrypt.
-func hashPassword(password string) (string, error) {
- bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
- return string(bytes), err
-}
-
-// checkPasswordHash compares a password with an hash to check if they match.
-func checkPasswordHash(password, hash string) bool {
- err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
- return err == nil
-}
-
-// generateRandomBytes returns securely generated random bytes.
-// It will return an error if the system's secure random
-// number generator fails to function correctly, in which
-// case the caller should not continue.
-func generateRandomBytes(n int) ([]byte, error) {
- b := make([]byte, n)
- _, err := rand.Read(b)
- // Note that err == nil only if we read len(b) bytes.
- if err != nil {
- return nil, err
- }
-
- return b, nil
-}
diff --git a/http/auth_test.go b/http/auth_test.go.txt
similarity index 97%
rename from http/auth_test.go
rename to http/auth_test.go.txt
index ee6ce8a5..fd8313ba 100644
--- a/http/auth_test.go
+++ b/http/auth_test.go.txt
@@ -45,7 +45,7 @@ func TestRenewHandler(t *testing.T) {
// First, we have to make an auth request to get the user authenticated,
r, err := http.NewRequest("POST", "/api/auth/get", strings.NewReader(defaultCredentials))
if err != nil {
- t.Fatal(err)
+ t.Fatal(fm.Err)
}
w := httptest.NewRecorder()
@@ -60,7 +60,7 @@ func TestRenewHandler(t *testing.T) {
// Test renew authorization via Authorization Header.
r, err = http.NewRequest("GET", "/api/auth/renew", nil)
if err != nil {
- t.Fatal(err)
+ t.Fatal(fm.Err)
}
r.Header.Set("Authorization", "Bearer "+token)
@@ -74,7 +74,7 @@ func TestRenewHandler(t *testing.T) {
// Test renew authorization via cookie field.
r, err = http.NewRequest("GET", "/api/auth/renew", nil)
if err != nil {
- t.Fatal(err)
+ t.Fatal(fm.Err)
}
r.AddCookie(&http.Cookie{
diff --git a/http/http.go b/http/http.go
index cc2291be..23653720 100644
--- a/http/http.go
+++ b/http/http.go
@@ -2,7 +2,6 @@ package http
import (
"encoding/json"
- "errors"
"html/template"
"net/http"
"os"
@@ -13,21 +12,10 @@ import (
fm "github.com/hacdias/filemanager"
)
-var (
- errUserExist = errors.New("user already exists")
- errUserNotExist = errors.New("user does not exist")
- errEmptyRequest = errors.New("request body is empty")
- errEmptyPassword = errors.New("password is empty")
- errEmptyUsername = errors.New("username is empty")
- errEmptyScope = errors.New("scope is empty")
- errWrongDataType = errors.New("wrong data type")
- errInvalidUpdateField = errors.New("invalid field to update")
-)
-
// ServeHTTP is the main entry point of this HTML application.
func ServeHTTP(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
// Checks if the URL contains the baseURL and strips it. Otherwise, it just
- // returns a 404 error because we're not supposed to be here!
+ // returns a 404 fm.Error because we're not supposed to be here!
p := strings.TrimPrefix(r.URL.Path, c.BaseURL)
if len(p) >= len(r.URL.Path) && c.BaseURL != "" {
@@ -41,7 +29,7 @@ func ServeHTTP(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, erro
if r.URL.Path == "/sw.js" {
return renderFile(
c, w,
- c.assets.MustString("sw.js"),
+ c.Assets.MustString("sw.js"),
"application/javascript",
)
}
@@ -83,7 +71,7 @@ func ServeHTTP(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, erro
return renderFile(
c, w,
- c.assets.MustString("index.html"),
+ c.Assets.MustString("index.html"),
"text/html",
)
}
@@ -91,13 +79,13 @@ func ServeHTTP(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, erro
// staticHandler handles the static assets path.
func staticHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
if r.URL.Path != "/static/manifest.json" {
- http.FileServer(c.assets.HTTPBox()).ServeHTTP(w, r)
+ http.FileServer(c.Assets.HTTPBox()).ServeHTTP(w, r)
return 0, nil
}
return renderFile(
c, w,
- c.assets.MustString("static/manifest.json"),
+ c.Assets.MustString("static/manifest.json"),
"application/json",
)
}
@@ -141,7 +129,7 @@ func apiHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, err
var err error
c.File, err = fm.GetInfo(r.URL, c.FileManager, c.User)
if err != nil {
- return errorToHTTP(err, false), err
+ return ErrorToHTTP(err, false), err
}
}
@@ -177,7 +165,7 @@ func checksumHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int
query := r.URL.Query().Get("algo")
val, err := c.File.Checksum(query)
- if err == errInvalidOption {
+ if err == fm.ErrInvalidOption {
return http.StatusBadRequest, err
} else if err != nil {
return http.StatusInternalServerError, err
@@ -211,7 +199,7 @@ func renderFile(c *fm.Context, w http.ResponseWriter, file string, contentType s
err := tpl.Execute(w, map[string]interface{}{
"BaseURL": c.RootURL(),
- "StaticGen": c.staticgen,
+ "StaticGen": c.StaticGen.Name(),
})
if err != nil {
return http.StatusInternalServerError, err
@@ -221,12 +209,11 @@ func renderFile(c *fm.Context, w http.ResponseWriter, file string, contentType s
}
func sharePage(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
- var s shareLink
- err := c.db.One("Hash", r.URL.Path, &s)
+ s, err := c.Store.Share.Get(r.URL.Path)
if err == storm.ErrNotFound {
return renderFile(
c, w,
- c.assets.MustString("static/share/404.html"),
+ c.Assets.MustString("static/share/404.html"),
"text/html",
)
}
@@ -236,10 +223,10 @@ func sharePage(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, erro
}
if s.Expires && s.ExpireDate.Before(time.Now()) {
- c.db.DeleteStruct(&s)
+ c.Store.Share.Delete(s.Hash)
return renderFile(
c, w,
- c.assets.MustString("static/share/404.html"),
+ c.Assets.MustString("static/share/404.html"),
"text/html",
)
}
@@ -248,10 +235,10 @@ func sharePage(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, erro
info, err := os.Stat(s.Path)
if err != nil {
- return errorToHTTP(err, false), err
+ return ErrorToHTTP(err, false), err
}
- c.File = &file{
+ c.File = &fm.File{
Path: s.Path,
Name: info.Name(),
ModTime: info.ModTime(),
@@ -263,7 +250,7 @@ func sharePage(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, erro
dl := r.URL.Query().Get("dl")
if dl == "" || dl == "0" {
- tpl := template.Must(template.New("file").Parse(c.assets.MustString("static/share/index.html")))
+ tpl := template.Must(template.New("file").Parse(c.Assets.MustString("static/share/index.html")))
w.Header().Set("Content-Type", "text/html; charset=utf-8")
err := tpl.Execute(w, map[string]interface{}{
@@ -303,8 +290,8 @@ func matchURL(first, second string) bool {
return strings.HasPrefix(first, second)
}
-// errorToHTTP converts errors to HTTP Status Code.
-func errorToHTTP(err error, gone bool) int {
+// ErrorToHTTP converts errors to HTTP Status Code.
+func ErrorToHTTP(err error, gone bool) int {
switch {
case err == nil:
return http.StatusOK
diff --git a/http/resource.go b/http/resource.go
index 0a5c5271..f983f05f 100644
--- a/http/resource.go
+++ b/http/resource.go
@@ -64,9 +64,9 @@ func resourceHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int
func resourceGetHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
// Gets the information of the directory/file.
- f, err := getInfo(r.URL, c.FileManager, c.User)
+ f, err := fm.GetInfo(r.URL, c.FileManager, c.User)
if err != nil {
- return errorToHTTP(err, false), err
+ return ErrorToHTTP(err, false), err
}
// If it's a dir and the path doesn't end with a trailing slash,
@@ -83,7 +83,7 @@ func resourceGetHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (
// Tries to get the file type.
if err = f.GetFileType(true); err != nil {
- return errorToHTTP(err, true), err
+ return ErrorToHTTP(err, true), err
}
// Serve a preview if the file can't be edited or the
@@ -97,7 +97,7 @@ func resourceGetHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (
f.Kind = "editor"
// Tries to get the editor data.
- if err = f.getEditor(); err != nil {
+ if err = f.GetEditor(); err != nil {
return http.StatusInternalServerError, err
}
@@ -109,11 +109,11 @@ func listingHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int,
f.Kind = "listing"
// Tries to get the listing data.
- if err := f.getListing(c, r); err != nil {
- return errorToHTTP(err, true), err
+ if err := f.GetListing(c.User, r); err != nil {
+ return ErrorToHTTP(err, true), err
}
- listing := f.listing
+ listing := f.Listing
// Defines the cookie scope.
cookieScope := c.RootURL()
@@ -144,7 +144,7 @@ func resourceDeleteHandler(c *fm.Context, w http.ResponseWriter, r *http.Request
// Remove the file or folder.
err := c.User.FileSystem.RemoveAll(r.URL.Path)
if err != nil {
- return errorToHTTP(err, true), err
+ return ErrorToHTTP(err, true), err
}
return http.StatusOK, nil
@@ -160,7 +160,7 @@ func resourcePostPutHandler(c *fm.Context, w http.ResponseWriter, r *http.Reques
}
// Discard any invalid upload before returning to avoid connection
- // reset error.
+ // reset fm.Error.
defer func() {
io.Copy(ioutil.Discard, r.Body)
}()
@@ -175,13 +175,13 @@ func resourcePostPutHandler(c *fm.Context, w http.ResponseWriter, r *http.Reques
// Otherwise we try to create the directory.
err := c.User.FileSystem.Mkdir(r.URL.Path, 0776)
- return errorToHTTP(err, false), err
+ return ErrorToHTTP(err, false), err
}
// If using POST method, we are trying to create a new file so it is not
- // desirable to override an already existent file. Thus, we check
+ // desirable to ovfm.Erride an already existent file. Thus, we check
// if the file already exists. If so, we just return a 409 Conflict.
- if r.Method == http.MethodPost && r.Header.Get("Action") != "override" {
+ if r.Method == http.MethodPost && r.Header.Get("Action") != "ovfm.Erride" {
if _, err := c.User.FileSystem.Stat(r.URL.Path); err == nil {
return http.StatusConflict, errors.New("There is already a file on that path")
}
@@ -190,20 +190,20 @@ func resourcePostPutHandler(c *fm.Context, w http.ResponseWriter, r *http.Reques
// Create/Open the file.
f, err := c.User.FileSystem.OpenFile(r.URL.Path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0776)
if err != nil {
- return errorToHTTP(err, false), err
+ return ErrorToHTTP(err, false), err
}
defer f.Close()
// Copies the new content for the file.
_, err = io.Copy(f, r.Body)
if err != nil {
- return errorToHTTP(err, false), err
+ return ErrorToHTTP(err, false), err
}
// Gets the info about the file.
fi, err := f.Stat()
if err != nil {
- return errorToHTTP(err, false), err
+ return ErrorToHTTP(err, false), err
}
// Check if this instance has a Static Generator and handles publishing
@@ -242,7 +242,7 @@ func resourcePublishSchedule(c *fm.Context, w http.ResponseWriter, r *http.Reque
return http.StatusInternalServerError, err
}
- c.cron.AddFunc(t.Format("05 04 15 02 01 *"), func() {
+ c.Cron.AddFunc(t.Format("05 04 15 02 01 *"), func() {
_, err := resourcePublish(c, w, r)
if err != nil {
log.Print(err)
@@ -283,7 +283,7 @@ func resourcePatchHandler(c *fm.Context, w http.ResponseWriter, r *http.Request)
action := r.Header.Get("Action")
dst, err := url.QueryUnescape(dst)
if err != nil {
- return errorToHTTP(err, true), err
+ return ErrorToHTTP(err, true), err
}
src := r.URL.Path
@@ -298,7 +298,7 @@ func resourcePatchHandler(c *fm.Context, w http.ResponseWriter, r *http.Request)
err = c.User.FileSystem.Rename(src, dst)
}
- return errorToHTTP(err, true), err
+ return ErrorToHTTP(err, true), err
}
// displayMode obtains the display mode from the Cookie.
diff --git a/http/settings.go b/http/settings.go
index 887dd6d6..f0d9b2a3 100644
--- a/http/settings.go
+++ b/http/settings.go
@@ -27,7 +27,7 @@ type option struct {
func parsePutSettingsRequest(r *http.Request) (*modifySettingsRequest, error) {
// Checks if the request body is empty.
if r.Body == nil {
- return nil, errEmptyRequest
+ return nil, fm.ErrEmptyRequest
}
// Parses the request body and checks if it's well formed.
@@ -39,7 +39,7 @@ func parsePutSettingsRequest(r *http.Request) (*modifySettingsRequest, error) {
// Checks if the request type is right.
if mod.What != "settings" {
- return nil, errWrongDataType
+ return nil, fm.ErrWrongDataType
}
return mod, nil
@@ -103,9 +103,10 @@ func settingsPutHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (
if err != nil {
return http.StatusBadRequest, err
}
+
// Update the commands.
if mod.Which == "commands" {
- if err := c.db.Set("config", "commands", mod.Data.Commands); err != nil {
+ if err := c.Store.Config.Save("commands", mod.Data.Commands); err != nil {
return http.StatusInternalServerError, err
}
@@ -120,7 +121,7 @@ func settingsPutHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (
return http.StatusInternalServerError, err
}
- err = c.db.Set("staticgen", c.staticgen, c.StaticGen)
+ err = c.Store.Config.Save("staticgen_"+c.StaticGen.Name(), c.StaticGen)
if err != nil {
return http.StatusInternalServerError, err
}
diff --git a/http/share.go b/http/share.go
index 8a628c48..0f9fe06e 100644
--- a/http/share.go
+++ b/http/share.go
@@ -13,13 +13,6 @@ import (
fm "github.com/hacdias/filemanager"
)
-type shareLink struct {
- Hash string `json:"hash" storm:"id,index"`
- Path string `json:"path" storm:"index"`
- Expires bool `json:"expires"`
- ExpireDate time.Time `json:"expireDate"`
-}
-
func shareHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
r.URL.Path = sanitizeURL(r.URL.Path)
@@ -36,12 +29,8 @@ func shareHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, e
}
func shareGetHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
- var (
- s []*shareLink
- path = filepath.Join(string(c.User.FileSystem), r.URL.Path)
- )
-
- err := c.db.Find("Path", path, &s)
+ path := filepath.Join(string(c.User.FileSystem), r.URL.Path)
+ s, err := c.Store.Share.GetByPath(path)
if err == storm.ErrNotFound {
return http.StatusNotFound, nil
}
@@ -52,7 +41,7 @@ func shareGetHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int
for i, link := range s {
if link.Expires && link.ExpireDate.Before(time.Now()) {
- c.db.DeleteStruct(&shareLink{Hash: link.Hash})
+ c.Store.Share.Delete(link.Hash)
s = append(s[:i], s[i+1:]...)
}
}
@@ -63,7 +52,7 @@ func shareGetHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int
func sharePostHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
path := filepath.Join(string(c.User.FileSystem), r.URL.Path)
- var s shareLink
+ var s fm.ShareLink
expire := r.URL.Query().Get("expires")
unit := r.URL.Query().Get("unit")
@@ -75,14 +64,14 @@ func sharePostHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (in
}
}
- bytes, err := generateRandomBytes(32)
+ bytes, err := fm.GenerateRandomBytes(32)
if err != nil {
return http.StatusInternalServerError, err
}
str := hex.EncodeToString(bytes)
- s = shareLink{
+ s = fm.ShareLink{
Path: path,
Hash: str,
Expires: expire != "",
@@ -109,8 +98,7 @@ func sharePostHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (in
s.ExpireDate = time.Now().Add(add)
}
- err = c.db.Save(&s)
- if err != nil {
+ if err := c.Store.Share.Save(&s); err != nil {
return http.StatusInternalServerError, err
}
@@ -118,9 +106,7 @@ func sharePostHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (in
}
func shareDeleteHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
- var s shareLink
-
- err := c.db.One("Hash", strings.TrimPrefix(r.URL.Path, "/"), &s)
+ s, err := c.Store.Share.Get(strings.TrimPrefix(r.URL.Path, "/"))
if err == storm.ErrNotFound {
return http.StatusNotFound, nil
}
@@ -129,7 +115,7 @@ func shareDeleteHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (
return http.StatusInternalServerError, err
}
- err = c.db.DeleteStruct(&s)
+ err = c.Store.Share.Delete(s.Hash)
if err != nil {
return http.StatusInternalServerError, err
}
diff --git a/http/users.go b/http/users.go
index 65384c7d..58fb0ed6 100644
--- a/http/users.go
+++ b/http/users.go
@@ -9,7 +9,6 @@ import (
"strconv"
"strings"
- "github.com/asdine/storm"
fm "github.com/hacdias/filemanager"
)
@@ -48,7 +47,7 @@ func usersHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, e
// getUserID returns the id from the user which is present
// in the request url. If the url is invalid and doesn't
-// contain a valid ID, it returns an error.
+// contain a valid ID, it returns an fm.Error.
func getUserID(r *http.Request) (int, error) {
// Obtains the ID in string from the URL and converts
// it into an integer.
@@ -64,11 +63,11 @@ func getUserID(r *http.Request) (int, error) {
// getUser returns the user which is present in the request
// body. If the body is empty or the JSON is invalid, it
-// returns an error.
+// returns an fm.Error.
func getUser(r *http.Request) (*fm.User, string, error) {
// Checks if the request body is empty.
if r.Body == nil {
- return nil, "", errEmptyRequest
+ return nil, "", fm.ErrEmptyRequest
}
// Parses the request body and checks if it's well formed.
@@ -80,7 +79,7 @@ func getUser(r *http.Request) (*fm.User, string, error) {
// Checks if the request type is right.
if mod.What != "user" {
- return nil, "", errWrongDataType
+ return nil, "", fm.ErrWrongDataType
}
return mod.Data, mod.Which, nil
@@ -94,15 +93,15 @@ func usersGetHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int
// Request for the listing of users.
if r.URL.Path == "/" {
- users := []User{}
+ users, err := c.Store.Users.Gets()
+ if err != nil {
+ return http.StatusInternalServerError, err
+ }
- for _, user := range c.Users {
- // Copies the user info and removes its
- // password so it won't be sent to the
- // front-end.
- u := *user
+ for _, u := range users {
+ // Removes the user password so it won't
+ // be sent to the front-end.
u.Password = ""
- users = append(users, u)
}
sort.Slice(users, func(i, j int) bool {
@@ -117,19 +116,17 @@ func usersGetHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int
return http.StatusInternalServerError, err
}
- // Searches for the user and prints the one who matches.
- for _, user := range c.Users {
- if user.ID != id {
- continue
- }
-
- u := *user
- u.Password = ""
- return renderJSON(w, u)
+ u, err := c.Store.Users.Get(id)
+ if err == fm.ErrExist {
+ return http.StatusNotFound, err
}
- // If there aren't any matches, return not found.
- return http.StatusNotFound, errUserNotExist
+ if err != nil {
+ return http.StatusInternalServerError, err
+ }
+
+ u.Password = ""
+ return renderJSON(w, u)
}
func usersPostHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
@@ -144,17 +141,17 @@ func usersPostHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (in
// Checks if username isn't empty.
if u.Username == "" {
- return http.StatusBadRequest, errEmptyUsername
+ return http.StatusBadRequest, fm.ErrEmptyUsername
}
// Checks if filesystem isn't empty.
if u.FileSystem == "" {
- return http.StatusBadRequest, errEmptyScope
+ return http.StatusBadRequest, fm.ErrEmptyScope
}
// Checks if password isn't empty.
if u.Password == "" {
- return http.StatusBadRequest, errEmptyPassword
+ return http.StatusBadRequest, fm.ErrEmptyPassword
}
// The username, password and scope cannot be empty.
@@ -164,7 +161,7 @@ func usersPostHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (in
// Initialize rules if they're not initialized.
if u.Rules == nil {
- u.Rules = []*Rule{}
+ u.Rules = []*fm.Rule{}
}
// Initialize commands if not initialized.
@@ -183,7 +180,7 @@ func usersPostHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (in
}
// Hashes the password.
- pw, err := hashPassword(u.Password)
+ pw, err := fm.HashPassword(u.Password)
if err != nil {
return http.StatusInternalServerError, err
}
@@ -191,18 +188,15 @@ func usersPostHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (in
u.Password = pw
// Saves the user to the database.
- err = c.db.Save(u)
- if err == storm.ErrAlreadyExists {
- return http.StatusConflict, errUserExist
+ err = c.Store.Users.Save(u)
+ if err == fm.ErrExist {
+ return http.StatusConflict, err
}
if err != nil {
return http.StatusInternalServerError, err
}
- // Saves the user to the memory.
- c.Users[u.Username] = u
-
// Set the Location header and return.
w.Header().Set("Location", "/users/"+strconv.Itoa(u.ID))
w.WriteHeader(http.StatusCreated)
@@ -243,23 +237,15 @@ func usersDeleteHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (
}
// Deletes the user from the database.
- err = c.db.DeleteStruct(&User{ID: id})
- if err == storm.ErrNotFound {
- return http.StatusNotFound, errUserNotExist
+ err = c.Store.Users.Delete(id)
+ if err == fm.ErrNotExist {
+ return http.StatusNotFound, fm.ErrNotExist
}
if err != nil {
return http.StatusInternalServerError, err
}
- // Delete the user from the in-memory users map.
- for _, user := range c.Users {
- if user.ID == id {
- delete(c.Users, user.Username)
- break
- }
- }
-
return http.StatusOK, nil
}
@@ -290,12 +276,8 @@ func usersPutHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int
if which == "partial" {
c.User.CSS = u.CSS
c.User.Locale = u.Locale
- err = c.db.UpdateField(&User{ID: c.User.ID}, "CSS", u.CSS)
- if err != nil {
- return http.StatusInternalServerError, err
- }
- err = c.db.UpdateField(&User{ID: c.User.ID}, "Locale", u.Locale)
+ err = c.Store.Users.Update(c.User, "CSS", "Locale")
if err != nil {
return http.StatusInternalServerError, err
}
@@ -306,16 +288,15 @@ func usersPutHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int
// Updates the Password.
if which == "password" {
if u.Password == "" {
- return http.StatusBadRequest, errEmptyPassword
+ return http.StatusBadRequest, fm.ErrEmptyPassword
}
- pw, err := hashPassword(u.Password)
+ c.User.Password, err = fm.HashPassword(u.Password)
if err != nil {
return http.StatusInternalServerError, err
}
- c.User.Password = pw
- err = c.db.UpdateField(&User{ID: c.User.ID}, "Password", pw)
+ err = c.Store.Users.Update(c.User, "Password")
if err != nil {
return http.StatusInternalServerError, err
}
@@ -325,17 +306,17 @@ func usersPutHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int
// If can only be all.
if which != "all" {
- return http.StatusBadRequest, errInvalidUpdateField
+ return http.StatusBadRequest, fm.ErrInvalidUpdateField
}
// Checks if username isn't empty.
if u.Username == "" {
- return http.StatusBadRequest, errEmptyUsername
+ return http.StatusBadRequest, fm.ErrEmptyUsername
}
// Checks if filesystem isn't empty.
if u.FileSystem == "" {
- return http.StatusBadRequest, errEmptyScope
+ return http.StatusBadRequest, fm.ErrEmptyScope
}
// Checks if the scope exists.
@@ -345,7 +326,7 @@ func usersPutHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int
// Initialize rules if they're not initialized.
if u.Rules == nil {
- u.Rules = []*Rule{}
+ u.Rules = []*fm.Rule{}
}
// Initialize commands if not initialized.
@@ -354,22 +335,20 @@ func usersPutHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int
}
// Gets the current saved user from the in-memory map.
- var suser *User
- for _, user := range c.Users {
- if user.ID == id {
- suser = user
- break
- }
- }
- if suser == nil {
+ suser, err := c.Store.Users.Get(id)
+ if err == fm.ErrNotExist {
return http.StatusNotFound, nil
}
+ if err != nil {
+ return http.StatusInternalServerError, err
+ }
+
u.ID = id
// Changes the password if the request wants it.
if u.Password != "" {
- pw, err := hashPassword(u.Password)
+ pw, err := fm.HashPassword(u.Password)
if err != nil {
return http.StatusInternalServerError, err
}
@@ -381,17 +360,10 @@ func usersPutHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int
// Updates the whole User struct because we always are supposed
// to send a new entire object.
- err = c.db.Save(u)
+ err = c.Store.Users.Update(u)
if err != nil {
return http.StatusInternalServerError, err
}
- // If the user changed the username, delete the old user
- // from the in-memory user map.
- if suser.Username != u.Username {
- delete(c.Users, suser.Username)
- }
-
- c.Users[u.Username] = u
return http.StatusOK, nil
}
diff --git a/http/websockets.go b/http/websockets.go
index c54818fe..c12cc4ba 100644
--- a/http/websockets.go
+++ b/http/websockets.go
@@ -28,7 +28,7 @@ var (
// command handles the requests for VCS related commands: git, svn and mercurial
func command(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
- // Upgrades the connection to a websocket and checks for errors.
+ // Upgrades the connection to a websocket and checks for fm.Errors.
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
return 0, err
@@ -92,7 +92,7 @@ func command(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error)
cmd.Stderr = buff
cmd.Stdout = buff
- // Starts the command and checks for errors.
+ // Starts the command and checks for fm.Errors.
err = cmd.Start()
if err != nil {
return http.StatusInternalServerError, err
@@ -241,7 +241,7 @@ func parseSearch(value string) *searchOptions {
// search searches for a file or directory.
func search(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
- // Upgrades the connection to a websocket and checks for errors.
+ // Upgrades the connection to a websocket and checks for fm.Errors.
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
return 0, err
diff --git a/user.go b/user.go
index 3bc4ecd8..18267b21 100644
--- a/user.go
+++ b/user.go
@@ -1,12 +1,28 @@
package filemanager
import (
+ "crypto/rand"
+ "errors"
"regexp"
"strings"
+ "time"
+
+ "golang.org/x/crypto/bcrypt"
"github.com/hacdias/fileutils"
)
+var (
+ ErrExist = errors.New("the resource already exists")
+ ErrNotExist = errors.New("the resource does not exist")
+ ErrEmptyRequest = errors.New("request body is empty")
+ ErrEmptyPassword = errors.New("password is empty")
+ ErrEmptyUsername = errors.New("username is empty")
+ ErrEmptyScope = errors.New("scope is empty")
+ ErrWrongDataType = errors.New("wrong data type")
+ ErrInvalidUpdateField = errors.New("invalid field to update")
+)
+
// DefaultUser is used on New, when no 'base' user is provided.
var DefaultUser = User{
AllowCommands: true,
@@ -110,10 +126,24 @@ func (r *Regexp) MatchString(s string) bool {
return r.regexp.MatchString(s)
}
+type ShareLink struct {
+ Hash string `json:"hash" storm:"id,index"`
+ Path string `json:"path" storm:"index"`
+ Expires bool `json:"expires"`
+ ExpireDate time.Time `json:"expireDate"`
+}
+
+type Store struct {
+ Users UsersStore
+ Config ConfigStore
+ Share ShareStore
+}
+
type UsersStore interface {
Get(id int) (*User, error)
Gets() ([]*User, error)
- Save(u *User, fields ...string) error
+ Save(u *User) error
+ Update(u *User, fields ...string) error
Delete(id int) error
}
@@ -123,6 +153,36 @@ type ConfigStore interface {
}
type ShareStore interface {
- Get(hash string)
- Save()
+ Get(hash string) (*ShareLink, error)
+ GetByPath(path string) ([]*ShareLink, error)
+ Gets() ([]*ShareLink, error)
+ Save(s *ShareLink) error
+ Delete(hash string) error
+}
+
+// HashPassword generates an hash from a password using bcrypt.
+func HashPassword(password string) (string, error) {
+ bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
+ return string(bytes), err
+}
+
+// CheckPasswordHash compares a password with an hash to check if they match.
+func CheckPasswordHash(password, hash string) bool {
+ err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
+ return err == nil
+}
+
+// GenerateRandomBytes returns securely generated random bytes.
+// It will return an fm.Error if the system's secure random
+// number generator fails to function correctly, in which
+// case the caller should not continue.
+func GenerateRandomBytes(n int) ([]byte, error) {
+ b := make([]byte, n)
+ _, err := rand.Read(b)
+ // Note that err == nil only if we read len(b) bytes.
+ if err != nil {
+ return nil, err
+ }
+
+ return b, nil
}