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
ftplib: command response shift - mismatch #69644
Comments
When handling the transfer socket manually the asynchronous status message "226 transfer complete" on the control socket is falsely taken as response for the last sent command. I can avoid that by using the undocumented ftplib internal method FTP.getline() after the transfer socket is closed and not sending more commands while the transfer socket is open. It would be useful, if ftplib empties the response socket buffer before sending the next command. But maybe the best solution is an optional function callback when the "226" response appears, while it is ignored when not matching the last sent command. Example code that triggers the problem: import ftplib
import socket
import re
ftp = ftplib.FTP()
ftp.set_debuglevel(1)
ftp.connect('ftp.debian.org', timeout=10)
ftp.login('anonymous','user@example.com')
ftp.sendcmd('TYPE A')
s = ftp.transfercmd('LIST') ''' fp = s.makefile('r')
fp.read()
fp.close()
s.close()
#ftplib falsely sees "226 transfer complete" as response to next command
ftp.sendcmd('NOOP') |
I'm not sure I understood the problem. Perhaps if you provide a patch that would make things more clear and you'd have more chances to see this issue fixed. |
The problem in my example is ftplib reports a "226" response to command "NOOP" which is nonsense. ftplib received "226" before "ftp.sendcmd('NOOP')" was called. Since "transfercmd()" returns a socket, ftplib was planned to allow for manual transfer socket handling, but it is currently unable to handle the asynchronous responses from the server when the transfer is done. This is a bug or design error. Multiple changes are needed to support manual transfer socket handling. I suggest the following: Since asynchronous responses from the server are possible on the command socket, "set_debuglevel(1)" must report them at once, but this would require multithreading. A good compromise is to debug print and clear any buffered status right before sending the next command. New attribute this.last_status_code = None #has no effect to any command or debug output
this.last_status_message New internal method #loop: look for buffered status response; if present, print debug and assign this.last_status = buffer.pop() New user methods #Set last status to None so we can use "get_last_status" to check/poll for the next one. #Return the last status received from the server; if there is one on the buffer, unbuffer and return that. |
I've updated "ftplib.py" from the 3.5.1 source code release. |
Here is a small test for the new version. (To see the original ftplib.py version failing copy+paste the code from my initial post into a python file and run) |
The current solution looks fishy to me. We should stick to https://tools.ietf.org/html/rfc959 . In particular, 226 is sent when the server closes the auxiliary socket, so the module should react accordingly. Debug printing and/or issuing warnings is an obvious no-go. Also attached a text file illustrating the problem. |
Found the root problem: a 1xx response doesn't complete a LIST command, it should wait further for a 2xx one. See RFC 959 section 6 (state diagrams). This could be |
Darn, my problem _is_ in urllib and thus is different that the one in this ticket. Though it too results in a "command response shift". |
The solution for the OP's problem is:
The built-in But the documentation doesn't mention this requirement in
|
Spawned the `urllib' issue to bpo-28931. |
I tried to fix Since The plan to fix follows, but first, some background: According to http://stackoverflow.com/questions/2549829/using-ftplib-for-multithread-uploads , FTP actually doesn't, and is not designed to, handle transfers in "background". In that you surely can send a further command on the socket, but the server won't actually read it until it's done with the transfer. According to http://stackoverflow.com/questions/31560701/ftp-data-connections-reuse , data connections cannot be reused. (RFC959 is vague on both points) Now, the proposed fix design:
Each option has drawbacks, so I'm not sure which one is the most pythonic. |
One more concern about the fix (so that you don't assume I didn't think of this ;) ) - handling of errors signified by the end-of-transfer response. Handling a response in a close handler prevents us from actually checking its code:
Now,
Looks like fixing this part warrants a separate ticket, though it does affect which option to take at this step - it speaks in favor of wrapping the data socket. I'll ask at python-dev for some feedback before I go any way. |
Maybe it's me but I still fail to understand what's the issue here. From what I understand from the last message(s) it seems there is complaint about not being able to use ftplib with threads. |
There isn't a single mention of "thread" in either. I can rub your nose in it, but I thought you're better than that :) Oh well... C:\Ivan>python
Python 2.7.12 (v2.7.12:d33e0cf91556, Jun 27 2016, 15:19:22) [MSC v.1500 32 bit Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import ftplib
>>> ftp = ftplib.FTP()
>>> ftp.connect('ftp.debian.org', timeout=10)
'220 ftp.debian.org FTP server'
>>> ftp.login('anonymous','user@example.com')
'230 Login successful.'
>>> ftp.sendcmd('TYPE A')
'200 Switching to ASCII mode.'
>>> s = ftp.transfercmd('LIST')
>>> fp = s.makefile('r')
>>> fp.read()
'drwxr-xr-x 9 1176 1176 4096 Dec 14 15:08 debian\r\n'
>>> fp.close()
>>> s.close()
>>> # Now the session is broken:
...
>>> ftp.sendcmd('NOOP')
'226 Directory send OK.'
>>> ftp.dir()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "C:\Py\lib\ftplib.py", line 534, in dir
self.retrlines(cmd, func)
File "C:\Py\lib\ftplib.py", line 437, in retrlines
conn = self.transfercmd(cmd)
File "C:\Py\lib\ftplib.py", line 376, in transfercmd
return self.ntransfercmd(cmd, rest)[0]
File "C:\Py\lib\ftplib.py", line 334, in ntransfercmd
host, port = self.makepasv()
File "C:\Py\lib\ftplib.py", line 312, in makepasv
host, port = parse227(self.sendcmd('PASV'))
File "C:\Py\lib\ftplib.py", line 830, in parse227
raise error_reply, resp
ftplib.error_reply: 200 Switching to ASCII mode. |
Python 3.5.2 (v3.5.2:4def2a2901a5, Jun 25 2016, 22:01:18) [MSC v.1900 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import ftplib
<...>
>>> ftp.sendcmd('NOOP')
'226 Directory send OK.' There are no changes in and `ntransfercmd' between 2.7 and default branches save for cosmetic ones. |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: