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 @@
-