Compare commits

...

35 Commits

Author SHA1 Message Date
Oleg Lobanov
f0bc9167b1 chore(release): 2.17.0 2021-08-21 16:51:34 +02:00
Ramires Viana
23d646c456 fix: escape quote on index template
fixes #1501
2021-08-20 14:43:06 +02:00
Ramires Viana
76add9e527 feat: open file option on preview 2021-08-20 14:43:06 +02:00
Ramires Viana
c63cc5a2d2 fix: file caching directive 2021-08-20 14:43:06 +02:00
Andrew Kennedy
25c8788390 fix: 401 error in share view open file button (#1495) 2021-08-19 14:35:24 +02:00
Oleg Lobanov
aa52b07bb1 chore(release): 2.16.1 2021-08-04 11:44:29 +02:00
Oleg Lobanov
76b466f649 fix: check symlink target type (closes #1488) 2021-08-04 11:44:02 +02:00
Oleg Lobanov
8ecc2da947 chore(release): 2.16.0 2021-07-26 13:03:53 +02:00
Oleg Lobanov
8650d2ffe7 fix: failure on broken symlink deletion 2021-07-26 13:02:11 +02:00
Oleg Lobanov
34d7d2c8c4 chore: upgrade golangci-lint 2021-07-26 12:00:05 +02:00
Oleg Lobanov
201329abce chore: add Content-Security-Policy header 2021-07-26 11:08:39 +02:00
Oleg Lobanov
f2b5dd3787 chore: don't break folder download if any file processing causes an error 2021-07-26 10:41:56 +02:00
Oleg Lobanov
5072bbb2cb fix: break resource create/update handlers on error (closes #1464) 2021-07-24 15:33:54 +02:00
Oleg Lobanov
6b19ab6613 fix: don't remove files on unsuccessful updates (closes #1456) 2021-07-24 15:32:24 +02:00
Oleg Lobanov
730be5ef6b Create SECURITY.md 2021-07-03 16:56:27 +02:00
laggardkernel
46ee595389 fix: short commit sha and typo fix in Makefile (#1411) 2021-05-25 11:29:38 +02:00
Oleg Lobanov
dee465ab86 Merge pull request #1373 from ramiresviana/fixes-9
Some fixes
2021-05-03 23:29:12 +02:00
Ramires Viana
209f9fa77f fix: omit file content 2021-04-23 12:04:02 +00:00
Ramires Viana
ba8c09f454 feat: show more button on share 2021-04-23 11:59:34 +00:00
Ramires Viana
16a34defc0 feat: file name on page title 2021-04-23 11:59:04 +00:00
Ramires Viana
7d1e03075d feat: mod time title on file info 2021-04-23 11:57:56 +00:00
Ramires Viana
1c25f6ee69 feat: open file option on share 2021-04-23 11:55:56 +00:00
Ramires Viana
aa172b8bb5 feat: gzip encoding for static js files 2021-04-22 12:48:45 +00:00
Ramires Viana
4711e7bcd5 chore: set public path on the fly 2021-04-20 19:51:10 +00:00
Ramires Viana
8a47aee137 chore: split preview creation logic 2021-04-19 13:16:48 +00:00
Ramires Viana
190cb99a79 feat: browser cache directives 2021-04-19 12:49:40 +00:00
Ramires Viana
603203848a feat: display error messages on settings 2021-04-16 15:07:05 +00:00
Ramires Viana
5e6f14b5dc feat: message for connection error 2021-04-16 14:01:10 +00:00
Ramires Viana
976eb5583d feat: loading spinner on views navigation 2021-04-16 12:47:50 +00:00
Ramires Viana
b92152693f chore: split action on resource patch handler 2021-04-16 12:04:06 +00:00
Ramires Viana
7ec24d9d77 feat: support for IE11 browser 2021-04-15 12:28:19 +00:00
Ramires Viana
20ebbf6611 fix: copying files with special characters 2021-04-15 11:50:30 +00:00
Ramires Viana
ba7e71a7c3 fix: inconsistent double click on listing item 2021-04-14 15:29:06 +00:00
Ramires Viana
8973c4598f fix: delete image cache when moving 2021-04-14 15:20:38 +00:00
Ramires Viana
18889ad725 fix: no items displayed on file listing 2021-04-14 11:51:33 +00:00
50 changed files with 1271 additions and 353 deletions

View File

@@ -6,6 +6,8 @@ linters-settings:
funlen: funlen:
lines: 100 lines: 100
statements: 50 statements: 50
gci:
local-prefixes: github.com/filebrowser/filebrowser
goconst: goconst:
min-len: 2 min-len: 2
min-occurrences: 2 min-occurrences: 2
@@ -26,8 +28,6 @@ linters-settings:
min-complexity: 15 min-complexity: 15
goimports: goimports:
local-prefixes: github.com/filebrowser/filebrowser local-prefixes: github.com/filebrowser/filebrowser
golint:
min-confidence: 0
gomnd: gomnd:
settings: settings:
mnd: mnd:
@@ -58,26 +58,25 @@ linters:
- dogsled - dogsled
- dupl - dupl
- errcheck - errcheck
- exportloopref
- exhaustive
- funlen - funlen
- gochecknoinits - gochecknoinits
- goconst - goconst
- gocritic - gocritic
- gocyclo - gocyclo
- goimports - goimports
- golint
- gomnd - gomnd
- goprintffuncname - goprintffuncname
- gosec - gosec
- gosimple - gosimple
- govet - govet
- ineffassign - ineffassign
- interfacer
- lll - lll
- misspell - misspell
- nakedret - nakedret
- nolintlint - nolintlint
- rowserrcheck - rowserrcheck
- scopelint
- staticcheck - staticcheck
- structcheck - structcheck
- stylecheck - stylecheck
@@ -89,19 +88,6 @@ linters:
- whitespace - whitespace
- prealloc - prealloc
# don't enable:
# - asciicheck
# - exhaustive (TODO: enable after next release; current release at time of writing is v1.27)
# - gochecknoglobals
# - gocognit
# - godot
# - godox
# - goerr113
# - maligned
# - nestif
# - testpackage
# - wsl
issues: issues:
exclude-rules: exclude-rules:
- path: cmd/.*.go - path: cmd/.*.go
@@ -118,6 +104,9 @@ issues:
- text: "Auther" - text: "Auther"
linters: linters:
- misspell - misspell
- text: "strconv.Parse"
linters:
- gomnd
run: run:
skip-dirs: skip-dirs:

View File

@@ -2,6 +2,56 @@
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. 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.17.0](https://github.com/filebrowser/filebrowser/compare/v2.16.1...v2.17.0) (2021-08-21)
### Features
* open file option on preview ([76add9e](https://github.com/filebrowser/filebrowser/commit/76add9e5274b0373c6b983e3b20e387a14ea6c9e))
### Bug Fixes
* 401 error in share view open file button ([#1495](https://github.com/filebrowser/filebrowser/issues/1495)) ([25c8788](https://github.com/filebrowser/filebrowser/commit/25c87883908babde073390a2e2320a8e5880a87c))
* escape quote on index template ([23d646c](https://github.com/filebrowser/filebrowser/commit/23d646c456876d06cf48e71c1e57b69de99511f0)), closes [#1501](https://github.com/filebrowser/filebrowser/issues/1501)
* file caching directive ([c63cc5a](https://github.com/filebrowser/filebrowser/commit/c63cc5a2d25909cc4e2f2e7235f276ec66c32bf2))
### [2.16.1](https://github.com/filebrowser/filebrowser/compare/v2.16.0...v2.16.1) (2021-08-04)
### Bug Fixes
* check symlink target type (closes [#1488](https://github.com/filebrowser/filebrowser/issues/1488)) ([76b466f](https://github.com/filebrowser/filebrowser/commit/76b466f6492e74cf13e66a33e7e5f597ac92b240))
## [2.16.0](https://github.com/filebrowser/filebrowser/compare/v2.15.0...v2.16.0) (2021-07-26)
### Features
* browser cache directives ([190cb99](https://github.com/filebrowser/filebrowser/commit/190cb99a79a0d438eca2da13539f8c6449ad73ac))
* display error messages on settings ([6032038](https://github.com/filebrowser/filebrowser/commit/603203848a8b2221158088b6d849609db4c0c46c))
* file name on page title ([16a34de](https://github.com/filebrowser/filebrowser/commit/16a34defc02554a77c6ac47b9e17e69d098a09fe))
* gzip encoding for static js files ([aa172b8](https://github.com/filebrowser/filebrowser/commit/aa172b8bb5f17d5f5cb9666bfb5ee650d8091fb5))
* loading spinner on views navigation ([976eb55](https://github.com/filebrowser/filebrowser/commit/976eb5583dae474125fd7ddec5dc19b6c291f98f))
* message for connection error ([5e6f14b](https://github.com/filebrowser/filebrowser/commit/5e6f14b5dcb9c5efdf526f1346e09c2d0b2f6974))
* mod time title on file info ([7d1e030](https://github.com/filebrowser/filebrowser/commit/7d1e03075d2c27148f60813defa0f68403d1d3c2))
* open file option on share ([1c25f6e](https://github.com/filebrowser/filebrowser/commit/1c25f6ee69bd71eed82af7020006d0e27537a967))
* show more button on share ([ba8c09f](https://github.com/filebrowser/filebrowser/commit/ba8c09f454feeadf4a1e97547a34151a81b389d5))
* support for IE11 browser ([7ec24d9](https://github.com/filebrowser/filebrowser/commit/7ec24d9d7794fa37825f64ca2d1575f568fb1362))
### Bug Fixes
* break resource create/update handlers on error (closes [#1464](https://github.com/filebrowser/filebrowser/issues/1464)) ([5072bbb](https://github.com/filebrowser/filebrowser/commit/5072bbb2cbf5b29d041629faa8367f15e4d145a2))
* copying files with special characters ([20ebbf6](https://github.com/filebrowser/filebrowser/commit/20ebbf6611b734371426fb1b9cb5e388be90bf7e))
* delete image cache when moving ([8973c45](https://github.com/filebrowser/filebrowser/commit/8973c4598ff817647f1f1ad6ee36480054cd2776))
* don't remove files on unsuccessful updates (closes [#1456](https://github.com/filebrowser/filebrowser/issues/1456)) ([6b19ab6](https://github.com/filebrowser/filebrowser/commit/6b19ab6613b12be7f075299cd98f4b41d43827c7))
* failure on broken symlink deletion ([8650d2f](https://github.com/filebrowser/filebrowser/commit/8650d2ffe7a29cbafa800efcecbf6a61598a9f0c))
* inconsistent double click on listing item ([ba7e71a](https://github.com/filebrowser/filebrowser/commit/ba7e71a7c3b0cc71012e5adf94b1c642e554972e))
* no items displayed on file listing ([18889ad](https://github.com/filebrowser/filebrowser/commit/18889ad725f7f7e5a7e3f7abcf156487556dbeaf))
* omit file content ([209f9fa](https://github.com/filebrowser/filebrowser/commit/209f9fa77f751054512355f2b74b9b7258465d0b))
* short commit sha and typo fix in Makefile ([#1411](https://github.com/filebrowser/filebrowser/issues/1411)) ([46ee595](https://github.com/filebrowser/filebrowser/commit/46ee59538914dc2859f0da6b32e2d062d0a01b10))
## [2.15.0](https://github.com/filebrowser/filebrowser/compare/v2.14.1...v2.15.0) (2021-04-06) ## [2.15.0](https://github.com/filebrowser/filebrowser/compare/v2.14.1...v2.15.0) (2021-04-06)

View File

@@ -20,10 +20,10 @@ MODULE = $(shell env GO111MODULE=on $(GO) list -m)
DATE ?= $(shell date +%FT%T%z) DATE ?= $(shell date +%FT%T%z)
VERSION ?= $(shell git describe --tags --always --match=v* 2> /dev/null || \ VERSION ?= $(shell git describe --tags --always --match=v* 2> /dev/null || \
cat $(CURDIR)/.version 2> /dev/null || echo v0) cat $(CURDIR)/.version 2> /dev/null || echo v0)
VERSION_HASH = $(shell git rev-parse HEAD) VERSION_HASH = $(shell git rev-parse --short HEAD)
BRANCH = $(shell git rev-parse --abbrev-ref HEAD) BRANCH = $(shell git rev-parse --abbrev-ref HEAD)
LDFLAGS += -X "$(MODULE)/varsion.Version=$(VERSION)" -X "$(MODULE)/varsion.CommitSHA=$(VERSION_HASH)" LDFLAGS += -X "$(MODULE)/version.Version=$(VERSION)" -X "$(MODULE)/version.CommitSHA=$(VERSION_HASH)"
# tools # tools
$(BIN): $(BIN):
@@ -32,10 +32,10 @@ $(BIN)/%: | $(BIN) ; $(info $(M) installing $(PACKAGE)…)
$Q env GOBIN=$(BIN) $(GO) install $(PACKAGE) $Q env GOBIN=$(BIN) $(GO) install $(PACKAGE)
GOLANGCI_LINT = $(BIN)/golangci-lint GOLANGCI_LINT = $(BIN)/golangci-lint
$(BIN)/golangci-lint: PACKAGE=github.com/golangci/golangci-lint/cmd/golangci-lint@v1.37.1 $(BIN)/golangci-lint: PACKAGE=github.com/golangci/golangci-lint/cmd/golangci-lint@v1.41.1
GOIMPORTS = $(BIN)/goimports GOIMPORTS = $(BIN)/goimports
$(BIN)/goimports: PACKAGE=golang.org/x/tools/cmd/goimports@v0.1.0 $(BIN)/goimports: PACKAGE=golang.org/x/tools/cmd/goimports@v0.1.5
## build: Build ## build: Build
.PHONY: build .PHONY: build

26
SECURITY.md Normal file
View File

@@ -0,0 +1,26 @@
# Security Policy
## Supported Versions
Use this section to tell people about which versions of your project are
currently being supported with security updates.
| Version | Supported |
| ------- | ------------------ |
| 2.x | :white_check_mark: |
| < 2.0 | :x: |
## Reporting a Vulnerability
Vulnerabilities should be reported to filebrowser@googlegroups.com - which is a private, maintainer-only group. Maintainers will attempt to respond to/confirm reports within 2-3 days, but if you believe your report to be "critical" to user safety and security, please note as such in the subject. We have tens of thousands of users using our software, and take security vulnerabilities seriously.
When reporting an issue, where possible, please provide at least:
* The commit version the issue was identified at
* A proof of concept (plaintext; no binaries)
* Steps to reproduce
* Your recommended remediation(s), if any.
The FileBrowser team is a volunteer-only effort, and may reach back out for clarification.
> Note: Please do not open public issues for security issues, as GitHub does not provide facility for private issues, and deleting the issue makes it hard to triage/respond back to the reporter.

View File

@@ -121,7 +121,7 @@ func getAuthentication(flags *pflag.FlagSet, defaults ...interface{}) (settings.
} }
func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Auther) { func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Auther) {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) //nolint:gomnd
fmt.Fprintf(w, "Sign up:\t%t\n", set.Signup) fmt.Fprintf(w, "Sign up:\t%t\n", set.Signup)
fmt.Fprintf(w, "Create User Dir:\t%t\n", set.CreateUserDir) fmt.Fprintf(w, "Create User Dir:\t%t\n", set.CreateUserDir)

View File

@@ -61,10 +61,10 @@ func addServerFlags(flags *pflag.FlagSet) {
flags.StringP("key", "k", "", "tls key") flags.StringP("key", "k", "", "tls key")
flags.StringP("root", "r", ".", "root to prepend to relative paths") flags.StringP("root", "r", ".", "root to prepend to relative paths")
flags.String("socket", "", "socket to listen to (cannot be used with address, port, cert nor key flags)") flags.String("socket", "", "socket to listen to (cannot be used with address, port, cert nor key flags)")
flags.Uint32("socket-perm", 0666, "unix socket file permissions") flags.Uint32("socket-perm", 0666, "unix socket file permissions") //nolint:gomnd
flags.StringP("baseurl", "b", "", "base url") flags.StringP("baseurl", "b", "", "base url")
flags.String("cache-dir", "", "file cache directory (disabled if empty)") flags.String("cache-dir", "", "file cache directory (disabled if empty)")
flags.Int("img-processors", 4, "image processors count") flags.Int("img-processors", 4, "image processors count") //nolint:gomnd
flags.Bool("disable-thumbnails", false, "disable image thumbnails") flags.Bool("disable-thumbnails", false, "disable image thumbnails")
flags.Bool("disable-preview-resize", false, "disable resize of image previews") flags.Bool("disable-preview-resize", false, "disable resize of image previews")
flags.Bool("disable-exec", false, "disables Command Runner feature") flags.Bool("disable-exec", false, "disables Command Runner feature")
@@ -128,7 +128,7 @@ user created with the credentials from options "username" and "password".`,
cacheDir, err := cmd.Flags().GetString("cache-dir") cacheDir, err := cmd.Flags().GetString("cache-dir")
checkErr(err) checkErr(err)
if cacheDir != "" { if cacheDir != "" {
if err := os.MkdirAll(cacheDir, 0700); err != nil { //nolint:govet if err := os.MkdirAll(cacheDir, 0700); err != nil { //nolint:govet,gomnd
log.Fatalf("can't make directory %s: %s", cacheDir, err) log.Fatalf("can't make directory %s: %s", cacheDir, err)
} }
fileCache = diskcache.New(afero.NewOsFs(), cacheDir) fileCache = diskcache.New(afero.NewOsFs(), cacheDir)

View File

@@ -28,7 +28,7 @@ You can also specify an optional parameter (index_end) so
you can remove all commands from 'index' to 'index_end', you can remove all commands from 'index' to 'index_end',
including 'index_end'.`, including 'index_end'.`,
Args: func(cmd *cobra.Command, args []string) error { Args: func(cmd *cobra.Command, args []string) error {
if err := cobra.RangeArgs(1, 2)(cmd, args); err != nil { if err := cobra.RangeArgs(1, 2)(cmd, args); err != nil { //nolint:gomnd
return err return err
} }

View File

@@ -26,7 +26,7 @@ var usersCmd = &cobra.Command{
} }
func printUsers(usrs []*users.User) { func printUsers(usrs []*users.User) {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) //nolint:gomnd
fmt.Fprintln(w, "ID\tUsername\tScope\tLocale\tV. Mode\tS.Click\tAdmin\tExecute\tCreate\tRename\tModify\tDelete\tShare\tDownload\tPwd Lock") fmt.Fprintln(w, "ID\tUsername\tScope\tLocale\tV. Mode\tS.Click\tAdmin\tExecute\tCreate\tRename\tModify\tDelete\tShare\tDownload\tPwd Lock")
for _, u := range usrs { for _, u := range usrs {
@@ -53,7 +53,7 @@ func printUsers(usrs []*users.User) {
} }
func parseUsernameOrID(arg string) (username string, id uint) { func parseUsernameOrID(arg string) (username string, id uint) {
id64, err := strconv.ParseUint(arg, 10, 0) id64, err := strconv.ParseUint(arg, 10, 64) //nolint:gomnd
if err != nil { if err != nil {
return arg, 0 return arg, 0
} }

View File

@@ -72,7 +72,7 @@ func dbExists(path string) (bool, error) {
d := filepath.Dir(path) d := filepath.Dir(path)
_, err = os.Stat(d) _, err = os.Stat(d)
if os.IsNotExist(err) { if os.IsNotExist(err) {
if err := os.MkdirAll(d, 0700); err != nil { //nolint:govet if err := os.MkdirAll(d, 0700); err != nil { //nolint:govet,gomnd
return false, err return false, err
} }
return false, nil return false, nil

View File

@@ -37,11 +37,11 @@ func (f *FileCache) Store(ctx context.Context, key string, value []byte) error {
defer mu.Unlock() defer mu.Unlock()
fileName := f.getFileName(key) fileName := f.getFileName(key)
if err := f.fs.MkdirAll(filepath.Dir(fileName), 0700); err != nil { if err := f.fs.MkdirAll(filepath.Dir(fileName), 0700); err != nil { //nolint:gomnd
return err return err
} }
if err := afero.WriteFile(f.fs, fileName, value, 0700); err != nil { if err := afero.WriteFile(f.fs, fileName, value, 0700); err != nil { //nolint:gomnd
return err return err
} }

View File

@@ -34,6 +34,7 @@ type FileInfo struct {
ModTime time.Time `json:"modified"` ModTime time.Time `json:"modified"`
Mode os.FileMode `json:"mode"` Mode os.FileMode `json:"mode"`
IsDir bool `json:"isDir"` IsDir bool `json:"isDir"`
IsSymlink bool `json:"isSymlink"`
Type string `json:"type"` Type string `json:"type"`
Subtitles []string `json:"subtitles,omitempty"` Subtitles []string `json:"subtitles,omitempty"`
Content string `json:"content,omitempty"` Content string `json:"content,omitempty"`
@@ -50,6 +51,7 @@ type FileOptions struct {
ReadHeader bool ReadHeader bool
Token string Token string
Checker rules.Checker Checker rules.Checker
Content bool
} }
// NewFileInfo creates a File object from a path and a given user. This File // NewFileInfo creates a File object from a path and a given user. This File
@@ -60,12 +62,73 @@ func NewFileInfo(opts FileOptions) (*FileInfo, error) {
return nil, os.ErrPermission return nil, os.ErrPermission
} }
info, err := opts.Fs.Stat(opts.Path) file, err := stat(opts)
if err != nil { if err != nil {
return nil, err return nil, err
} }
file := &FileInfo{ if opts.Expand {
if file.IsDir {
if err := file.readListing(opts.Checker, opts.ReadHeader); err != nil { //nolint:govet
return nil, err
}
return file, nil
}
err = file.detectType(opts.Modify, opts.Content, true)
if err != nil {
return nil, err
}
}
return file, err
}
func stat(opts FileOptions) (*FileInfo, error) {
var file *FileInfo
if lstaterFs, ok := opts.Fs.(afero.Lstater); ok {
info, _, err := lstaterFs.LstatIfPossible(opts.Path)
if err != nil {
return nil, err
}
file = &FileInfo{
Fs: opts.Fs,
Path: opts.Path,
Name: info.Name(),
ModTime: info.ModTime(),
Mode: info.Mode(),
IsDir: info.IsDir(),
IsSymlink: IsSymlink(info.Mode()),
Size: info.Size(),
Extension: filepath.Ext(info.Name()),
Token: opts.Token,
}
}
// regular file
if file != nil && !file.IsSymlink {
return file, nil
}
// fs doesn't support afero.Lstater interface or the file is a symlink
info, err := opts.Fs.Stat(opts.Path)
if err != nil {
// can't follow symlink
if file != nil && file.IsSymlink {
return file, nil
}
return nil, err
}
// set correct file size in case of symlink
if file != nil && file.IsSymlink {
file.Size = info.Size()
file.IsDir = info.IsDir()
return file, nil
}
file = &FileInfo{
Fs: opts.Fs, Fs: opts.Fs,
Path: opts.Path, Path: opts.Path,
Name: info.Name(), Name: info.Name(),
@@ -77,21 +140,7 @@ func NewFileInfo(opts FileOptions) (*FileInfo, error) {
Token: opts.Token, Token: opts.Token,
} }
if opts.Expand { return file, nil
if file.IsDir {
if err := file.readListing(opts.Checker, opts.ReadHeader); err != nil { //nolint:govet
return nil, err
}
return file, nil
}
err = file.detectType(opts.Modify, true, true)
if err != nil {
return nil, err
}
}
return file, err
} }
// Checksum checksums a given File for a given User, using a specific // Checksum checksums a given File for a given User, using a specific
@@ -203,7 +252,7 @@ func (i *FileInfo) readFirstBytes() []byte {
} }
defer reader.Close() defer reader.Close()
buffer := make([]byte, 512) buffer := make([]byte, 512) //nolint:gomnd
n, err := reader.Read(buffer) n, err := reader.Read(buffer)
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
log.Print(err) log.Print(err)
@@ -251,7 +300,9 @@ func (i *FileInfo) readListing(checker rules.Checker, readHeader bool) error {
continue continue
} }
isSymlink := false
if IsSymlink(f.Mode()) { if IsSymlink(f.Mode()) {
isSymlink = true
// It's a symbolic link. We try to follow it. If it doesn't work, // It's a symbolic link. We try to follow it. If it doesn't work,
// we stay with the link information instead of the target's. // we stay with the link information instead of the target's.
info, err := i.Fs.Stat(fPath) info, err := i.Fs.Stat(fPath)
@@ -267,6 +318,7 @@ func (i *FileInfo) readListing(checker rules.Checker, readHeader bool) error {
ModTime: f.ModTime(), ModTime: f.ModTime(),
Mode: f.Mode(), Mode: f.Mode(),
IsDir: f.IsDir(), IsDir: f.IsDir(),
IsSymlink: isSymlink,
Extension: filepath.Ext(name), Extension: filepath.Ext(name),
Path: fPath, Path: fPath,
} }

View File

@@ -40,13 +40,13 @@ func CopyFile(fs afero.Fs, source, dest string) error {
// Makes the directory needed to create the dst // Makes the directory needed to create the dst
// file. // file.
err = fs.MkdirAll(filepath.Dir(dest), 0666) err = fs.MkdirAll(filepath.Dir(dest), 0666) //nolint:gomnd
if err != nil { if err != nil {
return err return err
} }
// Create the destination file. // Create the destination file.
dst, err := fs.OpenFile(dest, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0775) dst, err := fs.OpenFile(dest, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0775) //nolint:gomnd
if err != nil { if err != nil {
return err return err
} }

View File

@@ -11,6 +11,7 @@
"ace-builds": "^1.4.7", "ace-builds": "^1.4.7",
"clipboard": "^2.0.4", "clipboard": "^2.0.4",
"core-js": "^3.9.1", "core-js": "^3.9.1",
"css-vars-ponyfill": "^2.4.3",
"js-base64": "^2.5.1", "js-base64": "^2.5.1",
"lodash.clonedeep": "^4.5.0", "lodash.clonedeep": "^4.5.0",
"lodash.throttle": "^4.1.1", "lodash.throttle": "^4.1.1",
@@ -25,7 +26,8 @@
"vue-lazyload": "^1.3.3", "vue-lazyload": "^1.3.3",
"vue-router": "^3.1.3", "vue-router": "^3.1.3",
"vuex": "^3.1.2", "vuex": "^3.1.2",
"vuex-router-sync": "^5.0.0" "vuex-router-sync": "^5.0.0",
"whatwg-fetch": "^3.6.2"
}, },
"devDependencies": { "devDependencies": {
"@vue/cli-plugin-babel": "^4.1.2", "@vue/cli-plugin-babel": "^4.1.2",
@@ -33,6 +35,7 @@
"@vue/cli-service": "^4.1.2", "@vue/cli-service": "^4.1.2",
"@vue/eslint-config-prettier": "^6.0.0", "@vue/eslint-config-prettier": "^6.0.0",
"babel-eslint": "^10.1.0", "babel-eslint": "^10.1.0",
"compression-webpack-plugin": "^6.0.3",
"eslint": "^6.7.2", "eslint": "^6.7.2",
"eslint-plugin-prettier": "^3.3.1", "eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-vue": "^6.2.2", "eslint-plugin-vue": "^6.2.2",
@@ -1383,6 +1386,46 @@
"node": ">= 6" "node": ">= 6"
} }
}, },
"node_modules/@npmcli/move-file": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz",
"integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==",
"dev": true,
"dependencies": {
"mkdirp": "^1.0.4",
"rimraf": "^3.0.2"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@npmcli/move-file/node_modules/mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
"dev": true,
"bin": {
"mkdirp": "bin/cmd.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@npmcli/move-file/node_modules/rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"dev": true,
"dependencies": {
"glob": "^7.1.3"
},
"bin": {
"rimraf": "bin.js"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@soda/friendly-errors-webpack-plugin": { "node_modules/@soda/friendly-errors-webpack-plugin": {
"version": "1.8.0", "version": "1.8.0",
"resolved": "https://registry.npmjs.org/@soda/friendly-errors-webpack-plugin/-/friendly-errors-webpack-plugin-1.8.0.tgz", "resolved": "https://registry.npmjs.org/@soda/friendly-errors-webpack-plugin/-/friendly-errors-webpack-plugin-1.8.0.tgz",
@@ -4092,6 +4135,165 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/compression-webpack-plugin": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/compression-webpack-plugin/-/compression-webpack-plugin-6.0.3.tgz",
"integrity": "sha512-xzSWiZWwBs+HHGhlYxw0oFaYL/0VYErEqDHCAJhJ3Mza5fmF5JJ4iaB6Ap2JT68C0UhhmoI4Mh37LVz/THv2Fw==",
"dev": true,
"dependencies": {
"cacache": "^15.0.5",
"find-cache-dir": "^3.3.1",
"schema-utils": "^3.0.0",
"serialize-javascript": "^5.0.1",
"webpack-sources": "^1.4.3"
},
"engines": {
"node": ">= 10.13.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"peerDependencies": {
"webpack": "^4.0.0 || ^5.0.0"
}
},
"node_modules/compression-webpack-plugin/node_modules/cacache": {
"version": "15.0.6",
"resolved": "https://registry.npmjs.org/cacache/-/cacache-15.0.6.tgz",
"integrity": "sha512-g1WYDMct/jzW+JdWEyjaX2zoBkZ6ZT9VpOyp2I/VMtDsNLffNat3kqPFfi1eDRSK9/SuKGyORDHcQMcPF8sQ/w==",
"dev": true,
"dependencies": {
"@npmcli/move-file": "^1.0.1",
"chownr": "^2.0.0",
"fs-minipass": "^2.0.0",
"glob": "^7.1.4",
"infer-owner": "^1.0.4",
"lru-cache": "^6.0.0",
"minipass": "^3.1.1",
"minipass-collect": "^1.0.2",
"minipass-flush": "^1.0.5",
"minipass-pipeline": "^1.2.2",
"mkdirp": "^1.0.3",
"p-map": "^4.0.0",
"promise-inflight": "^1.0.1",
"rimraf": "^3.0.2",
"ssri": "^8.0.1",
"tar": "^6.0.2",
"unique-filename": "^1.1.1"
},
"engines": {
"node": ">= 10"
}
},
"node_modules/compression-webpack-plugin/node_modules/chownr": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
"dev": true,
"engines": {
"node": ">=10"
}
},
"node_modules/compression-webpack-plugin/node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/compression-webpack-plugin/node_modules/mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
"dev": true,
"bin": {
"mkdirp": "bin/cmd.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/compression-webpack-plugin/node_modules/p-map": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
"integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==",
"dev": true,
"dependencies": {
"aggregate-error": "^3.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/compression-webpack-plugin/node_modules/rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"dev": true,
"dependencies": {
"glob": "^7.1.3"
},
"bin": {
"rimraf": "bin.js"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/compression-webpack-plugin/node_modules/schema-utils": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz",
"integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==",
"dev": true,
"dependencies": {
"@types/json-schema": "^7.0.6",
"ajv": "^6.12.5",
"ajv-keywords": "^3.5.2"
},
"engines": {
"node": ">= 10.13.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
}
},
"node_modules/compression-webpack-plugin/node_modules/serialize-javascript": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz",
"integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==",
"dev": true,
"dependencies": {
"randombytes": "^2.1.0"
}
},
"node_modules/compression-webpack-plugin/node_modules/ssri": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz",
"integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==",
"dev": true,
"dependencies": {
"minipass": "^3.1.1"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/compression-webpack-plugin/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/compression/node_modules/bytes": { "node_modules/compression/node_modules/bytes": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
@@ -4663,6 +4865,11 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/css-vars-ponyfill": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/css-vars-ponyfill/-/css-vars-ponyfill-2.4.3.tgz",
"integrity": "sha512-PBfIwjSu27s8kebu8taEYFM8ehVr8o2Qw4H4nSlJzHAJgcduAqxz4oPmYTJuzgauOKaWII9SHWStQ965fxsdZA=="
},
"node_modules/css-what": { "node_modules/css-what": {
"version": "3.4.2", "version": "3.4.2",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz", "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz",
@@ -9045,6 +9252,25 @@
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true "dev": true
}, },
"node_modules/minizlib": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
"integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
"dev": true,
"dependencies": {
"minipass": "^3.0.0",
"yallist": "^4.0.0"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/minizlib/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/mississippi": { "node_modules/mississippi": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
@@ -12746,6 +12972,50 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/tar": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.1.0.tgz",
"integrity": "sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA==",
"dev": true,
"dependencies": {
"chownr": "^2.0.0",
"fs-minipass": "^2.0.0",
"minipass": "^3.0.0",
"minizlib": "^2.1.1",
"mkdirp": "^1.0.3",
"yallist": "^4.0.0"
},
"engines": {
"node": ">= 10"
}
},
"node_modules/tar/node_modules/chownr": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
"dev": true,
"engines": {
"node": ">=10"
}
},
"node_modules/tar/node_modules/mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
"dev": true,
"bin": {
"mkdirp": "bin/cmd.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/tar/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/terser": { "node_modules/terser": {
"version": "4.8.0", "version": "4.8.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz",
@@ -14709,6 +14979,11 @@
"node": ">=0.8.0" "node": ">=0.8.0"
} }
}, },
"node_modules/whatwg-fetch": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz",
"integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA=="
},
"node_modules/which": { "node_modules/which": {
"version": "1.3.1", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
@@ -16234,6 +16509,33 @@
"integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==", "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==",
"dev": true "dev": true
}, },
"@npmcli/move-file": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz",
"integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==",
"dev": true,
"requires": {
"mkdirp": "^1.0.4",
"rimraf": "^3.0.2"
},
"dependencies": {
"mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
"dev": true
},
"rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"dev": true,
"requires": {
"glob": "^7.1.3"
}
}
}
},
"@soda/friendly-errors-webpack-plugin": { "@soda/friendly-errors-webpack-plugin": {
"version": "1.8.0", "version": "1.8.0",
"resolved": "https://registry.npmjs.org/@soda/friendly-errors-webpack-plugin/-/friendly-errors-webpack-plugin-1.8.0.tgz", "resolved": "https://registry.npmjs.org/@soda/friendly-errors-webpack-plugin/-/friendly-errors-webpack-plugin-1.8.0.tgz",
@@ -18503,6 +18805,120 @@
} }
} }
}, },
"compression-webpack-plugin": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/compression-webpack-plugin/-/compression-webpack-plugin-6.0.3.tgz",
"integrity": "sha512-xzSWiZWwBs+HHGhlYxw0oFaYL/0VYErEqDHCAJhJ3Mza5fmF5JJ4iaB6Ap2JT68C0UhhmoI4Mh37LVz/THv2Fw==",
"dev": true,
"requires": {
"cacache": "^15.0.5",
"find-cache-dir": "^3.3.1",
"schema-utils": "^3.0.0",
"serialize-javascript": "^5.0.1",
"webpack-sources": "^1.4.3"
},
"dependencies": {
"cacache": {
"version": "15.0.6",
"resolved": "https://registry.npmjs.org/cacache/-/cacache-15.0.6.tgz",
"integrity": "sha512-g1WYDMct/jzW+JdWEyjaX2zoBkZ6ZT9VpOyp2I/VMtDsNLffNat3kqPFfi1eDRSK9/SuKGyORDHcQMcPF8sQ/w==",
"dev": true,
"requires": {
"@npmcli/move-file": "^1.0.1",
"chownr": "^2.0.0",
"fs-minipass": "^2.0.0",
"glob": "^7.1.4",
"infer-owner": "^1.0.4",
"lru-cache": "^6.0.0",
"minipass": "^3.1.1",
"minipass-collect": "^1.0.2",
"minipass-flush": "^1.0.5",
"minipass-pipeline": "^1.2.2",
"mkdirp": "^1.0.3",
"p-map": "^4.0.0",
"promise-inflight": "^1.0.1",
"rimraf": "^3.0.2",
"ssri": "^8.0.1",
"tar": "^6.0.2",
"unique-filename": "^1.1.1"
}
},
"chownr": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
"dev": true
},
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"requires": {
"yallist": "^4.0.0"
}
},
"mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
"dev": true
},
"p-map": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
"integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==",
"dev": true,
"requires": {
"aggregate-error": "^3.0.0"
}
},
"rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"dev": true,
"requires": {
"glob": "^7.1.3"
}
},
"schema-utils": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz",
"integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==",
"dev": true,
"requires": {
"@types/json-schema": "^7.0.6",
"ajv": "^6.12.5",
"ajv-keywords": "^3.5.2"
}
},
"serialize-javascript": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz",
"integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==",
"dev": true,
"requires": {
"randombytes": "^2.1.0"
}
},
"ssri": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz",
"integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==",
"dev": true,
"requires": {
"minipass": "^3.1.1"
}
},
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
}
}
},
"concat-map": { "concat-map": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -18948,6 +19364,11 @@
} }
} }
}, },
"css-vars-ponyfill": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/css-vars-ponyfill/-/css-vars-ponyfill-2.4.3.tgz",
"integrity": "sha512-PBfIwjSu27s8kebu8taEYFM8ehVr8o2Qw4H4nSlJzHAJgcduAqxz4oPmYTJuzgauOKaWII9SHWStQ965fxsdZA=="
},
"css-what": { "css-what": {
"version": "3.4.2", "version": "3.4.2",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz", "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz",
@@ -22407,6 +22828,24 @@
"minipass": "^3.0.0" "minipass": "^3.0.0"
} }
}, },
"minizlib": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
"integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
"dev": true,
"requires": {
"minipass": "^3.0.0",
"yallist": "^4.0.0"
},
"dependencies": {
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
}
}
},
"mississippi": { "mississippi": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
@@ -25512,6 +25951,40 @@
"integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==",
"dev": true "dev": true
}, },
"tar": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.1.0.tgz",
"integrity": "sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA==",
"dev": true,
"requires": {
"chownr": "^2.0.0",
"fs-minipass": "^2.0.0",
"minipass": "^3.0.0",
"minizlib": "^2.1.1",
"mkdirp": "^1.0.3",
"yallist": "^4.0.0"
},
"dependencies": {
"chownr": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
"dev": true
},
"mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
"dev": true
},
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
}
}
},
"terser": { "terser": {
"version": "4.8.0", "version": "4.8.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz",
@@ -27088,6 +27561,11 @@
"integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==",
"dev": true "dev": true
}, },
"whatwg-fetch": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz",
"integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA=="
},
"which": { "which": {
"version": "1.3.1", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",

View File

@@ -13,6 +13,7 @@
"ace-builds": "^1.4.7", "ace-builds": "^1.4.7",
"clipboard": "^2.0.4", "clipboard": "^2.0.4",
"core-js": "^3.9.1", "core-js": "^3.9.1",
"css-vars-ponyfill": "^2.4.3",
"js-base64": "^2.5.1", "js-base64": "^2.5.1",
"lodash.clonedeep": "^4.5.0", "lodash.clonedeep": "^4.5.0",
"lodash.throttle": "^4.1.1", "lodash.throttle": "^4.1.1",
@@ -27,7 +28,8 @@
"vue-lazyload": "^1.3.3", "vue-lazyload": "^1.3.3",
"vue-router": "^3.1.3", "vue-router": "^3.1.3",
"vuex": "^3.1.2", "vuex": "^3.1.2",
"vuex-router-sync": "^5.0.0" "vuex-router-sync": "^5.0.0",
"whatwg-fetch": "^3.6.2"
}, },
"devDependencies": { "devDependencies": {
"@vue/cli-plugin-babel": "^4.1.2", "@vue/cli-plugin-babel": "^4.1.2",
@@ -35,6 +37,7 @@
"@vue/cli-service": "^4.1.2", "@vue/cli-service": "^4.1.2",
"@vue/eslint-config-prettier": "^6.0.0", "@vue/eslint-config-prettier": "^6.0.0",
"babel-eslint": "^10.1.0", "babel-eslint": "^10.1.0",
"compression-webpack-plugin": "^6.0.3",
"eslint": "^6.7.2", "eslint": "^6.7.2",
"eslint-plugin-prettier": "^3.3.1", "eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-vue": "^6.2.2", "eslint-plugin-vue": "^6.2.2",
@@ -64,6 +67,6 @@
"browserslist": [ "browserslist": [
"> 1%", "> 1%",
"last 2 versions", "last 2 versions",
"not ie <= 8" "not ie < 11"
] ]
} }

View File

@@ -30,7 +30,7 @@
<!-- Inject Some Variables and generate the manifest json --> <!-- Inject Some Variables and generate the manifest json -->
<script> <script>
window.FileBrowser = JSON.parse(`[{[ .Json ]}]`); window.FileBrowser = JSON.parse('[{[ .Json ]}]');
var fullStaticURL = window.location.origin + window.FileBrowser.StaticURL; var fullStaticURL = window.location.origin + window.FileBrowser.StaticURL;
var dynamicManifest = { var dynamicManifest = {
@@ -77,7 +77,7 @@
opacity: 0; opacity: 0;
} }
.spinner { #loading .spinner {
width: 70px; width: 70px;
text-align: center; text-align: center;
position: fixed; position: fixed;
@@ -87,7 +87,7 @@
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
} }
.spinner > div { #loading .spinner > div {
width: 18px; width: 18px;
height: 18px; height: 18px;
background-color: #333; background-color: #333;
@@ -97,12 +97,12 @@
animation: sk-bouncedelay 1.4s infinite ease-in-out both; animation: sk-bouncedelay 1.4s infinite ease-in-out both;
} }
.spinner .bounce1 { #loading .spinner .bounce1 {
-webkit-animation-delay: -0.32s; -webkit-animation-delay: -0.32s;
animation-delay: -0.32s; animation-delay: -0.32s;
} }
.spinner .bounce2 { #loading .spinner .bounce2 {
-webkit-animation-delay: -0.16s; -webkit-animation-delay: -0.16s;
animation-delay: -0.16s; animation-delay: -0.16s;
} }

View File

@@ -16,7 +16,7 @@ body {
#loading { #loading {
background: var(--background); background: var(--background);
} }
#loading .spinner div, #previewer .loading .spinner div { #loading .spinner div, main .spinner div {
background: var(--icon); background: var(--icon);
} }

View File

@@ -5,6 +5,9 @@
</template> </template>
<script> <script>
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.FileBrowser.StaticURL + "/";
export default { export default {
name: "app", name: "app",
mounted() { mounted() {

View File

@@ -8,13 +8,18 @@ export async function fetchURL(url, opts) {
let { headers, ...rest } = opts; let { headers, ...rest } = opts;
const res = await fetch(`${baseURL}${url}`, { let res;
headers: { try {
"X-Auth": store.state.jwt, res = await fetch(`${baseURL}${url}`, {
...headers, headers: {
}, "X-Auth": store.state.jwt,
...rest, ...headers,
}); },
...rest,
});
} catch (error) {
return { status: 0 };
}
if (res.headers.get("X-Renew-Token") === "true") { if (res.headers.get("X-Renew-Token") === "true") {
await renew(store.state.jwt); await renew(store.state.jwt);

View File

@@ -8,8 +8,6 @@
@dragover="dragOver" @dragover="dragOver"
@drop="drop" @drop="drop"
@click="itemClick" @click="itemClick"
@dblclick="dblclick"
@touchstart="touchstart"
:data-dir="isDir" :data-dir="isDir"
:aria-label="name" :aria-label="name"
:aria-selected="isSelected" :aria-selected="isSelected"
@@ -96,7 +94,7 @@ export default {
// reload the image when the file is replaced // reload the image when the file is replaced
const key = Date.parse(this.modified); const key = Date.parse(this.modified);
return `${baseURL}/api/preview/thumb/${path}?auth=${this.jwt}&inline=true&k=${key}`; return `${baseURL}/api/preview/thumb/${path}?k=${key}&inline=true`;
}, },
isThumbsEnabled() { isThumbsEnabled() {
return enableThumbs; return enableThumbs;
@@ -153,13 +151,13 @@ export default {
for (let i of this.selected) { for (let i of this.selected) {
items.push({ items.push({
from: this.req.items[i].url, from: this.req.items[i].url,
to: this.url + this.req.items[i].name, to: this.url + encodeURIComponent(this.req.items[i].name),
name: this.req.items[i].name, name: this.req.items[i].name,
}); });
} }
let base = el.querySelector(".name").innerHTML + "/"; // Get url from ListingItem instance
let path = this.$route.path + base; let path = el.__vue__.url;
let baseItems = (await api.fetch(path)).items; let baseItems = (await api.fetch(path)).items;
let action = (overwrite, rename) => { let action = (overwrite, rename) => {
@@ -200,6 +198,16 @@ export default {
}, },
click: function (event) { click: function (event) {
if (!this.singleClick && this.selectedCount !== 0) event.preventDefault(); if (!this.singleClick && this.selectedCount !== 0) event.preventDefault();
setTimeout(() => {
this.touches = 0;
}, 300);
this.touches++;
if (this.touches > 1) {
this.open();
}
if (this.$store.state.selected.indexOf(this.index) !== -1) { if (this.$store.state.selected.indexOf(this.index) !== -1) {
this.removeSelected(this.index); this.removeSelected(this.index);
return; return;
@@ -235,19 +243,6 @@ export default {
this.resetSelected(); this.resetSelected();
this.addSelected(this.index); this.addSelected(this.index);
}, },
dblclick: function () {
if (!this.singleClick) this.open();
},
touchstart() {
setTimeout(() => {
this.touches = 0;
}, 300);
this.touches++;
if (this.touches > 1) {
this.open();
}
},
open: function () { open: function () {
this.$router.push({ path: this.url }); this.$router.push({ path: this.url });
}, },

View File

@@ -16,7 +16,7 @@
<strong>{{ $t("prompts.size") }}:</strong> <strong>{{ $t("prompts.size") }}:</strong>
<span id="content_length"></span> {{ humanSize }} <span id="content_length"></span> {{ humanSize }}
</p> </p>
<p v-if="selected.length < 2"> <p v-if="selected.length < 2" :title="modTime">
<strong>{{ $t("prompts.lastModified") }}:</strong> {{ humanTime }} <strong>{{ $t("prompts.lastModified") }}:</strong> {{ humanTime }}
</p> </p>
@@ -110,6 +110,9 @@ export default {
return moment(this.req.items[this.selected[0]].modified).fromNow(); return moment(this.req.items[this.selected[0]].modified).fromNow();
}, },
modTime: function () {
return new Date(Date.parse(this.req.modified)).toLocaleString();
},
name: function () { name: function () {
return this.selectedCount === 0 return this.selectedCount === 0
? this.req.name ? this.req.name

View File

@@ -43,6 +43,15 @@
word-break: break-all; word-break: break-all;
} }
.share__box__element .button {
display: inline-block;
}
.share__box__element .button i {
display: block;
margin-bottom: 4px;
}
.share__box__items { .share__box__items {
text-align: left; text-align: left;
flex: 10 0 25em; flex: 10 0 25em;

View File

@@ -17,6 +17,48 @@
color: var(--blue); color: var(--blue);
} }
main .spinner {
display: block;
text-align: center;
line-height: 0;
padding: 1em 0;
}
main .spinner > div {
width: .8em;
height: .8em;
margin: 0 .1em;
font-size: 1em;
background-color: rgba(0, 0, 0, 0.3);
border-radius: 100%;
display: inline-block;
animation: sk-bouncedelay 1.4s infinite ease-in-out both;
}
main .spinner .bounce1 {
animation-delay: -0.32s;
}
main .spinner .bounce2 {
animation-delay: -0.16s;
}
.delayed {
animation: delayed linear 100ms;
}
@keyframes delayed {
0% {
opacity: 0;
}
99% {
opacity: 0;
}
100% {
opacity: 1;
}
}
/* * * * * * * * * * * * * * * * /* * * * * * * * * * * * * * * *
* ACTION * * ACTION *
* * * * * * * * * * * * * * * */ * * * * * * * * * * * * * * * */
@@ -163,6 +205,34 @@
height: 100%; height: 100%;
} }
#previewer .preview .info {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 1.5em;
color: #fff;
}
#previewer .preview .info .title {
margin-bottom: 1em;
}
#previewer .preview .info .title i {
display: block;
margin-bottom: .1em;
font-size: 4em;
}
#previewer .preview .info .button {
display: inline-block;
}
#previewer .preview .info .button:hover {
background-color: rgba(255, 255, 255, 0.2)
}
#previewer .preview .info .button i {
display: block;
margin-bottom: 4px;
font-size: 1.3em;
}
#previewer .pdf { #previewer .pdf {
width: 100%; width: 100%;
height: 100%; height: 100%;
@@ -204,6 +274,20 @@
right: 0.5em; right: 0.5em;
} }
#previewer .spinner {
text-align: center;
position: fixed;
top: calc(50% + 1.85em);
left: 50%;
transform: translate(-50%, -50%);
}
#previewer .spinner > div {
width: 18px;
height: 18px;
background-color: white;
}
/* EDITOR */ /* EDITOR */
#editor-container { #editor-container {
@@ -217,11 +301,6 @@
overflow: hidden; overflow: hidden;
} }
#previewer .loading {
height: 100%;
width: 100%;
}
#editor-container #editor { #editor-container #editor {
height: calc(100vh - 8.4em); height: calc(100vh - 8.4em);
} }
@@ -283,7 +362,6 @@
@keyframes spin { @keyframes spin {
100% { 100% {
-webkit-transform: rotate(-360deg);
transform: rotate(-360deg); transform: rotate(-360deg);
} }
} }

View File

@@ -33,7 +33,8 @@
"switchView": "Switch view", "switchView": "Switch view",
"toggleSidebar": "Toggle sidebar", "toggleSidebar": "Toggle sidebar",
"update": "Update", "update": "Update",
"upload": "Upload" "upload": "Upload",
"openFile": "Open file"
}, },
"download": { "download": {
"downloadFile": "Download File", "downloadFile": "Download File",
@@ -43,7 +44,8 @@
"errors": { "errors": {
"forbidden": "You don't have permissions to access this.", "forbidden": "You don't have permissions to access this.",
"internal": "Something really went wrong.", "internal": "Something really went wrong.",
"notFound": "This location can't be reached." "notFound": "This location can't be reached.",
"connection": "The server can't be reached."
}, },
"files": { "files": {
"body": "Body", "body": "Body",
@@ -61,7 +63,8 @@
"size": "Size", "size": "Size",
"sortByLastModified": "Sort by last modified", "sortByLastModified": "Sort by last modified",
"sortByName": "Sort by name", "sortByName": "Sort by name",
"sortBySize": "Sort by size" "sortBySize": "Sort by size",
"noPreview": "Preview is not available for this file."
}, },
"help": { "help": {
"click": "select file or directory", "click": "select file or directory",
@@ -214,6 +217,7 @@
"settingsUpdated": "Settings updated!", "settingsUpdated": "Settings updated!",
"shareDuration": "Share Duration", "shareDuration": "Share Duration",
"shareManagement": "Share Management", "shareManagement": "Share Management",
"shareDeleted": "Share deleted!",
"singleClick": "Use single clicks to open files and directories", "singleClick": "Use single clicks to open files and directories",
"themes": { "themes": {
"dark": "Dark", "dark": "Dark",

View File

@@ -1,3 +1,5 @@
import "whatwg-fetch";
import cssVars from "css-vars-ponyfill";
import { sync } from "vuex-router-sync"; import { sync } from "vuex-router-sync";
import store from "@/store"; import store from "@/store";
import router from "@/router"; import router from "@/router";
@@ -7,6 +9,8 @@ import { recaptcha, loginPage } from "@/utils/constants";
import { login, validateLogin } from "@/utils/auth"; import { login, validateLogin } from "@/utils/auth";
import App from "@/App"; import App from "@/App";
cssVars();
sync(store, router); sync(store, router);
async function start() { async function start() {

View File

@@ -12,6 +12,8 @@ export function parseToken(token) {
const data = JSON.parse(Base64.decode(parts[1])); const data = JSON.parse(Base64.decode(parts[1]));
document.cookie = `auth=${token}; path=/`;
localStorage.setItem("jwt", token); localStorage.setItem("jwt", token);
store.commit("setJWT", token); store.commit("setJWT", token);
store.commit("setUser", data.user); store.commit("setUser", data.user);
@@ -81,6 +83,8 @@ export async function signup(username, password) {
} }
export function logout() { export function logout() {
document.cookie = "auth=; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/";
store.commit("setJWT", ""); store.commit("setJWT", "");
store.commit("setUser", null); store.commit("setUser", null);
localStorage.setItem("jwt", null); localStorage.setItem("jwt", null);

View File

@@ -3,8 +3,8 @@
<header-bar v-if="showHeader" showMenu showLogo /> <header-bar v-if="showHeader" showMenu showLogo />
<h2 class="message"> <h2 class="message">
<i class="material-icons">{{ icon }}</i> <i class="material-icons">{{ info.icon }}</i>
<span>{{ message }}</span> <span>{{ $t(info.message) }}</span>
</h2> </h2>
</div> </div>
</template> </template>
@@ -13,6 +13,10 @@
import HeaderBar from "@/components/header/HeaderBar"; import HeaderBar from "@/components/header/HeaderBar";
const errors = { const errors = {
0: {
icon: "cloud_off",
message: "errors.connection",
},
403: { 403: {
icon: "error", icon: "error",
message: "errors.forbidden", message: "errors.forbidden",
@@ -33,11 +37,17 @@ export default {
HeaderBar, HeaderBar,
}, },
props: ["errorCode", "showHeader"], props: ["errorCode", "showHeader"],
data: function () { computed: {
return { code() {
icon: errors[this.errorCode].icon, return this.errorCode === "0" ||
message: this.$t(errors[this.errorCode].message), this.errorCode === "404" ||
}; this.errorCode === "403"
? parseInt(this.errorCode)
: 500;
},
info() {
return errors[this.code];
},
}, },
}; };
</script> </script>

View File

@@ -4,10 +4,15 @@
<breadcrumbs base="/files" /> <breadcrumbs base="/files" />
<errors v-if="error" :errorCode="errorCode" /> <errors v-if="error" :errorCode="error.message" />
<component v-else-if="currentView" :is="currentView"></component> <component v-else-if="currentView" :is="currentView"></component>
<div v-else> <div v-else>
<h2 class="message"> <h2 class="message delayed">
<div class="spinner">
<div class="bounce1"></div>
<div class="bounce2"></div>
<div class="bounce3"></div>
</div>
<span>{{ $t("files.loading") }}</span> <span>{{ $t("files.loading") }}</span>
</h2> </h2>
</div> </div>
@@ -62,11 +67,6 @@ export default {
return "preview"; return "preview";
} }
}, },
errorCode() {
return this.error.message === "404" || this.error.message === "403"
? parseInt(this.error.message)
: 500;
},
}, },
created() { created() {
this.fetchData(); this.fetchData();
@@ -116,7 +116,7 @@ export default {
} }
this.$store.commit("updateRequest", res); this.$store.commit("updateRequest", res);
document.title = res.name; document.title = `${res.name} - ${this.$route.name}`;
} catch (e) { } catch (e) {
this.error = e; this.error = e;
} finally { } finally {

View File

@@ -34,6 +34,17 @@
</div> </div>
</div> </div>
<div v-if="loading">
<h2 class="message delayed">
<div class="spinner">
<div class="bounce1"></div>
<div class="bounce2"></div>
<div class="bounce3"></div>
</div>
<span>{{ $t("files.loading") }}</span>
</h2>
</div>
<router-view></router-view> <router-view></router-view>
</div> </div>
</template> </template>
@@ -49,7 +60,7 @@ export default {
HeaderBar, HeaderBar,
}, },
computed: { computed: {
...mapState(["user"]), ...mapState(["user", "loading"]),
}, },
}; };
</script> </script>

View File

@@ -19,7 +19,50 @@
<breadcrumbs :base="'/share/' + hash" /> <breadcrumbs :base="'/share/' + hash" />
<div v-if="!loading"> <div v-if="loading">
<h2 class="message delayed">
<div class="spinner">
<div class="bounce1"></div>
<div class="bounce2"></div>
<div class="bounce3"></div>
</div>
<span>{{ $t("files.loading") }}</span>
</h2>
</div>
<div v-else-if="error">
<div v-if="error.message === '401'">
<div class="card floating" id="password">
<div v-if="attemptedPasswordLogin" class="share__wrong__password">
{{ $t("login.wrongCredentials") }}
</div>
<div class="card-title">
<h2>{{ $t("login.password") }}</h2>
</div>
<div class="card-content">
<input
v-focus
type="password"
:placeholder="$t('login.password')"
v-model="password"
@keyup.enter="fetchData"
/>
</div>
<div class="card-action">
<button
class="button button--flat"
@click="fetchData"
:aria-label="$t('buttons.submit')"
:title="$t('buttons.submit')"
>
{{ $t("buttons.submit") }}
</button>
</div>
</div>
</div>
<errors v-else :errorCode="error.message" />
</div>
<div v-else>
<div class="share"> <div class="share">
<div class="share__box share__box__info"> <div class="share__box share__box__info">
<div class="share__box__header"> <div class="share__box__header">
@@ -35,16 +78,30 @@
<div class="share__box__element"> <div class="share__box__element">
<strong>{{ $t("prompts.displayName") }}</strong> {{ req.name }} <strong>{{ $t("prompts.displayName") }}</strong> {{ req.name }}
</div> </div>
<div class="share__box__element"> <div class="share__box__element" :title="modTime">
<strong>{{ $t("prompts.lastModified") }}:</strong> {{ humanTime }} <strong>{{ $t("prompts.lastModified") }}:</strong> {{ humanTime }}
</div> </div>
<div class="share__box__element"> <div class="share__box__element">
<strong>{{ $t("prompts.size") }}:</strong> {{ humanSize }} <strong>{{ $t("prompts.size") }}:</strong> {{ humanSize }}
</div> </div>
<div class="share__box__element share__box__center"> <div class="share__box__element share__box__center">
<a target="_blank" :href="link" class="button button--flat">{{ <a target="_blank" :href="link" class="button button--flat">
$t("buttons.download") <div>
}}</a> <i class="material-icons">file_download</i
>{{ $t("buttons.download") }}
</div>
</a>
<a
target="_blank"
:href="inlineLink"
class="button button--flat"
v-if="!req.isDir"
>
<div>
<i class="material-icons">open_in_new</i
>{{ $t("buttons.openFile") }}
</div>
</a>
</div> </div>
<div class="share__box__element share__box__center"> <div class="share__box__element share__box__center">
<qrcode-vue :value="fullLink" size="200" level="M"></qrcode-vue> <qrcode-vue :value="fullLink" size="200" level="M"></qrcode-vue>
@@ -71,7 +128,11 @@
readOnly readOnly
> >
</item> </item>
<div v-if="req.items.length > showLimit" class="item"> <div
v-if="req.items.length > showLimit"
class="item"
@click="showLimit += 100"
>
<div> <div>
<p class="name">+ {{ req.items.length - showLimit }}</p> <p class="name">+ {{ req.items.length - showLimit }}</p>
</div> </div>
@@ -106,39 +167,6 @@
</div> </div>
</div> </div>
</div> </div>
<div v-if="error">
<div v-if="error.message === '401'">
<div class="card floating" id="password">
<div v-if="attemptedPasswordLogin" class="share__wrong__password">
{{ $t("login.wrongCredentials") }}
</div>
<div class="card-title">
<h2>{{ $t("login.password") }}</h2>
</div>
<div class="card-content">
<input
v-focus
type="password"
:placeholder="$t('login.password')"
v-model="password"
@keyup.enter="fetchData"
/>
</div>
<div class="card-action">
<button
class="button button--flat"
@click="fetchData"
:aria-label="$t('buttons.submit')"
:title="$t('buttons.submit')"
>
{{ $t("buttons.submit") }}
</button>
</div>
</div>
</div>
<errors v-else :errorCode="errorCode" />
</div>
</div> </div>
</template> </template>
@@ -168,14 +196,18 @@ export default {
}, },
data: () => ({ data: () => ({
error: null, error: null,
showLimit: 500, showLimit: 100,
password: "", password: "",
attemptedPasswordLogin: false, attemptedPasswordLogin: false,
hash: null, hash: null,
token: null, token: null,
}), }),
watch: { watch: {
$route: "fetchData", $route: function () {
this.showLimit = 100;
this.fetchData();
},
}, },
created: async function () { created: async function () {
const hash = this.$route.params.pathMatch.split("/")[0]; const hash = this.$route.params.pathMatch.split("/")[0];
@@ -207,6 +239,11 @@ export default {
const path = this.$route.path.split("/").splice(2).join("/"); const path = this.$route.path.split("/").splice(2).join("/");
return `${baseURL}/api/public/dl/${path}${queryArg}`; return `${baseURL}/api/public/dl/${path}${queryArg}`;
}, },
inlineLink: function () {
let url = new URL(this.link);
url.searchParams.set("inline", "true");
return url.href;
},
fullLink: function () { fullLink: function () {
return window.location.origin + this.link; return window.location.origin + this.link;
}, },
@@ -220,10 +257,8 @@ export default {
humanTime: function () { humanTime: function () {
return moment(this.req.modified).fromNow(); return moment(this.req.modified).fromNow();
}, },
errorCode() { modTime: function () {
return this.error.message === "404" || this.error.message === "403" return new Date(Date.parse(this.req.modified)).toLocaleString();
? parseInt(this.error.message)
: 500;
}, },
}, },
methods: { methods: {
@@ -256,9 +291,11 @@ export default {
this.token = file.token || ""; this.token = file.token || "";
this.updateRequest(file); this.updateRequest(file);
this.setLoading(false); document.title = `${file.name} - ${this.$route.name}`;
} catch (e) { } catch (e) {
this.error = e; this.error = e;
} finally {
this.setLoading(false);
} }
}, },
keyEvent(event) { keyEvent(event) {

View File

@@ -114,8 +114,13 @@
/> />
</div> </div>
<div v-if="$store.state.loading"> <div v-if="loading">
<h2 class="message"> <h2 class="message delayed">
<div class="spinner">
<div class="bounce1"></div>
<div class="bounce2"></div>
<div class="bounce3"></div>
</div>
<span>{{ $t("files.loading") }}</span> <span>{{ $t("files.loading") }}</span>
</h2> </h2>
</div> </div>
@@ -254,6 +259,7 @@
</template> </template>
<script> <script>
import Vue from "vue";
import { mapState, mapGetters, mapMutations } from "vuex"; import { mapState, mapGetters, mapMutations } from "vuex";
import { users, files as api } from "@/api"; import { users, files as api } from "@/api";
import { enableExec } from "@/utils/constants"; import { enableExec } from "@/utils/constants";
@@ -279,10 +285,19 @@ export default {
showLimit: 50, showLimit: 50,
dragCounter: 0, dragCounter: 0,
width: window.innerWidth, width: window.innerWidth,
itemWeight: 0,
}; };
}, },
computed: { computed: {
...mapState(["req", "selected", "user", "show", "multiple", "selected"]), ...mapState([
"req",
"selected",
"user",
"show",
"multiple",
"selected",
"loading",
]),
...mapGetters(["selectedCount"]), ...mapGetters(["selectedCount"]),
nameSorted() { nameSorted() {
return this.req.sorting.by === "name"; return this.req.sorting.by === "name";
@@ -362,8 +377,14 @@ export default {
// Reset the show value // Reset the show value
this.showLimit = 50; this.showLimit = 50;
// Fill and fit the window with listing items // Ensures that the listing is displayed
this.fillWindow(true); Vue.nextTick(() => {
// How much every listing item affects the window height
this.setItemWeight();
// Fill and fit the window with listing items
this.fillWindow(true);
});
}, },
}, },
mounted: function () { mounted: function () {
@@ -393,7 +414,7 @@ export default {
window.removeEventListener("scroll", this.scrollEvent); window.removeEventListener("scroll", this.scrollEvent);
window.removeEventListener("resize", this.windowsResize); window.removeEventListener("resize", this.windowsResize);
if (!this.user.perm.create) return; if (this.user && !this.user.perm.create) return;
document.removeEventListener("dragover", this.preventDefault); document.removeEventListener("dragover", this.preventDefault);
document.removeEventListener("dragenter", this.dragEnter); document.removeEventListener("dragenter", this.dragEnter);
document.removeEventListener("dragleave", this.dragLeave); document.removeEventListener("dragleave", this.dragLeave);
@@ -484,7 +505,7 @@ export default {
for (let i of this.selected) { for (let i of this.selected) {
items.push({ items.push({
from: this.req.items[i].url, from: this.req.items[i].url,
name: encodeURIComponent(this.req.items[i].name), name: this.req.items[i].name,
}); });
} }
@@ -509,7 +530,7 @@ export default {
const from = item.from.endsWith("/") const from = item.from.endsWith("/")
? item.from.slice(0, -1) ? item.from.slice(0, -1)
: item.from; : item.from;
const to = this.$route.path + item.name; const to = this.$route.path + encodeURIComponent(item.name);
items.push({ from, to, name: item.name }); items.push({ from, to, name: item.name });
} }
@@ -631,22 +652,20 @@ export default {
} }
} }
let base = ""; let files = await upload.scanFiles(dt);
let items = this.req.items;
let path = this.$route.path.endsWith("/")
? this.$route.path
: this.$route.path + "/";
if ( if (
el !== null && el !== null &&
el.classList.contains("item") && el.classList.contains("item") &&
el.dataset.dir === "true" el.dataset.dir === "true"
) { ) {
base = el.querySelector(".name").innerHTML + "/"; // Get url from ListingItem instance
} path = el.__vue__.url;
let files = await upload.scanFiles(dt);
let path = this.$route.path.endsWith("/")
? this.$route.path + base
: this.$route.path + "/" + base;
let items = this.req.items;
if (base !== "") {
try { try {
items = (await api.fetch(path)).items; items = (await api.fetch(path)).items;
} catch (error) { } catch (error) {
@@ -793,26 +812,28 @@ export default {
viewMode: this.user.viewMode === "mosaic" ? "list" : "mosaic", viewMode: this.user.viewMode === "mosaic" ? "list" : "mosaic",
}; };
try { users.update(data, ["viewMode"]).catch(this.$showError);
await users.update(data, ["viewMode"]);
// Await ensures correct value for setItemWeight() // Await ensures correct value for setItemWeight()
await this.$store.commit("updateUser", data); await this.$store.commit("updateUser", data);
this.setItemWeight(); this.setItemWeight();
this.fillWindow(); this.fillWindow();
} catch (e) {
this.$showError(e);
}
}, },
upload: function () { upload: function () {
if (typeof DataTransferItem.prototype.webkitGetAsEntry !== "undefined") { if (
typeof window.DataTransferItem !== "undefined" &&
typeof DataTransferItem.prototype.webkitGetAsEntry !== "undefined"
) {
this.$store.commit("showHover", "upload"); this.$store.commit("showHover", "upload");
} else { } else {
document.getElementById("upload-input").click(); document.getElementById("upload-input").click();
} }
}, },
setItemWeight() { setItemWeight() {
// Listing element is not displayed
if (this.$refs.listing == null) return;
let itemQuantity = this.req.numDirs + this.req.numFiles; let itemQuantity = this.req.numDirs + this.req.numFiles;
if (itemQuantity > this.showLimit) itemQuantity = this.showLimit; if (itemQuantity > this.showLimit) itemQuantity = this.showLimit;

View File

@@ -46,15 +46,14 @@
</template> </template>
</header-bar> </header-bar>
<div class="loading" v-if="loading"> <div class="loading delayed" v-if="loading">
<div class="spinner"> <div class="spinner">
<div class="bounce1"></div> <div class="bounce1"></div>
<div class="bounce2"></div> <div class="bounce2"></div>
<div class="bounce3"></div> <div class="bounce3"></div>
</div> </div>
</div> </div>
<template v-else>
<template v-if="!loading">
<div class="preview"> <div class="preview">
<ExtendedImage v-if="req.type == 'image'" :src="raw"></ExtendedImage> <ExtendedImage v-if="req.type == 'image'" :src="raw"></ExtendedImage>
<audio <audio
@@ -90,12 +89,31 @@
class="pdf" class="pdf"
:data="raw" :data="raw"
></object> ></object>
<a v-else-if="req.type == 'blob'" :href="downloadUrl"> <div v-else-if="req.type == 'blob'" class="info">
<h2 class="message"> <div class="title">
{{ $t("buttons.download") }} <i class="material-icons">feedback</i>
<i class="material-icons">file_download</i> {{ $t("files.noPreview") }}
</h2> </div>
</a> <div>
<a target="_blank" :href="downloadUrl" class="button button--flat">
<div>
<i class="material-icons">file_download</i
>{{ $t("buttons.download") }}
</div>
</a>
<a
target="_blank"
:href="downloadUrl + '&inline=true'"
class="button button--flat"
v-if="!req.isDir"
>
<div>
<i class="material-icons">open_in_new</i
>{{ $t("buttons.openFile") }}
</div>
</a>
</div>
</div>
</div> </div>
</template> </template>
@@ -176,11 +194,9 @@ export default {
if (this.req.type === "image" && !this.fullSize) { if (this.req.type === "image" && !this.fullSize) {
return `${baseURL}/api/preview/big${url.encodePath( return `${baseURL}/api/preview/big${url.encodePath(
this.req.path this.req.path
)}?auth=${this.jwt}&k=${key}`; )}?k=${key}`;
} }
return `${baseURL}/api/raw${url.encodePath(this.req.path)}?auth=${ return `${baseURL}/api/raw${url.encodePath(this.req.path)}?k=${key}`;
this.jwt
}&k=${key}`;
}, },
raw() { raw() {
return `${this.previewUrl}&inline=true`; return `${this.previewUrl}&inline=true`;
@@ -258,7 +274,7 @@ export default {
if (this.req.subtitles) { if (this.req.subtitles) {
this.subtitles = this.req.subtitles.map( this.subtitles = this.req.subtitles.map(
(sub) => `${baseURL}/api/raw${sub}?auth=${this.jwt}&inline=true` (sub) => `${baseURL}/api/raw${sub}?inline=true`
); );
} }

View File

@@ -1,5 +1,6 @@
<template> <template>
<div class="row" v-if="settings !== null"> <errors v-if="error" :errorCode="error.message" />
<div class="row" v-else-if="!loading">
<div class="column"> <div class="column">
<form class="card" @submit.prevent="save"> <form class="card" @submit.prevent="save">
<div class="card-title"> <div class="card-title">
@@ -170,12 +171,13 @@
</template> </template>
<script> <script>
import { mapState } from "vuex"; import { mapState, mapMutations } from "vuex";
import { settings as api } from "@/api"; import { settings as api } from "@/api";
import { enableExec } from "@/utils/constants";
import UserForm from "@/components/settings/UserForm"; import UserForm from "@/components/settings/UserForm";
import Rules from "@/components/settings/Rules"; import Rules from "@/components/settings/Rules";
import Themes from "@/components/settings/Themes"; import Themes from "@/components/settings/Themes";
import { enableExec } from "@/utils/constants"; import Errors from "@/views/Errors";
export default { export default {
name: "settings", name: "settings",
@@ -183,19 +185,23 @@ export default {
Themes, Themes,
UserForm, UserForm,
Rules, Rules,
Errors,
}, },
data: function () { data: function () {
return { return {
error: null,
originalSettings: null, originalSettings: null,
settings: null, settings: null,
}; };
}, },
computed: { computed: {
...mapState(["user"]), ...mapState(["user", "loading"]),
isExecEnabled: () => enableExec, isExecEnabled: () => enableExec,
}, },
async created() { async created() {
try { try {
this.setLoading(true);
const original = await api.get(); const original = await api.get();
let settings = { ...original, commands: [] }; let settings = { ...original, commands: [] };
@@ -211,10 +217,13 @@ export default {
this.originalSettings = original; this.originalSettings = original;
this.settings = settings; this.settings = settings;
} catch (e) { } catch (e) {
this.$showError(e); this.error = e;
} finally {
this.setLoading(false);
} }
}, },
methods: { methods: {
...mapMutations(["setLoading"]),
capitalize(name, where = "_") { capitalize(name, where = "_") {
if (where === "caps") where = /(?=[A-Z])/; if (where === "caps") where = /(?=[A-Z])/;
let splitted = name.split(where); let splitted = name.split(where);

View File

@@ -103,12 +103,13 @@ export default {
}, },
}, },
created() { created() {
this.setLoading(false);
this.locale = this.user.locale; this.locale = this.user.locale;
this.hideDotfiles = this.user.hideDotfiles; this.hideDotfiles = this.user.hideDotfiles;
this.singleClick = this.user.singleClick; this.singleClick = this.user.singleClick;
}, },
methods: { methods: {
...mapMutations(["updateUser"]), ...mapMutations(["updateUser", "setLoading"]),
async updatePassword(event) { async updatePassword(event) {
event.preventDefault(); event.preventDefault();

View File

@@ -1,12 +1,13 @@
<template> <template>
<div class="row"> <errors v-if="error" :errorCode="error.message" />
<div class="row" v-else-if="!loading">
<div class="column"> <div class="column">
<div class="card"> <div class="card">
<div class="card-title"> <div class="card-title">
<h2>{{ $t("settings.shareManagement") }}</h2> <h2>{{ $t("settings.shareManagement") }}</h2>
</div> </div>
<div class="card-content full"> <div class="card-content full" v-if="links.length > 0">
<table> <table>
<tr> <tr>
<th>{{ $t("settings.path") }}</th> <th>{{ $t("settings.path") }}</th>
@@ -52,6 +53,10 @@
</tr> </tr>
</table> </table>
</div> </div>
<h2 class="message" v-else>
<i class="material-icons">sentiment_dissatisfied</i>
<span>{{ $t("files.lonely") }}</span>
</h2>
</div> </div>
</div> </div>
</div> </div>
@@ -59,21 +64,28 @@
<script> <script>
import { share as api, users } from "@/api"; import { share as api, users } from "@/api";
import moment from "moment";
import { baseURL } from "@/utils/constants"; import { baseURL } from "@/utils/constants";
import { mapState, mapMutations } from "vuex";
import moment from "moment";
import Clipboard from "clipboard"; import Clipboard from "clipboard";
import { mapState } from "vuex"; import Errors from "@/views/Errors";
export default { export default {
name: "shares", name: "shares",
computed: mapState(["user"]), components: {
Errors,
},
computed: mapState(["user", "loading"]),
data: function () { data: function () {
return { return {
error: null,
links: [], links: [],
clip: null, clip: null,
}; };
}, },
async created() { async created() {
this.setLoading(true);
try { try {
let links = await api.list(); let links = await api.list();
if (this.user.perm.admin) { if (this.user.perm.admin) {
@@ -87,7 +99,9 @@ export default {
} }
this.links = links; this.links = links;
} catch (e) { } catch (e) {
this.$showError(e); this.error = e;
} finally {
this.setLoading(false);
} }
}, },
mounted() { mounted() {
@@ -100,6 +114,7 @@ export default {
this.clip.destroy(); this.clip.destroy();
}, },
methods: { methods: {
...mapMutations(["setLoading"]),
deleteLink: async function (event, link) { deleteLink: async function (event, link) {
event.preventDefault(); event.preventDefault();
@@ -108,8 +123,13 @@ export default {
confirm: () => { confirm: () => {
this.$store.commit("closeHovers"); this.$store.commit("closeHovers");
api.remove(link.hash); try {
this.links = this.links.filter((item) => item.hash !== link.hash); api.remove(link.hash);
this.links = this.links.filter((item) => item.hash !== link.hash);
this.$showSuccess(this.$t("settings.shareDeleted"));
} catch (e) {
this.$showError(e);
}
}, },
}); });
}, },

View File

@@ -1,7 +1,8 @@
<template> <template>
<div class="row"> <errors v-if="error" :errorCode="error.message" />
<div class="row" v-else-if="!loading">
<div class="column"> <div class="column">
<form v-if="loaded" @submit="save" class="card"> <form @submit="save" class="card">
<div class="card-title"> <div class="card-title">
<h2 v-if="user.id === 0">{{ $t("settings.newUser") }}</h2> <h2 v-if="user.id === 0">{{ $t("settings.newUser") }}</h2>
<h2 v-else>{{ $t("settings.user") }} {{ user.username }}</h2> <h2 v-else>{{ $t("settings.user") }} {{ user.username }}</h2>
@@ -55,21 +56,23 @@
</template> </template>
<script> <script>
import { mapMutations } from "vuex"; import { mapState, mapMutations } from "vuex";
import { users as api, settings } from "@/api"; import { users as api, settings } from "@/api";
import UserForm from "@/components/settings/UserForm"; import UserForm from "@/components/settings/UserForm";
import Errors from "@/views/Errors";
import deepClone from "lodash.clonedeep"; import deepClone from "lodash.clonedeep";
export default { export default {
name: "user", name: "user",
components: { components: {
UserForm, UserForm,
Errors,
}, },
data: () => { data: () => {
return { return {
error: null,
originalUser: null, originalUser: null,
user: {}, user: {},
loaded: false,
}; };
}, },
created() { created() {
@@ -79,6 +82,7 @@ export default {
isNew() { isNew() {
return this.$route.path === "/settings/users/new"; return this.$route.path === "/settings/users/new";
}, },
...mapState(["loading"]),
}, },
watch: { watch: {
$route: "fetchData", $route: "fetchData",
@@ -88,8 +92,10 @@ export default {
}, },
}, },
methods: { methods: {
...mapMutations(["closeHovers", "showHover", "setUser"]), ...mapMutations(["closeHovers", "showHover", "setUser", "setLoading"]),
async fetchData() { async fetchData() {
this.setLoading(true);
try { try {
if (this.isNew) { if (this.isNew) {
let { defaults } = await settings.get(); let { defaults } = await settings.get();
@@ -105,10 +111,10 @@ export default {
const id = this.$route.params.pathMatch; const id = this.$route.params.pathMatch;
this.user = { ...(await api.get(id)) }; this.user = { ...(await api.get(id)) };
} }
this.loaded = true;
} catch (e) { } catch (e) {
this.$router.push({ path: "/settings/users/new" }); this.error = e;
} finally {
this.setLoading(false);
} }
}, },
deletePrompt() { deletePrompt() {

View File

@@ -1,5 +1,6 @@
<template> <template>
<div class="row"> <errors v-if="error" :errorCode="error.message" />
<div class="row" v-else-if="!loading">
<div class="column"> <div class="column">
<div class="card"> <div class="card">
<div class="card-title"> <div class="card-title">
@@ -41,21 +42,37 @@
</template> </template>
<script> <script>
import { mapState, mapMutations } from "vuex";
import { users as api } from "@/api"; import { users as api } from "@/api";
import Errors from "@/views/Errors";
export default { export default {
name: "users", name: "users",
components: {
Errors,
},
data: function () { data: function () {
return { return {
error: null,
users: [], users: [],
}; };
}, },
async created() { async created() {
this.setLoading(true);
try { try {
this.users = await api.getAll(); this.users = await api.getAll();
} catch (e) { } catch (e) {
this.$showError(e); this.error = e;
} finally {
this.setLoading(false);
} }
}, },
computed: {
...mapState(["loading"]),
},
methods: {
...mapMutations(["setLoading"]),
},
}; };
</script> </script>

View File

@@ -1,5 +1,15 @@
const CompressionPlugin = require("compression-webpack-plugin");
module.exports = { module.exports = {
runtimeCompiler: true, runtimeCompiler: true,
publicPath: "[{[ .StaticURL ]}]", publicPath: "[{[ .StaticURL ]}]",
parallel: 2, parallel: 2,
configureWebpack: {
plugins: [
new CompressionPlugin({
include: /\.js$/,
deleteOriginalAssets: true,
}),
],
},
}; };

View File

@@ -48,11 +48,16 @@ func (e extractor) ExtractToken(r *http.Request) (string, error) {
} }
auth := r.URL.Query().Get("auth") auth := r.URL.Query().Get("auth")
if auth == "" { if auth != "" && strings.Count(auth, ".") == 2 {
return "", request.ErrNoTokenInRequest return auth, nil
} }
return auth, nil cookie, _ := r.Cookie("auth")
if cookie != nil && strings.Count(cookie.Value, ".") == 2 {
return cookie.Value, nil
}
return "", request.ErrNoTokenInRequest
} }
func withUser(fn handleFunc) handleFunc { func withUser(fn handleFunc) handleFunc {

View File

@@ -49,6 +49,8 @@ func (d *data) Check(path string) bool {
func handle(fn handleFunc, prefix string, store *storage.Storage, server *settings.Server) http.Handler { func handle(fn handleFunc, prefix string, store *storage.Storage, server *settings.Server) http.Handler {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
settings, err := store.Settings.Get() settings, err := store.Settings.Get()
if err != nil { if err != nil {
log.Fatalf("ERROR: couldn't get settings: %v\n", err) log.Fatalf("ERROR: couldn't get settings: %v\n", err)

View File

@@ -25,6 +25,12 @@ func NewHandler(
server.Clean() server.Clean()
r := mux.NewRouter() r := mux.NewRouter()
r.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Security-Policy", `default-src 'self'`)
next.ServeHTTP(w, r)
})
})
index, static := getStaticHandlers(store, server, assetsFs) index, static := getStaticHandlers(store, server, assetsFs)
// NOTE: This fixes the issue where it would redirect if people did not put a // NOTE: This fixes the issue where it would redirect if people did not put a
@@ -57,7 +63,7 @@ func NewHandler(
api.PathPrefix("/resources").Handler(monkey(resourceDeleteHandler(fileCache), "/api/resources")).Methods("DELETE") api.PathPrefix("/resources").Handler(monkey(resourceDeleteHandler(fileCache), "/api/resources")).Methods("DELETE")
api.PathPrefix("/resources").Handler(monkey(resourcePostHandler(fileCache), "/api/resources")).Methods("POST") api.PathPrefix("/resources").Handler(monkey(resourcePostHandler(fileCache), "/api/resources")).Methods("POST")
api.PathPrefix("/resources").Handler(monkey(resourcePutHandler, "/api/resources")).Methods("PUT") api.PathPrefix("/resources").Handler(monkey(resourcePutHandler, "/api/resources")).Methods("PUT")
api.PathPrefix("/resources").Handler(monkey(resourcePatchHandler, "/api/resources")).Methods("PATCH") api.PathPrefix("/resources").Handler(monkey(resourcePatchHandler(fileCache), "/api/resources")).Methods("PATCH")
api.Path("/shares").Handler(monkey(shareListHandler, "/api/shares")).Methods("GET") api.Path("/shares").Handler(monkey(shareListHandler, "/api/shares")).Methods("GET")
api.PathPrefix("/share").Handler(monkey(shareGetsHandler, "/api/share")).Methods("GET") api.PathPrefix("/share").Handler(monkey(shareGetsHandler, "/api/share")).Methods("GET")

View File

@@ -71,27 +71,38 @@ func previewHandler(imgSvc ImgService, fileCache FileCache, enableThumbnails, re
func handleImagePreview(w http.ResponseWriter, r *http.Request, imgSvc ImgService, fileCache FileCache, func handleImagePreview(w http.ResponseWriter, r *http.Request, imgSvc ImgService, fileCache FileCache,
file *files.FileInfo, previewSize PreviewSize, enableThumbnails, resizePreview bool) (int, error) { file *files.FileInfo, previewSize PreviewSize, enableThumbnails, resizePreview bool) (int, error) {
format, err := imgSvc.FormatFromExtension(file.Extension) format, err := imgSvc.FormatFromExtension(file.Extension)
// Unsupported extensions directly return the raw data
if err == img.ErrUnsupportedFormat || format == img.FormatGif {
return rawFileHandler(w, r, file)
}
if err != nil { if err != nil {
// Unsupported extensions directly return the raw data
if err == img.ErrUnsupportedFormat {
return rawFileHandler(w, r, file)
}
return errToStatus(err), err return errToStatus(err), err
} }
cacheKey := previewCacheKey(file.Path, file.ModTime.Unix(), previewSize) cacheKey := previewCacheKey(file.Path, file.ModTime.Unix(), previewSize)
cachedFile, ok, err := fileCache.Load(r.Context(), cacheKey) resizedImage, ok, err := fileCache.Load(r.Context(), cacheKey)
if err != nil { if err != nil {
return errToStatus(err), err return errToStatus(err), err
} }
if ok { if !ok {
_, _ = w.Write(cachedFile) resizedImage, err = createPreview(imgSvc, fileCache, file, previewSize, enableThumbnails, resizePreview)
return 0, nil if err != nil {
return errToStatus(err), err
}
} }
w.Header().Set("Cache-Control", "private")
http.ServeContent(w, r, file.Name, file.ModTime, bytes.NewReader(resizedImage))
return 0, nil
}
func createPreview(imgSvc ImgService, fileCache FileCache,
file *files.FileInfo, previewSize PreviewSize, enableThumbnails, resizePreview bool) ([]byte, error) {
fd, err := file.Fs.Open(file.Path) fd, err := file.Fs.Open(file.Path)
if err != nil { if err != nil {
return errToStatus(err), err return nil, err
} }
defer fd.Close() defer fd.Close()
@@ -102,7 +113,7 @@ func handleImagePreview(w http.ResponseWriter, r *http.Request, imgSvc ImgServic
) )
switch { switch {
case previewSize == PreviewSizeBig && resizePreview && format != img.FormatGif: case previewSize == PreviewSizeBig && resizePreview:
width = 1080 width = 1080
height = 1080 height = 1080
options = append(options, img.WithMode(img.ResizeModeFit), img.WithQuality(img.QualityMedium)) options = append(options, img.WithMode(img.ResizeModeFit), img.WithQuality(img.QualityMedium))
@@ -111,26 +122,22 @@ func handleImagePreview(w http.ResponseWriter, r *http.Request, imgSvc ImgServic
height = 128 height = 128
options = append(options, img.WithMode(img.ResizeModeFill), img.WithQuality(img.QualityLow), img.WithFormat(img.FormatJpeg)) options = append(options, img.WithMode(img.ResizeModeFill), img.WithQuality(img.QualityLow), img.WithFormat(img.FormatJpeg))
default: default:
if _, err := rawFileHandler(w, r, file); err != nil { return nil, img.ErrUnsupportedFormat
return errToStatus(err), err
}
return 0, nil
} }
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
if err := imgSvc.Resize(context.Background(), fd, width, height, buf, options...); err != nil { if err := imgSvc.Resize(context.Background(), fd, width, height, buf, options...); err != nil {
return 0, err return nil, err
} }
go func() { go func() {
cacheKey := previewCacheKey(file.Path, file.ModTime.Unix(), previewSize)
if err := fileCache.Store(context.Background(), cacheKey, buf.Bytes()); err != nil { if err := fileCache.Store(context.Background(), cacheKey, buf.Bytes()); err != nil {
fmt.Printf("failed to cache resized image: %v", err) fmt.Printf("failed to cache resized image: %v", err)
} }
}() }()
_, _ = w.Write(buf.Bytes()) return buf.Bytes(), nil
return 0, nil
} }
func previewCacheKey(fPath string, fTime int64, previewSize PreviewSize) string { func previewCacheKey(fPath string, fTime int64, previewSize PreviewSize) string {

View File

@@ -38,7 +38,7 @@ var withHashFile = func(fn handleFunc) handleFunc {
Fs: d.user.Fs, Fs: d.user.Fs,
Path: link.Path, Path: link.Path,
Modify: d.user.Perm.Modify, Modify: d.user.Perm.Modify,
Expand: true, Expand: false,
ReadHeader: d.server.TypeDetectionByHeader, ReadHeader: d.server.TypeDetectionByHeader,
Checker: d, Checker: d,
Token: link.Token, Token: link.Token,

View File

@@ -1,9 +1,8 @@
package http package http
import ( import (
"bytes"
"errors" "errors"
"io/ioutil" "log"
"net/http" "net/http"
"net/url" "net/url"
gopath "path" gopath "path"
@@ -11,7 +10,6 @@ import (
"strings" "strings"
"github.com/mholt/archiver" "github.com/mholt/archiver"
"github.com/spf13/afero"
"github.com/filebrowser/filebrowser/v2/files" "github.com/filebrowser/filebrowser/v2/files"
"github.com/filebrowser/filebrowser/v2/fileutils" "github.com/filebrowser/filebrowser/v2/fileutils"
@@ -117,19 +115,16 @@ func addFile(ar archiver.Writer, d *data, path, commonPath string) error {
return err return err
} }
var ( if !info.IsDir() && !info.Mode().IsRegular() {
file afero.File return nil
arcReadCloser = ioutil.NopCloser(&bytes.Buffer{})
)
if !files.IsNamedPipe(info.Mode()) {
file, err = d.user.Fs.Open(path)
if err != nil {
return err
}
defer file.Close()
arcReadCloser = file
} }
file, err := d.user.Fs.Open(path)
if err != nil {
return err
}
defer file.Close()
if path != commonPath { if path != commonPath {
filename := strings.TrimPrefix(path, commonPath) filename := strings.TrimPrefix(path, commonPath)
filename = strings.TrimPrefix(filename, string(filepath.Separator)) filename = strings.TrimPrefix(filename, string(filepath.Separator))
@@ -138,7 +133,7 @@ func addFile(ar archiver.Writer, d *data, path, commonPath string) error {
FileInfo: info, FileInfo: info,
CustomName: filename, CustomName: filename,
}, },
ReadCloser: arcReadCloser, ReadCloser: file,
}) })
if err != nil { if err != nil {
return err return err
@@ -152,9 +147,10 @@ func addFile(ar archiver.Writer, d *data, path, commonPath string) error {
} }
for _, name := range names { for _, name := range names {
err = addFile(ar, d, filepath.Join(path, name), commonPath) fPath := filepath.Join(path, name)
err = addFile(ar, d, fPath, commonPath)
if err != nil { if err != nil {
return err log.Printf("Failed to archive %s: %v", fPath, err)
} }
} }
} }
@@ -196,7 +192,7 @@ func rawDirHandler(w http.ResponseWriter, r *http.Request, d *data, file *files.
for _, fname := range filenames { for _, fname := range filenames {
err = addFile(ar, d, fname, commonDir) err = addFile(ar, d, fname, commonDir)
if err != nil { if err != nil {
return http.StatusInternalServerError, err log.Printf("Failed to archive %s: %v", fname, err)
} }
} }
@@ -212,6 +208,7 @@ func rawFileHandler(w http.ResponseWriter, r *http.Request, file *files.FileInfo
setContentDisposition(w, r, file) setContentDisposition(w, r, file)
w.Header().Set("Cache-Control", "private")
http.ServeContent(w, r, file.Name, file.ModTime, fd) http.ServeContent(w, r, file.Name, file.ModTime, fd)
return 0, nil return 0, nil
} }

View File

@@ -4,7 +4,6 @@ import (
"context" "context"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
@@ -27,6 +26,7 @@ var resourceGetHandler = withUser(func(w http.ResponseWriter, r *http.Request, d
Expand: true, Expand: true,
ReadHeader: d.server.TypeDetectionByHeader, ReadHeader: d.server.TypeDetectionByHeader,
Checker: d, Checker: d,
Content: true,
}) })
if err != nil { if err != nil {
return errToStatus(err), err return errToStatus(err), err
@@ -63,7 +63,7 @@ func resourceDeleteHandler(fileCache FileCache) handleFunc {
Fs: d.user.Fs, Fs: d.user.Fs,
Path: r.URL.Path, Path: r.URL.Path,
Modify: d.user.Perm.Modify, Modify: d.user.Perm.Modify,
Expand: true, Expand: false,
ReadHeader: d.server.TypeDetectionByHeader, ReadHeader: d.server.TypeDetectionByHeader,
Checker: d, Checker: d,
}) })
@@ -95,13 +95,9 @@ func resourcePostHandler(fileCache FileCache) handleFunc {
return http.StatusForbidden, nil return http.StatusForbidden, nil
} }
defer func() {
_, _ = io.Copy(ioutil.Discard, r.Body)
}()
// Directories creation on POST. // Directories creation on POST.
if strings.HasSuffix(r.URL.Path, "/") { if strings.HasSuffix(r.URL.Path, "/") {
err := d.user.Fs.MkdirAll(r.URL.Path, 0775) err := d.user.Fs.MkdirAll(r.URL.Path, 0775) //nolint:gomnd
return errToStatus(err), err return errToStatus(err), err
} }
@@ -109,7 +105,7 @@ func resourcePostHandler(fileCache FileCache) handleFunc {
Fs: d.user.Fs, Fs: d.user.Fs,
Path: r.URL.Path, Path: r.URL.Path,
Modify: d.user.Perm.Modify, Modify: d.user.Perm.Modify,
Expand: true, Expand: false,
ReadHeader: d.server.TypeDetectionByHeader, ReadHeader: d.server.TypeDetectionByHeader,
Checker: d, Checker: d,
}) })
@@ -153,16 +149,20 @@ var resourcePutHandler = withUser(func(w http.ResponseWriter, r *http.Request, d
return http.StatusForbidden, nil return http.StatusForbidden, nil
} }
defer func() {
_, _ = io.Copy(ioutil.Discard, r.Body)
}()
// Only allow PUT for files. // Only allow PUT for files.
if strings.HasSuffix(r.URL.Path, "/") { if strings.HasSuffix(r.URL.Path, "/") {
return http.StatusMethodNotAllowed, nil return http.StatusMethodNotAllowed, nil
} }
err := d.RunHook(func() error { exists, err := afero.Exists(d.user.Fs, r.URL.Path)
if err != nil {
return http.StatusInternalServerError, err
}
if !exists {
return http.StatusNotFound, nil
}
err = d.RunHook(func() error {
info, writeErr := writeFile(d.user.Fs, r.URL.Path, r.Body) info, writeErr := writeFile(d.user.Fs, r.URL.Path, r.Body)
if writeErr != nil { if writeErr != nil {
return writeErr return writeErr
@@ -173,71 +173,53 @@ var resourcePutHandler = withUser(func(w http.ResponseWriter, r *http.Request, d
return nil return nil
}, "save", r.URL.Path, "", d.user) }, "save", r.URL.Path, "", d.user)
if err != nil {
_ = d.user.Fs.RemoveAll(r.URL.Path)
}
return errToStatus(err), err return errToStatus(err), err
}) })
var resourcePatchHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { func resourcePatchHandler(fileCache FileCache) handleFunc {
src := r.URL.Path return withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
dst := r.URL.Query().Get("destination") src := r.URL.Path
action := r.URL.Query().Get("action") dst := r.URL.Query().Get("destination")
dst, err := url.QueryUnescape(dst) action := r.URL.Query().Get("action")
if !d.Check(src) || !d.Check(dst) { dst, err := url.QueryUnescape(dst)
return http.StatusForbidden, nil if !d.Check(src) || !d.Check(dst) {
} return http.StatusForbidden, nil
if err != nil { }
if err != nil {
return errToStatus(err), err
}
if dst == "/" || src == "/" {
return http.StatusForbidden, nil
}
err = checkParent(src, dst)
if err != nil {
return http.StatusBadRequest, err
}
override := r.URL.Query().Get("override") == "true"
rename := r.URL.Query().Get("rename") == "true"
if !override && !rename {
if _, err = d.user.Fs.Stat(dst); err == nil {
return http.StatusConflict, nil
}
}
if rename {
dst = addVersionSuffix(dst, d.user.Fs)
}
// Permission for overwriting the file
if override && !d.user.Perm.Modify {
return http.StatusForbidden, nil
}
err = d.RunHook(func() error {
return patchAction(r.Context(), action, src, dst, d, fileCache)
}, action, src, dst, d.user)
return errToStatus(err), err return errToStatus(err), err
} })
if dst == "/" || src == "/" { }
return http.StatusForbidden, nil
}
if err = checkParent(src, dst); err != nil {
return http.StatusBadRequest, err
}
override := r.URL.Query().Get("override") == "true"
rename := r.URL.Query().Get("rename") == "true"
if !override && !rename {
if _, err = d.user.Fs.Stat(dst); err == nil {
return http.StatusConflict, nil
}
}
if rename {
dst = addVersionSuffix(dst, d.user.Fs)
}
// Permission for overwriting the file
if override && !d.user.Perm.Modify {
return http.StatusForbidden, nil
}
err = d.RunHook(func() error {
switch action {
// TODO: use enum
case "copy":
if !d.user.Perm.Create {
return errors.ErrPermissionDenied
}
return fileutils.Copy(d.user.Fs, src, dst)
case "rename":
if !d.user.Perm.Rename {
return errors.ErrPermissionDenied
}
src = path.Clean("/" + src)
dst = path.Clean("/" + dst)
return fileutils.MoveFile(d.user.Fs, src, dst)
default:
return fmt.Errorf("unsupported action %s: %w", action, errors.ErrInvalidRequestParams)
}
}, action, src, dst, d.user)
return errToStatus(err), err
})
func checkParent(src, dst string) error { func checkParent(src, dst string) error {
rel, err := filepath.Rel(src, dst) rel, err := filepath.Rel(src, dst)
@@ -273,12 +255,12 @@ func addVersionSuffix(source string, fs afero.Fs) string {
func writeFile(fs afero.Fs, dst string, in io.Reader) (os.FileInfo, error) { func writeFile(fs afero.Fs, dst string, in io.Reader) (os.FileInfo, error) {
dir, _ := path.Split(dst) dir, _ := path.Split(dst)
err := fs.MkdirAll(dir, 0775) err := fs.MkdirAll(dir, 0775) //nolint:gomnd
if err != nil { if err != nil {
return nil, err return nil, err
} }
file, err := fs.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0775) file, err := fs.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0775) //nolint:gomnd
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -308,3 +290,43 @@ func delThumbs(ctx context.Context, fileCache FileCache, file *files.FileInfo) e
return nil return nil
} }
func patchAction(ctx context.Context, action, src, dst string, d *data, fileCache FileCache) error {
switch action {
// TODO: use enum
case "copy":
if !d.user.Perm.Create {
return errors.ErrPermissionDenied
}
return fileutils.Copy(d.user.Fs, src, dst)
case "rename":
if !d.user.Perm.Rename {
return errors.ErrPermissionDenied
}
src = path.Clean("/" + src)
dst = path.Clean("/" + dst)
file, err := files.NewFileInfo(files.FileOptions{
Fs: d.user.Fs,
Path: src,
Modify: d.user.Perm.Modify,
Expand: false,
ReadHeader: false,
Checker: d,
})
if err != nil {
return err
}
// delete thumbnails
err = delThumbs(ctx, fileCache, file)
if err != nil {
return err
}
return fileutils.MoveFile(d.user.Fs, src, dst)
default:
return fmt.Errorf("unsupported action %s: %w", action, errors.ErrInvalidRequestParams)
}
}

View File

@@ -90,7 +90,7 @@ var sharePostHandler = withPermShare(func(w http.ResponseWriter, r *http.Request
defer r.Body.Close() defer r.Body.Close()
} }
bytes := make([]byte, 6) bytes := make([]byte, 6) //nolint:gomnd
_, err := rand.Read(bytes) _, err := rand.Read(bytes)
if err != nil { if err != nil {
return http.StatusInternalServerError, err return http.StatusInternalServerError, err
@@ -129,7 +129,7 @@ var sharePostHandler = withPermShare(func(w http.ResponseWriter, r *http.Request
var token string var token string
if len(hash) > 0 { if len(hash) > 0 {
tokenBuffer := make([]byte, 96) tokenBuffer := make([]byte, 96) //nolint:gomnd
if _, err := rand.Read(tokenBuffer); err != nil { if _, err := rand.Read(tokenBuffer); err != nil {
return http.StatusInternalServerError, err return http.StatusInternalServerError, err
} }

View File

@@ -2,6 +2,7 @@ package http
import ( import (
"encoding/json" "encoding/json"
"fmt"
"io/fs" "io/fs"
"log" "log"
"net/http" "net/http"
@@ -71,12 +72,12 @@ func handleWithStaticData(w http.ResponseWriter, _ *http.Request, d *data, fSys
} }
} }
b, err := json.MarshalIndent(data, "", " ") b, err := json.Marshal(data)
if err != nil { if err != nil {
return http.StatusInternalServerError, err return http.StatusInternalServerError, err
} }
data["Json"] = string(b) data["Json"] = strings.ReplaceAll(string(b), `'`, `\'`)
fileContents, err := fs.ReadFile(fSys, file) fileContents, err := fs.ReadFile(fSys, file)
if err != nil { if err != nil {
@@ -109,6 +110,9 @@ func getStaticHandlers(store *storage.Storage, server *settings.Server, assetsFs
return http.StatusNotFound, nil return http.StatusNotFound, nil
} }
const maxAge = 86400 // 1 day
w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%v", maxAge))
if d.settings.Branding.Files != "" { if d.settings.Branding.Files != "" {
if strings.HasPrefix(r.URL.Path, "img/") { if strings.HasPrefix(r.URL.Path, "img/") {
fPath := filepath.Join(d.settings.Branding.Files, r.URL.Path) fPath := filepath.Join(d.settings.Branding.Files, r.URL.Path)
@@ -127,7 +131,19 @@ func getStaticHandlers(store *storage.Storage, server *settings.Server, assetsFs
return 0, nil return 0, nil
} }
return handleWithStaticData(w, r, d, assetsFs, r.URL.Path, "application/javascript; charset=utf-8") fileContents, err := fs.ReadFile(assetsFs, r.URL.Path+".gz")
if err != nil {
return http.StatusNotFound, err
}
w.Header().Set("Content-Encoding", "gzip")
w.Header().Set("Content-Type", "application/javascript; charset=utf-8")
if _, err := w.Write(fileContents); err != nil {
return http.StatusInternalServerError, err
}
return 0, nil
}, "/static/", store, server) }, "/static/", store, server)
return index, static return index, static

View File

@@ -25,7 +25,7 @@ type modifyUserRequest struct {
func getUserID(r *http.Request) (uint, error) { func getUserID(r *http.Request) (uint, error) {
vars := mux.Vars(r) vars := mux.Vars(r)
i, err := strconv.ParseUint(vars["id"], 10, 0) i, err := strconv.ParseUint(vars["id"], 10, 0) //nolint:gomnd
if err != nil { if err != nil {
return 0, err return 0, err
} }
@@ -138,7 +138,7 @@ var userPostHandler = withAdmin(func(w http.ResponseWriter, r *http.Request, d *
return http.StatusInternalServerError, err return http.StatusInternalServerError, err
} }
w.Header().Set("Location", "/settings/users/"+strconv.FormatUint(uint64(req.Data.ID), 10)) w.Header().Set("Location", "/settings/users/"+strconv.FormatUint(uint64(req.Data.ID), 10)) //nolint:gomnd
return http.StatusCreated, nil return http.StatusCreated, nil
}) })

View File

@@ -174,6 +174,8 @@ func (s *Service) Resize(ctx context.Context, in io.Reader, width, height int, o
switch config.resizeMode { switch config.resizeMode {
case ResizeModeFill: case ResizeModeFill:
img = imaging.Fill(img, width, height, imaging.Center, config.quality.resampleFilter()) img = imaging.Fill(img, width, height, imaging.Center, config.quality.resampleFilter())
case ResizeModeFit:
fallthrough //nolint:gocritic
default: default:
img = imaging.Fit(img, width, height, config.quality.resampleFilter()) img = imaging.Fit(img, width, height, config.quality.resampleFilter())
} }
@@ -205,7 +207,7 @@ func getEmbeddedThumbnail(in io.Reader) ([]byte, io.Reader, error) {
offset := 0 offset := 0
offsets := []int{12, 30} offsets := []int{12, 30}
head := make([]byte, 0xffff) head := make([]byte, 0xffff) //nolint:gomnd
_, err := r.Read(head) _, err := r.Read(head)
if err != nil { if err != nil {

View File

@@ -49,9 +49,9 @@ func (s *Server) Clean() {
s.BaseURL = strings.TrimSuffix(s.BaseURL, "/") s.BaseURL = strings.TrimSuffix(s.BaseURL, "/")
} }
// GenerateKey generates a key of 256 bits. // GenerateKey generates a key of 512 bits.
func GenerateKey() ([]byte, error) { func GenerateKey() ([]byte, error) {
b := make([]byte, 64) b := make([]byte, 64) //nolint:gomnd
_, err := rand.Read(b) _, err := rand.Read(b)
// Note that err == nil only if we read len(b) bytes. // Note that err == nil only if we read len(b) bytes.
if err != nil { if err != nil {

View File

@@ -17,7 +17,7 @@ func NewStorage(db *storm.DB) (*storage.Storage, error) {
settingsStore := settings.NewStorage(settingsBackend{db: db}) settingsStore := settings.NewStorage(settingsBackend{db: db})
authStore := auth.NewStorage(authBackend{db: db}, userStore) authStore := auth.NewStorage(authBackend{db: db}, userStore)
err := save(db, "version", 2) err := save(db, "version", 2) //nolint:gomnd
if err != nil { if err != nil {
return nil, err return nil, err
} }