Added support for RFC 959's REST command (restart), closing SF patch
#101187, which some modifications. Specifically, ntransfercmd(), transfercmd(), and retrbinary() all grow an optional `rest' argument, which if not None, is used as the argument to an FTP REST comman dbefore the socket is returned. Differences from the SF patch: - always compare against None with `is' or `is not' instead of == or != - no parens around conditional - RFC 959 defines the argument to REST is a string containing any ASCII characters in the range [33..126]. Therefore, we use the %s format character instead of %f or %d as suggested in the patch's comments. Note that we do /not/ sanity checkthe contents of the rest argument (but we'll document this in the library reference manual).
This commit is contained in:
parent
e0d9a83bea
commit
100d81e8e3
|
@ -1,7 +1,6 @@
|
||||||
"""An FTP client class and some helper functions.
|
"""An FTP client class and some helper functions.
|
||||||
|
|
||||||
Based on RFC 959: File Transfer Protocol
|
Based on RFC 959: File Transfer Protocol (FTP), by J. Postel and J. Reynolds
|
||||||
(FTP), by J. Postel and J. Reynolds
|
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
|
@ -235,7 +234,9 @@ class FTP:
|
||||||
return self.voidresp()
|
return self.voidresp()
|
||||||
|
|
||||||
def sendport(self, host, port):
|
def sendport(self, host, port):
|
||||||
'''Send a PORT command with the current host and the given port number.'''
|
'''Send a PORT command with the current host and the given
|
||||||
|
port number.
|
||||||
|
'''
|
||||||
hbytes = string.splitfields(host, '.')
|
hbytes = string.splitfields(host, '.')
|
||||||
pbytes = [`port/256`, `port%256`]
|
pbytes = [`port/256`, `port%256`]
|
||||||
bytes = hbytes + pbytes
|
bytes = hbytes + pbytes
|
||||||
|
@ -253,25 +254,35 @@ class FTP:
|
||||||
resp = self.sendport(host, port)
|
resp = self.sendport(host, port)
|
||||||
return sock
|
return sock
|
||||||
|
|
||||||
def ntransfercmd(self, cmd):
|
def ntransfercmd(self, cmd, rest=None):
|
||||||
'''Initiate a transfer over the data connection.
|
"""Initiate a transfer over the data connection.
|
||||||
If the transfer is active, send a port command and
|
|
||||||
the transfer command, and accept the connection.
|
If the transfer is active, send a port command and the
|
||||||
If the server is passive, send a pasv command, connect
|
transfer command, and accept the connection. If the server is
|
||||||
to it, and start the transfer command.
|
passive, send a pasv command, connect to it, and start the
|
||||||
Either way, return the socket for the connection and
|
transfer command. Either way, return the socket for the
|
||||||
the expected size of the transfer. The expected size
|
connection and the expected size of the transfer. The
|
||||||
may be None if it could not be determined.'''
|
expected size may be None if it could not be determined.
|
||||||
|
|
||||||
|
Optional `rest' argument can be a string that is sent as the
|
||||||
|
argument to a RESTART command. This is essentially a server
|
||||||
|
marker used to tell the server to skip over any data up to the
|
||||||
|
given marker.
|
||||||
|
"""
|
||||||
size = None
|
size = None
|
||||||
if self.passiveserver:
|
if self.passiveserver:
|
||||||
host, port = parse227(self.sendcmd('PASV'))
|
host, port = parse227(self.sendcmd('PASV'))
|
||||||
conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
conn=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
conn.connect((host, port))
|
conn.connect((host, port))
|
||||||
|
if rest is not None:
|
||||||
|
self.sendcmd("REST %s" % rest)
|
||||||
resp = self.sendcmd(cmd)
|
resp = self.sendcmd(cmd)
|
||||||
if resp[0] <> '1':
|
if resp[0] <> '1':
|
||||||
raise error_reply, resp
|
raise error_reply, resp
|
||||||
else:
|
else:
|
||||||
sock = self.makeport()
|
sock = self.makeport()
|
||||||
|
if rest is not None:
|
||||||
|
self.sendcmd("REST %s" % rest)
|
||||||
resp = self.sendcmd(cmd)
|
resp = self.sendcmd(cmd)
|
||||||
if resp[0] <> '1':
|
if resp[0] <> '1':
|
||||||
raise error_reply, resp
|
raise error_reply, resp
|
||||||
|
@ -281,10 +292,9 @@ class FTP:
|
||||||
size = parse150(resp)
|
size = parse150(resp)
|
||||||
return conn, size
|
return conn, size
|
||||||
|
|
||||||
def transfercmd(self, cmd):
|
def transfercmd(self, cmd, rest=None):
|
||||||
'''Initiate a transfer over the data connection. Returns
|
"""Like nstransfercmd() but returns only the socket."""
|
||||||
the socket for the connection. See also ntransfercmd().'''
|
return self.ntransfercmd(cmd, rest)[0]
|
||||||
return self.ntransfercmd(cmd)[0]
|
|
||||||
|
|
||||||
def login(self, user = '', passwd = '', acct = ''):
|
def login(self, user = '', passwd = '', acct = ''):
|
||||||
'''Login, default anonymous.'''
|
'''Login, default anonymous.'''
|
||||||
|
@ -312,13 +322,18 @@ class FTP:
|
||||||
raise error_reply, resp
|
raise error_reply, resp
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
def retrbinary(self, cmd, callback, blocksize=8192):
|
def retrbinary(self, cmd, callback, blocksize=8192, rest=None):
|
||||||
'''Retrieve data in binary mode.
|
"""Retrieve data in binary mode.
|
||||||
The argument is a RETR command.
|
|
||||||
The callback function is called for each block.
|
`cmd' is a RETR command. `callback' is a callback function is
|
||||||
This creates a new port for you'''
|
called for each block. No more than `blocksize' number of
|
||||||
|
bytes will be read from the socket. Optional `rest' is passed
|
||||||
|
to transfercmd().
|
||||||
|
|
||||||
|
A new port is created for you. Return the response code.
|
||||||
|
"""
|
||||||
self.voidcmd('TYPE I')
|
self.voidcmd('TYPE I')
|
||||||
conn = self.transfercmd(cmd)
|
conn = self.transfercmd(cmd, rest)
|
||||||
while 1:
|
while 1:
|
||||||
data = conn.recv(blocksize)
|
data = conn.recv(blocksize)
|
||||||
if not data:
|
if not data:
|
||||||
|
|
Loading…
Reference in New Issue