Ticket #5411: 5411-3.diff

File 5411-3.diff, 10.0 KB (added by adiroiban, 4 years ago)
  • twisted/protocols/ftp.py

    diff --git a/twisted/protocols/ftp.py b/twisted/protocols/ftp.py
    index 19504a7..bb517b6 100644
    a b from twisted.protocols import basic, policies 
    3030
    3131from twisted.python import log, failure, filepath
    3232from twisted.python.compat import reduce
     33from twisted.python.reflect import qual
    3334
    3435from twisted.cred import error as cred_error, portal, credentials, checkers
    3536
    class DTP(object, protocol.Protocol): 
    417418            self._onConnLost.callback(None)
    418419
    419420    def sendLine(self, line):
     421        """
     422        Send a line to data channel.
     423
     424        @type  line: L{bytes}
     425        @param line: The line to be sent.
     426        """
     427        if isinstance(line, unicode):
     428            warnings.warn(
     429                "Unicode date received in %s. "
     430                "Encoded to UTF-8. Please send bytes." % (
     431                    qual(self.__class__),),
     432                category=DeprecationWarning,
     433                stacklevel=2,
     434                )
     435            line = line.encode('utf-8')
     436
    420437        self.transport.write(line + '\r\n')
    421438
    422439
    class FTP(object, basic.LineReceiver, policies.TimeoutMixin): 
    951968        def gotListing(results):
    952969            self.reply(DATA_CNX_ALREADY_OPEN_START_XFR)
    953970            for (name, attrs) in results:
    954                 self.dtpInstance.sendListResponse(name, attrs)
     971                nameEncoded = self._getEncodedFilename(name)
     972                self.dtpInstance.sendListResponse(nameEncoded, attrs)
    955973            self.dtpInstance.transport.loseConnection()
    956974            return (TXFR_COMPLETE_OK,)
    957975
    class FTP(object, basic.LineReceiver, policies.TimeoutMixin): 
    10141032            self.reply(DATA_CNX_ALREADY_OPEN_START_XFR)
    10151033            for (name, ignored) in results:
    10161034                if not glob or (glob and fnmatch.fnmatch(name, glob)):
    1017                     self.dtpInstance.sendLine(name)
     1035                    nameEncoded = self._getEncodedFilename(name)
     1036                    self.dtpInstance.sendLine(nameEncoded)
    10181037            self.dtpInstance.transport.loseConnection()
    10191038            return (TXFR_COMPLETE_OK,)
    10201039
    class FTP(object, basic.LineReceiver, policies.TimeoutMixin): 
    10491068        return d
    10501069
    10511070
     1071    def _getEncodedFilename(self, name):
     1072        """
     1073        Return the UTF-8 encoded filename from an Unicode filename received
     1074        from IFTPShell.list.
     1075        """
     1076        try:
     1077            result = name.encode('utf-8')
     1078        except UnicodeDecodeError:
     1079            warnings.warn(
     1080                "Non-Unicode date received in %s from IFTPShell.list. "
     1081                "Data transmitted without "
     1082                "encoding it. Please send Unicode." % (qual(self.__class__),),
     1083                category=DeprecationWarning,
     1084                stacklevel=1,
     1085                )
     1086            result = name
     1087        return result
     1088
     1089
    10521090    def ftp_CWD(self, path):
    10531091        try:
    10541092            segments = toSegments(self.workingDirectory, path)
  • twisted/test/test_ftp.py

    diff --git a/twisted/test/test_ftp.py b/twisted/test/test_ftp.py
    index 7ba434e..a61b518 100644
    a b from twisted.internet.interfaces import IConsumer 
    2222from twisted.cred.error import UnauthorizedLogin
    2323from twisted.cred import portal, checkers, credentials
    2424from twisted.python import failure, filepath, runtime
     25from twisted.python.reflect import qual
    2526from twisted.test import proto_helpers
    2627
    2728from twisted.protocols import ftp, loopback
    class BasicFTPServerTestCase(FTPServerTestCase): 
    513514            )
    514515        return d
    515516
     517
     518    def test_getEncodedFilename_unicode(self):
     519        """
     520        When Unicode filenames are received from IFTPShell.list it will
     521        be encoded to UTF-8.
     522        """
     523        unicodeFilename = u'my resum\xe9'
     524
     525        encodedFilename = self.serverProtocol._getEncodedFilename(
     526            unicodeFilename)
     527
     528        self.assertEqual(
     529            unicodeFilename.encode('utf-8'), encodedFilename)
     530
     531
     532    def test_getEncodedFilename_non_unicode(self):
     533        """
     534        When non-Unicode filenames are received from IFTPShell.list it will
     535        pass the data without changing it together with raising a warning.
     536        """
     537        alreadyEncodedFilename = u'my resum\xe9'.encode('utf-8')
     538
     539        result = self.assertWarns(
     540            DeprecationWarning,
     541            "Non-Unicode date received in %s from IFTPShell.list. "
     542                "Data transmitted without encoding it. "
     543                "Please send Unicode." % (
     544                    qual(self.serverProtocol.__class__)),
     545            ftp.__file__,
     546            self.serverProtocol._getEncodedFilename, alreadyEncodedFilename)
     547
     548        self.assertIdentical(alreadyEncodedFilename, result)
     549
     550
    516551class FTPServerTestCaseAdvancedClient(FTPServerTestCase):
    517552    """
    518553    Test FTP server with the L{ftp.FTPClient} class.
    class FTPServerPasvDataConnectionTestCase(FTPServerTestCase): 
    672707            self.assertEqual('', download)
    673708        return d.addCallback(checkDownload)
    674709
     710
     711    def test_LISTUnicode(self):
     712        """
     713        LIST will receive Unicode filenames from IFTPShell.list, and will
     714        encode them using UTF-8.
     715        """
     716        d = self._anonymousLogin()
     717
     718        def patchedFTPShellList(me, segments):
     719            """
     720            Mock method that patches the IFTPShell.list.
     721            """
     722            return defer.succeed([(
     723                u'my resum\xe9', (0, 1, 0777, 0, 0, 'user', 'group'))])
     724
     725        def patchFTPShellList(result):
     726            """
     727            Patch the IFTPShell.list, once we got an instance.
     728            """
     729            self.patch(self.serverProtocol.shell, 'list', patchedFTPShellList)
     730            return result
     731        d.addCallback(patchFTPShellList)
     732
     733        self._download('LIST something', chainDeferred=d)
     734
     735        def checkDownload(download):
     736            self.assertEqual(
     737                'drwxrwxrwx   0 user      group                   '
     738                '0 Jan 01  1970 my resum\xc3\xa9\r\n',
     739                download)
     740        return d.addCallback(checkDownload)
     741
     742
    675743    def testManyLargeDownloads(self):
    676744        # Login
    677745        d = self._anonymousLogin()
    class FTPServerPasvDataConnectionTestCase(FTPServerTestCase): 
    756824        return d.addCallback(checkDownload)
    757825
    758826
     827    def test_NLSTUnicode(self):
     828        """
     829        NLST will receive Unicode filenames from IFTPShell.list, and will
     830        encode them using UTF-8.
     831        """
     832        d = self._anonymousLogin()
     833
     834        def patchedFTPShellList(me):
     835            """
     836            Mock method that patches the IFTPShell.list.
     837            """
     838            return defer.succeed([(u'my resum\xe9', None)])
     839
     840        def patchFTPShellList(result):
     841            """
     842            Patch the IFTPShell.list, once we got an instance.
     843            """
     844            self.patch(self.serverProtocol.shell, 'list', patchedFTPShellList)
     845            return result
     846        d.addCallback(patchFTPShellList)
     847
     848        self._download('NLST something', chainDeferred=d)
     849
     850        def checkDownload(download):
     851            self.assertEqual('my resum\xc3\xa9\r\n', download)
     852
     853        return d.addCallback(checkDownload)
     854
     855
    759856    def test_NLSTOnPathToFile(self):
    760857        """
    761858        NLST on an existent file returns only the path to that file.
    class FTPServerPasvDataConnectionTestCase(FTPServerTestCase): 
    767864        self.dirPath.child('test.txt').touch()
    768865
    769866        self._download('NLST test.txt', chainDeferred=d)
     867
    770868        def checkDownload(download):
    771869            filenames = download[:-2].split('\r\n')
    772870            self.assertEqual(['test.txt'], filenames)
    class DTPFactoryTests(unittest.TestCase): 
    9821080        return d
    9831081
    9841082
     1083class DTPTests(unittest.TestCase):
     1084    """
     1085    Tests for L{ftp.DTP}.
     1086
     1087    The DTP instances in these tests are generated using
     1088    DTPFactory.buildProtocol()
     1089    """
     1090
     1091    def setUp(self):
     1092        """
     1093        Create a fake protocol interpreter, a L{ftp.DTPFactory} instance,
     1094        and dummy transport to help with tests.
     1095        """
     1096        self.reactor = task.Clock()
     1097
     1098        class ProtocolInterpreter(object):
     1099            dtpInstance = None
     1100
     1101        self.protocolInterpreter = ProtocolInterpreter()
     1102        self.factory = ftp.DTPFactory(
     1103            self.protocolInterpreter, None, self.reactor)
     1104        self.transport = proto_helpers.StringTransportWithDisconnection()
     1105
     1106
     1107    def test_sendLine_newline(self):
     1108        """
     1109        When sending a line, the newline delimiter will be automatically
     1110        added.
     1111        """
     1112        dtpInstance = self.factory.buildProtocol(None)
     1113        dtpInstance.makeConnection(self.transport)
     1114        lineContent = 'line content'
     1115
     1116        dtpInstance.sendLine(lineContent)
     1117
     1118        dataSent = self.transport.value()
     1119        self.assertEqual(lineContent + '\r\n', dataSent)
     1120
     1121
     1122    def test_sendLine_unicode(self):
     1123        """
     1124        When sending an unicode line, it will be converted to str and
     1125        a warning is raised.
     1126        """
     1127        from twisted.trial import _synctest
     1128        dtpInstance = self.factory.buildProtocol(None)
     1129        dtpInstance.makeConnection(self.transport)
     1130        lineContent = u'my resum\xe9'
     1131
     1132        self.assertWarns(
     1133            DeprecationWarning,
     1134            "Unicode date received in %s. "
     1135                "Encoded to UTF-8. Please send bytes." % (
     1136                    qual(dtpInstance.__class__)),
     1137            _synctest.__file__,
     1138            dtpInstance.sendLine, lineContent)
     1139
     1140        dataSent = self.transport.value()
     1141        self.assertTrue(isinstance(dataSent, str))
     1142        self.assertEqual(lineContent.encode('utf-8') + '\r\n', dataSent)
     1143
     1144
    9851145
    9861146# -- Client Tests -----------------------------------------------------------
    9871147
  • new file twisted/topfiles/5411.bugfix

    diff --git a/twisted/topfiles/5411.bugfix b/twisted/topfiles/5411.bugfix
    new file mode 100644
    index 0000000..bce9487
    - +  
     1twisted.protocols.ftp.FTP 'ftp_LIST' and 'ftp_NLST' will encode to UTF-8 all Unicode filenames received from IFTPShell.
     2 No newline at end of file