Compare commits

..

16 Commits

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

* fix: apply gofmt formater

* feat: upload button prompt

* feat: empty folder upload
2020-06-16 21:56:44 +02:00
Oleg Lobanov
6d899a6335 chore: version v2.1.2 2020-06-06 17:49:14 +02:00
Oleg Lobanov
28672c0114 fix(security): check user permission to rename files 2020-06-06 17:45:51 +02:00
Oleg Lobanov
b8300b7121 chore: add dist folder to gitignore 2020-06-02 10:50:14 +02:00
21 changed files with 439 additions and 91 deletions

1
.gitignore vendored
View File

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

View File

@@ -79,3 +79,29 @@ dockers:
- "filebrowser/filebrowser:v{{ .Major }}-pi"
extra_files:
- .docker.json
-
dockerfile: Dockerfile.alpine
binaries:
- filebrowser
goos: linux
goarch: amd64
goarm: ''
image_templates:
- "filebrowser/filebrowser:alpine"
- "filebrowser/filebrowser:{{ .Tag }}-apline"
- "filebrowser/filebrowser:v{{ .Major }}-alpine"
extra_files:
- .docker.json
-
dockerfile: Dockerfile.debian
binaries:
- filebrowser
goos: linux
goarch: amd64
goarm: ''
image_templates:
- "filebrowser/filebrowser:debian"
- "filebrowser/filebrowser:{{ .Tag }}-debian"
- "filebrowser/filebrowser:v{{ .Major }}-debian"
extra_files:
- .docker.json

22
CHANGELOG.md Normal file
View File

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

11
Dockerfile.alpine Normal file
View File

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

9
Dockerfile.debian Normal file
View File

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

View File

