filebrowser/http/raw.go

223 lines
5.0 KiB
Go
Raw Normal View History

package http
import (
"bytes"
"errors"
"io/ioutil"
"net/http"
"net/url"
gopath "path"
"path/filepath"
"strings"
"github.com/mholt/archiver"
"github.com/spf13/afero"
2020-06-01 07:12:36 +08:00
"github.com/filebrowser/filebrowser/v2/files"
"github.com/filebrowser/filebrowser/v2/fileutils"
2020-06-01 07:12:36 +08:00
"github.com/filebrowser/filebrowser/v2/users"
)
func slashClean(name string) string {
if name == "" || name[0] != '/' {
name = "/" + name
}
return gopath.Clean(name)
}
2020-06-01 07:12:36 +08:00
func parseQueryFiles(r *http.Request, f *files.FileInfo, _ *users.User) ([]string, error) {
var fileSlice []string
names := strings.Split(r.URL.Query().Get("files"), ",")
if len(names) == 0 {
2020-06-01 07:12:36 +08:00
fileSlice = append(fileSlice, f.Path)
} else {
for _, name := range names {
name, err := url.QueryUnescape(strings.Replace(name, "+", "%2B", -1)) //nolint:govet
if err != nil {
return nil, err
}
name = slashClean(name)
2020-06-01 07:12:36 +08:00
fileSlice = append(fileSlice, filepath.Join(f.Path, name))
}
}
2020-06-01 07:12:36 +08:00
return fileSlice, nil
}
2020-06-01 07:12:36 +08:00
//nolint: goconst
func parseQueryAlgorithm(r *http.Request) (string, archiver.Writer, error) {
2020-06-01 07:12:36 +08:00
// TODO: use enum
switch r.URL.Query().Get("algo") {
case "zip", "true", "":
return ".zip", archiver.NewZip(), nil
case "tar":
return ".tar", archiver.NewTar(), nil
case "targz":
return ".tar.gz", archiver.NewTarGz(), nil
case "tarbz2":
return ".tar.bz2", archiver.NewTarBz2(), nil
case "tarxz":
return ".tar.xz", archiver.NewTarXz(), nil
case "tarlz4":
return ".tar.lz4", archiver.NewTarLz4(), nil
case "tarsz":
return ".tar.sz", archiver.NewTarSz(), nil
default:
return "", nil, errors.New("format not implemented")
}
}
func setContentDisposition(w http.ResponseWriter, r *http.Request, file *files.FileInfo) {
if r.URL.Query().Get("inline") == "true" {
w.Header().Set("Content-Disposition", "inline")
} else {
// As per RFC6266 section 4.3
w.Header().Set("Content-Disposition", "attachment; filename*=utf-8''"+url.PathEscape(file.Name))
}
}
var rawHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
if !d.user.Perm.Download {
return http.StatusAccepted, nil
}
file, err := files.NewFileInfo(files.FileOptions{
Fs: d.user.Fs,
Path: r.URL.Path,
Modify: d.user.Perm.Modify,
Expand: false,
ReadHeader: d.server.TypeDetectionByHeader,
Checker: d,
})
if err != nil {
return errToStatus(err), err
}
if files.IsNamedPipe(file.Mode) {
setContentDisposition(w, r, file)
return 0, nil
}
if !file.IsDir {
return rawFileHandler(w, r, file)
}
return rawDirHandler(w, r, d, file)
})
func addFile(ar archiver.Writer, d *data, path, commonPath string) error {
if !d.Check(path) {
return nil
}
info, err := d.user.Fs.Stat(path)
if err != nil {
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
}
2020-11-04 22:32:52 +08:00
if path != commonPath {
filename := strings.TrimPrefix(path, commonPath)
filename = strings.TrimPrefix(filename, string(filepath.Separator))
2020-11-04 22:32:52 +08:00
err = ar.Write(archiver.File{
FileInfo: archiver.FileInfo{
FileInfo: info,
CustomName: filename,
},
ReadCloser: arcReadCloser,
2020-11-04 22:32:52 +08:00
})
if err != nil {
return err
}
}
if info.IsDir() {
names, err := file.Readdirnames(0)
if err != nil {
return err
}
for _, name := range names {
err = addFile(ar, d, filepath.Join(path, name), commonPath)
if err != nil {
return err
}
}
}
return nil
}
func rawDirHandler(w http.ResponseWriter, r *http.Request, d *data, file *files.FileInfo) (int, error) {
filenames, err := parseQueryFiles(r, file, d.user)
if err != nil {
return http.StatusInternalServerError, err
}
extension, ar, err := parseQueryAlgorithm(r)
if err != nil {
return http.StatusInternalServerError, err
}
err = ar.Create(w)
if err != nil {
return http.StatusInternalServerError, err
}
defer ar.Close()
commonDir := fileutils.CommonPrefix(filepath.Separator, filenames...)
name := filepath.Base(commonDir)
if name == "." || name == "" || name == string(filepath.Separator) {
2021-03-12 23:52:52 +08:00
name = file.Name
}
// Prefix used to distinguish a filelist generated
// archive from the full directory archive
if len(filenames) > 1 {
name = "_" + name
2021-03-12 23:52:52 +08:00
}
name += extension
w.Header().Set("Content-Disposition", "attachment; filename*=utf-8''"+url.PathEscape(name))
for _, fname := range filenames {
err = addFile(ar, d, fname, commonDir)
if err != nil {
return http.StatusInternalServerError, err
}
}
return 0, nil
}
func rawFileHandler(w http.ResponseWriter, r *http.Request, file *files.FileInfo) (int, error) {
2021-04-19 20:49:40 +08:00
isFresh := checkEtag(w, r, file.ModTime.Unix(), file.Size)
if isFresh {
return http.StatusNotModified, nil
}
fd, err := file.Fs.Open(file.Path)
if err != nil {
return http.StatusInternalServerError, err
}
defer fd.Close()
setContentDisposition(w, r, file)
http.ServeContent(w, r, file.Name, file.ModTime, fd)
return 0, nil
}