Compare commits

...

81 Commits

Author SHA1 Message Date
Oleg Lobanov
4c20772e11 chore(release): 2.2.0 2020-06-22 19:12:12 +02:00
Oleg Lobanov
68f8348dde fix: apply all fs user rulles 2020-06-22 18:46:22 +02:00
Oleg Lobanov
5023e77296 Merge pull request #995 from ramiresviana/key-shortcuts 2020-06-22 13:48:56 +02:00
Ramires Viana
95316cbe8c feat: add key shortcuts
- 'Ctrl + a' selects all files in listing.
- 'Enter' to confirm a prompt.
2020-06-21 21:54:23 +00:00
Ramires Viana
cd454bae51 feat: upload progress based on total size (#993) 2020-06-19 09:46:33 +02:00
Oleg Lobanov
241201657c fix: add a workaround to fix window freezing when viewing a large file #992 2020-06-18 19:21:02 +02:00
Hampton
9eefaddd9b chore: fix documentation links on README (#987) 2020-06-18 17:54:37 +02:00
Oleg Lobanov
d6d47bbd6b Merge pull request #991 from ramiresviana/small-fixes 2020-06-18 09:59:27 +02:00
Ramires Viana
82c883f95e fix: save event hook
fix filebrowser/filebrowser#696
2020-06-17 22:57:13 +00:00
Ramires Viana
dd40b0d9b9 fix: frontend token validation
fix filebrowser/filebrowser#638
2020-06-17 22:57:07 +00:00
Ramires Viana
963837ef1d fix: multiple selection count
- Only add files to selected list that arent on it.
- Only shift key select when there are selected files.
2020-06-17 22:56:55 +00:00
Oleg Lobanov
66863b72f7 feat: add alpine and debian docker images 2020-06-16 23:18:22 +02:00
Ramires Viana
89773447a5 feat: add folder upload (#981)
* feat: folder upload
fix filebrowser/filebrowser#741

* fix: apply gofmt formater

* feat: upload button prompt

* feat: empty folder upload
2020-06-16 21:56:44 +02:00
Oleg Lobanov
6d899a6335 chore: version v2.1.2 2020-06-06 17:49:14 +02:00
Oleg Lobanov
28672c0114 fix(security): check user permission to rename files 2020-06-06 17:45:51 +02:00
Oleg Lobanov
b8300b7121 chore: add dist folder to gitignore 2020-06-02 10:50:14 +02:00
Oleg Lobanov
584ef4d4bd chore: version v2.1.1 2020-06-01 02:53:15 +02:00
Oleg Lobanov
e8295a944a fix(build): fix openbsd build
bump golang.org/x deps:
* golang.org/x/crypto
* golang.org/x/net
* golang.org/x/sys
2020-06-01 02:52:26 +02:00
Oleg Lobanov
f8f5698ad0 build(docker): add arm 5 docker image for raspberry pi 2020-06-01 02:14:11 +02:00
Oleg Lobanov
700f32718e refactor: add more go linters (#970) 2020-06-01 01:12:36 +02:00
Oleg Lobanov
54d92a2708 chore: bump go to 1.14.3 (#969) 2020-05-31 23:17:32 +02:00
Oleg Lobanov
ba47e3b2fe fix: fix static assets url generation (#965) 2020-05-31 22:26:10 +02:00
Oleg Lobanov
6e5405eeed Update README.md 2020-05-27 14:23:12 +02:00
Henrique Dias
45326e664f Update README.md 2020-04-16 13:25:03 +01:00
Henrique Dias
6ce44f7092 chore: version v2.1.0 2020-01-09 18:05:20 +00:00
Henrique Dias
b320419088 tidy 2020-01-09 18:03:18 +00:00
Henrique Dias
ca183a4fb8 Update README.md 2020-01-09 17:49:25 +00:00
Henrique Dias
895bb755cd Disable the logout method for authentication methods other than… (#934)
Disable the logout method for authentication methods other than 'json' (currently 'proxy' and 'none'.) Resolves #870.
2020-01-09 17:27:56 +00:00
Henrique Dias
a9e715dc50 Merge branch 'master' into remove-logout-button 2020-01-09 17:27:49 +00:00
Henrique Dias
7cb046c542 Merge pull request #938 from filebrowser/dependabot/go_modules/github.com/spf13/viper-1.6.1
chore(deps): bump github.com/spf13/viper from 1.5.0 to 1.6.1
2020-01-09 17:27:18 +00:00
dependabot-preview[bot]
cd03faf0fc chore(deps): bump vue-i18n from 8.15.1 to 8.15.3 in /frontend
Bumps [vue-i18n](https://github.com/kazupon/vue-i18n) from 8.15.1 to 8.15.3.
- [Release notes](https://github.com/kazupon/vue-i18n/releases)
- [Changelog](https://github.com/kazupon/vue-i18n/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/kazupon/vue-i18n/compare/v8.15.1...v8.15.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-09 17:27:09 +00:00
dependabot-preview[bot]
87ba03b224 chore(deps-dev): bump @vue/cli-service from 4.0.5 to 4.1.2 in /frontend
Bumps [@vue/cli-service](https://github.com/vuejs/vue-cli/tree/HEAD/packages/@vue/cli-service) from 4.0.5 to 4.1.2.
- [Release notes](https://github.com/vuejs/vue-cli/releases)
- [Changelog](https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-cli/commits/v4.1.2/packages/@vue/cli-service)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-09 17:26:58 +00:00
dependabot-preview[bot]
6458f91e1c chore(deps-dev): bump @vue/cli-plugin-babel in /frontend
Bumps [@vue/cli-plugin-babel](https://github.com/vuejs/vue-cli/tree/HEAD/packages/@vue/cli-plugin-babel) from 4.1.1 to 4.1.2.
- [Release notes](https://github.com/vuejs/vue-cli/releases)
- [Changelog](https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-cli/commits/v4.1.2/packages/@vue/cli-plugin-babel)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-09 17:26:49 +00:00
dependabot-preview[bot]
312ebbbcc0 chore(deps-dev): bump eslint-plugin-vue from 6.0.1 to 6.1.2 in /frontend
Bumps [eslint-plugin-vue](https://github.com/vuejs/eslint-plugin-vue) from 6.0.1 to 6.1.2.
- [Release notes](https://github.com/vuejs/eslint-plugin-vue/releases)
- [Commits](https://github.com/vuejs/eslint-plugin-vue/compare/v6.0.1...v6.1.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-09 17:26:42 +00:00
freedomlang
060a7ad80c refactor: Load Editor as need to reduce bundle size 2020-01-09 17:26:17 +00:00
freedomlang
ae893abc5f refactor: Remove useless react data and destory ace editor 2020-01-09 17:26:17 +00:00
freedomlang
12d6415f7f refactor: Freeze property to improve performance 2020-01-09 17:26:17 +00:00
freedomlang
897ac75281 refactor: Use v-for for language option 2020-01-09 17:26:17 +00:00
Hadrien Dorio
cec551c3de fix(docker): Add mime.types file
Uses the package mailcap from alpine as a source for /etc/mime.types
which is required by golang.org/pkg/mime on unix systems.
2020-01-09 17:26:06 +00:00
Alexey Larkov
cb98c913d4 Allow request manifest through reverse proxy with authentication 2020-01-09 17:25:30 +00:00
Ramires Viana
55a9d945cc Add dark theme 2020-01-09 17:24:59 +00:00
Ramires Viana
cc7ec4f0c5 Fix multiple selection 2020-01-09 17:24:30 +00:00
dependabot-preview[bot]
265b81a52b chore(deps): bump github.com/spf13/viper from 1.5.0 to 1.6.1
Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.5.0 to 1.6.1.
- [Release notes](https://github.com/spf13/viper/releases)
- [Commits](https://github.com/spf13/viper/compare/v1.5.0...v1.6.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-09 11:14:41 +00:00
Ovidiu Predescu
b42b09ccbe Disable the logout method for authentication methods other than 'json' (currently 'proxy' and 'none'.) 2019-12-03 17:31:11 -08:00
dependabot-preview[bot]
118071ba4b chore(deps-dev): bump @vue/cli-plugin-babel from 4.0.5 to 4.1.1… (#929)
Bumps [@vue/cli-plugin-babel](https://github.com/vuejs/vue-cli/tree/HEAD/packages/@vue/cli-plugin-babel) from 4.0.5 to 4.1.1.
- [Release notes](https://github.com/vuejs/vue-cli/releases)
- [Changelog](https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-cli/commits/v4.1.1/packages/@vue/cli-plugin-babel)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-02 15:52:26 +00:00
dependabot-preview[bot]
73b85eced4 chore(deps-dev): bump eslint from 6.6.0 to 6.7.2 in /frontend (#932)
Bumps [eslint](https://github.com/eslint/eslint) from 6.6.0 to 6.7.2.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v6.6.0...v6.7.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-02 15:52:20 +00:00
Henrique Dias
997a0a433f fix: disable eslint on one line
License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>
2019-12-02 15:48:55 +00:00
dependabot-preview[bot]
0d7e344ca3 chore(deps-dev): bump @vue/cli-plugin-eslint from 4.0.5 to 4.1.1… (#927)
Bumps [@vue/cli-plugin-eslint](https://github.com/vuejs/vue-cli/tree/HEAD/packages/@vue/cli-plugin-eslint) from 4.0.5 to 4.1.1.
- [Release notes](https://github.com/vuejs/vue-cli/releases)
- [Changelog](https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-cli/commits/v4.1.1/packages/@vue/cli-plugin-eslint)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-02 15:36:38 +00:00
dependabot-preview[bot]
1884d50c3b chore(deps): bump vue-i18n from 8.15.0 to 8.15.1 in /frontend (#925)
Bumps [vue-i18n](https://github.com/kazupon/vue-i18n) from 8.15.0 to 8.15.1.
- [Release notes](https://github.com/kazupon/vue-i18n/releases)
- [Changelog](https://github.com/kazupon/vue-i18n/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/kazupon/vue-i18n/compare/v8.15.0...v8.15.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-02 15:36:30 +00:00
dependabot-preview[bot]
f5fad7a01d chore(deps): bump gopkg.in/yaml.v2 from 2.2.5 to 2.2.7 (#921)
Bumps [gopkg.in/yaml.v2](https://github.com/go-yaml/yaml) from 2.2.5 to 2.2.7.
- [Release notes](https://github.com/go-yaml/yaml/releases)
- [Commits](https://github.com/go-yaml/yaml/compare/v2.2.5...v2.2.7)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-02 15:36:22 +00:00
dependabot-preview[bot]
5c2ed2b2f9 chore(deps-dev): bump eslint-plugin-vue from 6.0.0 to 6.0.1 in /… (#916)
Bumps [eslint-plugin-vue](https://github.com/vuejs/eslint-plugin-vue) from 6.0.0 to 6.0.1.
- [Release notes](https://github.com/vuejs/eslint-plugin-vue/releases)
- [Commits](https://github.com/vuejs/eslint-plugin-vue/compare/v6.0.0...v6.0.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-02 15:36:13 +00:00
dependabot-preview[bot]
05475eb4fc chore(deps): bump vuex from 3.1.1 to 3.1.2 in /frontend (#914)
Bumps [vuex](https://github.com/vuejs/vuex) from 3.1.1 to 3.1.2.
- [Release notes](https://github.com/vuejs/vuex/releases)
- [Commits](https://github.com/vuejs/vuex/compare/v3.1.1...v3.1.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-02 15:36:01 +00:00
dependabot-preview[bot]
9e6cc302c0 chore(deps): bump qrcode.vue from 1.6.3 to 1.7.0 in /frontend (#913)
Bumps [qrcode.vue](https://github.com/scopewu/qrcode.vue) from 1.6.3 to 1.7.0.
- [Release notes](https://github.com/scopewu/qrcode.vue/releases)
- [Changelog](https://github.com/scopewu/qrcode.vue/blob/master/CHANGELOG.md)
- [Commits](https://github.com/scopewu/qrcode.vue/compare/v1.6.3...v1.7.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-02 15:35:53 +00:00
Henrique Dias
d422421cf9 Merge pull request #918 from spacebat/real-ip
Determine the real IP address of the client for logging
2019-12-02 15:31:56 +00:00
freedomlang
23a3ef069e refactor: Optimize prompts component 2019-12-02 15:31:06 +00:00
blackywkl
2a81ea90db feat: add animation for disable multiple selection and break word for filename in info panel (#922) 2019-12-02 15:30:18 +00:00
A Kirkpatrick
5fb7207d65 Determine the real IP address of the client for logging
When running behind a reverse proxy such as nginx, the remote IP as
logged is always that of the proxy. Figuring out the correct address
in this context is a little tricky, hence the following module is
used:

https://github.com/tomasen/realip
2019-11-17 14:14:15 +10:30
Henrique Dias
d79d864825 chore(deps-dev): bump eslint from 5.16.0 to 6.6.0 in /frontend (#915)
chore(deps-dev): bump eslint from 5.16.0 to 6.6.0 in /frontend
2019-11-12 07:50:15 +00:00
dependabot-preview[bot]
d249b8b202 chore(deps-dev): bump eslint from 5.16.0 to 6.6.0 in /frontend
Bumps [eslint](https://github.com/eslint/eslint) from 5.16.0 to 6.6.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v5.16.0...v6.6.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-11 12:23:46 +00:00
Henrique Dias
e9bd68f3b0 docs: make warning more visivble 2019-11-10 09:13:32 +00:00
Henrique Dias
506e088236 chore(deps): bump github.com/spf13/viper from 1.4.0 to 1.5.0 (#908)
Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.4.0 to 1.5.0.
- [Release notes](https://github.com/spf13/viper/releases)
- [Commits](https://github.com/spf13/viper/compare/v1.4.0...v1.5.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-10 09:08:05 +00:00
dependabot-preview[bot]
c906d296be chore(deps): bump gopkg.in/yaml.v2 from 2.2.4 to 2.2.5 (#910)
Bumps [gopkg.in/yaml.v2](https://github.com/go-yaml/yaml) from 2.2.4 to 2.2.5.
- [Release notes](https://github.com/go-yaml/yaml/releases)
- [Commits](https://github.com/go-yaml/yaml/compare/v2.2.4...v2.2.5)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-10 09:07:55 +00:00
dependabot-preview[bot]
3b7f6ccf8e chore(deps-dev): bump eslint-plugin-vue from 5.2.3 to 6.0.0 in /… (#911)
Bumps [eslint-plugin-vue](https://github.com/vuejs/eslint-plugin-vue) from 5.2.3 to 6.0.0.
- [Release notes](https://github.com/vuejs/eslint-plugin-vue/releases)
- [Commits](https://github.com/vuejs/eslint-plugin-vue/compare/v5.2.3...v6.0.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-10 09:07:41 +00:00
dependabot-preview[bot]
f1a7d2f8d0 chore(deps): bump github.com/spf13/viper from 1.4.0 to 1.5.0
Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.4.0 to 1.5.0.
- [Release notes](https://github.com/spf13/viper/releases)
- [Commits](https://github.com/spf13/viper/compare/v1.4.0...v1.5.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-04 14:01:17 +00:00
Henrique Dias
fb13ded8e8 chore: version v2.0.16 2019-11-04 07:27:38 +00:00
dependabot-preview[bot]
85e4ff67e4 chore(deps): bump github.com/pelletier/go-toml from 1.5.0 to 1.6… (#904)
Bumps [github.com/pelletier/go-toml](https://github.com/pelletier/go-toml) from 1.5.0 to 1.6.0.
- [Release notes](https://github.com/pelletier/go-toml/releases)
- [Commits](https://github.com/pelletier/go-toml/compare/v1.5.0...v1.6.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-04 07:26:19 +00:00
Nate Dalliard
6250efa208 add wrap to the editor (#906) 2019-11-04 07:23:48 +00:00
dependabot-preview[bot]
f1e1a27408 chore(deps-dev): bump @vue/cli-plugin-eslint in /frontend (#900)
Bumps [@vue/cli-plugin-eslint](https://github.com/vuejs/vue-cli/tree/HEAD/packages/@vue/cli-plugin-eslint) from 4.0.4 to 4.0.5.
- [Release notes](https://github.com/vuejs/vue-cli/releases)
- [Changelog](https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-cli/commits/v4.0.5/packages/@vue/cli-plugin-eslint)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-24 12:11:16 +01:00
Miroslav Šedivý
076358ab79 fix: delete keyup only if no active prompt (#896) 2019-10-24 12:10:34 +01:00
dependabot-preview[bot]
d1efc14bb9 chore(deps): bump ace-builds from 1.4.6 to 1.4.7 in /frontend (#894)
Bumps [ace-builds](https://github.com/ajaxorg/ace-builds) from 1.4.6 to 1.4.7.
- [Release notes](https://github.com/ajaxorg/ace-builds/releases)
- [Changelog](https://github.com/ajaxorg/ace-builds/blob/master/ChangeLog.txt)
- [Commits](https://github.com/ajaxorg/ace-builds/compare/v1.4.6...v1.4.7)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-24 12:07:56 +01:00
dependabot-preview[bot]
508b7b326f chore(deps-dev): bump @vue/cli-service from 4.0.4 to 4.0.5 in /frontend (#899)
Bumps [@vue/cli-service](https://github.com/vuejs/vue-cli/tree/HEAD/packages/@vue/cli-service) from 4.0.4 to 4.0.5.
- [Release notes](https://github.com/vuejs/vue-cli/releases)
- [Changelog](https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-cli/commits/v4.0.5/packages/@vue/cli-service)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-24 12:07:35 +01:00
dependabot-preview[bot]
d1284972a3 chore(deps-dev): bump @vue/cli-plugin-babel in /frontend (#898)
Bumps [@vue/cli-plugin-babel](https://github.com/vuejs/vue-cli/tree/HEAD/packages/@vue/cli-plugin-babel) from 4.0.4 to 4.0.5.
- [Release notes](https://github.com/vuejs/vue-cli/releases)
- [Changelog](https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-cli/commits/v4.0.5/packages/@vue/cli-plugin-babel)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-24 12:07:21 +01:00
dependabot-preview[bot]
cdba1d0c52 chore(deps-dev): bump @vue/cli-plugin-eslint from 4.0.3 to 4.0.4… (#890)
Bumps [@vue/cli-plugin-eslint](https://github.com/vuejs/vue-cli/tree/HEAD/packages/@vue/cli-plugin-eslint) from 4.0.3 to 4.0.4.
- [Release notes](https://github.com/vuejs/vue-cli/releases)
- [Changelog](https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-cli/commits/v4.0.4/packages/@vue/cli-plugin-eslint)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-21 09:21:23 +01:00
Henrique Dias
ec28375208 chore: version v2.0.15 2019-10-18 15:54:07 +01:00
dependabot-preview[bot]
01068a9217 chore(deps-dev): bump @vue/cli-service from 4.0.3 to 4.0.4 in /f… (#891)
Bumps [@vue/cli-service](https://github.com/vuejs/vue-cli/tree/HEAD/packages/@vue/cli-service) from 4.0.3 to 4.0.4.
- [Release notes](https://github.com/vuejs/vue-cli/releases)
- [Changelog](https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-cli/commits/v4.0.4/packages/@vue/cli-service)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-18 13:33:11 +01:00
dependabot-preview[bot]
7f01753bc5 chore(deps-dev): bump @vue/cli-plugin-babel from 4.0.3 to 4.0.4… (#892)
Bumps [@vue/cli-plugin-babel](https://github.com/vuejs/vue-cli/tree/HEAD/packages/@vue/cli-plugin-babel) from 4.0.3 to 4.0.4.
- [Release notes](https://github.com/vuejs/vue-cli/releases)
- [Changelog](https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-cli/commits/v4.0.4/packages/@vue/cli-plugin-babel)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-18 13:33:03 +01:00
DanielV
0e223a056e feat: generate manifest.json dynamically (#889) 2019-10-18 09:00:13 +01:00
dependabot-preview[bot]
9d08f9bed1 chore(deps-dev): bump @vue/cli-plugin-eslint from 3.11.0 to 4.0.… (#887)
Bumps [@vue/cli-plugin-eslint](https://github.com/vuejs/vue-cli/tree/HEAD/packages/@vue/cli-plugin-eslint) from 3.11.0 to 4.0.3.
- [Release notes](https://github.com/vuejs/vue-cli/releases)
- [Changelog](https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-cli/commits/v4.0.3/packages/@vue/cli-plugin-eslint)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-18 00:30:15 +01:00
dependabot-preview[bot]
2cabeb8f68 chore(deps): bump vue-i18n from 8.14.1 to 8.15.0 in /frontend (#881)
Bumps [vue-i18n](https://github.com/kazupon/vue-i18n) from 8.14.1 to 8.15.0.
- [Release notes](https://github.com/kazupon/vue-i18n/releases)
- [Changelog](https://github.com/kazupon/vue-i18n/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/kazupon/vue-i18n/compare/v8.14.1...v8.15.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-18 00:28:01 +01:00
dependabot-preview[bot]
7aaebab348 chore(deps-dev): bump @vue/cli-service from 3.12.0 to 4.0.3 in /… (#886)
Bumps [@vue/cli-service](https://github.com/vuejs/vue-cli/tree/HEAD/packages/@vue/cli-service) from 3.12.0 to 4.0.3.
- [Release notes](https://github.com/vuejs/vue-cli/releases)
- [Changelog](https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-cli/commits/v4.0.3/packages/@vue/cli-service)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-18 00:27:54 +01:00
dependabot-preview[bot]
928cdfe2ae chore(deps-dev): bump @vue/cli-plugin-babel from 3.11.0 to 4.0.3… (#888)
Bumps [@vue/cli-plugin-babel](https://github.com/vuejs/vue-cli/tree/HEAD/packages/@vue/cli-plugin-babel) from 3.11.0 to 4.0.3.
- [Release notes](https://github.com/vuejs/vue-cli/releases)
- [Changelog](https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-cli/commits/v4.0.3/packages/@vue/cli-plugin-babel)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-18 00:27:35 +01:00
90 changed files with 4783 additions and 2735 deletions

View File

@@ -2,10 +2,10 @@ version: 2
jobs: jobs:
lint: lint:
docker: docker:
- image: golangci/golangci-lint:v1.16 - image: golangci/golangci-lint:v1.27.0
steps: steps:
- checkout - checkout
- run: golangci-lint run -v -D errcheck - run: golangci-lint run -v
build-node: build-node:
docker: docker:
- image: circleci/node - image: circleci/node
@@ -23,7 +23,7 @@ jobs:
- '*' - '*'
build-go: build-go:
docker: docker:
- image: circleci/golang:1.12 - image: circleci/golang:1.14.3
steps: steps:
- attach_workspace: - attach_workspace:
at: '~/project' at: '~/project'
@@ -41,7 +41,7 @@ jobs:
- '*' - '*'
release: release:
docker: docker:
- image: circleci/golang:1.12 - image: circleci/golang:1.14.3
steps: steps:
- attach_workspace: - attach_workspace:
at: '~/project' at: '~/project'

1
.gitignore vendored
View File

@@ -5,6 +5,7 @@ _old
rice-box.go rice-box.go
.idea/ .idea/
filebrowser filebrowser
dist/
.DS_Store .DS_Store
node_modules node_modules

132
.golangci.yml Normal file
View File

@@ -0,0 +1,132 @@
linters-settings:
dupl:
threshold: 100
exhaustive:
default-signifies-exhaustive: false
funlen:
lines: 100
statements: 50
goconst:
min-len: 2
min-occurrences: 2
gocritic:
enabled-tags:
- diagnostic
- experimental
- opinionated
- performance
- style
disabled-checks:
- dupImport # https://github.com/go-critic/go-critic/issues/845
- ifElseChain
- octalLiteral
- whyNoLint
- wrapperFunc
gocyclo:
min-complexity: 15
goimports:
local-prefixes: github.com/filebrowser/filebrowser
golint:
min-confidence: 0
gomnd:
settings:
mnd:
# don't include the "operation" and "assign"
checks: argument,case,condition,return
govet:
check-shadowing: true
lll:
line-length: 140
maligned:
suggest-new: true
misspell:
locale: US
nolintlint:
allow-leading-space: true # don't require machine-readable nolint directives (i.e. with no leading space)
allow-unused: false # report any unused nolint directives
require-explanation: false # don't require an explanation for nolint directives
require-specific: false # don't require nolint directives to be specific about which linter is being skipped
linters:
# please, do not use `enable-all`: it's deprecated and will be removed soon.
# inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint
disable-all: true
enable:
- bodyclose
- deadcode
- depguard
- dogsled
- dupl
- errcheck
- funlen
- gochecknoinits
- goconst
- gocritic
- gocyclo
- gofmt
- goimports
- golint
- gomnd
- goprintffuncname
- gosec
- gosimple
- govet
- ineffassign
- interfacer
- lll
- misspell
- nakedret
- nolintlint
- rowserrcheck
- scopelint
- staticcheck
- structcheck
- stylecheck
- typecheck
- unconvert
- unparam
- unused
- varcheck
- whitespace
- 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:
exclude-rules:
- path: cmd/.*.go
linters:
- gochecknoinits
- path: .*_test.go
linters:
- lll
- gochecknoinits
- gocyclo
- funlen
- dupl
- scopelint
- text: "Auther"
linters:
- misspell
run:
skip-dirs:
- frontend/
skip-files:
- http/rice-box.go
# golangci.com configuration
# https://github.com/golangci/golangci/wiki/Configuration
service:
golangci-lint-version: 1.27.x # use the fixed version to not introduce new linters unexpectedly

View File

@@ -54,6 +54,9 @@ archives:
dockers: dockers:
- -
dockerfile: Dockerfile
binaries:
- filebrowser
goos: linux goos: linux
goarch: amd64 goarch: amd64
goarm: '' goarm: ''
@@ -63,3 +66,42 @@ dockers:
- "filebrowser/filebrowser:v{{ .Major }}" - "filebrowser/filebrowser:v{{ .Major }}"
extra_files: extra_files:
- .docker.json - .docker.json
-
dockerfile: Dockerfile
binaries:
- filebrowser
goos: linux
goarch: arm
goarm: '5'
image_templates:
- "filebrowser/filebrowser:pi"
- "filebrowser/filebrowser:{{ .Tag }}-pi"
- "filebrowser/filebrowser:v{{ .Major }}-pi"
extra_files:
- .docker.json
-
dockerfile: Dockerfile.alpine
binaries:
- filebrowser
goos: linux
goarch: amd64
goarm: ''
image_templates:
- "filebrowser/filebrowser:alpine"
- "filebrowser/filebrowser:{{ .Tag }}-apline"
- "filebrowser/filebrowser:v{{ .Major }}-alpine"
extra_files:
- .docker.json
-
dockerfile: Dockerfile.debian
binaries:
- filebrowser
goos: linux
goarch: amd64
goarm: ''
image_templates:
- "filebrowser/filebrowser:debian"
- "filebrowser/filebrowser:{{ .Tag }}-debian"
- "filebrowser/filebrowser:v{{ .Major }}-debian"
extra_files:
- .docker.json

22
CHANGELOG.md Normal file
View File

@@ -0,0 +1,22 @@
# Changelog
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.2.0](https://github.com/filebrowser/filebrowser/compare/v2.1.2...v2.2.0) (2020-06-22)
### Features
* add alpine and debian docker images ([66863b7](https://github.com/filebrowser/filebrowser/commit/66863b72f7664e6cb9417f7da542a92fa77ca635))
* add folder upload ([#981](https://github.com/filebrowser/filebrowser/issues/981)) ([8977344](https://github.com/filebrowser/filebrowser/commit/89773447a56675b298394149d7a05c5df4039f14)), closes [filebrowser/filebrowser#741](https://github.com/filebrowser/filebrowser/issues/741)
* add key shortcuts ([95316cb](https://github.com/filebrowser/filebrowser/commit/95316cbe8c8ac3dbb28310bc11ec347c0caf699b))
* upload progress based on total size ([#993](https://github.com/filebrowser/filebrowser/issues/993)) ([cd454ba](https://github.com/filebrowser/filebrowser/commit/cd454bae51f40b1249e6fa6133c2949970eb3018))
### Bug Fixes
* add a workaround to fix window freezing when viewing a large file [#992](https://github.com/filebrowser/filebrowser/issues/992) ([2412016](https://github.com/filebrowser/filebrowser/commit/241201657c2bf01806d02a297eb846b26102a479))
* apply all fs user rulles ([68f8348](https://github.com/filebrowser/filebrowser/commit/68f8348ddeecba570a361e7aba4546052cc3e356))
* frontend token validation ([dd40b0d](https://github.com/filebrowser/filebrowser/commit/dd40b0d9b9cc6268a611306ac4684a1af852b79d)), closes [filebrowser/filebrowser#638](https://github.com/filebrowser/filebrowser/issues/638)
* multiple selection count ([963837e](https://github.com/filebrowser/filebrowser/commit/963837ef1dc6e2e84fcf924606ce388ac30f3891))
* save event hook ([82c883f](https://github.com/filebrowser/filebrowser/commit/82c883f95eead9eebe215e230f74773c945f864a)), closes [filebrowser/filebrowser#696](https://github.com/filebrowser/filebrowser/issues/696)

View File

@@ -1,8 +1,10 @@
FROM alpine:latest as certs FROM alpine:latest as alpine
RUN apk --update add ca-certificates RUN apk --update add ca-certificates
RUN apk --update add mailcap
FROM scratch FROM scratch
COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt COPY --from=alpine /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=alpine /etc/mime.types /etc/mime.types
VOLUME /srv VOLUME /srv
EXPOSE 80 EXPOSE 80

11
Dockerfile.alpine Normal file
View File

@@ -0,0 +1,11 @@
FROM alpine:latest as alpine
RUN apk --update add ca-certificates
RUN apk --update add mailcap
VOLUME /srv
EXPOSE 80
COPY .docker.json /.filebrowser.json
COPY filebrowser /filebrowser
ENTRYPOINT [ "/filebrowser" ]

9
Dockerfile.debian Normal file
View File

@@ -0,0 +1,9 @@
FROM debian:buster
VOLUME /srv
EXPOSE 80
COPY .docker.json /.filebrowser.json
COPY filebrowser /filebrowser
ENTRYPOINT [ "/filebrowser" ]

View File

@@ -2,8 +2,6 @@
<img src="https://raw.githubusercontent.com/filebrowser/logo/master/banner.png" width="550"/> <img src="https://raw.githubusercontent.com/filebrowser/logo/master/banner.png" width="550"/>
</p> </p>
> ⚠️ WARN: **This project is not under active development ATM. A small group of developers keeps the project alive, but due to lack of time, we can't continue adding new features or doing deep changes. Please read [#532](https://github.com/filebrowser/filebrowser/issues/532) for more info!**
![Preview](https://user-images.githubusercontent.com/5447088/50716739-ebd26700-107a-11e9-9817-14230c53efd2.gif) ![Preview](https://user-images.githubusercontent.com/5447088/50716739-ebd26700-107a-11e9-9817-14230c53efd2.gif)
[![Travis](https://img.shields.io/travis/com/filebrowser/filebrowser.svg?style=flat-square)](https://travis-ci.com/filebrowser/filebrowser) [![Travis](https://img.shields.io/travis/com/filebrowser/filebrowser.svg?style=flat-square)](https://travis-ci.com/filebrowser/filebrowser)
@@ -16,16 +14,20 @@ filebrowser provides a file managing interface within a specified directory and
## Features ## Features
Please refer to our docs at [filebrowser.xyz/features](https://filebrowser.xyz/features) Please refer to our docs at [https://filebrowser.org/features](https://filebrowser.org/features)
## Install ## Install
Please refer to our docs at [filebrowser.xyz](https://filebrowser.xyz/). For installation instructions please refer to our docs at [https://filebrowser.org/installation](https://filebrowser.org/installation).
## Usage ## Configuration
Please refer to our docs at [filebrowser.xyz/usage](https://filebrowser.xyz/usage). [Authentication Method](https://filebrowser.org/configuration/authentication-method) - You can change the way the user authenticates with the filebrowser server
[Commander Runner](https://filebrowser.org/configuration/command-runner) - The command runner is a feature that enables you to execute any shell command you want before or after a certain event.
[Custom Branding](https://filebrowser.org/configuration/custom-branding) - You can customize your File Browser installation by change its name to any other you want, by adding a global custom style sheet and by using your own logotype if you want.
## Contributing ## Contributing
Please refer to our docs at [filebrowser.xyz/contributing](https://filebrowser.xyz/contributing). If you're interested in contributing to this project, our docs are best places to start [https://filebrowser.org/contributing](https://filebrowser.org/contributing).

View File

@@ -20,7 +20,7 @@ type jsonCred struct {
ReCaptcha string `json:"recaptcha"` ReCaptcha string `json:"recaptcha"`
} }
// JSONAuth is a json implementaion of an Auther. // JSONAuth is a json implementation of an Auther.
type JSONAuth struct { type JSONAuth struct {
ReCaptcha *ReCaptcha `json:"recaptcha" yaml:"recaptcha"` ReCaptcha *ReCaptcha `json:"recaptcha" yaml:"recaptcha"`
} }
@@ -40,7 +40,7 @@ func (a JSONAuth) Auth(r *http.Request, sto *users.Storage, root string) (*users
// If ReCaptcha is enabled, check the code. // If ReCaptcha is enabled, check the code.
if a.ReCaptcha != nil && len(a.ReCaptcha.Secret) > 0 { if a.ReCaptcha != nil && len(a.ReCaptcha.Secret) > 0 {
ok, err := a.ReCaptcha.Ok(cred.ReCaptcha) ok, err := a.ReCaptcha.Ok(cred.ReCaptcha) //nolint:shadow
if err != nil { if err != nil {
return nil, err return nil, err
@@ -66,7 +66,7 @@ func (a JSONAuth) LoginPage() bool {
const reCaptchaAPI = "/recaptcha/api/siteverify" const reCaptchaAPI = "/recaptcha/api/siteverify"
// ReCaptcha identifies a recaptcha conenction. // ReCaptcha identifies a recaptcha connection.
type ReCaptcha struct { type ReCaptcha struct {
Host string `json:"host"` Host string `json:"host"`
Key string `json:"key"` Key string `json:"key"`
@@ -89,6 +89,7 @@ func (r *ReCaptcha) Ok(response string) (bool, error) {
if err != nil { if err != nil {
return false, err return false, err
} }
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
return false, nil return false, nil

View File

@@ -18,8 +18,8 @@ type Storage struct {
} }
// NewStorage creates a auth storage from a backend. // NewStorage creates a auth storage from a backend.
func NewStorage(back StorageBackend, users *users.Storage) *Storage { func NewStorage(back StorageBackend, userStore *users.Storage) *Storage {
return &Storage{back: back, users: users} return &Storage{back: back, users: userStore}
} }
// Get wraps a StorageBackend.Get. // Get wraps a StorageBackend.Get.

View File

@@ -14,7 +14,7 @@ var cmdsAddCmd = &cobra.Command{
Use: "add <event> <command>", Use: "add <event> <command>",
Short: "Add a command to run on a specific event", Short: "Add a command to run on a specific event",
Long: `Add a command to run on a specific event.`, Long: `Add a command to run on a specific event.`,
Args: cobra.MinimumNArgs(2), Args: cobra.MinimumNArgs(2), //nolint:mnd
Run: python(func(cmd *cobra.Command, args []string, d pythonData) { Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
s, err := d.store.Settings.Get() s, err := d.store.Settings.Get()
checkErr(err) checkErr(err)

View File

@@ -23,7 +23,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(2, 3)(cmd, args); err != nil { if err := cobra.RangeArgs(2, 3)(cmd, args); err != nil { //nolint:mnd
return err return err
} }
@@ -43,7 +43,7 @@ including 'index_end'.`,
i, err := strconv.Atoi(args[1]) i, err := strconv.Atoi(args[1])
checkErr(err) checkErr(err)
f := i f := i
if len(args) == 3 { if len(args) == 3 { //nolint:mnd
f, err = strconv.Atoi(args[2]) f, err = strconv.Atoi(args[2])
checkErr(err) checkErr(err)
} }

View File

@@ -8,11 +8,12 @@ import (
"strings" "strings"
"text/tabwriter" "text/tabwriter"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/filebrowser/filebrowser/v2/auth" "github.com/filebrowser/filebrowser/v2/auth"
"github.com/filebrowser/filebrowser/v2/errors" "github.com/filebrowser/filebrowser/v2/errors"
"github.com/filebrowser/filebrowser/v2/settings" "github.com/filebrowser/filebrowser/v2/settings"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
) )
func init() { func init() {
@@ -44,6 +45,7 @@ func addConfigFlags(flags *pflag.FlagSet) {
flags.Bool("branding.disableExternal", false, "disable external links such as GitHub links") flags.Bool("branding.disableExternal", false, "disable external links such as GitHub links")
} }
//nolint:gocyclo
func getAuthentication(flags *pflag.FlagSet, defaults ...interface{}) (settings.AuthMethod, auth.Auther) { func getAuthentication(flags *pflag.FlagSet, defaults ...interface{}) (settings.AuthMethod, auth.Auther) {
method := settings.AuthMethod(mustGetString(flags, "auth.method")) method := settings.AuthMethod(mustGetString(flags, "auth.method"))
@@ -53,11 +55,12 @@ func getAuthentication(flags *pflag.FlagSet, defaults ...interface{}) (settings.
for _, arg := range defaults { for _, arg := range defaults {
switch def := arg.(type) { switch def := arg.(type) {
case *settings.Settings: case *settings.Settings:
method = settings.AuthMethod(def.AuthMethod) method = def.AuthMethod
case auth.Auther: case auth.Auther:
ms, err := json.Marshal(def) ms, err := json.Marshal(def)
checkErr(err) checkErr(err)
json.Unmarshal(ms, &defaultAuther) err = json.Unmarshal(ms, &defaultAuther)
checkErr(err)
} }
} }
} }

View File

@@ -6,9 +6,10 @@ import (
"path/filepath" "path/filepath"
"reflect" "reflect"
"github.com/spf13/cobra"
"github.com/filebrowser/filebrowser/v2/auth" "github.com/filebrowser/filebrowser/v2/auth"
"github.com/filebrowser/filebrowser/v2/settings" "github.com/filebrowser/filebrowser/v2/settings"
"github.com/spf13/cobra"
) )
func init() { func init() {
@@ -55,7 +56,7 @@ The path must be for a json or yaml file.`,
checkErr(err) checkErr(err)
var rawAuther interface{} var rawAuther interface{}
if filepath.Ext(args[0]) != ".json" { if filepath.Ext(args[0]) != ".json" { //nolint:goconst
rawAuther = cleanUpInterfaceMap(file.Auther.(map[interface{}]interface{})) rawAuther = cleanUpInterfaceMap(file.Auther.(map[interface{}]interface{}))
} else { } else {
rawAuther = file.Auther rawAuther = file.Auther

View File

@@ -4,8 +4,9 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/filebrowser/filebrowser/v2/settings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/filebrowser/filebrowser/v2/settings"
) )
func init() { func init() {

View File

@@ -88,7 +88,7 @@ func generateMarkdown(cmd *cobra.Command, w io.Writer) {
short := cmd.Short short := cmd.Short
long := cmd.Long long := cmd.Long
if len(long) == 0 { if long == "" {
long = short long = short
} }
@@ -106,21 +106,21 @@ func generateMarkdown(cmd *cobra.Command, w io.Writer) {
buf.WriteString(fmt.Sprintf("```\n%s\n```\n\n", cmd.Example)) buf.WriteString(fmt.Sprintf("```\n%s\n```\n\n", cmd.Example))
} }
printOptions(buf, cmd, name) printOptions(buf, cmd)
_, err := buf.WriteTo(w) _, err := buf.WriteTo(w)
checkErr(err) checkErr(err)
} }
func generateFlagsTable(fs *pflag.FlagSet, buf io.StringWriter) { func generateFlagsTable(fs *pflag.FlagSet, buf io.StringWriter) {
buf.WriteString("| Name | Shorthand | Usage |\n") _, _ = buf.WriteString("| Name | Shorthand | Usage |\n")
buf.WriteString("|------|-----------|-------|\n") _, _ = buf.WriteString("|------|-----------|-------|\n")
fs.VisitAll(func(f *pflag.Flag) { fs.VisitAll(func(f *pflag.Flag) {
buf.WriteString("|" + f.Name + "|" + f.Shorthand + "|" + f.Usage + "|\n") _, _ = buf.WriteString("|" + f.Name + "|" + f.Shorthand + "|" + f.Usage + "|\n")
}) })
} }
func printOptions(buf *bytes.Buffer, cmd *cobra.Command, name string) { func printOptions(buf *bytes.Buffer, cmd *cobra.Command) {
flags := cmd.NonInheritedFlags() flags := cmd.NonInheritedFlags()
flags.SetOutput(buf) flags.SetOutput(buf)
if flags.HasAvailableFlags() { if flags.HasAvailableFlags() {

View File

@@ -3,8 +3,9 @@ package cmd
import ( import (
"fmt" "fmt"
"github.com/filebrowser/filebrowser/v2/users"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/filebrowser/filebrowser/v2/users"
) )
func init() { func init() {

View File

@@ -13,16 +13,17 @@ import (
"strings" "strings"
"syscall" "syscall"
"github.com/filebrowser/filebrowser/v2/auth"
fbhttp "github.com/filebrowser/filebrowser/v2/http"
"github.com/filebrowser/filebrowser/v2/settings"
"github.com/filebrowser/filebrowser/v2/storage"
"github.com/filebrowser/filebrowser/v2/users"
homedir "github.com/mitchellh/go-homedir" homedir "github.com/mitchellh/go-homedir"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag" "github.com/spf13/pflag"
v "github.com/spf13/viper" v "github.com/spf13/viper"
lumberjack "gopkg.in/natefinch/lumberjack.v2" lumberjack "gopkg.in/natefinch/lumberjack.v2"
"github.com/filebrowser/filebrowser/v2/auth"
fbhttp "github.com/filebrowser/filebrowser/v2/http"
"github.com/filebrowser/filebrowser/v2/settings"
"github.com/filebrowser/filebrowser/v2/storage"
"github.com/filebrowser/filebrowser/v2/users"
) )
var ( var (
@@ -113,16 +114,17 @@ user created with the credentials from options "username" and "password".`,
var listener net.Listener var listener net.Listener
if server.Socket != "" { switch {
case server.Socket != "":
listener, err = net.Listen("unix", server.Socket) listener, err = net.Listen("unix", server.Socket)
checkErr(err) checkErr(err)
} else if server.TLSKey != "" && server.TLSCert != "" { case server.TLSKey != "" && server.TLSCert != "":
cer, err := tls.LoadX509KeyPair(server.TLSCert, server.TLSKey) cer, err := tls.LoadX509KeyPair(server.TLSCert, server.TLSKey) //nolint:shadow
checkErr(err) checkErr(err)
listener, err = tls.Listen("tcp", adr, &tls.Config{Certificates: []tls.Certificate{cer}}) listener, err = tls.Listen("tcp", adr, &tls.Config{Certificates: []tls.Certificate{cer}}) //nolint:shadow
checkErr(err) checkErr(err)
} else { default:
listener, err = net.Listen("tcp", adr) listener, err = net.Listen("tcp", adr) //nolint:shadow
checkErr(err) checkErr(err)
} }
@@ -142,13 +144,14 @@ user created with the credentials from options "username" and "password".`,
}, pythonConfig{allowNoDB: true}), }, pythonConfig{allowNoDB: true}),
} }
func cleanupHandler(listener net.Listener, c chan os.Signal) { func cleanupHandler(listener net.Listener, c chan os.Signal) { //nolint:interfacer
sig := <-c sig := <-c
log.Printf("Caught signal %s: shutting down.", sig) log.Printf("Caught signal %s: shutting down.", sig)
listener.Close() listener.Close()
os.Exit(0) os.Exit(0)
} }
//nolint:gocyclo
func getRunParams(flags *pflag.FlagSet, st *storage.Storage) *settings.Server { func getRunParams(flags *pflag.FlagSet, st *storage.Storage) *settings.Server {
server, err := st.Settings.GetServer() server, err := st.Settings.GetServer()
checkErr(err) checkErr(err)
@@ -348,5 +351,4 @@ func initConfig() {
} else { } else {
cfgFile = "Using config file: " + v.ConfigFileUsed() cfgFile = "Using config file: " + v.ConfigFileUsed()
} }
} }

View File

@@ -3,15 +3,16 @@ package cmd
import ( import (
"strconv" "strconv"
"github.com/spf13/cobra"
"github.com/filebrowser/filebrowser/v2/settings" "github.com/filebrowser/filebrowser/v2/settings"
"github.com/filebrowser/filebrowser/v2/users" "github.com/filebrowser/filebrowser/v2/users"
"github.com/spf13/cobra"
) )
func init() { func init() {
rulesCmd.AddCommand(rulesRmCommand) rulesCmd.AddCommand(rulesRmCommand)
rulesRmCommand.Flags().Uint("index", 0, "index of rule to remove") rulesRmCommand.Flags().Uint("index", 0, "index of rule to remove")
rulesRmCommand.MarkFlagRequired("index") _ = rulesRmCommand.MarkFlagRequired("index")
} }
var rulesRmCommand = &cobra.Command{ var rulesRmCommand = &cobra.Command{
@@ -43,7 +44,7 @@ including 'index_end'.`,
i, err := strconv.Atoi(args[0]) i, err := strconv.Atoi(args[0])
checkErr(err) checkErr(err)
f := i f := i
if len(args) == 2 { if len(args) == 2 { //nolint:mnd
f, err = strconv.Atoi(args[1]) f, err = strconv.Atoi(args[1])
checkErr(err) checkErr(err)
} }

View File

@@ -3,12 +3,13 @@ package cmd
import ( import (
"fmt" "fmt"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/filebrowser/filebrowser/v2/rules" "github.com/filebrowser/filebrowser/v2/rules"
"github.com/filebrowser/filebrowser/v2/settings" "github.com/filebrowser/filebrowser/v2/settings"
"github.com/filebrowser/filebrowser/v2/storage" "github.com/filebrowser/filebrowser/v2/storage"
"github.com/filebrowser/filebrowser/v2/users" "github.com/filebrowser/filebrowser/v2/users"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
) )
func init() { func init() {
@@ -18,8 +19,8 @@ func init() {
} }
var rulesCmd = &cobra.Command{ var rulesCmd = &cobra.Command{
Use: "rules", Use: "rules",
Short: "Rules management utility", Short: "Rules management utility",
Long: `On each subcommand you'll have available at least two flags: Long: `On each subcommand you'll have available at least two flags:
"username" and "id". You must either set only one of them "username" and "id". You must either set only one of them
or none. If you set one of them, the command will apply to or none. If you set one of them, the command will apply to
@@ -28,14 +29,14 @@ rules.`,
Args: cobra.NoArgs, Args: cobra.NoArgs,
} }
func runRules(st *storage.Storage, cmd *cobra.Command, users func(*users.User), global func(*settings.Settings)) { func runRules(st *storage.Storage, cmd *cobra.Command, usersFn func(*users.User), globalFn func(*settings.Settings)) {
id := getUserIdentifier(cmd.Flags()) id := getUserIdentifier(cmd.Flags())
if id != nil { if id != nil {
user, err := st.Users.Get("", id) user, err := st.Users.Get("", id)
checkErr(err) checkErr(err)
if users != nil { if usersFn != nil {
users(user) usersFn(user)
} }
printRules(user.Rules, id) printRules(user.Rules, id)
@@ -45,8 +46,8 @@ func runRules(st *storage.Storage, cmd *cobra.Command, users func(*users.User),
s, err := st.Settings.Get() s, err := st.Settings.Get()
checkErr(err) checkErr(err)
if global != nil { if globalFn != nil {
global(s) globalFn(s)
} }
printRules(s.Rules, id) printRules(s.Rules, id)
@@ -65,14 +66,14 @@ func getUserIdentifier(flags *pflag.FlagSet) interface{} {
return nil return nil
} }
func printRules(rules []rules.Rule, id interface{}) { func printRules(rulez []rules.Rule, id interface{}) {
if id == nil { if id == nil {
fmt.Printf("Global Rules:\n\n") fmt.Printf("Global Rules:\n\n")
} else { } else {
fmt.Printf("Rules for user %v:\n\n", id) fmt.Printf("Rules for user %v:\n\n", id)
} }
for id, rule := range rules { for id, rule := range rulez {
fmt.Printf("(%d) ", id) fmt.Printf("(%d) ", id)
if rule.Regex { if rule.Regex {
if rule.Allow { if rule.Allow {

View File

@@ -3,10 +3,11 @@ package cmd
import ( import (
"regexp" "regexp"
"github.com/spf13/cobra"
"github.com/filebrowser/filebrowser/v2/rules" "github.com/filebrowser/filebrowser/v2/rules"
"github.com/filebrowser/filebrowser/v2/settings" "github.com/filebrowser/filebrowser/v2/settings"
"github.com/filebrowser/filebrowser/v2/users" "github.com/filebrowser/filebrowser/v2/users"
"github.com/spf13/cobra"
) )
func init() { func init() {

View File

@@ -1,8 +1,9 @@
package cmd package cmd
import ( import (
"github.com/filebrowser/filebrowser/v2/storage/bolt/importer"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/filebrowser/filebrowser/v2/storage/bolt/importer"
) )
func init() { func init() {
@@ -10,7 +11,7 @@ func init() {
upgradeCmd.Flags().String("old.database", "", "") upgradeCmd.Flags().String("old.database", "", "")
upgradeCmd.Flags().String("old.config", "", "") upgradeCmd.Flags().String("old.config", "", "")
upgradeCmd.MarkFlagRequired("old.database") _ = upgradeCmd.MarkFlagRequired("old.database")
} }
var upgradeCmd = &cobra.Command{ var upgradeCmd = &cobra.Command{

View File

@@ -7,10 +7,11 @@ import (
"strconv" "strconv"
"text/tabwriter" "text/tabwriter"
"github.com/filebrowser/filebrowser/v2/settings"
"github.com/filebrowser/filebrowser/v2/users"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"github.com/filebrowser/filebrowser/v2/settings"
"github.com/filebrowser/filebrowser/v2/users"
) )
func init() { func init() {
@@ -24,38 +25,38 @@ var usersCmd = &cobra.Command{
Args: cobra.NoArgs, Args: cobra.NoArgs,
} }
func printUsers(users []*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)
fmt.Fprintln(w, "ID\tUsername\tScope\tLocale\tV. Mode\tAdmin\tExecute\tCreate\tRename\tModify\tDelete\tShare\tDownload\tPwd Lock") fmt.Fprintln(w, "ID\tUsername\tScope\tLocale\tV. Mode\tAdmin\tExecute\tCreate\tRename\tModify\tDelete\tShare\tDownload\tPwd Lock")
for _, user := range users { for _, u := range usrs {
fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%s\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t\n", fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%s\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t\n",
user.ID, u.ID,
user.Username, u.Username,
user.Scope, u.Scope,
user.Locale, u.Locale,
user.ViewMode, u.ViewMode,
user.Perm.Admin, u.Perm.Admin,
user.Perm.Execute, u.Perm.Execute,
user.Perm.Create, u.Perm.Create,
user.Perm.Rename, u.Perm.Rename,
user.Perm.Modify, u.Perm.Modify,
user.Perm.Delete, u.Perm.Delete,
user.Perm.Share, u.Perm.Share,
user.Perm.Download, u.Perm.Download,
user.LockPassword, u.LockPassword,
) )
} }
w.Flush() w.Flush()
} }
func parseUsernameOrID(arg string) (string, uint) { func parseUsernameOrID(arg string) (username string, id uint) {
id, err := strconv.ParseUint(arg, 10, 0) id64, err := strconv.ParseUint(arg, 10, 0)
if err != nil { if err != nil {
return arg, 0 return arg, 0
} }
return "", uint(id) return "", uint(id64)
} }
func addUserFlags(flags *pflag.FlagSet) { func addUserFlags(flags *pflag.FlagSet) {
@@ -84,6 +85,7 @@ func getViewMode(flags *pflag.FlagSet) users.ViewMode {
return viewMode return viewMode
} }
//nolint:gocyclo
func getUserDefaults(flags *pflag.FlagSet, defaults *settings.UserDefaults, all bool) { func getUserDefaults(flags *pflag.FlagSet, defaults *settings.UserDefaults, all bool) {
visit := func(flag *pflag.Flag) { visit := func(flag *pflag.Flag) {
switch flag.Name { switch flag.Name {

View File

@@ -1,8 +1,9 @@
package cmd package cmd
import ( import (
"github.com/filebrowser/filebrowser/v2/users"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/filebrowser/filebrowser/v2/users"
) )
func init() { func init() {
@@ -14,7 +15,7 @@ var usersAddCmd = &cobra.Command{
Use: "add <username> <password>", Use: "add <username> <password>",
Short: "Create a new user", Short: "Create a new user",
Long: `Create a new user and add it to the database.`, Long: `Create a new user and add it to the database.`,
Args: cobra.ExactArgs(2), Args: cobra.ExactArgs(2), //nolint:mnd
Run: python(func(cmd *cobra.Command, args []string, d pythonData) { Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
s, err := d.store.Settings.Get() s, err := d.store.Settings.Get()
checkErr(err) checkErr(err)
@@ -33,9 +34,9 @@ var usersAddCmd = &cobra.Command{
servSettings, err := d.store.Settings.GetServer() servSettings, err := d.store.Settings.GetServer()
checkErr(err) checkErr(err)
//since getUserDefaults() polluted s.Defaults.Scope // since getUserDefaults() polluted s.Defaults.Scope
//which makes the Scope not the one saved in the db // which makes the Scope not the one saved in the db
//we need the right s.Defaults.Scope here // we need the right s.Defaults.Scope here
s2, err := d.store.Settings.Get() s2, err := d.store.Settings.Get()
checkErr(err) checkErr(err)

View File

@@ -1,8 +1,9 @@
package cmd package cmd
import ( import (
"github.com/filebrowser/filebrowser/v2/users"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/filebrowser/filebrowser/v2/users"
) )
func init() { func init() {

View File

@@ -2,11 +2,13 @@ package cmd
import ( import (
"errors" "errors"
"fmt"
"os" "os"
"strconv" "strconv"
"github.com/filebrowser/filebrowser/v2/users"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/filebrowser/filebrowser/v2/users"
) )
func init() { func init() {
@@ -65,8 +67,7 @@ list or set it to 0.`,
// with the new username. If there is, print an error and cancel the // with the new username. If there is, print an error and cancel the
// operation // operation
if user.Username != onDB.Username { if user.Username != onDB.Username {
conflictuous, err := d.store.Users.Get("", user.Username) if conflictuous, err := d.store.Users.Get("", user.Username); err == nil { //nolint:shadow
if err == nil {
checkErr(usernameConflictError(user.Username, conflictuous.ID, user.ID)) checkErr(usernameConflictError(user.Username, conflictuous.ID, user.ID))
} }
} }
@@ -82,6 +83,7 @@ list or set it to 0.`,
}, pythonConfig{}), }, pythonConfig{}),
} }
func usernameConflictError(username string, original, new uint) error { func usernameConflictError(username string, originalID, newID uint) error {
return errors.New("can't import user with ID " + strconv.Itoa(int(new)) + " and username \"" + username + "\" because the username is already registred with the user " + strconv.Itoa(int(original))) return fmt.Errorf(`can't import user with ID %d and username "%s" because the username is already registred with the user %d`,
newID, username, originalID)
} }

View File

@@ -1,9 +1,10 @@
package cmd package cmd
import ( import (
"github.com/spf13/cobra"
"github.com/filebrowser/filebrowser/v2/settings" "github.com/filebrowser/filebrowser/v2/settings"
"github.com/filebrowser/filebrowser/v2/users" "github.com/filebrowser/filebrowser/v2/users"
"github.com/spf13/cobra"
) )
func init() { func init() {

View File

@@ -9,12 +9,13 @@ import (
"path/filepath" "path/filepath"
"github.com/asdine/storm" "github.com/asdine/storm"
"github.com/filebrowser/filebrowser/v2/settings"
"github.com/filebrowser/filebrowser/v2/storage"
"github.com/filebrowser/filebrowser/v2/storage/bolt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag" "github.com/spf13/pflag"
yaml "gopkg.in/yaml.v2" yaml "gopkg.in/yaml.v2"
"github.com/filebrowser/filebrowser/v2/settings"
"github.com/filebrowser/filebrowser/v2/storage"
"github.com/filebrowser/filebrowser/v2/storage/bolt"
) )
func checkErr(err error) { func checkErr(err error) {
@@ -70,7 +71,9 @@ 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) {
os.MkdirAll(d, 0700) if err := os.MkdirAll(d, 0700); err != nil { //nolint:shadow
return false, err
}
return false, nil return false, nil
} }
} }
@@ -113,7 +116,7 @@ func marshal(filename string, data interface{}) error {
encoder := json.NewEncoder(fd) encoder := json.NewEncoder(fd)
encoder.SetIndent("", " ") encoder.SetIndent("", " ")
return encoder.Encode(data) return encoder.Encode(data)
case ".yml", ".yaml": case ".yml", ".yaml": //nolint:goconst
encoder := yaml.NewEncoder(fd) encoder := yaml.NewEncoder(fd)
return encoder.Encode(data) return encoder.Encode(data)
default: default:

View File

@@ -3,8 +3,9 @@ package cmd
import ( import (
"fmt" "fmt"
"github.com/filebrowser/filebrowser/v2/version"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/filebrowser/filebrowser/v2/version"
) )
func init() { func init() {

View File

@@ -3,15 +3,17 @@ package errors
import "errors" import "errors"
var ( var (
ErrEmptyKey = errors.New("empty key") ErrEmptyKey = errors.New("empty key")
ErrExist = errors.New("the resource already exists") ErrExist = errors.New("the resource already exists")
ErrNotExist = errors.New("the resource does not exist") ErrNotExist = errors.New("the resource does not exist")
ErrEmptyPassword = errors.New("password is empty") ErrEmptyPassword = errors.New("password is empty")
ErrEmptyUsername = errors.New("username is empty") ErrEmptyUsername = errors.New("username is empty")
ErrEmptyRequest = errors.New("empty request") ErrEmptyRequest = errors.New("empty request")
ErrScopeIsRelative = errors.New("scope is a relative path") ErrScopeIsRelative = errors.New("scope is a relative path")
ErrInvalidDataType = errors.New("invalid data type") ErrInvalidDataType = errors.New("invalid data type")
ErrIsDirectory = errors.New("file is directory") ErrIsDirectory = errors.New("file is directory")
ErrInvalidOption = errors.New("invalid option") ErrInvalidOption = errors.New("invalid option")
ErrInvalidAuthMethod = errors.New("invalid auth method") ErrInvalidAuthMethod = errors.New("invalid auth method")
ErrPermissionDenied = errors.New("permission denied")
ErrInvalidRequestParams = errors.New("invalid request params")
) )

View File

@@ -1,8 +1,8 @@
package files package files
import ( import (
"crypto/md5" "crypto/md5" //nolint:gosec
"crypto/sha1" "crypto/sha1" //nolint:gosec
"crypto/sha256" "crypto/sha256"
"crypto/sha512" "crypto/sha512"
"encoding/hex" "encoding/hex"
@@ -17,9 +17,10 @@ import (
"strings" "strings"
"time" "time"
"github.com/spf13/afero"
"github.com/filebrowser/filebrowser/v2/errors" "github.com/filebrowser/filebrowser/v2/errors"
"github.com/filebrowser/filebrowser/v2/rules" "github.com/filebrowser/filebrowser/v2/rules"
"github.com/spf13/afero"
) )
// FileInfo describes a file. // FileInfo describes a file.
@@ -74,7 +75,10 @@ func NewFileInfo(opts FileOptions) (*FileInfo, error) {
if opts.Expand { if opts.Expand {
if file.IsDir { if file.IsDir {
return file, file.readListing(opts.Checker) if err := file.readListing(opts.Checker); err != nil { //nolint:shadow
return nil, err
}
return file, nil
} }
err = file.detectType(opts.Modify, true) err = file.detectType(opts.Modify, true)
@@ -105,6 +109,7 @@ func (i *FileInfo) Checksum(algo string) error {
var h hash.Hash var h hash.Hash
//nolint:gosec
switch algo { switch algo {
case "md5": case "md5":
h = md5.New() h = md5.New()
@@ -127,6 +132,8 @@ func (i *FileInfo) Checksum(algo string) error {
return nil return nil
} }
//nolint:goconst
//TODO: use constants
func (i *FileInfo) detectType(modify, saveContent bool) error { func (i *FileInfo) detectType(modify, saveContent bool) error {
// failing to detect the type should not return error. // failing to detect the type should not return error.
// imagine the situation where a file in a dir with thousands // imagine the situation where a file in a dir with thousands
@@ -198,9 +205,9 @@ func (i *FileInfo) detectSubtitles() {
// TODO: detect multiple languages. Base.Lang.vtt // TODO: detect multiple languages. Base.Lang.vtt
path := strings.TrimSuffix(i.Path, ext) + ".vtt" fPath := strings.TrimSuffix(i.Path, ext) + ".vtt"
if _, err := i.Fs.Stat(path); err == nil { if _, err := i.Fs.Stat(fPath); err == nil {
i.Subtitles = append(i.Subtitles, path) i.Subtitles = append(i.Subtitles, fPath)
} }
} }
@@ -219,16 +226,16 @@ func (i *FileInfo) readListing(checker rules.Checker) error {
for _, f := range dir { for _, f := range dir {
name := f.Name() name := f.Name()
path := path.Join(i.Path, name) fPath := path.Join(i.Path, name)
if !checker.Check(path) { if !checker.Check(fPath) {
continue continue
} }
if strings.HasPrefix(f.Mode().String(), "L") { if strings.HasPrefix(f.Mode().String(), "L") {
// 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 if the target's. // we stay with the link information instead if the target's.
info, err := i.Fs.Stat(path) info, err := i.Fs.Stat(fPath)
if err == nil { if err == nil {
f = info f = info
} }
@@ -242,7 +249,7 @@ func (i *FileInfo) readListing(checker rules.Checker) error {
Mode: f.Mode(), Mode: f.Mode(),
IsDir: f.IsDir(), IsDir: f.IsDir(),
Extension: filepath.Ext(name), Extension: filepath.Ext(name),
Path: path, Path: fPath,
} }
if file.IsDir { if file.IsDir {

View File

@@ -16,8 +16,10 @@ type Listing struct {
} }
// ApplySort applies the sort order using .Order and .Sort // ApplySort applies the sort order using .Order and .Sort
//nolint:goconst
func (l Listing) ApplySort() { func (l Listing) ApplySort() {
// Check '.Order' to know how to sort // Check '.Order' to know how to sort
// TODO: use enum
if !l.Sorting.Asc { if !l.Sorting.Asc {
switch l.Sorting.By { switch l.Sorting.By {
case "name": case "name":
@@ -62,11 +64,11 @@ func (l byName) Swap(i, j int) {
// Treat upper and lower case equally // Treat upper and lower case equally
func (l byName) Less(i, j int) bool { func (l byName) Less(i, j int) bool {
if l.Items[i].IsDir && !l.Items[j].IsDir { if l.Items[i].IsDir && !l.Items[j].IsDir {
return true return l.Sorting.Asc
} }
if !l.Items[i].IsDir && l.Items[j].IsDir { if !l.Items[i].IsDir && l.Items[j].IsDir {
return false return !l.Sorting.Asc
} }
return natural.Less(strings.ToLower(l.Items[j].Name), strings.ToLower(l.Items[i].Name)) return natural.Less(strings.ToLower(l.Items[j].Name), strings.ToLower(l.Items[i].Name))

View File

@@ -4,41 +4,45 @@ import (
"unicode/utf8" "unicode/utf8"
) )
func isBinary(content []byte, n int) bool { func isBinary(content []byte, _ int) bool {
maybeStr := string(content) maybeStr := string(content)
runeCnt := utf8.RuneCount(content) runeCnt := utf8.RuneCount(content)
runeIndex := 0 runeIndex := 0
gotRuneErrCnt := 0 gotRuneErrCnt := 0
firstRuneErrIndex := -1 firstRuneErrIndex := -1
for _, b := range maybeStr { const (
// 8 and below are control chars (e.g. backspace, null, eof, etc) // 8 and below are control chars (e.g. backspace, null, eof, etc)
if b <= 8 { maxControlCharsCode = 8
// 0xFFFD(65533) is the "error" Rune or "Unicode replacement character"
// see https://golang.org/pkg/unicode/utf8/#pkg-constants
unicodeReplacementChar = 0xFFFD
)
for _, b := range maybeStr {
if b <= maxControlCharsCode {
return true return true
} }
// 0xFFFD(65533) is the "error" Rune or "Unicode replacement character" if b == unicodeReplacementChar {
// see https://golang.org/pkg/unicode/utf8/#pkg-constants // if it is not the last (utf8.UTFMax - x) rune
if b == 0xFFFD {
//if it is not the last (utf8.UTFMax - x) rune
if runeCnt > utf8.UTFMax && runeIndex < runeCnt-utf8.UTFMax { if runeCnt > utf8.UTFMax && runeIndex < runeCnt-utf8.UTFMax {
return true return true
} else { }
//else it is the last (utf8.UTFMax - x) rune // else it is the last (utf8.UTFMax - x) rune
//there maybe Vxxx, VVxx, VVVx, thus, we may got max 3 0xFFFD rune (asume V is the byte we got) // there maybe Vxxx, VVxx, VVVx, thus, we may got max 3 0xFFFD rune (assume V is the byte we got)
//for Chinese, it can only be Vxx, VVx, we may got max 2 0xFFFD rune // for Chinese, it can only be Vxx, VVx, we may got max 2 0xFFFD rune
gotRuneErrCnt++ gotRuneErrCnt++
//mark the first time // mark the first time
if firstRuneErrIndex == -1 { if firstRuneErrIndex == -1 {
firstRuneErrIndex = runeIndex firstRuneErrIndex = runeIndex
}
} }
} }
runeIndex++ runeIndex++
} }
//if last (utf8.UTFMax - x ) rune has the "error" Rune, but not all // if last (utf8.UTFMax - x ) rune has the "error" Rune, but not all
if firstRuneErrIndex != -1 && gotRuneErrCnt != runeCnt-firstRuneErrIndex { if firstRuneErrIndex != -1 && gotRuneErrCnt != runeCnt-firstRuneErrIndex {
return true return true
} }

View File

@@ -9,7 +9,7 @@ import (
// CopyDir copies a directory from source to dest and all // CopyDir copies a directory from source to dest and all
// of its sub-directories. It doesn't stop if it finds an error // of its sub-directories. It doesn't stop if it finds an error
// during the copy. Returns an error if any. // during the copy. Returns an error if any.
func CopyDir(fs afero.Fs, source string, dest string) error { func CopyDir(fs afero.Fs, source, dest string) error {
// Get properties of source. // Get properties of source.
srcinfo, err := fs.Stat(source) srcinfo, err := fs.Stat(source)
if err != nil { if err != nil {

View File

@@ -9,7 +9,7 @@ import (
// CopyFile copies a file from source to dest and returns // CopyFile copies a file from source to dest and returns
// an error if any. // an error if any.
func CopyFile(fs afero.Fs, source string, dest string) error { func CopyFile(fs afero.Fs, source, dest string) error {
// Open the source file. // Open the source file.
src, err := fs.Open(source) src, err := fs.Open(source)
if err != nil { if err != nil {

File diff suppressed because it is too large Load Diff

View File

@@ -9,28 +9,29 @@
"lint": "vue-cli-service lint --fix" "lint": "vue-cli-service lint --fix"
}, },
"dependencies": { "dependencies": {
"ace-builds": "^1.4.6", "ace-builds": "^1.4.7",
"clipboard": "^2.0.4", "clipboard": "^2.0.4",
"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",
"material-design-icons": "^3.0.1", "material-design-icons": "^3.0.1",
"moment": "^2.24.0", "moment": "^2.24.0",
"normalize.css": "^8.0.1", "normalize.css": "^8.0.1",
"noty": "^3.2.0-beta", "noty": "^3.2.0-beta",
"qrcode.vue": "^1.6.3", "qrcode.vue": "^1.7.0",
"vue": "^2.6.10", "vue": "^2.6.10",
"vue-i18n": "^8.14.1", "vue-i18n": "^8.15.3",
"vue-router": "^3.1.3", "vue-router": "^3.1.3",
"vuex": "^3.1.1", "vuex": "^3.1.2",
"vuex-router-sync": "^5.0.0" "vuex-router-sync": "^5.0.0"
}, },
"devDependencies": { "devDependencies": {
"@vue/cli-plugin-babel": "^3.11.0", "@vue/cli-plugin-babel": "^4.1.2",
"@vue/cli-plugin-eslint": "^3.11.0", "@vue/cli-plugin-eslint": "^4.1.1",
"@vue/cli-service": "^3.12.0", "@vue/cli-service": "^4.1.2",
"babel-eslint": "^10.0.3", "babel-eslint": "^10.0.3",
"eslint": "^5.16.0", "eslint": "^6.7.2",
"eslint-plugin-vue": "^5.2.3", "eslint-plugin-vue": "^6.1.2",
"vue-template-compiler": "^2.6.10" "vue-template-compiler": "^2.6.10"
}, },
"eslintConfig": { "eslintConfig": {

View File

@@ -11,24 +11,53 @@
<title>[{[ if .Name -]}][{[ .Name ]}][{[ else ]}]File Browser[{[ end ]}]</title> <title>[{[ if .Name -]}][{[ .Name ]}][{[ else ]}]File Browser[{[ end ]}]</title>
<link rel="icon" type="image/png" sizes="32x32" href="/[{[ .StaticURL ]}]/img/icons/favicon-32x32.png"> <link rel="icon" type="image/png" sizes="32x32" href="[{[ .StaticURL ]}]/img/icons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/[{[ .StaticURL ]}]/img/icons/favicon-16x16.png"> <link rel="icon" type="image/png" sizes="16x16" href="[{[ .StaticURL ]}]/img/icons/favicon-16x16.png">
<!-- Add to home screen for Android and modern mobile browsers --> <!-- Add to home screen for Android and modern mobile browsers -->
<link rel="manifest" href="/[{[ .StaticURL ]}]/manifest.json"> <link rel="manifest" id="manifestPlaceholder" crossorigin="use-credentials">
<meta name="theme-color" content="#2979ff"> <meta name="theme-color" content="#2979ff">
<!-- Add to home screen for Safari on iOS --> <!-- Add to home screen for Safari on iOS -->
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black"> <meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="assets"> <meta name="apple-mobile-web-app-title" content="assets">
<link rel="apple-touch-icon" href="/[{[ .StaticURL ]}]/img/icons/apple-touch-icon-152x152.png"> <link rel="apple-touch-icon" href="[{[ .StaticURL ]}]/img/icons/apple-touch-icon-152x152.png">
<!-- Add to home screen for Windows --> <!-- Add to home screen for Windows -->
<meta name="msapplication-TileImage" content="/[{[ .StaticURL ]}]/img/icons/msapplication-icon-144x144.png"> <meta name="msapplication-TileImage" content="[{[ .StaticURL ]}]/img/icons/msapplication-icon-144x144.png">
<meta name="msapplication-TileColor" content="#2979ff"> <meta name="msapplication-TileColor" content="#2979ff">
<!-- Inject Some Variables --> <!-- Inject Some Variables and generate the manifest json -->
<script>window.FileBrowser = JSON.parse(`[{[ .Json ]}]`)</script> <script>
window.FileBrowser = JSON.parse(`[{[ .Json ]}]`);
var fullStaticURL = window.location.origin + window.FileBrowser.StaticURL;
var dynamicManifest = {
"name": window.FileBrowser.Name || 'File Browser',
"short_name": window.FileBrowser.Name || 'File Browser',
"icons": [
{
"src": fullStaticURL + "/img/icons/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": fullStaticURL + "/img/icons/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"start_url": window.location.origin + window.FileBrowser.BaseURL,
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#455a64"
}
const stringManifest = JSON.stringify(dynamicManifest);
const blob = new Blob([stringManifest], {type: 'application/json'});
const manifestURL = URL.createObjectURL(blob);
document.querySelector('#manifestPlaceholder').setAttribute('href', manifestURL);
</script>
<style> <style>
#loading { #loading {
@@ -104,8 +133,11 @@
</div> </div>
</div> </div>
[{[ if .Theme -]}]
<link rel="stylesheet" href="[{[ .StaticURL ]}]/themes/[{[ .Theme ]}].css" />
[{[ end ]}]
[{[ if .CSS -]}] [{[ if .CSS -]}]
<link rel="stylesheet" href="/[{[ .StaticURL ]}]/custom.css" /> <link rel="stylesheet" href="[{[ .StaticURL ]}]/custom.css" />
[{[ end ]}] [{[ end ]}]
</body> </body>
</html> </html>

View File

@@ -0,0 +1,140 @@
:root {
--background: #121212;
--surfacePrimary: #171819;
--surfaceSecondary: #212528;
--divider: rgba(255, 255, 255, 0.12);
--icon: #ffffff;
--textPrimary: rgba(255, 255, 255, 0.87);
--textSecondary: rgba(255, 255, 255, 0.6);
}
body {
background: var(--background);
color: var(--textPrimary);
}
#loading {
background: var(--background);
}
#loading .spinner div {
background: var(--icon);
}
#login {
background: var(--background);
}
header {
background: var(--surfacePrimary);
}
#search #input {
background: var(--surfaceSecondary);
}
#search.active #input,
#search.active .boxes {
background: var(--surfacePrimary);
}
#search.active input {
color: var(--textPrimary);
}
#search.active #result {
background: var(--background);
color: var(--textPrimary);
}
#search.active .boxes h3 {
color: var(--textPrimary);
}
.action {
color: var(--textPrimary) !important;
}
.action i {
color: var(--icon) !important;
}
.action .counter {
border-color: var(--surfacePrimary);
}
nav > div {
border-color: var(--divider);
}
#breadcrumbs {
border-color: var(--divider);
color: var(--textPrimary) !important;
}
#breadcrumbs span {
color: var(--textPrimary) !important;
}
#listing .item {
background: var(--surfacePrimary);
color: var(--textPrimary);
border-color: var(--divider) !important;
}
#listing .item i {
color: var(--icon);
}
#listing .item .modified {
color: var(--textSecondary);
}
#listing h2,
#listing.list .header span {
color: var(--textPrimary) !important;
}
#listing.list .header span {
color: var(--textPrimary);
}
#listing.list .header i {
color: var(--icon);
}
#listing.list .item.header {
background: var(--background);
}
.card {
background: var(--surfacePrimary);
color: var(--textPrimary);
}
.button--flat:hover {
background: var(--surfaceSecondary);
}
.card h3,
.dashboard #nav,
.dashboard p label {
color: var(--textPrimary);
}
.input {
background: var(--surfaceSecondary);
color: var(--textPrimary);
}
.dashboard #nav li,
.collapsible {
border-color: var(--divider);
}
.collapsible > label * {
color: var(--textPrimary);
}
.shell {
background: var(--surfacePrimary);
color: var(--textPrimary);
}
@media (max-width: 736px) {
#file-selection {
background: var(--surfaceSecondary) !important;
}
#file-selection span {
color: var(--textPrimary) !important;
}
nav {
background: var(--surfaceSecondary) !important;
}
#dropdown {
background: var(--surfaceSecondary) !important;
}
}

View File

@@ -47,7 +47,7 @@
<upload-button v-show="showUpload"></upload-button> <upload-button v-show="showUpload"></upload-button>
<info-button v-show="isFiles"></info-button> <info-button v-show="isFiles"></info-button>
<button v-show="isListing" @click="openSelect" :aria-label="$t('buttons.selectMultiple')" :title="$t('buttons.selectMultiple')" class="action"> <button v-show="isListing" @click="toggleMultipleSelection" :aria-label="$t('buttons.selectMultiple')" :title="$t('buttons.selectMultiple')" class="action" >
<i class="material-icons">check_circle</i> <i class="material-icons">check_circle</i>
<span>{{ $t('buttons.select') }}</span> <span>{{ $t('buttons.select') }}</span>
</button> </button>
@@ -177,8 +177,8 @@ export default {
openSearch () { openSearch () {
this.$store.commit('showHover', 'search') this.$store.commit('showHover', 'search')
}, },
openSelect () { toggleMultipleSelection () {
this.$store.commit('multiple', true) this.$store.commit('multiple', !this.multiple)
this.resetPrompts() this.resetPrompts()
}, },
resetPrompts () { resetPrompts () {

View File

@@ -24,7 +24,7 @@
<span>{{ $t('sidebar.settings') }}</span> <span>{{ $t('sidebar.settings') }}</span>
</router-link> </router-link>
<button v-if="!noAuth" @click="logout" class="action" id="logout" :aria-label="$t('sidebar.logout')" :title="$t('sidebar.logout')"> <button v-if="authMethod == 'json'" @click="logout" class="action" id="logout" :aria-label="$t('sidebar.logout')" :title="$t('sidebar.logout')">
<i class="material-icons">exit_to_app</i> <i class="material-icons">exit_to_app</i>
<span>{{ $t('sidebar.logout') }}</span> <span>{{ $t('sidebar.logout') }}</span>
</button> </button>
@@ -56,7 +56,7 @@
<script> <script>
import { mapState, mapGetters } from 'vuex' import { mapState, mapGetters } from 'vuex'
import * as auth from '@/utils/auth' import * as auth from '@/utils/auth'
import { version, signup, disableExternal, noAuth } from '@/utils/constants' import { version, signup, disableExternal, noAuth, authMethod } from '@/utils/constants'
export default { export default {
name: 'sidebar', name: 'sidebar',
@@ -69,7 +69,8 @@ export default {
signup: () => signup, signup: () => signup,
version: () => version, version: () => version,
disableExternal: () => disableExternal, disableExternal: () => disableExternal,
noAuth: () => noAuth noAuth: () => noAuth,
authMethod: () => authMethod
}, },
methods: { methods: {
help () { help () {

View File

@@ -10,7 +10,11 @@ export default {
name: 'upload-button', name: 'upload-button',
methods: { methods: {
upload: function () { upload: function () {
document.getElementById('upload-input').click() if (typeof(DataTransferItem.prototype.webkitGetAsEntry) !== 'undefined') {
this.$store.commit('showHover', 'upload')
} else {
document.getElementById('upload-input').click();
}
} }
} }
} }

View File

@@ -10,6 +10,7 @@ import buttons from '@/utils/buttons'
import ace from 'ace-builds/src-min-noconflict/ace.js' import ace from 'ace-builds/src-min-noconflict/ace.js'
import modelist from 'ace-builds/src-min-noconflict/ext-modelist.js' import modelist from 'ace-builds/src-min-noconflict/ext-modelist.js'
import 'ace-builds/webpack-resolver' import 'ace-builds/webpack-resolver'
import { theme } from '@/utils/constants'
export default { export default {
name: 'editor', name: 'editor',
@@ -17,10 +18,7 @@ export default {
...mapState(['req']) ...mapState(['req'])
}, },
data: function () { data: function () {
return { return {}
content: null,
editor: null
}
}, },
created () { created () {
window.addEventListener('keydown', this.keyEvent) window.addEventListener('keydown', this.keyEvent)
@@ -29,21 +27,25 @@ export default {
beforeDestroy () { beforeDestroy () {
window.removeEventListener('keydown', this.keyEvent) window.removeEventListener('keydown', this.keyEvent)
document.getElementById('save-button').removeEventListener('click', this.save) document.getElementById('save-button').removeEventListener('click', this.save)
this.editor.destroy();
}, },
mounted: function () { mounted: function () {
if (this.req.content === undefined || this.req.content === null) { const fileContent = this.req.content || '';
this.req.content = ''
}
this.editor = ace.edit('editor', { this.editor = ace.edit('editor', {
maxLines: Infinity, maxLines: 80,
minLines: 20, minLines: 20,
value: this.req.content, value: fileContent,
showPrintMargin: false, showPrintMargin: false,
readOnly: this.req.type === 'textImmutable', readOnly: this.req.type === 'textImmutable',
theme: 'ace/theme/chrome', theme: 'ace/theme/chrome',
mode: modelist.getModeForPath(this.req.name).mode mode: modelist.getModeForPath(this.req.name).mode,
wrap: true
}) })
if (theme == 'dark') {
this.editor.setTheme("ace/theme/twilight");
}
}, },
methods: { methods: {
keyEvent (event) { keyEvent (event) {

View File

@@ -5,6 +5,7 @@
<span>{{ $t('files.lonely') }}</span> <span>{{ $t('files.lonely') }}</span>
</h2> </h2>
<input style="display:none" type="file" id="upload-input" @change="uploadInput($event)" multiple> <input style="display:none" type="file" id="upload-input" @change="uploadInput($event)" multiple>
<input style="display:none" type="file" id="upload-folder-input" @change="uploadInput($event)" webkitdirectory multiple>
</div> </div>
<div v-else id="listing" <div v-else id="listing"
:class="user.viewMode" :class="user.viewMode"
@@ -75,6 +76,7 @@
</div> </div>
<input style="display:none" type="file" id="upload-input" @change="uploadInput($event)" multiple> <input style="display:none" type="file" id="upload-input" @change="uploadInput($event)" multiple>
<input style="display:none" type="file" id="upload-folder-input" @change="uploadInput($event)" webkitdirectory multiple>
<div :class="{ active: $store.state.multiple }" id="multiple-selection"> <div :class="{ active: $store.state.multiple }" id="multiple-selection">
<p>{{ $t('files.multipleSelectionEnabled') }}</p> <p>{{ $t('files.multipleSelectionEnabled') }}</p>
@@ -87,6 +89,7 @@
<script> <script>
import { mapState, mapMutations } from 'vuex' import { mapState, mapMutations } from 'vuex'
import throttle from 'lodash.throttle'
import Item from './ListingItem' import Item from './ListingItem'
import css from '@/utils/css' import css from '@/utils/css'
import { users, files as api } from '@/api' import { users, files as api } from '@/api'
@@ -98,7 +101,13 @@ export default {
components: { Item }, components: { Item },
data: function () { data: function () {
return { return {
show: 50 show: 50,
uploading: {
id: 0,
count: 0,
size: 0,
progress: []
}
} }
}, },
computed: { computed: {
@@ -181,7 +190,7 @@ export default {
document.removeEventListener('drop', this.drop) document.removeEventListener('drop', this.drop)
}, },
methods: { methods: {
...mapMutations([ 'updateUser' ]), ...mapMutations([ 'updateUser', 'addSelected' ]),
base64: function (name) { base64: function (name) {
return window.btoa(unescape(encodeURIComponent(name))) return window.btoa(unescape(encodeURIComponent(name)))
}, },
@@ -204,6 +213,19 @@ export default {
case 'v': case 'v':
this.paste(event) this.paste(event)
break break
case 'a':
event.preventDefault()
for (let file of this.items.files) {
if (this.$store.state.selected.indexOf(file.index) === -1) {
this.addSelected(file.index)
}
}
for (let dir of this.items.dirs) {
if (this.$store.state.selected.indexOf(dir.index) === -1) {
this.addSelected(dir.index)
}
}
break
} }
}, },
preventDefault (event) { preventDefault (event) {
@@ -290,10 +312,9 @@ export default {
this.resetOpacity() this.resetOpacity()
let dt = event.dataTransfer let dt = event.dataTransfer
let files = dt.files
let el = event.target let el = event.target
if (files.length <= 0) return if (dt.files.length <= 0) return
for (let i = 0; i < 5; i++) { for (let i = 0; i < 5; i++) {
if (el !== null && !el.classList.contains('item')) { if (el !== null && !el.classList.contains('item')) {
@@ -306,28 +327,45 @@ export default {
base = el.querySelector('.name').innerHTML + '/' base = el.querySelector('.name').innerHTML + '/'
} }
if (base !== '') { if (base === '') {
api.fetch(this.$route.path + base) this.scanFiles(dt).then((result) => {
.then(req => { this.checkConflict(result, this.req.items, base)
this.checkConflict(files, req.items, base) })
}) } else {
.catch(this.$showError) this.scanFiles(dt).then((result) => {
api.fetch(this.$route.path + base)
return .then(req => {
this.checkConflict(result, req.items, base)
})
.catch(this.$showError)
})
} }
this.checkConflict(files, this.req.items, base)
}, },
checkConflict (files, items, base) { checkConflict (files, items, base) {
if (typeof items === 'undefined' || items === null) { if (typeof items === 'undefined' || items === null) {
items = [] items = []
} }
let folder_upload = false
if (files[0].fullPath !== undefined) {
folder_upload = true
}
let conflict = false let conflict = false
for (let i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
let file = files[i]
let name = file.name
if (folder_upload) {
let dirs = file.fullPath.split("/")
if (dirs.length > 1) {
name = dirs[0]
}
}
let res = items.findIndex(function hasConflict (element) { let res = items.findIndex(function hasConflict (element) {
return (element.name === this) return (element.name === this)
}, files[i].name) }, name)
if (res >= 0) { if (res >= 0) {
conflict = true conflict = true
@@ -350,7 +388,19 @@ export default {
}) })
}, },
uploadInput (event) { uploadInput (event) {
this.checkConflict(event.currentTarget.files, this.req.items, '') this.$store.commit('closeHovers')
let files = event.currentTarget.files
let folder_upload = files[0].webkitRelativePath !== undefined && files[0].webkitRelativePath !== ''
if (folder_upload) {
for (let i = 0; i < files.length; i++) {
let file = files[i]
files[i].fullPath = file.webkitRelativePath
}
}
this.checkConflict(files, this.req.items, '')
}, },
resetOpacity () { resetOpacity () {
let items = document.getElementsByClassName('item') let items = document.getElementsByClassName('item')
@@ -359,37 +409,137 @@ export default {
file.style.opacity = 1 file.style.opacity = 1
}) })
}, },
scanFiles(dt) {
return new Promise((resolve) => {
let reading = 0
const contents = []
if (dt.items !== undefined) {
for (let item of dt.items) {
if (item.kind === "file" && typeof item.webkitGetAsEntry === "function") {
const entry = item.webkitGetAsEntry()
readEntry(entry)
}
}
} else {
resolve(dt.files)
}
function readEntry(entry, directory = "") {
if (entry.isFile) {
reading++
entry.file(file => {
reading--
file.fullPath = `${directory}${file.name}`
contents.push(file)
if (reading === 0) {
resolve(contents)
}
})
} else if (entry.isDirectory) {
const dir = {
isDir: true,
path: `${directory}${entry.name}`
}
contents.push(dir)
readReaderContent(entry.createReader(), `${directory}${entry.name}`)
}
}
function readReaderContent(reader, directory) {
reading++
reader.readEntries(function (entries) {
reading--
if (entries.length > 0) {
for (const entry of entries) {
readEntry(entry, `${directory}/`)
}
readReaderContent(reader, `${directory}/`)
}
if (reading === 0) {
resolve(contents)
}
})
}
})
},
setProgress: throttle(function() {
if (this.uploading.count == 0) {
return
}
let sum = this.uploading.progress.reduce((acc, val) => acc + val)
this.$store.commit('setProgress', Math.ceil(sum / this.uploading.size * 100))
}, 100, {leading: false, trailing: true}),
handleFiles (files, base, overwrite = false) { handleFiles (files, base, overwrite = false) {
buttons.loading('upload') if (this.uploading.count == 0) {
buttons.loading('upload')
}
let promises = [] let promises = []
let progress = new Array(files.length).fill(0)
let onupload = (id) => (event) => { let onupload = (id) => (event) => {
progress[id] = (event.loaded / event.total) * 100 this.uploading.progress[id] = event.loaded
this.setProgress()
let sum = 0
for (let i = 0; i < progress.length; i++) {
sum += progress[i]
}
this.$store.commit('setProgress', Math.ceil(sum / progress.length))
} }
for (let i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
let file = files[i] let file = files[i]
let filenameEncoded = url.encodeRFC5987ValueChars(file.name)
promises.push(api.post(this.$route.path + base + filenameEncoded, file, overwrite, onupload(i))) if (!file.isDir) {
let filename = (file.fullPath !== undefined) ? file.fullPath : file.name
let filenameEncoded = url.encodeRFC5987ValueChars(filename)
let id = this.uploading.id
this.uploading.size += file.size
this.uploading.id++
this.uploading.count++
let promise = api.post(this.$route.path + base + filenameEncoded, file, overwrite, throttle(onupload(id), 100)).finally(() => {
this.uploading.count--
})
promises.push(promise)
} else {
let uri = this.$route.path + base
let folders = file.path.split("/")
for (let i = 0; i < folders.length; i++) {
let folder = folders[i]
let folderEncoded = encodeURIComponent(folder)
uri += folderEncoded + "/"
}
api.post(uri)
}
} }
let finish = () => { let finish = () => {
if (this.uploading.count > 0) {
return
}
buttons.success('upload') buttons.success('upload')
this.$store.commit('setProgress', 0) this.$store.commit('setProgress', 0)
this.$store.commit('setReload', true)
this.uploading.id = 0
this.uploading.sizes = []
this.uploading.progress = []
} }
Promise.all(promises) Promise.all(promises)
.then(() => { .then(() => {
finish() finish()
this.$store.commit('setReload', true)
}) })
.catch(error => { .catch(error => {
finish() finish()

View File

@@ -2,7 +2,7 @@
<div class="item" <div class="item"
role="button" role="button"
tabindex="0" tabindex="0"
draggable="true" :draggable="isDraggable"
@dragstart="dragStart" @dragstart="dragStart"
@dragover="dragOver" @dragover="dragOver"
@drop="drop" @drop="drop"
@@ -44,7 +44,7 @@ export default {
}, },
props: ['name', 'isDir', 'url', 'type', 'size', 'modified', 'index'], props: ['name', 'isDir', 'url', 'type', 'size', 'modified', 'index'],
computed: { computed: {
...mapState(['selected', 'req']), ...mapState(['selected', 'req', 'user']),
...mapGetters(['selectedCount']), ...mapGetters(['selectedCount']),
isSelected () { isSelected () {
return (this.selected.indexOf(this.index) !== -1) return (this.selected.indexOf(this.index) !== -1)
@@ -56,6 +56,9 @@ export default {
if (this.type === 'video') return 'movie' if (this.type === 'video') return 'movie'
return 'insert_drive_file' return 'insert_drive_file'
}, },
isDraggable () {
return this.user.perm.rename
},
canDrop () { canDrop () {
if (!this.isDir) return false if (!this.isDir) return false
@@ -129,7 +132,7 @@ export default {
return return
} }
if (event.shiftKey && this.selected.length === 1) { if (event.shiftKey && this.selected.length > 0) {
let fi = 0 let fi = 0
let la = 0 let la = 0
@@ -142,7 +145,9 @@ export default {
} }
for (; fi <= la; fi++) { for (; fi <= la; fi++) {
this.addSelected(fi) if (this.$store.state.selected.indexOf(fi) == -1) {
this.addSelected(fi)
}
} }
return return

View File

@@ -7,7 +7,7 @@
<div class="card-content"> <div class="card-content">
<p v-if="selected.length > 1">{{ $t('prompts.filesSelected', { count: selected.length }) }}</p> <p v-if="selected.length > 1">{{ $t('prompts.filesSelected', { count: selected.length }) }}</p>
<p v-if="selected.length < 2"><strong>{{ $t('prompts.displayName') }}</strong> {{ name }}</p> <p class="break-word" v-if="selected.length < 2"><strong>{{ $t('prompts.displayName') }}</strong> {{ name }}</p>
<p v-if="!dir || selected.length > 1"><strong>{{ $t('prompts.size') }}:</strong> <span id="content_length"></span> {{ humanSize }}</p> <p v-if="!dir || selected.length > 1"><strong>{{ $t('prompts.size') }}:</strong> <span id="content_length"></span> {{ humanSize }}</p>
<p v-if="selected.length < 2"><strong>{{ $t('prompts.lastModified') }}:</strong> {{ humanTime }}</p> <p v-if="selected.length < 2"><strong>{{ $t('prompts.lastModified') }}:</strong> {{ humanTime }}</p>
@@ -88,6 +88,7 @@ export default {
try { try {
const hash = await api.checksum(link, algo) const hash = await api.checksum(link, algo)
// eslint-disable-next-line
event.target.innerHTML = hash event.target.innerHTML = hash
} catch (e) { } catch (e) {
this.$showError(e) this.$showError(e)

View File

@@ -1,16 +1,6 @@
<template> <template>
<div> <div>
<help v-if="showHelp" ></help> <component ref="currentComponent" :is="currentComponent"></component>
<download v-else-if="showDownload"></download>
<new-file v-else-if="showNewFile"></new-file>
<new-dir v-else-if="showNewDir"></new-dir>
<rename v-else-if="showRename"></rename>
<delete v-else-if="showDelete"></delete>
<info v-else-if="showInfo"></info>
<move v-else-if="showMove"></move>
<copy v-else-if="showCopy"></copy>
<replace v-else-if="showReplace"></replace>
<share v-else-if="show === 'share'"></share>
<div v-show="showOverlay" @click="resetPrompts" class="overlay"></div> <div v-show="showOverlay" @click="resetPrompts" class="overlay"></div>
</div> </div>
</template> </template>
@@ -27,6 +17,7 @@ import NewFile from './NewFile'
import NewDir from './NewDir' import NewDir from './NewDir'
import Replace from './Replace' import Replace from './Replace'
import Share from './Share' import Share from './Share'
import Upload from './Upload'
import { mapState } from 'vuex' import { mapState } from 'vuex'
import buttons from '@/utils/buttons' import buttons from '@/utils/buttons'
@@ -43,7 +34,8 @@ export default {
NewFile, NewFile,
NewDir, NewDir,
Help, Help,
Replace Replace,
Upload
}, },
data: function () { data: function () {
return { return {
@@ -54,18 +46,53 @@ export default {
} }
} }
}, },
created () {
window.addEventListener('keydown', (event) => {
if (this.show == null)
return
let prompt = this.$refs.currentComponent;
// Enter
if (event.keyCode == 13) {
switch (this.show) {
case 'delete':
prompt.submit()
break;
case 'copy':
prompt.copy(event)
break;
case 'move':
prompt.move(event)
break;
case 'replace':
prompt.showConfirm(event)
break;
}
}
})
},
computed: { computed: {
...mapState(['show', 'plugins']), ...mapState(['show', 'plugins']),
showInfo: function () { return this.show === 'info' }, currentComponent: function () {
showHelp: function () { return this.show === 'help' }, const matched = [
showDelete: function () { return this.show === 'delete' }, 'info',
showRename: function () { return this.show === 'rename' }, 'help',
showMove: function () { return this.show === 'move' }, 'delete',
showCopy: function () { return this.show === 'copy' }, 'rename',
showNewFile: function () { return this.show === 'newFile' }, 'move',
showNewDir: function () { return this.show === 'newDir' }, 'copy',
showDownload: function () { return this.show === 'download' }, 'newFile',
showReplace: function () { return this.show === 'replace' }, 'newDir',
'download',
'replace',
'share',
'upload'
].indexOf(this.show) >= 0;
return matched && this.show || null;
},
showOverlay: function () { showOverlay: function () {
return (this.show !== null && this.show !== 'search' && this.show !== 'more') return (this.show !== null && this.show !== 'search' && this.show !== 'more')
} }

View File

@@ -0,0 +1,37 @@
<template>
<div class="card floating">
<div class="card-title">
<h2>{{ $t('prompts.upload') }}</h2>
</div>
<div class="card-content">
<p>{{ $t('prompts.uploadMessage') }}</p>
</div>
<div class="card-action full">
<div @click="uploadFile" class="action">
<i class="material-icons">insert_drive_file</i>
<div class="title">File</div>
</div>
<div @click="uploadFolder" class="action">
<i class="material-icons">folder</i>
<div class="title">Folder</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'upload',
methods: {
uploadFile: function () {
document.getElementById('upload-input').click()
},
uploadFolder: function () {
document.getElementById('upload-folder-input').click()
}
}
}
</script>

View File

@@ -1,30 +1,42 @@
<template> <template>
<select v-on:change="change" :value="locale"> <select v-on:change="change" :value="locale">
<option value="ar">{{ $t('languages.ar') }}</option> <option v-for="(language, value) in locales" :key="value" :value="value">{{ $t('languages.' + language) }}</option>
<option value="de">{{ $t('languages.de') }}</option>
<option value="en">{{ $t('languages.en') }}</option>
<option value="es">{{ $t('languages.es') }}</option>
<option value="fr">{{ $t('languages.fr') }}</option>
<option value="is">{{ $t('languages.is') }}</option>
<option value="it">{{ $t('languages.it') }}</option>
<option value="ja">{{ $t('languages.ja') }}</option>
<option value="ko">{{ $t('languages.ko') }}</option>
<option value="nl-be">{{ $t('languages.nlBE') }}</option>
<option value="pl">{{ $t('languages.pl') }}</option>
<option value="pt-br">{{ $t('languages.ptBR') }}</option>
<option value="pt">{{ $t('languages.pt') }}</option>
<option value="ro">{{ $t('languages.ro') }}</option>
<option value="ru">{{ $t('languages.ru') }}</option>
<option value="sv-se">{{ $t('languages.svSE') }}</option>
<option value="zh-cn">{{ $t('languages.zhCN') }}</option>
<option value="zh-tw">{{ $t('languages.zhTW') }}</option>
</select> </select>
</template> </template>
<script> <script>
export default { export default {
name: 'languages', name: 'languages',
props: [ 'locale' ], props: [ 'locale' ],
data() {
let dataObj = {
locales: {
ar: 'ar',
de: 'de',
en: 'en',
es: 'es',
fr: 'fr',
is: 'is',
it: 'it',
ja: 'ja',
ko: 'ko',
'nl-be': 'nlBE',
pl: 'pl',
'pt-br': 'ptBR',
pt: 'pt',
ro: 'ro',
ru: 'ru',
'sv-se': 'svSE',
'zh-cn': 'zhCN',
'zh-tw': 'zhTW'
}
};
Object.defineProperty(dataObj, "locales", { configurable: false, writable: false });
return dataObj;
},
methods: { methods: {
change (event) { change (event) {
this.$emit('update:locale', event.target.value) this.$emit('update:locale', event.target.value)

View File

@@ -0,0 +1,18 @@
<template>
<select v-on:change="change" :value="theme">
<option value="">{{ $t('settings.themes.light') }}</option>
<option value="dark">{{ $t('settings.themes.dark') }}</option>
</select>
</template>
<script>
export default {
name: 'themes',
props: [ 'theme' ],
methods: {
change (event) {
this.$emit('update:theme', event.target.value)
}
}
}
</script>

View File

@@ -124,3 +124,7 @@ main {
width: 0; width: 0;
transition: .2s ease width; transition: .2s ease width;
} }
.break-word {
word-break: break-all;
}

View File

@@ -96,6 +96,7 @@ table tr>*:last-child {
background-color: #fff; background-color: #fff;
border-radius: 2px; border-radius: 2px;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2); box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2);
overflow: auto;
} }
.card.floating { .card.floating {
@@ -366,3 +367,33 @@ table tr>*:last-child {
.card .collapsible .collapse { .card .collapsible .collapse {
padding: 0 1em; padding: 0 1em;
} }
.card .card-action.full {
padding-top: 0;
display: flex;
flex-wrap: wrap;
}
.card .card-action.full .action {
flex: 1;
padding: 2em;
border-radius: 0.2em;
border: 1px solid rgba(0, 0, 0, 0.1);
text-align: center;
}
.card .card-action.full .action {
margin: 0 0.25em 0.50em;
}
.card .card-action.full .action i {
display: block;
padding: 0;
margin-bottom: 0.25em;
font-size: 4em;
}
.card .card-action.full .action .title {
font-size: 1.5em;
font-weight: 500;
}

View File

@@ -12,10 +12,8 @@
#listing>div { #listing>div {
display: flex; display: flex;
padding: 0;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: flex-start; justify-content: flex-start;
position: relative;
} }
#listing .item { #listing .item {
@@ -207,16 +205,6 @@
font-weight: bold; font-weight: bold;
} }
@keyframes slidein {
from {
bottom: -4em;
}
to {
bottom: 0;
}
}
#listing #multiple-selection { #listing #multiple-selection {
position: fixed; position: fixed;
bottom: -4em; bottom: -4em;
@@ -225,16 +213,13 @@
width: 100%; width: 100%;
background-color: var(--blue); background-color: var(--blue);
height: 4em; height: 4em;
display: none;
padding: 0.5em 0.5em 0.5em 1em; padding: 0.5em 0.5em 0.5em 1em;
justify-content: space-between; justify-content: space-between;
align-items: center;
transition: .2s ease bottom; transition: .2s ease bottom;
} }
#listing #multiple-selection.active { #listing #multiple-selection.active {
animation: slidein 0.2s forwards; bottom: 0;
display: flex;
} }
#listing #multiple-selection p, #listing #multiple-selection p,

View File

@@ -116,9 +116,16 @@
"size": "Size", "size": "Size",
"schedule": "Schedule", "schedule": "Schedule",
"scheduleMessage": "Pick a date and time to schedule the publication of this post.", "scheduleMessage": "Pick a date and time to schedule the publication of this post.",
"newArchetype": "Create a new post based on an archetype. Your file will be created on content folder." "newArchetype": "Create a new post based on an archetype. Your file will be created on content folder.",
"upload": "Upload",
"uploadMessage": "Select an option to upload."
}, },
"settings": { "settings": {
"themes": {
"title": "Theme",
"light": "Light",
"dark": "Dark"
},
"instanceName": "Instance name", "instanceName": "Instance name",
"brandingDirectoryPath": "Branding directory path", "brandingDirectoryPath": "Branding directory path",
"documentation": "documentation", "documentation": "documentation",

View File

@@ -12,10 +12,6 @@ export function parseToken (token) {
const data = JSON.parse(Base64.decode(parts[1])) const data = JSON.parse(Base64.decode(parts[1]))
if (Math.round(new Date().getTime() / 1000) > data.exp) {
throw new Error('token expired')
}
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)

View File

@@ -6,9 +6,11 @@ const recaptcha = window.FileBrowser.ReCaptcha
const recaptchaKey = window.FileBrowser.ReCaptchaKey const recaptchaKey = window.FileBrowser.ReCaptchaKey
const signup = window.FileBrowser.Signup const signup = window.FileBrowser.Signup
const version = window.FileBrowser.Version const version = window.FileBrowser.Version
const logoURL = `/${staticURL}/img/logo.svg` const logoURL = `${staticURL}/img/logo.svg`
const noAuth = window.FileBrowser.NoAuth const noAuth = window.FileBrowser.NoAuth
const authMethod = window.FileBrowser.AuthMethod
const loginPage = window.FileBrowser.LoginPage const loginPage = window.FileBrowser.LoginPage
const theme = window.FileBrowser.Theme
export { export {
name, name,
@@ -20,5 +22,7 @@ export {
signup, signup,
version, version,
noAuth, noAuth,
loginPage authMethod,
loginPage,
theme
} }

View File

@@ -32,7 +32,6 @@ import NotFound from './errors/404'
import InternalError from './errors/500' import InternalError from './errors/500'
import Preview from '@/components/files/Preview' import Preview from '@/components/files/Preview'
import Listing from '@/components/files/Listing' import Listing from '@/components/files/Listing'
import Editor from '@/components/files/Editor'
import { files as api } from '@/api' import { files as api } from '@/api'
import { mapGetters, mapState, mapMutations } from 'vuex' import { mapGetters, mapState, mapMutations } from 'vuex'
@@ -48,7 +47,7 @@ export default {
InternalError, InternalError,
Preview, Preview,
Listing, Listing,
Editor Editor: () => import('@/components/files/Editor')
}, },
computed: { computed: {
...mapGetters([ ...mapGetters([
@@ -176,7 +175,8 @@ export default {
!this.isFiles || !this.isFiles ||
this.loading || this.loading ||
!this.user.perm.delete || !this.user.perm.delete ||
(this.isListing && this.selectedCount === 0)) return (this.isListing && this.selectedCount === 0) ||
this.$store.state.show != null) return
this.$store.commit('showHover', 'delete') this.$store.commit('showHover', 'delete')
} }

View File

@@ -29,6 +29,11 @@
{{ $t('settings.disableExternalLinks') }} {{ $t('settings.disableExternalLinks') }}
</p> </p>
<p>
<label for="theme">{{ $t('settings.themes.title') }}</label>
<themes class="input input--block" :theme.sync="settings.branding.theme" id="theme"></themes>
</p>
<p> <p>
<label for="branding-name">{{ $t('settings.instanceName') }}</label> <label for="branding-name">{{ $t('settings.instanceName') }}</label>
<input class="input input--block" type="text" v-model="settings.branding.name" id="branding-name" /> <input class="input input--block" type="text" v-model="settings.branding.name" id="branding-name" />
@@ -98,10 +103,12 @@ import { mapState } from 'vuex'
import { settings as api } from '@/api' import { settings as api } from '@/api'
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'
export default { export default {
name: 'settings', name: 'settings',
components: { components: {
Themes,
UserForm, UserForm,
Rules Rules
}, },

15
go.mod
View File

@@ -13,25 +13,28 @@ require (
github.com/gorilla/mux v1.7.3 github.com/gorilla/mux v1.7.3
github.com/gorilla/websocket v1.4.1 github.com/gorilla/websocket v1.4.1
github.com/hacdias/fileutils v0.0.0-20181202104838-227b317161a1 github.com/hacdias/fileutils v0.0.0-20181202104838-227b317161a1
github.com/magiconair/properties v1.8.1 // indirect
github.com/maruel/natural v0.0.0-20180416170133-dbcb3e2e8cf1 github.com/maruel/natural v0.0.0-20180416170133-dbcb3e2e8cf1
github.com/mholt/archiver v3.1.1+incompatible github.com/mholt/archiver v3.1.1+incompatible
github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/go-homedir v1.1.0
github.com/nwaples/rardecode v1.0.0 // indirect github.com/nwaples/rardecode v1.0.0 // indirect
github.com/pelletier/go-toml v1.5.0 github.com/pelletier/go-toml v1.6.0
github.com/pierrec/lz4 v0.0.0-20190131084431-473cd7ce01a1 // indirect github.com/pierrec/lz4 v0.0.0-20190131084431-473cd7ce01a1 // indirect
github.com/spf13/afero v1.2.2 github.com/spf13/afero v1.2.2
github.com/spf13/cobra v0.0.5 github.com/spf13/cobra v0.0.5
github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.4.0 github.com/spf13/viper v1.6.1
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
go.etcd.io/bbolt v1.3.3 go.etcd.io/bbolt v1.3.3
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529 golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37
golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862 // indirect golang.org/x/net v0.0.0-20200528225125-3c3fba18258b // indirect
golang.org/x/sys v0.0.0-20200523222454-059865788121 // indirect
golang.org/x/text v0.3.2 // indirect golang.org/x/text v0.3.2 // indirect
google.golang.org/appengine v1.5.0 // indirect google.golang.org/appengine v1.5.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 gopkg.in/natefinch/lumberjack.v2 v2.0.0
gopkg.in/yaml.v2 v2.2.4 gopkg.in/yaml.v2 v2.2.7
) )
go 1.14

36
go.sum
View File

@@ -74,6 +74,8 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
@@ -97,6 +99,8 @@ github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGAR
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jimstudt/http-authentication v0.0.0-20140401203705-3eca13d6893a/go.mod h1:wK6yTYYcgjHE1Z1QtXACPDjcFJyBskHEdagmnq3vsP8= github.com/jimstudt/http-authentication v0.0.0-20140401203705-3eca13d6893a/go.mod h1:wK6yTYYcgjHE1Z1QtXACPDjcFJyBskHEdagmnq3vsP8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
@@ -147,8 +151,8 @@ github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.5.0 h1:5BakdOZdtKJ1FFk6QdL8iSGrMWsXgchNJcrnarjbmJQ= github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4=
github.com/pelletier/go-toml v1.5.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys= github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
github.com/pierrec/lz4 v0.0.0-20190131084431-473cd7ce01a1 h1:0utzB5Mn6QyMzIeOn+oD7pjKQLjJwfM9bz6TkPPdxcw= github.com/pierrec/lz4 v0.0.0-20190131084431-473cd7ce01a1 h1:0utzB5Mn6QyMzIeOn+oD7pjKQLjJwfM9bz6TkPPdxcw=
github.com/pierrec/lz4 v0.0.0-20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v0.0.0-20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -168,6 +172,10 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So
github.com/russross/blackfriday v0.0.0-20170610170232-067529f716f4/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday v0.0.0-20170610170232-067529f716f4/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
@@ -188,15 +196,19 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M= github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU= github.com/spf13/viper v1.6.1 h1:VPZzIkznI1YhVMRi6vNFLHSwhnhReBfgTxIPccpfdZk=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.6.1/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce h1:fb190+cK2Xz/dvi9Hv8eCYJYvIGUTN2/KLq1pT6CjEc=
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ulikunitz/xz v0.5.6 h1:jGHAfXawEGZQ3blwU5wnWKQJvAraT7Ftq9EXjnXYgt8= github.com/ulikunitz/xz v0.5.6 h1:jGHAfXawEGZQ3blwU5wnWKQJvAraT7Ftq9EXjnXYgt8=
@@ -225,8 +237,8 @@ golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25 h1:jsG6UpNLt9iAsb0S2AGW28DveNzzgmbXR+ENoPjUeIU= golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25 h1:jsG6UpNLt9iAsb0S2AGW28DveNzzgmbXR+ENoPjUeIU=
golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529 h1:iMGN4xG0cnqj3t+zOM8wUB0BiPKHEwSxEZCvzcbZuvk= golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225 h1:kNX+jCowfMYzvlSvJu5pQWEmyWFrBXJ3PBy10xKMXK8= golang.org/x/net v0.0.0-20180724234803-3673e40ba225 h1:kNX+jCowfMYzvlSvJu5pQWEmyWFrBXJ3PBy10xKMXK8=
@@ -242,6 +254,8 @@ golang.org/x/net v0.0.0-20190328230028-74de082e2cca/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco= golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20200528225125-3c3fba18258b h1:IYiJPiJfzktmDAO1HQiwjMjwjlYKHAL7KzeD544RJPs=
golang.org/x/net v0.0.0-20200528225125-3c3fba18258b/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -259,8 +273,9 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e h1:ZytStCyV048ZqDsWHiYDdoI2Vd4msMcrDECFxS+tL9c= golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e h1:ZytStCyV048ZqDsWHiYDdoI2Vd4msMcrDECFxS+tL9c=
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862 h1:rM0ROo5vb9AdYJi1110yjWGMej9ITfKddS89P3Fkhug= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121 h1:rITEj+UZHYC927n8GT97eC3zrpzXdb/voyeOuVKS46o=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
@@ -270,6 +285,7 @@ golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -281,6 +297,8 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/mcuadros/go-syslog.v2 v2.2.1/go.mod h1:l5LPIyOOyIdQquNg+oU6Z3524YwrcqEm0aKH+5zpt2U= gopkg.in/mcuadros/go-syslog.v2 v2.2.1/go.mod h1:l5LPIyOOyIdQquNg+oU6Z3524YwrcqEm0aKH+5zpt2U=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
@@ -294,4 +312,6 @@ gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@@ -10,10 +10,15 @@ import (
jwt "github.com/dgrijalva/jwt-go" jwt "github.com/dgrijalva/jwt-go"
"github.com/dgrijalva/jwt-go/request" "github.com/dgrijalva/jwt-go/request"
"github.com/filebrowser/filebrowser/v2/errors" "github.com/filebrowser/filebrowser/v2/errors"
"github.com/filebrowser/filebrowser/v2/users" "github.com/filebrowser/filebrowser/v2/users"
) )
const (
TokenExpirationTime = time.Hour * 2
)
type userInfo struct { type userInfo struct {
ID uint `json:"id"` ID uint `json:"id"`
Locale string `json:"locale"` Locale string `json:"locale"`
@@ -161,7 +166,7 @@ var renewHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data
return printToken(w, r, d, d.user) return printToken(w, r, d, d.user)
}) })
func printToken(w http.ResponseWriter, r *http.Request, d *data, user *users.User) (int, error) { func printToken(w http.ResponseWriter, _ *http.Request, d *data, user *users.User) (int, error) {
claims := &authToken{ claims := &authToken{
User: userInfo{ User: userInfo{
ID: user.ID, ID: user.ID,
@@ -173,7 +178,7 @@ func printToken(w http.ResponseWriter, r *http.Request, d *data, user *users.Use
}, },
StandardClaims: jwt.StandardClaims{ StandardClaims: jwt.StandardClaims{
IssuedAt: time.Now().Unix(), IssuedAt: time.Now().Unix(),
ExpiresAt: time.Now().Add(time.Hour * 2).Unix(), ExpiresAt: time.Now().Add(TokenExpirationTime).Unix(),
Issuer: "File Browser", Issuer: "File Browser",
}, },
} }
@@ -185,6 +190,8 @@ func printToken(w http.ResponseWriter, r *http.Request, d *data, user *users.Use
} }
w.Header().Set("Content-Type", "cty") w.Header().Set("Content-Type", "cty")
w.Write([]byte(signed)) if _, err := w.Write([]byte(signed)); err != nil {
return http.StatusInternalServerError, err
}
return 0, nil return 0, nil
} }

View File

@@ -9,8 +9,13 @@ import (
"strings" "strings"
"time" "time"
"github.com/filebrowser/filebrowser/v2/runner"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/filebrowser/filebrowser/v2/runner"
)
const (
WSWriteDeadline = 10 * time.Second
) )
var upgrader = websocket.Upgrader{ var upgrader = websocket.Upgrader{
@@ -22,12 +27,14 @@ var (
cmdNotAllowed = []byte("Command not allowed.") cmdNotAllowed = []byte("Command not allowed.")
) )
func wsErr(ws *websocket.Conn, r *http.Request, status int, err error) { func wsErr(ws *websocket.Conn, r *http.Request, status int, err error) { //nolint:unparam
txt := http.StatusText(status) txt := http.StatusText(status)
if err != nil || status >= 400 { if err != nil || status >= 400 {
log.Printf("%s: %v %s %v", r.URL.Path, status, r.RemoteAddr, err) log.Printf("%s: %v %s %v", r.URL.Path, status, r.RemoteAddr, err)
} }
ws.WriteControl(websocket.CloseInternalServerErr, []byte(txt), time.Now().Add(10*time.Second)) if err := ws.WriteControl(websocket.CloseInternalServerErr, []byte(txt), time.Now().Add(WSWriteDeadline)); err != nil { //nolint:shadow
log.Print(err)
}
} }
var commandsHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { var commandsHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
@@ -40,7 +47,7 @@ var commandsHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *d
var raw string var raw string
for { for {
_, msg, err := conn.ReadMessage() _, msg, err := conn.ReadMessage() //nolint:shadow
if err != nil { if err != nil {
wsErr(conn, r, http.StatusInternalServerError, err) wsErr(conn, r, http.StatusInternalServerError, err)
return 0, nil return 0, nil
@@ -53,8 +60,7 @@ var commandsHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *d
} }
if !d.user.CanExecute(strings.Split(raw, " ")[0]) { if !d.user.CanExecute(strings.Split(raw, " ")[0]) {
err := conn.WriteMessage(websocket.TextMessage, cmdNotAllowed) if err := conn.WriteMessage(websocket.TextMessage, cmdNotAllowed); err != nil { //nolint:shadow
if err != nil {
wsErr(conn, r, http.StatusInternalServerError, err) wsErr(conn, r, http.StatusInternalServerError, err)
} }
@@ -63,15 +69,13 @@ var commandsHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *d
command, err := runner.ParseCommand(d.settings, raw) command, err := runner.ParseCommand(d.settings, raw)
if err != nil { if err != nil {
err := conn.WriteMessage(websocket.TextMessage, []byte(err.Error())) if err := conn.WriteMessage(websocket.TextMessage, []byte(err.Error())); err != nil { //nolint:shadow
if err != nil {
wsErr(conn, r, http.StatusInternalServerError, err) wsErr(conn, r, http.StatusInternalServerError, err)
} }
return 0, nil return 0, nil
} }
cmd := exec.Command(command[0], command[1:]...) cmd := exec.Command(command[0], command[1:]...) //nolint:gosec
cmd.Dir = d.user.FullPath(r.URL.Path) cmd.Dir = d.user.FullPath(r.URL.Path)
stdout, err := cmd.StdoutPipe() stdout, err := cmd.StdoutPipe()
@@ -93,7 +97,9 @@ var commandsHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *d
s := bufio.NewScanner(io.MultiReader(stdout, stderr)) s := bufio.NewScanner(io.MultiReader(stdout, stderr))
for s.Scan() { for s.Scan() {
conn.WriteMessage(websocket.TextMessage, s.Bytes()) if err := conn.WriteMessage(websocket.TextMessage, s.Bytes()); err != nil {
log.Print(err)
}
} }
if err := cmd.Wait(); err != nil { if err := cmd.Wait(); err != nil {

View File

@@ -5,6 +5,8 @@ import (
"net/http" "net/http"
"strconv" "strconv"
"github.com/tomasen/realip"
"github.com/filebrowser/filebrowser/v2/runner" "github.com/filebrowser/filebrowser/v2/runner"
"github.com/filebrowser/filebrowser/v2/settings" "github.com/filebrowser/filebrowser/v2/settings"
"github.com/filebrowser/filebrowser/v2/storage" "github.com/filebrowser/filebrowser/v2/storage"
@@ -24,24 +26,25 @@ type data struct {
// Check implements rules.Checker. // Check implements rules.Checker.
func (d *data) Check(path string) bool { func (d *data) Check(path string) bool {
for _, rule := range d.user.Rules { allow := true
if rule.Matches(path) {
return rule.Allow
}
}
for _, rule := range d.settings.Rules { for _, rule := range d.settings.Rules {
if rule.Matches(path) { if rule.Matches(path) {
return rule.Allow allow = rule.Allow
} }
} }
return true for _, rule := range d.user.Rules {
if rule.Matches(path) {
allow = rule.Allow
}
}
return allow
} }
func handle(fn handleFunc, prefix string, storage *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) {
settings, err := storage.Settings.Get() settings, err := store.Settings.Get()
if err != nil { if err != nil {
log.Fatalln("ERROR: couldn't get settings") log.Fatalln("ERROR: couldn't get settings")
return return
@@ -49,7 +52,7 @@ func handle(fn handleFunc, prefix string, storage *storage.Storage, server *sett
status, err := fn(w, r, &data{ status, err := fn(w, r, &data{
Runner: &runner.Runner{Settings: settings}, Runner: &runner.Runner{Settings: settings},
store: storage, store: store,
settings: settings, settings: settings,
server: server, server: server,
}) })
@@ -60,7 +63,8 @@ func handle(fn handleFunc, prefix string, storage *storage.Storage, server *sett
} }
if status >= 400 || err != nil { if status >= 400 || err != nil {
log.Printf("%s: %v %s %v", r.URL.Path, status, r.RemoteAddr, err) clientIP := realip.FromRequest(r)
log.Printf("%s: %v %s %v", r.URL.Path, status, clientIP, err)
} }
}) })

View File

@@ -3,9 +3,10 @@ package http
import ( import (
"net/http" "net/http"
"github.com/gorilla/mux"
"github.com/filebrowser/filebrowser/v2/settings" "github.com/filebrowser/filebrowser/v2/settings"
"github.com/filebrowser/filebrowser/v2/storage" "github.com/filebrowser/filebrowser/v2/storage"
"github.com/gorilla/mux"
) )
type modifyRequest struct { type modifyRequest struct {
@@ -13,11 +14,11 @@ type modifyRequest struct {
Which []string `json:"which"` // Answer to: which fields? Which []string `json:"which"` // Answer to: which fields?
} }
func NewHandler(storage *storage.Storage, server *settings.Server) (http.Handler, error) { func NewHandler(store *storage.Storage, server *settings.Server) (http.Handler, error) {
server.Clean() server.Clean()
r := mux.NewRouter() r := mux.NewRouter()
index, static := getStaticHandlers(storage, server) index, static := getStaticHandlers(store, server)
// 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
// trailing slash in the end. I hate this decision since this allows some awful // trailing slash in the end. I hate this decision since this allows some awful
@@ -25,7 +26,7 @@ func NewHandler(storage *storage.Storage, server *settings.Server) (http.Handler
r = r.SkipClean(true) r = r.SkipClean(true)
monkey := func(fn handleFunc, prefix string) http.Handler { monkey := func(fn handleFunc, prefix string) http.Handler {
return handle(fn, prefix, storage, server) return handle(fn, prefix, store, server)
} }
r.PathPrefix("/static").Handler(static) r.PathPrefix("/static").Handler(static)

View File

@@ -46,7 +46,7 @@ func ifPathWithName(r *http.Request) string {
pathElements := strings.Split(r.URL.Path, "/") pathElements := strings.Split(r.URL.Path, "/")
// prevent maliciously constructed parameters like `/api/public/dl/XZzCDnK2_not_exists_hash_name` // prevent maliciously constructed parameters like `/api/public/dl/XZzCDnK2_not_exists_hash_name`
// len(pathElements) will be 1, and golang will panic `runtime error: index out of range` // len(pathElements) will be 1, and golang will panic `runtime error: index out of range`
if len(pathElements) < 2 { if len(pathElements) < 2 { //nolint: mnd
return r.URL.Path return r.URL.Path
} }
id := pathElements[len(pathElements)-2] id := pathElements[len(pathElements)-2]

View File

@@ -7,34 +7,37 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/filebrowser/filebrowser/v2/files"
"github.com/filebrowser/filebrowser/v2/users"
"github.com/hacdias/fileutils" "github.com/hacdias/fileutils"
"github.com/mholt/archiver" "github.com/mholt/archiver"
"github.com/filebrowser/filebrowser/v2/files"
"github.com/filebrowser/filebrowser/v2/users"
) )
func parseQueryFiles(r *http.Request, f *files.FileInfo, u *users.User) ([]string, error) { func parseQueryFiles(r *http.Request, f *files.FileInfo, _ *users.User) ([]string, error) {
files := []string{} var fileSlice []string
names := strings.Split(r.URL.Query().Get("files"), ",") names := strings.Split(r.URL.Query().Get("files"), ",")
if len(names) == 0 { if len(names) == 0 {
files = append(files, f.Path) fileSlice = append(fileSlice, f.Path)
} else { } else {
for _, name := range names { for _, name := range names {
name, err := url.QueryUnescape(strings.Replace(name, "+", "%2B", -1)) name, err := url.QueryUnescape(strings.Replace(name, "+", "%2B", -1)) //nolint:shadow
if err != nil { if err != nil {
return nil, err return nil, err
} }
name = fileutils.SlashClean(name) name = fileutils.SlashClean(name)
files = append(files, filepath.Join(f.Path, name)) fileSlice = append(fileSlice, filepath.Join(f.Path, name))
} }
} }
return files, nil return fileSlice, nil
} }
//nolint: goconst
func parseQueryAlgorithm(r *http.Request) (string, archiver.Writer, error) { func parseQueryAlgorithm(r *http.Request) (string, archiver.Writer, error) {
// TODO: use enum
switch r.URL.Query().Get("algo") { switch r.URL.Query().Get("algo") {
case "zip", "true", "": case "zip", "true", "":
return ".zip", archiver.NewZip(), nil return ".zip", archiver.NewZip(), nil

View File

@@ -7,11 +7,11 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
"path/filepath"
"strings" "strings"
"github.com/filebrowser/filebrowser/v2/files"
"github.com/filebrowser/filebrowser/v2/errors" "github.com/filebrowser/filebrowser/v2/errors"
"github.com/filebrowser/filebrowser/v2/files"
"github.com/filebrowser/filebrowser/v2/fileutils" "github.com/filebrowser/filebrowser/v2/fileutils"
) )
@@ -74,7 +74,7 @@ var resourcePostPutHandler = withUser(func(w http.ResponseWriter, r *http.Reques
} }
defer func() { defer func() {
io.Copy(ioutil.Discard, r.Body) _, _ = io.Copy(ioutil.Discard, r.Body)
}() }()
// For directories, only allow POST for creation. // For directories, only allow POST for creation.
@@ -93,7 +93,18 @@ var resourcePostPutHandler = withUser(func(w http.ResponseWriter, r *http.Reques
} }
} }
action := "upload"
if r.Method == http.MethodPut {
action = "save"
}
err := d.RunHook(func() error { err := d.RunHook(func() error {
dir, _ := filepath.Split(r.URL.Path)
err := d.user.Fs.MkdirAll(dir, 0775)
if err != nil {
return err
}
file, err := d.user.Fs.OpenFile(r.URL.Path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0775) file, err := d.user.Fs.OpenFile(r.URL.Path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0775)
if err != nil { if err != nil {
return err return err
@@ -114,7 +125,7 @@ var resourcePostPutHandler = withUser(func(w http.ResponseWriter, r *http.Reques
etag := fmt.Sprintf(`"%x%x"`, info.ModTime().UnixNano(), info.Size()) etag := fmt.Sprintf(`"%x%x"`, info.ModTime().UnixNano(), info.Size())
w.Header().Set("ETag", etag) w.Header().Set("ETag", etag)
return nil return nil
}, "upload", r.URL.Path, "", d.user) }, action, r.URL.Path, "", d.user)
return errToStatus(err), err return errToStatus(err), err
}) })
@@ -133,25 +144,22 @@ var resourcePatchHandler = withUser(func(w http.ResponseWriter, r *http.Request,
return http.StatusForbidden, nil return http.StatusForbidden, nil
} }
switch action {
case "copy":
if !d.user.Perm.Create {
return http.StatusForbidden, nil
}
case "rename":
default:
action = "rename"
if !d.user.Perm.Rename {
return http.StatusForbidden, nil
}
}
err = d.RunHook(func() error { err = d.RunHook(func() error {
if action == "copy" { switch action {
// TODO: use enum
case "copy":
if !d.user.Perm.Create {
return errors.ErrPermissionDenied
}
return fileutils.Copy(d.user.Fs, src, dst) return fileutils.Copy(d.user.Fs, src, dst)
case "rename":
if !d.user.Perm.Rename {
return errors.ErrPermissionDenied
}
return d.user.Fs.Rename(src, dst)
default:
return fmt.Errorf("unsupported action %s: %w", action, errors.ErrInvalidRequestParams)
} }
return d.user.Fs.Rename(src, dst)
}, action, src, dst, d.user) }, action, src, dst, d.user)
return errToStatus(err), err return errToStatus(err), err

View File

@@ -4,6 +4,7 @@ import (
"crypto/rand" "crypto/rand"
"encoding/base64" "encoding/base64"
"net/http" "net/http"
"path"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@@ -56,7 +57,9 @@ var sharePostHandler = withPermShare(func(w http.ResponseWriter, r *http.Request
var err error var err error
s, err = d.store.Share.GetPermanent(r.URL.Path, d.user.ID) s, err = d.store.Share.GetPermanent(r.URL.Path, d.user.ID)
if err == nil { if err == nil {
w.Write([]byte(d.server.BaseURL + "/share/" + s.Hash)) if _, err := w.Write([]byte(path.Join(d.server.BaseURL, "/share/", s.Hash))); err != nil {
return http.StatusInternalServerError, err
}
return 0, nil return 0, nil
} }
} }

View File

@@ -5,22 +5,22 @@ import (
"log" "log"
"net/http" "net/http"
"os" "os"
"path"
"path/filepath" "path/filepath"
"strings" "strings"
"text/template" "text/template"
rice "github.com/GeertJohan/go.rice" rice "github.com/GeertJohan/go.rice"
"github.com/filebrowser/filebrowser/v2/auth" "github.com/filebrowser/filebrowser/v2/auth"
"github.com/filebrowser/filebrowser/v2/settings" "github.com/filebrowser/filebrowser/v2/settings"
"github.com/filebrowser/filebrowser/v2/storage" "github.com/filebrowser/filebrowser/v2/storage"
"github.com/filebrowser/filebrowser/v2/version" "github.com/filebrowser/filebrowser/v2/version"
) )
func handleWithStaticData(w http.ResponseWriter, r *http.Request, d *data, box *rice.Box, file, contentType string) (int, error) { func handleWithStaticData(w http.ResponseWriter, _ *http.Request, d *data, box *rice.Box, file, contentType string) (int, error) {
w.Header().Set("Content-Type", contentType) w.Header().Set("Content-Type", contentType)
staticURL := strings.TrimPrefix(d.server.BaseURL+"/static", "/")
auther, err := d.store.Auth.Get(d.settings.AuthMethod) auther, err := d.store.Auth.Get(d.settings.AuthMethod)
if err != nil { if err != nil {
return http.StatusInternalServerError, err return http.StatusInternalServerError, err
@@ -31,17 +31,19 @@ func handleWithStaticData(w http.ResponseWriter, r *http.Request, d *data, box *
"DisableExternal": d.settings.Branding.DisableExternal, "DisableExternal": d.settings.Branding.DisableExternal,
"BaseURL": d.server.BaseURL, "BaseURL": d.server.BaseURL,
"Version": version.Version, "Version": version.Version,
"StaticURL": staticURL, "StaticURL": path.Join(d.server.BaseURL, "/static"),
"Signup": d.settings.Signup, "Signup": d.settings.Signup,
"NoAuth": d.settings.AuthMethod == auth.MethodNoAuth, "NoAuth": d.settings.AuthMethod == auth.MethodNoAuth,
"AuthMethod": d.settings.AuthMethod,
"LoginPage": auther.LoginPage(), "LoginPage": auther.LoginPage(),
"CSS": false, "CSS": false,
"ReCaptcha": false, "ReCaptcha": false,
"Theme": d.settings.Branding.Theme,
} }
if d.settings.Branding.Files != "" { if d.settings.Branding.Files != "" {
path := filepath.Join(d.settings.Branding.Files, "custom.css") fPath := filepath.Join(d.settings.Branding.Files, "custom.css")
_, err := os.Stat(path) _, err := os.Stat(fPath) //nolint:shadow
if err != nil && !os.IsNotExist(err) { if err != nil && !os.IsNotExist(err) {
log.Printf("couldn't load custom styles: %v", err) log.Printf("couldn't load custom styles: %v", err)
@@ -53,7 +55,7 @@ func handleWithStaticData(w http.ResponseWriter, r *http.Request, d *data, box *
} }
if d.settings.AuthMethod == auth.MethodJSONAuth { if d.settings.AuthMethod == auth.MethodJSONAuth {
raw, err := d.store.Auth.Get(d.settings.AuthMethod) raw, err := d.store.Auth.Get(d.settings.AuthMethod) //nolint:shadow
if err != nil { if err != nil {
return http.StatusInternalServerError, err return http.StatusInternalServerError, err
} }
@@ -83,29 +85,29 @@ func handleWithStaticData(w http.ResponseWriter, r *http.Request, d *data, box *
return 0, nil return 0, nil
} }
func getStaticHandlers(storage *storage.Storage, server *settings.Server) (http.Handler, http.Handler) { func getStaticHandlers(store *storage.Storage, server *settings.Server) (index, static http.Handler) {
box := rice.MustFindBox("../frontend/dist") box := rice.MustFindBox("../frontend/dist")
handler := http.FileServer(box.HTTPBox()) handler := http.FileServer(box.HTTPBox())
index := handle(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { index = handle(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
if r.Method != http.MethodGet { if r.Method != http.MethodGet {
return http.StatusNotFound, nil return http.StatusNotFound, nil
} }
w.Header().Set("x-xss-protection", "1; mode=block") w.Header().Set("x-xss-protection", "1; mode=block")
return handleWithStaticData(w, r, d, box, "index.html", "text/html; charset=utf-8") return handleWithStaticData(w, r, d, box, "index.html", "text/html; charset=utf-8")
}, "", storage, server) }, "", store, server)
static := handle(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { static = handle(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
if r.Method != http.MethodGet { if r.Method != http.MethodGet {
return http.StatusNotFound, nil return http.StatusNotFound, nil
} }
if d.settings.Branding.Files != "" { if d.settings.Branding.Files != "" {
if strings.HasPrefix(r.URL.Path, "img/") { if strings.HasPrefix(r.URL.Path, "img/") {
path := filepath.Join(d.settings.Branding.Files, r.URL.Path) fPath := filepath.Join(d.settings.Branding.Files, r.URL.Path)
if _, err := os.Stat(path); err == nil { if _, err := os.Stat(fPath); err == nil {
http.ServeFile(w, r, path) http.ServeFile(w, r, fPath)
return 0, nil return 0, nil
} }
} else if r.URL.Path == "custom.css" && d.settings.Branding.Files != "" { } else if r.URL.Path == "custom.css" && d.settings.Branding.Files != "" {
@@ -120,7 +122,7 @@ func getStaticHandlers(storage *storage.Storage, server *settings.Server) (http.
} }
return handleWithStaticData(w, r, d, box, r.URL.Path, "application/javascript; charset=utf-8") return handleWithStaticData(w, r, d, box, r.URL.Path, "application/javascript; charset=utf-8")
}, "/static/", storage, server) }, "/static/", store, server)
return index, static return index, static
} }

View File

@@ -8,9 +8,10 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/gorilla/mux"
"github.com/filebrowser/filebrowser/v2/errors" "github.com/filebrowser/filebrowser/v2/errors"
"github.com/filebrowser/filebrowser/v2/users" "github.com/filebrowser/filebrowser/v2/users"
"github.com/gorilla/mux"
) )
type modifyUserRequest struct { type modifyUserRequest struct {
@@ -27,7 +28,7 @@ func getUserID(r *http.Request) (uint, error) {
return uint(i), err return uint(i), err
} }
func getUser(w http.ResponseWriter, r *http.Request) (*modifyUserRequest, error) { func getUser(_ http.ResponseWriter, r *http.Request) (*modifyUserRequest, error) {
if r.Body == nil { if r.Body == nil {
return nil, errors.ErrEmptyRequest return nil, errors.ErrEmptyRequest
} }

View File

@@ -2,15 +2,16 @@ package http
import ( import (
"encoding/json" "encoding/json"
"errors"
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
"strings" "strings"
"github.com/filebrowser/filebrowser/v2/errors" libErrors "github.com/filebrowser/filebrowser/v2/errors"
) )
func renderJSON(w http.ResponseWriter, r *http.Request, data interface{}) (int, error) { func renderJSON(w http.ResponseWriter, _ *http.Request, data interface{}) (int, error) {
marsh, err := json.Marshal(data) marsh, err := json.Marshal(data)
if err != nil { if err != nil {
@@ -31,10 +32,14 @@ func errToStatus(err error) int {
return http.StatusOK return http.StatusOK
case os.IsPermission(err): case os.IsPermission(err):
return http.StatusForbidden return http.StatusForbidden
case os.IsNotExist(err), err == errors.ErrNotExist: case os.IsNotExist(err), err == libErrors.ErrNotExist:
return http.StatusNotFound return http.StatusNotFound
case os.IsExist(err), err == errors.ErrExist: case os.IsExist(err), err == libErrors.ErrExist:
return http.StatusConflict return http.StatusConflict
case errors.Is(err, libErrors.ErrPermissionDenied):
return http.StatusForbidden
case errors.Is(err, libErrors.ErrInvalidRequestParams):
return http.StatusBadRequest
default: default:
return http.StatusInternalServerError return http.StatusInternalServerError
} }
@@ -43,7 +48,7 @@ func errToStatus(err error) int {
// This is an addaptation if http.StripPrefix in which we don't // This is an addaptation if http.StripPrefix in which we don't
// return 404 if the page doesn't have the needed prefix. // return 404 if the page doesn't have the needed prefix.
func stripPrefix(prefix string, h http.Handler) http.Handler { func stripPrefix(prefix string, h http.Handler) http.Handler {
if prefix == "" { if prefix == "" || prefix == "/" {
return h return h
} }

View File

@@ -3,15 +3,16 @@ package runner
import ( import (
"os/exec" "os/exec"
"github.com/filebrowser/filebrowser/v2/settings"
"github.com/caddyserver/caddy" "github.com/caddyserver/caddy"
"github.com/filebrowser/filebrowser/v2/settings"
) )
// ParseCommand parses the command taking in account if the current // ParseCommand parses the command taking in account if the current
// instance uses a shell to run the commands or just calls the binary // instance uses a shell to run the commands or just calls the binary
// directyly. // directyly.
func ParseCommand(s *settings.Settings, raw string) ([]string, error) { func ParseCommand(s *settings.Settings, raw string) ([]string, error) {
command := []string{} var command []string
if len(s.Shell) == 0 { if len(s.Shell) == 0 {
cmd, args, err := caddy.SplitCommandAndArgs(raw) cmd, args, err := caddy.SplitCommandAndArgs(raw)
@@ -27,7 +28,7 @@ func ParseCommand(s *settings.Settings, raw string) ([]string, error) {
command = append(command, cmd) command = append(command, cmd)
command = append(command, args...) command = append(command, args...)
} else { } else {
command = append(s.Shell, raw) command = append(s.Shell, raw) //nolint:gocritic
} }
return command, nil return command, nil

View File

@@ -60,9 +60,9 @@ func (r *Runner) exec(raw, evt, path, dst string, user *users.User) error {
return err return err
} }
cmd := exec.Command(command[0], command[1:]...) cmd := exec.Command(command[0], command[1:]...) //nolint:gosec
cmd.Env = append(os.Environ(), fmt.Sprintf("FILE=%s", path)) cmd.Env = append(os.Environ(), fmt.Sprintf("FILE=%s", path))
cmd.Env = append(cmd.Env, fmt.Sprintf("SCOPE=%s", user.Scope)) cmd.Env = append(cmd.Env, fmt.Sprintf("SCOPE=%s", user.Scope)) //nolint:gocritic
cmd.Env = append(cmd.Env, fmt.Sprintf("TRIGGER=%s", evt)) cmd.Env = append(cmd.Env, fmt.Sprintf("TRIGGER=%s", evt))
cmd.Env = append(cmd.Env, fmt.Sprintf("USERNAME=%s", user.Username)) cmd.Env = append(cmd.Env, fmt.Sprintf("USERNAME=%s", user.Username))
cmd.Env = append(cmd.Env, fmt.Sprintf("DESTINATION=%s", dst)) cmd.Env = append(cmd.Env, fmt.Sprintf("DESTINATION=%s", dst))

View File

@@ -4,8 +4,9 @@ import (
"os" "os"
"strings" "strings"
"github.com/filebrowser/filebrowser/v2/rules"
"github.com/spf13/afero" "github.com/spf13/afero"
"github.com/filebrowser/filebrowser/v2/rules"
) )
type searchOptions struct { type searchOptions struct {

View File

@@ -5,4 +5,5 @@ type Branding struct {
Name string `json:"name"` Name string `json:"name"`
DisableExternal bool `json:"disableExternal"` DisableExternal bool `json:"disableExternal"`
Files string `json:"files"` Files string `json:"files"`
Theme string `json:"theme"`
} }

View File

@@ -17,21 +17,21 @@ var (
) )
// MakeUserDir makes the user directory according to settings. // MakeUserDir makes the user directory according to settings.
func (settings *Settings) MakeUserDir(username, userScope, serverRoot string) (string, error) { func (s *Settings) MakeUserDir(username, userScope, serverRoot string) (string, error) {
var err error var err error
userScope = strings.TrimSpace(userScope) userScope = strings.TrimSpace(userScope)
if userScope == "" || userScope == "./" { if userScope == "" || userScope == "./" {
userScope = "." userScope = "."
} }
if !settings.CreateUserDir { if !s.CreateUserDir {
return userScope, nil return userScope, nil
} }
fs := afero.NewBasePathFs(afero.NewOsFs(), serverRoot) fs := afero.NewBasePathFs(afero.NewOsFs(), serverRoot)
// Use the default auto create logic only if specific scope is not the default scope // Use the default auto create logic only if specific scope is not the default scope
if userScope != settings.Defaults.Scope { if userScope != s.Defaults.Scope {
// Try create the dir, for example: settings.Defaults.Scope == "." and userScope == "./foo" // Try create the dir, for example: settings.Defaults.Scope == "." and userScope == "./foo"
if userScope != "." { if userScope != "." {
err = fs.MkdirAll(userScope, os.ModePerm) err = fs.MkdirAll(userScope, os.ModePerm)
@@ -50,7 +50,7 @@ func (settings *Settings) MakeUserDir(username, userScope, serverRoot string) (s
} }
// Create default user dir // Create default user dir
userHomeBase := settings.Defaults.Scope + string(os.PathSeparator) + "users" userHomeBase := s.Defaults.Scope + string(os.PathSeparator) + "users"
userHome := userHomeBase + string(os.PathSeparator) + username userHome := userHomeBase + string(os.PathSeparator) + username
err = fs.MkdirAll(userHome, os.ModePerm) err = fs.MkdirAll(userHome, os.ModePerm)
if err != nil { if err != nil {

View File

@@ -33,7 +33,9 @@ func (s *Storage) GetByHash(hash string) (*Link, error) {
} }
if link.Expire != 0 && link.Expire <= time.Now().Unix() { if link.Expire != 0 && link.Expire <= time.Now().Unix() {
s.Delete(link.Hash) if err := s.Delete(link.Hash); err != nil {
return nil, err
}
return nil, errors.ErrNotExist return nil, errors.ErrNotExist
} }
@@ -55,7 +57,9 @@ func (s *Storage) Gets(path string, id uint) ([]*Link, error) {
for i, link := range links { for i, link := range links {
if link.Expire != 0 && link.Expire <= time.Now().Unix() { if link.Expire != 0 && link.Expire <= time.Now().Unix() {
s.Delete(link.Hash) if err := s.Delete(link.Hash); err != nil {
return nil, err
}
links = append(links[:i], links[i+1:]...) links = append(links[:i], links[i+1:]...)
} }
} }

View File

@@ -2,6 +2,7 @@ package bolt
import ( import (
"github.com/asdine/storm" "github.com/asdine/storm"
"github.com/filebrowser/filebrowser/v2/auth" "github.com/filebrowser/filebrowser/v2/auth"
"github.com/filebrowser/filebrowser/v2/errors" "github.com/filebrowser/filebrowser/v2/errors"
"github.com/filebrowser/filebrowser/v2/settings" "github.com/filebrowser/filebrowser/v2/settings"

View File

@@ -2,6 +2,7 @@ package bolt
import ( import (
"github.com/asdine/storm" "github.com/asdine/storm"
"github.com/filebrowser/filebrowser/v2/auth" "github.com/filebrowser/filebrowser/v2/auth"
"github.com/filebrowser/filebrowser/v2/settings" "github.com/filebrowser/filebrowser/v2/settings"
"github.com/filebrowser/filebrowser/v2/share" "github.com/filebrowser/filebrowser/v2/share"
@@ -11,10 +12,10 @@ import (
// NewStorage creates a storage.Storage based on Bolt DB. // NewStorage creates a storage.Storage based on Bolt DB.
func NewStorage(db *storm.DB) (*storage.Storage, error) { func NewStorage(db *storm.DB) (*storage.Storage, error) {
users := users.NewStorage(usersBackend{db: db}) userStore := users.NewStorage(usersBackend{db: db})
share := share.NewStorage(shareBackend{db: db}) shareStore := share.NewStorage(shareBackend{db: db})
settings := settings.NewStorage(settingsBackend{db: db}) settingsStore := settings.NewStorage(settingsBackend{db: db})
auth := auth.NewStorage(authBackend{db: db}, users) authStore := auth.NewStorage(authBackend{db: db}, userStore)
err := save(db, "version", 2) err := save(db, "version", 2)
if err != nil { if err != nil {
@@ -22,9 +23,9 @@ func NewStorage(db *storm.DB) (*storage.Storage, error) {
} }
return &storage.Storage{ return &storage.Storage{
Auth: auth, Auth: authStore,
Users: users, Users: userStore,
Share: share, Share: shareStore,
Settings: settings, Settings: settingsStore,
}, nil }, nil
} }

View File

@@ -2,6 +2,7 @@ package bolt
import ( import (
"github.com/asdine/storm" "github.com/asdine/storm"
"github.com/filebrowser/filebrowser/v2/settings" "github.com/filebrowser/filebrowser/v2/settings"
) )
@@ -10,12 +11,12 @@ type settingsBackend struct {
} }
func (s settingsBackend) Get() (*settings.Settings, error) { func (s settingsBackend) Get() (*settings.Settings, error) {
settings := &settings.Settings{} set := &settings.Settings{}
return settings, get(s.db, "settings", settings) return set, get(s.db, "settings", set)
} }
func (s settingsBackend) Save(settings *settings.Settings) error { func (s settingsBackend) Save(set *settings.Settings) error {
return save(s.db, "settings", settings) return save(s.db, "settings", set)
} }
func (s settingsBackend) GetServer() (*settings.Server, error) { func (s settingsBackend) GetServer() (*settings.Server, error) {

View File

@@ -7,14 +7,14 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"github.com/filebrowser/filebrowser/v2/auth"
"github.com/filebrowser/filebrowser/v2/users"
"github.com/asdine/storm" "github.com/asdine/storm"
"github.com/filebrowser/filebrowser/v2/settings"
"github.com/filebrowser/filebrowser/v2/storage"
toml "github.com/pelletier/go-toml" toml "github.com/pelletier/go-toml"
yaml "gopkg.in/yaml.v2" yaml "gopkg.in/yaml.v2"
"github.com/filebrowser/filebrowser/v2/auth"
"github.com/filebrowser/filebrowser/v2/settings"
"github.com/filebrowser/filebrowser/v2/storage"
"github.com/filebrowser/filebrowser/v2/users"
) )
type oldDefs struct { type oldDefs struct {

View File

@@ -2,34 +2,35 @@ package importer
import ( import (
"github.com/asdine/storm" "github.com/asdine/storm"
"github.com/filebrowser/filebrowser/v2/storage/bolt" "github.com/filebrowser/filebrowser/v2/storage/bolt"
) )
// Import imports an old configuration to a newer database. // Import imports an old configuration to a newer database.
func Import(oldDB, oldConf, newDB string) error { func Import(oldDBPath, oldConf, newDBPath string) error {
old, err := storm.Open(oldDB) oldDB, err := storm.Open(oldDBPath)
if err != nil { if err != nil {
return err return err
} }
defer old.Close() defer oldDB.Close()
new, err := storm.Open(newDB) newDB, err := storm.Open(newDBPath)
if err != nil { if err != nil {
return err return err
} }
defer new.Close() defer newDB.Close()
sto, err := bolt.NewStorage(new) sto, err := bolt.NewStorage(newDB)
if err != nil { if err != nil {
return err return err
} }
err = importUsers(old, sto) err = importUsers(oldDB, sto)
if err != nil { if err != nil {
return err return err
} }
err = importConf(old, oldConf, sto) err = importConf(oldDB, oldConf, sto)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -5,10 +5,11 @@ import (
"fmt" "fmt"
"github.com/asdine/storm" "github.com/asdine/storm"
bolt "go.etcd.io/bbolt"
"github.com/filebrowser/filebrowser/v2/rules" "github.com/filebrowser/filebrowser/v2/rules"
"github.com/filebrowser/filebrowser/v2/storage" "github.com/filebrowser/filebrowser/v2/storage"
"github.com/filebrowser/filebrowser/v2/users" "github.com/filebrowser/filebrowser/v2/users"
bolt "go.etcd.io/bbolt"
) )
type oldUser struct { type oldUser struct {
@@ -29,7 +30,7 @@ type oldUser struct {
} }
func readOldUsers(db *storm.DB) ([]*oldUser, error) { func readOldUsers(db *storm.DB) ([]*oldUser, error) {
users := []*oldUser{} var oldUsers []*oldUser
err := db.Bolt.View(func(tx *bolt.Tx) error { err := db.Bolt.View(func(tx *bolt.Tx) error {
return tx.Bucket([]byte("User")).ForEach(func(k []byte, v []byte) error { return tx.Bucket([]byte("User")).ForEach(func(k []byte, v []byte) error {
if len(v) > 0 && string(v)[0] == '{' { if len(v) > 0 && string(v)[0] == '{' {
@@ -40,14 +41,14 @@ func readOldUsers(db *storm.DB) ([]*oldUser, error) {
return err return err
} }
users = append(users, user) oldUsers = append(oldUsers, user)
} }
return nil return nil
}) })
}) })
return users, err return oldUsers, err
} }
func convertUsersToNew(old []*oldUser) ([]*users.User, error) { func convertUsersToNew(old []*oldUser) ([]*users.User, error) {

View File

@@ -3,6 +3,7 @@ package bolt
import ( import (
"github.com/asdine/storm" "github.com/asdine/storm"
"github.com/asdine/storm/q" "github.com/asdine/storm/q"
"github.com/filebrowser/filebrowser/v2/errors" "github.com/filebrowser/filebrowser/v2/errors"
"github.com/filebrowser/filebrowser/v2/share" "github.com/filebrowser/filebrowser/v2/share"
) )
@@ -46,5 +47,9 @@ func (s shareBackend) Save(l *share.Link) error {
} }
func (s shareBackend) Delete(hash string) error { func (s shareBackend) Delete(hash string) error {
return s.db.DeleteStruct(&share.Link{Hash: hash}) err := s.db.DeleteStruct(&share.Link{Hash: hash})
if err == storm.ErrNotFound {
return nil
}
return err
} }

View File

@@ -4,6 +4,7 @@ import (
"reflect" "reflect"
"github.com/asdine/storm" "github.com/asdine/storm"
"github.com/filebrowser/filebrowser/v2/errors" "github.com/filebrowser/filebrowser/v2/errors"
"github.com/filebrowser/filebrowser/v2/users" "github.com/filebrowser/filebrowser/v2/users"
) )
@@ -38,17 +39,17 @@ func (st usersBackend) GetBy(i interface{}) (user *users.User, err error) {
} }
func (st usersBackend) Gets() ([]*users.User, error) { func (st usersBackend) Gets() ([]*users.User, error) {
users := []*users.User{} var allUsers []*users.User
err := st.db.All(&users) err := st.db.All(&allUsers)
if err == storm.ErrNotFound { if err == storm.ErrNotFound {
return nil, errors.ErrNotExist return nil, errors.ErrNotExist
} }
if err != nil { if err != nil {
return users, err return allUsers, err
} }
return users, err return allUsers, err
} }
func (st usersBackend) Update(user *users.User, fields ...string) error { func (st usersBackend) Update(user *users.User, fields ...string) error {

View File

@@ -2,6 +2,7 @@ package bolt
import ( import (
"github.com/asdine/storm" "github.com/asdine/storm"
"github.com/filebrowser/filebrowser/v2/errors" "github.com/filebrowser/filebrowser/v2/errors"
) )

View File

@@ -7,7 +7,7 @@ import (
"github.com/filebrowser/filebrowser/v2/users" "github.com/filebrowser/filebrowser/v2/users"
) )
// Storage is a storage powered by a Backend whih makes the neccessary // Storage is a storage powered by a Backend which makes the necessary
// verifications when fetching and saving data to ensure consistency. // verifications when fetching and saving data to ensure consistency.
type Storage struct { type Storage struct {
Users *users.Storage Users *users.Storage

View File

@@ -40,7 +40,9 @@ func (s *Storage) Get(baseScope string, id interface{}) (user *User, err error)
if err != nil { if err != nil {
return return
} }
user.Clean(baseScope) if err := user.Clean(baseScope); err != nil {
return nil, err
}
return return
} }
@@ -52,7 +54,9 @@ func (s *Storage) Gets(baseScope string) ([]*User, error) {
} }
for _, user := range users { for _, user := range users {
user.Clean(baseScope) if err := user.Clean(baseScope); err != nil { //nolint:shadow
return nil, err
}
} }
return users, err return users, err

View File

@@ -4,11 +4,11 @@ import (
"path/filepath" "path/filepath"
"regexp" "regexp"
"github.com/filebrowser/filebrowser/v2/errors" "github.com/spf13/afero"
"github.com/filebrowser/filebrowser/v2/errors"
"github.com/filebrowser/filebrowser/v2/files" "github.com/filebrowser/filebrowser/v2/files"
"github.com/filebrowser/filebrowser/v2/rules" "github.com/filebrowser/filebrowser/v2/rules"
"github.com/spf13/afero"
) )
// ViewMode describes a view mode. // ViewMode describes a view mode.
@@ -52,6 +52,7 @@ var checkableFields = []string{
// Clean cleans up a user and verifies if all its fields // Clean cleans up a user and verifies if all its fields
// are alright to be saved. // are alright to be saved.
//nolint:gocyclo
func (u *User) Clean(baseScope string, fields ...string) error { func (u *User) Clean(baseScope string, fields ...string) error {
if len(fields) == 0 { if len(fields) == 0 {
fields = checkableFields fields = checkableFields