Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remote path containing backslash not matched on Windows server when using scp.SCPClient.get from Linux client #138

Open
vtyw opened this issue Feb 27, 2020 · 10 comments

Comments

@vtyw
Copy link

vtyw commented Feb 27, 2020

When trying to get a file such as V:\test.txt from a Windows host, this exception occurs:

Traceback (most recent call last):
  ...
    scp.get("V:\\test.txt")
  File "/home/user/.virtualenvs/opencv-4.2.0/lib/python3.6/site-packages/scp.py", line 238, in get
    self._recv_all()
  File "/home/user/.virtualenvs/opencv-4.2.0/lib/python3.6/site-packages/scp.py", line 388, in _recv_all
    raise SCPException(asunicode(msg[1:]))
scp.SCPException: scp: 'V:/test.txt': No such file or directory

It looks like backslashes get converted to forward slashes which would make it impossible to copy anything from a Windows server to a Linux client. I also note that spaces in filenames passed to scp.SCPClient.get are treated as if scp is requesting multiple separate files, so it seems like there is both shell interpretation going on AND some unwarranted Linux-style translation of the path happening on a Linux client.

@remram44
Copy link
Collaborator

I'm not sure I understand the slash issue, does opening "V:/test.txt" not work on Windows, same as "V:\test.txt"?

Escaping spaces was implemented (#9) and is tested for, so if you're seeing problems there it might be another Windows-specific problem?

@vtyw
Copy link
Author

vtyw commented Feb 27, 2020

I'm not sure I understand the slash issue, does opening "V:/test.txt" not work on Windows, same as "V:\test.txt"?

I've just realized that scp expects you to use "/" as the path separator even when copying from a Windows machine. So get("V:/test.txt") does work, whereas get("V:\test.txt") does not and gives the misleading error that 'V:/test.txt' doesn't exist.

As for spaces, if I'm trying to copy "V:\test 1.txt", I've tried:

  • get('V:/sample\ 1.txt') - 'V:/sample: No such file or directory
  • get('"V:/sample 1.txt"') - 'V:/sample 1.txt': No such file or directory
  • get('"V:/sample\ 1.txt"') - 'V:/sample/ 1.txt': No such file or directory
  • get("'V:/sample\ 1.txt'") - ''''V:/sample: No such file or directory

There is clearly something weird going on with the formatting of the output message, though that may be a separate issue to whether escaping works.

@remram44
Copy link
Collaborator

The second version is expected to work (get('"V:/sample 1.txt"')). Are you sure the file exist? Is it "sample" or "test"?

Also all of this might depend on your server implementation (if using cygwin, you might need /cygdrive/v/sample 1.txt, etc)

@vtyw
Copy link
Author

vtyw commented Feb 28, 2020

Pardon my inconsistency with filename above. That aside, I am sure the file exists. For comparison I can copy it without problem using paramiko.SFTPClient:

sftp.get('V:/sample 1.txt', 'sample 1.txt') # Works
scp.get('"V:/sample 1.txt"') # scp.SCPException

I also just discovered that quoting doesn't seem to be acceptable in the first place, with or without spaces in the filename.

scp.get('V:/test.txt') # Works
scp.get('"V:/test.txt"') # SCPException
scp.get("'V:/test.txt'") # SCPException

@remram44
Copy link
Collaborator

remram44 commented Feb 28, 2020

I fear I know what is happening.

Do you mind trying the following:

scp.sanitize = lambda x: x
scp.get('V:/sample^ 1.txt', 's.txt')

@vtyw
Copy link
Author

vtyw commented Feb 28, 2020

>>> scp.sanitize
<function _sh_quote at 0x7f86bb527400>
>>> scp.sanitize = lambda x: x
>>> scp.get('V:/sample^ 1.txt', 's.txt')
scp.SCPException: scp: V:/sample: No such file or directory
>>> scp.get('V:/sample 1.txt', 's.txt')
scp.SCPException: scp: V:/sample: No such file or directory
>>> scp.get('"V:/sample 1.txt"') # Worked!

@remram44
Copy link
Collaborator

Ok well what is apparent is:

  1. The Windows SSH server does shell escaping like a Windows machine. As there is no way to know what kind of server it is talking to, scp.py assumes UNIX-compatible, and therefore the default value of the sanitize constructor argument is meant for UNIX. You will have to provide your own if you know the server is Windows.
  2. I do not understand how shell escaping on Windows works. I wrote this a while back, but it does not seem accurate: https://github.com/VIDA-NYU/reprozip/blob/4d841bd8232c72a8881ea763e12faeed9e97e422/reprounzip-qt/reprounzip_qt/reprounzip_interface.py#L42-L58

@remram44
Copy link
Collaborator

I could change the _sh_quote function to use double quotes instead of single quotes, which would make more filename patterns work on Windows (maybe? Does Windows have single quotes?), but that would still be accidental as those are different shells with different rules, and some characters would still cause breakage.

@vtyw
Copy link
Author

vtyw commented Feb 28, 2020

My understanding is that Windows command-line only supports double-quoted strings.

@remram44
Copy link
Collaborator

I will replace the current _sh_quote with this version then, which should improve the situation. I guess I have to revisit reprozip's quoting functions...

madscientist added a commit to madscientist/scp.py that referenced this issue Dec 4, 2020
The previous scp single-quote algorithm for pathnames worked well for
POSIX servers but had problems on Windows servers, where single quotes
are not recognized (just treated like normal characters).

Use double quotes instead, and implement proper double-quoted escaping
for POSIX strings.  This gives 100% correct paths on POSIX servers but
could still yield incorrect paths on Windows if the path contains
unusual characters like $ or `.  If the user requires 100% Windows
compatibility they'll need to provide their own sanitizer function when
communicating with a Windows server.

Addresses issue jbardin#138 and issue jbardin#147
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants