Compare commits

...

71 Commits

Author SHA1 Message Date
Oleg Lobanov
7f2d221083 chore(release): 2.18.0 2021-10-31 17:18:01 +01:00
Oleg Lobanov
74b7cd8e81 fix: security issue in command runner (closes #1621) 2021-10-31 17:13:16 +01:00
niubility000
6cb51b4eb4 Add files via upload (#1615) 2021-10-24 13:35:29 +04:00
niubility000
f09bf3e1d0 fix: fix sidebar navigation on mobile devices (#1618) 2021-10-19 20:37:12 +04:00
niubility000
6f345be3e4 fix: search box is misaligned when the browser preferred font size is other than 16px (#1613) 2021-10-19 20:34:17 +04:00
niubility000
ddd4ffa4ca fix: set correct editor height regardless of preferred font size (#1614) 2021-10-19 20:32:25 +04:00
niubility000
deabc80fd7 fix: back button behaviour in preview (#1573) 2021-10-12 13:09:05 +02:00
niubility000
b6a51bed51 fix: zoom pics when dlclick at first time (#1561) 2021-09-23 10:21:17 +02:00
lilihx
0426629a59 feat: add ability to select file modified time format (#1536) 2021-09-11 14:12:51 +02:00
Ryan Qian
0358e42d2c feat: add manifest theme color param (#1542) 2021-09-10 17:08:15 +02:00
Filip Hanes
3768e3345f chore: add slovak translation (#1534) 2021-09-03 12:03:50 +02:00
ahmetlutfu
16e434be66 chore: add turkish translation (#1524) 2021-08-31 11:49:33 +02:00
Oleg Lobanov
bf303c536a chore(release): 2.17.2 2021-08-27 12:40:51 +02:00
Andrew Kennedy
43a460993c fix: bug with inlineLink not creating url properly (#1515) 2021-08-26 12:43:37 +02:00
Oleg Lobanov
7f0673ee70 chore(release): 2.17.1 2021-08-23 10:03:31 +02:00
Oleg Lobanov
4c3099a086 fix: internal server error if --disable-preview-resize flag is set (closes #1510) 2021-08-23 10:03:11 +02:00
Oleg Lobanov
f0bc9167b1 chore(release): 2.17.0 2021-08-21 16:51:34 +02:00
Ramires Viana
23d646c456 fix: escape quote on index template
fixes #1501
2021-08-20 14:43:06 +02:00
Ramires Viana
76add9e527 feat: open file option on preview 2021-08-20 14:43:06 +02:00
Ramires Viana
c63cc5a2d2 fix: file caching directive 2021-08-20 14:43:06 +02:00
Andrew Kennedy
25c8788390 fix: 401 error in share view open file button (#1495) 2021-08-19 14:35:24 +02:00
Oleg Lobanov
aa52b07bb1 chore(release): 2.16.1 2021-08-04 11:44:29 +02:00
Oleg Lobanov
76b466f649 fix: check symlink target type (closes #1488) 2021-08-04 11:44:02 +02:00
Oleg Lobanov
8ecc2da947 chore(release): 2.16.0 2021-07-26 13:03:53 +02:00
Oleg Lobanov
8650d2ffe7 fix: failure on broken symlink deletion 2021-07-26 13:02:11 +02:00
Oleg Lobanov
34d7d2c8c4 chore: upgrade golangci-lint 2021-07-26 12:00:05 +02:00
Oleg Lobanov
201329abce chore: add Content-Security-Policy header 2021-07-26 11:08:39 +02:00
Oleg Lobanov
f2b5dd3787 chore: don't break folder download if any file processing causes an error 2021-07-26 10:41:56 +02:00
Oleg Lobanov
5072bbb2cb fix: break resource create/update handlers on error (closes #1464) 2021-07-24 15:33:54 +02:00
Oleg Lobanov
6b19ab6613 fix: don't remove files on unsuccessful updates (closes #1456) 2021-07-24 15:32:24 +02:00
Oleg Lobanov
730be5ef6b Create SECURITY.md 2021-07-03 16:56:27 +02:00
laggardkernel
46ee595389 fix: short commit sha and typo fix in Makefile (#1411) 2021-05-25 11:29:38 +02:00
Oleg Lobanov
dee465ab86 Merge pull request #1373 from ramiresviana/fixes-9
Some fixes
2021-05-03 23:29:12 +02:00
Ramires Viana
209f9fa77f fix: omit file content 2021-04-23 12:04:02 +00:00
Ramires Viana
ba8c09f454 feat: show more button on share 2021-04-23 11:59:34 +00:00
Ramires Viana
16a34defc0 feat: file name on page title 2021-04-23 11:59:04 +00:00
Ramires Viana
7d1e03075d feat: mod time title on file info 2021-04-23 11:57:56 +00:00
Ramires Viana
1c25f6ee69 feat: open file option on share 2021-04-23 11:55:56 +00:00
Ramires Viana
aa172b8bb5 feat: gzip encoding for static js files 2021-04-22 12:48:45 +00:00
Ramires Viana
4711e7bcd5 chore: set public path on the fly 2021-04-20 19:51:10 +00:00
Ramires Viana
8a47aee137 chore: split preview creation logic 2021-04-19 13:16:48 +00:00
Ramires Viana
190cb99a79 feat: browser cache directives 2021-04-19 12:49:40 +00:00
Ramires Viana
603203848a feat: display error messages on settings 2021-04-16 15:07:05 +00:00
Ramires Viana
5e6f14b5dc feat: message for connection error 2021-04-16 14:01:10 +00:00
Ramires Viana
976eb5583d feat: loading spinner on views navigation 2021-04-16 12:47:50 +00:00
Ramires Viana
b92152693f chore: split action on resource patch handler 2021-04-16 12:04:06 +00:00
Ramires Viana
7ec24d9d77 feat: support for IE11 browser 2021-04-15 12:28:19 +00:00
Ramires Viana
20ebbf6611 fix: copying files with special characters 2021-04-15 11:50:30 +00:00
Ramires Viana
ba7e71a7c3 fix: inconsistent double click on listing item 2021-04-14 15:29:06 +00:00
Ramires Viana
8973c4598f fix: delete image cache when moving 2021-04-14 15:20:38 +00:00
Ramires Viana
18889ad725 fix: no items displayed on file listing 2021-04-14 11:51:33 +00:00
Oleg Lobanov
73ccbe912f chore(release): 2.15.0 2021-04-06 13:57:29 +02:00
Oleg Lobanov
84e3a98303 Merge pull request #1353 from ramiresviana/fixes-8
Some fixes
2021-03-30 09:43:27 +02:00
adrium
7dd5b34d42 feat: add EXIF thumbnail support for JPEG files (#1234) 2021-03-29 11:40:00 +02:00
Alexis Lefebvre
4470d0a704 chore: update issue templates (#1355) 2021-03-28 12:52:03 +02:00
Ramires Viana
a76e01d2b7 feat: dynamic autoplay on previewer 2021-03-26 17:31:27 +00:00
Ramires Viana
2697093ac1 fix: empty archive name on directory download 2021-03-26 14:45:18 +00:00
Ramires Viana
59f9964e80 fix: check modify permission on file overwrite 2021-03-26 13:30:14 +00:00
Ramires Viana
1516d9932b fix: buttons without permission on header 2021-03-26 12:45:17 +00:00
Ramires Viana
fcb115f42d fix: mouse wheel zoom on previewer 2021-03-25 19:37:54 +00:00
Ramires Viana
e410272e6b feat: dynamic zoom limit on previewer 2021-03-25 19:36:53 +00:00
Ramires Viana
87f1881b42 fix: list item interactions on share 2021-03-25 15:47:49 +00:00
Ramires Viana
c0d85f3d85 fix: image quality switch on previewer 2021-03-25 14:24:46 +00:00
Ramires Viana
98d79b8ed9 fix: missing bold variation for Roboto font 2021-03-24 19:06:56 +00:00
Ramires Viana
fe80730bb1 fix: no header button animations on file listing 2021-03-24 19:05:15 +00:00
Ramires Viana
6c8ee96e6a feat: dynamic item count on file listing 2021-03-24 17:50:16 +00:00
Ramires Viana
b521dec8f9 fix: hidden editor header on Safari 2021-03-24 12:23:05 +00:00
Ramires Viana
e9baf0c4b6 fix: empty text file on editor 2021-03-23 18:18:02 +00:00
Ramires Viana
e1a6f593e1 fix: error causes panic on upload 2021-03-23 13:13:46 +00:00
Oleg Lobanov
4b068b3058 chore(release): 2.14.1 2021-03-21 14:24:33 +01:00
Oleg Lobanov
da54bd6c21 fix: display public routes with header proxy auth 2021-03-21 14:24:23 +01:00
100 changed files with 2370 additions and 447 deletions

View File

@@ -4,19 +4,19 @@ about: Create a report to help us improve
---
**Description**
A clear and concise description of what the issue is about. What are you trying to do?
<!-- A clear and concise description of what the issue is about. What are you trying to do? -->
**Expected behaviour**
What did you expect to happen?
<!-- What did you expect to happen? -->
**What is happening instead?**
Please, give full error messages and/or log.
<!-- Please, give full error messages and/or log. -->
**Additional context**
Add any other context about the problem here. If applicable, add screenshots to help explain your problem.
<!-- Add any other context about the problem here. If applicable, add screenshots to help explain your problem. -->
**How to reproduce?**
Tell us how to reproduce this issue. How can someone who is starting from scratch reproduce this behaviour as minimally as possible?
<!-- Tell us how to reproduce this issue. How can someone who is starting from scratch reproduce this behaviour as minimally as possible? -->
**Files**
A list of relevant files for this issue. Large files can be uploaded one-by-one or in a tarball/zipfile.
<!-- A list of relevant files for this issue. Large files can be uploaded one-by-one or in a tarball/zipfile. -->

View File

@@ -4,13 +4,13 @@ about: Suggest an idea for this project
---
**Is your feature request related to a problem? Please describe.**
Add a clear and concise description of what the problem is. E.g. *I'm always frustrated when [...]*
<!-- Add a clear and concise description of what the problem is. E.g. *I'm always frustrated when [...]* -->
**Describe the solution you'd like**
Add a clear and concise description of what you want to happen.
<!-- Add a clear and concise description of what you want to happen. -->
**Describe alternatives you've considered**
Add a clear and concise description of any alternative solutions or features you've considered.
<!-- Add a clear and concise description of any alternative solutions or features you've considered. -->
**Additional context**
Add any other context or screenshots about the feature request here.
<!-- Add any other context or screenshots about the feature request here. -->

View File

@@ -1,6 +1,8 @@
**Description**
<!--
Please explain the changes you made here.
If the feature changes current behaviour, explain why your solution is better.
-->
:rotating_light: Before submitting your PR, please read [community](https://github.com/filebrowser/community), and indicate which issues (in any of the repos) are either fixed or closed by this PR. See [GitHub Help: Closing issues using keywords](https://help.github.com/articles/closing-issues-via-commit-messages/).
@@ -11,6 +13,8 @@ If the feature changes current behaviour, explain why your solution is better.
- [ ] AVOID breaking the continuous integration build.
**Further comments**
<!--
If this is a relatively large or complex change, kick off the discussion by explaining why you chose the solution you did, what alternatives you considered, etc.
:heart: Thank you!
-->

View File

@@ -6,6 +6,8 @@ linters-settings:
funlen:
lines: 100
statements: 50
gci:
local-prefixes: github.com/filebrowser/filebrowser
goconst:
min-len: 2
min-occurrences: 2
@@ -26,8 +28,6 @@ linters-settings:
min-complexity: 15
goimports:
local-prefixes: github.com/filebrowser/filebrowser
golint:
min-confidence: 0
gomnd:
settings:
mnd:
@@ -58,26 +58,25 @@ linters:
- dogsled
- dupl
- errcheck
- exportloopref
- exhaustive
- funlen
- gochecknoinits
- goconst
- gocritic
- gocyclo
- goimports
- golint
- gomnd
- goprintffuncname
- gosec
- gosimple
- govet
- ineffassign
- interfacer
- lll
- misspell
- nakedret
- nolintlint
- rowserrcheck
- scopelint
- staticcheck
- structcheck
- stylecheck
@@ -89,19 +88,6 @@ linters:
- 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
@@ -118,6 +104,9 @@ issues:
- text: "Auther"
linters:
- misspell
- text: "strconv.Parse"
linters:
- gomnd
run:
skip-dirs:

View File

@@ -2,6 +2,120 @@
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.18.0](https://github.com/filebrowser/filebrowser/compare/v2.17.2...v2.18.0) (2021-10-31)
### Features
* add ability to select file modified time format ([#1536](https://github.com/filebrowser/filebrowser/issues/1536)) ([0426629](https://github.com/filebrowser/filebrowser/commit/0426629a59c712849570d3e29956948ae7725a4a))
* add manifest theme color param ([#1542](https://github.com/filebrowser/filebrowser/issues/1542)) ([0358e42](https://github.com/filebrowser/filebrowser/commit/0358e42d2c206732fffa77714f5a66f4fe50a69d))
### Bug Fixes
* back button behaviour in preview ([#1573](https://github.com/filebrowser/filebrowser/issues/1573)) ([deabc80](https://github.com/filebrowser/filebrowser/commit/deabc80fd7670983039dfcd29531b45002ca5d9e))
* fix sidebar navigation on mobile devices ([#1618](https://github.com/filebrowser/filebrowser/issues/1618)) ([f09bf3e](https://github.com/filebrowser/filebrowser/commit/f09bf3e1d076b27d29ba8a91cf448a99993bc444))
* search box is misaligned when the browser preferred font size is other than 16px ([#1613](https://github.com/filebrowser/filebrowser/issues/1613)) ([6f345be](https://github.com/filebrowser/filebrowser/commit/6f345be3e47ba57ecc1eb9a62587ab949078c125))
* security issue in command runner (closes [#1621](https://github.com/filebrowser/filebrowser/issues/1621)) ([74b7cd8](https://github.com/filebrowser/filebrowser/commit/74b7cd8e81840537a8206317344f118093153e8d))
* set correct editor height regardless of preferred font size ([#1614](https://github.com/filebrowser/filebrowser/issues/1614)) ([ddd4ffa](https://github.com/filebrowser/filebrowser/commit/ddd4ffa4caa6b292a3a644ecd897aba1237c7503))
* zoom pics when dlclick at first time ([#1561](https://github.com/filebrowser/filebrowser/issues/1561)) ([b6a51be](https://github.com/filebrowser/filebrowser/commit/b6a51bed516814944f8aa41440652242d57824c5))
### [2.17.2](https://github.com/filebrowser/filebrowser/compare/v2.17.1...v2.17.2) (2021-08-27)
### Bug Fixes
* bug with inlineLink not creating url properly ([#1515](https://github.com/filebrowser/filebrowser/issues/1515)) ([43a4609](https://github.com/filebrowser/filebrowser/commit/43a460993c3f0d158b876db4b20caa7963e9f361))
### [2.17.1](https://github.com/filebrowser/filebrowser/compare/v2.17.0...v2.17.1) (2021-08-23)
### Bug Fixes
* internal server error if --disable-preview-resize flag is set (closes [#1510](https://github.com/filebrowser/filebrowser/issues/1510)) ([4c3099a](https://github.com/filebrowser/filebrowser/commit/4c3099a086c206dcb3bc70ee8c8da02eee61c30b))
## [2.17.0](https://github.com/filebrowser/filebrowser/compare/v2.16.1...v2.17.0) (2021-08-21)
### Features
* open file option on preview ([76add9e](https://github.com/filebrowser/filebrowser/commit/76add9e5274b0373c6b983e3b20e387a14ea6c9e))
### Bug Fixes
* 401 error in share view open file button ([#1495](https://github.com/filebrowser/filebrowser/issues/1495)) ([25c8788](https://github.com/filebrowser/filebrowser/commit/25c87883908babde073390a2e2320a8e5880a87c))
* escape quote on index template ([23d646c](https://github.com/filebrowser/filebrowser/commit/23d646c456876d06cf48e71c1e57b69de99511f0)), closes [#1501](https://github.com/filebrowser/filebrowser/issues/1501)
* file caching directive ([c63cc5a](https://github.com/filebrowser/filebrowser/commit/c63cc5a2d25909cc4e2f2e7235f276ec66c32bf2))
### [2.16.1](https://github.com/filebrowser/filebrowser/compare/v2.16.0...v2.16.1) (2021-08-04)
### Bug Fixes
* check symlink target type (closes [#1488](https://github.com/filebrowser/filebrowser/issues/1488)) ([76b466f](https://github.com/filebrowser/filebrowser/commit/76b466f6492e74cf13e66a33e7e5f597ac92b240))
## [2.16.0](https://github.com/filebrowser/filebrowser/compare/v2.15.0...v2.16.0) (2021-07-26)
### Features
* browser cache directives ([190cb99](https://github.com/filebrowser/filebrowser/commit/190cb99a79a0d438eca2da13539f8c6449ad73ac))
* display error messages on settings ([6032038](https://github.com/filebrowser/filebrowser/commit/603203848a8b2221158088b6d849609db4c0c46c))
* file name on page title ([16a34de](https://github.com/filebrowser/filebrowser/commit/16a34defc02554a77c6ac47b9e17e69d098a09fe))
* gzip encoding for static js files ([aa172b8](https://github.com/filebrowser/filebrowser/commit/aa172b8bb5f17d5f5cb9666bfb5ee650d8091fb5))
* loading spinner on views navigation ([976eb55](https://github.com/filebrowser/filebrowser/commit/976eb5583dae474125fd7ddec5dc19b6c291f98f))
* message for connection error ([5e6f14b](https://github.com/filebrowser/filebrowser/commit/5e6f14b5dcb9c5efdf526f1346e09c2d0b2f6974))
* mod time title on file info ([7d1e030](https://github.com/filebrowser/filebrowser/commit/7d1e03075d2c27148f60813defa0f68403d1d3c2))
* open file option on share ([1c25f6e](https://github.com/filebrowser/filebrowser/commit/1c25f6ee69bd71eed82af7020006d0e27537a967))
* show more button on share ([ba8c09f](https://github.com/filebrowser/filebrowser/commit/ba8c09f454feeadf4a1e97547a34151a81b389d5))
* support for IE11 browser ([7ec24d9](https://github.com/filebrowser/filebrowser/commit/7ec24d9d7794fa37825f64ca2d1575f568fb1362))
### Bug Fixes
* break resource create/update handlers on error (closes [#1464](https://github.com/filebrowser/filebrowser/issues/1464)) ([5072bbb](https://github.com/filebrowser/filebrowser/commit/5072bbb2cbf5b29d041629faa8367f15e4d145a2))
* copying files with special characters ([20ebbf6](https://github.com/filebrowser/filebrowser/commit/20ebbf6611b734371426fb1b9cb5e388be90bf7e))
* delete image cache when moving ([8973c45](https://github.com/filebrowser/filebrowser/commit/8973c4598ff817647f1f1ad6ee36480054cd2776))
* don't remove files on unsuccessful updates (closes [#1456](https://github.com/filebrowser/filebrowser/issues/1456)) ([6b19ab6](https://github.com/filebrowser/filebrowser/commit/6b19ab6613b12be7f075299cd98f4b41d43827c7))
* failure on broken symlink deletion ([8650d2f](https://github.com/filebrowser/filebrowser/commit/8650d2ffe7a29cbafa800efcecbf6a61598a9f0c))
* inconsistent double click on listing item ([ba7e71a](https://github.com/filebrowser/filebrowser/commit/ba7e71a7c3b0cc71012e5adf94b1c642e554972e))
* no items displayed on file listing ([18889ad](https://github.com/filebrowser/filebrowser/commit/18889ad725f7f7e5a7e3f7abcf156487556dbeaf))
* omit file content ([209f9fa](https://github.com/filebrowser/filebrowser/commit/209f9fa77f751054512355f2b74b9b7258465d0b))
* short commit sha and typo fix in Makefile ([#1411](https://github.com/filebrowser/filebrowser/issues/1411)) ([46ee595](https://github.com/filebrowser/filebrowser/commit/46ee59538914dc2859f0da6b32e2d062d0a01b10))
## [2.15.0](https://github.com/filebrowser/filebrowser/compare/v2.14.1...v2.15.0) (2021-04-06)
### Features
* add EXIF thumbnail support for JPEG files ([#1234](https://github.com/filebrowser/filebrowser/issues/1234)) ([7dd5b34](https://github.com/filebrowser/filebrowser/commit/7dd5b34d425dfbc2782152310483cbecf85c800a))
* dynamic autoplay on previewer ([a76e01d](https://github.com/filebrowser/filebrowser/commit/a76e01d2b78a785f3665a8b3532c7cc566bfabce))
* dynamic item count on file listing ([6c8ee96](https://github.com/filebrowser/filebrowser/commit/6c8ee96e6a21fae5d4608bdc7a5c5a161d7dafd3))
* dynamic zoom limit on previewer ([e410272](https://github.com/filebrowser/filebrowser/commit/e410272e6be6a0b660efe8d4eee6c6e9dd834cc5))
### Bug Fixes
* buttons without permission on header ([1516d99](https://github.com/filebrowser/filebrowser/commit/1516d9932bf9926ac8b4cb3e738a5f51e80d5b1d))
* check modify permission on file overwrite ([59f9964](https://github.com/filebrowser/filebrowser/commit/59f9964e80c8233775f27be33a4c16a31bfe848a))
* empty archive name on directory download ([2697093](https://github.com/filebrowser/filebrowser/commit/2697093ac151f74eea3022951d128acfe04d1dcf))
* empty text file on editor ([e9baf0c](https://github.com/filebrowser/filebrowser/commit/e9baf0c4b688fab291cdc842ec464c7a7a816499))
* error causes panic on upload ([e1a6f59](https://github.com/filebrowser/filebrowser/commit/e1a6f593e1824e7fa4345a61dff5b1bb8cd22d05))
* hidden editor header on Safari ([b521dec](https://github.com/filebrowser/filebrowser/commit/b521dec8f9b14dd92248c429e902ebc639046389))
* image quality switch on previewer ([c0d85f3](https://github.com/filebrowser/filebrowser/commit/c0d85f3d85926c8790757bf142140d19455ae8ca))
* list item interactions on share ([87f1881](https://github.com/filebrowser/filebrowser/commit/87f1881b429877a740ea84a8e783ad4103248289))
* missing bold variation for Roboto font ([98d79b8](https://github.com/filebrowser/filebrowser/commit/98d79b8ed955df5691a306d709b4ab60d91da408))
* mouse wheel zoom on previewer ([fcb115f](https://github.com/filebrowser/filebrowser/commit/fcb115f42d33db2be7a4d428ec53d65d6050320b))
* no header button animations on file listing ([fe80730](https://github.com/filebrowser/filebrowser/commit/fe80730bb135b38e4d9de470c75cbe10b1aec201))
### [2.14.1](https://github.com/filebrowser/filebrowser/compare/v2.14.0...v2.14.1) (2021-03-21)
### Bug Fixes
* display public routes with header proxy auth ([da54bd6](https://github.com/filebrowser/filebrowser/commit/da54bd6c214d7ee39b71d710ddfe6dd25fc4e5d6))
## [2.14.0](https://github.com/filebrowser/filebrowser/compare/v2.13.0...v2.14.0) (2021-03-21)

View File

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

26
SECURITY.md Normal file
View File

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

View File

@@ -41,6 +41,7 @@ func addConfigFlags(flags *pflag.FlagSet) {
flags.String("recaptcha.secret", "", "ReCaptcha secret")
flags.String("branding.name", "", "replace 'File Browser' by this name")
flags.String("branding.color", "", "set the theme color")
flags.String("branding.files", "", "path to directory with images and custom styles")
flags.Bool("branding.disableExternal", false, "disable external links such as GitHub links")
}
@@ -121,7 +122,7 @@ func getAuthentication(flags *pflag.FlagSet, defaults ...interface{}) (settings.
}
func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Auther) {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) //nolint:gomnd
fmt.Fprintf(w, "Sign up:\t%t\n", set.Signup)
fmt.Fprintf(w, "Create User Dir:\t%t\n", set.CreateUserDir)
@@ -131,6 +132,7 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut
fmt.Fprintf(w, "\tName:\t%s\n", set.Branding.Name)
fmt.Fprintf(w, "\tFiles override:\t%s\n", set.Branding.Files)
fmt.Fprintf(w, "\tDisable external links:\t%t\n", set.Branding.DisableExternal)
fmt.Fprintf(w, "\tColor:\t%s\n", set.Branding.Color)
fmt.Fprintln(w, "\nServer:")
fmt.Fprintf(w, "\tLog:\t%s\n", ser.Log)
fmt.Fprintf(w, "\tPort:\t%s\n", ser.Port)

View File

@@ -51,6 +51,8 @@ you want to change. Other options will remain unchanged.`,
set.Shell = convertCmdStrToCmdArray(mustGetString(flags, flag.Name))
case "branding.name":
set.Branding.Name = mustGetString(flags, flag.Name)
case "branding.color":
set.Branding.Color = mustGetString(flags, flag.Name)
case "branding.disableExternal":
set.Branding.DisableExternal = mustGetBool(flags, flag.Name)
case "branding.files":

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -16,7 +16,7 @@
<!-- Add to home screen for Android and modern mobile browsers -->
<link rel="manifest" id="manifestPlaceholder" crossorigin="use-credentials">
<meta name="theme-color" content="#2979ff">
<meta name="theme-color" content="[{[ if .Color -]}][{[ .Color ]}][{[ else ]}]#2979ff[{[ end ]}]">
<!-- Add to home screen for Safari on iOS/iPadOS -->
<meta name="apple-mobile-web-app-capable" content="yes">
@@ -26,11 +26,11 @@
<!-- Add to home screen for Windows -->
<meta name="msapplication-TileImage" content="[{[ .StaticURL ]}]/img/icons/mstile-144x144.png">
<meta name="msapplication-TileColor" content="#2979ff">
<meta name="msapplication-TileColor" content="[{[ if .Color -]}][{[ .Color ]}][{[ else ]}]#2979ff[{[ end ]}]">
<!-- Inject Some Variables and generate the manifest json -->
<script>
window.FileBrowser = JSON.parse(`[{[ .Json ]}]`);
window.FileBrowser = JSON.parse('[{[ .Json ]}]');
var fullStaticURL = window.location.origin + window.FileBrowser.StaticURL;
var dynamicManifest = {
@@ -51,7 +51,7 @@
"start_url": window.location.origin + window.FileBrowser.BaseURL,
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#455a64"
"theme_color": window.FileBrowser.Color || "#455a64"
}
const stringManifest = JSON.stringify(dynamicManifest);
@@ -77,7 +77,7 @@
opacity: 0;
}
.spinner {
#loading .spinner {
width: 70px;
text-align: center;
position: fixed;
@@ -87,7 +87,7 @@
transform: translate(-50%, -50%);
}
.spinner > div {
#loading .spinner > div {
width: 18px;
height: 18px;
background-color: #333;
@@ -97,12 +97,12 @@
animation: sk-bouncedelay 1.4s infinite ease-in-out both;
}
.spinner .bounce1 {
#loading .spinner .bounce1 {
-webkit-animation-delay: -0.32s;
animation-delay: -0.32s;
}
.spinner .bounce2 {
#loading .spinner .bounce2 {
-webkit-animation-delay: -0.16s;
animation-delay: -0.16s;
}

View File

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

View File

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

View File

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,15 +1,15 @@
<template>
<nav :class="{ active }">
<template v-if="isLogged">
<router-link
<button
class="action"
to="/files/"
@click="toRoot"
:aria-label="$t('sidebar.myFiles')"
:title="$t('sidebar.myFiles')"
>
<i class="material-icons">folder</i>
<span>{{ $t("sidebar.myFiles") }}</span>
</router-link>
</button>
<div v-if="user.perm.create">
<button
@@ -34,15 +34,15 @@
</div>
<div>
<router-link
<button
class="action"
to="/settings"
@click="toSettings"
:aria-label="$t('sidebar.settings')"
:title="$t('sidebar.settings')"
>
<i class="material-icons">settings_applications</i>
<span>{{ $t("sidebar.settings") }}</span>
</router-link>
</button>
<button
v-if="authMethod == 'json'"
@@ -125,6 +125,14 @@ export default {
authMethod: () => authMethod,
},
methods: {
toRoot() {
this.$router.push({ path: "/files/" }, () => {});
this.$store.commit("closeHovers");
},
toSettings() {
this.$router.push({ path: "/settings" }, () => {});
this.$store.commit("closeHovers");
},
help() {
this.$store.commit("showHover", "help");
},

View File

@@ -29,14 +29,6 @@ export default {
type: Number,
default: () => 200,
},
maxScale: {
type: Number,
default: () => 4,
},
minScale: {
type: Number,
default: () => 0.25,
},
classList: {
type: Array,
default: () => [],
@@ -45,10 +37,6 @@ export default {
type: Number,
default: () => 0.25,
},
autofill: {
type: Boolean,
default: () => false,
},
},
data() {
return {
@@ -56,6 +44,7 @@ export default {
lastX: null,
lastY: null,
inDrag: false,
touches: 0,
lastTouchDistance: 0,
moveDisabled: false,
disabledTimer: null,
@@ -64,6 +53,8 @@ export default {
center: { x: 0, y: 0 },
relative: { x: 0, y: 0 },
},
maxScale: 4,
minScale: 0.25,
};
},
mounted() {
@@ -88,6 +79,10 @@ export default {
},
watch: {
src: function () {
if (!this.decodeUTIF()) {
this.$refs.imgex.src = this.src;
}
this.scale = 1;
this.setZoom();
this.setCenter();
@@ -122,6 +117,21 @@ export default {
img.classList.add("image-ex-img-ready");
document.addEventListener("mouseup", this.onMouseUp);
let realSize = img.naturalWidth;
let displaySize = img.offsetWidth;
// Image is in portrait orientation
if (img.naturalHeight > img.naturalWidth) {
realSize = img.naturalHeight;
displaySize = img.offsetHeight;
}
// Scale needed to display the image on full size
const fullScale = realSize / displaySize;
// Full size plus additional zoom
this.maxScale = fullScale + 4;
},
onMouseUp() {
this.inDrag = false;
@@ -251,7 +261,7 @@ export default {
}
},
wheelMove(event) {
this.scale += (event.wheelDeltaY / 100) * this.zoomStep;
this.scale += -Math.sign(event.deltaY) * this.zoomStep;
this.setZoom();
},
setZoom() {

View File

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

View File

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

View File

@@ -56,6 +56,12 @@ export default {
let prompt = this.$refs.currentComponent;
// Esc!
if (event.keyCode === 27) {
event.stopImmediatePropagation();
this.$store.commit("closeHovers");
}
// Enter
if (event.keyCode == 13) {
switch (this.show) {

View File

@@ -11,11 +11,11 @@
<div class="card-action full">
<div @click="uploadFile" class="action">
<i class="material-icons">insert_drive_file</i>
<div class="title">File</div>
<div class="title">{{ $t("buttons.file") }}</div>
</div>
<div @click="uploadFolder" class="action">
<i class="material-icons">folder</i>
<div class="title">Folder</div>
<div class="title">{{ $t("buttons.folder") }}</div>
</div>
</div>
</div>

View File

@@ -28,7 +28,9 @@ export default {
pt: "pt",
ro: "ro",
ru: "ru",
sk: "sk",
"sv-se": "svSE",
tr: "tr",
"zh-cn": "zhCN",
"zh-tw": "zhTW",
},

View File

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

View File

@@ -110,4 +110,60 @@
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
src: local('Roboto Bold'), local('Roboto-Bold'), url(../assets/fonts/roboto/bold-cyrillic-ext.woff2) format('woff2');
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
src: local('Roboto Bold'), local('Roboto-Bold'), url(../assets/fonts/roboto/bold-cyrillic.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
src: local('Roboto Bold'), local('Roboto-Bold'), url(../assets/fonts/roboto/bold-greek-ext.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
src: local('Roboto Bold'), local('Roboto-Bold'), url(../assets/fonts/roboto/bold-greek.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
src: local('Roboto Bold'), local('Roboto-Bold'), url(../assets/fonts/roboto/bold-vietnamese.woff2) format('woff2');
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
src: local('Roboto Bold'), local('Roboto-Bold'), url(../assets/fonts/roboto/bold-latin-ext.woff2) format('woff2');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
src: local('Roboto Bold'), local('Roboto-Bold'), url(../assets/fonts/roboto/bold-latin.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
}
@import "~material-design-icons/iconfont/material-icons.css";

View File

@@ -84,7 +84,8 @@ header .menu-button {
#search #input {
background-color: #f5f5f5;
display: flex;
padding: 0.75em;
height: 100%;
padding: 0em 0.75em;
border-radius: 0.3em;
transition: .1s ease all;
align-items: center;

View File

@@ -25,6 +25,7 @@
transition: .1s ease background, .1s ease opacity;
align-items: center;
cursor: pointer;
user-select: none;
}
#listing .item div:last-of-type {
@@ -55,6 +56,7 @@
#listing .item img {
width: 4em;
height: 4em;
object-fit: cover;
margin-right: 0.1em;
vertical-align: bottom;
}

View File

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

View File

@@ -92,7 +92,9 @@
"ptBR": "Português (Brasil)",
"ro": "",
"ru": "Русский",
"sk": "Slovenčina",
"svSE": "",
"tr" : "Türkçe",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)"
},

View File

@@ -92,7 +92,9 @@
"ptBR": "Português (Brasil)",
"ro": "",
"ru": "Русский",
"sk": "Slovenčina",
"svSE": "",
"tr" : "Türkçe",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)"
},

View File

@@ -8,6 +8,8 @@
"create": "Create",
"delete": "Delete",
"download": "Download",
"file": "File",
"folder": "Folder",
"hideDotfiles": "Hide dotfiles",
"info": "Info",
"more": "More",
@@ -33,7 +35,8 @@
"switchView": "Switch view",
"toggleSidebar": "Toggle sidebar",
"update": "Update",
"upload": "Upload"
"upload": "Upload",
"openFile": "Open file"
},
"download": {
"downloadFile": "Download File",
@@ -43,7 +46,8 @@
"errors": {
"forbidden": "You don't have permissions to access this.",
"internal": "Something really went wrong.",
"notFound": "This location can't be reached."
"notFound": "This location can't be reached.",
"connection": "The server can't be reached."
},
"files": {
"body": "Body",
@@ -61,7 +65,8 @@
"size": "Size",
"sortByLastModified": "Sort by last modified",
"sortByName": "Sort by name",
"sortBySize": "Sort by size"
"sortBySize": "Sort by size",
"noPreview": "Preview is not available for this file."
},
"help": {
"click": "select file or directory",
@@ -93,7 +98,9 @@
"ptBR": "Português (Brasil)",
"ro": "Romanian",
"ru": "Русский",
"sk": "Slovenčina",
"svSE": "Swedish (Sweden)",
"tr" : "Türkçe",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)"
},
@@ -211,9 +218,11 @@
"rules": "Rules",
"rulesHelp": "Here you can define a set of allow and disallow rules for this specific user. The blocked files won't show up in the listings and they wont be accessible to the user. We support regex and paths relative to the users scope.\n",
"scope": "Scope",
"setDateFormat": "Set exact date format",
"settingsUpdated": "Settings updated!",
"shareDuration": "Share Duration",
"shareManagement": "Share Management",
"shareDeleted": "Share deleted!",
"singleClick": "Use single clicks to open files and directories",
"themes": {
"dark": "Dark",

View File

@@ -92,7 +92,9 @@
"ptBR": "Português (Brasil)",
"ro": "",
"ru": "Русский",
"sk": "Slovenčina",
"svSE": "",
"tr" : "Türkçe",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)"
},

View File

@@ -92,7 +92,9 @@
"ptBR": "Português (Brasil)",
"ro": "",
"ru": "Русский",
"sk": "Slovenčina",
"svSE": "",
"tr" : "Türkçe",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)"
},

View File

@@ -16,6 +16,7 @@ import pt from "./pt.json";
import ptBR from "./pt-br.json";
import ro from "./ro.json";
import ru from "./ru.json";
import sk from "./sk.json";
import svSE from "./sv-se.json";
import zhCN from "./zh-cn.json";
import zhTW from "./zh-tw.json";
@@ -70,6 +71,9 @@ export function detectLocale() {
case /^ko.*/i.test(locale):
locale = "ko";
break;
case /^sk.*/i.test(locale):
locale = "sk";
break;
default:
locale = "en";
}
@@ -107,6 +111,7 @@ const i18n = new VueI18n({
pt: removeEmpty(pt),
ru: removeEmpty(ru),
ro: removeEmpty(ro),
sk: removeEmpty(sk),
"sv-se": removeEmpty(svSE),
"zh-cn": removeEmpty(zhCN),
"zh-tw": removeEmpty(zhTW),

View File

@@ -92,7 +92,9 @@
"ptBR": "Português (Brasil)",
"ro": "",
"ru": "Русский",
"sk": "Slovenčina",
"svSE": "",
"tr" : "Türkçe",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)"
},

View File

@@ -92,7 +92,9 @@
"ptBR": "Português (Brasil)",
"ro": "",
"ru": "Русский",
"sk": "Slovenčina",
"svSE": "",
"tr" : "Türkçe",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)"
},

View File

@@ -92,7 +92,9 @@
"ptBR": "Português (Brasil)",
"ro": "",
"ru": "Русский",
"sk": "Slovenčina",
"svSE": "",
"tr" : "Türkçe",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)"
},

View File

@@ -92,7 +92,9 @@
"ptBR": "Português (Brasil)",
"ro": "",
"ru": "Русский",
"sk": "Slovenčina",
"svSE": "",
"tr" : "Türkçe",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)"
},

View File

@@ -92,7 +92,9 @@
"ptBR": "Portugees (Brazilië)",
"ro": "",
"ru": "Russisch",
"sk": "Slovenčina",
"svSE": "",
"tr" : "Türkçe",
"zhCN": "Chinees (vereenvoudigd)",
"zhTW": "Chinees (traditioneel)"
},

View File

@@ -92,7 +92,9 @@
"ptBR": "Português (Brasil)",
"ro": "",
"ru": "Русский",
"sk": "Slovenčina",
"svSE": "",
"tr" : "Türkçe",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)"
},

View File

@@ -92,7 +92,9 @@
"ptBR": "Português (Brasil)",
"ro": "",
"ru": "Русский",
"sk": "Slovenčina",
"svSE": "",
"tr" : "Türkçe",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)"
},

View File

@@ -92,7 +92,9 @@
"ptBR": "Português (Brasil)",
"ro": "",
"ru": "Russo",
"sk": "Slovenčina",
"svSE": "",
"tr" : "Türkçe",
"zhCN": "Chinês simplificado",
"zhTW": "Chinês tradicional"
},

View File

@@ -92,7 +92,9 @@
"ptBR": "Português (Brasil)",
"ro": "",
"ru": "Русский",
"sk": "Slovenčina",
"svSE": "",
"tr" : "Türkçe",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)"
},

View File

@@ -93,6 +93,7 @@
"ro": "",
"ru": "Русский",
"svSE": "",
"tr" : "Türkçe",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)"
},

265
frontend/src/i18n/sk.json Normal file
View File

@@ -0,0 +1,265 @@
{
"buttons": {
"cancel": "Zrušiť",
"close": "Zavrieť",
"copy": "Kopírovať",
"copyFile": "Kopírovať súbor",
"copyToClipboard": "Kopírovať do schránky",
"create": "Vytvoriť",
"delete": "Odstrániť",
"download": "Stiahnuť",
"file": "Súbor",
"folder": "Priečinok",
"hideDotfiles": "Skryť súbory začínajúce bodkou",
"info": "Info",
"more": "Viac",
"move": "Presunúť",
"moveFile": "Presunúť súbory",
"new": "Nový",
"next": "Ďalšie",
"ok": "OK",
"permalink": "Získať trvalý odkaz",
"previous": "Predošlé",
"publish": "Zverejniť",
"rename": "Premenovať",
"replace": "Nahradiť",
"reportIssue": "Nahlásiť problém",
"save": "Uložiť",
"schedule": "Naplánovať",
"search": "Hľadať",
"select": "Vybrať",
"selectMultiple": "Vybrať viaceré",
"share": "Zdieľať",
"shell": "Prepnúť shell",
"submit": "Poslať",
"switchView": "Prepnúť pohľad",
"toggleSidebar": "Prepnúť sidebar",
"update": "Aktualizovať",
"upload": "Nahrať",
"openFile": "Otvoriť súbor"
},
"download": {
"downloadFile": "Stiahnuť súbor",
"downloadFolder": "Stiahnuť priečinok",
"downloadSelected": "Stiahnuť vybraté"
},
"errors": {
"forbidden": "You don't have permissions to access this.",
"internal": "Something really went wrong.",
"notFound": "This location can't be reached.",
"connection": "The server can't be reached."
},
"files": {
"body": "Telo",
"clear": "Zrušiť výber",
"closePreview": "Zavrieť náhľad",
"files": "Súbory",
"folders": "Priečinky",
"home": "Domov",
"lastModified": "Posledná zmena",
"loading": "Načítanie...",
"lonely": "Je tu tak pusto...",
"metadata": "Metadata",
"multipleSelectionEnabled": "Zapnutý viacnásobný výber",
"name": "Názov",
"size": "Veľkosť",
"sortByLastModified": "Zoradiť podľa dátumu",
"sortByName": "Zoradiť podľa názvu",
"sortBySize": "Zoradiť podľa veľkosti",
"noPreview": "Pre tento súbor nie je dostupný náhľad."
},
"help": {
"click": "vyberie súbor alebo priečinok",
"ctrl": {
"click": "vyberie viac súborov alebo priečinkov",
"f": "otvorí vyhľadávanie",
"s": "uloží súbor alebo stiahne priečinok tam kde ste"
},
"del": "odstráni vybraté položky",
"doubleClick": "otvorí súbor alebo priečinok",
"esc": "zruší výber a/alebo zavrie okno",
"f1": "tieto informácie",
"f2": "premenuje súbor",
"help": "Pomoc"
},
"languages": {
"ar": "العربية",
"de": "Deutsch",
"en": "English",
"es": "Español",
"fr": "Français",
"is": "Icelandic",
"it": "Italiano",
"ja": "日本語",
"ko": "한국어",
"nlBE": "Dutch (Belgium)",
"pl": "Polski",
"pt": "Português",
"ptBR": "Português (Brasil)",
"ro": "Romanian",
"ru": "Русский",
"sk": "Slovenčina",
"svSE": "Swedish (Sweden)",
"tr" : "Türkçe",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)"
},
"login": {
"createAnAccount": "Vytvoriť účet",
"loginInstead": "Už mám účet",
"password": "Heslo",
"passwordConfirm": "Potvrdenie hesla",
"passwordsDontMatch": "Heslá nesúhlasia",
"signup": "Registrovať",
"submit": "Prihlásiť",
"username": "Používateľské meno",
"usernameTaken": "Meno je už obsadené",
"wrongCredentials": "Nesprávne prihlasovacie údaje"
},
"permanent": "Trvalé",
"prompts": {
"copy": "Kopírovať",
"copyMessage": "Zvoľte miesto, kde chcete kopírovať súbory:",
"currentlyNavigating": "Aktuálna cesta:",
"deleteMessageMultiple": "Naozaj chcete odstrániť {count} súbor(ov)?",
"deleteMessageSingle": "Naozaj chcete odstrániť tento súbor/priečinok?",
"deleteMessageShare": "Naozaj chcete odstrániť toto zdieľanie({path})?",
"deleteTitle": "Odstránenie súborov",
"displayName": "Zobrazený názov:",
"download": "Stiahnuť súbory",
"downloadMessage": "Vyberte formát, ktorý chcete stiahnuť.",
"error": "Niečo sa pokazilo",
"fileInfo": "Informácie o súbore",
"filesSelected": "{count} súborov vybratých.",
"lastModified": "Dátum zmeny",
"move": "Presunúť",
"moveMessage": "Zvoľte nový domov pre vaše súbory/priečinky:",
"newArchetype": "Vytvorí nový príspevok z archetypu. Nový súbor sa vytvorí v priečinku s obsahom.",
"newDir": "Nový priečinok",
"newDirMessage": "Napíšte názov nového priečinka.",
"newFile": "Nový súbor",
"newFileMessage": "Napíšte názov nového súboru.",
"numberDirs": "Počet priečinkov",
"numberFiles": "Počet súborov",
"rename": "Premenovať",
"renameMessage": "Zadajte nový názov pre",
"replace": "Nahradiť",
"replaceMessage": "Niektorý nahrávaný súbor je v konflikte názvov. Chcete nahradiť existujúci súbor?\n",
"schedule": "Naplánovať",
"scheduleMessage": "Pick a date and time to schedule the publication of this post.",
"show": "Zobraziť",
"size": "Veľkosť",
"upload": "Nahrať",
"uploadMessage": "Zvoľte možnosť nahrávania.",
"optionalPassword": "Voliteľné heslo"
},
"search": {
"images": "Obrázky",
"music": "Hudba",
"pdf": "PDF",
"pressToSearch": "Vyhľadáte stlačením Enter...",
"search": "Hľadať...",
"typeToSearch": "Vyhľadáte písaním...",
"types": "Typy",
"video": "Video"
},
"settings": {
"admin": "Admin",
"administrator": "Administrátor",
"allowCommands": "Vykonávať príkazy",
"allowEdit": "Upravovať, premenovať a odstraňovať súbory a priečinky",
"allowNew": "Vytvárať nové súbory a priečinky",
"allowPublish": "Zverejňovať nové príspevky a stránky",
"allowSignup": "Povoliť registráciu používateľov",
"avoidChanges": "(nechajte prázdne, aby sa nezmenilo)",
"branding": "Vlastný vzhľad",
"brandingDirectoryPath": "Cesta k priečinku s vlastným vzhľadom",
"brandingHelp": "Môžete si prispôsobiť ako bude vyzerá váš File Browser instance zmenou jeho názvu, výmenou loga a pridaním vlastný štýlov alebo vypnutím externých odkazov na GitHub.\nViac informácií o vlastnom vzhľade nájdete na {0}.",
"changePassword": "Zmeniť heslo",
"commandRunner": "Spúšťač príkazov",
"commandRunnerHelp": "Sem môžete nastaviť príkazy, ktoré sa vykonajú pri určitých udalostiach. Musíte písať jeden na riadok. Premenné prostredia {0} a {1} sú k dispozícii, s tým že {0} relatívne k {1}. Viac informácií o tejto funkcionalite a dostupných premenných prostredia nájdete na {2}.",
"commandsUpdated": "Príkazy upravené!",
"createUserDir": "Automaticky vytvoriť domovský priečinok pri pridaní používateľa",
"customStylesheet": "Vlastný Stylesheet",
"defaultUserDescription": "Toto sú predvolané nastavenia nového používateľa.",
"disableExternalLinks": "Vypnúť externé odkazy (okrem dokumentácie)",
"documentation": "dokumentácia",
"examples": "Príklady",
"executeOnShell": "Vykonať cez shell",
"executeOnShellDescription": "Predvolene File Browser vykonáva príkazy volaním priamo ich binárok. Ak ich chcete spúšťať cez shell (napr. Bash alebo PowerShell), môžete ho napísať sem a pridať potrebné argumenty a flagy. Ak je nastavený, tak sa príkazy budú spúšťať pridaním na koniec ako argument. Toto sa týka používateľských príkazov aj udalostí.",
"globalRules": "Toto je globálne nastavenie pravidiel. Aplikujú sa na všetkých používateľov. Môžete definovať špecifické pravidlá pre každého používateľa a prekryť tak pravidlá nastavené tu.",
"globalSettings": "Globálne nastavenia",
"hideDotfiles": "Skryť súroby začínajúce bodkou",
"insertPath": "Vložte cestu",
"insertRegex": "Vložte regex výraz",
"instanceName": "Názov inštalácie",
"language": "Jazyk",
"lockPassword": "Zabrániť používateľovi meniť heslo",
"newPassword": "Nové heslo",
"newPasswordConfirm": "Potvrenie nového hesla",
"newUser": "Nový používateľ",
"password": "Heslo",
"passwordUpdated": "Heslo zmenené!",
"path": "Cesta",
"perm": {
"create": "Vytvárať súbory a priečinky",
"delete": "Odstraňovať súbory a priečinky",
"download": "Stiahnuť",
"execute": "Vykonávať príkazy",
"modify": "Upravovať súbory",
"rename": "Premenovať a presúvať súbory a priečinky",
"share": "Zdieľať súbory"
},
"permissions": "Práva",
"permissionsHelp": "Môžete nastaviť používateľa, aby bol administrátorom alebo vybrať práva jednotlivo. Ak zvolíte \"Administrator\", všetky ďalši budú automaticky zaškrtnuté. Manažment používateľov ostáva v správe administrátora.\n",
"profileSettings": "Nastavenia profilu",
"ruleExample1": "blokuje prístup ku všetkým súborom začínajúcim bodkou (napríklad .git, .gitignore) v každom priečinku.\n",
"ruleExample2": "blokuje prístup k súborom s názvom Caddyfile v koreňovom priečinku.",
"rules": "Pravidlá",
"rulesHelp": "Tu môžete definovať pravidilá pre konkrétneho používateľa. Blokované súbory používateľ nebude vidieť a ani nebude k nim mať prístup. Podporujeme regex a cesty relatívne k používateľovi.\n",
"scope": "Scope",
"settingsUpdated": "Nastavenia upravené!",
"shareDuration": "Trvanie zdieľania",
"shareManagement": "Správa zdieľania",
"shareDeleted": "Zdieľanie odstránené!",
"singleClick": "Používať jeden klik na otváranie súborov a priečinkov",
"themes": {
"dark": "Tmavá",
"light": "Svetlá",
"title": "Téma"
},
"user": "Používateľ",
"userCommands": "Príkazy",
"userCommandsHelp": "Zoznam povolených príkazov oddelených medzerou pre tohoto používateľa. Napríklad:\n",
"userCreated": "Používateľ vytvorený!",
"userDefaults": "Predovolené nastavenia používateľa",
"userDeleted": "Používateľ odstránený!",
"userManagement": "Správa používateľov",
"userUpdated": "Používateľ upravený!",
"username": "Meno používateľa",
"users": "Používatelia"
},
"sidebar": {
"help": "Pomoc",
"hugoNew": "Nový Hugo",
"login": "Prihlásiť",
"logout": "Odhlásiť",
"myFiles": "Moje súbory",
"newFile": "Nový súbor",
"newFolder": "Nový priečinok",
"preview": "Náhľad",
"settings": "Nastavenia",
"signup": "Registrovať",
"siteSettings": "Nastavenia stránky"
},
"success": {
"linkCopied": "Odkaz skopírovaný!"
},
"time": {
"days": "Dni",
"hours": "Hodiny",
"minutes": "Minúty",
"seconds": "Sekundy",
"unit": "Jednotka času"
}
}

View File

@@ -92,7 +92,9 @@
"ptBR": "Português (Brasil)",
"ro": "",
"ru": "Русский",
"sk": "Slovenčina",
"svSE": "",
"tr" : "Türkçe",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)"
},

263
frontend/src/i18n/tr.json Normal file
View File

@@ -0,0 +1,263 @@
{
"buttons": {
"cancel": "Vazgeç",
"close": "Kapat",
"copy": "Kopyala",
"copyFile": "Dosyayı kopyala",
"copyToClipboard": "Panoya kopyala",
"create": "Oluştur",
"delete": "Sil",
"download": "İndir",
"hideDotfiles": "Nokta dosyalarını gizle",
"info": "Bilgi",
"more": "Daha fazla",
"move": "Taşı",
"moveFile": "Dosyayı taşı",
"new": "Yeni",
"next": "Sonraki",
"ok": "Tamam",
"permalink": "Kalıcı Bağlantı Alın",
"previous": "Önceki",
"publish": "Yayınla",
"rename": "Yeniden anlandır",
"replace": "Değiştir",
"reportIssue": "Sorun bildir",
"save": "Kaydet",
"schedule": "Planla",
"search": "Ara",
"select": "Seç",
"selectMultiple": "Çoklu seçim",
"share": "Paylaş",
"shell": "Komut satırı aç/kapat",
"submit": "Gönder",
"switchView": "Görünümü değiştir",
"toggleSidebar": "Menüyü aç/kapat",
"update": "Güncelle",
"upload": "Yükle",
"openFile": "Dosyayı aç"
},
"download": {
"downloadFile": "Dosyayı indir",
"downloadFolder": "Klasörü indir",
"downloadSelected": "Seçilileri indir"
},
"errors": {
"forbidden": "Buna erişim izniniz yok.",
"internal": "Bir şeyler ters gitti.",
"notFound": "Bu konuma ulaşılamıyor.",
"connection": "Sunucuya ulaşılamıyor."
},
"files": {
"body": "Sayfa",
"clear": "Temizle",
"closePreview": "Önizlemeyi kapat",
"files": "Dosyalar",
"folders": "Klasörler",
"home": "Ana dizin",
"lastModified": "Son güncellenme",
"loading": "Yükleniyor...",
"lonely": "Burada yalnızlık hissediyorum...",
"metadata": "meta veri",
"multipleSelectionEnabled": "Çoklu seçim etkin",
"name": "İsim",
"size": "Boyut",
"sortByLastModified": "Güncelleme tarihine göre sırala",
"sortByName": "İsme göre sırala",
"sortBySize": "Boyuta göre sırala",
"noPreview": "Bu dosya için önizleme aktif değil"
},
"help": {
"click": "dosya veya klasör seçin",
"ctrl": {
"click": "çoklu dosya ve klasör seçin",
"f": "Aramayı aç",
"s": "bir dosyayı kaydedin veya bulunduğunuz dizini indirin"
},
"del": "seçilileri sil",
"doubleClick": "dosya veya dizini açın",
"esc": "seçimi temizle veya kapatın",
"f1": "bu bilgi",
"f2": "dosyayı yeniden adlandır",
"help": "Yardım"
},
"languages": {
"ar": "العربية",
"de": "Deutsch",
"en": "English",
"es": "Español",
"fr": "Français",
"is": "Icelandic",
"it": "Italiano",
"ja": "日本語",
"ko": "한국어",
"nlBE": "Dutch (Belgium)",
"pl": "Polski",
"pt": "Português",
"ptBR": "Português (Brasil)",
"ro": "Romanian",
"ru": "Русский",
"sk": "Slovenčina",
"svSE": "Swedish (Sweden)",
"tr" : "Türkçe",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)"
},
"login": {
"createAnAccount": "Bir hesap oluşturun",
"loginInstead": "Zaten hesabınız var mı",
"password": "Şifre",
"passwordConfirm": "Şifre tekrarı",
"passwordsDontMatch": "Şifreler uyuşmuyor",
"signup": "Üye Ol",
"submit": "Giriş",
"username": "Kullanıcı adı",
"usernameTaken": "Kullanıcı adı mevcut",
"wrongCredentials": "Yanlış hesap bilgileri"
},
"permanent": "Kalıcı",
"prompts": {
"copy": "Kopyala",
"copyMessage": "Dosyalarınızı kopyalayacağınız yeri seçin:",
"currentlyNavigating": "Şu anki lokasyon:",
"deleteMessageMultiple": "{count} dosyayı/dosyaları silmek istediğinizden emin misiniz?",
"deleteMessageSingle": "Bu dosyayı/klasörü silmek istediğinizden emin misiniz?",
"deleteMessageShare": "Bu paylaşımı({path}) silmek istediğinizden emin misiniz?",
"deleteTitle": "Dosyaları sil",
"displayName": "Görünen Ad:",
"download": "Dosyaları indirŞ",
"downloadMessage": "İndirmek istediğiniz formatı seçin.",
"error": "Bir şeyler yanlış gitti",
"fileInfo": "Dosya bilgisi",
"filesSelected": "{count} dosya seçildi.",
"lastModified": "Son güncellenme",
"move": "Taşı",
"moveMessage": "Dosya(lar)ınız/klasör(ler)iniz için yeni ana dizin seçin:",
"newArchetype": "Bir prototip temelinde yeni bir gönderi oluşturun. Dosyanız içerik klasöründe oluşturulacaktır.",
"newDir": "Yeni dizin",
"newDirMessage": "Yeni dizinin adını yazın.",
"newFile": "Yeni dosya",
"newFileMessage": "Yeni dosyanın adını yazın.",
"numberDirs": "Dizin sayısı",
"numberFiles": "Dosya sayısı",
"rename": "Yeniden adlandır",
"renameMessage": "için yeni bir ad girin",
"replace": "Değiştir",
"replaceMessage": "Yüklemeye çalıştığınız dosyalardan biri, adı nedeniyle çakışıyor. Mevcut olanı değiştirmek istiyor musunuz?\n",
"schedule": "Planla",
"scheduleMessage": "Bu paylaşımın yayınlanmasını planlamak için bir tarih ve saat seçin.",
"show": "Göster",
"size": "Boyut",
"upload": "Gönder",
"uploadMessage": "Yüklemek için bir seçenek belirleyin.",
"optionalPassword": "İsteğe bağlı şifre"
},
"search": {
"images": "Görseller",
"music": "Müzik",
"pdf": "PDF",
"pressToSearch": "Aramak için enter'a basın...",
"search": "Ara...",
"typeToSearch": "Aramak için yazın...",
"types": "Türler",
"video": "Video"
},
"settings": {
"admin": "Yönetim",
"administrator": "Yönetici",
"allowCommands": "Komutları çalıştır",
"allowEdit": "Dosyaları veya dizinleri düzenleyin, yeniden adlandırın ve silin",
"allowNew": "Yeni dosyalar ve dizinler oluşturun",
"allowPublish": "Yeni linkler ve sayfaları yayınlayın",
"allowSignup": "Kullanıcıların kaydolmasına izin ver",
"avoidChanges": "(değişiklikleri önlemek için boş bırakın)",
"branding": "Marka",
"brandingDirectoryPath": "Marka dizin yolu",
"brandingHelp": "Adını değiştirerek, logoyu değiştirerek, özel stiller ekleyerek ve hatta GitHub'a harici bağlantıları devre dışı bırakarak Filebrowser örneğinizin görünüşünü ve hissini özelleştirebilirsiniz.\nÖzel marka bilinci oluşturma hakkında daha fazla bilgi için lütfen {0} sayfasına göz atın.",
"changePassword": "Şifre Değiştir",
"commandRunner": "Komut satırı",
"commandRunnerHelp": "Burada, adlandırılmış olaylarda yürütülen komutları ayarlayabilirsiniz. Her satıra bir tane yazmalısınız. {0} ve {1} ortam değişkenleri, {1}'ye göre {0} olacak şekilde kullanılabilir olacaktır. Bu özellik ve mevcut ortam değişkenleri hakkında daha fazla bilgi için lütfen {2}'yi okuyun.",
"commandsUpdated": "Komutlar güncellendi!",
"createUserDir": "Kullanıcı eklerken, kullanıcı ana dizinini otomatik oluştur",
"customStylesheet": "Özel CSS",
"defaultUserDescription": "Bu, yeni kullanıcılar için varsayılan ayarlardır.",
"disableExternalLinks": "Harici bağlantıları devre dışı bırakın (dökümantasyon hariç)",
"documentation": "dökümantasyon",
"examples": "Örnekler",
"executeOnShell": "Komut satırında çalıştır",
"executeOnShellDescription": "Varsayılan olarak, FileBrowser komutları doğrudan dosyaları çağırarak yürütür. Bunları komut satırında çalıştırmak istiyorsanız (Bash veya PowerShell gibi), burada gerekli argümanlar ve flagler tanımlayabilirsiniz. Ayarlanırsa, yürüttüğünüz komut argüman olarak eklenir. Bu, hem kullanıcı komutları hem de event hooklar için geçerlidir.",
"globalRules": "Bu, genel bir izin verme ve izin vermeme kurallar bütünüdür. Her kullanıcı için geçerlidirler. Bunları geçersiz kılmak için her kullanıcının ayarlarında belirli kurallar tanımlayabilirsiniz.",
"globalSettings": "Genel Ayarlar",
"hideDotfiles": ". ile başlayan dosyaları gizle",
"insertPath": "Dizini ekle",
"insertRegex": "Regex ifadesini ekle",
"instanceName": "Instance adı",
"language": "Dil",
"lockPassword": "Kullanıcının parolayı değiştirmesini engelle",
"newPassword": "Yeni şifre",
"newPasswordConfirm": "Yeni şifre tekrarı",
"newUser": "Yeni Kullanıcı",
"password": "Şifre",
"passwordUpdated": "Şifre güncellendi",
"path": "Yol",
"perm": {
"create": "Dosyalar ve dizinler oluşturun",
"delete": "Dosyalar ve dizinleri silin",
"download": "İndir",
"execute": "Komutları çalıştır",
"modify": "Dosyaları değiştir",
"rename": "Dosyaları ve dizinleri yeniden adlandırın veya taşıyın",
"share": "Dosyaları paylaş"
},
"permissions": "İzinler",
"permissionsHelp": "Kullanıcıyı yönetici olarak ayarlayabilir veya izinleri ayrı ayrı seçebilirsiniz. \"Yönetici\"yi seçerseniz, diğer tüm seçenekler otomatik olarak kontrol edilecektir. Kullanıcıların yönetimi, bir yöneticinin yetkisi olarak kalır.\n",
"profileSettings": "Profil ayarları",
"ruleExample1": "her klasördeki herhangi bir noktalı dosyaya (.git, .gitignore gibi) erişimi engeller.\n",
"ruleExample2": "Root erişimidenki CaddyFile dosyalarına erişimi engelle.",
"rules": "Kurallar",
"rulesHelp": "Burada, bu belirli kullanıcı için bir dizi izin verme ve izin vermeme kuralı tanımlayabilirsiniz. Engellenen dosyalar listelerde görünmeyecek ve kullanıcı bunlara erişemeyecek. Kullanıcı erişimine göre regex ifadeleri destekliyoruz.\n",
"scope": "Kapsam",
"settingsUpdated": "Ayarlar güncellendi!",
"shareDuration": "Paylaşım süresi",
"shareManagement": "Paylaşım yönetimi",
"shareDeleted": "Paylaşım silindi!",
"singleClick": "Dosyaları ve dizinleri açmak için tek tıklamayı kullanın",
"themes": {
"dark": "Dark",
"light": "Light",
"title": "Theme"
},
"user": "Kullanıcı",
"userCommands": "Komutları",
"userCommandsHelp": "Bu kullanıcı için mevcut komutları içeren boşlukla ayrılmış bir liste. Örnek:\n",
"userCreated": "Kullanıcı oluşturuldu!",
"userDefaults": "Kullanıcı varsayılan ayarları",
"userDeleted": "Kullanıcı silindi!",
"userManagement": "Kullanıcı yönetimi",
"userUpdated": "Kullanıcı güncellendi!",
"username": "Kullanıcı adı",
"users": "Kullanıcılar"
},
"sidebar": {
"help": "Yardım",
"hugoNew": "Yeni Hugo",
"login": "Giriş",
"logout": ıkış",
"myFiles": "Dosyalarım",
"newFile": "Yeni dosya",
"newFolder": "Yeni klasör",
"preview": "Önizleme",
"settings": "Ayarlar",
"signup": "Kayıt",
"siteSettings": "Site ayarları!"
},
"success": {
"linkCopied": "Link kopyalandı!"
},
"time": {
"days": "Gün",
"hours": "Saat",
"minutes": "Dakika",
"seconds": "Saniye",
"unit": "Zaman birimi"
}
}

View File

@@ -93,7 +93,9 @@
"ptBR": "PortuguêsBrasil",
"ro": "Romanian",
"ru": "Русский",
"sk": "Slovenčina",
"svSE": "SwedishSweden",
"tr" : "Türkçe",
"zhCN": "中文(简体)",
"zhTW": "中文(繁體)"
},

View File

@@ -92,7 +92,9 @@
"ptBR": "Português (Brasil)",
"ro": "Romanian",
"ru": "Русский",
"sk": "Slovenčina",
"svSE": "SwedishSweden",
"tr" : "Türkçe",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)"
},

View File

@@ -1,3 +1,5 @@
import "whatwg-fetch";
import cssVars from "css-vars-ponyfill";
import { sync } from "vuex-router-sync";
import store from "@/store";
import router from "@/router";
@@ -7,13 +9,19 @@ import { recaptcha, loginPage } from "@/utils/constants";
import { login, validateLogin } from "@/utils/auth";
import App from "@/App";
cssVars();
sync(store, router);
async function start() {
if (loginPage) {
await validateLogin();
} else {
await login("", "", "");
try {
if (loginPage) {
await validateLogin();
} else {
await login("", "", "");
}
} catch (e) {
console.log(e);
}
if (recaptcha) {

View File

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

View File

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

View File

@@ -1,13 +1,18 @@
<template>
<div>
<header-bar v-if="error || !req.type" showMenu showLogo />
<header-bar v-if="error || req.type == null" showMenu showLogo />
<breadcrumbs base="/files" />
<errors v-if="error" :errorCode="errorCode" />
<errors v-if="error" :errorCode="error.message" />
<component v-else-if="currentView" :is="currentView"></component>
<div v-else>
<h2 class="message">
<h2 class="message delayed">
<div class="spinner">
<div class="bounce1"></div>
<div class="bounce2"></div>
<div class="bounce3"></div>
</div>
<span>{{ $t("files.loading") }}</span>
</h2>
</div>
@@ -62,11 +67,6 @@ export default {
return "preview";
}
},
errorCode() {
return this.error.message === "404" || this.error.message === "403"
? parseInt(this.error.message)
: 500;
},
},
created() {
this.fetchData();
@@ -116,7 +116,7 @@ export default {
}
this.$store.commit("updateRequest", res);
document.title = res.name;
document.title = `${res.name} - ${this.$route.name}`;
} catch (e) {
this.error = e;
} finally {
@@ -124,15 +124,6 @@ export default {
}
},
keyEvent(event) {
if (this.show !== null) {
// Esc!
if (event.keyCode === 27) {
this.$store.commit("closeHovers");
}
return;
}
// F1!
if (event.keyCode === 112) {
event.preventDefault();

View File

@@ -10,26 +10,22 @@
{{ $t("settings.profileSettings") }}
</li></router-link
>
<router-link to="/settings/shares"
<router-link to="/settings/shares" v-if="user.perm.share"
><li :class="{ active: $route.path === '/settings/shares' }">
{{ $t("settings.shareManagement") }}
</li></router-link
>
<router-link to="/settings/global"
><li
:class="{ active: $route.path === '/settings/global' }"
v-if="user.perm.admin"
>
<router-link to="/settings/global" v-if="user.perm.admin"
><li :class="{ active: $route.path === '/settings/global' }">
{{ $t("settings.globalSettings") }}
</li></router-link
>
<router-link to="/settings/users"
<router-link to="/settings/users" v-if="user.perm.admin"
><li
:class="{
active:
$route.path === '/settings/users' || $route.name === 'User',
}"
v-if="user.perm.admin"
>
{{ $t("settings.userManagement") }}
</li></router-link
@@ -38,6 +34,17 @@
</div>
</div>
<div v-if="loading">
<h2 class="message delayed">
<div class="spinner">
<div class="bounce1"></div>
<div class="bounce2"></div>
<div class="bounce3"></div>
</div>
<span>{{ $t("files.loading") }}</span>
</h2>
</div>
<router-view></router-view>
</div>
</template>
@@ -53,7 +60,7 @@ export default {
HeaderBar,
},
computed: {
...mapState(["user"]),
...mapState(["user", "loading"]),
},
};
</script>

View File

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

View File

@@ -4,14 +4,13 @@
<action icon="close" :label="$t('buttons.close')" @action="close()" />
<title>{{ req.name }}</title>
<template #actions>
<action
id="save-button"
icon="save"
:label="$t('buttons.save')"
@action="save()"
/>
</template>
<action
v-if="user.perm.modify"
id="save-button"
icon="save"
:label="$t('buttons.save')"
@action="save()"
/>
</header-bar>
<breadcrumbs base="/files" noLink />

View File

@@ -25,18 +25,21 @@
/>
<action
v-if="headerButtons.copy"
id="copy-button"
icon="content_copy"
:label="$t('buttons.copyFile')"
show="copy"
/>
<action
v-if="headerButtons.move"
id="move-button"
icon="forward"
:label="$t('buttons.moveFile')"
show="move"
/>
<action
v-if="headerButtons.delete"
id="delete-button"
icon="delete"
:label="$t('buttons.delete')"
show="delete"
@@ -55,13 +58,16 @@
@action="switchView"
/>
<action
v-if="headerButtons.download"
icon="file_download"
:label="$t('buttons.download')"
@action="download"
:counter="selectedCount"
/>
<action
v-if="headerButtons.upload"
icon="file_upload"
id="upload-button"
:label="$t('buttons.upload')"
@action="upload"
/>
@@ -108,8 +114,13 @@
/>
</div>
<div v-if="$store.state.loading">
<h2 class="message">
<div v-if="loading">
<h2 class="message delayed">
<div class="spinner">
<div class="bounce1"></div>
<div class="bounce2"></div>
<div class="bounce3"></div>
</div>
<span>{{ $t("files.loading") }}</span>
</h2>
</div>
@@ -135,7 +146,7 @@
multiple
/>
</div>
<div v-else id="listing" :class="user.viewMode">
<div v-else id="listing" ref="listing" :class="user.viewMode">
<div>
<div class="item header">
<div></div>
@@ -248,11 +259,13 @@
</template>
<script>
import Vue from "vue";
import { mapState, mapGetters, mapMutations } from "vuex";
import { users, files as api } from "@/api";
import { enableExec } from "@/utils/constants";
import * as upload from "@/utils/upload";
import css from "@/utils/css";
import throttle from "lodash.throttle";
import HeaderBar from "@/components/header/HeaderBar";
import Action from "@/components/header/Action";
@@ -272,10 +285,19 @@ export default {
showLimit: 50,
dragCounter: 0,
width: window.innerWidth,
itemWeight: 0,
};
},
computed: {
...mapState(["req", "selected", "user", "show", "multiple", "selected"]),
...mapState([
"req",
"selected",
"user",
"show",
"multiple",
"selected",
"loading",
]),
...mapGetters(["selectedCount"]),
nameSorted() {
return this.req.sorting.by === "name";
@@ -350,15 +372,37 @@ export default {
return this.width <= 736;
},
},
watch: {
req: function () {
// Reset the show value
this.showLimit = 50;
// Ensures that the listing is displayed
Vue.nextTick(() => {
// How much every listing item affects the window height
this.setItemWeight();
// Fill and fit the window with listing items
this.fillWindow(true);
});
},
},
mounted: function () {
// Check the columns size for the first time.
this.resizeEvent();
this.colunmsResize();
// How much every listing item affects the window height
this.setItemWeight();
// Fill and fit the window with listing items
this.fillWindow(true);
// Add the needed event listeners to the window and document.
window.addEventListener("keydown", this.keyEvent);
window.addEventListener("resize", this.resizeEvent);
window.addEventListener("scroll", this.scrollEvent);
window.addEventListener("resize", this.windowsResize);
if (!this.user.perm.create) return;
document.addEventListener("dragover", this.preventDefault);
document.addEventListener("dragenter", this.dragEnter);
document.addEventListener("dragleave", this.dragLeave);
@@ -367,9 +411,10 @@ export default {
beforeDestroy() {
// Remove event listeners before destroying this page.
window.removeEventListener("keydown", this.keyEvent);
window.removeEventListener("resize", this.resizeEvent);
window.removeEventListener("scroll", this.scrollEvent);
window.removeEventListener("resize", this.windowsResize);
if (this.user && !this.user.perm.create) return;
document.removeEventListener("dragover", this.preventDefault);
document.removeEventListener("dragenter", this.dragEnter);
document.removeEventListener("dragleave", this.dragLeave);
@@ -460,7 +505,7 @@ export default {
for (let i of this.selected) {
items.push({
from: this.req.items[i].url,
name: encodeURIComponent(this.req.items[i].name),
name: this.req.items[i].name,
});
}
@@ -485,7 +530,7 @@ export default {
const from = item.from.endsWith("/")
? item.from.slice(0, -1)
: item.from;
const to = this.$route.path + item.name;
const to = this.$route.path + encodeURIComponent(item.name);
items.push({ from, to, name: item.name });
}
@@ -543,7 +588,7 @@ export default {
action(overwrite, rename);
},
resizeEvent() {
colunmsResize() {
// Update the columns size based on the window width.
let columns = Math.floor(
document.querySelector("main").offsetWidth / 300
@@ -552,11 +597,27 @@ export default {
if (columns === 0) columns = 1;
items.style.width = `calc(${100 / columns}% - 1em)`;
},
scrollEvent() {
if (window.innerHeight + window.scrollY >= document.body.offsetHeight) {
this.showLimit += 50;
scrollEvent: throttle(function () {
const totalItems = this.req.numDirs + this.req.numFiles;
// All items are displayed
if (this.showLimit >= totalItems) return;
const currentPos = window.innerHeight + window.scrollY;
// Trigger at the 75% of the window height
const triggerPos = document.body.offsetHeight - window.innerHeight * 0.25;
if (currentPos > triggerPos) {
// Quantity of items needed to fill 2x of the window height
const showQuantity = Math.ceil(
(window.innerHeight * 2) / this.itemWeight
);
// Increase the number of displayed items
this.showLimit += showQuantity;
}
},
}, 100),
dragEnter() {
this.dragCounter++;
@@ -591,22 +652,20 @@ export default {
}
}
let base = "";
let files = await upload.scanFiles(dt);
let items = this.req.items;
let path = this.$route.path.endsWith("/")
? this.$route.path
: this.$route.path + "/";
if (
el !== null &&
el.classList.contains("item") &&
el.dataset.dir === "true"
) {
base = el.querySelector(".name").innerHTML + "/";
}
// Get url from ListingItem instance
path = el.__vue__.url;
let files = await upload.scanFiles(dt);
let path = this.$route.path.endsWith("/")
? this.$route.path + base
: this.$route.path + "/" + base;
let items = this.req.items;
if (base !== "") {
try {
items = (await api.fetch(path)).items;
} catch (error) {
@@ -707,9 +766,19 @@ export default {
this.$store.commit("multiple", !this.multiple);
this.$store.commit("closeHovers");
},
windowsResize() {
windowsResize: throttle(function () {
this.colunmsResize();
this.width = window.innerWidth;
},
// Listing element is not displayed
if (this.$refs.listing == null) return;
// How much every listing item affects the window height
this.setItemWeight();
// Fill but not fit the window
this.fillWindow();
}, 100),
download() {
if (this.selectedCount === 1 && !this.req.items[this.selected[0]].isDir) {
api.download(null, this.req.items[this.selected[0]].url);
@@ -743,20 +812,53 @@ export default {
viewMode: this.user.viewMode === "mosaic" ? "list" : "mosaic",
};
try {
await users.update(data, ["viewMode"]);
this.$store.commit("updateUser", data);
} catch (e) {
this.$showError(e);
}
users.update(data, ["viewMode"]).catch(this.$showError);
// Await ensures correct value for setItemWeight()
await this.$store.commit("updateUser", data);
this.setItemWeight();
this.fillWindow();
},
upload: function () {
if (typeof DataTransferItem.prototype.webkitGetAsEntry !== "undefined") {
if (
typeof window.DataTransferItem !== "undefined" &&
typeof DataTransferItem.prototype.webkitGetAsEntry !== "undefined"
) {
this.$store.commit("showHover", "upload");
} else {
document.getElementById("upload-input").click();
}
},
setItemWeight() {
// Listing element is not displayed
if (this.$refs.listing == null) return;
let itemQuantity = this.req.numDirs + this.req.numFiles;
if (itemQuantity > this.showLimit) itemQuantity = this.showLimit;
// How much every listing item affects the window height
this.itemWeight = this.$refs.listing.offsetHeight / itemQuantity;
},
fillWindow(fit = false) {
const totalItems = this.req.numDirs + this.req.numFiles;
// More items are displayed than the total
if (this.showLimit >= totalItems && !fit) return;
const windowHeight = window.innerHeight;
// Quantity of items needed to fill 2x of the window height
const showQuantity = Math.ceil(
(windowHeight + windowHeight * 2) / this.itemWeight
);
// Less items to display than current
if (this.showLimit > showQuantity && !fit) return;
// Set the number of displayed items
this.showLimit = showQuantity > totalItems ? totalItems : showQuantity;
},
},
};
</script>

View File

@@ -17,12 +17,14 @@
<template #actions>
<action
:disabled="loading"
v-if="user.perm.rename"
icon="mode_edit"
:label="$t('buttons.rename')"
show="rename"
/>
<action
:disabled="loading"
v-if="user.perm.delete"
icon="delete"
:label="$t('buttons.delete')"
@action="deleteFile"
@@ -30,6 +32,7 @@
/>
<action
:disabled="loading"
v-if="user.perm.download"
icon="file_download"
:label="$t('buttons.download')"
@action="download"
@@ -43,19 +46,32 @@
</template>
</header-bar>
<div class="loading" v-if="loading">
<div class="loading delayed" v-if="loading">
<div class="spinner">
<div class="bounce1"></div>
<div class="bounce2"></div>
<div class="bounce3"></div>
</div>
</div>
<template v-if="!loading">
<template v-else>
<div class="preview">
<ExtendedImage v-if="req.type == 'image'" :src="raw"></ExtendedImage>
<audio v-else-if="req.type == 'audio'" :src="raw" controls></audio>
<video v-else-if="req.type == 'video'" :src="raw" controls>
<audio
v-else-if="req.type == 'audio'"
ref="player"
:src="raw"
controls
:autoplay="autoPlay"
@play="autoPlay = true"
></audio>
<video
v-else-if="req.type == 'video'"
ref="player"
:src="raw"
controls
:autoplay="autoPlay"
@play="autoPlay = true"
>
<track
kind="captions"
v-for="(sub, index) in subtitles"
@@ -73,12 +89,31 @@
class="pdf"
:data="raw"
></object>
<a v-else-if="req.type == 'blob'" :href="downloadUrl">
<h2 class="message">
{{ $t("buttons.download") }}
<i class="material-icons">file_download</i>
</h2>
</a>
<div v-else-if="req.type == 'blob'" class="info">
<div class="title">
<i class="material-icons">feedback</i>
{{ $t("files.noPreview") }}
</div>
<div>
<a target="_blank" :href="downloadUrl" class="button button--flat">
<div>
<i class="material-icons">file_download</i
>{{ $t("buttons.download") }}
</div>
</a>
<a
target="_blank"
:href="downloadUrl + '&inline=true'"
class="button button--flat"
v-if="!req.isDir"
>
<div>
<i class="material-icons">open_in_new</i
>{{ $t("buttons.openFile") }}
</div>
</a>
</div>
</div>
</div>
</template>
@@ -136,6 +171,7 @@ export default {
showNav: true,
navTimeout: null,
hoverNav: false,
autoPlay: false,
};
},
computed: {
@@ -158,11 +194,9 @@ export default {
if (this.req.type === "image" && !this.fullSize) {
return `${baseURL}/api/preview/big${url.encodePath(
this.req.path
)}?auth=${this.jwt}&k=${key}`;
)}?k=${key}`;
}
return `${baseURL}/api/raw${url.encodePath(this.req.path)}?auth=${
this.jwt
}&k=${key}`;
return `${baseURL}/api/raw${url.encodePath(this.req.path)}?k=${key}`;
},
raw() {
return `${this.previewUrl}&inline=true`;
@@ -207,11 +241,11 @@ export default {
},
prev() {
this.hoverNav = false;
this.$router.push({ path: this.previousLink });
this.$router.replace({ path: this.previousLink });
},
next() {
this.hoverNav = false;
this.$router.push({ path: this.nextLink });
this.$router.replace({ path: this.nextLink });
},
key(event) {
if (this.show !== null) {
@@ -230,9 +264,17 @@ export default {
}
},
async updatePreview() {
if (
this.$refs.player &&
this.$refs.player.paused &&
!this.$refs.player.ended
) {
this.autoPlay = false;
}
if (this.req.subtitles) {
this.subtitles = this.req.subtitles.map(
(sub) => `${baseURL}/api/raw${sub}?auth=${this.jwt}&inline=true`
(sub) => `${baseURL}/api/raw${sub}?inline=true`
);
}

View File

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

View File

@@ -15,6 +15,10 @@
<input type="checkbox" v-model="singleClick" />
{{ $t("settings.singleClick") }}
</p>
<p>
<input type="checkbox" v-model="dateFormat" />
{{ $t("settings.setDateFormat") }}
</p>
<h3>{{ $t("settings.language") }}</h3>
<languages
class="input input--block"
@@ -83,6 +87,7 @@ export default {
passwordConf: "",
hideDotfiles: false,
singleClick: false,
dateFormat: false,
locale: "",
};
},
@@ -103,12 +108,14 @@ export default {
},
},
created() {
this.setLoading(false);
this.locale = this.user.locale;
this.hideDotfiles = this.user.hideDotfiles;
this.singleClick = this.user.singleClick;
this.dateFormat = this.user.dateFormat;
},
methods: {
...mapMutations(["updateUser"]),
...mapMutations(["updateUser", "setLoading"]),
async updatePassword(event) {
event.preventDefault();
@@ -134,8 +141,9 @@ export default {
locale: this.locale,
hideDotfiles: this.hideDotfiles,
singleClick: this.singleClick,
dateFormat: this.dateFormat,
};
await api.update(data, ["locale", "hideDotfiles", "singleClick"]);
await api.update(data, ["locale", "hideDotfiles", "singleClick", "dateFormat"]);
this.updateUser(data);
this.$showSuccess(this.$t("settings.settingsUpdated"));
} catch (e) {

View File

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

View File

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

View File

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

View File

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

3
go.mod
View File

@@ -10,6 +10,7 @@ require (
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/disintegration/imaging v1.6.2
github.com/dsnet/compress v0.0.1 // indirect
github.com/dsoprea/go-exif/v3 v3.0.0-20201216222538-db167117f483
github.com/golang/snappy v0.0.1 // indirect
github.com/gorilla/mux v1.7.3
github.com/gorilla/websocket v1.4.1
@@ -37,5 +38,5 @@ require (
golang.org/x/text v0.3.2 // indirect
google.golang.org/appengine v1.5.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0
gopkg.in/yaml.v2 v2.2.7
gopkg.in/yaml.v2 v2.3.0
)

26
go.sum
View File

@@ -39,6 +39,17 @@ github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44am
github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
github.com/dsoprea/go-exif/v2 v2.0.0-20200321225314-640175a69fe4/go.mod h1:Lm2lMM2zx8p4a34ZemkaUV95AnMl4ZvLbCUbwOvLC2E=
github.com/dsoprea/go-exif/v3 v3.0.0-20200717053412-08f1b6708903/go.mod h1:0nsO1ce0mh5czxGeLo4+OCZ/C6Eo6ZlMWsz7rH/Gxv8=
github.com/dsoprea/go-exif/v3 v3.0.0-20201216222538-db167117f483 h1:rz9dPf+Unge2D5RNIRNFvCc2OrGfhAPuxx4L6412jdI=
github.com/dsoprea/go-exif/v3 v3.0.0-20201216222538-db167117f483/go.mod h1:cg5SNYKHMmzxsr9X6ZeLh/nfBRHHp5PngtEPcujONtk=
github.com/dsoprea/go-logging v0.0.0-20190624164917-c4f10aab7696/go.mod h1:Nm/x2ZUNRW6Fe5C3LxdY1PyZY5wmDv/s5dkPJ/VB3iA=
github.com/dsoprea/go-logging v0.0.0-20200517223158-a10564966e9d h1:F/7L5wr/fP/SKeO5HuMlNEX9Ipyx2MbH2rV9G4zJRpk=
github.com/dsoprea/go-logging v0.0.0-20200517223158-a10564966e9d/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8=
github.com/dsoprea/go-utility v0.0.0-20200711062821-fab8125e9bdf h1:/w4QxepU4AHh3AuO6/g8y/YIIHH5+aKP3Bj8sg5cqhU=
github.com/dsoprea/go-utility v0.0.0-20200711062821-fab8125e9bdf/go.mod h1:95+K3z2L0mqsVYd6yveIv1lmtT3tcQQ3dVakPySffW8=
github.com/dsoprea/go-utility/v2 v2.0.0-20200717064901-2fccff4aa15e h1:IxIbA7VbCNrwumIYjDoMOdf4KOSkMC6NJE4s8oRbE7E=
github.com/dsoprea/go-utility/v2 v2.0.0-20200717064901-2fccff4aa15e/go.mod h1:uAzdkPTub5Y9yQwXe8W4m2XuP0tK4a9Q/dantD0+uaU=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
@@ -47,12 +58,19 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-acme/lego v2.5.0+incompatible h1:5fNN9yRQfv8ymH3DSsxla+4aYeQt2IgfZqHKVnK8f0s=
github.com/go-acme/lego v2.5.0+incompatible/go.mod h1:yzMNe9CasVUhkquNvti5nAtPmG94USbYxYrZfTkIn0M=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
github.com/go-errors/errors v1.1.1 h1:ljK/pL5ltg3qoN+OtN6yCv9HWSfMwxSx90GJCZQxYNg=
github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
github.com/golang/geo v0.0.0-20200319012246-673a6f80352d h1:C/hKUcHT483btRbeGkrRjJz+Zbcj8audldIi9tRJDCc=
github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
@@ -83,6 +101,8 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
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/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
@@ -225,6 +245,10 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190328230028-74de082e2cca/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/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
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=
@@ -280,6 +304,8 @@ gopkg.in/yaml.v2 v2.2.2/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=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@@ -28,6 +28,7 @@ type userInfo struct {
Commands []string `json:"commands"`
LockPassword bool `json:"lockPassword"`
HideDotfiles bool `json:"hideDotfiles"`
DateFormat bool `json:"dateFormat"`
}
type authToken struct {
@@ -48,11 +49,16 @@ func (e extractor) ExtractToken(r *http.Request) (string, error) {
}
auth := r.URL.Query().Get("auth")
if auth == "" {
return "", request.ErrNoTokenInRequest
if auth != "" && strings.Count(auth, ".") == 2 {
return auth, nil
}
return auth, nil
cookie, _ := r.Cookie("auth")
if cookie != nil && strings.Count(cookie.Value, ".") == 2 {
return cookie.Value, nil
}
return "", request.ErrNoTokenInRequest
}
func withUser(fn handleFunc) handleFunc {
@@ -179,6 +185,7 @@ func printToken(w http.ResponseWriter, _ *http.Request, d *data, user *users.Use
LockPassword: user.LockPassword,
Commands: user.Commands,
HideDotfiles: user.HideDotfiles,
DateFormat: user.DateFormat,
},
StandardClaims: jwt.StandardClaims{
IssuedAt: time.Now().Unix(),

View File

@@ -59,14 +59,6 @@ var commandsHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *d
}
}
if !d.server.EnableExec || !d.user.CanExecute(strings.Split(raw, " ")[0]) {
if err := conn.WriteMessage(websocket.TextMessage, cmdNotAllowed); err != nil { //nolint:govet
wsErr(conn, r, http.StatusInternalServerError, err)
}
return 0, nil
}
command, err := runner.ParseCommand(d.settings, raw)
if err != nil {
if err := conn.WriteMessage(websocket.TextMessage, []byte(err.Error())); err != nil { //nolint:govet
@@ -75,6 +67,14 @@ var commandsHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *d
return 0, nil
}
if !d.server.EnableExec || !d.user.CanExecute(command[0]) {
if err := conn.WriteMessage(websocket.TextMessage, cmdNotAllowed); err != nil { //nolint:govet
wsErr(conn, r, http.StatusInternalServerError, err)
}
return 0, nil
}
cmd := exec.Command(command[0], command[1:]...) //nolint:gosec
cmd.Dir = d.user.FullPath(r.URL.Path)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,7 +10,10 @@ import (
"io"
"github.com/disintegration/imaging"
"github.com/dsoprea/go-exif/v3"
"github.com/marusama/semaphore/v2"
exifcommon "github.com/dsoprea/go-exif/v3/common"
)
// ErrUnsupportedFormat means the given image format is not supported.
@@ -152,6 +155,17 @@ func (s *Service) Resize(ctx context.Context, in io.Reader, width, height int, o
option(&config)
}
if config.quality == QualityLow && format == FormatJpeg {
thm, newWrappedReader, errThm := getEmbeddedThumbnail(wrappedReader)
wrappedReader = newWrappedReader
if errThm == nil {
_, err = out.Write(thm)
if err == nil {
return nil
}
}
}
img, err := imaging.Decode(wrappedReader, imaging.AutoOrientation(true))
if err != nil {
return err
@@ -160,6 +174,8 @@ func (s *Service) Resize(ctx context.Context, in io.Reader, width, height int, o
switch config.resizeMode {
case ResizeModeFill:
img = imaging.Fill(img, width, height, imaging.Center, config.quality.resampleFilter())
case ResizeModeFit:
fallthrough //nolint:gocritic
default:
img = imaging.Fit(img, width, height, config.quality.resampleFilter())
}
@@ -183,3 +199,46 @@ func (s *Service) detectFormat(in io.Reader) (Format, io.Reader, error) {
return format, io.MultiReader(buf, in), nil
}
func getEmbeddedThumbnail(in io.Reader) ([]byte, io.Reader, error) {
buf := &bytes.Buffer{}
r := io.TeeReader(in, buf)
wrappedReader := io.MultiReader(buf, in)
offset := 0
offsets := []int{12, 30}
head := make([]byte, 0xffff) //nolint:gomnd
_, err := r.Read(head)
if err != nil {
return nil, wrappedReader, err
}
for _, offset = range offsets {
if _, err = exif.ParseExifHeader(head[offset:]); err == nil {
break
}
}
if err != nil {
return nil, wrappedReader, err
}
im, err := exifcommon.NewIfdMappingWithStandard()
if err != nil {
return nil, wrappedReader, err
}
_, index, err := exif.Collect(im, exif.NewTagIndex(), head[offset:])
if err != nil {
return nil, wrappedReader, err
}
ifd := index.RootIfd.NextIfd()
if ifd == nil {
return nil, wrappedReader, exif.ErrNoThumbnail
}
thm, err := ifd.Thumbnail()
return thm, wrappedReader, err
}

View File

@@ -216,6 +216,46 @@ func TestService_Resize(t *testing.T) {
},
matcher: sizeMatcher(100, 100),
},
"get thumbnail from file with APP0 JFIF": {
options: []Option{WithMode(ResizeModeFill), WithQuality(QualityLow)},
width: 100,
height: 100,
source: func(t *testing.T) afero.File {
t.Helper()
return openFile(t, "testdata/gray-sample.jpg")
},
matcher: sizeMatcher(125, 128),
},
"get thumbnail from file without APP0 JFIF": {
options: []Option{WithMode(ResizeModeFill), WithQuality(QualityLow)},
width: 100,
height: 100,
source: func(t *testing.T) afero.File {
t.Helper()
return openFile(t, "testdata/20130612_142406.jpg")
},
matcher: sizeMatcher(320, 240),
},
"resize from file without IFD1 thumbnail": {
options: []Option{WithMode(ResizeModeFill), WithQuality(QualityLow)},
width: 100,
height: 100,
source: func(t *testing.T) afero.File {
t.Helper()
return openFile(t, "testdata/IMG_2578.JPG")
},
matcher: sizeMatcher(100, 100),
},
"resize for higher quality levels": {
options: []Option{WithMode(ResizeModeFill), WithQuality(QualityMedium)},
width: 100,
height: 100,
source: func(t *testing.T) afero.File {
t.Helper()
return openFile(t, "testdata/gray-sample.jpg")
},
matcher: sizeMatcher(100, 100),
},
"broken file": {
options: []Option{WithMode(ResizeModeFit)},
width: 100,
@@ -348,6 +388,15 @@ func newGrayBmp(t *testing.T, width, height int) afero.File {
return file
}
func openFile(t *testing.T, name string) afero.File {
appfs := afero.NewOsFs()
file, err := appfs.Open(name)
require.NoError(t, err)
return file
}
func TestService_FormatFromExtension(t *testing.T) {
testCases := map[string]struct {
ext string

BIN
img/testdata/20130612_142406.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

BIN
img/testdata/IMG_2578.JPG vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
img/testdata/gray-sample.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

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

View File

@@ -16,6 +16,7 @@ type UserDefaults struct {
Perm users.Permissions `json:"perm"`
Commands []string `json:"commands"`
HideDotfiles bool `json:"hideDotfiles"`
DateFormat bool `json:"dateFormat"`
}
// Apply applies the default options to a user.
@@ -28,4 +29,5 @@ func (d *UserDefaults) Apply(u *users.User) {
u.Sorting = d.Sorting
u.Commands = d.Commands
u.HideDotfiles = d.HideDotfiles
u.DateFormat = d.DateFormat
}

View File

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

View File

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

View File

@@ -35,6 +35,7 @@ type User struct {
Fs afero.Fs `json:"-" yaml:"-"`
Rules []rules.Rule `json:"rules"`
HideDotfiles bool `json:"hideDotfiles"`
DateFormat bool `json:"dateFormat"`
}
// GetRules implements rules.Provider.