Index: ftplib.py
===================================================================
--- ftplib.py	(revision 63185)
+++ 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,105 @@
         Returns:
           The response code.
         """
+        import struct
+
         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:
+                ### Receive one byte at a time, not all 3 at once -- seriously
+                header = ''
+                for i in range(0,3):
+                    header += conn.recv(1)
+
+                (descriptor, blocklength) = struct.unpack('!BH', header)
+
+                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(len(data))
+                        for d in dtl:
+                            print '*data* recv size: %d:\n%s' % (len(d), d)
+                    ### Abort the transfer. Expect 426 response code.
+                    self.abort()
+                    ### Exception 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."
+
+                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:
+                        if self.debugging:
+                            print "*got socket error: %s*" % msg
+                        raise socket.error, msg
+                    if not buff:
+                        if self.debugging:
+                            print "*got no data on recv* final size: %d" % sread
+                        break
+                    if len(buff) > blocklength-sread:
+                        if self.debugging:
+                            print "*got more data than desired!* %d vs %d" % \
+                                (len(buff), blocklength-sread)
+                    sread = sread + len(buff)
+                    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 +518,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 +555,7 @@
           The response code.
         """
         self.voidcmd('TYPE I')
+        self.voidcmd('MODE S')
         conn = self.transfercmd(cmd)
         while 1:
             buf = fp.read(blocksize)
@@ -466,6 +578,7 @@
           The response code.
         """
         self.voidcmd('TYPE A')
+        self.voidcmd('MODE S')
         conn = self.transfercmd(cmd)
         while 1:
             buf = fp.readline()
@@ -575,7 +688,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):