@@ -14,16 +14,20 @@ filebrowser provides a file managing interface within a specified directory and
## Features
Please refer to our docs at [filebrowser.xyz/features](https://github.com/filebrowser/docs/tree/master/features)
Please refer to our docs at [https://filebrowser.org/features](https://filebrowser.org/features)
## Install
Please refer to our docs at [filebrowser.xyz](https://github.com/filebrowser/docs/tree/master/).
For installation instructions please refer to our docs at [https://filebrowser.org/installation](https://filebrowser.org/installation).
## Usage
## Configuration
Please refer to our docs at [filebrowser.xyz/usage](https://github.com/filebrowser/docs/tree/master/usage).
[Authentication Method](https://filebrowser.org/configuration/authentication-method) - You can change the way the user authenticates with the filebrowser server
[Commander Runner](https://filebrowser.org/configuration/command-runner) - The command runner is a feature that enables you to execute any shell command you want before or after a certain event.
[Custom Branding](https://filebrowser.org/configuration/custom-branding) - You can customize your File Browser installation by change its name to any other you want, by adding a global custom style sheet and by using your own logotype if you want.
## Contributing
Please refer to our docs at [filebrowser.xyz/contributing](https://github.com/filebrowser/docs/tree/master/contributing).
If you're interested in contributing to this project, our docs are best places to start [https://filebrowser.org/contributing](https://filebrowser.org/contributing).

View File

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

View File

@@ -8802,6 +8802,11 @@
"integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=",
"dev": true
},
"lodash.throttle": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
"integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ="
},
"lodash.transform": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/lodash.transform/-/lodash.transform-4.6.0.tgz",

View File

@@ -13,6 +13,7 @@
"clipboard": "^2.0.4",
"js-base64": "^2.5.1",
"lodash.clonedeep": "^4.5.0",
"lodash.throttle": "^4.1.1",
"material-design-icons": "^3.0.1",
"moment": "^2.24.0",
"normalize.css": "^8.0.1",

View File

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

View File

@@ -33,7 +33,7 @@ export default {
const fileContent = this.req.content || '';
this.editor = ace.edit('editor', {
maxLines: Infinity,
maxLines: 80,
minLines: 20,
value: fileContent,
showPrintMargin: false,

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
<template>
<div>
<component :is="currentComponent"></component>
<component ref="currentComponent" :is="currentComponent"></component>
<div v-show="showOverlay" @click="resetPrompts" class="overlay"></div>
</div>
</template>
@@ -17,6 +17,7 @@ import NewFile from './NewFile'
import NewDir from './NewDir'
import Replace from './Replace'
import Share from './Share'
import Upload from './Upload'
import { mapState } from 'vuex'
import buttons from '@/utils/buttons'
@@ -33,7 +34,8 @@ export default {
NewFile,
NewDir,
Help,
Replace
Replace,
Upload
},
data: function () {
return {
@@ -44,6 +46,33 @@ export default {
}
}
},
created () {
window.addEventListener('keydown', (event) => {
if (this.show == null)
return
let prompt = this.$refs.currentComponent;
// Enter
if (event.keyCode == 13) {
switch (this.show) {
case 'delete':
prompt.submit()
break;
case 'copy':
prompt.copy(event)
break;
case 'move':
prompt.move(event)
break;
case 'replace':
prompt.showConfirm(event)
break;
}
}
})
},
computed: {
...mapState(['show', 'plugins']),
currentComponent: function () {
@@ -58,7 +87,8 @@ export default {
'newDir',
'download',
'replace',
'share'
'share',
'upload'
].indexOf(this.show) >= 0;
return matched && this.show || null;

View File

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

View File

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

View File

@@ -116,7 +116,9 @@
"size": "Size",
"schedule": "Schedule",
"scheduleMessage": "Pick a date and time to schedule the publication of this post.",
"newArchetype": "Create a new post based on an archetype. Your file will be created on content folder."
"newArchetype": "Create a new post based on an archetype. Your file will be created on content folder.",
"upload": "Upload",
"uploadMessage": "Select an option to upload."
},
"settings": {
"themes": {

View File

@@ -12,10 +12,6 @@ export function parseToken (token) {
const data = JSON.parse(Base64.decode(parts[1]))
if (Math.round(new Date().getTime() / 1000) > data.exp) {
throw new Error('token expired')
}
localStorage.setItem('jwt', token)
store.commit('setJWT', token)
store.commit('setUser', data.user)

View File

@@ -26,19 +26,20 @@ type data struct {
// Check implements rules.Checker.
func (d *data) Check(path string) bool {
for _, rule := range d.user.Rules {
if rule.Matches(path) {
return rule.Allow
}
}
allow := true
for _, rule := range d.settings.Rules {
if rule.Matches(path) {
return rule.Allow
allow = rule.Allow
}
}
return true
for _, rule := range d.user.Rules {
if rule.Matches(path) {
allow = rule.Allow
}
}
return allow
}
func handle(fn handleFunc, prefix string, store *storage.Storage, server *settings.Server) http.Handler {

View File

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

View File

@@ -2,12 +2,13 @@ package http
import (
"encoding/json"
"errors"
"net/http"
"net/url"
"os"
"strings"
"github.com/filebrowser/filebrowser/v2/errors"
libErrors "github.com/filebrowser/filebrowser/v2/errors"
)
func renderJSON(w http.ResponseWriter, _ *http.Request, data interface{}) (int, error) {
@@ -31,10 +32,14 @@ func errToStatus(err error) int {
return http.StatusOK
case os.IsPermission(err):
return http.StatusForbidden
case os.IsNotExist(err), err == errors.ErrNotExist:
case os.IsNotExist(err), err == libErrors.ErrNotExist:
return http.StatusNotFound
case os.IsExist(err), err == errors.ErrExist:
case os.IsExist(err), err == libErrors.ErrExist:
return http.StatusConflict
case errors.Is(err, libErrors.ErrPermissionDenied):
return http.StatusForbidden
case errors.Is(err, libErrors.ErrInvalidRequestParams):
return http.StatusBadRequest
default:
return http.StatusInternalServerError
}