Ticket #4180: 4180-list-checks-3.diff
| File 4180-list-checks-3.diff, 11.7 KB (added by adiroiban, 11 months ago) |
|---|
-
twisted/protocols/ftp.py
diff --git a/twisted/protocols/ftp.py b/twisted/protocols/ftp.py index b035f04..444accd 100644
a b 25 25 26 26 # Twisted Imports 27 27 from twisted import copyright 28 from twisted.internet import reactor, interfaces, protocol, error, defer28 from twisted.internet import defer, error, interfaces, protocol, reactor, task 29 29 from twisted.protocols import basic, policies 30 30 31 31 from twisted.python import log, failure, filepath … … 72 72 73 73 SVC_NOT_AVAIL_CLOSING_CTRL_CNX = "421.1" 74 74 TOO_MANY_CONNECTIONS = "421.2" 75 CANT_OPEN_DATA_CNX = "425" 75 CANT_OPEN_DATA_CNX = "425.1" 76 DTP_TIMEOUT = "425.2" 76 77 CNX_CLOSED_TXFR_ABORTED = "426" 77 78 REQ_ACTN_ABRTD_FILE_UNAVAIL = "450" 78 79 REQ_ACTN_ABRTD_LOCAL_ERR = "451" … … 141 142 SVC_NOT_AVAIL_CLOSING_CTRL_CNX: '421 Service not available, closing control connection.', 142 143 TOO_MANY_CONNECTIONS: '421 Too many users right now, try again in a few minutes.', 143 144 CANT_OPEN_DATA_CNX: "425 Can't open data connection.", 145 DTP_TIMEOUT: '425 Data channel initialization timed out.', 144 146 CNX_CLOSED_TXFR_ABORTED: '426 Transfer aborted. Data connection closed.', 145 147 146 148 REQ_ACTN_ABRTD_FILE_UNAVAIL: '450 Requested action aborted. File unavailable.', … … 777 779 (self.passivePortRange,)) 778 780 779 781 782 def _checkDataTransportStarted(self, command): 783 """Checks that data transport is ready. 784 785 If data transport was not requested using PORT, PASV etc it raises 786 L{BadCmdSequenceError}. 787 """ 788 if self.dtpInstance is None: 789 raise BadCmdSequenceError( 790 'PORT or PASV required before %s' % (command,)) 791 780 792 def ftp_USER(self, username): 781 793 """ 782 794 First part of login. Get the username the peer wants to … … 829 841 830 842 831 843 def ftp_PASV(self): 832 """Request for a passive connection 844 """ 845 Request for a passive connection 833 846 834 847 from the rfc:: 835 848 … … 876 889 877 890 878 891 def ftp_LIST(self, path=''): 879 """ This command causes a list to be sent from the server to the 880 passive DTP. If the pathname specifies a directory or other 892 """ 893 This command causes a list to be sent from the server to the 894 passive DTP. 895 896 If the pathname specifies a directory or other 881 897 group of files, the server should transfer a list of files 882 in the specified directory. If the pathname specifies a 883 file then the server should send current information on the 884 file. A null argument implies the user's current working or 885 default directory. 898 in the specified directory. 899 If the pathname specifies a file then the server should send current 900 information on the file. 901 A null argument implies the user's current working or default 902 directory. 886 903 """ 887 # Uh, for now, do this retarded thing. 888 if self.dtpInstance is None or not self.dtpInstance.isConnected: 889 return defer.fail(BadCmdSequenceError('must send PORT or PASV before RETR')) 904 self._checkDataTransportStarted('LIST') 890 905 891 906 # bug in konqueror 892 907 if path == "-a": … … 908 923 self.dtpInstance.transport.loseConnection() 909 924 return (TXFR_COMPLETE_OK,) 910 925 926 def ebDTPTimeout(failure): 927 failure.trap(defer.TimeoutError) 928 self.reply(DTP_TIMEOUT) 929 911 930 try: 912 931 segments = toSegments(self.workingDirectory, path) 913 932 except InvalidPath: … … 917 936 segments, 918 937 ('size', 'directory', 'permissions', 'hardlinks', 919 938 'modified', 'owner', 'group')) 939 d.addCallback(self._cbWaitDTPConnectionWithTimeout) 920 940 d.addCallback(gotListing) 941 d.addErrback(ebDTPTimeout) 921 942 return d 922 943 923 944 … … 936 957 @return: a L{Deferred} which will be fired when the listing request 937 958 is finished. 938 959 """ 939 # XXX: why is this check different from ftp_RETR/ftp_STOR? See #4180 940 if self.dtpInstance is None or not self.dtpInstance.isConnected: 941 return defer.fail( 942 BadCmdSequenceError('must send PORT or PASV before RETR')) 943 960 self._checkDataTransportStarted('NLST') 944 961 try: 945 962 segments = toSegments(self.workingDirectory, path) 946 963 except InvalidPath: … … 995 1012 '*' in segments[-1] or '?' in segments[-1] or 996 1013 ('[' in segments[-1] and ']' in segments[-1])): 997 1014 d = self.shell.list(segments[:-1]) 1015 d.addCallback(self._cbWaitDTPConnectionWithTimeout) 998 1016 d.addCallback(cbGlob) 999 1017 else: 1000 1018 d = self.shell.list(segments) 1019 d.addCallback(self._cbWaitDTPConnectionWithTimeout) 1001 1020 d.addCallback(cbList) 1002 # self.shell.list will generate an error if the path is invalid 1003 d.addErrback(listErr) 1021 1022 # self.shell.list will generate an error if the path is invalid 1023 d.addErrback(listErr) 1004 1024 return d 1005 1025 1026 def _cbWaitDTPConnectionWithTimeout(self, result): 1027 """ 1028 Helper callback that waits for DTP instance to be connected. 1029 1030 It will raise a C{PortConnectionError} if DTP instance is not 1031 connected after the interval defined by self.factory.timeOut. 1032 """ 1033 def ebDTPTimeout(failure): 1034 """ 1035 Called at data transport port timeout. 1036 """ 1037 failure.trap(defer.CancelledError) 1038 return defer.fail(defer.TimeoutError("DTP connection timeout")) 1039 1040 def cbContinueCommand(ignore, timeoutCall): 1041 if timeoutCall is not None and timeoutCall.active(): 1042 timeoutCall.cancel() 1043 return result 1044 1045 def cbCallTimeout(ignore): 1046 self.dtpFactory.deferred.cancel() 1047 1048 timeoutCall = self.factory.callLater( 1049 self.factory.timeOut, cbCallTimeout, None) 1050 1051 self.dtpFactory.deferred.addCallback(cbContinueCommand, timeoutCall) 1052 self.dtpFactory.deferred.addErrback(ebDTPTimeout) 1053 1054 return self.dtpFactory.deferred 1006 1055 1007 1056 def ftp_CWD(self, path): 1008 1057 try: … … 1038 1087 @rtype: L{Deferred} 1039 1088 @return: a L{Deferred} which will be fired when the transfer is done. 1040 1089 """ 1041 if self.dtpInstance is None: 1042 raise BadCmdSequenceError('PORT or PASV required before RETR') 1090 self._checkDataTransportStarted('RETR') 1043 1091 1044 1092 try: 1045 1093 newsegs = toSegments(self.workingDirectory, path) … … 1100 1148 1101 1149 1102 1150 def ftp_STOR(self, path): 1103 if self.dtpInstance is None: 1104 raise BadCmdSequenceError('PORT or PASV required before STOR') 1151 """ 1152 This command causes the server-DTP to accept the data 1153 transferred via the data connection and to store the data as 1154 a file at the server site. If the file specified in the 1155 pathname exists at the server site, then its contents shall 1156 be replaced by the data being transferred. A new file is 1157 created at the server site if the file specified in the 1158 pathname does not already exist. 1159 1160 @type path: C{str} 1161 @param path: The file path where the content should be stored. 1162 1163 @rtype: L{Deferred} 1164 @return: a L{Deferred} which will be fired when the transfer 1165 is finished. 1166 """ 1167 self._checkDataTransportStarted('STOR') 1105 1168 1106 1169 try: 1107 1170 newsegs = toSegments(self.workingDirectory, path) … … 1289 1352 1290 1353 1291 1354 def cleanupDTP(self): 1292 """call when DTP connection exits 1355 """ 1356 Called when DTP connection exits. 1293 1357 """ 1294 1358 log.msg('cleanupDTP', debug=True) 1295 1359 … … 1329 1393 1330 1394 passivePortRange = xrange(0, 1) 1331 1395 1396 callLater = reactor.callLater 1397 1332 1398 def __init__(self, portal=None, userAnonymous='anonymous'): 1333 1399 self.portal = portal 1334 1400 self.userAnonymous = userAnonymous -
twisted/test/test_ftp.py
diff --git a/twisted/test/test_ftp.py b/twisted/test/test_ftp.py index 23ffcba..8789132 100644
a b 463 463 self.assertEqual(portRange, protocol.wrappedProtocol.passivePortRange) 464 464 465 465 466 def _startDataConnection(self): 467 """ 468 Prepare data transport protocol to look like it was created by 469 a previous call to PASV or PORT 470 """ 471 self.serverProtocol.dtpFactory = ftp.DTPFactory(self.serverProtocol) 472 self.serverProtocol.dtpFactory.buildProtocol('ignore_address') 473 474 self.serverProtocol.dtpPort = self.serverProtocol.listenFactory( 475 6000, self.serverProtocol.dtpFactory) 476 477 dtpTransport = proto_helpers.StringTransportWithDisconnection() 478 dtpTransport.protocol = ftp.DTP() 479 self.serverProtocol.dtpInstance.transport = dtpTransport 480 481 482 def test_LISTWithoutDataChannel(self): 483 """ 484 Calling LIST without prior setup of data connection will result in a 485 incorrect sequence of commands error. 486 """ 487 d = self._anonymousLogin() 488 self.assertCommandFailed( 489 'LIST .', 490 ["503 Incorrect sequence of commands: " 491 "PORT or PASV required before LIST"], 492 chainDeferred=d) 493 return d 494 495 496 def test_LISTTimeout(self): 497 """ 498 LIST will timeout if setting up the DTP instance will take to long. 499 """ 500 # Set timeout to a very small value to not slow down tests. 501 self.factory.timeOut = 0.01 502 503 d = self._anonymousLogin() 504 self._startDataConnection() 505 506 self.assertCommandFailed( 507 'LIST .', 508 ["425 Data channel initialization timed out."], 509 chainDeferred=d) 510 return d 511 512 513 def test_NLSTWithoutDataChannel(self): 514 """ 515 Calling NLST without prior setup of data connection will result in a 516 incorrect sequence of commands error. 517 """ 518 d = self._anonymousLogin() 519 self.assertCommandFailed( 520 'NLST .', 521 ["503 Incorrect sequence of commands: " 522 "PORT or PASV required before NLST"], 523 chainDeferred=d) 524 return d 525 526 527 def test_NLSTTimeout(self): 528 """ 529 NLST will timeout if setting up the DTP instance will take to long, 530 but NLST will not return any error. 531 """ 532 self.factory.timeOut = 0.01 533 534 d = self._anonymousLogin() 535 self._startDataConnection() 536 537 # Set timeout to a very small value to not slow down tests. 538 self.assertCommandResponse( 539 'NLST .', 540 ["226 Transfer Complete."], 541 chainDeferred=d) 542 return d 543 544 466 545 467 546 class FTPServerTestCaseAdvancedClient(FTPServerTestCase): 468 547 """
