pkg / sftp

SFTP support for the go.crypto/ssh package
BSD 2-Clause "Simplified" License
1.52k stars 380 forks source link

Inconsistent Result of Client.Stat() -> os.FileInfo.Name() for Remote Windows Machine #602

Closed jgriegershs closed 2 weeks ago

jgriegershs commented 2 weeks ago

Problem

When retrieving the file name of a file on a remote Windows machine like:

...
info, _ := sftpClient.Stat("c:\\temp\\my-file")

remoteFileName := info.Name()
...

remoteFileName will be "c:\temp\my-file" instead of the expected my-file.

This is due to path.Base("path") calls in the stat implementations, e.g.: https://github.com/pkg/sftp/blob/c8fe1f69640c3d92b05e1d7f0072addd6ece3ed2/client.go#L420

The name info does not come from the remote SFTP server, but is determined locally, expecting Unix paths.

Solution Proposal

I don't know the SFTP protocol in depth, maybe the file name can be determine on the remote machine.

My current workaround is to determine the remote OS (either Linux or Windows) by retrieving the remote working dir with Getwd() and checking for the prefix /home/. Based on that info, either path.Base() or filepath.Base() is being used for determining the file name (the code gets executed on a Windows machine, so filepath.Base() will return the correct file name when the remote path belongs to a Windows machine).

What do you think?

puellanivis commented 2 weeks ago

1) File Names in SSH are defined to assume the use of the / character as a directory separator. https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-02#section-6.2 Thus the use of path.Base in this case is the correct operation. 2) The SSH_FXP_STAT is defined to return on success the SSH_FXP_ATTRS packet, which contains only attributes and no filename. If you want to get the real filename of any particular path use the RealPath() https://pkg.go.dev/github.com/pkg/sftp#Client.RealPath call.

If your Windows host is not transcoding native filepaths to some form of / encoding, then it is not acting in a standards compliant way. Let us know which one it is, and we can then consider changes that could increase compatibility. But we’re always going to stick as closely to the standard as we can.

You could also use filepath.ToSlash() before sending any filenames. Windows accepts C:/temp/my-file as a filename just as much as using a backslash.

jgriegershs commented 2 weeks ago

You could also use filepath.ToSlash() before sending any filenames. Windows accepts C:/temp/my-file as a filename just as much as using a backslash.

I will try this for the remote OSs, thank you for the quick response. I'll be back 😊.

jgriegershs commented 2 weeks ago

Normalizing all paths with filepath.ToSlash() did the trick (even "slashing" the results of filepath.WalkDir() and filepath.Rel is needed on Windows).

Thanks!