Ignore:
Timestamp:
Mar 19, 2014, 11:31:01 PM (11 years ago)
Author:
dmik
Message:

python: Merge vendor 2.7.6 to trunk.

Location:
python/trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • python/trunk

  • python/trunk/Lib/test/test_ftplib.py

    r2 r391  
    11"""Test script for ftplib module."""
    22
    3 # Modified by Giampaolo Rodola' to test FTP class and IPv6 environment
     3# Modified by Giampaolo Rodola' to test FTP class, IPv6 and TLS
     4# environment
    45
    56import ftplib
    6 import threading
    77import asyncore
    88import asynchat
    99import socket
    1010import StringIO
     11import errno
     12import os
     13try:
     14    import ssl
     15except ImportError:
     16    ssl = None
    1117
    1218from unittest import TestCase
    1319from test import test_support
    14 from test.test_support import HOST
     20from test.test_support import HOST, HOSTv6
     21threading = test_support.import_module('threading')
    1522
    1623
     
    2330
    2431class DummyDTPHandler(asynchat.async_chat):
     32    dtp_conn_closed = False
    2533
    2634    def __init__(self, conn, baseclass):
     
    3341
    3442    def handle_close(self):
    35         self.baseclass.push('226 transfer complete')
    36         self.close()
     43        # XXX: this method can be called many times in a row for a single
     44        # connection, including in clear-text (non-TLS) mode.
     45        # (behaviour witnessed with test_data_connection)
     46        if not self.dtp_conn_closed:
     47            self.baseclass.push('226 transfer complete')
     48            self.close()
     49            self.dtp_conn_closed = True
     50
     51    def handle_error(self):
     52        raise
    3753
    3854
    3955class DummyFTPHandler(asynchat.async_chat):
     56
     57    dtp_handler = DummyDTPHandler
    4058
    4159    def __init__(self, conn):
     
    4765        self.last_received_data = ''
    4866        self.next_response = ''
     67        self.rest = None
     68        self.next_retr_data = RETR_DATA
    4969        self.push('220 welcome')
    5070
     
    81101        ip = '%d.%d.%d.%d' %tuple(addr[:4])
    82102        port = (addr[4] * 256) + addr[5]
    83         s = socket.create_connection((ip, port), timeout=2)
    84         self.dtp = DummyDTPHandler(s, baseclass=self)
     103        s = socket.create_connection((ip, port), timeout=10)
     104        self.dtp = self.dtp_handler(s, baseclass=self)
    85105        self.push('200 active data connection established')
    86106
     
    89109        sock.bind((self.socket.getsockname()[0], 0))
    90110        sock.listen(5)
    91         sock.settimeout(2)
     111        sock.settimeout(10)
    92112        ip, port = sock.getsockname()[:2]
    93         ip = ip.replace('.', ','); p1 = port / 256; p2 = port % 256
     113        ip = ip.replace('.', ',')
     114        p1, p2 = divmod(port, 256)
    94115        self.push('227 entering passive mode (%s,%d,%d)' %(ip, p1, p2))
    95116        conn, addr = sock.accept()
    96         self.dtp = DummyDTPHandler(conn, baseclass=self)
     117        self.dtp = self.dtp_handler(conn, baseclass=self)
    97118
    98119    def cmd_eprt(self, arg):
    99120        af, ip, port = arg.split(arg[0])[1:-1]
    100121        port = int(port)
    101         s = socket.create_connection((ip, port), timeout=2)
    102         self.dtp = DummyDTPHandler(s, baseclass=self)
     122        s = socket.create_connection((ip, port), timeout=10)
     123        self.dtp = self.dtp_handler(s, baseclass=self)
    103124        self.push('200 active data connection established')
    104125
     
    107128        sock.bind((self.socket.getsockname()[0], 0))
    108129        sock.listen(5)
    109         sock.settimeout(2)
     130        sock.settimeout(10)
    110131        port = sock.getsockname()[1]
    111132        self.push('229 entering extended passive mode (|||%d|)' %port)
    112133        conn, addr = sock.accept()
    113         self.dtp = DummyDTPHandler(conn, baseclass=self)
     134        self.dtp = self.dtp_handler(conn, baseclass=self)
    114135
    115136    def cmd_echo(self, arg):
     
    160181        self.push('125 stor ok')
    161182
     183    def cmd_rest(self, arg):
     184        self.rest = arg
     185        self.push('350 rest ok')
     186
    162187    def cmd_retr(self, arg):
    163188        self.push('125 retr ok')
    164         self.dtp.push(RETR_DATA)
     189        if self.rest is not None:
     190            offset = int(self.rest)
     191        else:
     192            offset = 0
     193        self.dtp.push(self.next_retr_data[offset:])
    165194        self.dtp.close_when_done()
     195        self.rest = None
    166196
    167197    def cmd_list(self, arg):
     
    174204        self.dtp.push(NLST_DATA)
    175205        self.dtp.close_when_done()
     206
     207    def cmd_setlongretr(self, arg):
     208        # For testing. Next RETR will return long line.
     209        self.next_retr_data = 'x' * int(arg)
     210        self.push('125 setlongretr ok')
    176211
    177212
     
    226261
    227262
     263if ssl is not None:
     264
     265    CERTFILE = os.path.join(os.path.dirname(__file__), "keycert.pem")
     266
     267    class SSLConnection(object, asyncore.dispatcher):
     268        """An asyncore.dispatcher subclass supporting TLS/SSL."""
     269
     270        _ssl_accepting = False
     271        _ssl_closing = False
     272
     273        def secure_connection(self):
     274            self.socket = ssl.wrap_socket(self.socket, suppress_ragged_eofs=False,
     275                                          certfile=CERTFILE, server_side=True,
     276                                          do_handshake_on_connect=False,
     277                                          ssl_version=ssl.PROTOCOL_SSLv23)
     278            self._ssl_accepting = True
     279
     280        def _do_ssl_handshake(self):
     281            try:
     282                self.socket.do_handshake()
     283            except ssl.SSLError, err:
     284                if err.args[0] in (ssl.SSL_ERROR_WANT_READ,
     285                                   ssl.SSL_ERROR_WANT_WRITE):
     286                    return
     287                elif err.args[0] == ssl.SSL_ERROR_EOF:
     288                    return self.handle_close()
     289                raise
     290            except socket.error, err:
     291                if err.args[0] == errno.ECONNABORTED:
     292                    return self.handle_close()
     293            else:
     294                self._ssl_accepting = False
     295
     296        def _do_ssl_shutdown(self):
     297            self._ssl_closing = True
     298            try:
     299                self.socket = self.socket.unwrap()
     300            except ssl.SSLError, err:
     301                if err.args[0] in (ssl.SSL_ERROR_WANT_READ,
     302                                   ssl.SSL_ERROR_WANT_WRITE):
     303                    return
     304            except socket.error, err:
     305                # Any "socket error" corresponds to a SSL_ERROR_SYSCALL return
     306                # from OpenSSL's SSL_shutdown(), corresponding to a
     307                # closed socket condition. See also:
     308                # http://www.mail-archive.com/openssl-users@openssl.org/msg60710.html
     309                pass
     310            self._ssl_closing = False
     311            super(SSLConnection, self).close()
     312
     313        def handle_read_event(self):
     314            if self._ssl_accepting:
     315                self._do_ssl_handshake()
     316            elif self._ssl_closing:
     317                self._do_ssl_shutdown()
     318            else:
     319                super(SSLConnection, self).handle_read_event()
     320
     321        def handle_write_event(self):
     322            if self._ssl_accepting:
     323                self._do_ssl_handshake()
     324            elif self._ssl_closing:
     325                self._do_ssl_shutdown()
     326            else:
     327                super(SSLConnection, self).handle_write_event()
     328
     329        def send(self, data):
     330            try:
     331                return super(SSLConnection, self).send(data)
     332            except ssl.SSLError, err:
     333                if err.args[0] in (ssl.SSL_ERROR_EOF, ssl.SSL_ERROR_ZERO_RETURN,
     334                                   ssl.SSL_ERROR_WANT_READ,
     335                                   ssl.SSL_ERROR_WANT_WRITE):
     336                    return 0
     337                raise
     338
     339        def recv(self, buffer_size):
     340            try:
     341                return super(SSLConnection, self).recv(buffer_size)
     342            except ssl.SSLError, err:
     343                if err.args[0] in (ssl.SSL_ERROR_WANT_READ,
     344                                   ssl.SSL_ERROR_WANT_WRITE):
     345                    return ''
     346                if err.args[0] in (ssl.SSL_ERROR_EOF, ssl.SSL_ERROR_ZERO_RETURN):
     347                    self.handle_close()
     348                    return ''
     349                raise
     350
     351        def handle_error(self):
     352            raise
     353
     354        def close(self):
     355            if (isinstance(self.socket, ssl.SSLSocket) and
     356                self.socket._sslobj is not None):
     357                self._do_ssl_shutdown()
     358
     359
     360    class DummyTLS_DTPHandler(SSLConnection, DummyDTPHandler):
     361        """A DummyDTPHandler subclass supporting TLS/SSL."""
     362
     363        def __init__(self, conn, baseclass):
     364            DummyDTPHandler.__init__(self, conn, baseclass)
     365            if self.baseclass.secure_data_channel:
     366                self.secure_connection()
     367
     368
     369    class DummyTLS_FTPHandler(SSLConnection, DummyFTPHandler):
     370        """A DummyFTPHandler subclass supporting TLS/SSL."""
     371
     372        dtp_handler = DummyTLS_DTPHandler
     373
     374        def __init__(self, conn):
     375            DummyFTPHandler.__init__(self, conn)
     376            self.secure_data_channel = False
     377
     378        def cmd_auth(self, line):
     379            """Set up secure control channel."""
     380            self.push('234 AUTH TLS successful')
     381            self.secure_connection()
     382
     383        def cmd_pbsz(self, line):
     384            """Negotiate size of buffer for secure data transfer.
     385            For TLS/SSL the only valid value for the parameter is '0'.
     386            Any other value is accepted but ignored.
     387            """
     388            self.push('200 PBSZ=0 successful.')
     389
     390        def cmd_prot(self, line):
     391            """Setup un/secure data channel."""
     392            arg = line.upper()
     393            if arg == 'C':
     394                self.push('200 Protection set to Clear')
     395                self.secure_data_channel = False
     396            elif arg == 'P':
     397                self.push('200 Protection set to Private')
     398                self.secure_data_channel = True
     399            else:
     400                self.push("502 Unrecognized PROT type (use C or P).")
     401
     402
     403    class DummyTLS_FTPServer(DummyFTPServer):
     404        handler = DummyTLS_FTPHandler
     405
     406
    228407class TestFTPClass(TestCase):
    229408
     
    231410        self.server = DummyFTPServer((HOST, 0))
    232411        self.server.start()
    233         self.client = ftplib.FTP(timeout=2)
     412        self.client = ftplib.FTP(timeout=10)
    234413        self.client.connect(self.server.host, self.server.port)
    235414
     
    302481        self.client.rmd('foo')
    303482
     483    def test_cwd(self):
     484        dir = self.client.cwd('/foo')
     485        self.assertEqual(dir, '250 cwd ok')
     486
     487    def test_mkd(self):
     488        dir = self.client.mkd('/foo')
     489        self.assertEqual(dir, '/foo')
     490
    304491    def test_pwd(self):
    305492        dir = self.client.pwd()
     
    315502        self.client.retrbinary('retr', received.append)
    316503        self.assertEqual(''.join(received), RETR_DATA)
     504
     505    def test_retrbinary_rest(self):
     506        for rest in (0, 10, 20):
     507            received = []
     508            self.client.retrbinary('retr', received.append, rest=rest)
     509            self.assertEqual(''.join(received), RETR_DATA[rest:],
     510                             msg='rest test case %d %d %d' % (rest,
     511                                                              len(''.join(received)),
     512                                                              len(RETR_DATA[rest:])))
    317513
    318514    def test_retrlines(self):
     
    331527        self.assertTrue(flag)
    332528
     529    def test_storbinary_rest(self):
     530        f = StringIO.StringIO(RETR_DATA)
     531        for r in (30, '30'):
     532            f.seek(0)
     533            self.client.storbinary('stor', f, rest=r)
     534            self.assertEqual(self.server.handler.rest, str(r))
     535
    333536    def test_storlines(self):
    334537        f = StringIO.StringIO(RETR_DATA.replace('\r\n', '\n'))
     
    357560    def test_makepasv(self):
    358561        host, port = self.client.makepasv()
    359         conn = socket.create_connection((host, port), 2)
     562        conn = socket.create_connection((host, port), 10)
    360563        conn.close()
    361564        # IPv4 is in use, just make sure send_epsv has not been used
    362565        self.assertEqual(self.server.handler.last_received_cmd, 'pasv')
    363566
     567    def test_line_too_long(self):
     568        self.assertRaises(ftplib.Error, self.client.sendcmd,
     569                          'x' * self.client.maxline * 2)
     570
     571    def test_retrlines_too_long(self):
     572        self.client.sendcmd('SETLONGRETR %d' % (self.client.maxline * 2))
     573        received = []
     574        self.assertRaises(ftplib.Error,
     575                          self.client.retrlines, 'retr', received.append)
     576
     577    def test_storlines_too_long(self):
     578        f = StringIO.StringIO('x' * self.client.maxline * 2)
     579        self.assertRaises(ftplib.Error, self.client.storlines, 'stor', f)
     580
    364581
    365582class TestIPv6Environment(TestCase):
    366583
    367584    def setUp(self):
    368         self.server = DummyFTPServer((HOST, 0), af=socket.AF_INET6)
     585        self.server = DummyFTPServer((HOSTv6, 0), af=socket.AF_INET6)
    369586        self.server.start()
    370587        self.client = ftplib.FTP()
     
    384601    def test_makepasv(self):
    385602        host, port = self.client.makepasv()
    386         conn = socket.create_connection((host, port), 2)
     603        conn = socket.create_connection((host, port), 10)
    387604        conn.close()
    388605        self.assertEqual(self.server.handler.last_received_cmd, 'epsv')
     
    399616
    400617
     618class TestTLS_FTPClassMixin(TestFTPClass):
     619    """Repeat TestFTPClass tests starting the TLS layer for both control
     620    and data connections first.
     621    """
     622
     623    def setUp(self):
     624        self.server = DummyTLS_FTPServer((HOST, 0))
     625        self.server.start()
     626        self.client = ftplib.FTP_TLS(timeout=10)
     627        self.client.connect(self.server.host, self.server.port)
     628        # enable TLS
     629        self.client.auth()
     630        self.client.prot_p()
     631
     632
     633class TestTLS_FTPClass(TestCase):
     634    """Specific TLS_FTP class tests."""
     635
     636    def setUp(self):
     637        self.server = DummyTLS_FTPServer((HOST, 0))
     638        self.server.start()
     639        self.client = ftplib.FTP_TLS(timeout=10)
     640        self.client.connect(self.server.host, self.server.port)
     641
     642    def tearDown(self):
     643        self.client.close()
     644        self.server.stop()
     645
     646    def test_control_connection(self):
     647        self.assertNotIsInstance(self.client.sock, ssl.SSLSocket)
     648        self.client.auth()
     649        self.assertIsInstance(self.client.sock, ssl.SSLSocket)
     650
     651    def test_data_connection(self):
     652        # clear text
     653        sock = self.client.transfercmd('list')
     654        self.assertNotIsInstance(sock, ssl.SSLSocket)
     655        sock.close()
     656        self.assertEqual(self.client.voidresp(), "226 transfer complete")
     657
     658        # secured, after PROT P
     659        self.client.prot_p()
     660        sock = self.client.transfercmd('list')
     661        self.assertIsInstance(sock, ssl.SSLSocket)
     662        sock.close()
     663        self.assertEqual(self.client.voidresp(), "226 transfer complete")
     664
     665        # PROT C is issued, the connection must be in cleartext again
     666        self.client.prot_c()
     667        sock = self.client.transfercmd('list')
     668        self.assertNotIsInstance(sock, ssl.SSLSocket)
     669        sock.close()
     670        self.assertEqual(self.client.voidresp(), "226 transfer complete")
     671
     672    def test_login(self):
     673        # login() is supposed to implicitly secure the control connection
     674        self.assertNotIsInstance(self.client.sock, ssl.SSLSocket)
     675        self.client.login()
     676        self.assertIsInstance(self.client.sock, ssl.SSLSocket)
     677        # make sure that AUTH TLS doesn't get issued again
     678        self.client.login()
     679
     680    def test_auth_issued_twice(self):
     681        self.client.auth()
     682        self.assertRaises(ValueError, self.client.auth)
     683
     684    def test_auth_ssl(self):
     685        try:
     686            self.client.ssl_version = ssl.PROTOCOL_SSLv3
     687            self.client.auth()
     688            self.assertRaises(ValueError, self.client.auth)
     689        finally:
     690            self.client.ssl_version = ssl.PROTOCOL_TLSv1
     691
     692
    401693class TestTimeouts(TestCase):
    402694
     
    404696        self.evt = threading.Event()
    405697        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    406         self.sock.settimeout(3)
     698        self.sock.settimeout(10)
    407699        self.port = test_support.bind_port(self.sock)
    408700        threading.Thread(target=self.server, args=(self.evt,self.sock)).start()
     
    439731    def testTimeoutDefault(self):
    440732        # default -- use global socket timeout
    441         self.assert_(socket.getdefaulttimeout() is None)
     733        self.assertTrue(socket.getdefaulttimeout() is None)
    442734        socket.setdefaulttimeout(30)
    443735        try:
    444             ftp = ftplib.FTP("localhost")
     736            ftp = ftplib.FTP(HOST)
    445737        finally:
    446738            socket.setdefaulttimeout(None)
     
    451743    def testTimeoutNone(self):
    452744        # no timeout -- do not use global socket timeout
    453         self.assert_(socket.getdefaulttimeout() is None)
     745        self.assertTrue(socket.getdefaulttimeout() is None)
    454746        socket.setdefaulttimeout(30)
    455747        try:
    456             ftp = ftplib.FTP("localhost", timeout=None)
     748            ftp = ftplib.FTP(HOST, timeout=None)
    457749        finally:
    458750            socket.setdefaulttimeout(None)
     
    500792        else:
    501793            tests.append(TestIPv6Environment)
     794
     795    if ssl is not None:
     796        tests.extend([TestTLS_FTPClassMixin, TestTLS_FTPClass])
     797
    502798    thread_info = test_support.threading_setup()
    503799    try:
Note: See TracChangeset for help on using the changeset viewer.