mirror of
https://github.com/mickael-kerjean/filestash.git
synced 2026-10-19 16:14:39 +00:00
chore (refactoring): migrate to server/pkg
This commit is contained in:
@@ -1,15 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
Backend IBackend
|
||||
Body map[string]interface{}
|
||||
Session map[string]string
|
||||
Share Share
|
||||
Context context.Context
|
||||
Authorization string
|
||||
Languages []string
|
||||
}
|
||||
+78
-84
@@ -2,49 +2,91 @@ package common
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
_ "unsafe"
|
||||
|
||||
"github.com/mickael-kerjean/filestash/server/pkg/env"
|
||||
)
|
||||
|
||||
//go:generate go run ../generator/constants.go
|
||||
var (
|
||||
APP_VERSION = "v0.6"
|
||||
COOKIE_NAME_PROOF = "proof"
|
||||
COOKIE_NAME_ADMIN = "admin"
|
||||
COOKIE_PATH_ADMIN = "/admin/api/"
|
||||
COOKIE_PATH = "/api/"
|
||||
URL_SETUP = "/admin/setup"
|
||||
)
|
||||
//go:linkname APP_VERSION github.com/mickael-kerjean/filestash/server/pkg/env.APP_VERSION
|
||||
var APP_VERSION string
|
||||
|
||||
//go:linkname COOKIE_NAME_PROOF github.com/mickael-kerjean/filestash/server/pkg/env.COOKIE_NAME_PROOF
|
||||
var COOKIE_NAME_PROOF string
|
||||
|
||||
//go:linkname COOKIE_NAME_ADMIN github.com/mickael-kerjean/filestash/server/pkg/env.COOKIE_NAME_ADMIN
|
||||
var COOKIE_NAME_ADMIN string
|
||||
|
||||
//go:linkname COOKIE_PATH_ADMIN github.com/mickael-kerjean/filestash/server/pkg/env.COOKIE_PATH_ADMIN
|
||||
var COOKIE_PATH_ADMIN string
|
||||
|
||||
//go:linkname COOKIE_PATH github.com/mickael-kerjean/filestash/server/pkg/env.COOKIE_PATH
|
||||
var COOKIE_PATH string
|
||||
|
||||
//go:linkname URL_SETUP github.com/mickael-kerjean/filestash/server/pkg/env.URL_SETUP
|
||||
var URL_SETUP string
|
||||
|
||||
//go:linkname CONFIG_PATH github.com/mickael-kerjean/filestash/server/pkg/env.CONFIG_PATH
|
||||
var CONFIG_PATH string
|
||||
|
||||
//go:linkname CERT_PATH github.com/mickael-kerjean/filestash/server/pkg/env.CERT_PATH
|
||||
var CERT_PATH string
|
||||
|
||||
//go:linkname PLUGIN_PATH github.com/mickael-kerjean/filestash/server/pkg/env.PLUGIN_PATH
|
||||
var PLUGIN_PATH string
|
||||
|
||||
//go:linkname DB_PATH github.com/mickael-kerjean/filestash/server/pkg/env.DB_PATH
|
||||
var DB_PATH string
|
||||
|
||||
//go:linkname FTS_PATH github.com/mickael-kerjean/filestash/server/pkg/env.FTS_PATH
|
||||
var FTS_PATH string
|
||||
|
||||
//go:linkname LOG_PATH github.com/mickael-kerjean/filestash/server/pkg/env.LOG_PATH
|
||||
var LOG_PATH string
|
||||
|
||||
//go:linkname TMP_PATH github.com/mickael-kerjean/filestash/server/pkg/env.TMP_PATH
|
||||
var TMP_PATH string
|
||||
|
||||
//go:linkname APPNAME github.com/mickael-kerjean/filestash/server/pkg/env.APPNAME
|
||||
var APPNAME string
|
||||
|
||||
//go:linkname BASE github.com/mickael-kerjean/filestash/server/pkg/env.BASE
|
||||
var BASE string
|
||||
|
||||
//go:linkname BUILD_REF github.com/mickael-kerjean/filestash/server/pkg/env.BUILD_REF
|
||||
var BUILD_REF string
|
||||
|
||||
//go:linkname BUILD_DATE github.com/mickael-kerjean/filestash/server/pkg/env.BUILD_DATE
|
||||
var BUILD_DATE string
|
||||
|
||||
//go:linkname LICENSE github.com/mickael-kerjean/filestash/server/pkg/env.LICENSE
|
||||
var LICENSE string
|
||||
|
||||
//go:linkname SECRET_KEY github.com/mickael-kerjean/filestash/server/pkg/env.SECRET_KEY
|
||||
var SECRET_KEY string
|
||||
|
||||
//go:linkname SECRET_KEY_DERIVATE_FOR_PROOF github.com/mickael-kerjean/filestash/server/pkg/env.SECRET_KEY_DERIVATE_FOR_PROOF
|
||||
var SECRET_KEY_DERIVATE_FOR_PROOF string
|
||||
|
||||
//go:linkname SECRET_KEY_DERIVATE_FOR_ADMIN github.com/mickael-kerjean/filestash/server/pkg/env.SECRET_KEY_DERIVATE_FOR_ADMIN
|
||||
var SECRET_KEY_DERIVATE_FOR_ADMIN string
|
||||
|
||||
//go:linkname SECRET_KEY_DERIVATE_FOR_USER github.com/mickael-kerjean/filestash/server/pkg/env.SECRET_KEY_DERIVATE_FOR_USER
|
||||
var SECRET_KEY_DERIVATE_FOR_USER string
|
||||
|
||||
//go:linkname SECRET_KEY_DERIVATE_FOR_HASH github.com/mickael-kerjean/filestash/server/pkg/env.SECRET_KEY_DERIVATE_FOR_HASH
|
||||
var SECRET_KEY_DERIVATE_FOR_HASH string
|
||||
|
||||
//go:linkname SECRET_KEY_DERIVATE_FOR_SIGNATURE github.com/mickael-kerjean/filestash/server/pkg/env.SECRET_KEY_DERIVATE_FOR_SIGNATURE
|
||||
var SECRET_KEY_DERIVATE_FOR_SIGNATURE string
|
||||
|
||||
var (
|
||||
CONFIG_PATH = "state/config/"
|
||||
CERT_PATH = "state/certs/"
|
||||
PLUGIN_PATH = "state/plugins/"
|
||||
DB_PATH = "state/db/"
|
||||
FTS_PATH = "state/search/"
|
||||
LOG_PATH = "state/log/"
|
||||
TMP_PATH = "cache/"
|
||||
WithBase = env.WithBase
|
||||
TrimBase = env.TrimBase
|
||||
IsWhiteLabel = env.IsWhiteLabel
|
||||
WhiteLabelText = env.WhiteLabelText
|
||||
)
|
||||
|
||||
func init() {
|
||||
// STEP1: setup app
|
||||
rootPath := "data/"
|
||||
if p := os.Getenv("FILESTASH_PATH"); p != "" {
|
||||
rootPath = p
|
||||
}
|
||||
LOG_PATH = filepath.Join(rootPath, LOG_PATH)
|
||||
CONFIG_PATH = filepath.Join(rootPath, CONFIG_PATH)
|
||||
DB_PATH = filepath.Join(rootPath, DB_PATH)
|
||||
FTS_PATH = filepath.Join(rootPath, FTS_PATH)
|
||||
CERT_PATH = filepath.Join(rootPath, CERT_PATH)
|
||||
TMP_PATH = filepath.Join(rootPath, TMP_PATH)
|
||||
PLUGIN_PATH = filepath.Join(rootPath, PLUGIN_PATH)
|
||||
BASE = strings.TrimSuffix(os.Getenv("FILESTASH_BASE"), "/")
|
||||
COOKIE_PATH_ADMIN = WithBase(COOKIE_PATH_ADMIN)
|
||||
COOKIE_PATH = WithBase(COOKIE_PATH)
|
||||
URL_SETUP = WithBase(URL_SETUP)
|
||||
|
||||
// STEP2: initialise the config
|
||||
os.MkdirAll(GetAbsolutePath(CERT_PATH), os.ModePerm)
|
||||
os.MkdirAll(GetAbsolutePath(DB_PATH), os.ModePerm)
|
||||
os.MkdirAll(GetAbsolutePath(FTS_PATH), os.ModePerm)
|
||||
@@ -53,51 +95,3 @@ func init() {
|
||||
os.RemoveAll(GetAbsolutePath(TMP_PATH))
|
||||
os.MkdirAll(GetAbsolutePath(TMP_PATH), os.ModePerm)
|
||||
}
|
||||
|
||||
var (
|
||||
APPNAME string = "Filestash"
|
||||
BASE string
|
||||
BUILD_REF string
|
||||
BUILD_DATE string
|
||||
LICENSE string = "agpl"
|
||||
SECRET_KEY string
|
||||
SECRET_KEY_DERIVATE_FOR_PROOF string
|
||||
SECRET_KEY_DERIVATE_FOR_ADMIN string
|
||||
SECRET_KEY_DERIVATE_FOR_USER string
|
||||
SECRET_KEY_DERIVATE_FOR_HASH string
|
||||
SECRET_KEY_DERIVATE_FOR_SIGNATURE string
|
||||
)
|
||||
|
||||
func InitSecretDerivate(secret string) {
|
||||
SECRET_KEY = secret
|
||||
SECRET_KEY_DERIVATE_FOR_PROOF = Hash("PROOF_"+SECRET_KEY, len(SECRET_KEY))
|
||||
SECRET_KEY_DERIVATE_FOR_ADMIN = Hash("ADMIN_"+SECRET_KEY, len(SECRET_KEY))
|
||||
SECRET_KEY_DERIVATE_FOR_USER = Hash("USER_"+SECRET_KEY, len(SECRET_KEY))
|
||||
SECRET_KEY_DERIVATE_FOR_HASH = Hash("HASH_"+SECRET_KEY, len(SECRET_KEY))
|
||||
SECRET_KEY_DERIVATE_FOR_SIGNATURE = Hash("SGN_"+SECRET_KEY, len(SECRET_KEY))
|
||||
}
|
||||
|
||||
func WithBase(href string) string {
|
||||
if BASE == "" {
|
||||
return href
|
||||
}
|
||||
return BASE + href
|
||||
}
|
||||
|
||||
func TrimBase(href string) string {
|
||||
if BASE == "" {
|
||||
return href
|
||||
}
|
||||
return strings.TrimPrefix(href, BASE)
|
||||
}
|
||||
|
||||
func IsWhiteLabel() bool {
|
||||
return APPNAME != "Filestash"
|
||||
}
|
||||
|
||||
func WhiteLabelText(a, b string) string {
|
||||
if IsWhiteLabel() {
|
||||
return b
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
@@ -1,185 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/bmatcuk/doublestar/v4"
|
||||
)
|
||||
|
||||
var MOCK_CURRENT_DIR string
|
||||
|
||||
func GetCurrentDir() string {
|
||||
if MOCK_CURRENT_DIR != "" {
|
||||
return MOCK_CURRENT_DIR
|
||||
}
|
||||
ex, _ := os.Executable()
|
||||
return filepath.Dir(ex)
|
||||
}
|
||||
|
||||
func GetAbsolutePath(base string, opts ...string) string {
|
||||
fullPath := base
|
||||
if strings.HasPrefix(base, "/") == false { // relative filepath are relative to the binary
|
||||
fullPath = filepath.Join(GetCurrentDir(), base)
|
||||
}
|
||||
if len(opts) == 0 {
|
||||
return fullPath
|
||||
}
|
||||
return filepath.Join(append([]string{fullPath}, opts...)...)
|
||||
}
|
||||
|
||||
func IsDirectory(path string) bool {
|
||||
if path == "" {
|
||||
return false
|
||||
}
|
||||
if path[len(path)-1:] != "/" {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/*
|
||||
* Join 2 path together, result has a file
|
||||
*/
|
||||
func JoinPath(base, file string) string {
|
||||
filePath := path.Join(base, file)
|
||||
if strings.HasPrefix(filePath, base) == false {
|
||||
return base
|
||||
}
|
||||
return filePath
|
||||
}
|
||||
|
||||
func EnforceDirectory(path string) string {
|
||||
if path == "" {
|
||||
return "/"
|
||||
} else if path[len(path)-1:] == "/" {
|
||||
return path
|
||||
}
|
||||
return path + "/"
|
||||
}
|
||||
|
||||
func SplitPath(path string) (root string, filename string) {
|
||||
if path == "" {
|
||||
path = "/"
|
||||
}
|
||||
if IsDirectory(path) == false {
|
||||
filename = filepath.Base(path)
|
||||
}
|
||||
if root = strings.TrimSuffix(path, filename); root == "" {
|
||||
root = "/"
|
||||
}
|
||||
return root, filename
|
||||
}
|
||||
|
||||
func SafeOsOpenFile(path string, flag int, perm os.FileMode) (*os.File, error) {
|
||||
if err := safePath(path); err != nil {
|
||||
Log.Debug("common::files safeOsOpenFile err[%s] path[%s]", err.Error(), path)
|
||||
return nil, ErrFilesystemError
|
||||
}
|
||||
if runtime.GOOS == "linux" {
|
||||
flag |= 0x20000 // O_NOFOLLOW ref:/usr/include/asm-generic/fcntl.h
|
||||
}
|
||||
f, err := os.OpenFile(path, flag, perm)
|
||||
if err != nil {
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
return nil, processError(err)
|
||||
}
|
||||
return f, err
|
||||
}
|
||||
|
||||
func SafeOsMkdir(path string, mode os.FileMode) error {
|
||||
if err := safePath(path); err != nil {
|
||||
Log.Debug("common::files safeOsMkdir err[%s] path[%s]", err.Error(), path)
|
||||
return ErrFilesystemError
|
||||
}
|
||||
return processError(os.Mkdir(path, mode))
|
||||
}
|
||||
|
||||
func SafeOsRemove(path string) error {
|
||||
if err := safePath(path); err != nil {
|
||||
Log.Debug("common::files safeOsRemove err[%s] path[%s]", err.Error(), path)
|
||||
return ErrFilesystemError
|
||||
}
|
||||
return processError(os.Remove(path))
|
||||
}
|
||||
|
||||
func SafeOsRemoveAll(path string) error {
|
||||
if err := safePath(path); err != nil {
|
||||
Log.Debug("common::files safeOsRemoveAll err[%s] path[%s]", err.Error(), path)
|
||||
return ErrFilesystemError
|
||||
}
|
||||
return processError(os.RemoveAll(path))
|
||||
}
|
||||
|
||||
func SafeOsRename(from string, to string) error {
|
||||
if err := safePath(from); err != nil {
|
||||
Log.Debug("common::files safeOsRename err[%s] from[%s]", err.Error(), from)
|
||||
return ErrFilesystemError
|
||||
} else if err := safePath(to); err != nil {
|
||||
Log.Debug("common::files safeOsRemove err[%s] to[%s]", err.Error(), to)
|
||||
return ErrFilesystemError
|
||||
}
|
||||
return processError(os.Rename(from, to))
|
||||
}
|
||||
|
||||
func GlobMatch(pattern, name string) bool {
|
||||
m, _ := doublestar.Match(pattern, name)
|
||||
return m
|
||||
}
|
||||
|
||||
func safePath(path string) error {
|
||||
p, err := filepath.EvalSymlinks(path)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) == false {
|
||||
return err
|
||||
}
|
||||
parentPath := filepath.Join(path, "../")
|
||||
return safePath(parentPath)
|
||||
}
|
||||
if p != filepath.Clean(path) {
|
||||
Log.Debug("common::files safePath path[%s] p[%s]", path, p)
|
||||
return ErrFilesystemError
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func processError(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if pe, ok := err.(*os.PathError); ok {
|
||||
return pe.Err
|
||||
}
|
||||
if le, ok := err.(*os.LinkError); ok {
|
||||
return le.Err
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func PathBuilder(ctx *App, path string) (string, error) {
|
||||
if path == "" {
|
||||
return "", NewError("No path available", 400)
|
||||
}
|
||||
chroot := ctx.Session["path"]
|
||||
fullpath := filepath.ToSlash(filepath.Join(chroot, path))
|
||||
if strings.HasSuffix(path, "/") && fullpath != "/" {
|
||||
fullpath += "/"
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(fullpath, EnforceDirectory(chroot)) {
|
||||
if strings.HasSuffix(chroot, "/") {
|
||||
return "", ErrFilesystemError
|
||||
}
|
||||
return chroot, nil
|
||||
} else if !strings.HasSuffix(chroot, "/") && strings.HasSuffix(chroot, path) {
|
||||
return chroot, nil
|
||||
}
|
||||
return fullpath, nil
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
_ "unsafe"
|
||||
|
||||
"github.com/mickael-kerjean/filestash/server/pkg/admin"
|
||||
"github.com/mickael-kerjean/filestash/server/pkg/config"
|
||||
"github.com/mickael-kerjean/filestash/server/pkg/core"
|
||||
"github.com/mickael-kerjean/filestash/server/pkg/kernel"
|
||||
"github.com/mickael-kerjean/filestash/server/pkg/mime"
|
||||
"github.com/mickael-kerjean/filestash/server/pkg/utils"
|
||||
)
|
||||
|
||||
type App = core.App
|
||||
type Share = core.Share
|
||||
type IBackend = core.IBackend
|
||||
type Form = core.Form
|
||||
type FormElement = core.FormElement
|
||||
type ISpan = core.ISpan
|
||||
type AuditQueryResult = core.AuditQueryResult
|
||||
type IFile = core.IFile
|
||||
type File = utils.File
|
||||
type Metadata = core.Metadata
|
||||
type IMetadata = core.IMetadata
|
||||
type IAction = core.IAction
|
||||
type ITrigger = core.ITrigger
|
||||
type ITriggerEvent = core.ITriggerEvent
|
||||
type WorkflowSpecs = core.WorkflowSpecs
|
||||
type IAuthentication = core.IAuthentication
|
||||
type Middleware = core.Middleware
|
||||
type HandlerFunc = core.HandlerFunc
|
||||
|
||||
type AppError = utils.AppError
|
||||
type AppCache = utils.AppCache
|
||||
|
||||
var Backend = kernel.Backend
|
||||
|
||||
// plugin.go
|
||||
var Hooks = kernel.Hooks
|
||||
|
||||
// log.go
|
||||
var (
|
||||
Log = utils.Log
|
||||
NewNilLogger = utils.NewNilLogger
|
||||
)
|
||||
|
||||
// error.go
|
||||
var (
|
||||
NewError = utils.NewError
|
||||
|
||||
ErrNotFound = utils.ErrNotFound
|
||||
ErrNotAllowed = utils.ErrNotAllowed
|
||||
ErrPermissionDenied = utils.ErrPermissionDenied
|
||||
ErrNotValid = utils.ErrNotValid
|
||||
ErrConflict = utils.ErrConflict
|
||||
ErrNotReachable = utils.ErrNotReachable
|
||||
ErrInvalidPassword = utils.ErrInvalidPassword
|
||||
ErrNotImplemented = utils.ErrNotImplemented
|
||||
ErrNotSupported = utils.ErrNotSupported
|
||||
ErrFilesystemError = utils.ErrFilesystemError
|
||||
ErrMissingDependency = utils.ErrMissingDependency
|
||||
ErrNotAuthorized = utils.ErrNotAuthorized
|
||||
ErrAuthenticationFailed = utils.ErrAuthenticationFailed
|
||||
ErrCongestion = utils.ErrCongestion
|
||||
ErrTimeout = utils.ErrTimeout
|
||||
ErrInternal = utils.ErrInternal
|
||||
|
||||
HTTPFriendlyStatus = utils.HTTPFriendlyStatus
|
||||
HTTPError = utils.HTTPError
|
||||
IsATranslatedError = utils.IsATranslatedError
|
||||
)
|
||||
|
||||
// crypto.go
|
||||
var (
|
||||
EncryptString = utils.EncryptString
|
||||
DecryptString = utils.DecryptString
|
||||
RandomString = utils.RandomString
|
||||
DecryptAESGCM = utils.DecryptAESGCM
|
||||
EncryptAESGCM = utils.EncryptAESGCM
|
||||
|
||||
QuickString = utils.QuickString
|
||||
QuickHash = utils.QuickHash
|
||||
HashStream = utils.HashStream
|
||||
Hash = utils.Hash
|
||||
|
||||
GenerateID = utils.GenerateID
|
||||
)
|
||||
|
||||
// mime
|
||||
var (
|
||||
AllMimeTypes = mime.AllMimeTypes
|
||||
GetMimeType = mime.GetMimeType
|
||||
)
|
||||
|
||||
// config
|
||||
var (
|
||||
SaveConfig = config.SaveConfig
|
||||
InitConfig = config.InitConfig
|
||||
)
|
||||
|
||||
// cache.go
|
||||
var (
|
||||
NewAppCache = utils.NewAppCache
|
||||
NewQuickCache = utils.NewQuickCache
|
||||
)
|
||||
|
||||
// http.go
|
||||
var (
|
||||
HTTP = utils.HTTP
|
||||
HTTPClient = utils.HTTPClient
|
||||
WithTrace = utils.WithTrace
|
||||
WithInsecure = utils.WithInsecure
|
||||
WithoutTimeout = utils.WithoutTimeout
|
||||
DefaultTLSConfig = utils.DefaultTLSConfig
|
||||
)
|
||||
|
||||
type HTTPClientOption = utils.HTTPClientOption
|
||||
|
||||
// convert.go
|
||||
var (
|
||||
PrettyPrint = utils.PrettyPrint
|
||||
NewBool = utils.NewBool
|
||||
NewString = utils.NewString
|
||||
NewInt = utils.NewInt
|
||||
NewBoolFromInterface = utils.NewBoolFromInterface
|
||||
NewInt64pFromInterface = utils.NewInt64pFromInterface
|
||||
NewStringpFromInterface = utils.NewStringpFromInterface
|
||||
NewStringFromInterface = utils.NewStringFromInterface
|
||||
NewReadCloserFromBytes = utils.NewReadCloserFromBytes
|
||||
NewReadCloserFromReader = utils.NewReadCloserFromReader
|
||||
MapStringInterfaceToMapStringString = utils.MapStringInterfaceToMapStringString
|
||||
)
|
||||
|
||||
// http.go
|
||||
var (
|
||||
SendSuccessResult = kernel.SendSuccessResult
|
||||
SendSuccessResults = kernel.SendSuccessResults
|
||||
SendSuccessResultWithEtagAndGzip = kernel.SendSuccessResultWithEtagAndGzip
|
||||
SendErrorResult = kernel.SendErrorResult
|
||||
Page = kernel.Page
|
||||
RedirectPage = kernel.RedirectPage
|
||||
)
|
||||
|
||||
// path.go
|
||||
var (
|
||||
GetCurrentDir = utils.GetCurrentDir
|
||||
GetAbsolutePath = utils.GetAbsolutePath
|
||||
IsDirectory = utils.IsDirectory
|
||||
JoinPath = utils.JoinPath
|
||||
EnforceDirectory = utils.EnforceDirectory
|
||||
SplitPath = utils.SplitPath
|
||||
GlobMatch = utils.GlobMatch
|
||||
SafeOsOpenFile = utils.SafeOsOpenFile
|
||||
SafeOsMkdir = utils.SafeOsMkdir
|
||||
SafeOsRemove = utils.SafeOsRemove
|
||||
SafeOsRemoveAll = utils.SafeOsRemoveAll
|
||||
SafeOsRename = utils.SafeOsRename
|
||||
PathBuilder = kernel.PathBuilder
|
||||
)
|
||||
|
||||
// admin.go
|
||||
var NewAdminToken = admin.NewAdminToken
|
||||
|
||||
func InitLogger() error {
|
||||
return utils.InitLogger(GetAbsolutePath(LOG_PATH, "access.log"))
|
||||
}
|
||||
|
||||
//go:linkname Config github.com/mickael-kerjean/filestash/server/pkg/config.Config
|
||||
var Config config.Configuration
|
||||
@@ -1,16 +0,0 @@
|
||||
package ctrl
|
||||
|
||||
import "github.com/mickael-kerjean/filestash/server/pkg/files"
|
||||
|
||||
var (
|
||||
FileLs = files.FileLs
|
||||
FileCat = files.FileCat
|
||||
FileAccess = files.FileAccess
|
||||
FileSave = files.FileSave
|
||||
FileMv = files.FileMv
|
||||
FileRm = files.FileRm
|
||||
FileMkdir = files.FileMkdir
|
||||
FileTouch = files.FileTouch
|
||||
FileDownloader = files.FileDownloader
|
||||
FileExtract = files.FileExtract
|
||||
)
|
||||
@@ -0,0 +1,42 @@
|
||||
package ctrl
|
||||
|
||||
import (
|
||||
"github.com/mickael-kerjean/filestash/server/pkg/admin"
|
||||
"github.com/mickael-kerjean/filestash/server/pkg/files"
|
||||
"github.com/mickael-kerjean/filestash/server/pkg/kernel"
|
||||
)
|
||||
|
||||
// tmpl.go
|
||||
var (
|
||||
TmplExec = kernel.TmplExec
|
||||
TmplParams = kernel.TmplParams
|
||||
)
|
||||
|
||||
// files
|
||||
var (
|
||||
FileLs = files.FileLs
|
||||
FileCat = files.FileCat
|
||||
FileAccess = files.FileAccess
|
||||
FileSave = files.FileSave
|
||||
FileMv = files.FileMv
|
||||
FileRm = files.FileRm
|
||||
FileMkdir = files.FileMkdir
|
||||
FileTouch = files.FileTouch
|
||||
FileDownloader = files.FileDownloader
|
||||
FileExtract = files.FileExtract
|
||||
|
||||
FileSearch = files.FileSearch
|
||||
MetaSearch = files.MetaSearch
|
||||
MetaGet = files.MetaGet
|
||||
MetaUpsert = files.MetaUpsert
|
||||
)
|
||||
|
||||
// admin
|
||||
var (
|
||||
AdminSessionGet = admin.AdminSessionGet
|
||||
AdminSessionAuthenticate = admin.AdminSessionAuthenticate
|
||||
FetchAuditHandler = admin.FetchAuditHandler
|
||||
FetchLogHandler = admin.FetchLogHandler
|
||||
AdminBackend = admin.AdminBackend
|
||||
AdminAuthenticationMiddleware = admin.AdminAuthenticationMiddleware
|
||||
)
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/mickael-kerjean/filestash/server/model"
|
||||
"github.com/mickael-kerjean/filestash/server/pkg/cookie"
|
||||
"github.com/mickael-kerjean/filestash/server/pkg/token"
|
||||
"github.com/mickael-kerjean/filestash/server/pkg/utils"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
@@ -53,7 +54,7 @@ func SessionGet(ctx *App, res http.ResponseWriter, req *http.Request) {
|
||||
|
||||
func SessionAuthenticate(ctx *App, res http.ResponseWriter, req *http.Request) {
|
||||
ctx.Body["timestamp"] = time.Now().Format(time.RFC3339)
|
||||
session := model.MapStringInterfaceToMapStringString(ctx.Body)
|
||||
session := utils.MapStringInterfaceToMapStringString(ctx.Body)
|
||||
session["path"] = EnforceDirectory(session["path"])
|
||||
|
||||
backend, err := model.NewBackend(ctx, session)
|
||||
@@ -72,7 +73,7 @@ func SessionAuthenticate(ctx *App, res http.ResponseWriter, req *http.Request) {
|
||||
SendErrorResult(res, NewError("Can't authenticate (OAuth error)", 401))
|
||||
return
|
||||
}
|
||||
session = model.MapStringInterfaceToMapStringString(ctx.Body)
|
||||
session = utils.MapStringInterfaceToMapStringString(ctx.Body)
|
||||
backend, err = model.NewBackend(ctx, session)
|
||||
if err != nil {
|
||||
Log.Debug("[auth] action=authenticate::oauth::newBackend err=%s", ferror(err))
|
||||
|
||||
@@ -6,28 +6,15 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cmd, b := exec.Command("git", "rev-parse", "HEAD"), new(strings.Builder)
|
||||
cmd.Stdout = b
|
||||
cmd.Run()
|
||||
|
||||
f, err := os.OpenFile("../common/constants_generated.go", os.O_CREATE|os.O_WRONLY, os.ModePerm)
|
||||
if err != nil {
|
||||
cmd := exec.Command("go", "run", "generator.go")
|
||||
cmd.Dir = "../pkg/env"
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
return
|
||||
}
|
||||
f.Write([]byte(fmt.Sprintf(`
|
||||
package common
|
||||
|
||||
func init() {
|
||||
BUILD_REF = "%s"
|
||||
BUILD_DATE = "%s"
|
||||
}
|
||||
`, strings.TrimSpace(b.String()), time.Now().Format("20060102"))))
|
||||
f.Close()
|
||||
}
|
||||
|
||||
@@ -3,45 +3,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"go/format"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
func main() {
|
||||
f, err := os.OpenFile("../../config/mime.json", os.O_RDONLY, os.ModePerm)
|
||||
if err != nil {
|
||||
cmd := exec.Command("go", "run", "generator.go")
|
||||
cmd.Dir = "../pkg/mime"
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
j, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
mTypes := make(map[string]string, 0)
|
||||
json.Unmarshal(j, &mTypes)
|
||||
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString("package common\n")
|
||||
buf.WriteString("func init() {\n")
|
||||
for key, value := range mTypes {
|
||||
fmt.Fprintf(&buf, "MimeTypes[\"%s\"] = \"%s\"\n", key, value)
|
||||
}
|
||||
buf.WriteString("}\n")
|
||||
formatted, err := format.Source(buf.Bytes())
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error formatting: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err = os.WriteFile("../common/mime_generated.go", formatted, 0644); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error writing file: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
+17
-77
@@ -1,81 +1,21 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
_ "unsafe"
|
||||
import "github.com/mickael-kerjean/filestash/server/pkg/middleware"
|
||||
|
||||
. "github.com/mickael-kerjean/filestash/server/common"
|
||||
var (
|
||||
NewMiddlewareChain = middleware.NewMiddlewareChain
|
||||
IndexHeaders = middleware.IndexHeaders
|
||||
StaticHeaders = middleware.StaticHeaders
|
||||
ApiHeaders = middleware.ApiHeaders
|
||||
PublicCORS = middleware.PublicCORS
|
||||
SecureHeaders = middleware.SecureHeaders
|
||||
SecureOrigin = middleware.SecureOrigin
|
||||
LoggedInOnly = middleware.LoggedInOnly
|
||||
AdminOnly = middleware.AdminOnly
|
||||
SessionTry = middleware.SessionTry
|
||||
SessionStart = middleware.SessionStart
|
||||
BodyParser = middleware.BodyParser
|
||||
CanManageShare = middleware.CanManageShare
|
||||
PluginInjector = middleware.PluginInjector
|
||||
RateLimiter = middleware.RateLimiter
|
||||
)
|
||||
|
||||
func init() {
|
||||
Hooks.Register.Onload(func() {
|
||||
go func() {
|
||||
for {
|
||||
time.Sleep(10 * time.Second)
|
||||
telemetry.Flush()
|
||||
}
|
||||
}()
|
||||
})
|
||||
}
|
||||
|
||||
func NewMiddlewareChain(fn HandlerFunc, m []Middleware) http.HandlerFunc {
|
||||
return func(res http.ResponseWriter, req *http.Request) {
|
||||
var f func(*App, http.ResponseWriter, *http.Request) = fn
|
||||
for i := len(m) - 1; i >= 0; i-- {
|
||||
f = m[i](f)
|
||||
}
|
||||
var (
|
||||
app = App{Context: req.Context()}
|
||||
resw = NewResponseWriter(res)
|
||||
)
|
||||
f(&app, &resw, req)
|
||||
if req.Body != nil {
|
||||
req.Body.Close()
|
||||
}
|
||||
logger(&app, &resw, req)
|
||||
}
|
||||
}
|
||||
|
||||
type ResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
status int
|
||||
start int64
|
||||
}
|
||||
|
||||
func NewResponseWriter(res http.ResponseWriter) ResponseWriter {
|
||||
return ResponseWriter{
|
||||
ResponseWriter: res,
|
||||
start: now(),
|
||||
}
|
||||
}
|
||||
|
||||
func (w *ResponseWriter) WriteHeader(status int) {
|
||||
w.status = status
|
||||
w.ResponseWriter.WriteHeader(status)
|
||||
}
|
||||
|
||||
func (w *ResponseWriter) Write(b []byte) (int, error) {
|
||||
if w.status == 0 {
|
||||
w.status = 200
|
||||
}
|
||||
return w.ResponseWriter.Write(b)
|
||||
}
|
||||
|
||||
func (w *ResponseWriter) Status() int {
|
||||
return w.status
|
||||
}
|
||||
|
||||
func (w *ResponseWriter) Flush() {
|
||||
w.ResponseWriter.(http.Flusher).Flush()
|
||||
}
|
||||
|
||||
func PluginInjector(fn HandlerFunc) HandlerFunc {
|
||||
for _, middleware := range Hooks.Get.Middleware() {
|
||||
fn = middleware(fn)
|
||||
}
|
||||
return fn
|
||||
}
|
||||
|
||||
//go:linkname now runtime.nanotime
|
||||
func now() int64
|
||||
|
||||
+8
-40
@@ -1,46 +1,14 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
. "github.com/mickael-kerjean/filestash/server/common"
|
||||
_ "github.com/mickael-kerjean/filestash/server/pkg/admin"
|
||||
"github.com/mickael-kerjean/filestash/server/pkg/files"
|
||||
"github.com/mickael-kerjean/filestash/server/pkg/sqlite"
|
||||
)
|
||||
|
||||
var DB *sql.DB
|
||||
var DB = sqlite.DB
|
||||
|
||||
func init() {
|
||||
Hooks.Register.Onload(func() {
|
||||
var err error
|
||||
if DB, err = sql.Open("sqlite3", GetAbsolutePath(DB_PATH)+"/share.sql?_fk=true"); err != nil {
|
||||
Log.Error("model::index sqlite open error '%s'", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if stmt, err := DB.Prepare("CREATE TABLE IF NOT EXISTS Location(backend VARCHAR(16), path VARCHAR(512), CONSTRAINT pk_location PRIMARY KEY(backend, path))"); err == nil {
|
||||
stmt.Exec()
|
||||
}
|
||||
|
||||
if stmt, err := DB.Prepare("CREATE TABLE IF NOT EXISTS Share(id VARCHAR(64) PRIMARY KEY, related_backend VARCHAR(16), related_path VARCHAR(512), params JSON, auth VARCHAR(4093) NOT NULL, FOREIGN KEY (related_backend, related_path) REFERENCES Location(backend, path) ON UPDATE CASCADE ON DELETE CASCADE)"); err == nil {
|
||||
stmt.Exec()
|
||||
}
|
||||
|
||||
if stmt, err := DB.Prepare("CREATE TABLE IF NOT EXISTS Verification(key VARCHAR(512), code VARCHAR(4), expire DATETIME DEFAULT (datetime('now', '+10 minutes')))"); err == nil {
|
||||
stmt.Exec()
|
||||
if stmt, err = DB.Prepare("CREATE INDEX idx_verification ON Verification(code, expire)"); err == nil {
|
||||
stmt.Exec()
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
autovacuum()
|
||||
}()
|
||||
})
|
||||
}
|
||||
|
||||
func autovacuum() {
|
||||
if stmt, err := DB.Prepare("DELETE FROM Verification WHERE expire < datetime('now')"); err == nil {
|
||||
stmt.Exec()
|
||||
}
|
||||
time.Sleep(6 * time.Hour)
|
||||
}
|
||||
var (
|
||||
NewBackend = files.NewBackend
|
||||
GetHome = files.GetHome
|
||||
)
|
||||
|
||||
@@ -5,16 +5,17 @@ import (
|
||||
"crypto/tls"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
. "github.com/mickael-kerjean/filestash/server/common"
|
||||
"github.com/mickael-kerjean/filestash/server/pkg/sqlite"
|
||||
"github.com/mickael-kerjean/filestash/server/pkg/utils"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gopkg.in/gomail.v2"
|
||||
"html/template"
|
||||
)
|
||||
|
||||
type Proof struct {
|
||||
@@ -84,7 +85,7 @@ func ShareGet(id string) (Share, error) {
|
||||
|
||||
func ShareUpsert(p *Share) error {
|
||||
if p.Password != nil {
|
||||
if *p.Password == PASSWORD_DUMMY {
|
||||
if *p.Password == utils.PASSWORD_DUMMY {
|
||||
if s, err := ShareGet(p.Id); err != nil {
|
||||
p.Password = s.Password
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package model
|
||||
package admin
|
||||
|
||||
import (
|
||||
. "github.com/mickael-kerjean/filestash/server/common"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/core"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/kernel"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -1,17 +1,21 @@
|
||||
package ctrl
|
||||
package admin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
. "github.com/mickael-kerjean/filestash/server/common"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
var logpath = GetAbsolutePath(LOG_PATH, "access.log")
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/config"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/core"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/env"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/kernel"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/utils"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func AdminSessionGet(ctx *App, res http.ResponseWriter, req *http.Request) {
|
||||
if admin := Config.Get("auth.admin").String(); admin == "" {
|
||||
@@ -102,7 +106,7 @@ func AdminAuthenticationMiddleware(ctx *App, res http.ResponseWriter, req *http.
|
||||
}
|
||||
|
||||
func FetchLogHandler(ctx *App, res http.ResponseWriter, req *http.Request) {
|
||||
file, err := os.OpenFile(logpath, os.O_RDONLY, os.ModePerm)
|
||||
file, err := os.OpenFile(GetAbsolutePath(LOG_PATH, "access.log"), os.O_RDONLY, os.ModePerm)
|
||||
if err != nil {
|
||||
SendErrorResult(res, err)
|
||||
return
|
||||
@@ -1,4 +1,4 @@
|
||||
package common
|
||||
package admin
|
||||
|
||||
import (
|
||||
"time"
|
||||
@@ -1,4 +1,4 @@
|
||||
package common
|
||||
package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -8,6 +8,10 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/core"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/env"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/utils"
|
||||
)
|
||||
|
||||
var Config = NewConfiguration()
|
||||
@@ -27,30 +31,6 @@ type ConfigElement struct {
|
||||
cfg *Configuration
|
||||
}
|
||||
|
||||
type Form struct {
|
||||
Title string
|
||||
Form []Form
|
||||
Elmnts []FormElement
|
||||
}
|
||||
|
||||
type FormElement struct {
|
||||
Id string `json:"id,omitempty"`
|
||||
Name string `json:"label"`
|
||||
Type string `json:"type"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Placeholder string `json:"placeholder,omitempty"`
|
||||
Pattern string `json:"pattern,omitempty"`
|
||||
Opts []string `json:"options,omitempty"`
|
||||
Target []string `json:"target,omitempty"`
|
||||
ReadOnly bool `json:"readonly"`
|
||||
Default interface{} `json:"default"`
|
||||
Value interface{} `json:"value"`
|
||||
MultiValue bool `json:"multi,omitempty"`
|
||||
Datalist []string `json:"datalist,omitempty"`
|
||||
Order int `json:"-"`
|
||||
Required bool `json:"required"`
|
||||
}
|
||||
|
||||
func InitConfig() error {
|
||||
Config = NewConfiguration()
|
||||
if err := Config.Load(); err != nil {
|
||||
@@ -149,47 +129,6 @@ func (this *Configuration) Connections() []map[string]any {
|
||||
return this.state.Load().conn
|
||||
}
|
||||
|
||||
func (this Form) MarshalJSON() ([]byte, error) {
|
||||
return formToJSON(this, func(el FormElement) any { return el })
|
||||
}
|
||||
|
||||
func formToJSON(f Form, fn func(FormElement) any) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteByte('{')
|
||||
first := true
|
||||
for _, el := range f.Elmnts {
|
||||
v := fn(el)
|
||||
if v == nil {
|
||||
continue
|
||||
}
|
||||
if !first {
|
||||
buf.WriteByte(',')
|
||||
}
|
||||
first = false
|
||||
key, _ := json.Marshal(strings.ReplaceAll(el.Name, " ", "_"))
|
||||
val, _ := json.Marshal(v)
|
||||
buf.Write(key)
|
||||
buf.WriteByte(':')
|
||||
buf.Write(val)
|
||||
}
|
||||
for _, sub := range f.Form {
|
||||
subBytes, _ := formToJSON(sub, fn)
|
||||
if bytes.Equal(subBytes, []byte("{}")) {
|
||||
continue
|
||||
}
|
||||
if !first {
|
||||
buf.WriteByte(',')
|
||||
}
|
||||
first = false
|
||||
key, _ := json.Marshal(strings.ReplaceAll(sub.Title, " ", "_"))
|
||||
buf.Write(key)
|
||||
buf.WriteByte(':')
|
||||
buf.Write(subBytes)
|
||||
}
|
||||
buf.WriteByte('}')
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (this *Configuration) Load() error {
|
||||
cFile, err := LoadConfig()
|
||||
if err != nil {
|
||||
@@ -225,7 +164,7 @@ func (this *Configuration) Load() error {
|
||||
}
|
||||
|
||||
Log.SetVisibility(this.Get("log.level").String())
|
||||
for _, fn := range Hooks.Get.OnConfig() {
|
||||
for _, fn := range changeListeners {
|
||||
fn()
|
||||
}
|
||||
return nil
|
||||
@@ -274,7 +213,7 @@ func (this *Configuration) Initialise() {
|
||||
|
||||
func (this *Configuration) Save() {
|
||||
s := this.state.Load()
|
||||
formBytes, err := formToJSON(Form{Form: s.forms}, func(el FormElement) any { return el.Value })
|
||||
formBytes, err := FormToJSON(Form{Form: s.forms}, func(el FormElement) any { return el.Value })
|
||||
conn, _ := json.Marshal(s.conn)
|
||||
if err != nil {
|
||||
Log.Error("config::save marshal %s", err.Error())
|
||||
@@ -297,7 +236,18 @@ func (this *Configuration) Save() {
|
||||
|
||||
func (this *Configuration) Get(key string) ConfigElement {
|
||||
s := this.state.Load()
|
||||
return ConfigElement{currentElement: find(&s.forms, key, false), key: key, cfg: this}
|
||||
el := find(&s.forms, key, false)
|
||||
if el == nil {
|
||||
for {
|
||||
old := this.state.Load()
|
||||
next := &configState{forms: cloneForms(old.forms), conn: old.conn}
|
||||
el = find(&next.forms, key, true)
|
||||
if this.state.CompareAndSwap(old, next) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return ConfigElement{currentElement: el, key: key, cfg: this}
|
||||
}
|
||||
|
||||
func (this ConfigElement) Schema(fn func(*FormElement) *FormElement) ConfigElement {
|
||||
@@ -494,3 +444,9 @@ func find(forms *[]Form, key string, create bool) *FormElement {
|
||||
start = end + 1
|
||||
}
|
||||
}
|
||||
|
||||
var changeListeners []func()
|
||||
|
||||
func RegisterChange(fn func()) {
|
||||
changeListeners = append(changeListeners, fn)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package common
|
||||
package config
|
||||
|
||||
/*
|
||||
* WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNI
|
||||
@@ -15,10 +15,14 @@ package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/tidwall/gjson"
|
||||
"github.com/tidwall/sjson"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/env"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/utils"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
"github.com/tidwall/sjson"
|
||||
)
|
||||
|
||||
var configKeysToEncrypt []string = []string{
|
||||
@@ -26,6 +30,15 @@ var configKeysToEncrypt []string = []string{
|
||||
"middleware.attribute_mapping.params",
|
||||
}
|
||||
|
||||
func InitSecretDerivate(secret string) {
|
||||
SECRET_KEY = secret
|
||||
SECRET_KEY_DERIVATE_FOR_PROOF = Hash("PROOF_"+SECRET_KEY, len(SECRET_KEY))
|
||||
SECRET_KEY_DERIVATE_FOR_ADMIN = Hash("ADMIN_"+SECRET_KEY, len(SECRET_KEY))
|
||||
SECRET_KEY_DERIVATE_FOR_USER = Hash("USER_"+SECRET_KEY, len(SECRET_KEY))
|
||||
SECRET_KEY_DERIVATE_FOR_HASH = Hash("HASH_"+SECRET_KEY, len(SECRET_KEY))
|
||||
SECRET_KEY_DERIVATE_FOR_SIGNATURE = Hash("SGN_"+SECRET_KEY, len(SECRET_KEY))
|
||||
}
|
||||
|
||||
func LoadConfig() ([]byte, error) {
|
||||
file, err := os.OpenFile(GetAbsolutePath(CONFIG_PATH, "config.json"), os.O_RDONLY, os.ModePerm)
|
||||
if err != nil {
|
||||
@@ -111,14 +124,3 @@ func SaveConfig(v []byte) error {
|
||||
}
|
||||
return file.Close()
|
||||
}
|
||||
|
||||
func init() {
|
||||
Hooks.Register.Onload(func() {
|
||||
if err := os.Chmod(GetAbsolutePath(CONFIG_PATH), 0770); err != nil && os.IsNotExist(err) == false {
|
||||
Log.Warning("common::config_state::onload cannot chmod config directory: %s", err.Error())
|
||||
}
|
||||
if err := os.Chmod(GetAbsolutePath(CONFIG_PATH, "config.json"), 0660); err != nil && os.IsNotExist(err) == false {
|
||||
Log.Warning("common::config_state::onload cannot chmod config file: %s", err.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Form struct {
|
||||
Title string
|
||||
Form []Form
|
||||
Elmnts []FormElement
|
||||
}
|
||||
|
||||
type FormElement struct {
|
||||
Id string `json:"id,omitempty"`
|
||||
Name string `json:"label"`
|
||||
Type string `json:"type"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Placeholder string `json:"placeholder,omitempty"`
|
||||
Pattern string `json:"pattern,omitempty"`
|
||||
Opts []string `json:"options,omitempty"`
|
||||
Target []string `json:"target,omitempty"`
|
||||
ReadOnly bool `json:"readonly"`
|
||||
Default interface{} `json:"default"`
|
||||
Value interface{} `json:"value"`
|
||||
MultiValue bool `json:"multi,omitempty"`
|
||||
Datalist []string `json:"datalist,omitempty"`
|
||||
Order int `json:"-"`
|
||||
Required bool `json:"required"`
|
||||
}
|
||||
|
||||
func (this Form) MarshalJSON() ([]byte, error) {
|
||||
return FormToJSON(this, func(el FormElement) any { return el })
|
||||
}
|
||||
|
||||
func FormToJSON(f Form, fn func(FormElement) any) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteByte('{')
|
||||
first := true
|
||||
for _, el := range f.Elmnts {
|
||||
v := fn(el)
|
||||
if v == nil {
|
||||
continue
|
||||
}
|
||||
if !first {
|
||||
buf.WriteByte(',')
|
||||
}
|
||||
first = false
|
||||
key, _ := json.Marshal(strings.ReplaceAll(el.Name, " ", "_"))
|
||||
val, _ := json.Marshal(v)
|
||||
buf.Write(key)
|
||||
buf.WriteByte(':')
|
||||
buf.Write(val)
|
||||
}
|
||||
for _, sub := range f.Form {
|
||||
subBytes, _ := FormToJSON(sub, fn)
|
||||
if bytes.Equal(subBytes, []byte("{}")) {
|
||||
continue
|
||||
}
|
||||
if !first {
|
||||
buf.WriteByte(',')
|
||||
}
|
||||
first = false
|
||||
key, _ := json.Marshal(strings.ReplaceAll(sub.Title, " ", "_"))
|
||||
buf.Write(key)
|
||||
buf.WriteByte(':')
|
||||
buf.Write(subBytes)
|
||||
}
|
||||
buf.WriteByte('}')
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/mickael-kerjean/filestash/server/pkg/utils"
|
||||
)
|
||||
|
||||
type Share struct {
|
||||
Id string `json:"id"`
|
||||
Backend string `json:"-"`
|
||||
Auth string `json:"auth,omitempty"`
|
||||
Path string `json:"path"`
|
||||
Password *string `json:"password,omitempty"`
|
||||
Users *string `json:"users,omitempty"`
|
||||
Expire *int64 `json:"expire,omitempty"`
|
||||
Url *string `json:"url,omitempty"`
|
||||
CanShare bool `json:"can_share"`
|
||||
CanManageOwn bool `json:"can_manage_own"`
|
||||
CanRead bool `json:"can_read"`
|
||||
CanWrite bool `json:"can_write"`
|
||||
CanUpload bool `json:"can_upload"`
|
||||
}
|
||||
|
||||
func (s Share) IsValid() error {
|
||||
if s.Expire != nil {
|
||||
now := time.Now().UnixNano() / 1000000
|
||||
if now > *s.Expire {
|
||||
return utils.NewError("Link has expired", 410)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Share) MarshalJSON() ([]byte, error) {
|
||||
p := Share{
|
||||
s.Id,
|
||||
s.Backend,
|
||||
"",
|
||||
s.Path,
|
||||
func(pass *string) *string {
|
||||
if pass != nil {
|
||||
return utils.NewString(utils.PASSWORD_DUMMY)
|
||||
}
|
||||
return nil
|
||||
}(s.Password),
|
||||
s.Users,
|
||||
s.Expire,
|
||||
s.Url,
|
||||
s.CanShare,
|
||||
s.CanManageOwn,
|
||||
s.CanRead,
|
||||
s.CanWrite,
|
||||
s.CanUpload,
|
||||
}
|
||||
return json.Marshal(p)
|
||||
}
|
||||
func (s *Share) UnmarshallJSON(b []byte) error {
|
||||
var tmp map[string]interface{}
|
||||
if err := json.Unmarshal(b, &tmp); err != nil {
|
||||
return err
|
||||
}
|
||||
for key, value := range tmp {
|
||||
switch key {
|
||||
case "password":
|
||||
s.Password = utils.NewStringpFromInterface(value)
|
||||
case "users":
|
||||
s.Users = utils.NewStringpFromInterface(value)
|
||||
case "expire":
|
||||
s.Expire = utils.NewInt64pFromInterface(value)
|
||||
case "url":
|
||||
s.Url = utils.NewStringpFromInterface(value)
|
||||
case "can_share":
|
||||
s.CanShare = utils.NewBoolFromInterface(value)
|
||||
case "can_manage_own":
|
||||
s.CanManageOwn = utils.NewBoolFromInterface(value)
|
||||
case "can_read":
|
||||
s.CanRead = utils.NewBoolFromInterface(value)
|
||||
case "can_write":
|
||||
s.CanWrite = utils.NewBoolFromInterface(value)
|
||||
case "can_upload":
|
||||
s.CanUpload = utils.NewBoolFromInterface(value)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package common
|
||||
package core
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -10,6 +10,16 @@ import (
|
||||
"github.com/mickael-kerjean/filestash/server/pkg/tracer"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
Backend IBackend
|
||||
Body map[string]interface{}
|
||||
Session map[string]string
|
||||
Share Share
|
||||
Context context.Context
|
||||
Authorization string
|
||||
Languages []string
|
||||
}
|
||||
|
||||
type IBackend interface {
|
||||
Init(params map[string]string, app *App) (IBackend, error)
|
||||
Ls(path string) ([]os.FileInfo, error)
|
||||
@@ -73,12 +83,6 @@ type AuditQueryResult struct {
|
||||
type ITracer = tracer.ITracer
|
||||
type ISpan = tracer.ISpan
|
||||
|
||||
const (
|
||||
MetaModeTag = 1 << iota
|
||||
MetaModeBookmark
|
||||
MetaModeForm
|
||||
)
|
||||
|
||||
type IMetadata interface {
|
||||
Get(ctx *App, path string) ([]FormElement, error)
|
||||
Set(ctx *App, path string, value []FormElement) error
|
||||
@@ -119,49 +123,6 @@ type WorkflowSpecs struct {
|
||||
Order int `json:"-"`
|
||||
}
|
||||
|
||||
type File struct {
|
||||
FName string `json:"name"`
|
||||
FType string `json:"type"`
|
||||
FTime int64 `json:"time"`
|
||||
FSize int64 `json:"size"`
|
||||
FPath string `json:"path,omitempty"`
|
||||
Offline bool `json:"offline,omitempty"`
|
||||
Metadata any `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
func (f File) Name() string {
|
||||
return f.FName
|
||||
}
|
||||
func (f File) Size() int64 {
|
||||
return f.FSize
|
||||
}
|
||||
func (f File) Mode() os.FileMode {
|
||||
if f.IsDir() {
|
||||
return os.ModeDir
|
||||
}
|
||||
return os.FileMode(0664)
|
||||
}
|
||||
func (f File) ModTime() time.Time {
|
||||
if f.FTime == 0 {
|
||||
t := new(time.Time)
|
||||
return *t
|
||||
}
|
||||
return time.Unix(f.FTime, 0)
|
||||
}
|
||||
func (f File) IsDir() bool {
|
||||
if f.FType != "directory" {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
func (f File) Sys() interface{} {
|
||||
return f
|
||||
}
|
||||
|
||||
func (f File) Path() string {
|
||||
return f.FPath
|
||||
}
|
||||
|
||||
type Metadata struct {
|
||||
CanSee *bool `json:"can_read,omitempty"`
|
||||
CanCreateFile *bool `json:"can_create_file,omitempty"`
|
||||
@@ -176,87 +137,6 @@ type Metadata struct {
|
||||
Expire *time.Time `json:"-"`
|
||||
}
|
||||
|
||||
const PASSWORD_DUMMY = "{{PASSWORD}}"
|
||||
|
||||
type Share struct {
|
||||
Id string `json:"id"`
|
||||
Backend string `json:"-"`
|
||||
Auth string `json:"auth,omitempty"`
|
||||
Path string `json:"path"`
|
||||
Password *string `json:"password,omitempty"`
|
||||
Users *string `json:"users,omitempty"`
|
||||
Expire *int64 `json:"expire,omitempty"`
|
||||
Url *string `json:"url,omitempty"`
|
||||
CanShare bool `json:"can_share"`
|
||||
CanManageOwn bool `json:"can_manage_own"`
|
||||
CanRead bool `json:"can_read"`
|
||||
CanWrite bool `json:"can_write"`
|
||||
CanUpload bool `json:"can_upload"`
|
||||
}
|
||||
|
||||
func (s Share) IsValid() error {
|
||||
if s.Expire != nil {
|
||||
now := time.Now().UnixNano() / 1000000
|
||||
if now > *s.Expire {
|
||||
return NewError("Link has expired", 410)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Share) MarshalJSON() ([]byte, error) {
|
||||
p := Share{
|
||||
s.Id,
|
||||
s.Backend,
|
||||
"",
|
||||
s.Path,
|
||||
func(pass *string) *string {
|
||||
if pass != nil {
|
||||
return NewString(PASSWORD_DUMMY)
|
||||
}
|
||||
return nil
|
||||
}(s.Password),
|
||||
s.Users,
|
||||
s.Expire,
|
||||
s.Url,
|
||||
s.CanShare,
|
||||
s.CanManageOwn,
|
||||
s.CanRead,
|
||||
s.CanWrite,
|
||||
s.CanUpload,
|
||||
}
|
||||
return json.Marshal(p)
|
||||
}
|
||||
func (s *Share) UnmarshallJSON(b []byte) error {
|
||||
var tmp map[string]interface{}
|
||||
if err := json.Unmarshal(b, &tmp); err != nil {
|
||||
return err
|
||||
}
|
||||
for key, value := range tmp {
|
||||
switch key {
|
||||
case "password":
|
||||
s.Password = NewStringpFromInterface(value)
|
||||
case "users":
|
||||
s.Users = NewStringpFromInterface(value)
|
||||
case "expire":
|
||||
s.Expire = NewInt64pFromInterface(value)
|
||||
case "url":
|
||||
s.Url = NewStringpFromInterface(value)
|
||||
case "can_share":
|
||||
s.CanShare = NewBoolFromInterface(value)
|
||||
case "can_manage_own":
|
||||
s.CanManageOwn = NewBoolFromInterface(value)
|
||||
case "can_read":
|
||||
s.CanRead = NewBoolFromInterface(value)
|
||||
case "can_write":
|
||||
s.CanWrite = NewBoolFromInterface(value)
|
||||
case "can_upload":
|
||||
s.CanUpload = NewBoolFromInterface(value)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type HandlerFunc func(*App, http.ResponseWriter, *http.Request)
|
||||
|
||||
type Middleware func(HandlerFunc) HandlerFunc
|
||||
Vendored
+63
@@ -0,0 +1,63 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//go:generate go run generator.go
|
||||
var (
|
||||
APP_VERSION string = "v0.6"
|
||||
APPNAME string = "Filestash"
|
||||
BASE string
|
||||
BUILD_REF string
|
||||
BUILD_DATE string
|
||||
LICENSE string = "agpl"
|
||||
)
|
||||
|
||||
var (
|
||||
SECRET_KEY string
|
||||
SECRET_KEY_DERIVATE_FOR_PROOF string
|
||||
SECRET_KEY_DERIVATE_FOR_ADMIN string
|
||||
SECRET_KEY_DERIVATE_FOR_USER string
|
||||
SECRET_KEY_DERIVATE_FOR_HASH string
|
||||
SECRET_KEY_DERIVATE_FOR_SIGNATURE string
|
||||
)
|
||||
|
||||
var (
|
||||
CONFIG_PATH = "state/config/"
|
||||
CERT_PATH = "state/certs/"
|
||||
PLUGIN_PATH = "state/plugins/"
|
||||
DB_PATH = "state/db/"
|
||||
FTS_PATH = "state/search/"
|
||||
LOG_PATH = "state/log/"
|
||||
TMP_PATH = "cache/"
|
||||
)
|
||||
|
||||
var (
|
||||
COOKIE_NAME_PROOF = "proof"
|
||||
COOKIE_NAME_ADMIN = "admin"
|
||||
COOKIE_PATH_ADMIN = "/admin/api/"
|
||||
COOKIE_PATH = "/api/"
|
||||
URL_SETUP = "/admin/setup"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// STEP1: setup app
|
||||
rootPath := "data/"
|
||||
if p := os.Getenv("FILESTASH_PATH"); p != "" {
|
||||
rootPath = p
|
||||
}
|
||||
LOG_PATH = filepath.Join(rootPath, LOG_PATH)
|
||||
CONFIG_PATH = filepath.Join(rootPath, CONFIG_PATH)
|
||||
DB_PATH = filepath.Join(rootPath, DB_PATH)
|
||||
FTS_PATH = filepath.Join(rootPath, FTS_PATH)
|
||||
CERT_PATH = filepath.Join(rootPath, CERT_PATH)
|
||||
TMP_PATH = filepath.Join(rootPath, TMP_PATH)
|
||||
PLUGIN_PATH = filepath.Join(rootPath, PLUGIN_PATH)
|
||||
BASE = strings.TrimSuffix(os.Getenv("FILESTASH_BASE"), "/")
|
||||
COOKIE_PATH_ADMIN = WithBase(COOKIE_PATH_ADMIN)
|
||||
COOKIE_PATH = WithBase(COOKIE_PATH)
|
||||
URL_SETUP = WithBase(URL_SETUP)
|
||||
}
|
||||
Vendored
+33
@@ -0,0 +1,33 @@
|
||||
//go:build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cmd, b := exec.Command("git", "rev-parse", "HEAD"), new(strings.Builder)
|
||||
cmd.Stdout = b
|
||||
cmd.Run()
|
||||
|
||||
f, err := os.OpenFile("constants_generated.go", os.O_CREATE|os.O_WRONLY, os.ModePerm)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
return
|
||||
}
|
||||
f.Write([]byte(fmt.Sprintf(`
|
||||
package env
|
||||
|
||||
func init() {
|
||||
BUILD_REF = "%s"
|
||||
BUILD_DATE = "%s"
|
||||
}
|
||||
`, strings.TrimSpace(b.String()), time.Now().Format("20060102"))))
|
||||
f.Close()
|
||||
}
|
||||
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
package env
|
||||
|
||||
import "strings"
|
||||
|
||||
func WithBase(href string) string {
|
||||
if BASE == "" {
|
||||
return href
|
||||
}
|
||||
return BASE + href
|
||||
}
|
||||
|
||||
func TrimBase(href string) string {
|
||||
if BASE == "" {
|
||||
return href
|
||||
}
|
||||
return strings.TrimPrefix(href, BASE)
|
||||
}
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
package env
|
||||
|
||||
func IsWhiteLabel() bool {
|
||||
return APPNAME != "Filestash"
|
||||
}
|
||||
|
||||
func WhiteLabelText(a, b string) string {
|
||||
if IsWhiteLabel() {
|
||||
return b
|
||||
}
|
||||
return a
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
package files
|
||||
|
||||
import (
|
||||
. "github.com/mickael-kerjean/filestash/server/common"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/config"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/core"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/kernel"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -4,8 +4,10 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
. "github.com/mickael-kerjean/filestash/server/common"
|
||||
"github.com/mickael-kerjean/filestash/server/pkg/permissions"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/core"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/kernel"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/permissions"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/utils"
|
||||
)
|
||||
|
||||
func FileAccess(ctx *App, res http.ResponseWriter, req *http.Request) {
|
||||
@@ -21,18 +23,18 @@ func FileAccess(ctx *App, res http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
|
||||
allowed := []string{}
|
||||
if permissions.CanRead(ctx) {
|
||||
if CanRead(ctx) {
|
||||
if perms.CanSee == nil || *perms.CanSee == true {
|
||||
allowed = append(allowed, "GET")
|
||||
}
|
||||
}
|
||||
if permissions.CanEdit(ctx) {
|
||||
if CanEdit(ctx) {
|
||||
if (perms.CanCreateFile == nil || *perms.CanCreateFile == true) &&
|
||||
(perms.CanCreateDirectory == nil || *perms.CanCreateDirectory == true) {
|
||||
allowed = append(allowed, "PUT")
|
||||
}
|
||||
}
|
||||
if permissions.CanUpload(ctx) {
|
||||
if CanUpload(ctx) {
|
||||
if perms.CanUpload == nil || *perms.CanUpload == true {
|
||||
allowed = append(allowed, "POST")
|
||||
}
|
||||
|
||||
@@ -10,8 +10,13 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
. "github.com/mickael-kerjean/filestash/server/common"
|
||||
"github.com/mickael-kerjean/filestash/server/pkg/permissions"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/config"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/core"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/env"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/kernel"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/mime"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/permissions"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/utils"
|
||||
)
|
||||
|
||||
var file_cache AppCache
|
||||
@@ -40,7 +45,7 @@ func FileCat(ctx *App, res http.ResponseWriter, req *http.Request) {
|
||||
MaxAge: -1,
|
||||
Path: "/",
|
||||
})
|
||||
if permissions.CanRead(ctx) == false {
|
||||
if CanRead(ctx) == false {
|
||||
Log.Debug("cat::permission 'permission denied'")
|
||||
SendErrorResult(res, ErrPermissionDenied)
|
||||
return
|
||||
|
||||
@@ -11,12 +11,14 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
. "github.com/mickael-kerjean/filestash/server/common"
|
||||
"github.com/mickael-kerjean/filestash/server/pkg/permissions"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/core"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/kernel"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/permissions"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/utils"
|
||||
)
|
||||
|
||||
func FileExtract(ctx *App, res http.ResponseWriter, req *http.Request) {
|
||||
if permissions.CanRead(ctx) == false {
|
||||
if CanRead(ctx) == false {
|
||||
Log.Debug("extract::permission 'permission denied'")
|
||||
SendErrorResult(res, ErrPermissionDenied)
|
||||
return
|
||||
|
||||
@@ -10,8 +10,10 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
. "github.com/mickael-kerjean/filestash/server/common"
|
||||
"github.com/mickael-kerjean/filestash/server/pkg/permissions"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/core"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/kernel"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/permissions"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/utils"
|
||||
)
|
||||
|
||||
type FileInfo struct {
|
||||
@@ -24,8 +26,8 @@ type FileInfo struct {
|
||||
}
|
||||
|
||||
func FileLs(ctx *App, res http.ResponseWriter, req *http.Request) {
|
||||
if permissions.CanRead(ctx) == false {
|
||||
if permissions.CanUpload(ctx) == false {
|
||||
if CanRead(ctx) == false {
|
||||
if CanUpload(ctx) == false {
|
||||
Log.Debug("ls::permission 'permission denied'")
|
||||
SendErrorResult(res, ErrPermissionDenied)
|
||||
return
|
||||
@@ -71,7 +73,7 @@ func FileLs(ctx *App, res http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
ctx.Context = context.WithValue(ctx.Context, "AUDIT", nil)
|
||||
}
|
||||
if permissions.CanEdit(ctx) == false {
|
||||
if CanEdit(ctx) == false {
|
||||
perms.CanCreateFile = NewBool(false)
|
||||
perms.CanCreateDirectory = NewBool(false)
|
||||
perms.CanRename = NewBool(false)
|
||||
@@ -79,14 +81,14 @@ func FileLs(ctx *App, res http.ResponseWriter, req *http.Request) {
|
||||
perms.CanDelete = NewBool(false)
|
||||
perms.CanUpload = NewBool(false)
|
||||
}
|
||||
if permissions.CanUpload(ctx) == false {
|
||||
if CanUpload(ctx) == false {
|
||||
perms.CanCreateDirectory = NewBool(false)
|
||||
perms.CanRename = NewBool(false)
|
||||
perms.CanMove = NewBool(false)
|
||||
perms.CanDelete = NewBool(false)
|
||||
perms.CanUpload = NewBool(false)
|
||||
}
|
||||
if permissions.CanShare(ctx) == false {
|
||||
if CanShare(ctx) == false {
|
||||
perms.CanShare = NewBool(false)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package ctrl
|
||||
package files
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
. "github.com/mickael-kerjean/filestash/server/common"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/core"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/kernel"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/utils"
|
||||
)
|
||||
|
||||
func MetaGet(ctx *App, w http.ResponseWriter, r *http.Request) {
|
||||
@@ -3,12 +3,14 @@ package files
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
. "github.com/mickael-kerjean/filestash/server/common"
|
||||
"github.com/mickael-kerjean/filestash/server/pkg/permissions"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/core"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/kernel"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/permissions"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/utils"
|
||||
)
|
||||
|
||||
func FileMkdir(ctx *App, res http.ResponseWriter, req *http.Request) {
|
||||
if permissions.CanUpload(ctx) == false {
|
||||
if CanUpload(ctx) == false {
|
||||
Log.Debug("mkdir::permission 'permission denied'")
|
||||
SendErrorResult(res, NewError("Permission denied", 403))
|
||||
return
|
||||
|
||||
@@ -3,12 +3,14 @@ package files
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
. "github.com/mickael-kerjean/filestash/server/common"
|
||||
"github.com/mickael-kerjean/filestash/server/pkg/permissions"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/core"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/kernel"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/permissions"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/utils"
|
||||
)
|
||||
|
||||
func FileMv(ctx *App, res http.ResponseWriter, req *http.Request) {
|
||||
if permissions.CanEdit(ctx) == false {
|
||||
if CanEdit(ctx) == false {
|
||||
Log.Debug("mv::permission 'permission denied'")
|
||||
SendErrorResult(res, NewError("Permission denied", 403))
|
||||
return
|
||||
|
||||
@@ -3,12 +3,14 @@ package files
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
. "github.com/mickael-kerjean/filestash/server/common"
|
||||
"github.com/mickael-kerjean/filestash/server/pkg/permissions"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/core"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/kernel"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/permissions"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/utils"
|
||||
)
|
||||
|
||||
func FileRm(ctx *App, res http.ResponseWriter, req *http.Request) {
|
||||
if permissions.CanEdit(ctx) == false {
|
||||
if CanEdit(ctx) == false {
|
||||
Log.Debug("rm::permission 'permission denied'")
|
||||
SendErrorResult(res, NewError("Permission denied", 403))
|
||||
return
|
||||
|
||||
@@ -16,8 +16,10 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
. "github.com/mickael-kerjean/filestash/server/common"
|
||||
"github.com/mickael-kerjean/filestash/server/pkg/permissions"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/core"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/kernel"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/permissions"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/utils"
|
||||
)
|
||||
|
||||
var chunkedUploadCache AppCache
|
||||
@@ -44,8 +46,8 @@ func FileSave(ctx *App, res http.ResponseWriter, req *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if permissions.CanEdit(ctx) == false {
|
||||
if permissions.CanUpload(ctx) == false {
|
||||
if CanEdit(ctx) == false {
|
||||
if CanUpload(ctx) == false {
|
||||
Log.Debug("files::save action=permission_upload err=permission_denied")
|
||||
SendErrorResult(res, ErrPermissionDenied)
|
||||
return
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package ctrl
|
||||
package files
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
. "github.com/mickael-kerjean/filestash/server/common"
|
||||
"github.com/mickael-kerjean/filestash/server/pkg/permissions"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/core"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/kernel"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/permissions"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/utils"
|
||||
)
|
||||
|
||||
func FileSearch(ctx *App, res http.ResponseWriter, req *http.Request) {
|
||||
@@ -14,7 +16,7 @@ func FileSearch(ctx *App, res http.ResponseWriter, req *http.Request) {
|
||||
path = "/"
|
||||
}
|
||||
q := req.URL.Query().Get("q")
|
||||
if permissions.CanRead(ctx) == false {
|
||||
if CanRead(ctx) == false {
|
||||
Log.Debug("ctrl::search 'can not read \"%s\"'", path)
|
||||
SendErrorResult(res, ErrPermissionDenied)
|
||||
return
|
||||
@@ -3,12 +3,14 @@ package files
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
. "github.com/mickael-kerjean/filestash/server/common"
|
||||
"github.com/mickael-kerjean/filestash/server/pkg/permissions"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/core"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/kernel"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/permissions"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/utils"
|
||||
)
|
||||
|
||||
func FileTouch(ctx *App, res http.ResponseWriter, req *http.Request) {
|
||||
if permissions.CanUpload(ctx) == false {
|
||||
if CanUpload(ctx) == false {
|
||||
Log.Debug("touch::permission 'permission denied'")
|
||||
SendErrorResult(res, NewError("Permission denied", 403))
|
||||
return
|
||||
|
||||
@@ -9,13 +9,15 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
. "github.com/mickael-kerjean/filestash/server/common"
|
||||
"github.com/mickael-kerjean/filestash/server/pkg/permissions"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/core"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/kernel"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/permissions"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/utils"
|
||||
)
|
||||
|
||||
func FileDownloader(ctx *App, res http.ResponseWriter, req *http.Request) {
|
||||
var err error
|
||||
if permissions.CanRead(ctx) == false {
|
||||
if CanRead(ctx) == false {
|
||||
Log.Debug("downloader::permission 'permission denied'")
|
||||
SendErrorResult(res, ErrPermissionDenied)
|
||||
return
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package model
|
||||
package files
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
. "github.com/mickael-kerjean/filestash/server/common"
|
||||
"strings"
|
||||
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/config"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/core"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/kernel"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/utils"
|
||||
)
|
||||
|
||||
func NewBackend(ctx *App, conn map[string]string) (IBackend, error) {
|
||||
@@ -72,14 +75,3 @@ func GetHome(b IBackend, base string) (string, error) {
|
||||
}
|
||||
return "/", nil
|
||||
}
|
||||
|
||||
func MapStringInterfaceToMapStringString(m map[string]interface{}) map[string]string {
|
||||
res := make(map[string]string)
|
||||
for key, value := range m {
|
||||
res[key] = fmt.Sprintf("%v", value)
|
||||
if res[key] == "<nil>" {
|
||||
res[key] = ""
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
package common
|
||||
package kernel
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/core"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/utils"
|
||||
)
|
||||
|
||||
const BACKEND_NIL = "_nothing_"
|
||||
@@ -1,4 +1,4 @@
|
||||
package common
|
||||
package kernel
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -6,6 +6,10 @@ import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/config"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/env"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/utils"
|
||||
)
|
||||
|
||||
const IndentSize = " "
|
||||
@@ -42,7 +46,6 @@ func SendSuccessResultWithEtagAndGzip(res http.ResponseWriter, req *http.Request
|
||||
var dataToSend []byte
|
||||
var err error
|
||||
if dataToSend, err = json.MarshalIndent(APISuccessResult{"ok", data}, "", IndentSize); err != nil {
|
||||
Log.Warning("common::response Marshal %s", err.Error())
|
||||
dataToSend = []byte("{}")
|
||||
}
|
||||
mode := "normal"
|
||||
@@ -0,0 +1,25 @@
|
||||
package kernel
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/mickael-kerjean/filestash/server/pkg/config"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/env"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/utils"
|
||||
)
|
||||
|
||||
func init() {
|
||||
config.RegisterChange(func() {
|
||||
for _, fn := range Hooks.Get.OnConfig() {
|
||||
fn()
|
||||
}
|
||||
})
|
||||
Hooks.Register.Onload(func() {
|
||||
if err := os.Chmod(GetAbsolutePath(CONFIG_PATH), 0770); err != nil && os.IsNotExist(err) == false {
|
||||
Log.Warning("common::config_state::onload cannot chmod config directory: %s", err.Error())
|
||||
}
|
||||
if err := os.Chmod(GetAbsolutePath(CONFIG_PATH, "config.json"), 0660); err != nil && os.IsNotExist(err) == false {
|
||||
Log.Warning("common::config_state::onload cannot chmod config file: %s", err.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package kernel
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/core"
|
||||
"github.com/mickael-kerjean/filestash/server/pkg/utils"
|
||||
)
|
||||
|
||||
func PathBuilder(ctx *App, path string) (string, error) {
|
||||
if path == "" {
|
||||
return "", utils.NewError("No path available", 400)
|
||||
}
|
||||
chroot := ctx.Session["path"]
|
||||
fullpath := filepath.ToSlash(filepath.Join(chroot, path))
|
||||
if strings.HasSuffix(path, "/") && fullpath != "/" {
|
||||
fullpath += "/"
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(fullpath, utils.EnforceDirectory(chroot)) {
|
||||
if strings.HasSuffix(chroot, "/") {
|
||||
return "", utils.ErrFilesystemError
|
||||
}
|
||||
return chroot, nil
|
||||
} else if !strings.HasSuffix(chroot, "/") && strings.HasSuffix(chroot, path) {
|
||||
return chroot, nil
|
||||
}
|
||||
return fullpath, nil
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package common
|
||||
package kernel
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -10,9 +10,12 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/core"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/mime"
|
||||
"github.com/mickael-kerjean/filestash/server/pkg/tracer"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/utils"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
type Register struct{}
|
||||
@@ -1,4 +1,4 @@
|
||||
package ctrl
|
||||
package kernel
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -12,7 +12,8 @@ import (
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
. "github.com/mickael-kerjean/filestash/server/common"
|
||||
"github.com/mickael-kerjean/filestash/server/pkg/system"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/utils"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
@@ -42,7 +43,7 @@ func TmplParams(data map[string]string) map[string]string {
|
||||
for key, value := range data {
|
||||
out[key] = value
|
||||
}
|
||||
out["machine_id"] = GenerateMachineID()
|
||||
out["machine_id"] = system.GenerateMachineID()
|
||||
for _, value := range os.Environ() {
|
||||
pair := strings.SplitN(value, "=", 2)
|
||||
if len(pair) == 2 {
|
||||
@@ -0,0 +1,81 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
_ "unsafe"
|
||||
|
||||
. "github.com/mickael-kerjean/filestash/server/common"
|
||||
)
|
||||
|
||||
func init() {
|
||||
Hooks.Register.Onload(func() {
|
||||
go func() {
|
||||
for {
|
||||
time.Sleep(10 * time.Second)
|
||||
telemetry.Flush()
|
||||
}
|
||||
}()
|
||||
})
|
||||
}
|
||||
|
||||
func NewMiddlewareChain(fn HandlerFunc, m []Middleware) http.HandlerFunc {
|
||||
return func(res http.ResponseWriter, req *http.Request) {
|
||||
var f func(*App, http.ResponseWriter, *http.Request) = fn
|
||||
for i := len(m) - 1; i >= 0; i-- {
|
||||
f = m[i](f)
|
||||
}
|
||||
var (
|
||||
app = App{Context: req.Context()}
|
||||
resw = NewResponseWriter(res)
|
||||
)
|
||||
f(&app, &resw, req)
|
||||
if req.Body != nil {
|
||||
req.Body.Close()
|
||||
}
|
||||
logger(&app, &resw, req)
|
||||
}
|
||||
}
|
||||
|
||||
type ResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
status int
|
||||
start int64
|
||||
}
|
||||
|
||||
func NewResponseWriter(res http.ResponseWriter) ResponseWriter {
|
||||
return ResponseWriter{
|
||||
ResponseWriter: res,
|
||||
start: now(),
|
||||
}
|
||||
}
|
||||
|
||||
func (w *ResponseWriter) WriteHeader(status int) {
|
||||
w.status = status
|
||||
w.ResponseWriter.WriteHeader(status)
|
||||
}
|
||||
|
||||
func (w *ResponseWriter) Write(b []byte) (int, error) {
|
||||
if w.status == 0 {
|
||||
w.status = 200
|
||||
}
|
||||
return w.ResponseWriter.Write(b)
|
||||
}
|
||||
|
||||
func (w *ResponseWriter) Status() int {
|
||||
return w.status
|
||||
}
|
||||
|
||||
func (w *ResponseWriter) Flush() {
|
||||
w.ResponseWriter.(http.Flusher).Flush()
|
||||
}
|
||||
|
||||
func PluginInjector(fn HandlerFunc) HandlerFunc {
|
||||
for _, middleware := range Hooks.Get.Middleware() {
|
||||
fn = middleware(fn)
|
||||
}
|
||||
return fn
|
||||
}
|
||||
|
||||
//go:linkname now runtime.nanotime
|
||||
func now() int64
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
. "github.com/mickael-kerjean/filestash/server/common"
|
||||
"github.com/mickael-kerjean/filestash/server/model"
|
||||
"github.com/mickael-kerjean/filestash/server/pkg/admin"
|
||||
"github.com/mickael-kerjean/filestash/server/pkg/token"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
@@ -28,7 +29,7 @@ func LoggedInOnly(fn HandlerFunc) HandlerFunc {
|
||||
|
||||
func AdminOnly(fn HandlerFunc) HandlerFunc {
|
||||
return HandlerFunc(func(ctx *App, res http.ResponseWriter, req *http.Request) {
|
||||
if admin := Config.Get("auth.admin").String(); admin != "" {
|
||||
if setupWizard := Config.Get("auth.admin").String() == ""; !setupWizard {
|
||||
authStr := strings.TrimPrefix(req.Header.Get("Authorization"), "Bearer ")
|
||||
if authStr == "" {
|
||||
c, err := req.Cookie(COOKIE_NAME_ADMIN)
|
||||
@@ -43,7 +44,7 @@ func AdminOnly(fn HandlerFunc) HandlerFunc {
|
||||
SendErrorResult(res, ErrPermissionDenied)
|
||||
return
|
||||
}
|
||||
token := AdminToken{}
|
||||
token := admin.AdminToken{}
|
||||
json.Unmarshal([]byte(str), &token)
|
||||
|
||||
if token.IsValid() == false || token.IsAdmin() == false {
|
||||
@@ -109,7 +109,6 @@ func logger(ctx *App, res *ResponseWriter, req *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func (this *Telemetry) Record(point LogEntry) {
|
||||
point.Duration *= 10
|
||||
this.mu.Lock()
|
||||
@@ -0,0 +1,47 @@
|
||||
//go:build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"go/format"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
f, err := os.OpenFile("mime.json", os.O_RDONLY, os.ModePerm)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
j, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
mTypes := make(map[string]string, 0)
|
||||
json.Unmarshal(j, &mTypes)
|
||||
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString("package mime\n")
|
||||
buf.WriteString("func init() {\n")
|
||||
for key, value := range mTypes {
|
||||
fmt.Fprintf(&buf, "MimeTypes[\"%s\"] = \"%s\"\n", key, value)
|
||||
}
|
||||
buf.WriteString("}\n")
|
||||
formatted, err := format.Source(buf.Bytes())
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error formatting: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err = os.WriteFile("mime_generated.go", formatted, 0644); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error writing file: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
package common
|
||||
package mime
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//go:generate go run ../generator/mime.go
|
||||
//go:generate go run generator.go
|
||||
var MimeTypes map[string]string = make(map[string]string, 0)
|
||||
|
||||
func GetMimeType(p string) string {
|
||||
@@ -0,0 +1,46 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
. "github.com/mickael-kerjean/filestash/server/common"
|
||||
)
|
||||
|
||||
var DB *sql.DB
|
||||
|
||||
func init() {
|
||||
Hooks.Register.Onload(func() {
|
||||
var err error
|
||||
if DB, err = sql.Open("sqlite3", GetAbsolutePath(DB_PATH)+"/share.sql?_fk=true"); err != nil {
|
||||
Log.Error("model::index sqlite open error '%s'", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if stmt, err := DB.Prepare("CREATE TABLE IF NOT EXISTS Location(backend VARCHAR(16), path VARCHAR(512), CONSTRAINT pk_location PRIMARY KEY(backend, path))"); err == nil {
|
||||
stmt.Exec()
|
||||
}
|
||||
|
||||
if stmt, err := DB.Prepare("CREATE TABLE IF NOT EXISTS Share(id VARCHAR(64) PRIMARY KEY, related_backend VARCHAR(16), related_path VARCHAR(512), params JSON, auth VARCHAR(4093) NOT NULL, FOREIGN KEY (related_backend, related_path) REFERENCES Location(backend, path) ON UPDATE CASCADE ON DELETE CASCADE)"); err == nil {
|
||||
stmt.Exec()
|
||||
}
|
||||
|
||||
if stmt, err := DB.Prepare("CREATE TABLE IF NOT EXISTS Verification(key VARCHAR(512), code VARCHAR(4), expire DATETIME DEFAULT (datetime('now', '+10 minutes')))"); err == nil {
|
||||
stmt.Exec()
|
||||
if stmt, err = DB.Prepare("CREATE INDEX idx_verification ON Verification(code, expire)"); err == nil {
|
||||
stmt.Exec()
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
autovacuum()
|
||||
}()
|
||||
})
|
||||
}
|
||||
|
||||
func autovacuum() {
|
||||
if stmt, err := DB.Prepare("DELETE FROM Verification WHERE expire < datetime('now')"); err == nil {
|
||||
stmt.Exec()
|
||||
}
|
||||
time.Sleep(6 * time.Hour)
|
||||
}
|
||||
@@ -3,8 +3,8 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"database/sql"
|
||||
"errors"
|
||||
|
||||
modernc "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
|
||||
. "github.com/mickael-kerjean/filestash/server/common"
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func GenerateMachineID() string {
|
||||
if runtime.GOOS == "linux" {
|
||||
if f, err := os.OpenFile("/etc/machine-id", os.O_RDONLY, os.ModePerm); err == nil {
|
||||
defer f.Close()
|
||||
b := make([]byte, 32)
|
||||
if _, err = f.Read(b); err == nil {
|
||||
return string(b)
|
||||
}
|
||||
} else if f, err := os.OpenFile("/var/lib/dbus/machine-id", os.O_RDONLY, os.ModePerm); err == nil {
|
||||
defer f.Close()
|
||||
b := make([]byte, 32)
|
||||
if _, err = f.Read(b); err == nil {
|
||||
return string(b)
|
||||
}
|
||||
}
|
||||
}
|
||||
return "na"
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package common
|
||||
package system
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -0,0 +1,15 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mickael-kerjean/filestash/server/pkg/env"
|
||||
)
|
||||
|
||||
var USER_AGENT = fmt.Sprintf("Filestash/%s.%s (http://filestash.app)", env.APP_VERSION, env.BUILD_DATE)
|
||||
|
||||
func init() {
|
||||
if env.IsWhiteLabel() {
|
||||
USER_AGENT = env.APPNAME
|
||||
}
|
||||
}
|
||||
@@ -5,12 +5,13 @@ import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
. "github.com/mickael-kerjean/filestash/server/common"
|
||||
"io"
|
||||
"math/rand"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
. "github.com/mickael-kerjean/filestash/server/common"
|
||||
)
|
||||
|
||||
func Office(r io.ReadCloser) (io.ReadCloser, error) {
|
||||
|
||||
@@ -3,11 +3,12 @@ package textify
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
. "github.com/mickael-kerjean/filestash/server/common"
|
||||
"io"
|
||||
"math/rand"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
. "github.com/mickael-kerjean/filestash/server/common"
|
||||
)
|
||||
|
||||
func PDF(r io.ReadCloser) (io.ReadCloser, error) {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package common
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/hashstructure"
|
||||
"github.com/patrickmn/go-cache"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/hashstructure"
|
||||
"github.com/patrickmn/go-cache"
|
||||
)
|
||||
|
||||
type AppCache struct {
|
||||
@@ -1,8 +1,7 @@
|
||||
package common
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
@@ -83,12 +82,13 @@ func NewReadCloserFromReader(r io.Reader) io.ReadCloser {
|
||||
return io.NopCloser(r)
|
||||
}
|
||||
|
||||
func PrettyPrint(json_dirty []byte) []byte {
|
||||
var json_pretty bytes.Buffer
|
||||
error := json.Indent(&json_pretty, json_dirty, "", " ")
|
||||
if error != nil {
|
||||
return json_dirty
|
||||
func MapStringInterfaceToMapStringString(m map[string]interface{}) map[string]string {
|
||||
res := make(map[string]string)
|
||||
for key, value := range m {
|
||||
res[key] = fmt.Sprintf("%v", value)
|
||||
if res[key] == "<nil>" {
|
||||
res[key] = ""
|
||||
}
|
||||
}
|
||||
json_pretty.Write([]byte("\n"))
|
||||
return json_pretty.Bytes()
|
||||
return res
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package common
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -10,18 +10,19 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"hash/fnv"
|
||||
"io"
|
||||
"math/big"
|
||||
mathrand "math/rand"
|
||||
"os"
|
||||
"runtime"
|
||||
"sort"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/mickael-kerjean/filestash/server/pkg/env"
|
||||
)
|
||||
|
||||
var (
|
||||
Letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
||||
Letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
||||
GCMNonce = NewNonceGenerator()
|
||||
)
|
||||
|
||||
@@ -138,8 +139,7 @@ func EncryptAESGCM(key []byte, plaintext []byte) ([]byte, error) {
|
||||
|
||||
nonce := GCMNonce.Next()
|
||||
if gcm.NonceSize() != len(nonce) {
|
||||
Log.Error("common::crypto nonce size isn't '12' but '%d'", gcm.NonceSize())
|
||||
return nil, ErrNotValid
|
||||
return nil, errors.ErrUnsupported
|
||||
}
|
||||
return gcm.Seal(nonce, nonce, plaintext, nil), nil
|
||||
}
|
||||
@@ -157,7 +157,7 @@ func DecryptAESGCM(key []byte, ciphertext []byte) ([]byte, error) {
|
||||
|
||||
nonceSize := gcm.NonceSize()
|
||||
if len(ciphertext) < nonceSize {
|
||||
return nil, NewError("ciphertext too short", 500)
|
||||
return nil, errors.New("ciphertext too short")
|
||||
}
|
||||
|
||||
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
|
||||
@@ -214,32 +214,12 @@ func GenerateID(params map[string]string) string {
|
||||
if p == "" {
|
||||
return "na"
|
||||
}
|
||||
p += "salt=>" + SECRET_KEY
|
||||
p += "salt=>" + env.SECRET_KEY
|
||||
return Hash(p, 20)
|
||||
}
|
||||
|
||||
// Create an ID that identify a machine
|
||||
func GenerateMachineID() string {
|
||||
if runtime.GOOS == "linux" {
|
||||
if f, err := os.OpenFile("/etc/machine-id", os.O_RDONLY, os.ModePerm); err == nil {
|
||||
defer f.Close()
|
||||
b := make([]byte, 32)
|
||||
if _, err = f.Read(b); err == nil {
|
||||
return string(b)
|
||||
}
|
||||
} else if f, err := os.OpenFile("/var/lib/dbus/machine-id", os.O_RDONLY, os.ModePerm); err == nil {
|
||||
defer f.Close()
|
||||
b := make([]byte, 32)
|
||||
if _, err = f.Read(b); err == nil {
|
||||
return string(b)
|
||||
}
|
||||
}
|
||||
}
|
||||
return "na"
|
||||
}
|
||||
|
||||
type NonceGenerator struct {
|
||||
start [12]byte
|
||||
start [12]byte
|
||||
current atomic.Uint64
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
package utils
|
||||
|
||||
const PASSWORD_DUMMY = "{{PASSWORD}}"
|
||||
@@ -1,17 +1,10 @@
|
||||
package common
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func NewError(message string, status int) AppError {
|
||||
if status == 0 {
|
||||
status = 500
|
||||
}
|
||||
return AppError{message, status}
|
||||
}
|
||||
|
||||
var (
|
||||
ErrNotFound = NewError("Not Found", 404)
|
||||
ErrNotAllowed = NewError("Not Allowed", 403)
|
||||
@@ -31,6 +24,13 @@ var (
|
||||
ErrInternal = NewError("Internal Error", 500)
|
||||
)
|
||||
|
||||
func NewError(message string, status int) AppError {
|
||||
if status == 0 {
|
||||
status = 500
|
||||
}
|
||||
return AppError{message, status}
|
||||
}
|
||||
|
||||
func IsATranslatedError(err error) bool {
|
||||
if err == ErrNotFound || err == ErrNotAllowed || err == ErrPermissionDenied ||
|
||||
err == ErrNotValid || err == ErrInvalidPassword || err == ErrNotImplemented ||
|
||||
@@ -0,0 +1,54 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
type File struct {
|
||||
FName string `json:"name"`
|
||||
FType string `json:"type"`
|
||||
FTime int64 `json:"time"`
|
||||
FSize int64 `json:"size"`
|
||||
FPath string `json:"path,omitempty"`
|
||||
Offline bool `json:"offline,omitempty"`
|
||||
Metadata any `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
func (f File) Name() string {
|
||||
return f.FName
|
||||
}
|
||||
|
||||
func (f File) Size() int64 {
|
||||
return f.FSize
|
||||
}
|
||||
|
||||
func (f File) Mode() os.FileMode {
|
||||
if f.IsDir() {
|
||||
return os.ModeDir
|
||||
}
|
||||
return os.FileMode(0664)
|
||||
}
|
||||
|
||||
func (f File) ModTime() time.Time {
|
||||
if f.FTime == 0 {
|
||||
t := new(time.Time)
|
||||
return *t
|
||||
}
|
||||
return time.Unix(f.FTime, 0)
|
||||
}
|
||||
|
||||
func (f File) IsDir() bool {
|
||||
if f.FType != "directory" {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (f File) Sys() interface{} {
|
||||
return f
|
||||
}
|
||||
|
||||
func (f File) Path() string {
|
||||
return f.FPath
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func SafeOsOpenFile(path string, flag int, perm os.FileMode) (*os.File, error) {
|
||||
if err := safePath(path); err != nil {
|
||||
return nil, ErrFilesystemError
|
||||
}
|
||||
if runtime.GOOS == "linux" {
|
||||
flag |= 0x20000 // O_NOFOLLOW ref:/usr/include/asm-generic/fcntl.h
|
||||
}
|
||||
f, err := os.OpenFile(path, flag, perm)
|
||||
if err != nil {
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
return nil, processError(err)
|
||||
}
|
||||
return f, err
|
||||
}
|
||||
|
||||
func SafeOsMkdir(path string, mode os.FileMode) error {
|
||||
if err := safePath(path); err != nil {
|
||||
return ErrFilesystemError
|
||||
}
|
||||
return processError(os.Mkdir(path, mode))
|
||||
}
|
||||
|
||||
func SafeOsRemove(path string) error {
|
||||
if err := safePath(path); err != nil {
|
||||
return ErrFilesystemError
|
||||
}
|
||||
return processError(os.Remove(path))
|
||||
}
|
||||
|
||||
func SafeOsRemoveAll(path string) error {
|
||||
if err := safePath(path); err != nil {
|
||||
return ErrFilesystemError
|
||||
}
|
||||
return processError(os.RemoveAll(path))
|
||||
}
|
||||
|
||||
func SafeOsRename(from string, to string) error {
|
||||
if err := safePath(from); err != nil {
|
||||
return ErrFilesystemError
|
||||
} else if err := safePath(to); err != nil {
|
||||
return ErrFilesystemError
|
||||
}
|
||||
return processError(os.Rename(from, to))
|
||||
}
|
||||
|
||||
func processError(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if pe, ok := err.(*os.PathError); ok {
|
||||
return pe.Err
|
||||
}
|
||||
if le, ok := err.(*os.LinkError); ok {
|
||||
return le.Err
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func safePath(path string) error {
|
||||
p, err := filepath.EvalSymlinks(path)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) == false {
|
||||
return err
|
||||
}
|
||||
parentPath := filepath.Join(path, "../")
|
||||
return safePath(parentPath)
|
||||
}
|
||||
if p != filepath.Clean(path) {
|
||||
return ErrFilesystemError
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,23 +1,15 @@
|
||||
package common
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/mickael-kerjean/filestash/server/pkg/system"
|
||||
"github.com/mickael-kerjean/filestash/server/pkg/tracer"
|
||||
)
|
||||
|
||||
var USER_AGENT = fmt.Sprintf("Filestash/%s.%s (http://filestash.app)", APP_VERSION, BUILD_DATE)
|
||||
|
||||
func init() {
|
||||
if IsWhiteLabel() {
|
||||
USER_AGENT = APPNAME
|
||||
}
|
||||
}
|
||||
|
||||
type httpClientConfig struct {
|
||||
transport *http.Transport
|
||||
traceService string
|
||||
@@ -99,6 +91,6 @@ type TransformedTransport struct {
|
||||
}
|
||||
|
||||
func (this *TransformedTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
req.Header.Add("User-Agent", USER_AGENT)
|
||||
req.Header.Add("User-Agent", system.USER_AGENT)
|
||||
return this.Orig.RoundTrip(req)
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
func PrettyPrint(json_dirty []byte) []byte {
|
||||
var json_pretty bytes.Buffer
|
||||
error := json.Indent(&json_pretty, json_dirty, "", " ")
|
||||
if error != nil {
|
||||
return json_dirty
|
||||
}
|
||||
json_pretty.Write([]byte("\n"))
|
||||
return json_pretty.Bytes()
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package common
|
||||
package utils
|
||||
|
||||
import (
|
||||
"io"
|
||||
@@ -1,4 +1,4 @@
|
||||
package common
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -20,8 +20,8 @@ var Log = &log{
|
||||
},
|
||||
}
|
||||
|
||||
func InitLogger() (err error) {
|
||||
f, err := os.OpenFile(GetAbsolutePath(LOG_PATH, "access.log"), os.O_APPEND|os.O_WRONLY|os.O_CREATE, os.ModePerm)
|
||||
func InitLogger(path string) (err error) {
|
||||
f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY|os.O_CREATE, os.ModePerm)
|
||||
if err != nil {
|
||||
slog.Printf("ERROR log file: %+v", err)
|
||||
return err
|
||||
@@ -0,0 +1,79 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/bmatcuk/doublestar/v4"
|
||||
)
|
||||
|
||||
var MOCK_CURRENT_DIR string
|
||||
|
||||
func GetCurrentDir() string {
|
||||
if MOCK_CURRENT_DIR != "" {
|
||||
return MOCK_CURRENT_DIR
|
||||
}
|
||||
ex, _ := os.Executable()
|
||||
return filepath.Dir(ex)
|
||||
}
|
||||
|
||||
func GetAbsolutePath(base string, opts ...string) string {
|
||||
fullPath := base
|
||||
if strings.HasPrefix(base, "/") == false { // relative filepath are relative to the binary
|
||||
fullPath = filepath.Join(GetCurrentDir(), base)
|
||||
}
|
||||
if len(opts) == 0 {
|
||||
return fullPath
|
||||
}
|
||||
return filepath.Join(append([]string{fullPath}, opts...)...)
|
||||
}
|
||||
|
||||
func IsDirectory(path string) bool {
|
||||
if path == "" {
|
||||
return false
|
||||
}
|
||||
if path[len(path)-1:] != "/" {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/*
|
||||
* Join 2 path together, result has a file
|
||||
*/
|
||||
func JoinPath(base, file string) string {
|
||||
filePath := path.Join(base, file)
|
||||
if strings.HasPrefix(filePath, base) == false {
|
||||
return base
|
||||
}
|
||||
return filePath
|
||||
}
|
||||
|
||||
func EnforceDirectory(path string) string {
|
||||
if path == "" {
|
||||
return "/"
|
||||
} else if path[len(path)-1:] == "/" {
|
||||
return path
|
||||
}
|
||||
return path + "/"
|
||||
}
|
||||
|
||||
func SplitPath(path string) (root string, filename string) {
|
||||
if path == "" {
|
||||
path = "/"
|
||||
}
|
||||
if IsDirectory(path) == false {
|
||||
filename = filepath.Base(path)
|
||||
}
|
||||
if root = strings.TrimSuffix(path, filename); root == "" {
|
||||
root = "/"
|
||||
}
|
||||
return root, filename
|
||||
}
|
||||
|
||||
func GlobMatch(pattern, name string) bool {
|
||||
m, _ := doublestar.Match(pattern, name)
|
||||
return m
|
||||
}
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
"time"
|
||||
|
||||
. "github.com/mickael-kerjean/filestash/server/common"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/workflow/model"
|
||||
"github.com/mickael-kerjean/filestash/server/model"
|
||||
. "github.com/mickael-kerjean/filestash/server/pkg/workflow/model"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
Reference in New Issue
Block a user