Browse Source

sftpd: add Readlink support

Nicola Murino 5 years ago
parent
commit
02e35ee002

+ 1 - 0
common/common.go

@@ -146,6 +146,7 @@ type ActiveTransfer interface {
 	GetStartTime() time.Time
 	SignalClose()
 	Truncate(fsPath string, size int64) (int64, error)
+	GetRealFsPath(fsPath string) string
 }
 
 // ActiveConnection defines the interface for the current active connections

+ 15 - 3
common/connection.go

@@ -172,6 +172,18 @@ func (c *BaseConnection) SignalTransfersAbort() error {
 	return nil
 }
 
+func (c *BaseConnection) getRealFsPath(fsPath string) string {
+	c.RLock()
+	defer c.RUnlock()
+
+	for _, t := range c.activeTransfers {
+		if p := t.GetRealFsPath(fsPath); len(p) > 0 {
+			return p
+		}
+	}
+	return fsPath
+}
+
 func (c *BaseConnection) truncateOpenHandle(fsPath string, size int64) (int64, error) {
 	c.RLock()
 	defer c.RUnlock()
@@ -439,7 +451,7 @@ func (c *BaseConnection) SetStat(fsPath, virtualPath string, attributes *StatAtt
 		if !c.User.HasPerm(dataprovider.PermChmod, pathForPerms) {
 			return c.GetPermissionDeniedError()
 		}
-		if err := c.Fs.Chmod(fsPath, attributes.Mode); err != nil {
+		if err := c.Fs.Chmod(c.getRealFsPath(fsPath), attributes.Mode); err != nil {
 			c.Log(logger.LevelWarn, "failed to chmod path %#v, mode: %v, err: %+v", fsPath, attributes.Mode.String(), err)
 			return c.GetFsError(err)
 		}
@@ -451,7 +463,7 @@ func (c *BaseConnection) SetStat(fsPath, virtualPath string, attributes *StatAtt
 		if !c.User.HasPerm(dataprovider.PermChown, pathForPerms) {
 			return c.GetPermissionDeniedError()
 		}
-		if err := c.Fs.Chown(fsPath, attributes.UID, attributes.GID); err != nil {
+		if err := c.Fs.Chown(c.getRealFsPath(fsPath), attributes.UID, attributes.GID); err != nil {
 			c.Log(logger.LevelWarn, "failed to chown path %#v, uid: %v, gid: %v, err: %+v", fsPath, attributes.UID,
 				attributes.GID, err)
 			return c.GetFsError(err)
@@ -465,7 +477,7 @@ func (c *BaseConnection) SetStat(fsPath, virtualPath string, attributes *StatAtt
 			return c.GetPermissionDeniedError()
 		}
 
-		if err := c.Fs.Chtimes(fsPath, attributes.Atime, attributes.Mtime); err != nil {
+		if err := c.Fs.Chtimes(c.getRealFsPath(fsPath), attributes.Atime, attributes.Mtime); err != nil {
 			c.Log(logger.LevelWarn, "failed to chtimes for path %#v, access time: %v, modification time: %v, err: %+v",
 				fsPath, attributes.Atime, attributes.Mtime, err)
 			return c.GetFsError(err)

+ 2 - 2
common/connection_test.go

@@ -605,7 +605,7 @@ func TestSpaceForCrossRename(t *testing.T) {
 		testDir := filepath.Join(os.TempDir(), "dir")
 		err = os.MkdirAll(testDir, os.ModePerm)
 		assert.NoError(t, err)
-		err = ioutil.WriteFile(filepath.Join(testDir, "afile"), []byte("content"), os.ModePerm)
+		err = ioutil.WriteFile(filepath.Join(testDir, "afile.txt"), []byte("content"), os.ModePerm)
 		assert.NoError(t, err)
 		err = os.Chmod(testDir, 0001)
 		assert.NoError(t, err)
@@ -616,7 +616,7 @@ func TestSpaceForCrossRename(t *testing.T) {
 		assert.NoError(t, err)
 	}
 
-	testFile := filepath.Join(os.TempDir(), "afile")
+	testFile := filepath.Join(os.TempDir(), "afile.txt")
 	err = ioutil.WriteFile(testFile, []byte("test data"), os.ModePerm)
 	assert.NoError(t, err)
 	quotaResult = vfs.QuotaCheckResult{

+ 12 - 0
common/transfer.go

@@ -108,6 +108,18 @@ func (t *BaseTransfer) GetFsPath() string {
 	return t.fsPath
 }
 
+// GetRealFsPath returns the real transfer filesystem path.
+// If atomic uploads are enabled this differ from fsPath
+func (t *BaseTransfer) GetRealFsPath(fsPath string) string {
+	if fsPath == t.GetFsPath() {
+		if t.File != nil {
+			return t.File.Name()
+		}
+		return t.fsPath
+	}
+	return ""
+}
+
 // SetCancelFn sets the cancel function for the transfer
 func (t *BaseTransfer) SetCancelFn(cancelFn func()) {
 	t.cancelFn = cancelFn

+ 32 - 2
common/transfer_test.go

@@ -83,6 +83,36 @@ func TestTransferThrottling(t *testing.T) {
 	assert.NoError(t, err)
 }
 
+func TestRealPath(t *testing.T) {
+	testFile := filepath.Join(os.TempDir(), "afile.txt")
+	fs := vfs.NewOsFs("123", os.TempDir(), nil)
+	u := dataprovider.User{
+		Username: "user",
+		HomeDir:  os.TempDir(),
+	}
+	u.Permissions = make(map[string][]string)
+	u.Permissions["/"] = []string{dataprovider.PermAny}
+	file, err := os.Create(testFile)
+	if !assert.NoError(t, err) {
+		assert.FailNow(t, "unable to open test file")
+	}
+	conn := NewBaseConnection(fs.ConnectionID(), ProtocolSFTP, u, fs)
+	transfer := NewBaseTransfer(file, conn, nil, testFile, "/transfer_test_file", TransferUpload, 0, 0, 0, true, fs)
+	rPath := transfer.GetRealFsPath(testFile)
+	assert.Equal(t, testFile, rPath)
+	rPath = conn.getRealFsPath(testFile)
+	assert.Equal(t, testFile, rPath)
+	err = transfer.Close()
+	assert.NoError(t, err)
+	err = file.Close()
+	assert.NoError(t, err)
+	transfer.File = nil
+	rPath = transfer.GetRealFsPath(testFile)
+	assert.Equal(t, testFile, rPath)
+	rPath = transfer.GetRealFsPath("")
+	assert.Empty(t, rPath)
+}
+
 func TestTruncate(t *testing.T) {
 	testFile := filepath.Join(os.TempDir(), "transfer_test_file")
 	fs := vfs.NewOsFs("123", os.TempDir(), nil)
@@ -99,14 +129,14 @@ func TestTruncate(t *testing.T) {
 	_, err = file.Write([]byte("hello"))
 	assert.NoError(t, err)
 	conn := NewBaseConnection(fs.ConnectionID(), ProtocolSFTP, u, fs)
-	transfer := NewBaseTransfer(file, conn, nil, testFile, "/transfer_test_file", TransferUpload, 0, 0, 100, true, fs)
+	transfer := NewBaseTransfer(file, conn, nil, testFile, "/transfer_test_file", TransferUpload, 0, 5, 100, false, fs)
 
 	err = conn.SetStat(testFile, "/transfer_test_file", &StatAttributes{
 		Size:  2,
 		Flags: StatAttrSize,
 	})
 	assert.NoError(t, err)
-	assert.Equal(t, int64(98), transfer.MaxWriteSize)
+	assert.Equal(t, int64(103), transfer.MaxWriteSize)
 	err = transfer.Close()
 	assert.NoError(t, err)
 	err = file.Close()

+ 1 - 1
dataprovider/user.go

@@ -227,7 +227,7 @@ func (u *User) AddVirtualDirs(list []os.FileInfo, sftpPath string) []os.FileInfo
 	}
 	for _, v := range u.VirtualFolders {
 		if path.Dir(v.VirtualPath) == sftpPath {
-			fi := vfs.NewFileInfo(v.VirtualPath, true, 0, time.Now())
+			fi := vfs.NewFileInfo(v.VirtualPath, true, 0, time.Now(), false)
 			found := false
 			for index, f := range list {
 				if f.Name() == fi.Name() {

+ 17 - 0
sftpd/handler.go

@@ -195,6 +195,23 @@ func (c *Connection) Filelist(request *sftp.Request) (sftp.ListerAt, error) {
 		}
 
 		return listerAt([]os.FileInfo{s}), nil
+	case "Readlink":
+		if !c.User.HasPerm(dataprovider.PermListItems, path.Dir(request.Filepath)) {
+			return nil, sftp.ErrSSHFxPermissionDenied
+		}
+
+		s, err := c.Fs.Readlink(p)
+		if err != nil {
+			c.Log(logger.LevelWarn, "error running readlink on path %#v: %+v", p, err)
+			return nil, c.GetFsError(err)
+		}
+
+		if !c.User.HasPerm(dataprovider.PermListItems, path.Dir(s)) {
+			return nil, sftp.ErrSSHFxPermissionDenied
+		}
+
+		return listerAt([]os.FileInfo{vfs.NewFileInfo(s, false, 0, time.Now(), true)}), nil
+
 	default:
 		return nil, sftp.ErrSSHFxOpUnsupported
 	}

+ 11 - 0
sftpd/internal_test.go

@@ -229,6 +229,17 @@ func TestReadWriteErrors(t *testing.T) {
 	assert.NoError(t, err)
 }
 
+func TestUnsupportedListOP(t *testing.T) {
+	fs := vfs.NewOsFs("", os.TempDir(), nil)
+	conn := common.NewBaseConnection("", common.ProtocolSFTP, dataprovider.User{}, fs)
+	sftpConn := Connection{
+		BaseConnection: conn,
+	}
+	request := sftp.NewRequest("Unsupported", "")
+	_, err := sftpConn.Filelist(request)
+	assert.EqualError(t, err, sftp.ErrSSHFxOpUnsupported.Error())
+}
+
 func TestTransferCancelFn(t *testing.T) {
 	testfile := "testfile"
 	file, err := os.Create(testfile)

+ 40 - 3
sftpd/sftpd_test.go

@@ -608,8 +608,9 @@ func TestLink(t *testing.T) {
 		assert.NoError(t, err)
 		err = client.Symlink(testFileName, testFileName+".link")
 		assert.NoError(t, err)
-		_, err = client.ReadLink(testFileName + ".link")
-		assert.Error(t, err, "readlink is currently not implemented so must fail")
+		linkName, err := client.ReadLink(testFileName + ".link")
+		assert.NoError(t, err)
+		assert.Equal(t, path.Join("/", testFileName), linkName)
 		err = client.Symlink(testFileName, testFileName+".link")
 		assert.Error(t, err, "creating a symlink to an existing one must fail")
 		err = client.Link(testFileName, testFileName+".hlink")
@@ -657,7 +658,12 @@ func TestStat(t *testing.T) {
 			assert.Equal(t, newPerm, newFi.Mode().Perm())
 		}
 		_, err = client.ReadLink(testFileName)
-		assert.Error(t, err, "readlink is not supported and must fail")
+		assert.Error(t, err, "readlink on a file must fail")
+		err = client.Symlink(testFileName, testFileName+".sym")
+		assert.NoError(t, err)
+		linkName, err := client.ReadLink(testFileName + ".sym")
+		assert.NoError(t, err)
+		assert.Equal(t, path.Join("/", testFileName), linkName)
 		newPerm = os.FileMode(0666)
 		err = client.Chmod(testFileName, newPerm)
 		assert.NoError(t, err)
@@ -674,6 +680,21 @@ func TestStat(t *testing.T) {
 			err = f.Close()
 			assert.NoError(t, err)
 		}
+		f, err = client.OpenFile(testFileName, os.O_WRONLY)
+		newPerm = os.FileMode(0444)
+		if assert.NoError(t, err) {
+			err = f.Chmod(newPerm)
+			assert.NoError(t, err)
+			err = f.Close()
+			assert.NoError(t, err)
+		}
+		newFi, err = client.Lstat(testFileName)
+		if assert.NoError(t, err) {
+			assert.Equal(t, newPerm, newFi.Mode().Perm())
+		}
+		newPerm = os.FileMode(0666)
+		err = client.Chmod(testFileName, newPerm)
+		assert.NoError(t, err)
 		err = os.Remove(testFilePath)
 		assert.NoError(t, err)
 	}
@@ -4650,6 +4671,7 @@ func TestPermList(t *testing.T) {
 	u.Permissions["/"] = []string{dataprovider.PermDownload, dataprovider.PermUpload, dataprovider.PermDelete, dataprovider.PermRename,
 		dataprovider.PermCreateDirs, dataprovider.PermCreateSymlinks, dataprovider.PermOverwrite, dataprovider.PermChmod,
 		dataprovider.PermChown, dataprovider.PermChtimes}
+	u.Permissions["/sub"] = []string{dataprovider.PermCreateSymlinks, dataprovider.PermListItems}
 	user, _, err := httpd.AddUser(u, http.StatusOK)
 	assert.NoError(t, err)
 	client, err := getSftpClient(user, usePubKey)
@@ -4659,6 +4681,21 @@ func TestPermList(t *testing.T) {
 		assert.Error(t, err, "read remote dir without permission should not succeed")
 		_, err = client.Stat("test_file")
 		assert.Error(t, err, "stat remote file without permission should not succeed")
+		_, err = client.ReadLink("test_link")
+		assert.Error(t, err, "read remote link without permission on source dir should not succeed")
+		f, err := client.Create(testFileName)
+		if assert.NoError(t, err) {
+			_, err = f.Write([]byte("content"))
+			assert.NoError(t, err)
+			err = f.Close()
+			assert.NoError(t, err)
+		}
+		err = client.Mkdir("sub")
+		assert.NoError(t, err)
+		err = client.Symlink(testFileName, path.Join("/sub", testFileName))
+		assert.NoError(t, err)
+		_, err = client.ReadLink(path.Join("/sub", testFileName))
+		assert.Error(t, err, "read remote link without permission on targe dir should not succeed")
 	}
 	_, err = httpd.RemoveUser(user, http.StatusOK)
 	assert.NoError(t, err)

+ 6 - 2
vfs/fileinfo.go

@@ -21,16 +21,20 @@ type FileInfo struct {
 }
 
 // NewFileInfo creates file info.
-func NewFileInfo(name string, isDirectory bool, sizeInBytes int64, modTime time.Time) FileInfo {
+func NewFileInfo(name string, isDirectory bool, sizeInBytes int64, modTime time.Time, fullName bool) FileInfo {
 	mode := os.FileMode(0644)
 	contentType := ""
 	if isDirectory {
 		mode = os.FileMode(0755) | os.ModeDir
 		contentType = "inode/directory"
 	}
+	if !fullName {
+		// we have always Unix style paths here
+		name = path.Base(name)
+	}
 
 	return FileInfo{
-		name:        path.Base(name), // we have always Unix style paths here
+		name:        name,
 		sizeInBytes: sizeInBytes,
 		modTime:     modTime,
 		mode:        mode,

+ 13 - 8
vfs/gcsfs.go

@@ -86,10 +86,10 @@ func (fs GCSFs) Stat(name string) (os.FileInfo, error) {
 		if err != nil {
 			return result, err
 		}
-		return NewFileInfo(name, true, 0, time.Now()), nil
+		return NewFileInfo(name, true, 0, time.Now(), false), nil
 	}
 	if fs.config.KeyPrefix == name+"/" {
-		return NewFileInfo(name, true, 0, time.Now()), nil
+		return NewFileInfo(name, true, 0, time.Now(), false), nil
 	}
 	prefix := fs.getPrefixForStat(name)
 	query := &storage.Query{Prefix: prefix, Delimiter: "/"}
@@ -108,7 +108,7 @@ func (fs GCSFs) Stat(name string) (os.FileInfo, error) {
 		}
 		if len(attrs.Prefix) > 0 {
 			if fs.isEqual(attrs.Prefix, name) {
-				result = NewFileInfo(name, true, 0, time.Now())
+				result = NewFileInfo(name, true, 0, time.Now(), false)
 				break
 			}
 		} else {
@@ -117,7 +117,7 @@ func (fs GCSFs) Stat(name string) (os.FileInfo, error) {
 			}
 			if fs.isEqual(attrs.Name, name) {
 				isDir := strings.HasSuffix(attrs.Name, "/")
-				result = NewFileInfo(name, isDir, attrs.Size, attrs.Updated)
+				result = NewFileInfo(name, isDir, attrs.Size, attrs.Updated, false)
 				if !isDir {
 					result.setContentType(attrs.ContentType)
 				}
@@ -280,6 +280,11 @@ func (GCSFs) Symlink(source, target string) error {
 	return errors.New("403 symlinks are not supported")
 }
 
+// Readlink returns the destination of the named symbolic link
+func (GCSFs) Readlink(name string) (string, error) {
+	return "", errors.New("403 readlink is not supported")
+}
+
 // Chown changes the numeric uid and gid of the named file.
 // Silently ignored.
 func (GCSFs) Chown(name string, uid int, gid int) error {
@@ -333,7 +338,7 @@ func (fs GCSFs) ReadDir(dirname string) ([]os.FileInfo, error) {
 		}
 		if len(attrs.Prefix) > 0 {
 			name, _ := fs.resolve(attrs.Prefix, prefix)
-			result = append(result, NewFileInfo(name, true, 0, time.Now()))
+			result = append(result, NewFileInfo(name, true, 0, time.Now(), false))
 		} else {
 			name, isDir := fs.resolve(attrs.Name, prefix)
 			if len(name) == 0 {
@@ -342,7 +347,7 @@ func (fs GCSFs) ReadDir(dirname string) ([]os.FileInfo, error) {
 			if !attrs.Deleted.IsZero() {
 				continue
 			}
-			fi := NewFileInfo(name, isDir, attrs.Size, attrs.Updated)
+			fi := NewFileInfo(name, isDir, attrs.Size, attrs.Updated, false)
 			if !isDir {
 				fi.setContentType(attrs.ContentType)
 			}
@@ -508,13 +513,13 @@ func (fs GCSFs) Walk(root string, walkFn filepath.WalkFunc) error {
 		if len(name) == 0 {
 			continue
 		}
-		err = walkFn(attrs.Name, NewFileInfo(name, isDir, attrs.Size, attrs.Updated), nil)
+		err = walkFn(attrs.Name, NewFileInfo(name, isDir, attrs.Size, attrs.Updated, false), nil)
 		if err != nil {
 			break
 		}
 	}
 
-	walkFn(root, NewFileInfo(root, true, 0, time.Now()), err) //nolint:errcheck
+	walkFn(root, NewFileInfo(root, true, 0, time.Now(), false), err) //nolint:errcheck
 	metrics.GCSListObjectsCompleted(err)
 	return err
 }

+ 12 - 2
vfs/osfs.go

@@ -56,7 +56,7 @@ func (fs OsFs) Stat(name string) (os.FileInfo, error) {
 	}
 	for _, v := range fs.virtualFolders {
 		if v.MappedPath == name {
-			info := NewFileInfo(v.VirtualPath, true, fi.Size(), fi.ModTime())
+			info := NewFileInfo(v.VirtualPath, true, fi.Size(), fi.ModTime(), false)
 			return info, nil
 		}
 	}
@@ -71,7 +71,7 @@ func (fs OsFs) Lstat(name string) (os.FileInfo, error) {
 	}
 	for _, v := range fs.virtualFolders {
 		if v.MappedPath == name {
-			info := NewFileInfo(v.VirtualPath, true, fi.Size(), fi.ModTime())
+			info := NewFileInfo(v.VirtualPath, true, fi.Size(), fi.ModTime(), false)
 			return info, nil
 		}
 	}
@@ -116,6 +116,16 @@ func (OsFs) Symlink(source, target string) error {
 	return os.Symlink(source, target)
 }
 
+// Readlink returns the destination of the named symbolic link
+// as absolute virtual path
+func (fs OsFs) Readlink(name string) (string, error) {
+	p, err := os.Readlink(name)
+	if err != nil {
+		return p, err
+	}
+	return fs.GetRelativePath(p), err
+}
+
 // Chown changes the numeric uid and gid of the named file.
 func (OsFs) Chown(name string, uid int, gid int) error {
 	return os.Chown(name, uid, gid)

+ 13 - 8
vfs/s3fs.go

@@ -112,10 +112,10 @@ func (fs S3Fs) Stat(name string) (os.FileInfo, error) {
 		if err != nil {
 			return result, err
 		}
-		return NewFileInfo(name, true, 0, time.Now()), nil
+		return NewFileInfo(name, true, 0, time.Now(), false), nil
 	}
 	if "/"+fs.config.KeyPrefix == name+"/" {
-		return NewFileInfo(name, true, 0, time.Now()), nil
+		return NewFileInfo(name, true, 0, time.Now(), false), nil
 	}
 	prefix := path.Dir(name)
 	if prefix == "/" || prefix == "." {
@@ -135,7 +135,7 @@ func (fs S3Fs) Stat(name string) (os.FileInfo, error) {
 	}, func(page *s3.ListObjectsV2Output, lastPage bool) bool {
 		for _, p := range page.CommonPrefixes {
 			if fs.isEqual(p.Prefix, name) {
-				result = NewFileInfo(name, true, 0, time.Now())
+				result = NewFileInfo(name, true, 0, time.Now(), false)
 				return false
 			}
 		}
@@ -144,7 +144,7 @@ func (fs S3Fs) Stat(name string) (os.FileInfo, error) {
 				objectSize := *fileObject.Size
 				objectModTime := *fileObject.LastModified
 				isDir := strings.HasSuffix(*fileObject.Key, "/")
-				result = NewFileInfo(name, isDir, objectSize, objectModTime)
+				result = NewFileInfo(name, isDir, objectSize, objectModTime, false)
 				return false
 			}
 		}
@@ -312,6 +312,11 @@ func (S3Fs) Symlink(source, target string) error {
 	return errors.New("403 symlinks are not supported")
 }
 
+// Readlink returns the destination of the named symbolic link
+func (S3Fs) Readlink(name string) (string, error) {
+	return "", errors.New("403 readlink is not supported")
+}
+
 // Chown changes the numeric uid and gid of the named file.
 // Silently ignored.
 func (S3Fs) Chown(name string, uid int, gid int) error {
@@ -358,7 +363,7 @@ func (fs S3Fs) ReadDir(dirname string) ([]os.FileInfo, error) {
 	}, func(page *s3.ListObjectsV2Output, lastPage bool) bool {
 		for _, p := range page.CommonPrefixes {
 			name, isDir := fs.resolve(p.Prefix, prefix)
-			result = append(result, NewFileInfo(name, isDir, 0, time.Now()))
+			result = append(result, NewFileInfo(name, isDir, 0, time.Now(), false))
 		}
 		for _, fileObject := range page.Contents {
 			objectSize := *fileObject.Size
@@ -367,7 +372,7 @@ func (fs S3Fs) ReadDir(dirname string) ([]os.FileInfo, error) {
 			if len(name) == 0 {
 				continue
 			}
-			result = append(result, NewFileInfo(name, isDir, objectSize, objectModTime))
+			result = append(result, NewFileInfo(name, isDir, objectSize, objectModTime, false))
 		}
 		return true
 	})
@@ -505,7 +510,7 @@ func (fs S3Fs) Walk(root string, walkFn filepath.WalkFunc) error {
 			if len(name) == 0 {
 				continue
 			}
-			err := walkFn(fs.Join("/", *fileObject.Key), NewFileInfo(name, isDir, objectSize, objectModTime), nil)
+			err := walkFn(fs.Join("/", *fileObject.Key), NewFileInfo(name, isDir, objectSize, objectModTime, false), nil)
 			if err != nil {
 				return false
 			}
@@ -513,7 +518,7 @@ func (fs S3Fs) Walk(root string, walkFn filepath.WalkFunc) error {
 		return true
 	})
 	metrics.S3ListObjectsCompleted(err)
-	walkFn(root, NewFileInfo(root, true, 0, time.Now()), err) //nolint:errcheck
+	walkFn(root, NewFileInfo(root, true, 0, time.Now(), false), err) //nolint:errcheck
 
 	return err
 }

+ 1 - 0
vfs/vfs.go

@@ -33,6 +33,7 @@ type Fs interface {
 	Chtimes(name string, atime, mtime time.Time) error
 	Truncate(name string, size int64) error
 	ReadDir(dirname string) ([]os.FileInfo, error)
+	Readlink(name string) (string, error)
 	IsUploadResumeSupported() bool
 	IsAtomicUploadSupported() bool
 	CheckRootPath(username string, uid int, gid int) bool

+ 1 - 1
webdavd/file.go

@@ -98,7 +98,7 @@ func (f *webDavFile) Stat() (os.FileInfo, error) {
 	f.Unlock()
 	if f.GetType() == common.TransferUpload && closed && errUpload == nil {
 		info := webDavFileInfo{
-			FileInfo: vfs.NewFileInfo(f.GetFsPath(), false, atomic.LoadInt64(&f.BytesReceived), time.Now()),
+			FileInfo: vfs.NewFileInfo(f.GetFsPath(), false, atomic.LoadInt64(&f.BytesReceived), time.Now(), false),
 			file:     f,
 		}
 		return info, nil

+ 6 - 6
webdavd/internal_test.go

@@ -79,11 +79,11 @@ func (fs MockOsFs) Rename(source, target string) error {
 // Walk returns a duplicate path for testing
 func (fs MockOsFs) Walk(root string, walkFn filepath.WalkFunc) error {
 	if fs.err == errWalkDir {
-		walkFn("fsdpath", vfs.NewFileInfo("dpath", true, 0, time.Now()), nil) //nolint:errcheck
-		walkFn("fsdpath", vfs.NewFileInfo("dpath", true, 0, time.Now()), nil) //nolint:errcheck
+		walkFn("fsdpath", vfs.NewFileInfo("dpath", true, 0, time.Now(), false), nil) //nolint:errcheck
+		walkFn("fsdpath", vfs.NewFileInfo("dpath", true, 0, time.Now(), false), nil) //nolint:errcheck
 		return nil
 	}
-	walkFn("fsfpath", vfs.NewFileInfo("fpath", false, 0, time.Now()), nil) //nolint:errcheck
+	walkFn("fsfpath", vfs.NewFileInfo("fpath", false, 0, time.Now(), false), nil) //nolint:errcheck
 	return fs.err
 }
 
@@ -317,12 +317,12 @@ func TestFileAccessErrors(t *testing.T) {
 	if assert.Error(t, err) {
 		assert.EqualError(t, err, os.ErrNotExist.Error())
 	}
-	info := vfs.NewFileInfo(missingPath, true, 0, time.Now())
+	info := vfs.NewFileInfo(missingPath, true, 0, time.Now(), false)
 	_, err = connection.getFile(fsMissingPath, missingPath, info)
 	if assert.Error(t, err) {
 		assert.EqualError(t, err, os.ErrNotExist.Error())
 	}
-	info = vfs.NewFileInfo(missingPath, false, 123, time.Now())
+	info = vfs.NewFileInfo(missingPath, false, 123, time.Now(), false)
 	_, err = connection.getFile(fsMissingPath, missingPath, info)
 	if assert.Error(t, err) {
 		assert.EqualError(t, err, os.ErrNotExist.Error())
@@ -429,7 +429,7 @@ func TestContentType(t *testing.T) {
 	ctx := context.Background()
 	baseTransfer := common.NewBaseTransfer(nil, connection.BaseConnection, nil, testFilePath, testFile,
 		common.TransferDownload, 0, 0, 0, false, fs)
-	info := vfs.NewFileInfo(testFilePath, true, 0, time.Now())
+	info := vfs.NewFileInfo(testFilePath, true, 0, time.Now(), false)
 	davFile := newWebDavFile(baseTransfer, nil, nil, info)
 	fi, err := davFile.Stat()
 	if assert.NoError(t, err) {