feat: put selected files in the root of the archive (closes #1065)

This commit is contained in:
Oleg Lobanov 2020-09-11 16:53:37 +02:00
parent c5abbb4e1c
commit 8142b32f38
No known key found for this signature in database
GPG Key ID: 7CC64E41212621B0
3 changed files with 112 additions and 4 deletions

View File

@ -3,6 +3,7 @@ package fileutils
import (
"io"
"os"
"path"
"path/filepath"
"github.com/spf13/afero"
@ -50,3 +51,59 @@ func CopyFile(fs afero.Fs, source, dest string) error {
return nil
}
// CommonPrefix returns common directory path of provided files
func CommonPrefix(sep byte, paths ...string) string {
// Handle special cases.
switch len(paths) {
case 0:
return ""
case 1:
return path.Clean(paths[0])
}
// Note, we treat string as []byte, not []rune as is often
// done in Go. (And sep as byte, not rune). This is because
// most/all supported OS' treat paths as string of non-zero
// bytes. A filename may be displayed as a sequence of Unicode
// runes (typically encoded as UTF-8) but paths are
// not required to be valid UTF-8 or in any normalized form
// (e.g. "é" (U+00C9) and "é" (U+0065,U+0301) are different
// file names.
c := []byte(path.Clean(paths[0]))
// We add a trailing sep to handle the case where the
// common prefix directory is included in the path list
// (e.g. /home/user1, /home/user1/foo, /home/user1/bar).
// path.Clean will have cleaned off trailing / separators with
// the exception of the root directory, "/" (in which case we
// make it "//", but this will get fixed up to "/" bellow).
c = append(c, sep)
// Ignore the first path since it's already in c
for _, v := range paths[1:] {
// Clean up each path before testing it
v = path.Clean(v) + string(sep)
// Find the first non-common byte and truncate c
if len(v) < len(c) {
c = c[:len(v)]
}
for i := 0; i < len(c); i++ {
if v[i] != c[i] {
c = c[:i]
break
}
}
}
// Remove trailing non-separator characters and the final separator
for i := len(c) - 1; i >= 0; i-- {
if c[i] == sep {
c = c[:i]
break
}
}
return string(c)
}

46
fileutils/file_test.go Normal file
View File

@ -0,0 +1,46 @@
package fileutils
import "testing"
func TestCommonPrefix(t *testing.T) {
testCases := map[string]struct {
paths []string
want string
}{
"same lvl": {
paths: []string{
"/home/user/file1",
"/home/user/file2",
},
want: "/home/user",
},
"sub folder": {
paths: []string{
"/home/user/folder",
"/home/user/folder/file",
},
want: "/home/user/folder",
},
"relative path": {
paths: []string{
"/home/user/folder",
"/home/user/folder/../folder2",
},
want: "/home/user",
},
"no common path": {
paths: []string{
"/home/user/folder",
"/etc/file",
},
want: "",
},
}
for name, tt := range testCases {
t.Run(name, func(t *testing.T) {
if got := CommonPrefix('/', tt.paths...); got != tt.want {
t.Errorf("CommonPrefix() = %v, want %v", got, tt.want)
}
})
}
}

View File

@ -11,6 +11,7 @@ import (
"github.com/mholt/archiver"
"github.com/filebrowser/filebrowser/v2/files"
"github.com/filebrowser/filebrowser/v2/fileutils"
"github.com/filebrowser/filebrowser/v2/users"
)
@ -97,7 +98,7 @@ var rawHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data)
return rawDirHandler(w, r, d, file)
})
func addFile(ar archiver.Writer, d *data, path string) error {
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) {
@ -115,10 +116,12 @@ func addFile(ar archiver.Writer, d *data, path string) error {
}
defer file.Close()
filename := strings.TrimPrefix(path, commonPath)
filename = strings.TrimPrefix(filename, "/")
err = ar.Write(archiver.File{
FileInfo: archiver.FileInfo{
FileInfo: info,
CustomName: strings.TrimPrefix(path, "/"),
CustomName: filename,
},
ReadCloser: file,
})
@ -133,7 +136,7 @@ func addFile(ar archiver.Writer, d *data, path string) error {
}
for _, name := range names {
err = addFile(ar, d, filepath.Join(path, name))
err = addFile(ar, d, filepath.Join(path, name), commonPath)
if err != nil {
return err
}
@ -167,8 +170,10 @@ func rawDirHandler(w http.ResponseWriter, r *http.Request, d *data, file *files.
}
defer ar.Close()
commonDir := fileutils.CommonPrefix('/', filenames...)
for _, fname := range filenames {
err = addFile(ar, d, fname)
err = addFile(ar, d, fname, commonDir)
if err != nil {
return http.StatusInternalServerError, err
}