Merge pull request #1321 from ramiresviana/fixes-6

This commit is contained in:
Oleg Lobanov 2021-03-14 14:19:50 +01:00 committed by GitHub
commit 381f09087a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 223 additions and 123 deletions

View File

@ -1,23 +1,47 @@
import { fetchJSON, removePrefix } from './utils'
import { fetchURL, removePrefix } from './utils'
import { baseURL } from '@/utils/constants'
export async function fetch(hash, password = "") {
return fetchJSON(`/api/public/share/${hash}`, {
export async function fetch (url, password = "") {
url = removePrefix(url)
const res = await fetchURL(`/api/public/share${url}`, {
headers: {'X-SHARE-PASSWORD': password},
})
if (res.status === 200) {
let data = await res.json()
data.url = `/share${url}`
if (data.isDir) {
if (!data.url.endsWith('/')) data.url += '/'
data.items = data.items.map((item, index) => {
item.index = index
item.url = `${data.url}${encodeURIComponent(item.name)}`
if (item.isDir) {
item.url += '/'
}
return item
})
}
return data
} else {
throw new Error(res.status)
}
}
export function download(format, hash, token, ...files) {
let url = `${baseURL}/api/public/dl/${hash}`
const prefix = `/share/${hash}`
if (files.length === 1) {
url += removePrefix(files[0], prefix) + '?'
url += encodeURIComponent(files[0]) + '?'
} else {
let arg = ''
for (let file of files) {
arg += removePrefix(file, prefix) + ','
arg += encodeURIComponent(file) + ','
}
arg = arg.substring(0, arg.length - 1)

View File

@ -33,12 +33,8 @@ export async function fetchJSON (url, opts) {
}
}
export function removePrefix (url, prefix) {
if (url.startsWith('/files')) {
url = url.slice(6)
} else if (prefix) {
url = url.replace(prefix, '')
}
export function removePrefix (url) {
url = url.split('/').splice(2).join('/')
if (url === '') url = '/'
if (url[0] !== '/') url = '/' + url

View File

@ -66,7 +66,7 @@ export default {
return this.readOnly == undefined && this.user.perm.rename
},
canDrop () {
if (!this.isDir || this.readOnly == undefined) return false
if (!this.isDir || this.readOnly !== undefined) return false
for (let i of this.selected) {
if (this.req.items[i].url === this.url) {
@ -78,7 +78,11 @@ export default {
},
thumbnailUrl () {
const path = this.url.replace(/^\/files\//, '')
return `${baseURL}/api/preview/thumb/${path}?auth=${this.jwt}&inline=true`
// 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}`
},
isThumbsEnabled () {
return enableThumbs

View File

@ -63,7 +63,7 @@ export default {
return moment(this.req.modified).fromNow()
}
return moment(this.req.items[this.selected[0]]).fromNow()
return moment(this.req.items[this.selected[0]].modified).fromNow()
},
name: function () {
return this.selectedCount === 0 ? this.req.name : this.req.items[this.selected[0]].name

View File

@ -123,15 +123,21 @@
color: #fff;
}
#previewer .action i {
#previewer header > .action i {
color: #fff;
}
#previewer .action:hover {
@media (min-width: 738px) {
#previewer header #dropdown .action i {
color: #fff;
}
}
#previewer header .action:hover {
background-color: rgba(255, 255, 255, 0.3)
}
#previewer .action span {
#previewer header .action span {
display: none;
}

View File

@ -6,6 +6,10 @@ function loading (button) {
return
}
if (el.innerHTML == 'autorenew' || el.innerHTML == 'done') {
return
}
el.dataset.icon = el.innerHTML
el.style.opacity = 0

View File

@ -31,7 +31,7 @@
<a target="_blank" :href="link" class="button button--flat">{{ $t('buttons.download') }}</a>
</div>
<div class="share__box__element share__box__center">
<qrcode-vue :value="fullLink" size="200" level="M"></qrcode-vue>
<qrcode-vue :value="link" size="200" level="M"></qrcode-vue>
</div>
</div>
<div v-if="req.isDir && req.items.length > 0" class="share__box share__box__items">
@ -122,7 +122,6 @@ export default {
},
data: () => ({
error: null,
path: '',
showLimit: 500,
password: '',
attemptedPasswordLogin: false,
@ -158,10 +157,9 @@ export default {
if (this.token !== ''){
queryArg = `?token=${this.token}`
}
return `${baseURL}/api/public/dl/${this.hash}${this.path}${queryArg}`
},
fullLink: function () {
return window.location.origin + this.link
const path = this.$route.path.split('/').splice(2).join('/')
return `${baseURL}/api/public/dl/${path}${queryArg}`
},
humanSize: function () {
if (this.req.isDir) {
@ -193,20 +191,19 @@ export default {
this.setLoading(true)
this.error = null
if (this.password !== ''){
this.attemptedPasswordLogin = true
}
let url = this.$route.path
if (url === '') url = '/'
if (url[0] !== '/') url = '/' + url
try {
if (this.password !== ''){
this.attemptedPasswordLogin = true
}
let file = await api.fetch(encodeURIComponent(this.$route.params.pathMatch), this.password)
this.path = file.path
if (this.path.endsWith('/')) this.path = this.path.slice(0, -1)
let file = await api.fetch(url, this.password)
this.token = file.token || ''
if (file.isDir) file.items = file.items.map((item, index) => {
item.index = index
item.url = `/share/${this.hash}${this.path}/${encodeURIComponent(item.name)}`
return item
})
this.updateRequest(file)
this.setLoading(false)
} catch (e) {
@ -228,7 +225,7 @@ export default {
},
download () {
if (this.selectedCount === 1 && !this.req.items[this.selected[0]].isDir) {
api.download(null, this.hash, this.token, this.req.items[this.selected[0]].url)
api.download(null, this.hash, this.token, this.req.items[this.selected[0]].path)
return
}
@ -240,7 +237,7 @@ export default {
let files = []
for (let i of this.selected) {
files.push(this.req.items[i].url)
files.push(this.req.items[i].path)
}
api.download(format, this.hash, this.token, ...files)

View File

@ -588,8 +588,12 @@ export default {
let files = []
for (let i of this.selected) {
files.push(this.req.items[i].url)
if (this.selectedCount > 0) {
for (let i of this.selected) {
files.push(this.req.items[i].url)
}
} else {
files.push(this.$route.path)
}
api.download(format, ...files)

View File

@ -102,10 +102,13 @@ export default {
return `${baseURL}/api/raw${url.encodePath(this.req.path)}?auth=${this.jwt}`
},
previewUrl () {
// reload the image when the file is replaced
const key = Date.parse(this.req.modified)
if (this.req.type === 'image' && !this.fullSize) {
return `${baseURL}/api/preview/big${url.encodePath(this.req.path)}?auth=${this.jwt}`
return `${baseURL}/api/preview/big${url.encodePath(this.req.path)}?auth=${this.jwt}&k=${key}`
}
return `${baseURL}/api/raw${url.encodePath(this.req.path)}?auth=${this.jwt}`
return `${baseURL}/api/raw${url.encodePath(this.req.path)}?auth=${this.jwt}&k=${key}`
},
raw () {
return `${this.previewUrl}&inline=true`

View File

@ -54,8 +54,8 @@ func NewHandler(
api.PathPrefix("/resources").Handler(monkey(resourceGetHandler, "/api/resources")).Methods("GET")
api.PathPrefix("/resources").Handler(monkey(resourceDeleteHandler(fileCache), "/api/resources")).Methods("DELETE")
api.PathPrefix("/resources").Handler(monkey(resourcePostPutHandler, "/api/resources")).Methods("POST")
api.PathPrefix("/resources").Handler(monkey(resourcePostPutHandler, "/api/resources")).Methods("PUT")
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.Path("/shares").Handler(monkey(shareListHandler, "/api/shares")).Methods("GET")

View File

@ -79,7 +79,7 @@ func handleImagePreview(w http.ResponseWriter, r *http.Request, imgSvc ImgServic
return errToStatus(err), err
}
cacheKey := previewCacheKey(file.Path, previewSize)
cacheKey := previewCacheKey(file.Path, file.ModTime.Unix(), previewSize)
cachedFile, ok, err := fileCache.Load(r.Context(), cacheKey)
if err != nil {
return errToStatus(err), err
@ -133,6 +133,6 @@ func handleImagePreview(w http.ResponseWriter, r *http.Request, imgSvc ImgServic
return 0, nil
}
func previewCacheKey(fPath string, previewSize PreviewSize) string {
return fPath + previewSize.String()
func previewCacheKey(fPath string, fTime int64, previewSize PreviewSize) string {
return fmt.Sprintf("%x%x%x", fPath, fTime, previewSize)
}

View File

@ -16,7 +16,7 @@ import (
var withHashFile = func(fn handleFunc) handleFunc {
return func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
id, path := ifPathWithName(r)
id, ifPath := ifPathWithName(r)
link, err := d.store.Share.GetByHash(id)
if err != nil {
return errToStatus(err), err
@ -47,21 +47,30 @@ var withHashFile = func(fn handleFunc) handleFunc {
return errToStatus(err), err
}
if file.IsDir {
// set fs root to the shared folder
d.user.Fs = afero.NewBasePathFs(d.user.Fs, filepath.Dir(link.Path))
// share base path
basePath := link.Path
file, err = files.NewFileInfo(files.FileOptions{
Fs: d.user.Fs,
Path: path,
Modify: d.user.Perm.Modify,
Expand: true,
Checker: d,
Token: link.Token,
})
if err != nil {
return errToStatus(err), err
}
// file relative path
filePath := ""
if file.IsDir {
basePath = filepath.Dir(basePath)
filePath = ifPath
}
// set fs root to the shared file/folder
d.user.Fs = afero.NewBasePathFs(d.user.Fs, basePath)
file, err = files.NewFileInfo(files.FileOptions{
Fs: d.user.Fs,
Path: filePath,
Modify: d.user.Perm.Modify,
Expand: true,
Checker: d,
Token: link.Token,
})
if err != nil {
return errToStatus(err), err
}
d.raw = file

View File

@ -108,8 +108,6 @@ var rawHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data)
})
func addFile(ar archiver.Writer, d *data, path, commonPath string) error {
// Checks are always done with paths with "/" as path separator.
path = strings.Replace(path, "\\", "/", -1)
if !d.Check(path) {
return nil
}
@ -134,7 +132,7 @@ func addFile(ar archiver.Writer, d *data, path, commonPath string) error {
if path != commonPath {
filename := strings.TrimPrefix(path, commonPath)
filename = strings.TrimPrefix(filename, "/")
filename = strings.TrimPrefix(filename, string(filepath.Separator))
err = ar.Write(archiver.File{
FileInfo: archiver.FileInfo{
FileInfo: info,
@ -175,20 +173,25 @@ func rawDirHandler(w http.ResponseWriter, r *http.Request, d *data, file *files.
return http.StatusInternalServerError, err
}
name := file.Name
if name == "." || name == "" {
name = "archive"
}
name += extension
w.Header().Set("Content-Disposition", "attachment; filename*=utf-8''"+url.PathEscape(name))
err = ar.Create(w)
if err != nil {
return http.StatusInternalServerError, err
}
defer ar.Close()
commonDir := fileutils.CommonPrefix('/', filenames...)
commonDir := fileutils.CommonPrefix(filepath.Separator, filenames...)
var name string
if len(filenames) > 1 {
name = "_" + filepath.Base(commonDir)
} else {
name = file.Name
}
if name == "." || name == "" {
name = "archive"
}
name += extension
w.Header().Set("Content-Disposition", "attachment; filename*=utf-8''"+url.PathEscape(name))
for _, fname := range filenames {
err = addFile(ar, d, fname, commonDir)

View File

@ -1,6 +1,7 @@
package http
import (
"context"
"fmt"
"io"
"io/ioutil"
@ -71,11 +72,9 @@ func resourceDeleteHandler(fileCache FileCache) handleFunc {
}
// delete thumbnails
for _, previewSizeName := range PreviewSizeNames() {
size, _ := ParsePreviewSize(previewSizeName)
if err := fileCache.Delete(r.Context(), previewCacheKey(file.Path, size)); err != nil { //nolint:govet
return errToStatus(err), err
}
err = delThumbs(r.Context(), fileCache, file)
if err != nil {
return errToStatus(err), err
}
err = d.RunHook(func() error {
@ -90,12 +89,59 @@ func resourceDeleteHandler(fileCache FileCache) handleFunc {
})
}
var resourcePostPutHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
if !d.user.Perm.Create && r.Method == http.MethodPost {
return http.StatusForbidden, nil
}
func resourcePostHandler(fileCache FileCache) handleFunc {
return withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
if !d.user.Perm.Create || !d.Check(r.URL.Path) {
return http.StatusForbidden, nil
}
if !d.user.Perm.Modify && r.Method == http.MethodPut {
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)
return errToStatus(err), err
}
file, err := files.NewFileInfo(files.FileOptions{
Fs: d.user.Fs,
Path: r.URL.Path,
Modify: d.user.Perm.Modify,
Expand: true,
ReadHeader: d.server.TypeDetectionByHeader,
Checker: d,
})
if err == nil {
if r.URL.Query().Get("override") != "true" {
return http.StatusConflict, nil
}
err = delThumbs(r.Context(), fileCache, file)
if err != nil {
return errToStatus(err), err
}
}
err = d.RunHook(func() error {
info, _ := writeFile(d.user.Fs, r.URL.Path, r.Body)
etag := fmt.Sprintf(`"%x%x"`, info.ModTime().UnixNano(), info.Size())
w.Header().Set("ETag", etag)
return nil
}, "upload", r.URL.Path, "", d.user)
if err != nil {
_ = d.user.Fs.RemoveAll(r.URL.Path)
}
return errToStatus(err), err
})
}
var resourcePutHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
if !d.user.Perm.Modify || !d.Check(r.URL.Path) {
return http.StatusForbidden, nil
}
@ -103,55 +149,18 @@ var resourcePostPutHandler = withUser(func(w http.ResponseWriter, r *http.Reques
_, _ = io.Copy(ioutil.Discard, r.Body)
}()
// For directories, only allow POST for creation.
// Only allow PUT for files.
if strings.HasSuffix(r.URL.Path, "/") {
if r.Method == http.MethodPut {
return http.StatusMethodNotAllowed, nil
}
err := d.user.Fs.MkdirAll(r.URL.Path, 0775)
return errToStatus(err), err
}
if r.Method == http.MethodPost && r.URL.Query().Get("override") != "true" {
if _, err := d.user.Fs.Stat(r.URL.Path); err == nil {
return http.StatusConflict, nil
}
}
action := "upload"
if r.Method == http.MethodPut {
action = "save"
return http.StatusMethodNotAllowed, nil
}
err := d.RunHook(func() error {
dir, _ := path.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
}
defer file.Close()
_, err = io.Copy(file, r.Body)
if err != nil {
return err
}
// Gets the info about the file.
info, err := file.Stat()
if err != nil {
return err
}
info, _ := writeFile(d.user.Fs, r.URL.Path, r.Body)
etag := fmt.Sprintf(`"%x%x"`, info.ModTime().UnixNano(), info.Size())
w.Header().Set("ETag", etag)
return nil
}, action, r.URL.Path, "", d.user)
}, "save", r.URL.Path, "", d.user)
if err != nil {
_ = d.user.Fs.RemoveAll(r.URL.Path)
@ -165,6 +174,9 @@ var resourcePatchHandler = withUser(func(w http.ResponseWriter, r *http.Request,
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
}
@ -242,3 +254,41 @@ func addVersionSuffix(source string, fs afero.Fs) string {
return source
}
func writeFile(fs afero.Fs, dst string, in io.Reader) (os.FileInfo, error) {
dir, _ := path.Split(dst)
err := fs.MkdirAll(dir, 0775)
if err != nil {
return nil, err
}
file, err := fs.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0775)
if err != nil {
return nil, err
}
defer file.Close()
_, err = io.Copy(file, in)
if err != nil {
return nil, err
}
// Gets the info about the file.
info, err := file.Stat()
if err != nil {
return nil, err
}
return info, nil
}
func delThumbs(ctx context.Context, fileCache FileCache, file *files.FileInfo) error {
for _, previewSizeName := range PreviewSizeNames() {
size, _ := ParsePreviewSize(previewSizeName)
if err := fileCache.Delete(ctx, previewCacheKey(file.Path, file.ModTime.Unix(), size)); err != nil {
return err
}
}
return nil
}