Index: ftplib.py =================================================================== --- ftplib.py (revision 62329) +++ ftplib.py (working copy) @@ -33,6 +33,7 @@ # Modified by Jack to work on the mac. # Modified by Siebren to support docstrings and PASV. # Modified by Phil Schwartz to add storbinary and storlines callbacks. +# Modified by Jonathan Bell to support block transmission mode in retrbinary. # import os @@ -104,6 +105,8 @@ file = None welcome = None passiveserver = 1 + transmissionmode = 'S' + dataconn = None # Initialization method (called by class instantiation). # Initialize host to localhost, port to standard ftp port @@ -318,7 +321,14 @@ given marker. """ size = None - if self.passiveserver: + if self.dataconn is not None: + if rest is not None: + self.sendcmd("REST %s" % rest) + resp = self.sendcmd(cmd) + if resp[0] != '1': + raise error_reply, resp + conn = self.dataconn + elif self.passiveserver: host, port = self.makepasv() conn = socket.create_connection((host, port), self.timeout) if rest is not None: @@ -334,6 +344,8 @@ resp = self.getresp() if resp[0] != '1': raise error_reply, resp + if self.transmissionmode == 'B': + self.dataconn = conn else: sock = self.makeport() if rest is not None: @@ -345,6 +357,8 @@ if resp[0] != '1': raise error_reply, resp conn, sockaddr = sock.accept() + if self.transmissionmode == 'B': + self.dataconn = conn if resp[:3] == '150': # this is conditional in case we received a 125 size = parse150(resp) @@ -389,14 +403,103 @@ Returns: The response code. """ + import array + self.voidcmd('TYPE I') - conn = self.transfercmd(cmd, rest) - while 1: - data = conn.recv(blocksize) - if not data: - break - callback(data) - conn.close() + self.voidcmd('MODE %s' % self.transmissionmode) + + if self.transmissionmode == 'S': + conn = self.transfercmd(cmd, rest) + while 1: + data = conn.recv(blocksize) + if not data: + break + callback(data) + conn.close() + elif self.transmissionmode == 'B': + conn = self.transfercmd(cmd, rest) + + while 1: + header = array.array('B') + for i in range(0,3): + d = conn.recv(1) + header.append(ord(d)) + + descriptor = header[0] + if ( descriptor >= 0 and descriptor <= 240 ) and \ + ( float(descriptor) / 16 == float(int(descriptor) / 16) ): + pass + else: + if self.debugging: + print '*invalid header block* fileno: %d' % int(conn.fileno()) + print '*data* total size: %d' % int(data.__len__()) + for d in dtl: + print '*data* recv size: %d:\n%s' % (d.__len__(), d) + ### Abort the transfer. Expect 426 response code. + self.abort() + ### Excpetion so close the data connection. + self.close_dataconn() + ### Catch the 226 response code. + self.voidresp() + raise error_proto, "Unexpected header bits. Data block is invalid." + + blocklength = long((header[1])<<8) + (header[2]) + if self.debugging: + print "*header* %d\t%d" % (descriptor, blocklength) + data = '' + dtl = [] + sread = 0 + while 1: + if blocklength == 0: + break + try: + buff = conn.recv(blocklength-sread, 0) + except socket.error, msg: + raise socket.error, msg + if not buff: + if self.debugging: + print "*got no data on recv* final size: %d" % sread + break + if buff.__len__() > blocklength-sread: + if self.debugging: + print "*got more data than desired!* %d vs %d" % \ + (buff.__len__(), blocklength-sread) + sread = sread + buff.__len__() + data = data + buff + dtl.append(buff) + if sread >= blocklength: + break + + if descriptor & 128: + ### End of "record" as defined by file type. + if self.debugging: + print '*end-of-record*' + callback(data) + elif descriptor & 64: + ### End of file. + callback(data) + if self.debugging: + print '*EOF*' + break + elif descriptor & 32: + ### Data is suspect (i.e. "magnetic tape read errors") + callback(data) + if self.debugging: + print '*suspect data*' + elif descriptor & 16: + ### Data block is a restart. Not yet implemented. + self.abort() + if self.debugging: + print '*restart marker*' + raise Exception, "Remote server sent a restart marker. Operation unsupported." + else: + callback(data) + else: + try: + self.close_dataconn() + except: + pass + raise Exception return self.voidresp() def retrlines(self, cmd, callback = None): @@ -413,20 +516,26 @@ """ if callback is None: callback = print_line resp = self.sendcmd('TYPE A') - conn = self.transfercmd(cmd) - fp = conn.makefile('rb') - while 1: - line = fp.readline() - if self.debugging > 2: print '*retr*', repr(line) - if not line: - break - if line[-2:] == CRLF: - line = line[:-2] - elif line[-1:] == '\n': - line = line[:-1] - callback(line) - fp.close() - conn.close() + self.voidcmd('MODE S') + try: + conn = self.transfercmd(cmd) + fp = conn.makefile('rb') + while 1: + line = fp.readline() + if self.debugging > 2: print '*retr*', repr(line) + if not line: + break + if line[-2:] == CRLF: + line = line[:-2] + elif line[-1:] == '\n': + line = line[:-1] + callback(line) + finally: + try: + fp.close() + conn.close() + except: + pass return self.voidresp() def storbinary(self, cmd, fp, blocksize=8192, callback=None): @@ -444,6 +553,7 @@ The response code. """ self.voidcmd('TYPE I') + self.voidcmd('MODE S') conn = self.transfercmd(cmd) while 1: buf = fp.read(blocksize) @@ -466,6 +576,7 @@ The response code. """ self.voidcmd('TYPE A') + self.voidcmd('MODE S') conn = self.transfercmd(cmd) while 1: buf = fp.readline() @@ -575,7 +686,27 @@ self.sock.close() self.file = self.sock = None + def set_transmissionmode(self, mode): + """Set the transmission mode. + Args: + mode: Mode in which the FTP server should transmit files. + S indicates Stream mode, the default mode. + B indicates Block mode, in which data is sent as a series + of data blocks, each preceded by a header specifying + the block length and a descriptor. + C indicates Compressed mode. Not implemented by this library. + + Currently, the mode set is applied only when a client calls retrbinary. + """ + self.transmissionmode = mode + + def close_dataconn(self): + '''Close the persistent data connection.''' + if self.dataconn: + self.dataconn.close() + self.dataconn = None + _150_re = None def parse150(resp):