mirror of
https://github.com/drakkan/sftpgo.git
synced 2024-11-22 07:30:25 +00:00
zip downloads: make zip entries relative to the current dir when possible
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
parent
554a1cb1f4
commit
f19691250d
7 changed files with 40 additions and 20 deletions
4
go.mod
4
go.mod
|
@ -68,7 +68,7 @@ require (
|
||||||
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90
|
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90
|
||||||
golang.org/x/net v0.0.0-20220909164309-bea034e7d591
|
golang.org/x/net v0.0.0-20220909164309-bea034e7d591
|
||||||
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1
|
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1
|
||||||
golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41
|
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8
|
||||||
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9
|
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9
|
||||||
google.golang.org/api v0.96.0
|
google.golang.org/api v0.96.0
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||||
|
@ -156,7 +156,7 @@ require (
|
||||||
golang.org/x/tools v0.1.12 // indirect
|
golang.org/x/tools v0.1.12 // indirect
|
||||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa // indirect
|
google.golang.org/genproto v0.0.0-20220919141832-68c03719ef51 // indirect
|
||||||
google.golang.org/grpc v1.49.0 // indirect
|
google.golang.org/grpc v1.49.0 // indirect
|
||||||
google.golang.org/protobuf v1.28.1 // indirect
|
google.golang.org/protobuf v1.28.1 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
|
|
8
go.sum
8
go.sum
|
@ -980,8 +980,8 @@ golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41 h1:ohgcoMbSofXygzo6AD2I1kz3BFmW1QArPYTtwEM3UXc=
|
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 h1:h+EGohizhe9XlX18rfpa8k8RAc5XyaeamM+0VHRd4lc=
|
||||||
golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
@ -1229,8 +1229,8 @@ google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP
|
||||||
google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
|
google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
|
||||||
google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
|
google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
|
||||||
google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
|
google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
|
||||||
google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa h1:VWkrxnAx2C2hirAP+W5ADU7e/+93Yhk//ioKd2XFyDI=
|
google.golang.org/genproto v0.0.0-20220919141832-68c03719ef51 h1:ucpgjuzWqWrj0NEwjUpsGTf2IGxyLtmuSk0oGgifjec=
|
||||||
google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=
|
google.golang.org/genproto v0.0.0-20220919141832-68c03719ef51/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
|
|
|
@ -271,13 +271,13 @@ func (s *httpdServer) downloadFromShare(w http.ResponseWriter, r *http.Request)
|
||||||
|
|
||||||
compress := true
|
compress := true
|
||||||
var info os.FileInfo
|
var info os.FileInfo
|
||||||
if len(share.Paths) == 1 && r.URL.Query().Get("compress") == "false" {
|
if len(share.Paths) == 1 {
|
||||||
info, err = connection.Stat(share.Paths[0], 1)
|
info, err = connection.Stat(share.Paths[0], 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sendAPIResponse(w, r, err, "", getRespStatus(err))
|
sendAPIResponse(w, r, err, "", getRespStatus(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if info.Mode().IsRegular() {
|
if info.Mode().IsRegular() && r.URL.Query().Get("compress") == "false" {
|
||||||
compress = false
|
compress = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -289,9 +289,16 @@ func (s *httpdServer) downloadFromShare(w http.ResponseWriter, r *http.Request)
|
||||||
err = connection.GetReadQuotaExceededError()
|
err = connection.GetReadQuotaExceededError()
|
||||||
connection.Log(logger.LevelInfo, "denying share read due to quota limits")
|
connection.Log(logger.LevelInfo, "denying share read due to quota limits")
|
||||||
sendAPIResponse(w, r, err, "", getMappedStatusCode(err))
|
sendAPIResponse(w, r, err, "", getMappedStatusCode(err))
|
||||||
|
dataprovider.UpdateShareLastUse(&share, -1) //nolint:errcheck
|
||||||
|
return
|
||||||
|
}
|
||||||
|
baseDir := "/"
|
||||||
|
if info != nil && info.IsDir() {
|
||||||
|
baseDir = share.Paths[0]
|
||||||
|
share.Paths[0] = "/"
|
||||||
}
|
}
|
||||||
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"share-%v.zip\"", share.Name))
|
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"share-%v.zip\"", share.Name))
|
||||||
renderCompressedFiles(w, connection, "/", share.Paths, &share)
|
renderCompressedFiles(w, connection, baseDir, share.Paths, &share)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if status, err := downloadFile(w, r, connection, share.Paths[0], info, false, &share); err != nil {
|
if status, err := downloadFile(w, r, connection, share.Paths[0], info, false, &share); err != nil {
|
||||||
|
|
|
@ -264,14 +264,19 @@ func addZipEntry(wr *zip.Writer, conn *Connection, entryPath, baseDir string) er
|
||||||
conn.Log(logger.LevelDebug, "unable to add zip entry %#v, stat error: %v", entryPath, err)
|
conn.Log(logger.LevelDebug, "unable to add zip entry %#v, stat error: %v", entryPath, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
entryName, err := getZipEntryName(entryPath, baseDir)
|
||||||
|
if err != nil {
|
||||||
|
conn.Log(logger.LevelError, "unable to get zip entry name: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
if info.IsDir() {
|
if info.IsDir() {
|
||||||
_, err := wr.CreateHeader(&zip.FileHeader{
|
_, err = wr.CreateHeader(&zip.FileHeader{
|
||||||
Name: getZipEntryName(entryPath, baseDir) + "/",
|
Name: entryName + "/",
|
||||||
Method: zip.Deflate,
|
Method: zip.Deflate,
|
||||||
Modified: info.ModTime(),
|
Modified: info.ModTime(),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Log(logger.LevelDebug, "unable to create zip entry %#v: %v", entryPath, err)
|
conn.Log(logger.LevelError, "unable to create zip entry %#v: %v", entryPath, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
contents, err := conn.ReadDir(entryPath)
|
contents, err := conn.ReadDir(entryPath)
|
||||||
|
@ -289,7 +294,7 @@ func addZipEntry(wr *zip.Writer, conn *Connection, entryPath, baseDir string) er
|
||||||
}
|
}
|
||||||
if !info.Mode().IsRegular() {
|
if !info.Mode().IsRegular() {
|
||||||
// we only allow regular files
|
// we only allow regular files
|
||||||
conn.Log(logger.LevelDebug, "skipping zip entry for non regular file %#v", entryPath)
|
conn.Log(logger.LevelInfo, "skipping zip entry for non regular file %#v", entryPath)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
reader, err := conn.getFileReader(entryPath, 0, http.MethodGet)
|
reader, err := conn.getFileReader(entryPath, 0, http.MethodGet)
|
||||||
|
@ -300,21 +305,24 @@ func addZipEntry(wr *zip.Writer, conn *Connection, entryPath, baseDir string) er
|
||||||
defer reader.Close()
|
defer reader.Close()
|
||||||
|
|
||||||
f, err := wr.CreateHeader(&zip.FileHeader{
|
f, err := wr.CreateHeader(&zip.FileHeader{
|
||||||
Name: getZipEntryName(entryPath, baseDir),
|
Name: entryName,
|
||||||
Method: zip.Deflate,
|
Method: zip.Deflate,
|
||||||
Modified: info.ModTime(),
|
Modified: info.ModTime(),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Log(logger.LevelDebug, "unable to create zip entry %#v: %v", entryPath, err)
|
conn.Log(logger.LevelError, "unable to create zip entry %#v: %v", entryPath, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = io.Copy(f, reader)
|
_, err = io.Copy(f, reader)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func getZipEntryName(entryPath, baseDir string) string {
|
func getZipEntryName(entryPath, baseDir string) (string, error) {
|
||||||
|
if !strings.HasPrefix(entryPath, baseDir) {
|
||||||
|
return "", fmt.Errorf("entry path %q is outside base dir %q", entryPath, baseDir)
|
||||||
|
}
|
||||||
entryPath = strings.TrimPrefix(entryPath, baseDir)
|
entryPath = strings.TrimPrefix(entryPath, baseDir)
|
||||||
return strings.TrimPrefix(entryPath, "/")
|
return strings.TrimPrefix(entryPath, "/"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkDownloadFileFromShare(share *dataprovider.Share, info os.FileInfo) error {
|
func checkDownloadFileFromShare(share *dataprovider.Share, info os.FileInfo) error {
|
||||||
|
|
|
@ -11555,8 +11555,8 @@ func TestShareUsage(t *testing.T) {
|
||||||
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
share.Scope = dataprovider.ShareScopeReadWrite
|
share.Scope = dataprovider.ShareScopeRead
|
||||||
share.Paths = []string{"/missing"}
|
share.Paths = []string{"/missing1", "/missing2"}
|
||||||
err = dataprovider.UpdateShare(&share, user.Username, "")
|
err = dataprovider.UpdateShare(&share, user.Username, "")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
|
|
@ -1811,6 +1811,11 @@ func TestZipErrors(t *testing.T) {
|
||||||
assert.Contains(t, err.Error(), "write error")
|
assert.Contains(t, err.Error(), "write error")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = addZipEntry(wr, connection, "/"+filepath.Base(testDir), path.Join("/", filepath.Base(testDir), "dir"))
|
||||||
|
if assert.Error(t, err) {
|
||||||
|
assert.Contains(t, err.Error(), "is outside base dir")
|
||||||
|
}
|
||||||
|
|
||||||
testFilePath := filepath.Join(testDir, "ziptest.zip")
|
testFilePath := filepath.Join(testDir, "ziptest.zip")
|
||||||
err = os.WriteFile(testFilePath, util.GenerateRandomBytes(65535), os.ModePerm)
|
err = os.WriteFile(testFilePath, util.GenerateRandomBytes(65535), os.ModePerm)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
|
@ -191,7 +191,7 @@ func ByteCountIEC(b int64) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func byteCount(b int64, unit int64, maxPrecision bool) string {
|
func byteCount(b int64, unit int64, maxPrecision bool) string {
|
||||||
if b <= 0 {
|
if b <= 0 && maxPrecision {
|
||||||
return strconv.FormatInt(b, 10)
|
return strconv.FormatInt(b, 10)
|
||||||
}
|
}
|
||||||
if b < unit {
|
if b < unit {
|
||||||
|
|
Loading…
Reference in a new issue