diff --git a/CHANGELOG.md b/CHANGELOG.md
index be7348f5..c5fac70d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,22 @@
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.11.0](https://github.com/filebrowser/filebrowser/compare/v2.10.0...v2.11.0) (2020-12-28)
+
+
+### Features
+
+* add sharing management ([#1178](https://github.com/filebrowser/filebrowser/issues/1178)) (closes [#1000](https://github.com/filebrowser/filebrowser/issues/1000)) ([677bce3](https://github.com/filebrowser/filebrowser/commit/677bce376b024d9ff38f34e74243034fe5a1ec3c))
+* download shared subdirectory ([#1184](https://github.com/filebrowser/filebrowser/issues/1184)) ([fb5b28d](https://github.com/filebrowser/filebrowser/commit/fb5b28d9cbdee10d38fcd719b9fd832121be58ef))
+
+
+### Bug Fixes
+
+* check user input to prevent permission elevation ([#1196](https://github.com/filebrowser/filebrowser/issues/1196)) (closes [#1195](https://github.com/filebrowser/filebrowser/issues/1195)) ([f62806f](https://github.com/filebrowser/filebrowser/commit/f62806f6c9e9c7f392d1b747d65b8fe40b313e89))
+* delete extra remove prefix ([#1186](https://github.com/filebrowser/filebrowser/issues/1186)) ([7a5298a](https://github.com/filebrowser/filebrowser/commit/7a5298a7556f7dcc52f59b8ea76d040d3ddc3d12))
+* move files between different volumes (closes [#1177](https://github.com/filebrowser/filebrowser/issues/1177)) ([58835b7](https://github.com/filebrowser/filebrowser/commit/58835b7e535cc96e1c8a5d85821c1545743ca757))
+* recaptcha race condition ([#1176](https://github.com/filebrowser/filebrowser/issues/1176)) ([ac3673e](https://github.com/filebrowser/filebrowser/commit/ac3673e111afac6616af9650ca07028b6c27e6cd))
+
## [2.10.0](https://github.com/filebrowser/filebrowser/compare/v2.9.0...v2.10.0) (2020-11-24)
diff --git a/cmd/root.go b/cmd/root.go
index a8a484f0..75506f78 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -66,6 +66,7 @@ func addServerFlags(flags *pflag.FlagSet) {
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-type-detection-by-header", false, "disables type detection by reading file headers")
}
var rootCmd = &cobra.Command{
@@ -243,6 +244,9 @@ func getRunParams(flags *pflag.FlagSet, st *storage.Storage) *settings.Server {
_, disablePreviewResize := getParamB(flags, "disable-preview-resize")
server.ResizePreview = !disablePreviewResize
+ _, disableTypeDetectionByHeader := getParamB(flags, "disable-type-detection-by-header")
+ server.TypeDetectionByHeader = !disableTypeDetectionByHeader
+
_, disableExec := getParamB(flags, "disable-exec")
server.EnableExec = !disableExec
diff --git a/errors/errors.go b/errors/errors.go
index c79e3783..5ec364c0 100644
--- a/errors/errors.go
+++ b/errors/errors.go
@@ -17,4 +17,5 @@ var (
ErrPermissionDenied = errors.New("permission denied")
ErrInvalidRequestParams = errors.New("invalid request params")
ErrSourceIsParent = errors.New("source is parent")
+ ErrRootUserDeletion = errors.New("user with id 1 can't be deleted")
)
diff --git a/files/file.go b/files/file.go
index d58b2a34..fa102049 100644
--- a/files/file.go
+++ b/files/file.go
@@ -42,11 +42,12 @@ type FileInfo struct {
// FileOptions are the options when getting a file info.
type FileOptions struct {
- Fs afero.Fs
- Path string
- Modify bool
- Expand bool
- Checker rules.Checker
+ Fs afero.Fs
+ Path string
+ Modify bool
+ Expand bool
+ ReadHeader bool
+ Checker rules.Checker
}
// NewFileInfo creates a File object from a path and a given user. This File
@@ -75,13 +76,13 @@ func NewFileInfo(opts FileOptions) (*FileInfo, error) {
if opts.Expand {
if file.IsDir {
- if err := file.readListing(opts.Checker); err != nil { //nolint:shadow
+ if err := file.readListing(opts.Checker, opts.ReadHeader); err != nil { //nolint:shadow
return nil, err
}
return file, nil
}
- err = file.detectType(opts.Modify, true)
+ err = file.detectType(opts.Modify, true, true)
if err != nil {
return nil, err
}
@@ -134,7 +135,7 @@ func (i *FileInfo) Checksum(algo string) error {
//nolint:goconst
//TODO: use constants
-func (i *FileInfo) detectType(modify, saveContent bool) error {
+func (i *FileInfo) detectType(modify, saveContent, readHeader bool) error {
if IsNamedPipe(i.Mode) {
i.Type = "blob"
return nil
@@ -143,6 +144,51 @@ func (i *FileInfo) detectType(modify, saveContent bool) error {
// imagine the situation where a file in a dir with thousands
// of files couldn't be opened: we'd have immediately
// a 500 even though it doesn't matter. So we just log it.
+
+ var buffer []byte
+
+ mimetype := mime.TypeByExtension(i.Extension)
+ if mimetype == "" && readHeader {
+ buffer = i.readFirstBytes()
+ mimetype = http.DetectContentType(buffer)
+ }
+
+ switch {
+ case strings.HasPrefix(mimetype, "video"):
+ i.Type = "video"
+ i.detectSubtitles()
+ return nil
+ case strings.HasPrefix(mimetype, "audio"):
+ i.Type = "audio"
+ return nil
+ case strings.HasPrefix(mimetype, "image"):
+ i.Type = "image"
+ return nil
+ case (strings.HasPrefix(mimetype, "text") || (len(buffer) > 0 && !isBinary(buffer))) && i.Size <= 10*1024*1024: // 10 MB
+ i.Type = "text"
+
+ if !modify {
+ i.Type = "textImmutable"
+ }
+
+ if saveContent {
+ afs := &afero.Afero{Fs: i.Fs}
+ content, err := afs.ReadFile(i.Path)
+ if err != nil {
+ return err
+ }
+
+ i.Content = string(content)
+ }
+ return nil
+ default:
+ i.Type = "blob"
+ }
+
+ return nil
+}
+
+func (i *FileInfo) readFirstBytes() []byte {
reader, err := i.Fs.Open(i.Path)
if err != nil {
log.Print(err)
@@ -159,44 +205,7 @@ func (i *FileInfo) detectType(modify, saveContent bool) error {
return nil
}
- mimetype := mime.TypeByExtension(i.Extension)
- if mimetype == "" {
- mimetype = http.DetectContentType(buffer[:n])
- }
-
- switch {
- case strings.HasPrefix(mimetype, "video"):
- i.Type = "video"
- i.detectSubtitles()
- return nil
- case strings.HasPrefix(mimetype, "audio"):
- i.Type = "audio"
- return nil
- case strings.HasPrefix(mimetype, "image"):
- i.Type = "image"
- return nil
- case isBinary(buffer[:n], n) || i.Size > 10*1024*1024: // 10 MB
- i.Type = "blob"
- return nil
- default:
- i.Type = "text"
-
- if !modify {
- i.Type = "textImmutable"
- }
-
- if saveContent {
- afs := &afero.Afero{Fs: i.Fs}
- content, err := afs.ReadFile(i.Path)
- if err != nil {
- return err
- }
-
- i.Content = string(content)
- }
- }
-
- return nil
+ return buffer[:n]
}
func (i *FileInfo) detectSubtitles() {
@@ -215,7 +224,7 @@ func (i *FileInfo) detectSubtitles() {
}
}
-func (i *FileInfo) readListing(checker rules.Checker) error {
+func (i *FileInfo) readListing(checker rules.Checker, readHeader bool) error {
afs := &afero.Afero{Fs: i.Fs}
dir, err := afs.ReadDir(i.Path)
if err != nil {
@@ -261,7 +270,7 @@ func (i *FileInfo) readListing(checker rules.Checker) error {
} else {
listing.NumFiles++
- err := file.detectType(true, false)
+ err := file.detectType(true, false, readHeader)
if err != nil {
return err
}
diff --git a/files/utils.go b/files/utils.go
index bcaa13f1..f4b0365d 100644
--- a/files/utils.go
+++ b/files/utils.go
@@ -5,7 +5,7 @@ import (
"unicode/utf8"
)
-func isBinary(content []byte, _ int) bool {
+func isBinary(content []byte) bool {
maybeStr := string(content)
runeCnt := utf8.RuneCount(content)
runeIndex := 0
diff --git a/fileutils/file.go b/fileutils/file.go
index 5c0248df..6e6cd2af 100644
--- a/fileutils/file.go
+++ b/fileutils/file.go
@@ -9,6 +9,25 @@ import (
"github.com/spf13/afero"
)
+// MoveFile moves file from src to dst.
+// By default the rename filesystem system call is used. If src and dst point to different volumes
+// the file copy is used as a fallback
+func MoveFile(fs afero.Fs, src, dst string) error {
+ if fs.Rename(src, dst) == nil {
+ return nil
+ }
+ // fallback
+ err := CopyFile(fs, src, dst)
+ if err != nil {
+ _ = fs.Remove(dst)
+ return err
+ }
+ if err := fs.Remove(src); err != nil {
+ return err
+ }
+ return nil
+}
+
// CopyFile copies a file from source to dest and returns
// an error if any.
func CopyFile(fs afero.Fs, source, dest string) error {
@@ -39,14 +58,14 @@ func CopyFile(fs afero.Fs, source, dest string) error {
return err
}
- // Copy the mode if the user can't
- // open the file.
+ // Copy the mode
info, err := fs.Stat(source)
if err != nil {
- err = fs.Chmod(dest, info.Mode())
- if err != nil {
- return err
- }
+ return err
+ }
+ err = fs.Chmod(dest, info.Mode())
+ if err != nil {
+ return err
}
return nil
diff --git a/frontend/src/api/files.js b/frontend/src/api/files.js
index 2b5cf540..d12a4237 100644
--- a/frontend/src/api/files.js
+++ b/frontend/src/api/files.js
@@ -58,7 +58,7 @@ export async function put (url, content = '') {
}
export function download (format, ...files) {
- let url = `${baseURL}/api/raw`
+ let url = store.getters['isSharing'] ? `${baseURL}/api/public/dl/${store.state.hash}` : `${baseURL}/api/raw`
if (files.length === 1) {
url += removePrefix(files[0]) + '?'
diff --git a/frontend/src/api/share.js b/frontend/src/api/share.js
index e14c4e81..6f8a2a17 100644
--- a/frontend/src/api/share.js
+++ b/frontend/src/api/share.js
@@ -1,5 +1,9 @@
import { fetchURL, fetchJSON, removePrefix } from './utils'
+export async function list() {
+ return fetchJSON('/api/shares')
+}
+
export async function getHash(hash) {
return fetchJSON(`/api/public/share/${hash}`)
}
diff --git a/frontend/src/api/utils.js b/frontend/src/api/utils.js
index 617e2258..68337d10 100644
--- a/frontend/src/api/utils.js
+++ b/frontend/src/api/utils.js
@@ -36,6 +36,8 @@ export async function fetchJSON (url, opts) {
export function removePrefix (url) {
if (url.startsWith('/files')) {
url = url.slice(6)
+ } else if (store.getters['isSharing']) {
+ url = url.slice(7 + store.state.hash.length)
}
if (url === '') url = '/'
diff --git a/frontend/src/components/Header.vue b/frontend/src/components/Header.vue
index b9278d10..6d9a1270 100644
--- a/frontend/src/components/Header.vue
+++ b/frontend/src/components/Header.vue
@@ -8,8 +8,8 @@
{{ $t('prompts.deleteMessageShare', {path: hash.path}) }}
+- {{ $t('settings.singleClick') }} -
-