]>
Commit | Line | Data |
---|---|---|
4710c53d | 1 | """Test script for ftplib module."""\r |
2 | \r | |
3 | # Modified by Giampaolo Rodola' to test FTP class, IPv6 and TLS\r | |
4 | # environment\r | |
5 | \r | |
6 | import ftplib\r | |
7 | import asyncore\r | |
8 | import asynchat\r | |
9 | import socket\r | |
10 | import StringIO\r | |
11 | import errno\r | |
12 | import os\r | |
13 | try:\r | |
14 | import ssl\r | |
15 | except ImportError:\r | |
16 | ssl = None\r | |
17 | \r | |
18 | from unittest import TestCase\r | |
19 | from test import test_support\r | |
20 | from test.test_support import HOST\r | |
21 | threading = test_support.import_module('threading')\r | |
22 | \r | |
23 | \r | |
24 | # the dummy data returned by server over the data channel when\r | |
25 | # RETR, LIST and NLST commands are issued\r | |
26 | RETR_DATA = 'abcde12345\r\n' * 1000\r | |
27 | LIST_DATA = 'foo\r\nbar\r\n'\r | |
28 | NLST_DATA = 'foo\r\nbar\r\n'\r | |
29 | \r | |
30 | \r | |
31 | class DummyDTPHandler(asynchat.async_chat):\r | |
32 | dtp_conn_closed = False\r | |
33 | \r | |
34 | def __init__(self, conn, baseclass):\r | |
35 | asynchat.async_chat.__init__(self, conn)\r | |
36 | self.baseclass = baseclass\r | |
37 | self.baseclass.last_received_data = ''\r | |
38 | \r | |
39 | def handle_read(self):\r | |
40 | self.baseclass.last_received_data += self.recv(1024)\r | |
41 | \r | |
42 | def handle_close(self):\r | |
43 | # XXX: this method can be called many times in a row for a single\r | |
44 | # connection, including in clear-text (non-TLS) mode.\r | |
45 | # (behaviour witnessed with test_data_connection)\r | |
46 | if not self.dtp_conn_closed:\r | |
47 | self.baseclass.push('226 transfer complete')\r | |
48 | self.close()\r | |
49 | self.dtp_conn_closed = True\r | |
50 | \r | |
51 | def handle_error(self):\r | |
52 | raise\r | |
53 | \r | |
54 | \r | |
55 | class DummyFTPHandler(asynchat.async_chat):\r | |
56 | \r | |
57 | dtp_handler = DummyDTPHandler\r | |
58 | \r | |
59 | def __init__(self, conn):\r | |
60 | asynchat.async_chat.__init__(self, conn)\r | |
61 | self.set_terminator("\r\n")\r | |
62 | self.in_buffer = []\r | |
63 | self.dtp = None\r | |
64 | self.last_received_cmd = None\r | |
65 | self.last_received_data = ''\r | |
66 | self.next_response = ''\r | |
67 | self.rest = None\r | |
68 | self.push('220 welcome')\r | |
69 | \r | |
70 | def collect_incoming_data(self, data):\r | |
71 | self.in_buffer.append(data)\r | |
72 | \r | |
73 | def found_terminator(self):\r | |
74 | line = ''.join(self.in_buffer)\r | |
75 | self.in_buffer = []\r | |
76 | if self.next_response:\r | |
77 | self.push(self.next_response)\r | |
78 | self.next_response = ''\r | |
79 | cmd = line.split(' ')[0].lower()\r | |
80 | self.last_received_cmd = cmd\r | |
81 | space = line.find(' ')\r | |
82 | if space != -1:\r | |
83 | arg = line[space + 1:]\r | |
84 | else:\r | |
85 | arg = ""\r | |
86 | if hasattr(self, 'cmd_' + cmd):\r | |
87 | method = getattr(self, 'cmd_' + cmd)\r | |
88 | method(arg)\r | |
89 | else:\r | |
90 | self.push('550 command "%s" not understood.' %cmd)\r | |
91 | \r | |
92 | def handle_error(self):\r | |
93 | raise\r | |
94 | \r | |
95 | def push(self, data):\r | |
96 | asynchat.async_chat.push(self, data + '\r\n')\r | |
97 | \r | |
98 | def cmd_port(self, arg):\r | |
99 | addr = map(int, arg.split(','))\r | |
100 | ip = '%d.%d.%d.%d' %tuple(addr[:4])\r | |
101 | port = (addr[4] * 256) + addr[5]\r | |
102 | s = socket.create_connection((ip, port), timeout=10)\r | |
103 | self.dtp = self.dtp_handler(s, baseclass=self)\r | |
104 | self.push('200 active data connection established')\r | |
105 | \r | |
106 | def cmd_pasv(self, arg):\r | |
107 | sock = socket.socket()\r | |
108 | sock.bind((self.socket.getsockname()[0], 0))\r | |
109 | sock.listen(5)\r | |
110 | sock.settimeout(10)\r | |
111 | ip, port = sock.getsockname()[:2]\r | |
112 | ip = ip.replace('.', ',')\r | |
113 | p1, p2 = divmod(port, 256)\r | |
114 | self.push('227 entering passive mode (%s,%d,%d)' %(ip, p1, p2))\r | |
115 | conn, addr = sock.accept()\r | |
116 | self.dtp = self.dtp_handler(conn, baseclass=self)\r | |
117 | \r | |
118 | def cmd_eprt(self, arg):\r | |
119 | af, ip, port = arg.split(arg[0])[1:-1]\r | |
120 | port = int(port)\r | |
121 | s = socket.create_connection((ip, port), timeout=10)\r | |
122 | self.dtp = self.dtp_handler(s, baseclass=self)\r | |
123 | self.push('200 active data connection established')\r | |
124 | \r | |
125 | def cmd_epsv(self, arg):\r | |
126 | sock = socket.socket(socket.AF_INET6)\r | |
127 | sock.bind((self.socket.getsockname()[0], 0))\r | |
128 | sock.listen(5)\r | |
129 | sock.settimeout(10)\r | |
130 | port = sock.getsockname()[1]\r | |
131 | self.push('229 entering extended passive mode (|||%d|)' %port)\r | |
132 | conn, addr = sock.accept()\r | |
133 | self.dtp = self.dtp_handler(conn, baseclass=self)\r | |
134 | \r | |
135 | def cmd_echo(self, arg):\r | |
136 | # sends back the received string (used by the test suite)\r | |
137 | self.push(arg)\r | |
138 | \r | |
139 | def cmd_user(self, arg):\r | |
140 | self.push('331 username ok')\r | |
141 | \r | |
142 | def cmd_pass(self, arg):\r | |
143 | self.push('230 password ok')\r | |
144 | \r | |
145 | def cmd_acct(self, arg):\r | |
146 | self.push('230 acct ok')\r | |
147 | \r | |
148 | def cmd_rnfr(self, arg):\r | |
149 | self.push('350 rnfr ok')\r | |
150 | \r | |
151 | def cmd_rnto(self, arg):\r | |
152 | self.push('250 rnto ok')\r | |
153 | \r | |
154 | def cmd_dele(self, arg):\r | |
155 | self.push('250 dele ok')\r | |
156 | \r | |
157 | def cmd_cwd(self, arg):\r | |
158 | self.push('250 cwd ok')\r | |
159 | \r | |
160 | def cmd_size(self, arg):\r | |
161 | self.push('250 1000')\r | |
162 | \r | |
163 | def cmd_mkd(self, arg):\r | |
164 | self.push('257 "%s"' %arg)\r | |
165 | \r | |
166 | def cmd_rmd(self, arg):\r | |
167 | self.push('250 rmd ok')\r | |
168 | \r | |
169 | def cmd_pwd(self, arg):\r | |
170 | self.push('257 "pwd ok"')\r | |
171 | \r | |
172 | def cmd_type(self, arg):\r | |
173 | self.push('200 type ok')\r | |
174 | \r | |
175 | def cmd_quit(self, arg):\r | |
176 | self.push('221 quit ok')\r | |
177 | self.close()\r | |
178 | \r | |
179 | def cmd_stor(self, arg):\r | |
180 | self.push('125 stor ok')\r | |
181 | \r | |
182 | def cmd_rest(self, arg):\r | |
183 | self.rest = arg\r | |
184 | self.push('350 rest ok')\r | |
185 | \r | |
186 | def cmd_retr(self, arg):\r | |
187 | self.push('125 retr ok')\r | |
188 | if self.rest is not None:\r | |
189 | offset = int(self.rest)\r | |
190 | else:\r | |
191 | offset = 0\r | |
192 | self.dtp.push(RETR_DATA[offset:])\r | |
193 | self.dtp.close_when_done()\r | |
194 | self.rest = None\r | |
195 | \r | |
196 | def cmd_list(self, arg):\r | |
197 | self.push('125 list ok')\r | |
198 | self.dtp.push(LIST_DATA)\r | |
199 | self.dtp.close_when_done()\r | |
200 | \r | |
201 | def cmd_nlst(self, arg):\r | |
202 | self.push('125 nlst ok')\r | |
203 | self.dtp.push(NLST_DATA)\r | |
204 | self.dtp.close_when_done()\r | |
205 | \r | |
206 | \r | |
207 | class DummyFTPServer(asyncore.dispatcher, threading.Thread):\r | |
208 | \r | |
209 | handler = DummyFTPHandler\r | |
210 | \r | |
211 | def __init__(self, address, af=socket.AF_INET):\r | |
212 | threading.Thread.__init__(self)\r | |
213 | asyncore.dispatcher.__init__(self)\r | |
214 | self.create_socket(af, socket.SOCK_STREAM)\r | |
215 | self.bind(address)\r | |
216 | self.listen(5)\r | |
217 | self.active = False\r | |
218 | self.active_lock = threading.Lock()\r | |
219 | self.host, self.port = self.socket.getsockname()[:2]\r | |
220 | \r | |
221 | def start(self):\r | |
222 | assert not self.active\r | |
223 | self.__flag = threading.Event()\r | |
224 | threading.Thread.start(self)\r | |
225 | self.__flag.wait()\r | |
226 | \r | |
227 | def run(self):\r | |
228 | self.active = True\r | |
229 | self.__flag.set()\r | |
230 | while self.active and asyncore.socket_map:\r | |
231 | self.active_lock.acquire()\r | |
232 | asyncore.loop(timeout=0.1, count=1)\r | |
233 | self.active_lock.release()\r | |
234 | asyncore.close_all(ignore_all=True)\r | |
235 | \r | |
236 | def stop(self):\r | |
237 | assert self.active\r | |
238 | self.active = False\r | |
239 | self.join()\r | |
240 | \r | |
241 | def handle_accept(self):\r | |
242 | conn, addr = self.accept()\r | |
243 | self.handler = self.handler(conn)\r | |
244 | self.close()\r | |
245 | \r | |
246 | def handle_connect(self):\r | |
247 | self.close()\r | |
248 | handle_read = handle_connect\r | |
249 | \r | |
250 | def writable(self):\r | |
251 | return 0\r | |
252 | \r | |
253 | def handle_error(self):\r | |
254 | raise\r | |
255 | \r | |
256 | \r | |
257 | if ssl is not None:\r | |
258 | \r | |
259 | CERTFILE = os.path.join(os.path.dirname(__file__), "keycert.pem")\r | |
260 | \r | |
261 | class SSLConnection(object, asyncore.dispatcher):\r | |
262 | """An asyncore.dispatcher subclass supporting TLS/SSL."""\r | |
263 | \r | |
264 | _ssl_accepting = False\r | |
265 | _ssl_closing = False\r | |
266 | \r | |
267 | def secure_connection(self):\r | |
268 | self.socket = ssl.wrap_socket(self.socket, suppress_ragged_eofs=False,\r | |
269 | certfile=CERTFILE, server_side=True,\r | |
270 | do_handshake_on_connect=False,\r | |
271 | ssl_version=ssl.PROTOCOL_SSLv23)\r | |
272 | self._ssl_accepting = True\r | |
273 | \r | |
274 | def _do_ssl_handshake(self):\r | |
275 | try:\r | |
276 | self.socket.do_handshake()\r | |
277 | except ssl.SSLError, err:\r | |
278 | if err.args[0] in (ssl.SSL_ERROR_WANT_READ,\r | |
279 | ssl.SSL_ERROR_WANT_WRITE):\r | |
280 | return\r | |
281 | elif err.args[0] == ssl.SSL_ERROR_EOF:\r | |
282 | return self.handle_close()\r | |
283 | raise\r | |
284 | except socket.error, err:\r | |
285 | if err.args[0] == errno.ECONNABORTED:\r | |
286 | return self.handle_close()\r | |
287 | else:\r | |
288 | self._ssl_accepting = False\r | |
289 | \r | |
290 | def _do_ssl_shutdown(self):\r | |
291 | self._ssl_closing = True\r | |
292 | try:\r | |
293 | self.socket = self.socket.unwrap()\r | |
294 | except ssl.SSLError, err:\r | |
295 | if err.args[0] in (ssl.SSL_ERROR_WANT_READ,\r | |
296 | ssl.SSL_ERROR_WANT_WRITE):\r | |
297 | return\r | |
298 | except socket.error, err:\r | |
299 | # Any "socket error" corresponds to a SSL_ERROR_SYSCALL return\r | |
300 | # from OpenSSL's SSL_shutdown(), corresponding to a\r | |
301 | # closed socket condition. See also:\r | |
302 | # http://www.mail-archive.com/openssl-users@openssl.org/msg60710.html\r | |
303 | pass\r | |
304 | self._ssl_closing = False\r | |
305 | super(SSLConnection, self).close()\r | |
306 | \r | |
307 | def handle_read_event(self):\r | |
308 | if self._ssl_accepting:\r | |
309 | self._do_ssl_handshake()\r | |
310 | elif self._ssl_closing:\r | |
311 | self._do_ssl_shutdown()\r | |
312 | else:\r | |
313 | super(SSLConnection, self).handle_read_event()\r | |
314 | \r | |
315 | def handle_write_event(self):\r | |
316 | if self._ssl_accepting:\r | |
317 | self._do_ssl_handshake()\r | |
318 | elif self._ssl_closing:\r | |
319 | self._do_ssl_shutdown()\r | |
320 | else:\r | |
321 | super(SSLConnection, self).handle_write_event()\r | |
322 | \r | |
323 | def send(self, data):\r | |
324 | try:\r | |
325 | return super(SSLConnection, self).send(data)\r | |
326 | except ssl.SSLError, err:\r | |
327 | if err.args[0] in (ssl.SSL_ERROR_EOF, ssl.SSL_ERROR_ZERO_RETURN,\r | |
328 | ssl.SSL_ERROR_WANT_READ,\r | |
329 | ssl.SSL_ERROR_WANT_WRITE):\r | |
330 | return 0\r | |
331 | raise\r | |
332 | \r | |
333 | def recv(self, buffer_size):\r | |
334 | try:\r | |
335 | return super(SSLConnection, self).recv(buffer_size)\r | |
336 | except ssl.SSLError, err:\r | |
337 | if err.args[0] in (ssl.SSL_ERROR_WANT_READ,\r | |
338 | ssl.SSL_ERROR_WANT_WRITE):\r | |
339 | return ''\r | |
340 | if err.args[0] in (ssl.SSL_ERROR_EOF, ssl.SSL_ERROR_ZERO_RETURN):\r | |
341 | self.handle_close()\r | |
342 | return ''\r | |
343 | raise\r | |
344 | \r | |
345 | def handle_error(self):\r | |
346 | raise\r | |
347 | \r | |
348 | def close(self):\r | |
349 | if (isinstance(self.socket, ssl.SSLSocket) and\r | |
350 | self.socket._sslobj is not None):\r | |
351 | self._do_ssl_shutdown()\r | |
352 | \r | |
353 | \r | |
354 | class DummyTLS_DTPHandler(SSLConnection, DummyDTPHandler):\r | |
355 | """A DummyDTPHandler subclass supporting TLS/SSL."""\r | |
356 | \r | |
357 | def __init__(self, conn, baseclass):\r | |
358 | DummyDTPHandler.__init__(self, conn, baseclass)\r | |
359 | if self.baseclass.secure_data_channel:\r | |
360 | self.secure_connection()\r | |
361 | \r | |
362 | \r | |
363 | class DummyTLS_FTPHandler(SSLConnection, DummyFTPHandler):\r | |
364 | """A DummyFTPHandler subclass supporting TLS/SSL."""\r | |
365 | \r | |
366 | dtp_handler = DummyTLS_DTPHandler\r | |
367 | \r | |
368 | def __init__(self, conn):\r | |
369 | DummyFTPHandler.__init__(self, conn)\r | |
370 | self.secure_data_channel = False\r | |
371 | \r | |
372 | def cmd_auth(self, line):\r | |
373 | """Set up secure control channel."""\r | |
374 | self.push('234 AUTH TLS successful')\r | |
375 | self.secure_connection()\r | |
376 | \r | |
377 | def cmd_pbsz(self, line):\r | |
378 | """Negotiate size of buffer for secure data transfer.\r | |
379 | For TLS/SSL the only valid value for the parameter is '0'.\r | |
380 | Any other value is accepted but ignored.\r | |
381 | """\r | |
382 | self.push('200 PBSZ=0 successful.')\r | |
383 | \r | |
384 | def cmd_prot(self, line):\r | |
385 | """Setup un/secure data channel."""\r | |
386 | arg = line.upper()\r | |
387 | if arg == 'C':\r | |
388 | self.push('200 Protection set to Clear')\r | |
389 | self.secure_data_channel = False\r | |
390 | elif arg == 'P':\r | |
391 | self.push('200 Protection set to Private')\r | |
392 | self.secure_data_channel = True\r | |
393 | else:\r | |
394 | self.push("502 Unrecognized PROT type (use C or P).")\r | |
395 | \r | |
396 | \r | |
397 | class DummyTLS_FTPServer(DummyFTPServer):\r | |
398 | handler = DummyTLS_FTPHandler\r | |
399 | \r | |
400 | \r | |
401 | class TestFTPClass(TestCase):\r | |
402 | \r | |
403 | def setUp(self):\r | |
404 | self.server = DummyFTPServer((HOST, 0))\r | |
405 | self.server.start()\r | |
406 | self.client = ftplib.FTP(timeout=10)\r | |
407 | self.client.connect(self.server.host, self.server.port)\r | |
408 | \r | |
409 | def tearDown(self):\r | |
410 | self.client.close()\r | |
411 | self.server.stop()\r | |
412 | \r | |
413 | def test_getwelcome(self):\r | |
414 | self.assertEqual(self.client.getwelcome(), '220 welcome')\r | |
415 | \r | |
416 | def test_sanitize(self):\r | |
417 | self.assertEqual(self.client.sanitize('foo'), repr('foo'))\r | |
418 | self.assertEqual(self.client.sanitize('pass 12345'), repr('pass *****'))\r | |
419 | self.assertEqual(self.client.sanitize('PASS 12345'), repr('PASS *****'))\r | |
420 | \r | |
421 | def test_exceptions(self):\r | |
422 | self.assertRaises(ftplib.error_temp, self.client.sendcmd, 'echo 400')\r | |
423 | self.assertRaises(ftplib.error_temp, self.client.sendcmd, 'echo 499')\r | |
424 | self.assertRaises(ftplib.error_perm, self.client.sendcmd, 'echo 500')\r | |
425 | self.assertRaises(ftplib.error_perm, self.client.sendcmd, 'echo 599')\r | |
426 | self.assertRaises(ftplib.error_proto, self.client.sendcmd, 'echo 999')\r | |
427 | \r | |
428 | def test_all_errors(self):\r | |
429 | exceptions = (ftplib.error_reply, ftplib.error_temp, ftplib.error_perm,\r | |
430 | ftplib.error_proto, ftplib.Error, IOError, EOFError)\r | |
431 | for x in exceptions:\r | |
432 | try:\r | |
433 | raise x('exception not included in all_errors set')\r | |
434 | except ftplib.all_errors:\r | |
435 | pass\r | |
436 | \r | |
437 | def test_set_pasv(self):\r | |
438 | # passive mode is supposed to be enabled by default\r | |
439 | self.assertTrue(self.client.passiveserver)\r | |
440 | self.client.set_pasv(True)\r | |
441 | self.assertTrue(self.client.passiveserver)\r | |
442 | self.client.set_pasv(False)\r | |
443 | self.assertFalse(self.client.passiveserver)\r | |
444 | \r | |
445 | def test_voidcmd(self):\r | |
446 | self.client.voidcmd('echo 200')\r | |
447 | self.client.voidcmd('echo 299')\r | |
448 | self.assertRaises(ftplib.error_reply, self.client.voidcmd, 'echo 199')\r | |
449 | self.assertRaises(ftplib.error_reply, self.client.voidcmd, 'echo 300')\r | |
450 | \r | |
451 | def test_login(self):\r | |
452 | self.client.login()\r | |
453 | \r | |
454 | def test_acct(self):\r | |
455 | self.client.acct('passwd')\r | |
456 | \r | |
457 | def test_rename(self):\r | |
458 | self.client.rename('a', 'b')\r | |
459 | self.server.handler.next_response = '200'\r | |
460 | self.assertRaises(ftplib.error_reply, self.client.rename, 'a', 'b')\r | |
461 | \r | |
462 | def test_delete(self):\r | |
463 | self.client.delete('foo')\r | |
464 | self.server.handler.next_response = '199'\r | |
465 | self.assertRaises(ftplib.error_reply, self.client.delete, 'foo')\r | |
466 | \r | |
467 | def test_size(self):\r | |
468 | self.client.size('foo')\r | |
469 | \r | |
470 | def test_mkd(self):\r | |
471 | dir = self.client.mkd('/foo')\r | |
472 | self.assertEqual(dir, '/foo')\r | |
473 | \r | |
474 | def test_rmd(self):\r | |
475 | self.client.rmd('foo')\r | |
476 | \r | |
477 | def test_pwd(self):\r | |
478 | dir = self.client.pwd()\r | |
479 | self.assertEqual(dir, 'pwd ok')\r | |
480 | \r | |
481 | def test_quit(self):\r | |
482 | self.assertEqual(self.client.quit(), '221 quit ok')\r | |
483 | # Ensure the connection gets closed; sock attribute should be None\r | |
484 | self.assertEqual(self.client.sock, None)\r | |
485 | \r | |
486 | def test_retrbinary(self):\r | |
487 | received = []\r | |
488 | self.client.retrbinary('retr', received.append)\r | |
489 | self.assertEqual(''.join(received), RETR_DATA)\r | |
490 | \r | |
491 | def test_retrbinary_rest(self):\r | |
492 | for rest in (0, 10, 20):\r | |
493 | received = []\r | |
494 | self.client.retrbinary('retr', received.append, rest=rest)\r | |
495 | self.assertEqual(''.join(received), RETR_DATA[rest:],\r | |
496 | msg='rest test case %d %d %d' % (rest,\r | |
497 | len(''.join(received)),\r | |
498 | len(RETR_DATA[rest:])))\r | |
499 | \r | |
500 | def test_retrlines(self):\r | |
501 | received = []\r | |
502 | self.client.retrlines('retr', received.append)\r | |
503 | self.assertEqual(''.join(received), RETR_DATA.replace('\r\n', ''))\r | |
504 | \r | |
505 | def test_storbinary(self):\r | |
506 | f = StringIO.StringIO(RETR_DATA)\r | |
507 | self.client.storbinary('stor', f)\r | |
508 | self.assertEqual(self.server.handler.last_received_data, RETR_DATA)\r | |
509 | # test new callback arg\r | |
510 | flag = []\r | |
511 | f.seek(0)\r | |
512 | self.client.storbinary('stor', f, callback=lambda x: flag.append(None))\r | |
513 | self.assertTrue(flag)\r | |
514 | \r | |
515 | def test_storbinary_rest(self):\r | |
516 | f = StringIO.StringIO(RETR_DATA)\r | |
517 | for r in (30, '30'):\r | |
518 | f.seek(0)\r | |
519 | self.client.storbinary('stor', f, rest=r)\r | |
520 | self.assertEqual(self.server.handler.rest, str(r))\r | |
521 | \r | |
522 | def test_storlines(self):\r | |
523 | f = StringIO.StringIO(RETR_DATA.replace('\r\n', '\n'))\r | |
524 | self.client.storlines('stor', f)\r | |
525 | self.assertEqual(self.server.handler.last_received_data, RETR_DATA)\r | |
526 | # test new callback arg\r | |
527 | flag = []\r | |
528 | f.seek(0)\r | |
529 | self.client.storlines('stor foo', f, callback=lambda x: flag.append(None))\r | |
530 | self.assertTrue(flag)\r | |
531 | \r | |
532 | def test_nlst(self):\r | |
533 | self.client.nlst()\r | |
534 | self.assertEqual(self.client.nlst(), NLST_DATA.split('\r\n')[:-1])\r | |
535 | \r | |
536 | def test_dir(self):\r | |
537 | l = []\r | |
538 | self.client.dir(lambda x: l.append(x))\r | |
539 | self.assertEqual(''.join(l), LIST_DATA.replace('\r\n', ''))\r | |
540 | \r | |
541 | def test_makeport(self):\r | |
542 | self.client.makeport()\r | |
543 | # IPv4 is in use, just make sure send_eprt has not been used\r | |
544 | self.assertEqual(self.server.handler.last_received_cmd, 'port')\r | |
545 | \r | |
546 | def test_makepasv(self):\r | |
547 | host, port = self.client.makepasv()\r | |
548 | conn = socket.create_connection((host, port), 10)\r | |
549 | conn.close()\r | |
550 | # IPv4 is in use, just make sure send_epsv has not been used\r | |
551 | self.assertEqual(self.server.handler.last_received_cmd, 'pasv')\r | |
552 | \r | |
553 | \r | |
554 | class TestIPv6Environment(TestCase):\r | |
555 | \r | |
556 | def setUp(self):\r | |
557 | self.server = DummyFTPServer((HOST, 0), af=socket.AF_INET6)\r | |
558 | self.server.start()\r | |
559 | self.client = ftplib.FTP()\r | |
560 | self.client.connect(self.server.host, self.server.port)\r | |
561 | \r | |
562 | def tearDown(self):\r | |
563 | self.client.close()\r | |
564 | self.server.stop()\r | |
565 | \r | |
566 | def test_af(self):\r | |
567 | self.assertEqual(self.client.af, socket.AF_INET6)\r | |
568 | \r | |
569 | def test_makeport(self):\r | |
570 | self.client.makeport()\r | |
571 | self.assertEqual(self.server.handler.last_received_cmd, 'eprt')\r | |
572 | \r | |
573 | def test_makepasv(self):\r | |
574 | host, port = self.client.makepasv()\r | |
575 | conn = socket.create_connection((host, port), 10)\r | |
576 | conn.close()\r | |
577 | self.assertEqual(self.server.handler.last_received_cmd, 'epsv')\r | |
578 | \r | |
579 | def test_transfer(self):\r | |
580 | def retr():\r | |
581 | received = []\r | |
582 | self.client.retrbinary('retr', received.append)\r | |
583 | self.assertEqual(''.join(received), RETR_DATA)\r | |
584 | self.client.set_pasv(True)\r | |
585 | retr()\r | |
586 | self.client.set_pasv(False)\r | |
587 | retr()\r | |
588 | \r | |
589 | \r | |
590 | class TestTLS_FTPClassMixin(TestFTPClass):\r | |
591 | """Repeat TestFTPClass tests starting the TLS layer for both control\r | |
592 | and data connections first.\r | |
593 | """\r | |
594 | \r | |
595 | def setUp(self):\r | |
596 | self.server = DummyTLS_FTPServer((HOST, 0))\r | |
597 | self.server.start()\r | |
598 | self.client = ftplib.FTP_TLS(timeout=10)\r | |
599 | self.client.connect(self.server.host, self.server.port)\r | |
600 | # enable TLS\r | |
601 | self.client.auth()\r | |
602 | self.client.prot_p()\r | |
603 | \r | |
604 | \r | |
605 | class TestTLS_FTPClass(TestCase):\r | |
606 | """Specific TLS_FTP class tests."""\r | |
607 | \r | |
608 | def setUp(self):\r | |
609 | self.server = DummyTLS_FTPServer((HOST, 0))\r | |
610 | self.server.start()\r | |
611 | self.client = ftplib.FTP_TLS(timeout=10)\r | |
612 | self.client.connect(self.server.host, self.server.port)\r | |
613 | \r | |
614 | def tearDown(self):\r | |
615 | self.client.close()\r | |
616 | self.server.stop()\r | |
617 | \r | |
618 | def test_control_connection(self):\r | |
619 | self.assertNotIsInstance(self.client.sock, ssl.SSLSocket)\r | |
620 | self.client.auth()\r | |
621 | self.assertIsInstance(self.client.sock, ssl.SSLSocket)\r | |
622 | \r | |
623 | def test_data_connection(self):\r | |
624 | # clear text\r | |
625 | sock = self.client.transfercmd('list')\r | |
626 | self.assertNotIsInstance(sock, ssl.SSLSocket)\r | |
627 | sock.close()\r | |
628 | self.assertEqual(self.client.voidresp(), "226 transfer complete")\r | |
629 | \r | |
630 | # secured, after PROT P\r | |
631 | self.client.prot_p()\r | |
632 | sock = self.client.transfercmd('list')\r | |
633 | self.assertIsInstance(sock, ssl.SSLSocket)\r | |
634 | sock.close()\r | |
635 | self.assertEqual(self.client.voidresp(), "226 transfer complete")\r | |
636 | \r | |
637 | # PROT C is issued, the connection must be in cleartext again\r | |
638 | self.client.prot_c()\r | |
639 | sock = self.client.transfercmd('list')\r | |
640 | self.assertNotIsInstance(sock, ssl.SSLSocket)\r | |
641 | sock.close()\r | |
642 | self.assertEqual(self.client.voidresp(), "226 transfer complete")\r | |
643 | \r | |
644 | def test_login(self):\r | |
645 | # login() is supposed to implicitly secure the control connection\r | |
646 | self.assertNotIsInstance(self.client.sock, ssl.SSLSocket)\r | |
647 | self.client.login()\r | |
648 | self.assertIsInstance(self.client.sock, ssl.SSLSocket)\r | |
649 | # make sure that AUTH TLS doesn't get issued again\r | |
650 | self.client.login()\r | |
651 | \r | |
652 | def test_auth_issued_twice(self):\r | |
653 | self.client.auth()\r | |
654 | self.assertRaises(ValueError, self.client.auth)\r | |
655 | \r | |
656 | def test_auth_ssl(self):\r | |
657 | try:\r | |
658 | self.client.ssl_version = ssl.PROTOCOL_SSLv3\r | |
659 | self.client.auth()\r | |
660 | self.assertRaises(ValueError, self.client.auth)\r | |
661 | finally:\r | |
662 | self.client.ssl_version = ssl.PROTOCOL_TLSv1\r | |
663 | \r | |
664 | \r | |
665 | class TestTimeouts(TestCase):\r | |
666 | \r | |
667 | def setUp(self):\r | |
668 | self.evt = threading.Event()\r | |
669 | self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\r | |
670 | self.sock.settimeout(3)\r | |
671 | self.port = test_support.bind_port(self.sock)\r | |
672 | threading.Thread(target=self.server, args=(self.evt,self.sock)).start()\r | |
673 | # Wait for the server to be ready.\r | |
674 | self.evt.wait()\r | |
675 | self.evt.clear()\r | |
676 | ftplib.FTP.port = self.port\r | |
677 | \r | |
678 | def tearDown(self):\r | |
679 | self.evt.wait()\r | |
680 | \r | |
681 | def server(self, evt, serv):\r | |
682 | # This method sets the evt 3 times:\r | |
683 | # 1) when the connection is ready to be accepted.\r | |
684 | # 2) when it is safe for the caller to close the connection\r | |
685 | # 3) when we have closed the socket\r | |
686 | serv.listen(5)\r | |
687 | # (1) Signal the caller that we are ready to accept the connection.\r | |
688 | evt.set()\r | |
689 | try:\r | |
690 | conn, addr = serv.accept()\r | |
691 | except socket.timeout:\r | |
692 | pass\r | |
693 | else:\r | |
694 | conn.send("1 Hola mundo\n")\r | |
695 | # (2) Signal the caller that it is safe to close the socket.\r | |
696 | evt.set()\r | |
697 | conn.close()\r | |
698 | finally:\r | |
699 | serv.close()\r | |
700 | # (3) Signal the caller that we are done.\r | |
701 | evt.set()\r | |
702 | \r | |
703 | def testTimeoutDefault(self):\r | |
704 | # default -- use global socket timeout\r | |
705 | self.assertTrue(socket.getdefaulttimeout() is None)\r | |
706 | socket.setdefaulttimeout(30)\r | |
707 | try:\r | |
708 | ftp = ftplib.FTP("localhost")\r | |
709 | finally:\r | |
710 | socket.setdefaulttimeout(None)\r | |
711 | self.assertEqual(ftp.sock.gettimeout(), 30)\r | |
712 | self.evt.wait()\r | |
713 | ftp.close()\r | |
714 | \r | |
715 | def testTimeoutNone(self):\r | |
716 | # no timeout -- do not use global socket timeout\r | |
717 | self.assertTrue(socket.getdefaulttimeout() is None)\r | |
718 | socket.setdefaulttimeout(30)\r | |
719 | try:\r | |
720 | ftp = ftplib.FTP("localhost", timeout=None)\r | |
721 | finally:\r | |
722 | socket.setdefaulttimeout(None)\r | |
723 | self.assertTrue(ftp.sock.gettimeout() is None)\r | |
724 | self.evt.wait()\r | |
725 | ftp.close()\r | |
726 | \r | |
727 | def testTimeoutValue(self):\r | |
728 | # a value\r | |
729 | ftp = ftplib.FTP(HOST, timeout=30)\r | |
730 | self.assertEqual(ftp.sock.gettimeout(), 30)\r | |
731 | self.evt.wait()\r | |
732 | ftp.close()\r | |
733 | \r | |
734 | def testTimeoutConnect(self):\r | |
735 | ftp = ftplib.FTP()\r | |
736 | ftp.connect(HOST, timeout=30)\r | |
737 | self.assertEqual(ftp.sock.gettimeout(), 30)\r | |
738 | self.evt.wait()\r | |
739 | ftp.close()\r | |
740 | \r | |
741 | def testTimeoutDifferentOrder(self):\r | |
742 | ftp = ftplib.FTP(timeout=30)\r | |
743 | ftp.connect(HOST)\r | |
744 | self.assertEqual(ftp.sock.gettimeout(), 30)\r | |
745 | self.evt.wait()\r | |
746 | ftp.close()\r | |
747 | \r | |
748 | def testTimeoutDirectAccess(self):\r | |
749 | ftp = ftplib.FTP()\r | |
750 | ftp.timeout = 30\r | |
751 | ftp.connect(HOST)\r | |
752 | self.assertEqual(ftp.sock.gettimeout(), 30)\r | |
753 | self.evt.wait()\r | |
754 | ftp.close()\r | |
755 | \r | |
756 | \r | |
757 | def test_main():\r | |
758 | tests = [TestFTPClass, TestTimeouts]\r | |
759 | if socket.has_ipv6:\r | |
760 | try:\r | |
761 | DummyFTPServer((HOST, 0), af=socket.AF_INET6)\r | |
762 | except socket.error:\r | |
763 | pass\r | |
764 | else:\r | |
765 | tests.append(TestIPv6Environment)\r | |
766 | \r | |
767 | if ssl is not None:\r | |
768 | tests.extend([TestTLS_FTPClassMixin, TestTLS_FTPClass])\r | |
769 | \r | |
770 | thread_info = test_support.threading_setup()\r | |
771 | try:\r | |
772 | test_support.run_unittest(*tests)\r | |
773 | finally:\r | |
774 | test_support.threading_cleanup(*thread_info)\r | |
775 | \r | |
776 | \r | |
777 | if __name__ == '__main__':\r | |
778 | test_main()\r |