updates
parent
ec190d28a8
commit
174330929a
37
file.go
37
file.go
|
@ -27,8 +27,8 @@ var (
|
||||||
errInvalidOption = errors.New("Invalid option")
|
errInvalidOption = errors.New("Invalid option")
|
||||||
)
|
)
|
||||||
|
|
||||||
// file contains the information about a particular file or directory.
|
// File contains the information about a particular file or directory.
|
||||||
type file struct {
|
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"`
|
Kind string `json:"kind"`
|
||||||
// The name of the file.
|
// The name of the file.
|
||||||
|
@ -63,7 +63,7 @@ type file struct {
|
||||||
// A listing is the context used to fill out a template.
|
// A listing is the context used to fill out a template.
|
||||||
type listing struct {
|
type listing struct {
|
||||||
// The items (files and folders) in the path.
|
// The items (files and folders) in the path.
|
||||||
Items []*file `json:"items"`
|
Items []*File `json:"items"`
|
||||||
// The number of directories in the listing.
|
// The number of directories in the listing.
|
||||||
NumDirs int `json:"numDirs"`
|
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.
|
||||||
|
@ -76,12 +76,12 @@ type listing struct {
|
||||||
Display string `json:"display"`
|
Display string `json:"display"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// getInfo gets the file information and, in case of error, returns the
|
// GetInfo gets the file information and, in case of error, returns the
|
||||||
// respective HTTP error code
|
// respective HTTP error code
|
||||||
func getInfo(url *url.URL, c *FileManager, u *User) (*file, error) {
|
func GetInfo(url *url.URL, c *FileManager, u *User) (*File, error) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
i := &file{
|
i := &File{
|
||||||
URL: "/files" + url.String(),
|
URL: "/files" + url.String(),
|
||||||
VirtualPath: url.Path,
|
VirtualPath: url.Path,
|
||||||
Path: filepath.Join(string(u.FileSystem), url.Path),
|
Path: filepath.Join(string(u.FileSystem), url.Path),
|
||||||
|
@ -106,11 +106,11 @@ func getInfo(url *url.URL, c *FileManager, u *User) (*file, error) {
|
||||||
return i, nil
|
return i, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getListing gets the information about a specific directory and its files.
|
// GetListing gets the information about a specific directory and its files.
|
||||||
func (i *file) getListing(c *RequestContext, r *http.Request) error {
|
func (i *File) GetListing(u *User, r *http.Request) error {
|
||||||
// Gets the directory information using the Virtual File System of
|
// Gets the directory information using the Virtual File System of
|
||||||
// the user configuration.
|
// the user configuration.
|
||||||
f, err := c.User.FileSystem.OpenFile(c.File.VirtualPath, os.O_RDONLY, 0)
|
f, err := u.FileSystem.OpenFile(i.VirtualPath, os.O_RDONLY, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -123,7 +123,7 @@ func (i *file) getListing(c *RequestContext, r *http.Request) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
fileinfos []*file
|
fileinfos []*File
|
||||||
dirCount, fileCount int
|
dirCount, fileCount int
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -134,7 +134,7 @@ func (i *file) getListing(c *RequestContext, r *http.Request) error {
|
||||||
|
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
name := f.Name()
|
name := f.Name()
|
||||||
allowed := c.User.Allowed("/" + name)
|
allowed := u.Allowed("/" + name)
|
||||||
|
|
||||||
if !allowed {
|
if !allowed {
|
||||||
continue
|
continue
|
||||||
|
@ -150,7 +150,7 @@ func (i *file) getListing(c *RequestContext, r *http.Request) error {
|
||||||
// Absolute URL
|
// Absolute URL
|
||||||
url := url.URL{Path: baseurl + name}
|
url := url.URL{Path: baseurl + name}
|
||||||
|
|
||||||
i := &file{
|
i := &File{
|
||||||
Name: f.Name(),
|
Name: f.Name(),
|
||||||
Size: f.Size(),
|
Size: f.Size(),
|
||||||
ModTime: f.ModTime(),
|
ModTime: f.ModTime(),
|
||||||
|
@ -175,8 +175,8 @@ func (i *file) getListing(c *RequestContext, r *http.Request) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getEditor gets the editor based on a Info struct
|
// GetEditor gets the editor based on a Info struct
|
||||||
func (i *file) getEditor() error {
|
func (i *File) GetEditor() error {
|
||||||
i.Language = editorLanguage(i.Extension)
|
i.Language = editorLanguage(i.Extension)
|
||||||
// If the editor will hold only content, leave now.
|
// If the editor will hold only content, leave now.
|
||||||
if editorMode(i.Language) == "content" {
|
if editorMode(i.Language) == "content" {
|
||||||
|
@ -205,7 +205,7 @@ func (i *file) getEditor() error {
|
||||||
|
|
||||||
// GetFileType obtains the mimetype and converts it to a simple
|
// GetFileType obtains the mimetype and converts it to a simple
|
||||||
// type nomenclature.
|
// type nomenclature.
|
||||||
func (i *file) GetFileType(checkContent bool) error {
|
func (i *File) GetFileType(checkContent bool) error {
|
||||||
var content []byte
|
var content []byte
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
@ -283,7 +283,8 @@ End:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i file) Checksum(kind string) (string, error) {
|
// Checksum retrieves the checksum of a file.
|
||||||
|
func (i File) Checksum(algo string) (string, error) {
|
||||||
file, err := os.Open(i.Path)
|
file, err := os.Open(i.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
@ -293,7 +294,7 @@ func (i file) Checksum(kind string) (string, error) {
|
||||||
|
|
||||||
var h hash.Hash
|
var h hash.Hash
|
||||||
|
|
||||||
switch kind {
|
switch algo {
|
||||||
case "md5":
|
case "md5":
|
||||||
h = md5.New()
|
h = md5.New()
|
||||||
case "sha1":
|
case "sha1":
|
||||||
|
@ -315,7 +316,7 @@ func (i file) Checksum(kind string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CanBeEdited checks if the extension of a file is supported by the editor
|
// CanBeEdited checks if the extension of a file is supported by the editor
|
||||||
func (i file) CanBeEdited() bool {
|
func (i File) CanBeEdited() bool {
|
||||||
return i.Type == "text"
|
return i.Type == "text"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
187
filemanager.go
187
filemanager.go
|
@ -60,28 +60,14 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
rice "github.com/GeertJohan/go.rice"
|
rice "github.com/GeertJohan/go.rice"
|
||||||
"github.com/asdine/storm"
|
"github.com/asdine/storm"
|
||||||
"github.com/hacdias/fileutils"
|
|
||||||
"github.com/mholt/caddy"
|
"github.com/mholt/caddy"
|
||||||
"github.com/robfig/cron"
|
"github.com/robfig/cron"
|
||||||
)
|
)
|
||||||
|
|
||||||
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")
|
|
||||||
)
|
|
||||||
|
|
||||||
// FileManager is a file manager instance. It should be creating using the
|
// FileManager is a file manager instance. It should be creating using the
|
||||||
// 'New' function and not directly.
|
// 'New' function and not directly.
|
||||||
type FileManager struct {
|
type FileManager struct {
|
||||||
|
@ -111,8 +97,6 @@ type FileManager struct {
|
||||||
// there will only exist one user, called "admin".
|
// there will only exist one user, called "admin".
|
||||||
NoAuth bool
|
NoAuth bool
|
||||||
|
|
||||||
// staticgen is the name of the current static website generator.
|
|
||||||
staticgen string
|
|
||||||
// StaticGen is the static websit generator handler.
|
// StaticGen is the static websit generator handler.
|
||||||
StaticGen StaticGen
|
StaticGen StaticGen
|
||||||
|
|
||||||
|
@ -124,82 +108,18 @@ type FileManager struct {
|
||||||
|
|
||||||
// A map of events to a slice of commands.
|
// A map of events to a slice of commands.
|
||||||
Commands map[string][]string
|
Commands map[string][]string
|
||||||
|
|
||||||
|
Store *Store
|
||||||
|
}
|
||||||
|
|
||||||
|
type Store struct {
|
||||||
|
Users *UsersStore
|
||||||
}
|
}
|
||||||
|
|
||||||
// Command is a command function.
|
// Command is a command function.
|
||||||
type Command func(r *http.Request, m *FileManager, u *User) error
|
type Command func(r *http.Request, m *FileManager, u *User) error
|
||||||
|
|
||||||
// User contains the configuration for each user.
|
/*
|
||||||
type User struct {
|
|
||||||
// ID is the required primary key with auto increment0
|
|
||||||
ID int `storm:"id,increment"`
|
|
||||||
|
|
||||||
// Username is the user username used to login.
|
|
||||||
Username string `json:"username" storm:"index,unique"`
|
|
||||||
|
|
||||||
// The hashed password. This never reaches the front-end because it's temporarily
|
|
||||||
// emptied during JSON marshall.
|
|
||||||
Password string `json:"password"`
|
|
||||||
|
|
||||||
// Tells if this user is an admin.
|
|
||||||
Admin bool `json:"admin"`
|
|
||||||
|
|
||||||
// FileSystem is the virtual file system the user has access.
|
|
||||||
FileSystem fileutils.Dir `json:"filesystem"`
|
|
||||||
|
|
||||||
// Rules is an array of access and deny rules.
|
|
||||||
Rules []*Rule `json:"rules"`
|
|
||||||
|
|
||||||
// Custom styles for this user.
|
|
||||||
CSS string `json:"css"`
|
|
||||||
|
|
||||||
// Locale is the language of the user.
|
|
||||||
Locale string `json:"locale"`
|
|
||||||
|
|
||||||
// These indicate if the user can perform certain actions.
|
|
||||||
AllowNew bool `json:"allowNew"` // Create files and folders
|
|
||||||
AllowEdit bool `json:"allowEdit"` // Edit/rename files
|
|
||||||
AllowCommands bool `json:"allowCommands"` // Execute commands
|
|
||||||
AllowPublish bool `json:"allowPublish"` // Publish content (to use with static gen)
|
|
||||||
|
|
||||||
// Commands is the list of commands the user can execute.
|
|
||||||
Commands []string `json:"commands"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rule is a dissalow/allow rule.
|
|
||||||
type Rule struct {
|
|
||||||
// Regex indicates if this rule uses Regular Expressions or not.
|
|
||||||
Regex bool `json:"regex"`
|
|
||||||
|
|
||||||
// Allow indicates if this is an allow rule. Set 'false' to be a disallow rule.
|
|
||||||
Allow bool `json:"allow"`
|
|
||||||
|
|
||||||
// Path is the corresponding URL path for this rule.
|
|
||||||
Path string `json:"path"`
|
|
||||||
|
|
||||||
// Regexp is the regular expression. Only use this when 'Regex' was set to true.
|
|
||||||
Regexp *Regexp `json:"regexp"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Regexp is a regular expression wrapper around native regexp.
|
|
||||||
type Regexp struct {
|
|
||||||
Raw string `json:"raw"`
|
|
||||||
regexp *regexp.Regexp
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultUser is used on New, when no 'base' user is provided.
|
|
||||||
var DefaultUser = User{
|
|
||||||
AllowCommands: true,
|
|
||||||
AllowEdit: true,
|
|
||||||
AllowNew: true,
|
|
||||||
AllowPublish: true,
|
|
||||||
Commands: []string{},
|
|
||||||
Rules: []*Rule{},
|
|
||||||
CSS: "",
|
|
||||||
Admin: true,
|
|
||||||
Locale: "en",
|
|
||||||
FileSystem: fileutils.Dir("."),
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates a new File Manager instance. If 'database' file already
|
// New creates a new File Manager instance. If 'database' file already
|
||||||
// exists, it will load the users from there. Otherwise, a new user
|
// exists, it will load the users from there. Otherwise, a new user
|
||||||
|
@ -308,7 +228,7 @@ func New(database string, base User) (*FileManager, error) {
|
||||||
m.cron.Start()
|
m.cron.Start()
|
||||||
|
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
} */
|
||||||
|
|
||||||
// RootURL returns the actual URL where
|
// RootURL returns the actual URL where
|
||||||
// File Manager interface can be accessed.
|
// File Manager interface can be accessed.
|
||||||
|
@ -336,7 +256,7 @@ func (m *FileManager) SetBaseURL(url string) {
|
||||||
|
|
||||||
// ServeHTTP handles the request.
|
// ServeHTTP handles the request.
|
||||||
func (m *FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (m *FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
code, err := serveHTTP(&RequestContext{
|
/* code, err := serveHTTP(&RequestContext{
|
||||||
FileManager: m,
|
FileManager: m,
|
||||||
User: nil,
|
User: nil,
|
||||||
File: nil,
|
File: nil,
|
||||||
|
@ -355,66 +275,32 @@ func (m *FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
w.Write([]byte(err.Error()))
|
w.Write([]byte(err.Error()))
|
||||||
}
|
} */
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnableStaticGen attaches a static generator to the current File Manager
|
// Attach attaches a static generator to the current File Manager.
|
||||||
// instance.
|
func (m *FileManager) Attach(s StaticGen) error {
|
||||||
func (m *FileManager) EnableStaticGen(data StaticGen) error {
|
if reflect.TypeOf(s).Kind() != reflect.Ptr {
|
||||||
if reflect.TypeOf(data).Kind() != reflect.Ptr {
|
|
||||||
return errors.New("data should be a pointer to interface, not interface")
|
return errors.New("data should be a pointer to interface, not interface")
|
||||||
}
|
}
|
||||||
|
|
||||||
if h, ok := data.(*Hugo); ok {
|
err := s.Setup()
|
||||||
return m.enableHugo(h)
|
if err != nil {
|
||||||
}
|
|
||||||
|
|
||||||
if j, ok := data.(*Jekyll); ok {
|
|
||||||
return m.enableJekyll(j)
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors.New("unknown static website generator")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *FileManager) enableHugo(h *Hugo) error {
|
|
||||||
if err := h.find(); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
m.staticgen = "hugo"
|
m.StaticGen = s
|
||||||
m.StaticGen = h
|
|
||||||
|
|
||||||
err := m.db.Get("staticgen", "hugo", h)
|
// TODO: Save...
|
||||||
|
/* err := m.db.Get("staticgen", "hugo", h)
|
||||||
if err != nil && err == storm.ErrNotFound {
|
if err != nil && err == storm.ErrNotFound {
|
||||||
err = m.db.Set("staticgen", "hugo", *h)
|
err = m.db.Set("staticgen", "hugo", *h)
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *FileManager) enableJekyll(j *Jekyll) error {
|
/*
|
||||||
if err := j.find(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(j.Args) == 0 {
|
|
||||||
j.Args = []string{"build"}
|
|
||||||
}
|
|
||||||
|
|
||||||
if j.Args[0] != "build" {
|
|
||||||
j.Args = append([]string{"build"}, j.Args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
m.staticgen = "jekyll"
|
|
||||||
m.StaticGen = j
|
|
||||||
|
|
||||||
err := m.db.Get("staticgen", "jekyll", j)
|
|
||||||
if err != nil && err == storm.ErrNotFound {
|
|
||||||
err = m.db.Set("staticgen", "jekyll", *j)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
// This function is set to run periodically.
|
||||||
|
@ -437,38 +323,7 @@ func (m FileManager) shareCleaner() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} */
|
||||||
|
|
||||||
// Allowed checks if the user has permission to access a directory/file.
|
|
||||||
func (u User) Allowed(url string) bool {
|
|
||||||
var rule *Rule
|
|
||||||
i := len(u.Rules) - 1
|
|
||||||
|
|
||||||
for i >= 0 {
|
|
||||||
rule = u.Rules[i]
|
|
||||||
|
|
||||||
if rule.Regex {
|
|
||||||
if rule.Regexp.MatchString(url) {
|
|
||||||
return rule.Allow
|
|
||||||
}
|
|
||||||
} else if strings.HasPrefix(url, rule.Path) {
|
|
||||||
return rule.Allow
|
|
||||||
}
|
|
||||||
|
|
||||||
i--
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// MatchString checks if this string matches the regular expression.
|
|
||||||
func (r *Regexp) MatchString(s string) bool {
|
|
||||||
if r.regexp == nil {
|
|
||||||
r.regexp = regexp.MustCompile(r.Raw)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r.regexp.MatchString(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Runner runs the commands for a certain event type.
|
// Runner runs the commands for a certain event type.
|
||||||
func (m FileManager) Runner(event string, path string) error {
|
func (m FileManager) Runner(event string, path string) error {
|
||||||
|
|
325
http.go
325
http.go
|
@ -1,320 +1,23 @@
|
||||||
package filemanager
|
package filemanager
|
||||||
|
|
||||||
import (
|
import "net/http"
|
||||||
"encoding/json"
|
|
||||||
"html/template"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/asdine/storm"
|
// StaticGen is a static website generator.
|
||||||
)
|
type StaticGen interface {
|
||||||
|
SettingsPath() string
|
||||||
|
Name() string
|
||||||
|
Setup() error
|
||||||
|
|
||||||
// RequestContext contains the needed information to make handlers work.
|
Hook(c *Context, w http.ResponseWriter, r *http.Request) (int, error)
|
||||||
type RequestContext struct {
|
Preview(c *Context, w http.ResponseWriter, r *http.Request) (int, error)
|
||||||
|
Publish(c *Context, w http.ResponseWriter, r *http.Request) (int, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Context contains the needed information to make handlers work.
|
||||||
|
type Context struct {
|
||||||
*FileManager
|
*FileManager
|
||||||
User *User
|
User *User
|
||||||
File *file
|
File *File
|
||||||
// On API handlers, Router is the APi handler we want.
|
// On API handlers, Router is the APi handler we want.
|
||||||
Router string
|
Router string
|
||||||
}
|
}
|
||||||
|
|
||||||
// serveHTTP is the main entry point of this HTML application.
|
|
||||||
func serveHTTP(c *RequestContext, 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!
|
|
||||||
p := strings.TrimPrefix(r.URL.Path, c.BaseURL)
|
|
||||||
|
|
||||||
if len(p) >= len(r.URL.Path) && c.BaseURL != "" {
|
|
||||||
return http.StatusNotFound, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
r.URL.Path = p
|
|
||||||
|
|
||||||
// Check if this request is made to the service worker. If so,
|
|
||||||
// pass it through a template to add the needed variables.
|
|
||||||
if r.URL.Path == "/sw.js" {
|
|
||||||
return renderFile(
|
|
||||||
c, w,
|
|
||||||
c.assets.MustString("sw.js"),
|
|
||||||
"application/javascript",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks if this request is made to the static assets folder. If so, and
|
|
||||||
// if it is a GET request, returns with the asset. Otherwise, returns
|
|
||||||
// a status not implemented.
|
|
||||||
if matchURL(r.URL.Path, "/static") {
|
|
||||||
if r.Method != http.MethodGet {
|
|
||||||
return http.StatusNotImplemented, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return staticHandler(c, w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks if this request is made to the API and directs to the
|
|
||||||
// API handler if so.
|
|
||||||
if matchURL(r.URL.Path, "/api") {
|
|
||||||
r.URL.Path = strings.TrimPrefix(r.URL.Path, "/api")
|
|
||||||
return apiHandler(c, w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If it is a request to the preview and a static website generator is
|
|
||||||
// active, build the preview.
|
|
||||||
if strings.HasPrefix(r.URL.Path, "/preview") && c.StaticGen != nil {
|
|
||||||
r.URL.Path = strings.TrimPrefix(r.URL.Path, "/preview")
|
|
||||||
return c.StaticGen.Preview(c, w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasPrefix(r.URL.Path, "/share/") {
|
|
||||||
r.URL.Path = strings.TrimPrefix(r.URL.Path, "/share/")
|
|
||||||
return sharePage(c, w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Any other request should show the index.html file.
|
|
||||||
w.Header().Set("x-frame-options", "SAMEORIGIN")
|
|
||||||
w.Header().Set("x-content-type", "nosniff")
|
|
||||||
w.Header().Set("x-xss-protection", "1; mode=block")
|
|
||||||
|
|
||||||
return renderFile(
|
|
||||||
c, w,
|
|
||||||
c.assets.MustString("index.html"),
|
|
||||||
"text/html",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// staticHandler handles the static assets path.
|
|
||||||
func staticHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
|
||||||
if r.URL.Path != "/static/manifest.json" {
|
|
||||||
http.FileServer(c.assets.HTTPBox()).ServeHTTP(w, r)
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return renderFile(
|
|
||||||
c, w,
|
|
||||||
c.assets.MustString("static/manifest.json"),
|
|
||||||
"application/json",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// apiHandler is the main entry point for the /api endpoint.
|
|
||||||
func apiHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
|
||||||
if r.URL.Path == "/auth/get" {
|
|
||||||
return authHandler(c, w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.URL.Path == "/auth/renew" {
|
|
||||||
return renewAuthHandler(c, w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
valid, _ := validateAuth(c, r)
|
|
||||||
if !valid {
|
|
||||||
return http.StatusForbidden, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Router, r.URL.Path = splitURL(r.URL.Path)
|
|
||||||
|
|
||||||
if !c.User.Allowed(r.URL.Path) {
|
|
||||||
return http.StatusForbidden, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.StaticGen != nil {
|
|
||||||
// If we are using the 'magic url' for the settings,
|
|
||||||
// we should redirect the request for the acutual path.
|
|
||||||
if r.URL.Path == "/settings" {
|
|
||||||
r.URL.Path = c.StaticGen.SettingsPath()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Executes the Static website generator hook.
|
|
||||||
code, err := c.StaticGen.Hook(c, w, r)
|
|
||||||
if code != 0 || err != nil {
|
|
||||||
return code, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Router == "checksum" || c.Router == "download" {
|
|
||||||
var err error
|
|
||||||
c.File, err = getInfo(r.URL, c.FileManager, c.User)
|
|
||||||
if err != nil {
|
|
||||||
return errorToHTTP(err, false), err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var code int
|
|
||||||
var err error
|
|
||||||
|
|
||||||
switch c.Router {
|
|
||||||
case "download":
|
|
||||||
code, err = downloadHandler(c, w, r)
|
|
||||||
case "checksum":
|
|
||||||
code, err = checksumHandler(c, w, r)
|
|
||||||
case "command":
|
|
||||||
code, err = command(c, w, r)
|
|
||||||
case "search":
|
|
||||||
code, err = search(c, w, r)
|
|
||||||
case "resource":
|
|
||||||
code, err = resourceHandler(c, w, r)
|
|
||||||
case "users":
|
|
||||||
code, err = usersHandler(c, w, r)
|
|
||||||
case "settings":
|
|
||||||
code, err = settingsHandler(c, w, r)
|
|
||||||
case "share":
|
|
||||||
code, err = shareHandler(c, w, r)
|
|
||||||
default:
|
|
||||||
code = http.StatusNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
return code, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// serveChecksum calculates the hash of a file. Supports MD5, SHA1, SHA256 and SHA512.
|
|
||||||
func checksumHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
|
||||||
query := r.URL.Query().Get("algo")
|
|
||||||
|
|
||||||
val, err := c.File.Checksum(query)
|
|
||||||
if err == errInvalidOption {
|
|
||||||
return http.StatusBadRequest, err
|
|
||||||
} else if err != nil {
|
|
||||||
return http.StatusInternalServerError, err
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Write([]byte(val))
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// splitURL splits the path and returns everything that stands
|
|
||||||
// before the first slash and everything that goes after.
|
|
||||||
func splitURL(path string) (string, string) {
|
|
||||||
if path == "" {
|
|
||||||
return "", ""
|
|
||||||
}
|
|
||||||
|
|
||||||
path = strings.TrimPrefix(path, "/")
|
|
||||||
|
|
||||||
i := strings.Index(path, "/")
|
|
||||||
if i == -1 {
|
|
||||||
return "", path
|
|
||||||
}
|
|
||||||
|
|
||||||
return path[0:i], path[i:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// renderFile renders a file using a template with some needed variables.
|
|
||||||
func renderFile(c *RequestContext, w http.ResponseWriter, file string, contentType string) (int, error) {
|
|
||||||
tpl := template.Must(template.New("file").Parse(file))
|
|
||||||
w.Header().Set("Content-Type", contentType+"; charset=utf-8")
|
|
||||||
|
|
||||||
err := tpl.Execute(w, map[string]interface{}{
|
|
||||||
"BaseURL": c.RootURL(),
|
|
||||||
"StaticGen": c.staticgen,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return http.StatusInternalServerError, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func sharePage(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
|
||||||
var s shareLink
|
|
||||||
err := c.db.One("Hash", r.URL.Path, &s)
|
|
||||||
if err == storm.ErrNotFound {
|
|
||||||
return renderFile(
|
|
||||||
c, w,
|
|
||||||
c.assets.MustString("static/share/404.html"),
|
|
||||||
"text/html",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return http.StatusInternalServerError, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.Expires && s.ExpireDate.Before(time.Now()) {
|
|
||||||
c.db.DeleteStruct(&s)
|
|
||||||
return renderFile(
|
|
||||||
c, w,
|
|
||||||
c.assets.MustString("static/share/404.html"),
|
|
||||||
"text/html",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
r.URL.Path = s.Path
|
|
||||||
|
|
||||||
info, err := os.Stat(s.Path)
|
|
||||||
if err != nil {
|
|
||||||
return errorToHTTP(err, false), err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.File = &file{
|
|
||||||
Path: s.Path,
|
|
||||||
Name: info.Name(),
|
|
||||||
ModTime: info.ModTime(),
|
|
||||||
Mode: info.Mode(),
|
|
||||||
IsDir: info.IsDir(),
|
|
||||||
Size: info.Size(),
|
|
||||||
}
|
|
||||||
|
|
||||||
dl := r.URL.Query().Get("dl")
|
|
||||||
|
|
||||||
if dl == "" || dl == "0" {
|
|
||||||
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{}{
|
|
||||||
"BaseURL": c.RootURL(),
|
|
||||||
"File": c.File,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return http.StatusInternalServerError, err
|
|
||||||
}
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return downloadHandler(c, w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// renderJSON prints the JSON version of data to the browser.
|
|
||||||
func renderJSON(w http.ResponseWriter, data interface{}) (int, error) {
|
|
||||||
marsh, err := json.Marshal(data)
|
|
||||||
if err != nil {
|
|
||||||
return http.StatusInternalServerError, err
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
||||||
if _, err := w.Write(marsh); err != nil {
|
|
||||||
return http.StatusInternalServerError, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// matchURL checks if the first URL matches the second.
|
|
||||||
func matchURL(first, second string) bool {
|
|
||||||
first = strings.ToLower(first)
|
|
||||||
second = strings.ToLower(second)
|
|
||||||
|
|
||||||
return strings.HasPrefix(first, second)
|
|
||||||
}
|
|
||||||
|
|
||||||
// errorToHTTP converts errors to HTTP Status Code.
|
|
||||||
func errorToHTTP(err error, gone bool) int {
|
|
||||||
switch {
|
|
||||||
case err == nil:
|
|
||||||
return http.StatusOK
|
|
||||||
case os.IsPermission(err):
|
|
||||||
return http.StatusForbidden
|
|
||||||
case os.IsNotExist(err):
|
|
||||||
if !gone {
|
|
||||||
return http.StatusNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
return http.StatusGone
|
|
||||||
case os.IsExist(err):
|
|
||||||
return http.StatusConflict
|
|
||||||
default:
|
|
||||||
return http.StatusInternalServerError
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package filemanager
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
@ -11,17 +11,18 @@ import (
|
||||||
|
|
||||||
jwt "github.com/dgrijalva/jwt-go"
|
jwt "github.com/dgrijalva/jwt-go"
|
||||||
"github.com/dgrijalva/jwt-go/request"
|
"github.com/dgrijalva/jwt-go/request"
|
||||||
|
fm "github.com/hacdias/filemanager"
|
||||||
)
|
)
|
||||||
|
|
||||||
// authHandler proccesses the authentication for the user.
|
// authHandler proccesses the authentication for the user.
|
||||||
func authHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
func authHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
// NoAuth instances shouldn't call this method.
|
// NoAuth instances shouldn't call this method.
|
||||||
if c.NoAuth {
|
if c.NoAuth {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Receive the credentials from the request and unmarshal them.
|
// Receive the credentials from the request and unmarshal them.
|
||||||
var cred User
|
var cred fm.User
|
||||||
if r.Body == nil {
|
if r.Body == nil {
|
||||||
return http.StatusForbidden, nil
|
return http.StatusForbidden, nil
|
||||||
}
|
}
|
||||||
|
@ -48,7 +49,7 @@ func authHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int
|
||||||
|
|
||||||
// renewAuthHandler is used when the front-end already has a JWT token
|
// renewAuthHandler is used when the front-end already has a JWT token
|
||||||
// and is checking if it is up to date. If so, updates its info.
|
// and is checking if it is up to date. If so, updates its info.
|
||||||
func renewAuthHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
func renewAuthHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
ok, u := validateAuth(c, r)
|
ok, u := validateAuth(c, r)
|
||||||
if !ok {
|
if !ok {
|
||||||
return http.StatusForbidden, nil
|
return http.StatusForbidden, nil
|
||||||
|
@ -60,16 +61,16 @@ func renewAuthHandler(c *RequestContext, w http.ResponseWriter, r *http.Request)
|
||||||
|
|
||||||
// claims is the JWT claims.
|
// claims is the JWT claims.
|
||||||
type claims struct {
|
type claims struct {
|
||||||
User
|
fm.User
|
||||||
NoAuth bool `json:"noAuth"`
|
NoAuth bool `json:"noAuth"`
|
||||||
jwt.StandardClaims
|
jwt.StandardClaims
|
||||||
}
|
}
|
||||||
|
|
||||||
// printToken prints the final JWT token to the user.
|
// printToken prints the final JWT token to the user.
|
||||||
func printToken(c *RequestContext, w http.ResponseWriter) (int, error) {
|
func printToken(c *fm.Context, w http.ResponseWriter) (int, error) {
|
||||||
// Creates a copy of the user and removes it password
|
// Creates a copy of the user and removes it password
|
||||||
// hash so it never arrives to the user.
|
// hash so it never arrives to the user.
|
||||||
u := User{}
|
u := fm.User{}
|
||||||
u = *c.User
|
u = *c.User
|
||||||
u.Password = ""
|
u.Password = ""
|
||||||
|
|
||||||
|
@ -119,7 +120,7 @@ func (e extractor) ExtractToken(r *http.Request) (string, error) {
|
||||||
|
|
||||||
// validateAuth is used to validate the authentication and returns the
|
// validateAuth is used to validate the authentication and returns the
|
||||||
// User if it is valid.
|
// User if it is valid.
|
||||||
func validateAuth(c *RequestContext, r *http.Request) (bool, *User) {
|
func validateAuth(c *fm.Context, r *http.Request) (bool, *fm.User) {
|
||||||
if c.NoAuth {
|
if c.NoAuth {
|
||||||
c.User = c.DefaultUser
|
c.User = c.DefaultUser
|
||||||
return true, c.User
|
return true, c.User
|
|
@ -1,4 +1,4 @@
|
||||||
package filemanager
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
|
@ -1,4 +1,4 @@
|
||||||
package filemanager
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
@ -9,13 +9,14 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
fm "github.com/hacdias/filemanager"
|
||||||
"github.com/hacdias/fileutils"
|
"github.com/hacdias/fileutils"
|
||||||
"github.com/mholt/archiver"
|
"github.com/mholt/archiver"
|
||||||
)
|
)
|
||||||
|
|
||||||
// downloadHandler creates an archive in one of the supported formats (zip, tar,
|
// downloadHandler creates an archive in one of the supported formats (zip, tar,
|
||||||
// tar.gz or tar.bz2) and sends it to be downloaded.
|
// tar.gz or tar.bz2) and sends it to be downloaded.
|
||||||
func downloadHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
func downloadHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
query := r.URL.Query().Get("format")
|
query := r.URL.Query().Get("format")
|
||||||
|
|
||||||
// If the file isn't a directory, serve it using http.ServeFile. We display it
|
// If the file isn't a directory, serve it using http.ServeFile. We display it
|
|
@ -0,0 +1,324 @@
|
||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/asdine/storm"
|
||||||
|
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!
|
||||||
|
p := strings.TrimPrefix(r.URL.Path, c.BaseURL)
|
||||||
|
|
||||||
|
if len(p) >= len(r.URL.Path) && c.BaseURL != "" {
|
||||||
|
return http.StatusNotFound, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
r.URL.Path = p
|
||||||
|
|
||||||
|
// Check if this request is made to the service worker. If so,
|
||||||
|
// pass it through a template to add the needed variables.
|
||||||
|
if r.URL.Path == "/sw.js" {
|
||||||
|
return renderFile(
|
||||||
|
c, w,
|
||||||
|
c.assets.MustString("sw.js"),
|
||||||
|
"application/javascript",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks if this request is made to the static assets folder. If so, and
|
||||||
|
// if it is a GET request, returns with the asset. Otherwise, returns
|
||||||
|
// a status not implemented.
|
||||||
|
if matchURL(r.URL.Path, "/static") {
|
||||||
|
if r.Method != http.MethodGet {
|
||||||
|
return http.StatusNotImplemented, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return staticHandler(c, w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks if this request is made to the API and directs to the
|
||||||
|
// API handler if so.
|
||||||
|
if matchURL(r.URL.Path, "/api") {
|
||||||
|
r.URL.Path = strings.TrimPrefix(r.URL.Path, "/api")
|
||||||
|
return apiHandler(c, w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it is a request to the preview and a static website generator is
|
||||||
|
// active, build the preview.
|
||||||
|
if strings.HasPrefix(r.URL.Path, "/preview") && c.StaticGen != nil {
|
||||||
|
r.URL.Path = strings.TrimPrefix(r.URL.Path, "/preview")
|
||||||
|
return c.StaticGen.Preview(c, w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(r.URL.Path, "/share/") {
|
||||||
|
r.URL.Path = strings.TrimPrefix(r.URL.Path, "/share/")
|
||||||
|
return sharePage(c, w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any other request should show the index.html file.
|
||||||
|
w.Header().Set("x-frame-options", "SAMEORIGIN")
|
||||||
|
w.Header().Set("x-content-type", "nosniff")
|
||||||
|
w.Header().Set("x-xss-protection", "1; mode=block")
|
||||||
|
|
||||||
|
return renderFile(
|
||||||
|
c, w,
|
||||||
|
c.assets.MustString("index.html"),
|
||||||
|
"text/html",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return renderFile(
|
||||||
|
c, w,
|
||||||
|
c.assets.MustString("static/manifest.json"),
|
||||||
|
"application/json",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// apiHandler is the main entry point for the /api endpoint.
|
||||||
|
func apiHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
|
if r.URL.Path == "/auth/get" {
|
||||||
|
return authHandler(c, w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.URL.Path == "/auth/renew" {
|
||||||
|
return renewAuthHandler(c, w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
valid, _ := validateAuth(c, r)
|
||||||
|
if !valid {
|
||||||
|
return http.StatusForbidden, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Router, r.URL.Path = splitURL(r.URL.Path)
|
||||||
|
|
||||||
|
if !c.User.Allowed(r.URL.Path) {
|
||||||
|
return http.StatusForbidden, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.StaticGen != nil {
|
||||||
|
// If we are using the 'magic url' for the settings,
|
||||||
|
// we should redirect the request for the acutual path.
|
||||||
|
if r.URL.Path == "/settings" {
|
||||||
|
r.URL.Path = c.StaticGen.SettingsPath()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Executes the Static website generator hook.
|
||||||
|
code, err := c.StaticGen.Hook(c, w, r)
|
||||||
|
if code != 0 || err != nil {
|
||||||
|
return code, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Router == "checksum" || c.Router == "download" {
|
||||||
|
var err error
|
||||||
|
c.File, err = fm.GetInfo(r.URL, c.FileManager, c.User)
|
||||||
|
if err != nil {
|
||||||
|
return errorToHTTP(err, false), err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var code int
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch c.Router {
|
||||||
|
case "download":
|
||||||
|
code, err = downloadHandler(c, w, r)
|
||||||
|
case "checksum":
|
||||||
|
code, err = checksumHandler(c, w, r)
|
||||||
|
case "command":
|
||||||
|
code, err = command(c, w, r)
|
||||||
|
case "search":
|
||||||
|
code, err = search(c, w, r)
|
||||||
|
case "resource":
|
||||||
|
code, err = resourceHandler(c, w, r)
|
||||||
|
case "users":
|
||||||
|
code, err = usersHandler(c, w, r)
|
||||||
|
case "settings":
|
||||||
|
code, err = settingsHandler(c, w, r)
|
||||||
|
case "share":
|
||||||
|
code, err = shareHandler(c, w, r)
|
||||||
|
default:
|
||||||
|
code = http.StatusNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return code, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// serveChecksum calculates the hash of a file. Supports MD5, SHA1, SHA256 and SHA512.
|
||||||
|
func checksumHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
|
query := r.URL.Query().Get("algo")
|
||||||
|
|
||||||
|
val, err := c.File.Checksum(query)
|
||||||
|
if err == errInvalidOption {
|
||||||
|
return http.StatusBadRequest, err
|
||||||
|
} else if err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write([]byte(val))
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// splitURL splits the path and returns everything that stands
|
||||||
|
// before the first slash and everything that goes after.
|
||||||
|
func splitURL(path string) (string, string) {
|
||||||
|
if path == "" {
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
path = strings.TrimPrefix(path, "/")
|
||||||
|
|
||||||
|
i := strings.Index(path, "/")
|
||||||
|
if i == -1 {
|
||||||
|
return "", path
|
||||||
|
}
|
||||||
|
|
||||||
|
return path[0:i], path[i:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// renderFile renders a file using a template with some needed variables.
|
||||||
|
func renderFile(c *fm.Context, w http.ResponseWriter, file string, contentType string) (int, error) {
|
||||||
|
tpl := template.Must(template.New("file").Parse(file))
|
||||||
|
w.Header().Set("Content-Type", contentType+"; charset=utf-8")
|
||||||
|
|
||||||
|
err := tpl.Execute(w, map[string]interface{}{
|
||||||
|
"BaseURL": c.RootURL(),
|
||||||
|
"StaticGen": c.staticgen,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
if err == storm.ErrNotFound {
|
||||||
|
return renderFile(
|
||||||
|
c, w,
|
||||||
|
c.assets.MustString("static/share/404.html"),
|
||||||
|
"text/html",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Expires && s.ExpireDate.Before(time.Now()) {
|
||||||
|
c.db.DeleteStruct(&s)
|
||||||
|
return renderFile(
|
||||||
|
c, w,
|
||||||
|
c.assets.MustString("static/share/404.html"),
|
||||||
|
"text/html",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.URL.Path = s.Path
|
||||||
|
|
||||||
|
info, err := os.Stat(s.Path)
|
||||||
|
if err != nil {
|
||||||
|
return errorToHTTP(err, false), err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.File = &file{
|
||||||
|
Path: s.Path,
|
||||||
|
Name: info.Name(),
|
||||||
|
ModTime: info.ModTime(),
|
||||||
|
Mode: info.Mode(),
|
||||||
|
IsDir: info.IsDir(),
|
||||||
|
Size: info.Size(),
|
||||||
|
}
|
||||||
|
|
||||||
|
dl := r.URL.Query().Get("dl")
|
||||||
|
|
||||||
|
if dl == "" || dl == "0" {
|
||||||
|
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{}{
|
||||||
|
"BaseURL": c.RootURL(),
|
||||||
|
"File": c.File,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return downloadHandler(c, w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// renderJSON prints the JSON version of data to the browser.
|
||||||
|
func renderJSON(w http.ResponseWriter, data interface{}) (int, error) {
|
||||||
|
marsh, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
if _, err := w.Write(marsh); err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// matchURL checks if the first URL matches the second.
|
||||||
|
func matchURL(first, second string) bool {
|
||||||
|
first = strings.ToLower(first)
|
||||||
|
second = strings.ToLower(second)
|
||||||
|
|
||||||
|
return strings.HasPrefix(first, second)
|
||||||
|
}
|
||||||
|
|
||||||
|
// errorToHTTP converts errors to HTTP Status Code.
|
||||||
|
func errorToHTTP(err error, gone bool) int {
|
||||||
|
switch {
|
||||||
|
case err == nil:
|
||||||
|
return http.StatusOK
|
||||||
|
case os.IsPermission(err):
|
||||||
|
return http.StatusForbidden
|
||||||
|
case os.IsNotExist(err):
|
||||||
|
if !gone {
|
||||||
|
return http.StatusNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return http.StatusGone
|
||||||
|
case os.IsExist(err):
|
||||||
|
return http.StatusConflict
|
||||||
|
default:
|
||||||
|
return http.StatusInternalServerError
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package filemanager
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
@ -13,6 +13,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
fm "github.com/hacdias/filemanager"
|
||||||
"github.com/hacdias/fileutils"
|
"github.com/hacdias/fileutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -26,7 +27,7 @@ func sanitizeURL(url string) string {
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
func resourceHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
r.URL.Path = sanitizeURL(r.URL.Path)
|
r.URL.Path = sanitizeURL(r.URL.Path)
|
||||||
|
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
|
@ -61,7 +62,7 @@ func resourceHandler(c *RequestContext, w http.ResponseWriter, r *http.Request)
|
||||||
return http.StatusNotImplemented, nil
|
return http.StatusNotImplemented, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceGetHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
func resourceGetHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
// Gets the information of the directory/file.
|
// Gets the information of the directory/file.
|
||||||
f, err := getInfo(r.URL, c.FileManager, c.User)
|
f, err := getInfo(r.URL, c.FileManager, c.User)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -103,7 +104,7 @@ func resourceGetHandler(c *RequestContext, w http.ResponseWriter, r *http.Reques
|
||||||
return renderJSON(w, f)
|
return renderJSON(w, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
func listingHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
func listingHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
f := c.File
|
f := c.File
|
||||||
f.Kind = "listing"
|
f.Kind = "listing"
|
||||||
|
|
||||||
|
@ -134,7 +135,7 @@ func listingHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (
|
||||||
return renderJSON(w, f)
|
return renderJSON(w, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceDeleteHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
func resourceDeleteHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
// Prevent the removal of the root directory.
|
// Prevent the removal of the root directory.
|
||||||
if r.URL.Path == "/" || !c.User.AllowEdit {
|
if r.URL.Path == "/" || !c.User.AllowEdit {
|
||||||
return http.StatusForbidden, nil
|
return http.StatusForbidden, nil
|
||||||
|
@ -149,7 +150,7 @@ func resourceDeleteHandler(c *RequestContext, w http.ResponseWriter, r *http.Req
|
||||||
return http.StatusOK, nil
|
return http.StatusOK, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourcePostPutHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
func resourcePostPutHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
if !c.User.AllowNew && r.Method == http.MethodPost {
|
if !c.User.AllowNew && r.Method == http.MethodPost {
|
||||||
return http.StatusForbidden, nil
|
return http.StatusForbidden, nil
|
||||||
}
|
}
|
||||||
|
@ -220,7 +221,7 @@ func resourcePostPutHandler(c *RequestContext, w http.ResponseWriter, r *http.Re
|
||||||
return http.StatusOK, nil
|
return http.StatusOK, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourcePublishSchedule(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
func resourcePublishSchedule(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
publish := r.Header.Get("Publish")
|
publish := r.Header.Get("Publish")
|
||||||
schedule := r.Header.Get("Schedule")
|
schedule := r.Header.Get("Schedule")
|
||||||
|
|
||||||
|
@ -251,7 +252,7 @@ func resourcePublishSchedule(c *RequestContext, w http.ResponseWriter, r *http.R
|
||||||
return http.StatusOK, nil
|
return http.StatusOK, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourcePublish(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
func resourcePublish(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
path := filepath.Join(string(c.User.FileSystem), r.URL.Path)
|
path := filepath.Join(string(c.User.FileSystem), r.URL.Path)
|
||||||
|
|
||||||
// Before save command handler.
|
// Before save command handler.
|
||||||
|
@ -273,7 +274,7 @@ func resourcePublish(c *RequestContext, w http.ResponseWriter, r *http.Request)
|
||||||
}
|
}
|
||||||
|
|
||||||
// resourcePatchHandler is the entry point for resource handler.
|
// resourcePatchHandler is the entry point for resource handler.
|
||||||
func resourcePatchHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
func resourcePatchHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
if !c.User.AllowEdit {
|
if !c.User.AllowEdit {
|
||||||
return http.StatusForbidden, nil
|
return http.StatusForbidden, nil
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package filemanager
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
@ -6,6 +6,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
|
fm "github.com/hacdias/filemanager"
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -44,7 +45,7 @@ func parsePutSettingsRequest(r *http.Request) (*modifySettingsRequest, error) {
|
||||||
return mod, nil
|
return mod, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func settingsHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
func settingsHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
if r.URL.Path != "" && r.URL.Path != "/" {
|
if r.URL.Path != "" && r.URL.Path != "/" {
|
||||||
return http.StatusNotFound, nil
|
return http.StatusNotFound, nil
|
||||||
}
|
}
|
||||||
|
@ -64,7 +65,7 @@ type settingsGetRequest struct {
|
||||||
StaticGen []option `json:"staticGen"`
|
StaticGen []option `json:"staticGen"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func settingsGetHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
func settingsGetHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
if !c.User.Admin {
|
if !c.User.Admin {
|
||||||
return http.StatusForbidden, nil
|
return http.StatusForbidden, nil
|
||||||
}
|
}
|
||||||
|
@ -93,7 +94,7 @@ func settingsGetHandler(c *RequestContext, w http.ResponseWriter, r *http.Reques
|
||||||
return renderJSON(w, result)
|
return renderJSON(w, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
func settingsPutHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
func settingsPutHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
if !c.User.Admin {
|
if !c.User.Admin {
|
||||||
return http.StatusForbidden, nil
|
return http.StatusForbidden, nil
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package filemanager
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
@ -10,6 +10,7 @@ import (
|
||||||
|
|
||||||
"github.com/asdine/storm"
|
"github.com/asdine/storm"
|
||||||
"github.com/asdine/storm/q"
|
"github.com/asdine/storm/q"
|
||||||
|
fm "github.com/hacdias/filemanager"
|
||||||
)
|
)
|
||||||
|
|
||||||
type shareLink struct {
|
type shareLink struct {
|
||||||
|
@ -19,7 +20,7 @@ type shareLink struct {
|
||||||
ExpireDate time.Time `json:"expireDate"`
|
ExpireDate time.Time `json:"expireDate"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func shareHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
func shareHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
r.URL.Path = sanitizeURL(r.URL.Path)
|
r.URL.Path = sanitizeURL(r.URL.Path)
|
||||||
|
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
|
@ -34,7 +35,7 @@ func shareHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (in
|
||||||
return http.StatusNotImplemented, nil
|
return http.StatusNotImplemented, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func shareGetHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
func shareGetHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
var (
|
var (
|
||||||
s []*shareLink
|
s []*shareLink
|
||||||
path = filepath.Join(string(c.User.FileSystem), r.URL.Path)
|
path = filepath.Join(string(c.User.FileSystem), r.URL.Path)
|
||||||
|
@ -59,7 +60,7 @@ func shareGetHandler(c *RequestContext, w http.ResponseWriter, r *http.Request)
|
||||||
return renderJSON(w, s)
|
return renderJSON(w, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func sharePostHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
func sharePostHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
path := filepath.Join(string(c.User.FileSystem), r.URL.Path)
|
path := filepath.Join(string(c.User.FileSystem), r.URL.Path)
|
||||||
|
|
||||||
var s shareLink
|
var s shareLink
|
||||||
|
@ -116,7 +117,7 @@ func sharePostHandler(c *RequestContext, w http.ResponseWriter, r *http.Request)
|
||||||
return renderJSON(w, s)
|
return renderJSON(w, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func shareDeleteHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
func shareDeleteHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
var s shareLink
|
var s shareLink
|
||||||
|
|
||||||
err := c.db.One("Hash", strings.TrimPrefix(r.URL.Path, "/"), &s)
|
err := c.db.One("Hash", strings.TrimPrefix(r.URL.Path, "/"), &s)
|
|
@ -1,4 +1,4 @@
|
||||||
package filemanager
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
@ -10,6 +10,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/asdine/storm"
|
"github.com/asdine/storm"
|
||||||
|
fm "github.com/hacdias/filemanager"
|
||||||
)
|
)
|
||||||
|
|
||||||
type modifyRequest struct {
|
type modifyRequest struct {
|
||||||
|
@ -19,12 +20,12 @@ type modifyRequest struct {
|
||||||
|
|
||||||
type modifyUserRequest struct {
|
type modifyUserRequest struct {
|
||||||
*modifyRequest
|
*modifyRequest
|
||||||
Data *User `json:"data"`
|
Data *fm.User `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// usersHandler is the entry point of the users API. It's just a router
|
// usersHandler is the entry point of the users API. It's just a router
|
||||||
// to send the request to its
|
// to send the request to its
|
||||||
func usersHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
func usersHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
// If the user isn't admin and isn't making a PUT
|
// If the user isn't admin and isn't making a PUT
|
||||||
// request, then return forbidden.
|
// request, then return forbidden.
|
||||||
if !c.User.Admin && r.Method != http.MethodPut {
|
if !c.User.Admin && r.Method != http.MethodPut {
|
||||||
|
@ -64,7 +65,7 @@ func getUserID(r *http.Request) (int, error) {
|
||||||
// getUser returns the user which is present in the request
|
// getUser returns the user which is present in the request
|
||||||
// body. If the body is empty or the JSON is invalid, it
|
// body. If the body is empty or the JSON is invalid, it
|
||||||
// returns an error.
|
// returns an error.
|
||||||
func getUser(r *http.Request) (*User, string, error) {
|
func getUser(r *http.Request) (*fm.User, string, error) {
|
||||||
// Checks if the request body is empty.
|
// Checks if the request body is empty.
|
||||||
if r.Body == nil {
|
if r.Body == nil {
|
||||||
return nil, "", errEmptyRequest
|
return nil, "", errEmptyRequest
|
||||||
|
@ -85,7 +86,7 @@ func getUser(r *http.Request) (*User, string, error) {
|
||||||
return mod.Data, mod.Which, nil
|
return mod.Data, mod.Which, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func usersGetHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
func usersGetHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
// Request for the default user data.
|
// Request for the default user data.
|
||||||
if r.URL.Path == "/base" {
|
if r.URL.Path == "/base" {
|
||||||
return renderJSON(w, c.DefaultUser)
|
return renderJSON(w, c.DefaultUser)
|
||||||
|
@ -131,7 +132,7 @@ func usersGetHandler(c *RequestContext, w http.ResponseWriter, r *http.Request)
|
||||||
return http.StatusNotFound, errUserNotExist
|
return http.StatusNotFound, errUserNotExist
|
||||||
}
|
}
|
||||||
|
|
||||||
func usersPostHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
func usersPostHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
if r.URL.Path != "/" {
|
if r.URL.Path != "/" {
|
||||||
return http.StatusMethodNotAllowed, nil
|
return http.StatusMethodNotAllowed, nil
|
||||||
}
|
}
|
||||||
|
@ -231,7 +232,7 @@ func checkFS(path string) (int, error) {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func usersDeleteHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
func usersDeleteHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
if r.URL.Path == "/" {
|
if r.URL.Path == "/" {
|
||||||
return http.StatusMethodNotAllowed, nil
|
return http.StatusMethodNotAllowed, nil
|
||||||
}
|
}
|
||||||
|
@ -262,7 +263,7 @@ func usersDeleteHandler(c *RequestContext, w http.ResponseWriter, r *http.Reques
|
||||||
return http.StatusOK, nil
|
return http.StatusOK, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func usersPutHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
func usersPutHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
// New users should be created on /api/users.
|
// New users should be created on /api/users.
|
||||||
if r.URL.Path == "/" {
|
if r.URL.Path == "/" {
|
||||||
return http.StatusMethodNotAllowed, nil
|
return http.StatusMethodNotAllowed, nil
|
|
@ -1,4 +1,4 @@
|
||||||
package filemanager
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
@ -13,6 +13,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
|
fm "github.com/hacdias/filemanager"
|
||||||
)
|
)
|
||||||
|
|
||||||
var upgrader = websocket.Upgrader{
|
var upgrader = websocket.Upgrader{
|
||||||
|
@ -26,7 +27,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// command handles the requests for VCS related commands: git, svn and mercurial
|
// command handles the requests for VCS related commands: git, svn and mercurial
|
||||||
func command(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
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 errors.
|
||||||
conn, err := upgrader.Upgrade(w, r, nil)
|
conn, err := upgrader.Upgrade(w, r, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -239,7 +240,7 @@ func parseSearch(value string) *searchOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
// search searches for a file or directory.
|
// search searches for a file or directory.
|
||||||
func search(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
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 errors.
|
||||||
conn, err := upgrader.Upgrade(w, r, nil)
|
conn, err := upgrader.Upgrade(w, r, nil)
|
||||||
if err != nil {
|
if err != nil {
|
File diff suppressed because it is too large
Load Diff
2342
rice-box.go
2342
rice-box.go
File diff suppressed because one or more lines are too long
|
@ -1,4 +1,4 @@
|
||||||
package filemanager
|
package staticgen
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
@ -10,6 +10,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
fm "github.com/hacdias/filemanager"
|
||||||
"github.com/hacdias/varutils"
|
"github.com/hacdias/varutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -17,15 +18,6 @@ var (
|
||||||
errUnsupportedFileType = errors.New("The type of the provided file isn't supported for this action")
|
errUnsupportedFileType = errors.New("The type of the provided file isn't supported for this action")
|
||||||
)
|
)
|
||||||
|
|
||||||
// StaticGen is a static website generator.
|
|
||||||
type StaticGen interface {
|
|
||||||
SettingsPath() string
|
|
||||||
|
|
||||||
Hook(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error)
|
|
||||||
Preview(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error)
|
|
||||||
Publish(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hugo is the Hugo static website generator.
|
// Hugo is the Hugo static website generator.
|
||||||
type Hugo struct {
|
type Hugo struct {
|
||||||
// Website root
|
// Website root
|
||||||
|
@ -66,8 +58,13 @@ func (h Hugo) SettingsPath() string {
|
||||||
return "/config." + frontmatter
|
return "/config." + frontmatter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Name is the plugin's name.
|
||||||
|
func (h Hugo) Name() string {
|
||||||
|
return "hugo"
|
||||||
|
}
|
||||||
|
|
||||||
// Hook is the pre-api handler.
|
// Hook is the pre-api handler.
|
||||||
func (h Hugo) Hook(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
func (h Hugo) Hook(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
// If we are not using HTTP Post, we shall return Method Not Allowed
|
// If we are not using HTTP Post, we shall return Method Not Allowed
|
||||||
// since we are only working with this method.
|
// since we are only working with this method.
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
|
@ -110,7 +107,7 @@ func (h Hugo) Hook(c *RequestContext, w http.ResponseWriter, r *http.Request) (i
|
||||||
}
|
}
|
||||||
|
|
||||||
// Publish publishes a post.
|
// Publish publishes a post.
|
||||||
func (h Hugo) Publish(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
func (h Hugo) Publish(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
filename := filepath.Join(string(c.User.FileSystem), r.URL.Path)
|
filename := filepath.Join(string(c.User.FileSystem), r.URL.Path)
|
||||||
|
|
||||||
// We only run undraft command if it is a file.
|
// We only run undraft command if it is a file.
|
||||||
|
@ -127,7 +124,7 @@ func (h Hugo) Publish(c *RequestContext, w http.ResponseWriter, r *http.Request)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Preview handles the preview path.
|
// Preview handles the preview path.
|
||||||
func (h *Hugo) Preview(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
func (h *Hugo) Preview(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
// Get a new temporary path if there is none.
|
// Get a new temporary path if there is none.
|
||||||
if h.previewPath == "" {
|
if h.previewPath == "" {
|
||||||
path, err := ioutil.TempDir("", "")
|
path, err := ioutil.TempDir("", "")
|
||||||
|
@ -186,7 +183,8 @@ func (h Hugo) undraft(file string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Hugo) find() error {
|
// Setup sets up the plugin.
|
||||||
|
func (h *Hugo) Setup() error {
|
||||||
var err error
|
var err error
|
||||||
if h.Exe, err = exec.LookPath("hugo"); err != nil {
|
if h.Exe, err = exec.LookPath("hugo"); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -194,114 +192,3 @@ func (h *Hugo) find() error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Jekyll is the Jekyll static website generator.
|
|
||||||
type Jekyll struct {
|
|
||||||
// Website root
|
|
||||||
Root string `name:"Website Root"`
|
|
||||||
// Public folder
|
|
||||||
Public string `name:"Public Directory"`
|
|
||||||
// Jekyll executable path
|
|
||||||
Exe string `name:"Executable"`
|
|
||||||
// Jekyll arguments
|
|
||||||
Args []string `name:"Arguments"`
|
|
||||||
// Indicates if we should clean public before a new publish.
|
|
||||||
CleanPublic bool `name:"Clean Public"`
|
|
||||||
// previewPath is the temporary path for a preview
|
|
||||||
previewPath string
|
|
||||||
}
|
|
||||||
|
|
||||||
// SettingsPath retrieves the correct settings path.
|
|
||||||
func (j Jekyll) SettingsPath() string {
|
|
||||||
return "/_config.yml"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hook is the pre-api handler.
|
|
||||||
func (j Jekyll) Hook(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Publish publishes a post.
|
|
||||||
func (j Jekyll) Publish(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
|
||||||
filename := filepath.Join(string(c.User.FileSystem), r.URL.Path)
|
|
||||||
|
|
||||||
// We only run undraft command if it is a file.
|
|
||||||
if err := j.undraft(filename); err != nil {
|
|
||||||
return http.StatusInternalServerError, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Regenerates the file
|
|
||||||
j.run()
|
|
||||||
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Preview handles the preview path.
|
|
||||||
func (j *Jekyll) Preview(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
|
||||||
// Get a new temporary path if there is none.
|
|
||||||
if j.previewPath == "" {
|
|
||||||
path, err := ioutil.TempDir("", "")
|
|
||||||
if err != nil {
|
|
||||||
return http.StatusInternalServerError, err
|
|
||||||
}
|
|
||||||
|
|
||||||
j.previewPath = path
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the arguments to execute Hugo: change the base URL,
|
|
||||||
// build the drafts and update the destination.
|
|
||||||
args := j.Args
|
|
||||||
args = append(args, "--baseurl", c.RootURL()+"/preview/")
|
|
||||||
args = append(args, "--drafts")
|
|
||||||
args = append(args, "--destination", j.previewPath)
|
|
||||||
|
|
||||||
// Builds the preview.
|
|
||||||
if err := runCommand(j.Exe, args, j.Root); err != nil {
|
|
||||||
return http.StatusInternalServerError, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serves the temporary path with the preview.
|
|
||||||
http.FileServer(http.Dir(j.previewPath)).ServeHTTP(w, r)
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j Jekyll) run() {
|
|
||||||
// If the CleanPublic option is enabled, clean it.
|
|
||||||
if j.CleanPublic {
|
|
||||||
os.RemoveAll(j.Public)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := runCommand(j.Exe, j.Args, j.Root); err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j Jekyll) undraft(file string) error {
|
|
||||||
if !strings.Contains(file, "_drafts") {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return os.Rename(file, strings.Replace(file, "_drafts", "_posts", 1))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j *Jekyll) find() error {
|
|
||||||
var err error
|
|
||||||
if j.Exe, err = exec.LookPath("jekyll"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// runCommand executes an external command
|
|
||||||
func runCommand(command string, args []string, path string) error {
|
|
||||||
cmd := exec.Command(command, args...)
|
|
||||||
cmd.Dir = path
|
|
||||||
out, err := cmd.CombinedOutput()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return errors.New(string(out))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
package staticgen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
fm "github.com/hacdias/filemanager"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Jekyll is the Jekyll static website generator.
|
||||||
|
type Jekyll struct {
|
||||||
|
// Website root
|
||||||
|
Root string `name:"Website Root"`
|
||||||
|
// Public folder
|
||||||
|
Public string `name:"Public Directory"`
|
||||||
|
// Jekyll executable path
|
||||||
|
Exe string `name:"Executable"`
|
||||||
|
// Jekyll arguments
|
||||||
|
Args []string `name:"Arguments"`
|
||||||
|
// Indicates if we should clean public before a new publish.
|
||||||
|
CleanPublic bool `name:"Clean Public"`
|
||||||
|
// previewPath is the temporary path for a preview
|
||||||
|
previewPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
// SettingsPath retrieves the correct settings path.
|
||||||
|
func (j Jekyll) SettingsPath() string {
|
||||||
|
return "/_config.yml"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hook is the pre-api handler.
|
||||||
|
func (j Jekyll) Hook(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Publish publishes a post.
|
||||||
|
func (j Jekyll) Publish(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
|
filename := filepath.Join(string(c.User.FileSystem), r.URL.Path)
|
||||||
|
|
||||||
|
// We only run undraft command if it is a file.
|
||||||
|
if err := j.undraft(filename); err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regenerates the file
|
||||||
|
j.run()
|
||||||
|
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preview handles the preview path.
|
||||||
|
func (j *Jekyll) Preview(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
|
// Get a new temporary path if there is none.
|
||||||
|
if j.previewPath == "" {
|
||||||
|
path, err := ioutil.TempDir("", "")
|
||||||
|
if err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
|
||||||
|
j.previewPath = path
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the arguments to execute Hugo: change the base URL,
|
||||||
|
// build the drafts and update the destination.
|
||||||
|
args := j.Args
|
||||||
|
args = append(args, "--baseurl", c.RootURL()+"/preview/")
|
||||||
|
args = append(args, "--drafts")
|
||||||
|
args = append(args, "--destination", j.previewPath)
|
||||||
|
|
||||||
|
// Builds the preview.
|
||||||
|
if err := runCommand(j.Exe, args, j.Root); err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serves the temporary path with the preview.
|
||||||
|
http.FileServer(http.Dir(j.previewPath)).ServeHTTP(w, r)
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j Jekyll) run() {
|
||||||
|
// If the CleanPublic option is enabled, clean it.
|
||||||
|
if j.CleanPublic {
|
||||||
|
os.RemoveAll(j.Public)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := runCommand(j.Exe, j.Args, j.Root); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j Jekyll) undraft(file string) error {
|
||||||
|
if !strings.Contains(file, "_drafts") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.Rename(file, strings.Replace(file, "_drafts", "_posts", 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup sets up the plugin.
|
||||||
|
func (j *Jekyll) Setup() error {
|
||||||
|
var err error
|
||||||
|
if j.Exe, err = exec.LookPath("jekyll"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(j.Args) == 0 {
|
||||||
|
j.Args = []string{"build"}
|
||||||
|
}
|
||||||
|
|
||||||
|
if j.Args[0] != "build" {
|
||||||
|
j.Args = append([]string{"build"}, j.Args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package staticgen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
// runCommand executes an external command
|
||||||
|
func runCommand(command string, args []string, path string) error {
|
||||||
|
cmd := exec.Command(command, args...)
|
||||||
|
cmd.Dir = path
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return errors.New(string(out))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,128 @@
|
||||||
|
package filemanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hacdias/fileutils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultUser is used on New, when no 'base' user is provided.
|
||||||
|
var DefaultUser = User{
|
||||||
|
AllowCommands: true,
|
||||||
|
AllowEdit: true,
|
||||||
|
AllowNew: true,
|
||||||
|
AllowPublish: true,
|
||||||
|
Commands: []string{},
|
||||||
|
Rules: []*Rule{},
|
||||||
|
CSS: "",
|
||||||
|
Admin: true,
|
||||||
|
Locale: "en",
|
||||||
|
FileSystem: fileutils.Dir("."),
|
||||||
|
}
|
||||||
|
|
||||||
|
// User contains the configuration for each user.
|
||||||
|
type User struct {
|
||||||
|
// ID is the required primary key with auto increment0
|
||||||
|
ID int `storm:"id,increment"`
|
||||||
|
|
||||||
|
// Username is the user username used to login.
|
||||||
|
Username string `json:"username" storm:"index,unique"`
|
||||||
|
|
||||||
|
// The hashed password. This never reaches the front-end because it's temporarily
|
||||||
|
// emptied during JSON marshall.
|
||||||
|
Password string `json:"password"`
|
||||||
|
|
||||||
|
// Tells if this user is an admin.
|
||||||
|
Admin bool `json:"admin"`
|
||||||
|
|
||||||
|
// FileSystem is the virtual file system the user has access.
|
||||||
|
FileSystem fileutils.Dir `json:"filesystem"`
|
||||||
|
|
||||||
|
// Rules is an array of access and deny rules.
|
||||||
|
Rules []*Rule `json:"rules"`
|
||||||
|
|
||||||
|
// Custom styles for this user.
|
||||||
|
CSS string `json:"css"`
|
||||||
|
|
||||||
|
// Locale is the language of the user.
|
||||||
|
Locale string `json:"locale"`
|
||||||
|
|
||||||
|
// These indicate if the user can perform certain actions.
|
||||||
|
AllowNew bool `json:"allowNew"` // Create files and folders
|
||||||
|
AllowEdit bool `json:"allowEdit"` // Edit/rename files
|
||||||
|
AllowCommands bool `json:"allowCommands"` // Execute commands
|
||||||
|
AllowPublish bool `json:"allowPublish"` // Publish content (to use with static gen)
|
||||||
|
|
||||||
|
// Commands is the list of commands the user can execute.
|
||||||
|
Commands []string `json:"commands"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rule is a dissalow/allow rule.
|
||||||
|
type Rule struct {
|
||||||
|
// Regex indicates if this rule uses Regular Expressions or not.
|
||||||
|
Regex bool `json:"regex"`
|
||||||
|
|
||||||
|
// Allow indicates if this is an allow rule. Set 'false' to be a disallow rule.
|
||||||
|
Allow bool `json:"allow"`
|
||||||
|
|
||||||
|
// Path is the corresponding URL path for this rule.
|
||||||
|
Path string `json:"path"`
|
||||||
|
|
||||||
|
// Regexp is the regular expression. Only use this when 'Regex' was set to true.
|
||||||
|
Regexp *Regexp `json:"regexp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regexp is a regular expression wrapper around native regexp.
|
||||||
|
type Regexp struct {
|
||||||
|
Raw string `json:"raw"`
|
||||||
|
regexp *regexp.Regexp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allowed checks if the user has permission to access a directory/file.
|
||||||
|
func (u User) Allowed(url string) bool {
|
||||||
|
var rule *Rule
|
||||||
|
i := len(u.Rules) - 1
|
||||||
|
|
||||||
|
for i >= 0 {
|
||||||
|
rule = u.Rules[i]
|
||||||
|
|
||||||
|
if rule.Regex {
|
||||||
|
if rule.Regexp.MatchString(url) {
|
||||||
|
return rule.Allow
|
||||||
|
}
|
||||||
|
} else if strings.HasPrefix(url, rule.Path) {
|
||||||
|
return rule.Allow
|
||||||
|
}
|
||||||
|
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchString checks if this string matches the regular expression.
|
||||||
|
func (r *Regexp) MatchString(s string) bool {
|
||||||
|
if r.regexp == nil {
|
||||||
|
r.regexp = regexp.MustCompile(r.Raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.regexp.MatchString(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
type UsersStore interface {
|
||||||
|
Get(id int) (*User, error)
|
||||||
|
Gets() ([]*User, error)
|
||||||
|
Save(u *User, fields ...string) error
|
||||||
|
Delete(id int) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConfigStore interface {
|
||||||
|
Get(name string, to interface{}) error
|
||||||
|
Save(name string, from interface{}) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type ShareStore interface {
|
||||||
|
Get(hash string)
|
||||||
|
Save()
|
||||||
|
}
|
Loading…
Reference in New Issue