fix: harden webdav directory copies
This commit is contained in:
@@ -1236,19 +1236,28 @@ func (f *webdavStorageFS) Copy(ctx context.Context, name, dest string, opts *web
|
||||
return false, err
|
||||
}
|
||||
if info.IsDir() {
|
||||
if opts != nil && opts.NoOverwrite {
|
||||
if err := preflightCopyNoOverwrite(f.store, src, dst); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
if err := f.store.MkdirAll(dst); err != nil {
|
||||
return false, err
|
||||
}
|
||||
items, err := f.store.List(src)
|
||||
if err != nil {
|
||||
if !dstExists {
|
||||
_ = f.store.Remove(dst)
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
for _, item := range items {
|
||||
created, err := f.Copy(ctx, path.Join(src, item.Name()), path.Join(dst, item.Name()), opts)
|
||||
if err != nil {
|
||||
if _, err := f.Copy(ctx, path.Join(src, item.Name()), path.Join(dst, item.Name()), opts); err != nil {
|
||||
if !dstExists {
|
||||
_ = f.store.Remove(dst)
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
_ = created
|
||||
}
|
||||
return !dstExists, nil
|
||||
}
|
||||
@@ -1257,26 +1266,59 @@ func (f *webdavStorageFS) Copy(ctx context.Context, name, dest string, opts *web
|
||||
return false, err
|
||||
}
|
||||
defer in.Close()
|
||||
if err := copyFileToDestination(f.store, in, dst, dstExists); err != nil {
|
||||
if err := copyFileToDestination(f.store, in, dst); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return !dstExists, nil
|
||||
}
|
||||
|
||||
func copyFileToDestination(st storage.Storage, src io.Reader, dst string, dstExists bool) error {
|
||||
tmp := fmt.Sprintf("%s.chfs-copy-%d", dst, time.Now().UnixNano())
|
||||
for i := 0; i < 16; i++ {
|
||||
if _, err := st.Stat(tmp); os.IsNotExist(err) {
|
||||
break
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
tmp = fmt.Sprintf("%s.chfs-copy-%d-%d", dst, time.Now().UnixNano(), i)
|
||||
func preflightCopyNoOverwrite(st storage.Storage, src, dst string) error {
|
||||
if _, err := st.Stat(dst); err == nil {
|
||||
return webdav.NewHTTPError(http.StatusPreconditionFailed, fmt.Errorf("destination exists"))
|
||||
} else if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
out, err := createExclusive(st, tmp)
|
||||
info, err := st.Stat(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
items, err := st.List(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, item := range items {
|
||||
if err := preflightCopyNoOverwrite(st, path.Join(src, item.Name()), path.Join(dst, item.Name())); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyFileToDestination(st storage.Storage, src io.Reader, dst string) error {
|
||||
var tmp string
|
||||
var out *os.File
|
||||
for i := 0; i < 16; i++ {
|
||||
suffix := fmt.Sprintf("%d", time.Now().UnixNano())
|
||||
if i > 0 {
|
||||
suffix = fmt.Sprintf("%s-%d", suffix, i)
|
||||
}
|
||||
candidate := fmt.Sprintf("%s.chfs-copy-%s", dst, suffix)
|
||||
var err error
|
||||
out, err = createExclusive(st, candidate)
|
||||
if err == nil {
|
||||
tmp = candidate
|
||||
break
|
||||
}
|
||||
if !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if out == nil {
|
||||
return fmt.Errorf("unable to allocate temporary copy path")
|
||||
}
|
||||
_, copyErr := io.Copy(out, src)
|
||||
closeErr := out.Close()
|
||||
if copyErr != nil {
|
||||
@@ -1291,7 +1333,6 @@ func copyFileToDestination(st storage.Storage, src io.Reader, dst string, dstExi
|
||||
_ = st.Remove(tmp)
|
||||
return err
|
||||
}
|
||||
_ = dstExists
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user