blob: 2b2c4cf0a90c3d057f8f22b8501b11ab25bdb337 [file] [log] [blame]
Benjamin Petersonbe17a112008-09-27 21:49:47 +00001"""Test script for ftplib module."""
2
3# Modified by Giampaolo Rodola' to test FTP class and IPv6 environment
4
Guido van Rossumd8faa362007-04-27 19:54:29 +00005import ftplib
Benjamin Petersonbe17a112008-09-27 21:49:47 +00006import threading
7import asyncore
8import asynchat
9import socket
10import io
Guido van Rossumd8faa362007-04-27 19:54:29 +000011
12from unittest import TestCase
Benjamin Petersonee8712c2008-05-20 21:35:26 +000013from test import support
Benjamin Petersonbe17a112008-09-27 21:49:47 +000014from test.support import HOST
Guido van Rossumd8faa362007-04-27 19:54:29 +000015
Benjamin Petersonbe17a112008-09-27 21:49:47 +000016# the dummy data returned by server over the data channel when
17# RETR, LIST and NLST commands are issued
18RETR_DATA = 'abcde12345\r\n' * 1000
19LIST_DATA = 'foo\r\nbar\r\n'
20NLST_DATA = 'foo\r\nbar\r\n'
Christian Heimes836baa52008-02-26 08:18:30 +000021
Christian Heimes836baa52008-02-26 08:18:30 +000022
Benjamin Petersonbe17a112008-09-27 21:49:47 +000023class DummyDTPHandler(asynchat.async_chat):
24
25 def __init__(self, conn, baseclass):
26 asynchat.async_chat.__init__(self, conn)
27 self.baseclass = baseclass
28 self.baseclass.last_received_data = ''
29
30 def handle_read(self):
31 self.baseclass.last_received_data += self.recv(1024).decode('ascii')
32
33 def handle_close(self):
34 self.baseclass.push('226 transfer complete')
35 self.close()
36
37 def push(self, what):
38 super(DummyDTPHandler, self).push(what.encode('ascii'))
39
40
41class DummyFTPHandler(asynchat.async_chat):
42
43 def __init__(self, conn):
44 asynchat.async_chat.__init__(self, conn)
Giampaolo Rodola'0b5c21f2011-05-07 19:03:47 +020045 # tells the socket to handle urgent data inline (ABOR command)
46 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_OOBINLINE, 1)
Benjamin Petersonbe17a112008-09-27 21:49:47 +000047 self.set_terminator(b"\r\n")
48 self.in_buffer = []
49 self.dtp = None
50 self.last_received_cmd = None
51 self.last_received_data = ''
52 self.next_response = ''
53 self.push('220 welcome')
54
55 def collect_incoming_data(self, data):
56 self.in_buffer.append(data)
57
58 def found_terminator(self):
59 line = b''.join(self.in_buffer).decode('ascii')
60 self.in_buffer = []
61 if self.next_response:
62 self.push(self.next_response)
63 self.next_response = ''
64 cmd = line.split(' ')[0].lower()
65 self.last_received_cmd = cmd
66 space = line.find(' ')
67 if space != -1:
68 arg = line[space + 1:]
69 else:
70 arg = ""
71 if hasattr(self, 'cmd_' + cmd):
72 method = getattr(self, 'cmd_' + cmd)
73 method(arg)
74 else:
75 self.push('550 command "%s" not understood.' %cmd)
76
77 def handle_error(self):
78 raise
79
80 def push(self, data):
81 asynchat.async_chat.push(self, data.encode('ascii') + b'\r\n')
82
83 def cmd_port(self, arg):
84 addr = list(map(int, arg.split(',')))
85 ip = '%d.%d.%d.%d' %tuple(addr[:4])
86 port = (addr[4] * 256) + addr[5]
Antoine Pitrou51be0f42010-10-14 20:44:58 +000087 s = socket.create_connection((ip, port), timeout=10)
Benjamin Petersonbe17a112008-09-27 21:49:47 +000088 self.dtp = DummyDTPHandler(s, baseclass=self)
89 self.push('200 active data connection established')
90
91 def cmd_pasv(self, arg):
92 sock = socket.socket()
93 sock.bind((self.socket.getsockname()[0], 0))
94 sock.listen(5)
Antoine Pitrou51be0f42010-10-14 20:44:58 +000095 sock.settimeout(10)
Benjamin Petersonbe17a112008-09-27 21:49:47 +000096 ip, port = sock.getsockname()[:2]
97 ip = ip.replace('.', ','); p1 = port / 256; p2 = port % 256
98 self.push('227 entering passive mode (%s,%d,%d)' %(ip, p1, p2))
99 conn, addr = sock.accept()
100 self.dtp = DummyDTPHandler(conn, baseclass=self)
101
102 def cmd_eprt(self, arg):
103 af, ip, port = arg.split(arg[0])[1:-1]
104 port = int(port)
Antoine Pitrou51be0f42010-10-14 20:44:58 +0000105 s = socket.create_connection((ip, port), timeout=10)
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000106 self.dtp = DummyDTPHandler(s, baseclass=self)
107 self.push('200 active data connection established')
108
109 def cmd_epsv(self, arg):
110 sock = socket.socket(socket.AF_INET6)
111 sock.bind((self.socket.getsockname()[0], 0))
112 sock.listen(5)
Antoine Pitrou51be0f42010-10-14 20:44:58 +0000113 sock.settimeout(10)
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000114 port = sock.getsockname()[1]
115 self.push('229 entering extended passive mode (|||%d|)' %port)
116 conn, addr = sock.accept()
117 self.dtp = DummyDTPHandler(conn, baseclass=self)
118
119 def cmd_echo(self, arg):
120 # sends back the received string (used by the test suite)
121 self.push(arg)
122
123 def cmd_user(self, arg):
124 self.push('331 username ok')
125
126 def cmd_pass(self, arg):
127 self.push('230 password ok')
128
129 def cmd_acct(self, arg):
130 self.push('230 acct ok')
131
132 def cmd_rnfr(self, arg):
133 self.push('350 rnfr ok')
134
135 def cmd_rnto(self, arg):
136 self.push('250 rnto ok')
137
138 def cmd_dele(self, arg):
139 self.push('250 dele ok')
140
141 def cmd_cwd(self, arg):
142 self.push('250 cwd ok')
143
144 def cmd_size(self, arg):
145 self.push('250 1000')
146
147 def cmd_mkd(self, arg):
148 self.push('257 "%s"' %arg)
149
150 def cmd_rmd(self, arg):
151 self.push('250 rmd ok')
152
153 def cmd_pwd(self, arg):
154 self.push('257 "pwd ok"')
155
156 def cmd_type(self, arg):
157 self.push('200 type ok')
158
159 def cmd_quit(self, arg):
160 self.push('221 quit ok')
161 self.close()
162
Giampaolo Rodola'0b5c21f2011-05-07 19:03:47 +0200163 def cmd_abor(self, arg):
164 self.push('226 abor ok')
165
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000166 def cmd_stor(self, arg):
167 self.push('125 stor ok')
168
169 def cmd_retr(self, arg):
170 self.push('125 retr ok')
171 self.dtp.push(RETR_DATA)
172 self.dtp.close_when_done()
173
174 def cmd_list(self, arg):
175 self.push('125 list ok')
176 self.dtp.push(LIST_DATA)
177 self.dtp.close_when_done()
178
179 def cmd_nlst(self, arg):
180 self.push('125 nlst ok')
181 self.dtp.push(NLST_DATA)
182 self.dtp.close_when_done()
183
184
185class DummyFTPServer(asyncore.dispatcher, threading.Thread):
186
187 handler = DummyFTPHandler
188
189 def __init__(self, address, af=socket.AF_INET):
190 threading.Thread.__init__(self)
191 asyncore.dispatcher.__init__(self)
192 self.create_socket(af, socket.SOCK_STREAM)
193 self.bind(address)
194 self.listen(5)
195 self.active = False
196 self.active_lock = threading.Lock()
197 self.host, self.port = self.socket.getsockname()[:2]
198
199 def start(self):
200 assert not self.active
201 self.__flag = threading.Event()
202 threading.Thread.start(self)
203 self.__flag.wait()
204
205 def run(self):
206 self.active = True
207 self.__flag.set()
208 while self.active and asyncore.socket_map:
209 self.active_lock.acquire()
210 asyncore.loop(timeout=0.1, count=1)
211 self.active_lock.release()
212 asyncore.close_all(ignore_all=True)
213
214 def stop(self):
215 assert self.active
216 self.active = False
217 self.join()
218
219 def handle_accept(self):
220 conn, addr = self.accept()
221 self.handler = self.handler(conn)
Benjamin Petersond06e3b02008-09-28 21:00:42 +0000222 self.close()
223
224 def handle_connect(self):
225 self.close()
226 handle_read = handle_connect
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000227
228 def writable(self):
229 return 0
230
231 def handle_error(self):
232 raise
233
234
235class TestFTPClass(TestCase):
236
237 def setUp(self):
238 self.server = DummyFTPServer((HOST, 0))
239 self.server.start()
Antoine Pitrou51be0f42010-10-14 20:44:58 +0000240 self.client = ftplib.FTP(timeout=10)
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000241 self.client.connect(self.server.host, self.server.port)
242
243 def tearDown(self):
244 self.client.close()
245 self.server.stop()
246
247 def test_getwelcome(self):
248 self.assertEqual(self.client.getwelcome(), '220 welcome')
249
250 def test_sanitize(self):
251 self.assertEqual(self.client.sanitize('foo'), repr('foo'))
252 self.assertEqual(self.client.sanitize('pass 12345'), repr('pass *****'))
253 self.assertEqual(self.client.sanitize('PASS 12345'), repr('PASS *****'))
254
255 def test_exceptions(self):
256 self.assertRaises(ftplib.error_temp, self.client.sendcmd, 'echo 400')
257 self.assertRaises(ftplib.error_temp, self.client.sendcmd, 'echo 499')
258 self.assertRaises(ftplib.error_perm, self.client.sendcmd, 'echo 500')
259 self.assertRaises(ftplib.error_perm, self.client.sendcmd, 'echo 599')
260 self.assertRaises(ftplib.error_proto, self.client.sendcmd, 'echo 999')
261
262 def test_all_errors(self):
263 exceptions = (ftplib.error_reply, ftplib.error_temp, ftplib.error_perm,
264 ftplib.error_proto, ftplib.Error, IOError, EOFError)
265 for x in exceptions:
266 try:
267 raise x('exception not included in all_errors set')
268 except ftplib.all_errors:
269 pass
270
271 def test_set_pasv(self):
272 # passive mode is supposed to be enabled by default
273 self.assertTrue(self.client.passiveserver)
274 self.client.set_pasv(True)
275 self.assertTrue(self.client.passiveserver)
276 self.client.set_pasv(False)
277 self.assertFalse(self.client.passiveserver)
278
279 def test_voidcmd(self):
280 self.client.voidcmd('echo 200')
281 self.client.voidcmd('echo 299')
282 self.assertRaises(ftplib.error_reply, self.client.voidcmd, 'echo 199')
283 self.assertRaises(ftplib.error_reply, self.client.voidcmd, 'echo 300')
284
285 def test_login(self):
286 self.client.login()
287
288 def test_acct(self):
289 self.client.acct('passwd')
290
291 def test_rename(self):
292 self.client.rename('a', 'b')
293 self.server.handler.next_response = '200'
294 self.assertRaises(ftplib.error_reply, self.client.rename, 'a', 'b')
295
296 def test_delete(self):
297 self.client.delete('foo')
298 self.server.handler.next_response = '199'
299 self.assertRaises(ftplib.error_reply, self.client.delete, 'foo')
300
301 def test_size(self):
302 self.client.size('foo')
303
304 def test_mkd(self):
305 dir = self.client.mkd('/foo')
306 self.assertEqual(dir, '/foo')
307
308 def test_rmd(self):
309 self.client.rmd('foo')
310
311 def test_pwd(self):
312 dir = self.client.pwd()
313 self.assertEqual(dir, 'pwd ok')
314
315 def test_quit(self):
316 self.assertEqual(self.client.quit(), '221 quit ok')
317 # Ensure the connection gets closed; sock attribute should be None
318 self.assertEqual(self.client.sock, None)
319
Giampaolo Rodola'0b5c21f2011-05-07 19:03:47 +0200320 def test_abort(self):
321 self.client.abort()
322
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000323 def test_retrbinary(self):
324 def callback(data):
325 received.append(data.decode('ascii'))
326 received = []
327 self.client.retrbinary('retr', callback)
328 self.assertEqual(''.join(received), RETR_DATA)
329
330 def test_retrlines(self):
331 received = []
332 self.client.retrlines('retr', received.append)
333 self.assertEqual(''.join(received), RETR_DATA.replace('\r\n', ''))
334
335 def test_storbinary(self):
336 f = io.BytesIO(RETR_DATA.encode('ascii'))
337 self.client.storbinary('stor', f)
338 self.assertEqual(self.server.handler.last_received_data, RETR_DATA)
339 # test new callback arg
340 flag = []
341 f.seek(0)
342 self.client.storbinary('stor', f, callback=lambda x: flag.append(None))
343 self.assertTrue(flag)
344
345 def test_storlines(self):
346 f = io.BytesIO(RETR_DATA.replace('\r\n', '\n').encode('ascii'))
347 self.client.storlines('stor', f)
348 self.assertEqual(self.server.handler.last_received_data, RETR_DATA)
349 # test new callback arg
350 flag = []
351 f.seek(0)
352 self.client.storlines('stor foo', f, callback=lambda x: flag.append(None))
353 self.assertTrue(flag)
354
355 def test_nlst(self):
356 self.client.nlst()
357 self.assertEqual(self.client.nlst(), NLST_DATA.split('\r\n')[:-1])
358
359 def test_dir(self):
360 l = []
361 self.client.dir(lambda x: l.append(x))
362 self.assertEqual(''.join(l), LIST_DATA.replace('\r\n', ''))
363
Benjamin Peterson3a53fbb2008-09-27 22:04:16 +0000364 def test_makeport(self):
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000365 self.client.makeport()
366 # IPv4 is in use, just make sure send_eprt has not been used
367 self.assertEqual(self.server.handler.last_received_cmd, 'port')
368
369 def test_makepasv(self):
370 host, port = self.client.makepasv()
Antoine Pitrou51be0f42010-10-14 20:44:58 +0000371 conn = socket.create_connection((host, port), 10)
Guido van Rossumd8faa362007-04-27 19:54:29 +0000372 conn.close()
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000373 # IPv4 is in use, just make sure send_epsv has not been used
374 self.assertEqual(self.server.handler.last_received_cmd, 'pasv')
Guido van Rossumd8faa362007-04-27 19:54:29 +0000375
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000376
377class TestIPv6Environment(TestCase):
378
379 def setUp(self):
380 self.server = DummyFTPServer((HOST, 0), af=socket.AF_INET6)
381 self.server.start()
382 self.client = ftplib.FTP()
383 self.client.connect(self.server.host, self.server.port)
384
385 def tearDown(self):
386 self.client.close()
387 self.server.stop()
388
389 def test_af(self):
390 self.assertEqual(self.client.af, socket.AF_INET6)
391
392 def test_makeport(self):
393 self.client.makeport()
394 self.assertEqual(self.server.handler.last_received_cmd, 'eprt')
395
396 def test_makepasv(self):
397 host, port = self.client.makepasv()
Antoine Pitrou51be0f42010-10-14 20:44:58 +0000398 conn = socket.create_connection((host, port), 10)
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000399 conn.close()
400 self.assertEqual(self.server.handler.last_received_cmd, 'epsv')
401
402 def test_transfer(self):
403 def retr():
404 def callback(data):
405 received.append(data.decode('ascii'))
406 received = []
407 self.client.retrbinary('retr', callback)
408 self.assertEqual(''.join(received), RETR_DATA)
409 self.client.set_pasv(True)
410 retr()
411 self.client.set_pasv(False)
412 retr()
413
414
415class TestTimeouts(TestCase):
Guido van Rossumd8faa362007-04-27 19:54:29 +0000416
417 def setUp(self):
Guido van Rossumd8faa362007-04-27 19:54:29 +0000418 self.evt = threading.Event()
Christian Heimes5e696852008-04-09 08:37:03 +0000419 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
420 self.sock.settimeout(3)
Benjamin Petersonee8712c2008-05-20 21:35:26 +0000421 self.port = support.bind_port(self.sock)
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000422 threading.Thread(target=self.server, args=(self.evt,self.sock)).start()
Christian Heimes836baa52008-02-26 08:18:30 +0000423 # Wait for the server to be ready.
424 self.evt.wait()
425 self.evt.clear()
Christian Heimes5e696852008-04-09 08:37:03 +0000426 ftplib.FTP.port = self.port
Guido van Rossumd8faa362007-04-27 19:54:29 +0000427
428 def tearDown(self):
429 self.evt.wait()
430
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000431 def server(self, evt, serv):
432 # This method sets the evt 3 times:
433 # 1) when the connection is ready to be accepted.
434 # 2) when it is safe for the caller to close the connection
435 # 3) when we have closed the socket
436 serv.listen(5)
437 # (1) Signal the caller that we are ready to accept the connection.
438 evt.set()
439 try:
440 conn, addr = serv.accept()
441 except socket.timeout:
442 pass
443 else:
444 conn.send(b"1 Hola mundo\n")
445 # (2) Signal the caller that it is safe to close the socket.
446 evt.set()
447 conn.close()
448 finally:
449 serv.close()
450 # (3) Signal the caller that we are done.
451 evt.set()
Guido van Rossumd8faa362007-04-27 19:54:29 +0000452
453 def testTimeoutDefault(self):
Georg Brandlf78e02b2008-06-10 17:40:04 +0000454 # default -- use global socket timeout
Georg Brandlab91fde2009-08-13 08:51:18 +0000455 self.assertTrue(socket.getdefaulttimeout() is None)
Georg Brandlf78e02b2008-06-10 17:40:04 +0000456 socket.setdefaulttimeout(30)
457 try:
458 ftp = ftplib.FTP("localhost")
459 finally:
460 socket.setdefaulttimeout(None)
461 self.assertEqual(ftp.sock.gettimeout(), 30)
462 self.evt.wait()
463 ftp.close()
464
465 def testTimeoutNone(self):
466 # no timeout -- do not use global socket timeout
Georg Brandlab91fde2009-08-13 08:51:18 +0000467 self.assertTrue(socket.getdefaulttimeout() is None)
Georg Brandlf78e02b2008-06-10 17:40:04 +0000468 socket.setdefaulttimeout(30)
469 try:
470 ftp = ftplib.FTP("localhost", timeout=None)
471 finally:
472 socket.setdefaulttimeout(None)
Guido van Rossumd8faa362007-04-27 19:54:29 +0000473 self.assertTrue(ftp.sock.gettimeout() is None)
Christian Heimes836baa52008-02-26 08:18:30 +0000474 self.evt.wait()
Georg Brandlf78e02b2008-06-10 17:40:04 +0000475 ftp.close()
Guido van Rossumd8faa362007-04-27 19:54:29 +0000476
477 def testTimeoutValue(self):
478 # a value
Christian Heimes5e696852008-04-09 08:37:03 +0000479 ftp = ftplib.FTP(HOST, timeout=30)
Guido van Rossumd8faa362007-04-27 19:54:29 +0000480 self.assertEqual(ftp.sock.gettimeout(), 30)
Christian Heimes836baa52008-02-26 08:18:30 +0000481 self.evt.wait()
Georg Brandlf78e02b2008-06-10 17:40:04 +0000482 ftp.close()
Guido van Rossumd8faa362007-04-27 19:54:29 +0000483
484 def testTimeoutConnect(self):
485 ftp = ftplib.FTP()
Christian Heimes5e696852008-04-09 08:37:03 +0000486 ftp.connect(HOST, timeout=30)
Guido van Rossumd8faa362007-04-27 19:54:29 +0000487 self.assertEqual(ftp.sock.gettimeout(), 30)
Christian Heimes836baa52008-02-26 08:18:30 +0000488 self.evt.wait()
Georg Brandlf78e02b2008-06-10 17:40:04 +0000489 ftp.close()
Guido van Rossumd8faa362007-04-27 19:54:29 +0000490
491 def testTimeoutDifferentOrder(self):
492 ftp = ftplib.FTP(timeout=30)
Christian Heimes5e696852008-04-09 08:37:03 +0000493 ftp.connect(HOST)
Guido van Rossumd8faa362007-04-27 19:54:29 +0000494 self.assertEqual(ftp.sock.gettimeout(), 30)
Christian Heimes836baa52008-02-26 08:18:30 +0000495 self.evt.wait()
Georg Brandlf78e02b2008-06-10 17:40:04 +0000496 ftp.close()
Guido van Rossumd8faa362007-04-27 19:54:29 +0000497
498 def testTimeoutDirectAccess(self):
499 ftp = ftplib.FTP()
500 ftp.timeout = 30
Christian Heimes5e696852008-04-09 08:37:03 +0000501 ftp.connect(HOST)
Guido van Rossumd8faa362007-04-27 19:54:29 +0000502 self.assertEqual(ftp.sock.gettimeout(), 30)
Christian Heimes836baa52008-02-26 08:18:30 +0000503 self.evt.wait()
Guido van Rossumd8faa362007-04-27 19:54:29 +0000504 ftp.close()
505
506
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000507def test_main():
508 tests = [TestFTPClass, TestTimeouts]
509 if socket.has_ipv6:
510 try:
511 DummyFTPServer((HOST, 0), af=socket.AF_INET6)
512 except socket.error:
513 pass
514 else:
515 tests.append(TestIPv6Environment)
516 thread_info = support.threading_setup()
517 try:
518 support.run_unittest(*tests)
519 finally:
520 support.threading_cleanup(*thread_info)
521
Guido van Rossumd8faa362007-04-27 19:54:29 +0000522
523if __name__ == '__main__':
524 test_main()