Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
da03728cd7 | ||
|
|
e735491c57 | ||
|
|
4d830f707f | ||
|
|
f84a6db680 | ||
|
|
a430eb2e60 | ||
|
|
c232d41f90 | ||
|
|
c1e4fd648b | ||
|
|
d5b39a14fd | ||
|
|
e2e1e49130 | ||
|
|
b0f92dd2d7 | ||
|
|
21b0827808 | ||
|
|
d6d84e2b48 | ||
|
|
ca86f91621 | ||
|
|
4bfbf33249 | ||
|
|
f19943a42e | ||
|
|
e74c958862 | ||
|
|
221451a517 |
29
CHANGELOG.md
29
CHANGELOG.md
@@ -2,6 +2,35 @@
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
### [2.33.10](https://github.com/filebrowser/filebrowser/compare/v2.33.9...v2.33.10) (2025-06-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* correctly check if command is allowed when using shell ([4d830f7](https://github.com/filebrowser/filebrowser/commit/4d830f707fc4314741fd431e70c2ce50cd5a3108))
|
||||
* correctly split shell ([f84a6db](https://github.com/filebrowser/filebrowser/commit/f84a6db680b6df1c7c8f06f1816f7e4c9e963668))
|
||||
* ignore linting error ([e735491](https://github.com/filebrowser/filebrowser/commit/e735491c57b12c3b19dd2e4b570723df78f4eb44))
|
||||
|
||||
### [2.33.9](https://github.com/filebrowser/filebrowser/compare/v2.33.8...v2.33.9) (2025-06-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* check exact match on command allow list ([e2e1e49](https://github.com/filebrowser/filebrowser/commit/e2e1e4913085cca8917e0f69171dc28d3c6af1b6))
|
||||
* remove auth token from /api/command ([d5b39a1](https://github.com/filebrowser/filebrowser/commit/d5b39a14fd3fc0d1c364116b41289484df7c27b2))
|
||||
* remove unused import ([c232d41](https://github.com/filebrowser/filebrowser/commit/c232d41f903d3026ec290bbe819b6c59a933048e))
|
||||
|
||||
### [2.33.8](https://github.com/filebrowser/filebrowser/compare/v2.33.7...v2.33.8) (2025-06-25)
|
||||
|
||||
### [2.33.7](https://github.com/filebrowser/filebrowser/compare/v2.33.6...v2.33.7) (2025-06-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* correctly parse negative boolean flags ([221451a](https://github.com/filebrowser/filebrowser/commit/221451a5179c8f139819a315b80d0ecb0e7220c3))
|
||||
* linting issues ([4bfbf33](https://github.com/filebrowser/filebrowser/commit/4bfbf332499fc8aea5f6df6aae1efa0de918d1ae))
|
||||
* linting issues ([e74c958](https://github.com/filebrowser/filebrowser/commit/e74c95886226c0ee429af1860eed21dd1f8601aa))
|
||||
|
||||
### [2.33.6](https://github.com/filebrowser/filebrowser/compare/v2.33.5...v2.33.6) (2025-06-24)
|
||||
|
||||
|
||||
|
||||
@@ -38,6 +38,9 @@ File Browser is a **create-your-own-cloud-kind** of software where you can insta
|
||||
| :----------------------: | :----------------------: | :----------------------: |
|
||||
|  |  |  |
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> The **command execution** functionality has been disabled for all existent and new installations by default from version v2.33.8 and onwards, due to continuous and known security vulnerabilities. You should only use this feature if you are aware of all of the security risks involved. For more up to date information, consult issue [#5199](https://github.com/filebrowser/filebrowser/issues/5199).
|
||||
|
||||
## Install
|
||||
|
||||
|
||||
95
cmd/root.go
95
cmd/root.go
@@ -68,7 +68,7 @@ func addServerFlags(flags *pflag.FlagSet) {
|
||||
flags.Int("img-processors", 4, "image processors count") //nolint:gomnd
|
||||
flags.Bool("disable-thumbnails", false, "disable image thumbnails")
|
||||
flags.Bool("disable-preview-resize", false, "disable resize of image previews")
|
||||
flags.Bool("disable-exec", false, "disables Command Runner feature")
|
||||
flags.Bool("disable-exec", true, "disables Command Runner feature")
|
||||
flags.Bool("disable-type-detection-by-header", false, "disables type detection by reading file headers")
|
||||
}
|
||||
|
||||
@@ -201,42 +201,42 @@ func getRunParams(flags *pflag.FlagSet, st *storage.Storage) *settings.Server {
|
||||
server, err := st.Settings.GetServer()
|
||||
checkErr(err)
|
||||
|
||||
if val, set := getParamB(flags, "root"); set {
|
||||
if val, set := getStringParamB(flags, "root"); set {
|
||||
server.Root = val
|
||||
}
|
||||
|
||||
if val, set := getParamB(flags, "baseurl"); set {
|
||||
if val, set := getStringParamB(flags, "baseurl"); set {
|
||||
server.BaseURL = val
|
||||
}
|
||||
|
||||
if val, set := getParamB(flags, "log"); set {
|
||||
if val, set := getStringParamB(flags, "log"); set {
|
||||
server.Log = val
|
||||
}
|
||||
|
||||
isSocketSet := false
|
||||
isAddrSet := false
|
||||
|
||||
if val, set := getParamB(flags, "address"); set {
|
||||
if val, set := getStringParamB(flags, "address"); set {
|
||||
server.Address = val
|
||||
isAddrSet = isAddrSet || set
|
||||
}
|
||||
|
||||
if val, set := getParamB(flags, "port"); set {
|
||||
if val, set := getStringParamB(flags, "port"); set {
|
||||
server.Port = val
|
||||
isAddrSet = isAddrSet || set
|
||||
}
|
||||
|
||||
if val, set := getParamB(flags, "key"); set {
|
||||
if val, set := getStringParamB(flags, "key"); set {
|
||||
server.TLSKey = val
|
||||
isAddrSet = isAddrSet || set
|
||||
}
|
||||
|
||||
if val, set := getParamB(flags, "cert"); set {
|
||||
if val, set := getStringParamB(flags, "cert"); set {
|
||||
server.TLSCert = val
|
||||
isAddrSet = isAddrSet || set
|
||||
}
|
||||
|
||||
if val, set := getParamB(flags, "socket"); set {
|
||||
if val, set := getStringParamB(flags, "socket"); set {
|
||||
server.Socket = val
|
||||
isSocketSet = isSocketSet || set
|
||||
}
|
||||
@@ -250,33 +250,69 @@ func getRunParams(flags *pflag.FlagSet, st *storage.Storage) *settings.Server {
|
||||
server.Socket = ""
|
||||
}
|
||||
|
||||
_, disableThumbnails := getParamB(flags, "disable-thumbnails")
|
||||
disableThumbnails := getBoolParam(flags, "disable-thumbnails")
|
||||
server.EnableThumbnails = !disableThumbnails
|
||||
|
||||
_, disablePreviewResize := getParamB(flags, "disable-preview-resize")
|
||||
disablePreviewResize := getBoolParam(flags, "disable-preview-resize")
|
||||
server.ResizePreview = !disablePreviewResize
|
||||
|
||||
_, disableTypeDetectionByHeader := getParamB(flags, "disable-type-detection-by-header")
|
||||
disableTypeDetectionByHeader := getBoolParam(flags, "disable-type-detection-by-header")
|
||||
server.TypeDetectionByHeader = !disableTypeDetectionByHeader
|
||||
|
||||
_, disableExec := getParamB(flags, "disable-exec")
|
||||
disableExec := getBoolParam(flags, "disable-exec")
|
||||
server.EnableExec = !disableExec
|
||||
|
||||
if val, set := getParamB(flags, "token-expiration-time"); set {
|
||||
if server.EnableExec {
|
||||
log.Println("WARNING: Command Runner feature enabled!")
|
||||
log.Println("WARNING: This feature has known security vulnerabilities and should not")
|
||||
log.Println("WARNING: you fully understand the risks involved. For more information")
|
||||
log.Println("WARNING: read https://github.com/filebrowser/filebrowser/issues/5199")
|
||||
}
|
||||
|
||||
if val, set := getStringParamB(flags, "token-expiration-time"); set {
|
||||
server.TokenExpirationTime = val
|
||||
}
|
||||
|
||||
return server
|
||||
}
|
||||
|
||||
// getParamB returns a parameter as a string and a boolean to tell if it is different from the default
|
||||
// getBoolParamB returns a parameter as a string and a boolean to tell if it is different from the default
|
||||
//
|
||||
// NOTE: we could simply bind the flags to viper and use IsSet.
|
||||
// Although there is a bug on Viper that always returns true on IsSet
|
||||
// if a flag is binded. Our alternative way is to manually check
|
||||
// the flag and then the value from env/config/gotten by viper.
|
||||
// https://github.com/spf13/viper/pull/331
|
||||
func getParamB(flags *pflag.FlagSet, key string) (string, bool) {
|
||||
func getBoolParamB(flags *pflag.FlagSet, key string) (value, ok bool) {
|
||||
value, _ = flags.GetBool(key)
|
||||
|
||||
// If set on Flags, use it.
|
||||
if flags.Changed(key) {
|
||||
return value, true
|
||||
}
|
||||
|
||||
// If set through viper (env, config), return it.
|
||||
if v.IsSet(key) {
|
||||
return v.GetBool(key), true
|
||||
}
|
||||
|
||||
// Otherwise use default value on flags.
|
||||
return value, false
|
||||
}
|
||||
|
||||
func getBoolParam(flags *pflag.FlagSet, key string) bool {
|
||||
val, _ := getBoolParamB(flags, key)
|
||||
return val
|
||||
}
|
||||
|
||||
// getStringParamB returns a parameter as a string and a boolean to tell if it is different from the default
|
||||
//
|
||||
// NOTE: we could simply bind the flags to viper and use IsSet.
|
||||
// Although there is a bug on Viper that always returns true on IsSet
|
||||
// if a flag is binded. Our alternative way is to manually check
|
||||
// the flag and then the value from env/config/gotten by viper.
|
||||
// https://github.com/spf13/viper/pull/331
|
||||
func getStringParamB(flags *pflag.FlagSet, key string) (string, bool) {
|
||||
value, _ := flags.GetString(key)
|
||||
|
||||
// If set on Flags, use it.
|
||||
@@ -293,8 +329,8 @@ func getParamB(flags *pflag.FlagSet, key string) (string, bool) {
|
||||
return value, false
|
||||
}
|
||||
|
||||
func getParam(flags *pflag.FlagSet, key string) string {
|
||||
val, _ := getParamB(flags, key)
|
||||
func getStringParam(flags *pflag.FlagSet, key string) string {
|
||||
val, _ := getStringParamB(flags, key)
|
||||
return val
|
||||
}
|
||||
|
||||
@@ -349,7 +385,7 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) {
|
||||
}
|
||||
|
||||
var err error
|
||||
if _, noauth := getParamB(flags, "noauth"); noauth {
|
||||
if _, noauth := getStringParamB(flags, "noauth"); noauth {
|
||||
set.AuthMethod = auth.MethodNoAuth
|
||||
err = d.store.Auth.Save(&auth.NoAuth{})
|
||||
} else {
|
||||
@@ -362,27 +398,27 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) {
|
||||
checkErr(err)
|
||||
|
||||
ser := &settings.Server{
|
||||
BaseURL: getParam(flags, "baseurl"),
|
||||
Port: getParam(flags, "port"),
|
||||
Log: getParam(flags, "log"),
|
||||
TLSKey: getParam(flags, "key"),
|
||||
TLSCert: getParam(flags, "cert"),
|
||||
Address: getParam(flags, "address"),
|
||||
Root: getParam(flags, "root"),
|
||||
BaseURL: getStringParam(flags, "baseurl"),
|
||||
Port: getStringParam(flags, "port"),
|
||||
Log: getStringParam(flags, "log"),
|
||||
TLSKey: getStringParam(flags, "key"),
|
||||
TLSCert: getStringParam(flags, "cert"),
|
||||
Address: getStringParam(flags, "address"),
|
||||
Root: getStringParam(flags, "root"),
|
||||
}
|
||||
|
||||
err = d.store.Settings.SaveServer(ser)
|
||||
checkErr(err)
|
||||
|
||||
username := getParam(flags, "username")
|
||||
password := getParam(flags, "password")
|
||||
username := getStringParam(flags, "username")
|
||||
password := getStringParam(flags, "password")
|
||||
|
||||
if password == "" {
|
||||
var pwd string
|
||||
pwd, err = users.RandomPwd()
|
||||
checkErr(err)
|
||||
|
||||
log.Println("Generated random admin password for quick setup:", pwd)
|
||||
log.Println("Randomly generated password for user 'admin':", pwd)
|
||||
|
||||
password, err = users.HashPwd(pwd)
|
||||
checkErr(err)
|
||||
@@ -420,6 +456,7 @@ func initConfig() {
|
||||
v.SetEnvPrefix("FB")
|
||||
v.AutomaticEnv()
|
||||
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||
v.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
|
||||
|
||||
if err := v.ReadInConfig(); err != nil {
|
||||
var configParseError v.ConfigParseError
|
||||
|
||||
@@ -25,7 +25,7 @@ this version.`,
|
||||
flags := cmd.Flags()
|
||||
oldDB := mustGetString(flags, "old.database")
|
||||
oldConf := mustGetString(flags, "old.config")
|
||||
err := importer.Import(oldDB, oldConf, getParam(flags, "database"))
|
||||
err := importer.Import(oldDB, oldConf, getStringParam(flags, "database"))
|
||||
checkErr(err)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/spf13/pflag"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/filebrowser/filebrowser/v2/files"
|
||||
"github.com/filebrowser/filebrowser/v2/settings"
|
||||
"github.com/filebrowser/filebrowser/v2/storage"
|
||||
"github.com/filebrowser/filebrowser/v2/storage/bolt"
|
||||
@@ -86,7 +87,7 @@ func python(fn pythonFunc, cfg pythonConfig) cobraFunc {
|
||||
return func(cmd *cobra.Command, args []string) {
|
||||
data := pythonData{hadDB: true}
|
||||
|
||||
path := getParam(cmd.Flags(), "database")
|
||||
path := getStringParam(cmd.Flags(), "database")
|
||||
absPath, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -105,7 +106,7 @@ func python(fn pythonFunc, cfg pythonConfig) cobraFunc {
|
||||
|
||||
log.Println("Using database: " + absPath)
|
||||
data.hadDB = exists
|
||||
db, err := storm.Open(path)
|
||||
db, err := storm.Open(path, storm.BoltOptions(files.PermFile, nil))
|
||||
checkErr(err)
|
||||
defer db.Close()
|
||||
data.store, err = bolt.NewStorage(db)
|
||||
|
||||
@@ -27,8 +27,8 @@ import (
|
||||
"github.com/filebrowser/filebrowser/v2/rules"
|
||||
)
|
||||
|
||||
const PermFile = 0644
|
||||
const PermDir = 0755
|
||||
const PermFile = 0640
|
||||
const PermDir = 0750
|
||||
|
||||
var (
|
||||
reSubDirs = regexp.MustCompile("(?i)^sub(s|titles)$")
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"ace-builds": "^1.37.5",
|
||||
"core-js": "^3.40.0",
|
||||
"dayjs": "^1.11.10",
|
||||
"dompurify": "^3.2.6",
|
||||
"epubjs": "^0.3.93",
|
||||
"filesize": "^10.1.1",
|
||||
"js-base64": "^3.7.7",
|
||||
|
||||
30
frontend/pnpm-lock.yaml
generated
30
frontend/pnpm-lock.yaml
generated
@@ -26,6 +26,9 @@ importers:
|
||||
dayjs:
|
||||
specifier: ^1.11.10
|
||||
version: 1.11.13
|
||||
dompurify:
|
||||
specifier: ^3.2.6
|
||||
version: 3.2.6
|
||||
epubjs:
|
||||
specifier: ^0.3.93
|
||||
version: 0.3.93
|
||||
@@ -946,8 +949,8 @@ packages:
|
||||
resolution: {integrity: sha512-dF2iMMy8P9uKVHV/20LA1ulFLL+MKSbfMiixSmn6fpwqzvix38OIc7ebgnFbBqElvghZCW9ACtzKTGKsTGTWGA==}
|
||||
engines: {node: '>= 16'}
|
||||
|
||||
'@intlify/shared@11.1.3':
|
||||
resolution: {integrity: sha512-pTFBgqa/99JRA2H1qfyqv97MKWJrYngXBA/I0elZcYxvJgcCw3mApAoPW3mJ7vx3j+Ti0FyKUFZ4hWxdjKaxvA==}
|
||||
'@intlify/shared@11.1.7':
|
||||
resolution: {integrity: sha512-4yZeMt2Aa/7n5Ehy4KalUlvt3iRLcg1tq9IBVfOgkyWFArN4oygn6WxgGIFibP3svpaH8DarbNaottq+p0gUZQ==}
|
||||
engines: {node: '>= 16'}
|
||||
|
||||
'@intlify/shared@12.0.0-alpha.2':
|
||||
@@ -1174,6 +1177,9 @@ packages:
|
||||
'@types/node@22.10.10':
|
||||
resolution: {integrity: sha512-X47y/mPNzxviAGY5TcYPtYL8JsY3kAq2n8fMmKoRCxq/c4v4pyGNCzM2R6+M5/umG4ZfHuT+sgqDYqWc9rJ6ww==}
|
||||
|
||||
'@types/trusted-types@2.0.7':
|
||||
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
|
||||
|
||||
'@types/web-bluetooth@0.0.20':
|
||||
resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==}
|
||||
|
||||
@@ -1602,6 +1608,9 @@ packages:
|
||||
dom-walk@0.1.2:
|
||||
resolution: {integrity: sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==}
|
||||
|
||||
dompurify@3.2.6:
|
||||
resolution: {integrity: sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==}
|
||||
|
||||
electron-to-chromium@1.5.67:
|
||||
resolution: {integrity: sha512-nz88NNBsD7kQSAGGJyp8hS6xSPtWwqNogA0mjtc2nUYeEf3nURK9qpV18TuBdDmEDgVWotS8Wkzf+V52dSQ/LQ==}
|
||||
|
||||
@@ -3628,7 +3637,7 @@ snapshots:
|
||||
|
||||
'@intlify/shared@11.1.2': {}
|
||||
|
||||
'@intlify/shared@11.1.3': {}
|
||||
'@intlify/shared@11.1.7': {}
|
||||
|
||||
'@intlify/shared@12.0.0-alpha.2': {}
|
||||
|
||||
@@ -3636,8 +3645,8 @@ snapshots:
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.4.1(eslint@9.19.0)
|
||||
'@intlify/bundle-utils': 10.0.0(vue-i18n@11.1.2(vue@3.5.13(typescript@5.6.3)))
|
||||
'@intlify/shared': 11.1.3
|
||||
'@intlify/vue-i18n-extensions': 8.0.0(@intlify/shared@11.1.3)(@vue/compiler-dom@3.5.13)(vue-i18n@11.1.2(vue@3.5.13(typescript@5.6.3)))(vue@3.5.13(typescript@5.6.3))
|
||||
'@intlify/shared': 11.1.7
|
||||
'@intlify/vue-i18n-extensions': 8.0.0(@intlify/shared@11.1.7)(@vue/compiler-dom@3.5.13)(vue-i18n@11.1.2(vue@3.5.13(typescript@5.6.3)))(vue@3.5.13(typescript@5.6.3))
|
||||
'@rollup/pluginutils': 5.1.4(rollup@4.40.1)
|
||||
'@typescript-eslint/scope-manager': 8.21.0
|
||||
'@typescript-eslint/typescript-estree': 8.21.0(typescript@5.6.3)
|
||||
@@ -3659,11 +3668,11 @@ snapshots:
|
||||
- supports-color
|
||||
- typescript
|
||||
|
||||
'@intlify/vue-i18n-extensions@8.0.0(@intlify/shared@11.1.3)(@vue/compiler-dom@3.5.13)(vue-i18n@11.1.2(vue@3.5.13(typescript@5.6.3)))(vue@3.5.13(typescript@5.6.3))':
|
||||
'@intlify/vue-i18n-extensions@8.0.0(@intlify/shared@11.1.7)(@vue/compiler-dom@3.5.13)(vue-i18n@11.1.2(vue@3.5.13(typescript@5.6.3)))(vue@3.5.13(typescript@5.6.3))':
|
||||
dependencies:
|
||||
'@babel/parser': 7.26.7
|
||||
optionalDependencies:
|
||||
'@intlify/shared': 11.1.3
|
||||
'@intlify/shared': 11.1.7
|
||||
'@vue/compiler-dom': 3.5.13
|
||||
vue: 3.5.13(typescript@5.6.3)
|
||||
vue-i18n: 11.1.2(vue@3.5.13(typescript@5.6.3))
|
||||
@@ -3812,6 +3821,9 @@ snapshots:
|
||||
dependencies:
|
||||
undici-types: 6.20.0
|
||||
|
||||
'@types/trusted-types@2.0.7':
|
||||
optional: true
|
||||
|
||||
'@types/web-bluetooth@0.0.20': {}
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.21.0(@typescript-eslint/parser@8.21.0(eslint@9.19.0)(typescript@5.6.3))(eslint@9.19.0)(typescript@5.6.3)':
|
||||
@@ -4282,6 +4294,10 @@ snapshots:
|
||||
|
||||
dom-walk@0.1.2: {}
|
||||
|
||||
dompurify@3.2.6:
|
||||
optionalDependencies:
|
||||
'@types/trusted-types': 2.0.7
|
||||
|
||||
electron-to-chromium@1.5.67: {}
|
||||
|
||||
emoji-regex@8.0.0: {}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { removePrefix } from "./utils";
|
||||
import { baseURL } from "@/utils/constants";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { removePrefix } from "./utils";
|
||||
|
||||
const ssl = window.location.protocol === "https:";
|
||||
const protocol = ssl ? "wss:" : "ws:";
|
||||
@@ -11,10 +10,8 @@ export default function command(
|
||||
onmessage: WebSocket["onmessage"],
|
||||
onclose: WebSocket["onclose"]
|
||||
) {
|
||||
const authStore = useAuthStore();
|
||||
|
||||
url = removePrefix(url);
|
||||
url = `${protocol}//${window.location.host}${baseURL}/api/command${url}?auth=${authStore.jwt}`;
|
||||
url = `${protocol}//${window.location.host}${baseURL}/api/command${url}`;
|
||||
|
||||
const conn = new window.WebSocket(url);
|
||||
conn.onopen = () => conn.send(command);
|
||||
|
||||
@@ -41,6 +41,7 @@ import url from "@/utils/url";
|
||||
import ace, { Ace, version as ace_version } from "ace-builds";
|
||||
import modelist from "ace-builds/src-noconflict/ext-modelist";
|
||||
import "ace-builds/src-noconflict/ext-language_tools";
|
||||
import DOMPurify from "dompurify";
|
||||
|
||||
import HeaderBar from "@/components/header/HeaderBar.vue";
|
||||
import Action from "@/components/header/Action.vue";
|
||||
@@ -83,7 +84,7 @@ onMounted(() => {
|
||||
if (isMarkdownFile && isPreview.value) {
|
||||
const new_value = editor.value?.getValue() || "";
|
||||
try {
|
||||
previewContent.value = await marked(new_value);
|
||||
previewContent.value = DOMPurify.sanitize(await marked(new_value));
|
||||
} catch (error) {
|
||||
console.error("Failed to convert content to HTML:", error);
|
||||
previewContent.value = "";
|
||||
|
||||
@@ -321,7 +321,10 @@ const save = async () => {
|
||||
.filter((cmd: string) => cmd !== "");
|
||||
}
|
||||
}
|
||||
newSettings.shell = shellValue.value.split("\n");
|
||||
newSettings.shell = shellValue.value
|
||||
.trim()
|
||||
.split(" ")
|
||||
.filter((s) => s !== "");
|
||||
|
||||
if (newSettings.branding.theme !== getTheme()) {
|
||||
setTheme(newSettings.branding.theme);
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -60,7 +61,16 @@ var commandsHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *d
|
||||
}
|
||||
}
|
||||
|
||||
command, err := runner.ParseCommand(d.settings, raw)
|
||||
// Fail fast
|
||||
if !d.server.EnableExec || !d.user.Perm.Execute {
|
||||
if err := conn.WriteMessage(websocket.TextMessage, cmdNotAllowed); err != nil { //nolint:govet
|
||||
wsErr(conn, r, http.StatusInternalServerError, err)
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
command, name, err := runner.ParseCommand(d.settings, raw)
|
||||
if err != nil {
|
||||
if err := conn.WriteMessage(websocket.TextMessage, []byte(err.Error())); err != nil { //nolint:govet
|
||||
wsErr(conn, r, http.StatusInternalServerError, err)
|
||||
@@ -68,7 +78,7 @@ var commandsHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *d
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
if !d.server.EnableExec || !d.user.CanExecute(command[0]) {
|
||||
if !slices.Contains(d.user.Commands, name) {
|
||||
if err := conn.WriteMessage(websocket.TextMessage, cmdNotAllowed); err != nil { //nolint:govet
|
||||
wsErr(conn, r, http.StatusInternalServerError, err)
|
||||
}
|
||||
|
||||
@@ -1,33 +1,24 @@
|
||||
package runner
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
|
||||
"github.com/filebrowser/filebrowser/v2/settings"
|
||||
)
|
||||
|
||||
// ParseCommand parses the command taking in account if the current
|
||||
// instance uses a shell to run the commands or just calls the binary
|
||||
// directyly.
|
||||
func ParseCommand(s *settings.Settings, raw string) ([]string, error) {
|
||||
var command []string
|
||||
// directly.
|
||||
func ParseCommand(s *settings.Settings, raw string) (command []string, name string, err error) {
|
||||
name, args, err := SplitCommandAndArgs(raw)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(s.Shell) == 0 || s.Shell[0] == "" {
|
||||
cmd, args, err := SplitCommandAndArgs(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = exec.LookPath(cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
command = append(command, cmd)
|
||||
command = append(command, name)
|
||||
command = append(command, args...)
|
||||
} else {
|
||||
command = append(s.Shell, raw) //nolint:gocritic
|
||||
}
|
||||
|
||||
return command, nil
|
||||
return command, name, nil
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ func (r *Runner) exec(raw, evt, path, dst string, user *users.User) error {
|
||||
raw = strings.TrimSpace(strings.TrimSuffix(raw, "&"))
|
||||
}
|
||||
|
||||
command, err := ParseCommand(r.Settings, raw)
|
||||
command, _, err := ParseCommand(r.Settings, raw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package users
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
|
||||
@@ -104,18 +103,3 @@ func (u *User) Clean(baseScope string, fields ...string) error {
|
||||
func (u *User) FullPath(path string) string {
|
||||
return afero.FullBaseFsPath(u.Fs.(*afero.BasePathFs), path)
|
||||
}
|
||||
|
||||
// CanExecute checks if an user can execute a specific command.
|
||||
func (u *User) CanExecute(command string) bool {
|
||||
if !u.Perm.Execute {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, cmd := range u.Commands {
|
||||
if regexp.MustCompile(cmd).MatchString(command) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user