chore (refactoring): migrate to server/pkg

This commit is contained in:
MickaelK
2026-08-20 14:00:25 +10:00
parent 4414aec58a
commit 9b11653afd
73 changed files with 1335 additions and 837 deletions
-15
View File
@@ -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
View File
@@ -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
}
-185
View File
@@ -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
}
+169
View File
@@ -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
-16
View File
@@ -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
)
+42
View File
@@ -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
)
+3 -2
View File
@@ -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))
+5 -18
View File
@@ -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()
}
+6 -33
View File
@@ -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
View File
@@ -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
View File
@@ -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
)
+3 -2
View File
@@ -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())
}
})
}
+72
View File
@@ -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
}
+87
View File
@@ -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
+63
View File
@@ -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)
}
+33
View File
@@ -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()
}
+17
View File
@@ -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)
}
+12
View File
@@ -0,0 +1,12 @@
package env
func IsWhiteLabel() bool {
return APPNAME != "Filestash"
}
func WhiteLabelText(a, b string) string {
if IsWhiteLabel() {
return b
}
return a
}
+3 -1
View File
@@ -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() {
+7 -5
View File
@@ -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")
}
+8 -3
View File
@@ -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
+5 -3
View File
@@ -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
+9 -7
View File
@@ -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) {
+5 -3
View File
@@ -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
+5 -3
View File
@@ -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
+5 -3
View File
@@ -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
+6 -4
View File
@@ -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
+5 -3
View File
@@ -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
+5 -3
View File
@@ -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"
+25
View File
@@ -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())
}
})
}
+30
View File
@@ -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 {
+81
View File
@@ -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()
+47
View File
@@ -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 {
+46
View File
@@ -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)
}
+1 -1
View File
@@ -3,8 +3,8 @@
package sqlite
import (
"errors"
"database/sql"
"errors"
modernc "modernc.org/sqlite"
)
+1
View File
@@ -4,6 +4,7 @@ import (
"crypto/rsa"
"crypto/tls"
"crypto/x509"
. "github.com/mickael-kerjean/filestash/server/common"
)
+25
View File
@@ -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"
+15
View File
@@ -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
}
}
+2 -1
View File
@@ -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) {
+2 -1
View File
@@ -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
}
+3
View File
@@ -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 ||
+54
View File
@@ -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
}
+84
View File
@@ -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)
}
+16
View File
@@ -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
+79
View File
@@ -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
}
+1 -1
View File
@@ -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 (