| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 1 | """Test script for ftplib module.""" | 
 | 2 |  | 
| Antoine Pitrou | f988cd0 | 2009-11-17 20:21:14 +0000 | [diff] [blame] | 3 | # Modified by Giampaolo Rodola' to test FTP class, IPv6 and TLS | 
 | 4 | # environment | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 5 |  | 
| Guido van Rossum | d8faa36 | 2007-04-27 19:54:29 +0000 | [diff] [blame] | 6 | import ftplib | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 7 | import asyncore | 
 | 8 | import asynchat | 
 | 9 | import socket | 
 | 10 | import io | 
| Antoine Pitrou | f988cd0 | 2009-11-17 20:21:14 +0000 | [diff] [blame] | 11 | import errno | 
 | 12 | import os | 
| Giampaolo Rodolà | bd576b7 | 2010-05-10 14:53:29 +0000 | [diff] [blame] | 13 | import time | 
| Antoine Pitrou | f988cd0 | 2009-11-17 20:21:14 +0000 | [diff] [blame] | 14 | try: | 
 | 15 |     import ssl | 
 | 16 | except ImportError: | 
 | 17 |     ssl = None | 
| Christian Heimes | e7945d7 | 2013-12-15 19:38:22 +0100 | [diff] [blame] | 18 |     HAS_SNI = False | 
 | 19 | else: | 
 | 20 |     from ssl import HAS_SNI | 
| Guido van Rossum | d8faa36 | 2007-04-27 19:54:29 +0000 | [diff] [blame] | 21 |  | 
| Serhiy Storchaka | 4376763 | 2013-11-03 21:31:38 +0200 | [diff] [blame] | 22 | from unittest import TestCase, skipUnless | 
| Benjamin Peterson | ee8712c | 2008-05-20 21:35:26 +0000 | [diff] [blame] | 23 | from test import support | 
| Antoine Pitrou | f6fbf56 | 2013-08-22 00:39:46 +0200 | [diff] [blame] | 24 | from test.support import HOST, HOSTv6 | 
| Victor Stinner | 45df820 | 2010-04-28 22:31:17 +0000 | [diff] [blame] | 25 | threading = support.import_module('threading') | 
| Guido van Rossum | d8faa36 | 2007-04-27 19:54:29 +0000 | [diff] [blame] | 26 |  | 
| Giampaolo Rodola' | 0d4f08c | 2013-05-16 15:12:01 +0200 | [diff] [blame] | 27 | TIMEOUT = 3 | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 28 | # the dummy data returned by server over the data channel when | 
| Giampaolo Rodola' | d78def9 | 2011-05-06 19:49:08 +0200 | [diff] [blame] | 29 | # RETR, LIST, NLST, MLSD commands are issued | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 30 | RETR_DATA = 'abcde12345\r\n' * 1000 | 
 | 31 | LIST_DATA = 'foo\r\nbar\r\n' | 
 | 32 | NLST_DATA = 'foo\r\nbar\r\n' | 
| Giampaolo Rodola' | d78def9 | 2011-05-06 19:49:08 +0200 | [diff] [blame] | 33 | MLSD_DATA = ("type=cdir;perm=el;unique==keVO1+ZF4; test\r\n" | 
 | 34 |              "type=pdir;perm=e;unique==keVO1+d?3; ..\r\n" | 
 | 35 |              "type=OS.unix=slink:/foobar;perm=;unique==keVO1+4G4; foobar\r\n" | 
 | 36 |              "type=OS.unix=chr-13/29;perm=;unique==keVO1+5G4; device\r\n" | 
 | 37 |              "type=OS.unix=blk-11/108;perm=;unique==keVO1+6G4; block\r\n" | 
 | 38 |              "type=file;perm=awr;unique==keVO1+8G4; writable\r\n" | 
 | 39 |              "type=dir;perm=cpmel;unique==keVO1+7G4; promiscuous\r\n" | 
 | 40 |              "type=dir;perm=;unique==keVO1+1t2; no-exec\r\n" | 
 | 41 |              "type=file;perm=r;unique==keVO1+EG4; two words\r\n" | 
 | 42 |              "type=file;perm=r;unique==keVO1+IH4;  leading space\r\n" | 
 | 43 |              "type=file;perm=r;unique==keVO1+1G4; file1\r\n" | 
 | 44 |              "type=dir;perm=cpmel;unique==keVO1+7G4; incoming\r\n" | 
 | 45 |              "type=file;perm=r;unique==keVO1+1G4; file2\r\n" | 
 | 46 |              "type=file;perm=r;unique==keVO1+1G4; file3\r\n" | 
 | 47 |              "type=file;perm=r;unique==keVO1+1G4; file4\r\n") | 
| Christian Heimes | 836baa5 | 2008-02-26 08:18:30 +0000 | [diff] [blame] | 48 |  | 
| Christian Heimes | 836baa5 | 2008-02-26 08:18:30 +0000 | [diff] [blame] | 49 |  | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 50 | class DummyDTPHandler(asynchat.async_chat): | 
| Antoine Pitrou | 2c4f98b | 2010-04-23 00:16:21 +0000 | [diff] [blame] | 51 |     dtp_conn_closed = False | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 52 |  | 
 | 53 |     def __init__(self, conn, baseclass): | 
 | 54 |         asynchat.async_chat.__init__(self, conn) | 
 | 55 |         self.baseclass = baseclass | 
 | 56 |         self.baseclass.last_received_data = '' | 
 | 57 |  | 
 | 58 |     def handle_read(self): | 
| Giampaolo Rodolà | f96482e | 2010-08-04 10:36:18 +0000 | [diff] [blame] | 59 |         self.baseclass.last_received_data += self.recv(1024).decode('ascii') | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 60 |  | 
 | 61 |     def handle_close(self): | 
| Antoine Pitrou | 2c4f98b | 2010-04-23 00:16:21 +0000 | [diff] [blame] | 62 |         # XXX: this method can be called many times in a row for a single | 
 | 63 |         # connection, including in clear-text (non-TLS) mode. | 
 | 64 |         # (behaviour witnessed with test_data_connection) | 
 | 65 |         if not self.dtp_conn_closed: | 
 | 66 |             self.baseclass.push('226 transfer complete') | 
 | 67 |             self.close() | 
 | 68 |             self.dtp_conn_closed = True | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 69 |  | 
 | 70 |     def push(self, what): | 
| Giampaolo Rodola' | d78def9 | 2011-05-06 19:49:08 +0200 | [diff] [blame] | 71 |         if self.baseclass.next_data is not None: | 
 | 72 |             what = self.baseclass.next_data | 
 | 73 |             self.baseclass.next_data = None | 
 | 74 |         if not what: | 
 | 75 |             return self.close_when_done() | 
| Giampaolo Rodolà | f96482e | 2010-08-04 10:36:18 +0000 | [diff] [blame] | 76 |         super(DummyDTPHandler, self).push(what.encode('ascii')) | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 77 |  | 
| Giampaolo Rodolà | d930b63 | 2010-05-06 20:21:57 +0000 | [diff] [blame] | 78 |     def handle_error(self): | 
 | 79 |         raise | 
 | 80 |  | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 81 |  | 
 | 82 | class DummyFTPHandler(asynchat.async_chat): | 
 | 83 |  | 
| Antoine Pitrou | f988cd0 | 2009-11-17 20:21:14 +0000 | [diff] [blame] | 84 |     dtp_handler = DummyDTPHandler | 
 | 85 |  | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 86 |     def __init__(self, conn): | 
 | 87 |         asynchat.async_chat.__init__(self, conn) | 
| Giampaolo Rodola' | 0b5c21f | 2011-05-07 19:03:47 +0200 | [diff] [blame] | 88 |         # tells the socket to handle urgent data inline (ABOR command) | 
 | 89 |         self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_OOBINLINE, 1) | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 90 |         self.set_terminator(b"\r\n") | 
 | 91 |         self.in_buffer = [] | 
 | 92 |         self.dtp = None | 
 | 93 |         self.last_received_cmd = None | 
 | 94 |         self.last_received_data = '' | 
 | 95 |         self.next_response = '' | 
| Giampaolo Rodola' | d78def9 | 2011-05-06 19:49:08 +0200 | [diff] [blame] | 96 |         self.next_data = None | 
| Antoine Pitrou | 648bcd7 | 2009-11-27 13:23:26 +0000 | [diff] [blame] | 97 |         self.rest = None | 
| Serhiy Storchaka | c30b178 | 2013-10-20 16:58:27 +0300 | [diff] [blame] | 98 |         self.next_retr_data = RETR_DATA | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 99 |         self.push('220 welcome') | 
 | 100 |  | 
 | 101 |     def collect_incoming_data(self, data): | 
 | 102 |         self.in_buffer.append(data) | 
 | 103 |  | 
 | 104 |     def found_terminator(self): | 
 | 105 |         line = b''.join(self.in_buffer).decode('ascii') | 
 | 106 |         self.in_buffer = [] | 
 | 107 |         if self.next_response: | 
 | 108 |             self.push(self.next_response) | 
 | 109 |             self.next_response = '' | 
 | 110 |         cmd = line.split(' ')[0].lower() | 
 | 111 |         self.last_received_cmd = cmd | 
 | 112 |         space = line.find(' ') | 
 | 113 |         if space != -1: | 
 | 114 |             arg = line[space + 1:] | 
 | 115 |         else: | 
 | 116 |             arg = "" | 
 | 117 |         if hasattr(self, 'cmd_' + cmd): | 
 | 118 |             method = getattr(self, 'cmd_' + cmd) | 
 | 119 |             method(arg) | 
 | 120 |         else: | 
 | 121 |             self.push('550 command "%s" not understood.' %cmd) | 
 | 122 |  | 
 | 123 |     def handle_error(self): | 
 | 124 |         raise | 
 | 125 |  | 
 | 126 |     def push(self, data): | 
 | 127 |         asynchat.async_chat.push(self, data.encode('ascii') + b'\r\n') | 
 | 128 |  | 
 | 129 |     def cmd_port(self, arg): | 
 | 130 |         addr = list(map(int, arg.split(','))) | 
 | 131 |         ip = '%d.%d.%d.%d' %tuple(addr[:4]) | 
 | 132 |         port = (addr[4] * 256) + addr[5] | 
| Giampaolo Rodola' | 0d4f08c | 2013-05-16 15:12:01 +0200 | [diff] [blame] | 133 |         s = socket.create_connection((ip, port), timeout=TIMEOUT) | 
| Antoine Pitrou | f988cd0 | 2009-11-17 20:21:14 +0000 | [diff] [blame] | 134 |         self.dtp = self.dtp_handler(s, baseclass=self) | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 135 |         self.push('200 active data connection established') | 
 | 136 |  | 
 | 137 |     def cmd_pasv(self, arg): | 
| Brett Cannon | 918e2d4 | 2010-10-29 23:26:25 +0000 | [diff] [blame] | 138 |         with socket.socket() as sock: | 
 | 139 |             sock.bind((self.socket.getsockname()[0], 0)) | 
| Charles-François Natali | 6e20460 | 2014-07-23 19:28:13 +0100 | [diff] [blame^] | 140 |             sock.listen() | 
| Giampaolo Rodola' | 0d4f08c | 2013-05-16 15:12:01 +0200 | [diff] [blame] | 141 |             sock.settimeout(TIMEOUT) | 
| Brett Cannon | 918e2d4 | 2010-10-29 23:26:25 +0000 | [diff] [blame] | 142 |             ip, port = sock.getsockname()[:2] | 
 | 143 |             ip = ip.replace('.', ','); p1 = port / 256; p2 = port % 256 | 
 | 144 |             self.push('227 entering passive mode (%s,%d,%d)' %(ip, p1, p2)) | 
 | 145 |             conn, addr = sock.accept() | 
 | 146 |             self.dtp = self.dtp_handler(conn, baseclass=self) | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 147 |  | 
 | 148 |     def cmd_eprt(self, arg): | 
 | 149 |         af, ip, port = arg.split(arg[0])[1:-1] | 
 | 150 |         port = int(port) | 
| Giampaolo Rodola' | 0d4f08c | 2013-05-16 15:12:01 +0200 | [diff] [blame] | 151 |         s = socket.create_connection((ip, port), timeout=TIMEOUT) | 
| Antoine Pitrou | f988cd0 | 2009-11-17 20:21:14 +0000 | [diff] [blame] | 152 |         self.dtp = self.dtp_handler(s, baseclass=self) | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 153 |         self.push('200 active data connection established') | 
 | 154 |  | 
 | 155 |     def cmd_epsv(self, arg): | 
| Brett Cannon | 918e2d4 | 2010-10-29 23:26:25 +0000 | [diff] [blame] | 156 |         with socket.socket(socket.AF_INET6) as sock: | 
 | 157 |             sock.bind((self.socket.getsockname()[0], 0)) | 
| Charles-François Natali | 6e20460 | 2014-07-23 19:28:13 +0100 | [diff] [blame^] | 158 |             sock.listen() | 
| Giampaolo Rodola' | 0d4f08c | 2013-05-16 15:12:01 +0200 | [diff] [blame] | 159 |             sock.settimeout(TIMEOUT) | 
| Brett Cannon | 918e2d4 | 2010-10-29 23:26:25 +0000 | [diff] [blame] | 160 |             port = sock.getsockname()[1] | 
 | 161 |             self.push('229 entering extended passive mode (|||%d|)' %port) | 
 | 162 |             conn, addr = sock.accept() | 
 | 163 |             self.dtp = self.dtp_handler(conn, baseclass=self) | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 164 |  | 
 | 165 |     def cmd_echo(self, arg): | 
 | 166 |         # sends back the received string (used by the test suite) | 
 | 167 |         self.push(arg) | 
 | 168 |  | 
| Giampaolo Rodolà | bd576b7 | 2010-05-10 14:53:29 +0000 | [diff] [blame] | 169 |     def cmd_noop(self, arg): | 
 | 170 |         self.push('200 noop ok') | 
 | 171 |  | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 172 |     def cmd_user(self, arg): | 
 | 173 |         self.push('331 username ok') | 
 | 174 |  | 
 | 175 |     def cmd_pass(self, arg): | 
 | 176 |         self.push('230 password ok') | 
 | 177 |  | 
 | 178 |     def cmd_acct(self, arg): | 
 | 179 |         self.push('230 acct ok') | 
 | 180 |  | 
 | 181 |     def cmd_rnfr(self, arg): | 
 | 182 |         self.push('350 rnfr ok') | 
 | 183 |  | 
 | 184 |     def cmd_rnto(self, arg): | 
 | 185 |         self.push('250 rnto ok') | 
 | 186 |  | 
 | 187 |     def cmd_dele(self, arg): | 
 | 188 |         self.push('250 dele ok') | 
 | 189 |  | 
 | 190 |     def cmd_cwd(self, arg): | 
 | 191 |         self.push('250 cwd ok') | 
 | 192 |  | 
 | 193 |     def cmd_size(self, arg): | 
 | 194 |         self.push('250 1000') | 
 | 195 |  | 
 | 196 |     def cmd_mkd(self, arg): | 
 | 197 |         self.push('257 "%s"' %arg) | 
 | 198 |  | 
 | 199 |     def cmd_rmd(self, arg): | 
 | 200 |         self.push('250 rmd ok') | 
 | 201 |  | 
 | 202 |     def cmd_pwd(self, arg): | 
 | 203 |         self.push('257 "pwd ok"') | 
 | 204 |  | 
 | 205 |     def cmd_type(self, arg): | 
| Giampaolo Rodolà | f96482e | 2010-08-04 10:36:18 +0000 | [diff] [blame] | 206 |         self.push('200 type ok') | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 207 |  | 
 | 208 |     def cmd_quit(self, arg): | 
 | 209 |         self.push('221 quit ok') | 
 | 210 |         self.close() | 
 | 211 |  | 
| Giampaolo Rodola' | 0b5c21f | 2011-05-07 19:03:47 +0200 | [diff] [blame] | 212 |     def cmd_abor(self, arg): | 
 | 213 |         self.push('226 abor ok') | 
 | 214 |  | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 215 |     def cmd_stor(self, arg): | 
 | 216 |         self.push('125 stor ok') | 
 | 217 |  | 
| Antoine Pitrou | 648bcd7 | 2009-11-27 13:23:26 +0000 | [diff] [blame] | 218 |     def cmd_rest(self, arg): | 
 | 219 |         self.rest = arg | 
 | 220 |         self.push('350 rest ok') | 
 | 221 |  | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 222 |     def cmd_retr(self, arg): | 
 | 223 |         self.push('125 retr ok') | 
| Antoine Pitrou | 648bcd7 | 2009-11-27 13:23:26 +0000 | [diff] [blame] | 224 |         if self.rest is not None: | 
 | 225 |             offset = int(self.rest) | 
 | 226 |         else: | 
 | 227 |             offset = 0 | 
| Serhiy Storchaka | c30b178 | 2013-10-20 16:58:27 +0300 | [diff] [blame] | 228 |         self.dtp.push(self.next_retr_data[offset:]) | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 229 |         self.dtp.close_when_done() | 
| Antoine Pitrou | 648bcd7 | 2009-11-27 13:23:26 +0000 | [diff] [blame] | 230 |         self.rest = None | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 231 |  | 
 | 232 |     def cmd_list(self, arg): | 
 | 233 |         self.push('125 list ok') | 
 | 234 |         self.dtp.push(LIST_DATA) | 
 | 235 |         self.dtp.close_when_done() | 
 | 236 |  | 
 | 237 |     def cmd_nlst(self, arg): | 
 | 238 |         self.push('125 nlst ok') | 
 | 239 |         self.dtp.push(NLST_DATA) | 
 | 240 |         self.dtp.close_when_done() | 
 | 241 |  | 
| Giampaolo Rodola' | d78def9 | 2011-05-06 19:49:08 +0200 | [diff] [blame] | 242 |     def cmd_opts(self, arg): | 
 | 243 |         self.push('200 opts ok') | 
 | 244 |  | 
 | 245 |     def cmd_mlsd(self, arg): | 
 | 246 |         self.push('125 mlsd ok') | 
 | 247 |         self.dtp.push(MLSD_DATA) | 
 | 248 |         self.dtp.close_when_done() | 
 | 249 |  | 
| Serhiy Storchaka | c30b178 | 2013-10-20 16:58:27 +0300 | [diff] [blame] | 250 |     def cmd_setlongretr(self, arg): | 
 | 251 |         # For testing. Next RETR will return long line. | 
 | 252 |         self.next_retr_data = 'x' * int(arg) | 
 | 253 |         self.push('125 setlongretr ok') | 
 | 254 |  | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 255 |  | 
 | 256 | class DummyFTPServer(asyncore.dispatcher, threading.Thread): | 
 | 257 |  | 
 | 258 |     handler = DummyFTPHandler | 
 | 259 |  | 
 | 260 |     def __init__(self, address, af=socket.AF_INET): | 
 | 261 |         threading.Thread.__init__(self) | 
 | 262 |         asyncore.dispatcher.__init__(self) | 
 | 263 |         self.create_socket(af, socket.SOCK_STREAM) | 
 | 264 |         self.bind(address) | 
 | 265 |         self.listen(5) | 
 | 266 |         self.active = False | 
 | 267 |         self.active_lock = threading.Lock() | 
 | 268 |         self.host, self.port = self.socket.getsockname()[:2] | 
| Giampaolo Rodolà | bd576b7 | 2010-05-10 14:53:29 +0000 | [diff] [blame] | 269 |         self.handler_instance = None | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 270 |  | 
 | 271 |     def start(self): | 
 | 272 |         assert not self.active | 
 | 273 |         self.__flag = threading.Event() | 
 | 274 |         threading.Thread.start(self) | 
 | 275 |         self.__flag.wait() | 
 | 276 |  | 
 | 277 |     def run(self): | 
 | 278 |         self.active = True | 
 | 279 |         self.__flag.set() | 
 | 280 |         while self.active and asyncore.socket_map: | 
 | 281 |             self.active_lock.acquire() | 
 | 282 |             asyncore.loop(timeout=0.1, count=1) | 
 | 283 |             self.active_lock.release() | 
 | 284 |         asyncore.close_all(ignore_all=True) | 
 | 285 |  | 
 | 286 |     def stop(self): | 
 | 287 |         assert self.active | 
 | 288 |         self.active = False | 
 | 289 |         self.join() | 
 | 290 |  | 
| Giampaolo Rodolà | 977c707 | 2010-10-04 21:08:36 +0000 | [diff] [blame] | 291 |     def handle_accepted(self, conn, addr): | 
| Giampaolo Rodolà | bd576b7 | 2010-05-10 14:53:29 +0000 | [diff] [blame] | 292 |         self.handler_instance = self.handler(conn) | 
| Benjamin Peterson | d06e3b0 | 2008-09-28 21:00:42 +0000 | [diff] [blame] | 293 |  | 
 | 294 |     def handle_connect(self): | 
 | 295 |         self.close() | 
 | 296 |     handle_read = handle_connect | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 297 |  | 
 | 298 |     def writable(self): | 
 | 299 |         return 0 | 
 | 300 |  | 
 | 301 |     def handle_error(self): | 
 | 302 |         raise | 
 | 303 |  | 
 | 304 |  | 
| Antoine Pitrou | f988cd0 | 2009-11-17 20:21:14 +0000 | [diff] [blame] | 305 | if ssl is not None: | 
 | 306 |  | 
| Christian Heimes | e5b5edf | 2013-12-02 02:56:02 +0100 | [diff] [blame] | 307 |     CERTFILE = os.path.join(os.path.dirname(__file__), "keycert3.pem") | 
 | 308 |     CAFILE = os.path.join(os.path.dirname(__file__), "pycacert.pem") | 
| Antoine Pitrou | f988cd0 | 2009-11-17 20:21:14 +0000 | [diff] [blame] | 309 |  | 
 | 310 |     class SSLConnection(asyncore.dispatcher): | 
 | 311 |         """An asyncore.dispatcher subclass supporting TLS/SSL.""" | 
 | 312 |  | 
 | 313 |         _ssl_accepting = False | 
| Antoine Pitrou | 2c4f98b | 2010-04-23 00:16:21 +0000 | [diff] [blame] | 314 |         _ssl_closing = False | 
| Antoine Pitrou | f988cd0 | 2009-11-17 20:21:14 +0000 | [diff] [blame] | 315 |  | 
 | 316 |         def secure_connection(self): | 
| Antoine Pitrou | f988cd0 | 2009-11-17 20:21:14 +0000 | [diff] [blame] | 317 |             socket = ssl.wrap_socket(self.socket, suppress_ragged_eofs=False, | 
 | 318 |                                      certfile=CERTFILE, server_side=True, | 
 | 319 |                                      do_handshake_on_connect=False, | 
 | 320 |                                      ssl_version=ssl.PROTOCOL_SSLv23) | 
| Giampaolo Rodola' | 096dcb1 | 2011-06-27 11:17:51 +0200 | [diff] [blame] | 321 |             self.del_channel() | 
| Antoine Pitrou | f988cd0 | 2009-11-17 20:21:14 +0000 | [diff] [blame] | 322 |             self.set_socket(socket) | 
 | 323 |             self._ssl_accepting = True | 
 | 324 |  | 
 | 325 |         def _do_ssl_handshake(self): | 
 | 326 |             try: | 
 | 327 |                 self.socket.do_handshake() | 
 | 328 |             except ssl.SSLError as err: | 
 | 329 |                 if err.args[0] in (ssl.SSL_ERROR_WANT_READ, | 
 | 330 |                                    ssl.SSL_ERROR_WANT_WRITE): | 
 | 331 |                     return | 
 | 332 |                 elif err.args[0] == ssl.SSL_ERROR_EOF: | 
 | 333 |                     return self.handle_close() | 
 | 334 |                 raise | 
| Andrew Svetlov | 0832af6 | 2012-12-18 23:10:48 +0200 | [diff] [blame] | 335 |             except OSError as err: | 
| Antoine Pitrou | f988cd0 | 2009-11-17 20:21:14 +0000 | [diff] [blame] | 336 |                 if err.args[0] == errno.ECONNABORTED: | 
 | 337 |                     return self.handle_close() | 
 | 338 |             else: | 
 | 339 |                 self._ssl_accepting = False | 
 | 340 |  | 
| Antoine Pitrou | 2c4f98b | 2010-04-23 00:16:21 +0000 | [diff] [blame] | 341 |         def _do_ssl_shutdown(self): | 
 | 342 |             self._ssl_closing = True | 
 | 343 |             try: | 
 | 344 |                 self.socket = self.socket.unwrap() | 
 | 345 |             except ssl.SSLError as err: | 
 | 346 |                 if err.args[0] in (ssl.SSL_ERROR_WANT_READ, | 
 | 347 |                                    ssl.SSL_ERROR_WANT_WRITE): | 
 | 348 |                     return | 
| Andrew Svetlov | 0832af6 | 2012-12-18 23:10:48 +0200 | [diff] [blame] | 349 |             except OSError as err: | 
| Antoine Pitrou | 2c4f98b | 2010-04-23 00:16:21 +0000 | [diff] [blame] | 350 |                 # Any "socket error" corresponds to a SSL_ERROR_SYSCALL return | 
 | 351 |                 # from OpenSSL's SSL_shutdown(), corresponding to a | 
 | 352 |                 # closed socket condition. See also: | 
 | 353 |                 # http://www.mail-archive.com/openssl-users@openssl.org/msg60710.html | 
 | 354 |                 pass | 
 | 355 |             self._ssl_closing = False | 
| Benjamin Peterson | b29614e | 2012-10-09 11:16:03 -0400 | [diff] [blame] | 356 |             if getattr(self, '_ccc', False) is False: | 
| Giampaolo Rodola' | 096dcb1 | 2011-06-27 11:17:51 +0200 | [diff] [blame] | 357 |                 super(SSLConnection, self).close() | 
 | 358 |             else: | 
 | 359 |                 pass | 
| Antoine Pitrou | 2c4f98b | 2010-04-23 00:16:21 +0000 | [diff] [blame] | 360 |  | 
| Antoine Pitrou | f988cd0 | 2009-11-17 20:21:14 +0000 | [diff] [blame] | 361 |         def handle_read_event(self): | 
 | 362 |             if self._ssl_accepting: | 
 | 363 |                 self._do_ssl_handshake() | 
| Antoine Pitrou | 2c4f98b | 2010-04-23 00:16:21 +0000 | [diff] [blame] | 364 |             elif self._ssl_closing: | 
 | 365 |                 self._do_ssl_shutdown() | 
| Antoine Pitrou | f988cd0 | 2009-11-17 20:21:14 +0000 | [diff] [blame] | 366 |             else: | 
 | 367 |                 super(SSLConnection, self).handle_read_event() | 
 | 368 |  | 
 | 369 |         def handle_write_event(self): | 
 | 370 |             if self._ssl_accepting: | 
 | 371 |                 self._do_ssl_handshake() | 
| Antoine Pitrou | 2c4f98b | 2010-04-23 00:16:21 +0000 | [diff] [blame] | 372 |             elif self._ssl_closing: | 
 | 373 |                 self._do_ssl_shutdown() | 
| Antoine Pitrou | f988cd0 | 2009-11-17 20:21:14 +0000 | [diff] [blame] | 374 |             else: | 
 | 375 |                 super(SSLConnection, self).handle_write_event() | 
 | 376 |  | 
 | 377 |         def send(self, data): | 
 | 378 |             try: | 
 | 379 |                 return super(SSLConnection, self).send(data) | 
 | 380 |             except ssl.SSLError as err: | 
| Antoine Pitrou | 5733c08 | 2010-03-22 14:49:10 +0000 | [diff] [blame] | 381 |                 if err.args[0] in (ssl.SSL_ERROR_EOF, ssl.SSL_ERROR_ZERO_RETURN, | 
 | 382 |                                    ssl.SSL_ERROR_WANT_READ, | 
 | 383 |                                    ssl.SSL_ERROR_WANT_WRITE): | 
| Antoine Pitrou | f988cd0 | 2009-11-17 20:21:14 +0000 | [diff] [blame] | 384 |                     return 0 | 
 | 385 |                 raise | 
 | 386 |  | 
 | 387 |         def recv(self, buffer_size): | 
 | 388 |             try: | 
 | 389 |                 return super(SSLConnection, self).recv(buffer_size) | 
 | 390 |             except ssl.SSLError as err: | 
| Antoine Pitrou | 5733c08 | 2010-03-22 14:49:10 +0000 | [diff] [blame] | 391 |                 if err.args[0] in (ssl.SSL_ERROR_WANT_READ, | 
 | 392 |                                    ssl.SSL_ERROR_WANT_WRITE): | 
| Antoine Pitrou | 2c4f98b | 2010-04-23 00:16:21 +0000 | [diff] [blame] | 393 |                     return b'' | 
| Antoine Pitrou | f988cd0 | 2009-11-17 20:21:14 +0000 | [diff] [blame] | 394 |                 if err.args[0] in (ssl.SSL_ERROR_EOF, ssl.SSL_ERROR_ZERO_RETURN): | 
 | 395 |                     self.handle_close() | 
 | 396 |                     return b'' | 
 | 397 |                 raise | 
 | 398 |  | 
 | 399 |         def handle_error(self): | 
 | 400 |             raise | 
 | 401 |  | 
 | 402 |         def close(self): | 
| Antoine Pitrou | 2c4f98b | 2010-04-23 00:16:21 +0000 | [diff] [blame] | 403 |             if (isinstance(self.socket, ssl.SSLSocket) and | 
 | 404 |                 self.socket._sslobj is not None): | 
 | 405 |                 self._do_ssl_shutdown() | 
| Benjamin Peterson | 1bd93a7 | 2010-10-31 19:58:07 +0000 | [diff] [blame] | 406 |             else: | 
 | 407 |                 super(SSLConnection, self).close() | 
| Antoine Pitrou | f988cd0 | 2009-11-17 20:21:14 +0000 | [diff] [blame] | 408 |  | 
 | 409 |  | 
 | 410 |     class DummyTLS_DTPHandler(SSLConnection, DummyDTPHandler): | 
 | 411 |         """A DummyDTPHandler subclass supporting TLS/SSL.""" | 
 | 412 |  | 
 | 413 |         def __init__(self, conn, baseclass): | 
 | 414 |             DummyDTPHandler.__init__(self, conn, baseclass) | 
 | 415 |             if self.baseclass.secure_data_channel: | 
 | 416 |                 self.secure_connection() | 
 | 417 |  | 
 | 418 |  | 
 | 419 |     class DummyTLS_FTPHandler(SSLConnection, DummyFTPHandler): | 
 | 420 |         """A DummyFTPHandler subclass supporting TLS/SSL.""" | 
 | 421 |  | 
 | 422 |         dtp_handler = DummyTLS_DTPHandler | 
 | 423 |  | 
 | 424 |         def __init__(self, conn): | 
 | 425 |             DummyFTPHandler.__init__(self, conn) | 
 | 426 |             self.secure_data_channel = False | 
| Giampaolo Rodola' | 096dcb1 | 2011-06-27 11:17:51 +0200 | [diff] [blame] | 427 |             self._ccc = False | 
| Antoine Pitrou | f988cd0 | 2009-11-17 20:21:14 +0000 | [diff] [blame] | 428 |  | 
 | 429 |         def cmd_auth(self, line): | 
 | 430 |             """Set up secure control channel.""" | 
 | 431 |             self.push('234 AUTH TLS successful') | 
 | 432 |             self.secure_connection() | 
 | 433 |  | 
| Giampaolo Rodola' | 096dcb1 | 2011-06-27 11:17:51 +0200 | [diff] [blame] | 434 |         def cmd_ccc(self, line): | 
 | 435 |             self.push('220 Reverting back to clear-text') | 
 | 436 |             self._ccc = True | 
 | 437 |             self._do_ssl_shutdown() | 
 | 438 |  | 
| Antoine Pitrou | f988cd0 | 2009-11-17 20:21:14 +0000 | [diff] [blame] | 439 |         def cmd_pbsz(self, line): | 
 | 440 |             """Negotiate size of buffer for secure data transfer. | 
 | 441 |             For TLS/SSL the only valid value for the parameter is '0'. | 
 | 442 |             Any other value is accepted but ignored. | 
 | 443 |             """ | 
 | 444 |             self.push('200 PBSZ=0 successful.') | 
 | 445 |  | 
 | 446 |         def cmd_prot(self, line): | 
 | 447 |             """Setup un/secure data channel.""" | 
 | 448 |             arg = line.upper() | 
 | 449 |             if arg == 'C': | 
 | 450 |                 self.push('200 Protection set to Clear') | 
 | 451 |                 self.secure_data_channel = False | 
 | 452 |             elif arg == 'P': | 
 | 453 |                 self.push('200 Protection set to Private') | 
 | 454 |                 self.secure_data_channel = True | 
 | 455 |             else: | 
 | 456 |                 self.push("502 Unrecognized PROT type (use C or P).") | 
 | 457 |  | 
 | 458 |  | 
 | 459 |     class DummyTLS_FTPServer(DummyFTPServer): | 
 | 460 |         handler = DummyTLS_FTPHandler | 
 | 461 |  | 
 | 462 |  | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 463 | class TestFTPClass(TestCase): | 
 | 464 |  | 
 | 465 |     def setUp(self): | 
 | 466 |         self.server = DummyFTPServer((HOST, 0)) | 
 | 467 |         self.server.start() | 
| Giampaolo Rodola' | 0d4f08c | 2013-05-16 15:12:01 +0200 | [diff] [blame] | 468 |         self.client = ftplib.FTP(timeout=TIMEOUT) | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 469 |         self.client.connect(self.server.host, self.server.port) | 
 | 470 |  | 
 | 471 |     def tearDown(self): | 
 | 472 |         self.client.close() | 
 | 473 |         self.server.stop() | 
 | 474 |  | 
| Giampaolo Rodola' | 8bc8585 | 2012-01-09 17:10:10 +0100 | [diff] [blame] | 475 |     def check_data(self, received, expected): | 
 | 476 |         self.assertEqual(len(received), len(expected)) | 
 | 477 |         self.assertEqual(received, expected) | 
 | 478 |  | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 479 |     def test_getwelcome(self): | 
 | 480 |         self.assertEqual(self.client.getwelcome(), '220 welcome') | 
 | 481 |  | 
 | 482 |     def test_sanitize(self): | 
 | 483 |         self.assertEqual(self.client.sanitize('foo'), repr('foo')) | 
 | 484 |         self.assertEqual(self.client.sanitize('pass 12345'), repr('pass *****')) | 
 | 485 |         self.assertEqual(self.client.sanitize('PASS 12345'), repr('PASS *****')) | 
 | 486 |  | 
 | 487 |     def test_exceptions(self): | 
 | 488 |         self.assertRaises(ftplib.error_temp, self.client.sendcmd, 'echo 400') | 
 | 489 |         self.assertRaises(ftplib.error_temp, self.client.sendcmd, 'echo 499') | 
 | 490 |         self.assertRaises(ftplib.error_perm, self.client.sendcmd, 'echo 500') | 
 | 491 |         self.assertRaises(ftplib.error_perm, self.client.sendcmd, 'echo 599') | 
 | 492 |         self.assertRaises(ftplib.error_proto, self.client.sendcmd, 'echo 999') | 
 | 493 |  | 
 | 494 |     def test_all_errors(self): | 
 | 495 |         exceptions = (ftplib.error_reply, ftplib.error_temp, ftplib.error_perm, | 
| Andrew Svetlov | f7a17b4 | 2012-12-25 16:47:37 +0200 | [diff] [blame] | 496 |                       ftplib.error_proto, ftplib.Error, OSError, EOFError) | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 497 |         for x in exceptions: | 
 | 498 |             try: | 
 | 499 |                 raise x('exception not included in all_errors set') | 
 | 500 |             except ftplib.all_errors: | 
 | 501 |                 pass | 
 | 502 |  | 
 | 503 |     def test_set_pasv(self): | 
 | 504 |         # passive mode is supposed to be enabled by default | 
 | 505 |         self.assertTrue(self.client.passiveserver) | 
 | 506 |         self.client.set_pasv(True) | 
 | 507 |         self.assertTrue(self.client.passiveserver) | 
 | 508 |         self.client.set_pasv(False) | 
 | 509 |         self.assertFalse(self.client.passiveserver) | 
 | 510 |  | 
 | 511 |     def test_voidcmd(self): | 
 | 512 |         self.client.voidcmd('echo 200') | 
 | 513 |         self.client.voidcmd('echo 299') | 
 | 514 |         self.assertRaises(ftplib.error_reply, self.client.voidcmd, 'echo 199') | 
 | 515 |         self.assertRaises(ftplib.error_reply, self.client.voidcmd, 'echo 300') | 
 | 516 |  | 
 | 517 |     def test_login(self): | 
 | 518 |         self.client.login() | 
 | 519 |  | 
 | 520 |     def test_acct(self): | 
 | 521 |         self.client.acct('passwd') | 
 | 522 |  | 
 | 523 |     def test_rename(self): | 
 | 524 |         self.client.rename('a', 'b') | 
| Giampaolo Rodolà | bd576b7 | 2010-05-10 14:53:29 +0000 | [diff] [blame] | 525 |         self.server.handler_instance.next_response = '200' | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 526 |         self.assertRaises(ftplib.error_reply, self.client.rename, 'a', 'b') | 
 | 527 |  | 
 | 528 |     def test_delete(self): | 
 | 529 |         self.client.delete('foo') | 
| Giampaolo Rodolà | bd576b7 | 2010-05-10 14:53:29 +0000 | [diff] [blame] | 530 |         self.server.handler_instance.next_response = '199' | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 531 |         self.assertRaises(ftplib.error_reply, self.client.delete, 'foo') | 
 | 532 |  | 
 | 533 |     def test_size(self): | 
 | 534 |         self.client.size('foo') | 
 | 535 |  | 
 | 536 |     def test_mkd(self): | 
 | 537 |         dir = self.client.mkd('/foo') | 
 | 538 |         self.assertEqual(dir, '/foo') | 
 | 539 |  | 
 | 540 |     def test_rmd(self): | 
 | 541 |         self.client.rmd('foo') | 
 | 542 |  | 
| Senthil Kumaran | 0d53860 | 2013-08-12 22:25:27 -0700 | [diff] [blame] | 543 |     def test_cwd(self): | 
 | 544 |         dir = self.client.cwd('/foo') | 
 | 545 |         self.assertEqual(dir, '250 cwd ok') | 
 | 546 |  | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 547 |     def test_pwd(self): | 
 | 548 |         dir = self.client.pwd() | 
 | 549 |         self.assertEqual(dir, 'pwd ok') | 
 | 550 |  | 
 | 551 |     def test_quit(self): | 
 | 552 |         self.assertEqual(self.client.quit(), '221 quit ok') | 
 | 553 |         # Ensure the connection gets closed; sock attribute should be None | 
 | 554 |         self.assertEqual(self.client.sock, None) | 
 | 555 |  | 
| Giampaolo Rodola' | 0b5c21f | 2011-05-07 19:03:47 +0200 | [diff] [blame] | 556 |     def test_abort(self): | 
 | 557 |         self.client.abort() | 
 | 558 |  | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 559 |     def test_retrbinary(self): | 
 | 560 |         def callback(data): | 
 | 561 |             received.append(data.decode('ascii')) | 
 | 562 |         received = [] | 
 | 563 |         self.client.retrbinary('retr', callback) | 
| Giampaolo Rodola' | 8bc8585 | 2012-01-09 17:10:10 +0100 | [diff] [blame] | 564 |         self.check_data(''.join(received), RETR_DATA) | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 565 |  | 
| Antoine Pitrou | 648bcd7 | 2009-11-27 13:23:26 +0000 | [diff] [blame] | 566 |     def test_retrbinary_rest(self): | 
 | 567 |         def callback(data): | 
 | 568 |             received.append(data.decode('ascii')) | 
 | 569 |         for rest in (0, 10, 20): | 
 | 570 |             received = [] | 
 | 571 |             self.client.retrbinary('retr', callback, rest=rest) | 
| Giampaolo Rodola' | 8bc8585 | 2012-01-09 17:10:10 +0100 | [diff] [blame] | 572 |             self.check_data(''.join(received), RETR_DATA[rest:]) | 
| Antoine Pitrou | 648bcd7 | 2009-11-27 13:23:26 +0000 | [diff] [blame] | 573 |  | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 574 |     def test_retrlines(self): | 
 | 575 |         received = [] | 
 | 576 |         self.client.retrlines('retr', received.append) | 
| Giampaolo Rodola' | 8bc8585 | 2012-01-09 17:10:10 +0100 | [diff] [blame] | 577 |         self.check_data(''.join(received), RETR_DATA.replace('\r\n', '')) | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 578 |  | 
 | 579 |     def test_storbinary(self): | 
 | 580 |         f = io.BytesIO(RETR_DATA.encode('ascii')) | 
 | 581 |         self.client.storbinary('stor', f) | 
| Giampaolo Rodola' | 8bc8585 | 2012-01-09 17:10:10 +0100 | [diff] [blame] | 582 |         self.check_data(self.server.handler_instance.last_received_data, RETR_DATA) | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 583 |         # test new callback arg | 
 | 584 |         flag = [] | 
 | 585 |         f.seek(0) | 
 | 586 |         self.client.storbinary('stor', f, callback=lambda x: flag.append(None)) | 
 | 587 |         self.assertTrue(flag) | 
 | 588 |  | 
| Antoine Pitrou | 648bcd7 | 2009-11-27 13:23:26 +0000 | [diff] [blame] | 589 |     def test_storbinary_rest(self): | 
 | 590 |         f = io.BytesIO(RETR_DATA.replace('\r\n', '\n').encode('ascii')) | 
 | 591 |         for r in (30, '30'): | 
 | 592 |             f.seek(0) | 
 | 593 |             self.client.storbinary('stor', f, rest=r) | 
| Giampaolo Rodolà | bd576b7 | 2010-05-10 14:53:29 +0000 | [diff] [blame] | 594 |             self.assertEqual(self.server.handler_instance.rest, str(r)) | 
| Antoine Pitrou | 648bcd7 | 2009-11-27 13:23:26 +0000 | [diff] [blame] | 595 |  | 
| Giampaolo Rodolà | f96482e | 2010-08-04 10:36:18 +0000 | [diff] [blame] | 596 |     def test_storlines(self): | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 597 |         f = io.BytesIO(RETR_DATA.replace('\r\n', '\n').encode('ascii')) | 
 | 598 |         self.client.storlines('stor', f) | 
| Giampaolo Rodola' | 8bc8585 | 2012-01-09 17:10:10 +0100 | [diff] [blame] | 599 |         self.check_data(self.server.handler_instance.last_received_data, RETR_DATA) | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 600 |         # test new callback arg | 
 | 601 |         flag = [] | 
 | 602 |         f.seek(0) | 
 | 603 |         self.client.storlines('stor foo', f, callback=lambda x: flag.append(None)) | 
 | 604 |         self.assertTrue(flag) | 
 | 605 |  | 
| Victor Stinner | ed3a303 | 2013-04-02 22:13:27 +0200 | [diff] [blame] | 606 |         f = io.StringIO(RETR_DATA.replace('\r\n', '\n')) | 
 | 607 |         # storlines() expects a binary file, not a text file | 
| Florent Xicluna | 5f3fef3 | 2013-07-06 15:08:21 +0200 | [diff] [blame] | 608 |         with support.check_warnings(('', BytesWarning), quiet=True): | 
 | 609 |             self.assertRaises(TypeError, self.client.storlines, 'stor foo', f) | 
| Victor Stinner | ed3a303 | 2013-04-02 22:13:27 +0200 | [diff] [blame] | 610 |  | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 611 |     def test_nlst(self): | 
 | 612 |         self.client.nlst() | 
 | 613 |         self.assertEqual(self.client.nlst(), NLST_DATA.split('\r\n')[:-1]) | 
 | 614 |  | 
 | 615 |     def test_dir(self): | 
 | 616 |         l = [] | 
 | 617 |         self.client.dir(lambda x: l.append(x)) | 
 | 618 |         self.assertEqual(''.join(l), LIST_DATA.replace('\r\n', '')) | 
 | 619 |  | 
| Giampaolo Rodola' | d78def9 | 2011-05-06 19:49:08 +0200 | [diff] [blame] | 620 |     def test_mlsd(self): | 
 | 621 |         list(self.client.mlsd()) | 
 | 622 |         list(self.client.mlsd(path='/')) | 
 | 623 |         list(self.client.mlsd(path='/', facts=['size', 'type'])) | 
 | 624 |  | 
 | 625 |         ls = list(self.client.mlsd()) | 
 | 626 |         for name, facts in ls: | 
| Giampaolo Rodola' | a55efb3 | 2011-05-07 16:06:59 +0200 | [diff] [blame] | 627 |             self.assertIsInstance(name, str) | 
 | 628 |             self.assertIsInstance(facts, dict) | 
| Giampaolo Rodola' | d78def9 | 2011-05-06 19:49:08 +0200 | [diff] [blame] | 629 |             self.assertTrue(name) | 
| Giampaolo Rodola' | a55efb3 | 2011-05-07 16:06:59 +0200 | [diff] [blame] | 630 |             self.assertIn('type', facts) | 
 | 631 |             self.assertIn('perm', facts) | 
 | 632 |             self.assertIn('unique', facts) | 
| Giampaolo Rodola' | d78def9 | 2011-05-06 19:49:08 +0200 | [diff] [blame] | 633 |  | 
 | 634 |         def set_data(data): | 
 | 635 |             self.server.handler_instance.next_data = data | 
 | 636 |  | 
 | 637 |         def test_entry(line, type=None, perm=None, unique=None, name=None): | 
 | 638 |             type = 'type' if type is None else type | 
 | 639 |             perm = 'perm' if perm is None else perm | 
 | 640 |             unique = 'unique' if unique is None else unique | 
 | 641 |             name = 'name' if name is None else name | 
 | 642 |             set_data(line) | 
 | 643 |             _name, facts = next(self.client.mlsd()) | 
 | 644 |             self.assertEqual(_name, name) | 
 | 645 |             self.assertEqual(facts['type'], type) | 
 | 646 |             self.assertEqual(facts['perm'], perm) | 
 | 647 |             self.assertEqual(facts['unique'], unique) | 
 | 648 |  | 
 | 649 |         # plain | 
 | 650 |         test_entry('type=type;perm=perm;unique=unique; name\r\n') | 
 | 651 |         # "=" in fact value | 
 | 652 |         test_entry('type=ty=pe;perm=perm;unique=unique; name\r\n', type="ty=pe") | 
 | 653 |         test_entry('type==type;perm=perm;unique=unique; name\r\n', type="=type") | 
 | 654 |         test_entry('type=t=y=pe;perm=perm;unique=unique; name\r\n', type="t=y=pe") | 
 | 655 |         test_entry('type=====;perm=perm;unique=unique; name\r\n', type="====") | 
 | 656 |         # spaces in name | 
 | 657 |         test_entry('type=type;perm=perm;unique=unique; na me\r\n', name="na me") | 
 | 658 |         test_entry('type=type;perm=perm;unique=unique; name \r\n', name="name ") | 
 | 659 |         test_entry('type=type;perm=perm;unique=unique;  name\r\n', name=" name") | 
 | 660 |         test_entry('type=type;perm=perm;unique=unique; n am  e\r\n', name="n am  e") | 
 | 661 |         # ";" in name | 
 | 662 |         test_entry('type=type;perm=perm;unique=unique; na;me\r\n', name="na;me") | 
 | 663 |         test_entry('type=type;perm=perm;unique=unique; ;name\r\n', name=";name") | 
 | 664 |         test_entry('type=type;perm=perm;unique=unique; ;name;\r\n', name=";name;") | 
 | 665 |         test_entry('type=type;perm=perm;unique=unique; ;;;;\r\n', name=";;;;") | 
 | 666 |         # case sensitiveness | 
 | 667 |         set_data('Type=type;TyPe=perm;UNIQUE=unique; name\r\n') | 
 | 668 |         _name, facts = next(self.client.mlsd()) | 
| Giampaolo Rodola' | a55efb3 | 2011-05-07 16:06:59 +0200 | [diff] [blame] | 669 |         for x in facts: | 
 | 670 |             self.assertTrue(x.islower()) | 
| Giampaolo Rodola' | d78def9 | 2011-05-06 19:49:08 +0200 | [diff] [blame] | 671 |         # no data (directory empty) | 
 | 672 |         set_data('') | 
 | 673 |         self.assertRaises(StopIteration, next, self.client.mlsd()) | 
 | 674 |         set_data('') | 
 | 675 |         for x in self.client.mlsd(): | 
 | 676 |             self.fail("unexpected data %s" % data) | 
 | 677 |  | 
| Benjamin Peterson | 3a53fbb | 2008-09-27 22:04:16 +0000 | [diff] [blame] | 678 |     def test_makeport(self): | 
| Brett Cannon | 918e2d4 | 2010-10-29 23:26:25 +0000 | [diff] [blame] | 679 |         with self.client.makeport(): | 
 | 680 |             # IPv4 is in use, just make sure send_eprt has not been used | 
 | 681 |             self.assertEqual(self.server.handler_instance.last_received_cmd, | 
 | 682 |                                 'port') | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 683 |  | 
 | 684 |     def test_makepasv(self): | 
 | 685 |         host, port = self.client.makepasv() | 
| Giampaolo Rodola' | 0d4f08c | 2013-05-16 15:12:01 +0200 | [diff] [blame] | 686 |         conn = socket.create_connection((host, port), timeout=TIMEOUT) | 
| Guido van Rossum | d8faa36 | 2007-04-27 19:54:29 +0000 | [diff] [blame] | 687 |         conn.close() | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 688 |         # IPv4 is in use, just make sure send_epsv has not been used | 
| Giampaolo Rodolà | bd576b7 | 2010-05-10 14:53:29 +0000 | [diff] [blame] | 689 |         self.assertEqual(self.server.handler_instance.last_received_cmd, 'pasv') | 
 | 690 |  | 
 | 691 |     def test_with_statement(self): | 
 | 692 |         self.client.quit() | 
 | 693 |  | 
 | 694 |         def is_client_connected(): | 
 | 695 |             if self.client.sock is None: | 
 | 696 |                 return False | 
 | 697 |             try: | 
 | 698 |                 self.client.sendcmd('noop') | 
| Andrew Svetlov | 0832af6 | 2012-12-18 23:10:48 +0200 | [diff] [blame] | 699 |             except (OSError, EOFError): | 
| Giampaolo Rodolà | bd576b7 | 2010-05-10 14:53:29 +0000 | [diff] [blame] | 700 |                 return False | 
 | 701 |             return True | 
 | 702 |  | 
 | 703 |         # base test | 
| Giampaolo Rodola' | 0d4f08c | 2013-05-16 15:12:01 +0200 | [diff] [blame] | 704 |         with ftplib.FTP(timeout=TIMEOUT) as self.client: | 
| Giampaolo Rodolà | bd576b7 | 2010-05-10 14:53:29 +0000 | [diff] [blame] | 705 |             self.client.connect(self.server.host, self.server.port) | 
 | 706 |             self.client.sendcmd('noop') | 
 | 707 |             self.assertTrue(is_client_connected()) | 
 | 708 |         self.assertEqual(self.server.handler_instance.last_received_cmd, 'quit') | 
 | 709 |         self.assertFalse(is_client_connected()) | 
 | 710 |  | 
 | 711 |         # QUIT sent inside the with block | 
| Giampaolo Rodola' | 0d4f08c | 2013-05-16 15:12:01 +0200 | [diff] [blame] | 712 |         with ftplib.FTP(timeout=TIMEOUT) as self.client: | 
| Giampaolo Rodolà | bd576b7 | 2010-05-10 14:53:29 +0000 | [diff] [blame] | 713 |             self.client.connect(self.server.host, self.server.port) | 
 | 714 |             self.client.sendcmd('noop') | 
 | 715 |             self.client.quit() | 
 | 716 |         self.assertEqual(self.server.handler_instance.last_received_cmd, 'quit') | 
 | 717 |         self.assertFalse(is_client_connected()) | 
 | 718 |  | 
 | 719 |         # force a wrong response code to be sent on QUIT: error_perm | 
 | 720 |         # is expected and the connection is supposed to be closed | 
 | 721 |         try: | 
| Giampaolo Rodola' | 0d4f08c | 2013-05-16 15:12:01 +0200 | [diff] [blame] | 722 |             with ftplib.FTP(timeout=TIMEOUT) as self.client: | 
| Giampaolo Rodolà | bd576b7 | 2010-05-10 14:53:29 +0000 | [diff] [blame] | 723 |                 self.client.connect(self.server.host, self.server.port) | 
 | 724 |                 self.client.sendcmd('noop') | 
 | 725 |                 self.server.handler_instance.next_response = '550 error on quit' | 
 | 726 |         except ftplib.error_perm as err: | 
 | 727 |             self.assertEqual(str(err), '550 error on quit') | 
 | 728 |         else: | 
 | 729 |             self.fail('Exception not raised') | 
 | 730 |         # needed to give the threaded server some time to set the attribute | 
 | 731 |         # which otherwise would still be == 'noop' | 
 | 732 |         time.sleep(0.1) | 
 | 733 |         self.assertEqual(self.server.handler_instance.last_received_cmd, 'quit') | 
 | 734 |         self.assertFalse(is_client_connected()) | 
| Guido van Rossum | d8faa36 | 2007-04-27 19:54:29 +0000 | [diff] [blame] | 735 |  | 
| Giampaolo Rodolà | 396ff06 | 2011-02-28 19:19:51 +0000 | [diff] [blame] | 736 |     def test_source_address(self): | 
 | 737 |         self.client.quit() | 
 | 738 |         port = support.find_unused_port() | 
| Antoine Pitrou | 6dca527 | 2011-04-03 18:29:45 +0200 | [diff] [blame] | 739 |         try: | 
 | 740 |             self.client.connect(self.server.host, self.server.port, | 
 | 741 |                                 source_address=(HOST, port)) | 
 | 742 |             self.assertEqual(self.client.sock.getsockname()[1], port) | 
 | 743 |             self.client.quit() | 
| Andrew Svetlov | f7a17b4 | 2012-12-25 16:47:37 +0200 | [diff] [blame] | 744 |         except OSError as e: | 
| Antoine Pitrou | 6dca527 | 2011-04-03 18:29:45 +0200 | [diff] [blame] | 745 |             if e.errno == errno.EADDRINUSE: | 
 | 746 |                 self.skipTest("couldn't bind to port %d" % port) | 
 | 747 |             raise | 
| Giampaolo Rodolà | 396ff06 | 2011-02-28 19:19:51 +0000 | [diff] [blame] | 748 |  | 
 | 749 |     def test_source_address_passive_connection(self): | 
 | 750 |         port = support.find_unused_port() | 
 | 751 |         self.client.source_address = (HOST, port) | 
| Antoine Pitrou | 6dca527 | 2011-04-03 18:29:45 +0200 | [diff] [blame] | 752 |         try: | 
 | 753 |             with self.client.transfercmd('list') as sock: | 
 | 754 |                 self.assertEqual(sock.getsockname()[1], port) | 
| Andrew Svetlov | f7a17b4 | 2012-12-25 16:47:37 +0200 | [diff] [blame] | 755 |         except OSError as e: | 
| Antoine Pitrou | 6dca527 | 2011-04-03 18:29:45 +0200 | [diff] [blame] | 756 |             if e.errno == errno.EADDRINUSE: | 
 | 757 |                 self.skipTest("couldn't bind to port %d" % port) | 
 | 758 |             raise | 
| Giampaolo Rodolà | 396ff06 | 2011-02-28 19:19:51 +0000 | [diff] [blame] | 759 |  | 
| Giampaolo Rodolà | bbc4782 | 2010-08-23 22:10:32 +0000 | [diff] [blame] | 760 |     def test_parse257(self): | 
 | 761 |         self.assertEqual(ftplib.parse257('257 "/foo/bar"'), '/foo/bar') | 
 | 762 |         self.assertEqual(ftplib.parse257('257 "/foo/bar" created'), '/foo/bar') | 
 | 763 |         self.assertEqual(ftplib.parse257('257 ""'), '') | 
 | 764 |         self.assertEqual(ftplib.parse257('257 "" created'), '') | 
 | 765 |         self.assertRaises(ftplib.error_reply, ftplib.parse257, '250 "/foo/bar"') | 
 | 766 |         # The 257 response is supposed to include the directory | 
 | 767 |         # name and in case it contains embedded double-quotes | 
 | 768 |         # they must be doubled (see RFC-959, chapter 7, appendix 2). | 
 | 769 |         self.assertEqual(ftplib.parse257('257 "/foo/b""ar"'), '/foo/b"ar') | 
 | 770 |         self.assertEqual(ftplib.parse257('257 "/foo/b""ar" created'), '/foo/b"ar') | 
 | 771 |  | 
| Serhiy Storchaka | c30b178 | 2013-10-20 16:58:27 +0300 | [diff] [blame] | 772 |     def test_line_too_long(self): | 
 | 773 |         self.assertRaises(ftplib.Error, self.client.sendcmd, | 
 | 774 |                           'x' * self.client.maxline * 2) | 
 | 775 |  | 
 | 776 |     def test_retrlines_too_long(self): | 
 | 777 |         self.client.sendcmd('SETLONGRETR %d' % (self.client.maxline * 2)) | 
 | 778 |         received = [] | 
 | 779 |         self.assertRaises(ftplib.Error, | 
 | 780 |                           self.client.retrlines, 'retr', received.append) | 
 | 781 |  | 
 | 782 |     def test_storlines_too_long(self): | 
 | 783 |         f = io.BytesIO(b'x' * self.client.maxline * 2) | 
 | 784 |         self.assertRaises(ftplib.Error, self.client.storlines, 'stor', f) | 
 | 785 |  | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 786 |  | 
| Serhiy Storchaka | 4376763 | 2013-11-03 21:31:38 +0200 | [diff] [blame] | 787 | @skipUnless(support.IPV6_ENABLED, "IPv6 not enabled") | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 788 | class TestIPv6Environment(TestCase): | 
 | 789 |  | 
 | 790 |     def setUp(self): | 
| Antoine Pitrou | f6fbf56 | 2013-08-22 00:39:46 +0200 | [diff] [blame] | 791 |         self.server = DummyFTPServer((HOSTv6, 0), af=socket.AF_INET6) | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 792 |         self.server.start() | 
| Giampaolo Rodola' | 0d4f08c | 2013-05-16 15:12:01 +0200 | [diff] [blame] | 793 |         self.client = ftplib.FTP(timeout=TIMEOUT) | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 794 |         self.client.connect(self.server.host, self.server.port) | 
 | 795 |  | 
 | 796 |     def tearDown(self): | 
 | 797 |         self.client.close() | 
 | 798 |         self.server.stop() | 
 | 799 |  | 
 | 800 |     def test_af(self): | 
 | 801 |         self.assertEqual(self.client.af, socket.AF_INET6) | 
 | 802 |  | 
 | 803 |     def test_makeport(self): | 
| Brett Cannon | 918e2d4 | 2010-10-29 23:26:25 +0000 | [diff] [blame] | 804 |         with self.client.makeport(): | 
 | 805 |             self.assertEqual(self.server.handler_instance.last_received_cmd, | 
 | 806 |                                 'eprt') | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 807 |  | 
 | 808 |     def test_makepasv(self): | 
 | 809 |         host, port = self.client.makepasv() | 
| Giampaolo Rodola' | 0d4f08c | 2013-05-16 15:12:01 +0200 | [diff] [blame] | 810 |         conn = socket.create_connection((host, port), timeout=TIMEOUT) | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 811 |         conn.close() | 
| Giampaolo Rodolà | bd576b7 | 2010-05-10 14:53:29 +0000 | [diff] [blame] | 812 |         self.assertEqual(self.server.handler_instance.last_received_cmd, 'epsv') | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 813 |  | 
 | 814 |     def test_transfer(self): | 
 | 815 |         def retr(): | 
 | 816 |             def callback(data): | 
 | 817 |                 received.append(data.decode('ascii')) | 
 | 818 |             received = [] | 
 | 819 |             self.client.retrbinary('retr', callback) | 
| Giampaolo Rodola' | 8bc8585 | 2012-01-09 17:10:10 +0100 | [diff] [blame] | 820 |             self.assertEqual(len(''.join(received)), len(RETR_DATA)) | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 821 |             self.assertEqual(''.join(received), RETR_DATA) | 
 | 822 |         self.client.set_pasv(True) | 
 | 823 |         retr() | 
 | 824 |         self.client.set_pasv(False) | 
 | 825 |         retr() | 
 | 826 |  | 
 | 827 |  | 
| Serhiy Storchaka | 4376763 | 2013-11-03 21:31:38 +0200 | [diff] [blame] | 828 | @skipUnless(ssl, "SSL not available") | 
| Antoine Pitrou | f988cd0 | 2009-11-17 20:21:14 +0000 | [diff] [blame] | 829 | class TestTLS_FTPClassMixin(TestFTPClass): | 
 | 830 |     """Repeat TestFTPClass tests starting the TLS layer for both control | 
 | 831 |     and data connections first. | 
 | 832 |     """ | 
 | 833 |  | 
 | 834 |     def setUp(self): | 
 | 835 |         self.server = DummyTLS_FTPServer((HOST, 0)) | 
 | 836 |         self.server.start() | 
| Giampaolo Rodola' | 0d4f08c | 2013-05-16 15:12:01 +0200 | [diff] [blame] | 837 |         self.client = ftplib.FTP_TLS(timeout=TIMEOUT) | 
| Antoine Pitrou | f988cd0 | 2009-11-17 20:21:14 +0000 | [diff] [blame] | 838 |         self.client.connect(self.server.host, self.server.port) | 
 | 839 |         # enable TLS | 
 | 840 |         self.client.auth() | 
 | 841 |         self.client.prot_p() | 
 | 842 |  | 
 | 843 |  | 
| Serhiy Storchaka | 4376763 | 2013-11-03 21:31:38 +0200 | [diff] [blame] | 844 | @skipUnless(ssl, "SSL not available") | 
| Antoine Pitrou | f988cd0 | 2009-11-17 20:21:14 +0000 | [diff] [blame] | 845 | class TestTLS_FTPClass(TestCase): | 
 | 846 |     """Specific TLS_FTP class tests.""" | 
 | 847 |  | 
 | 848 |     def setUp(self): | 
 | 849 |         self.server = DummyTLS_FTPServer((HOST, 0)) | 
 | 850 |         self.server.start() | 
| Giampaolo Rodola' | 0d4f08c | 2013-05-16 15:12:01 +0200 | [diff] [blame] | 851 |         self.client = ftplib.FTP_TLS(timeout=TIMEOUT) | 
| Antoine Pitrou | f988cd0 | 2009-11-17 20:21:14 +0000 | [diff] [blame] | 852 |         self.client.connect(self.server.host, self.server.port) | 
 | 853 |  | 
 | 854 |     def tearDown(self): | 
 | 855 |         self.client.close() | 
 | 856 |         self.server.stop() | 
 | 857 |  | 
 | 858 |     def test_control_connection(self): | 
| Ezio Melotti | e961593 | 2010-01-24 19:26:24 +0000 | [diff] [blame] | 859 |         self.assertNotIsInstance(self.client.sock, ssl.SSLSocket) | 
| Antoine Pitrou | f988cd0 | 2009-11-17 20:21:14 +0000 | [diff] [blame] | 860 |         self.client.auth() | 
| Ezio Melotti | e961593 | 2010-01-24 19:26:24 +0000 | [diff] [blame] | 861 |         self.assertIsInstance(self.client.sock, ssl.SSLSocket) | 
| Antoine Pitrou | f988cd0 | 2009-11-17 20:21:14 +0000 | [diff] [blame] | 862 |  | 
 | 863 |     def test_data_connection(self): | 
 | 864 |         # clear text | 
| Brett Cannon | 918e2d4 | 2010-10-29 23:26:25 +0000 | [diff] [blame] | 865 |         with self.client.transfercmd('list') as sock: | 
 | 866 |             self.assertNotIsInstance(sock, ssl.SSLSocket) | 
| Antoine Pitrou | 2c4f98b | 2010-04-23 00:16:21 +0000 | [diff] [blame] | 867 |         self.assertEqual(self.client.voidresp(), "226 transfer complete") | 
| Antoine Pitrou | f988cd0 | 2009-11-17 20:21:14 +0000 | [diff] [blame] | 868 |  | 
 | 869 |         # secured, after PROT P | 
 | 870 |         self.client.prot_p() | 
| Brett Cannon | 918e2d4 | 2010-10-29 23:26:25 +0000 | [diff] [blame] | 871 |         with self.client.transfercmd('list') as sock: | 
 | 872 |             self.assertIsInstance(sock, ssl.SSLSocket) | 
| Antoine Pitrou | 2c4f98b | 2010-04-23 00:16:21 +0000 | [diff] [blame] | 873 |         self.assertEqual(self.client.voidresp(), "226 transfer complete") | 
| Antoine Pitrou | f988cd0 | 2009-11-17 20:21:14 +0000 | [diff] [blame] | 874 |  | 
 | 875 |         # PROT C is issued, the connection must be in cleartext again | 
 | 876 |         self.client.prot_c() | 
| Brett Cannon | 918e2d4 | 2010-10-29 23:26:25 +0000 | [diff] [blame] | 877 |         with self.client.transfercmd('list') as sock: | 
 | 878 |             self.assertNotIsInstance(sock, ssl.SSLSocket) | 
| Antoine Pitrou | 2c4f98b | 2010-04-23 00:16:21 +0000 | [diff] [blame] | 879 |         self.assertEqual(self.client.voidresp(), "226 transfer complete") | 
| Antoine Pitrou | f988cd0 | 2009-11-17 20:21:14 +0000 | [diff] [blame] | 880 |  | 
 | 881 |     def test_login(self): | 
 | 882 |         # login() is supposed to implicitly secure the control connection | 
| Ezio Melotti | e961593 | 2010-01-24 19:26:24 +0000 | [diff] [blame] | 883 |         self.assertNotIsInstance(self.client.sock, ssl.SSLSocket) | 
| Antoine Pitrou | f988cd0 | 2009-11-17 20:21:14 +0000 | [diff] [blame] | 884 |         self.client.login() | 
| Ezio Melotti | e961593 | 2010-01-24 19:26:24 +0000 | [diff] [blame] | 885 |         self.assertIsInstance(self.client.sock, ssl.SSLSocket) | 
| Antoine Pitrou | f988cd0 | 2009-11-17 20:21:14 +0000 | [diff] [blame] | 886 |         # make sure that AUTH TLS doesn't get issued again | 
 | 887 |         self.client.login() | 
 | 888 |  | 
 | 889 |     def test_auth_issued_twice(self): | 
 | 890 |         self.client.auth() | 
 | 891 |         self.assertRaises(ValueError, self.client.auth) | 
 | 892 |  | 
 | 893 |     def test_auth_ssl(self): | 
 | 894 |         try: | 
 | 895 |             self.client.ssl_version = ssl.PROTOCOL_SSLv3 | 
 | 896 |             self.client.auth() | 
 | 897 |             self.assertRaises(ValueError, self.client.auth) | 
 | 898 |         finally: | 
 | 899 |             self.client.ssl_version = ssl.PROTOCOL_TLSv1 | 
 | 900 |  | 
| Giampaolo Rodolà | a67299e | 2010-05-26 18:06:04 +0000 | [diff] [blame] | 901 |     def test_context(self): | 
 | 902 |         self.client.quit() | 
 | 903 |         ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1) | 
 | 904 |         self.assertRaises(ValueError, ftplib.FTP_TLS, keyfile=CERTFILE, | 
 | 905 |                           context=ctx) | 
 | 906 |         self.assertRaises(ValueError, ftplib.FTP_TLS, certfile=CERTFILE, | 
 | 907 |                           context=ctx) | 
 | 908 |         self.assertRaises(ValueError, ftplib.FTP_TLS, certfile=CERTFILE, | 
 | 909 |                           keyfile=CERTFILE, context=ctx) | 
 | 910 |  | 
| Giampaolo Rodola' | 0d4f08c | 2013-05-16 15:12:01 +0200 | [diff] [blame] | 911 |         self.client = ftplib.FTP_TLS(context=ctx, timeout=TIMEOUT) | 
| Giampaolo Rodolà | a67299e | 2010-05-26 18:06:04 +0000 | [diff] [blame] | 912 |         self.client.connect(self.server.host, self.server.port) | 
 | 913 |         self.assertNotIsInstance(self.client.sock, ssl.SSLSocket) | 
 | 914 |         self.client.auth() | 
 | 915 |         self.assertIs(self.client.sock.context, ctx) | 
 | 916 |         self.assertIsInstance(self.client.sock, ssl.SSLSocket) | 
 | 917 |  | 
 | 918 |         self.client.prot_p() | 
| Brett Cannon | 918e2d4 | 2010-10-29 23:26:25 +0000 | [diff] [blame] | 919 |         with self.client.transfercmd('list') as sock: | 
 | 920 |             self.assertIs(sock.context, ctx) | 
 | 921 |             self.assertIsInstance(sock, ssl.SSLSocket) | 
| Giampaolo Rodolà | a67299e | 2010-05-26 18:06:04 +0000 | [diff] [blame] | 922 |  | 
| Giampaolo Rodola' | 096dcb1 | 2011-06-27 11:17:51 +0200 | [diff] [blame] | 923 |     def test_ccc(self): | 
 | 924 |         self.assertRaises(ValueError, self.client.ccc) | 
 | 925 |         self.client.login(secure=True) | 
 | 926 |         self.assertIsInstance(self.client.sock, ssl.SSLSocket) | 
 | 927 |         self.client.ccc() | 
 | 928 |         self.assertRaises(ValueError, self.client.sock.unwrap) | 
| Giampaolo Rodola' | 096dcb1 | 2011-06-27 11:17:51 +0200 | [diff] [blame] | 929 |  | 
| Christian Heimes | b2a794d | 2013-12-15 19:50:13 +0100 | [diff] [blame] | 930 |     @skipUnless(HAS_SNI, 'No SNI support in ssl module') | 
| Christian Heimes | e5b5edf | 2013-12-02 02:56:02 +0100 | [diff] [blame] | 931 |     def test_check_hostname(self): | 
 | 932 |         self.client.quit() | 
 | 933 |         ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1) | 
 | 934 |         ctx.verify_mode = ssl.CERT_REQUIRED | 
 | 935 |         ctx.check_hostname = True | 
 | 936 |         ctx.load_verify_locations(CAFILE) | 
 | 937 |         self.client = ftplib.FTP_TLS(context=ctx, timeout=TIMEOUT) | 
 | 938 |  | 
 | 939 |         # 127.0.0.1 doesn't match SAN | 
 | 940 |         self.client.connect(self.server.host, self.server.port) | 
 | 941 |         with self.assertRaises(ssl.CertificateError): | 
 | 942 |             self.client.auth() | 
 | 943 |         # exception quits connection | 
 | 944 |  | 
 | 945 |         self.client.connect(self.server.host, self.server.port) | 
 | 946 |         self.client.prot_p() | 
 | 947 |         with self.assertRaises(ssl.CertificateError): | 
 | 948 |             with self.client.transfercmd("list") as sock: | 
 | 949 |                 pass | 
 | 950 |         self.client.quit() | 
 | 951 |  | 
 | 952 |         self.client.connect("localhost", self.server.port) | 
 | 953 |         self.client.auth() | 
 | 954 |         self.client.quit() | 
 | 955 |  | 
 | 956 |         self.client.connect("localhost", self.server.port) | 
 | 957 |         self.client.prot_p() | 
 | 958 |         with self.client.transfercmd("list") as sock: | 
 | 959 |             pass | 
 | 960 |  | 
| Antoine Pitrou | f988cd0 | 2009-11-17 20:21:14 +0000 | [diff] [blame] | 961 |  | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 962 | class TestTimeouts(TestCase): | 
| Guido van Rossum | d8faa36 | 2007-04-27 19:54:29 +0000 | [diff] [blame] | 963 |  | 
 | 964 |     def setUp(self): | 
| Guido van Rossum | d8faa36 | 2007-04-27 19:54:29 +0000 | [diff] [blame] | 965 |         self.evt = threading.Event() | 
| Christian Heimes | 5e69685 | 2008-04-09 08:37:03 +0000 | [diff] [blame] | 966 |         self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | 
| Antoine Pitrou | 08d0272 | 2012-12-19 20:44:02 +0100 | [diff] [blame] | 967 |         self.sock.settimeout(20) | 
| Benjamin Peterson | ee8712c | 2008-05-20 21:35:26 +0000 | [diff] [blame] | 968 |         self.port = support.bind_port(self.sock) | 
| Antoine Pitrou | 08d0272 | 2012-12-19 20:44:02 +0100 | [diff] [blame] | 969 |         self.server_thread = threading.Thread(target=self.server) | 
 | 970 |         self.server_thread.start() | 
| Christian Heimes | 836baa5 | 2008-02-26 08:18:30 +0000 | [diff] [blame] | 971 |         # Wait for the server to be ready. | 
 | 972 |         self.evt.wait() | 
 | 973 |         self.evt.clear() | 
| Antoine Pitrou | 08d0272 | 2012-12-19 20:44:02 +0100 | [diff] [blame] | 974 |         self.old_port = ftplib.FTP.port | 
| Christian Heimes | 5e69685 | 2008-04-09 08:37:03 +0000 | [diff] [blame] | 975 |         ftplib.FTP.port = self.port | 
| Guido van Rossum | d8faa36 | 2007-04-27 19:54:29 +0000 | [diff] [blame] | 976 |  | 
 | 977 |     def tearDown(self): | 
| Antoine Pitrou | 08d0272 | 2012-12-19 20:44:02 +0100 | [diff] [blame] | 978 |         ftplib.FTP.port = self.old_port | 
 | 979 |         self.server_thread.join() | 
| Guido van Rossum | d8faa36 | 2007-04-27 19:54:29 +0000 | [diff] [blame] | 980 |  | 
| Antoine Pitrou | 08d0272 | 2012-12-19 20:44:02 +0100 | [diff] [blame] | 981 |     def server(self): | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 982 |         # This method sets the evt 3 times: | 
 | 983 |         #  1) when the connection is ready to be accepted. | 
 | 984 |         #  2) when it is safe for the caller to close the connection | 
 | 985 |         #  3) when we have closed the socket | 
| Charles-François Natali | 6e20460 | 2014-07-23 19:28:13 +0100 | [diff] [blame^] | 986 |         self.sock.listen() | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 987 |         # (1) Signal the caller that we are ready to accept the connection. | 
| Antoine Pitrou | 08d0272 | 2012-12-19 20:44:02 +0100 | [diff] [blame] | 988 |         self.evt.set() | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 989 |         try: | 
| Antoine Pitrou | 08d0272 | 2012-12-19 20:44:02 +0100 | [diff] [blame] | 990 |             conn, addr = self.sock.accept() | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 991 |         except socket.timeout: | 
 | 992 |             pass | 
 | 993 |         else: | 
| Antoine Pitrou | 08d0272 | 2012-12-19 20:44:02 +0100 | [diff] [blame] | 994 |             conn.sendall(b"1 Hola mundo\n") | 
 | 995 |             conn.shutdown(socket.SHUT_WR) | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 996 |             # (2) Signal the caller that it is safe to close the socket. | 
| Antoine Pitrou | 08d0272 | 2012-12-19 20:44:02 +0100 | [diff] [blame] | 997 |             self.evt.set() | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 998 |             conn.close() | 
 | 999 |         finally: | 
| Antoine Pitrou | 08d0272 | 2012-12-19 20:44:02 +0100 | [diff] [blame] | 1000 |             self.sock.close() | 
| Guido van Rossum | d8faa36 | 2007-04-27 19:54:29 +0000 | [diff] [blame] | 1001 |  | 
 | 1002 |     def testTimeoutDefault(self): | 
| Georg Brandl | f78e02b | 2008-06-10 17:40:04 +0000 | [diff] [blame] | 1003 |         # default -- use global socket timeout | 
| Serhiy Storchaka | 25d8aea | 2014-02-08 14:50:08 +0200 | [diff] [blame] | 1004 |         self.assertIsNone(socket.getdefaulttimeout()) | 
| Georg Brandl | f78e02b | 2008-06-10 17:40:04 +0000 | [diff] [blame] | 1005 |         socket.setdefaulttimeout(30) | 
 | 1006 |         try: | 
| Antoine Pitrou | f6fbf56 | 2013-08-22 00:39:46 +0200 | [diff] [blame] | 1007 |             ftp = ftplib.FTP(HOST) | 
| Georg Brandl | f78e02b | 2008-06-10 17:40:04 +0000 | [diff] [blame] | 1008 |         finally: | 
 | 1009 |             socket.setdefaulttimeout(None) | 
 | 1010 |         self.assertEqual(ftp.sock.gettimeout(), 30) | 
 | 1011 |         self.evt.wait() | 
 | 1012 |         ftp.close() | 
 | 1013 |  | 
 | 1014 |     def testTimeoutNone(self): | 
 | 1015 |         # no timeout -- do not use global socket timeout | 
| Serhiy Storchaka | 25d8aea | 2014-02-08 14:50:08 +0200 | [diff] [blame] | 1016 |         self.assertIsNone(socket.getdefaulttimeout()) | 
| Georg Brandl | f78e02b | 2008-06-10 17:40:04 +0000 | [diff] [blame] | 1017 |         socket.setdefaulttimeout(30) | 
 | 1018 |         try: | 
| Antoine Pitrou | f6fbf56 | 2013-08-22 00:39:46 +0200 | [diff] [blame] | 1019 |             ftp = ftplib.FTP(HOST, timeout=None) | 
| Georg Brandl | f78e02b | 2008-06-10 17:40:04 +0000 | [diff] [blame] | 1020 |         finally: | 
 | 1021 |             socket.setdefaulttimeout(None) | 
| Serhiy Storchaka | 25d8aea | 2014-02-08 14:50:08 +0200 | [diff] [blame] | 1022 |         self.assertIsNone(ftp.sock.gettimeout()) | 
| Christian Heimes | 836baa5 | 2008-02-26 08:18:30 +0000 | [diff] [blame] | 1023 |         self.evt.wait() | 
| Georg Brandl | f78e02b | 2008-06-10 17:40:04 +0000 | [diff] [blame] | 1024 |         ftp.close() | 
| Guido van Rossum | d8faa36 | 2007-04-27 19:54:29 +0000 | [diff] [blame] | 1025 |  | 
 | 1026 |     def testTimeoutValue(self): | 
 | 1027 |         # a value | 
| Christian Heimes | 5e69685 | 2008-04-09 08:37:03 +0000 | [diff] [blame] | 1028 |         ftp = ftplib.FTP(HOST, timeout=30) | 
| Guido van Rossum | d8faa36 | 2007-04-27 19:54:29 +0000 | [diff] [blame] | 1029 |         self.assertEqual(ftp.sock.gettimeout(), 30) | 
| Christian Heimes | 836baa5 | 2008-02-26 08:18:30 +0000 | [diff] [blame] | 1030 |         self.evt.wait() | 
| Georg Brandl | f78e02b | 2008-06-10 17:40:04 +0000 | [diff] [blame] | 1031 |         ftp.close() | 
| Guido van Rossum | d8faa36 | 2007-04-27 19:54:29 +0000 | [diff] [blame] | 1032 |  | 
 | 1033 |     def testTimeoutConnect(self): | 
 | 1034 |         ftp = ftplib.FTP() | 
| Christian Heimes | 5e69685 | 2008-04-09 08:37:03 +0000 | [diff] [blame] | 1035 |         ftp.connect(HOST, timeout=30) | 
| Guido van Rossum | d8faa36 | 2007-04-27 19:54:29 +0000 | [diff] [blame] | 1036 |         self.assertEqual(ftp.sock.gettimeout(), 30) | 
| Christian Heimes | 836baa5 | 2008-02-26 08:18:30 +0000 | [diff] [blame] | 1037 |         self.evt.wait() | 
| Georg Brandl | f78e02b | 2008-06-10 17:40:04 +0000 | [diff] [blame] | 1038 |         ftp.close() | 
| Guido van Rossum | d8faa36 | 2007-04-27 19:54:29 +0000 | [diff] [blame] | 1039 |  | 
 | 1040 |     def testTimeoutDifferentOrder(self): | 
 | 1041 |         ftp = ftplib.FTP(timeout=30) | 
| Christian Heimes | 5e69685 | 2008-04-09 08:37:03 +0000 | [diff] [blame] | 1042 |         ftp.connect(HOST) | 
| Guido van Rossum | d8faa36 | 2007-04-27 19:54:29 +0000 | [diff] [blame] | 1043 |         self.assertEqual(ftp.sock.gettimeout(), 30) | 
| Christian Heimes | 836baa5 | 2008-02-26 08:18:30 +0000 | [diff] [blame] | 1044 |         self.evt.wait() | 
| Georg Brandl | f78e02b | 2008-06-10 17:40:04 +0000 | [diff] [blame] | 1045 |         ftp.close() | 
| Guido van Rossum | d8faa36 | 2007-04-27 19:54:29 +0000 | [diff] [blame] | 1046 |  | 
 | 1047 |     def testTimeoutDirectAccess(self): | 
 | 1048 |         ftp = ftplib.FTP() | 
 | 1049 |         ftp.timeout = 30 | 
| Christian Heimes | 5e69685 | 2008-04-09 08:37:03 +0000 | [diff] [blame] | 1050 |         ftp.connect(HOST) | 
| Guido van Rossum | d8faa36 | 2007-04-27 19:54:29 +0000 | [diff] [blame] | 1051 |         self.assertEqual(ftp.sock.gettimeout(), 30) | 
| Christian Heimes | 836baa5 | 2008-02-26 08:18:30 +0000 | [diff] [blame] | 1052 |         self.evt.wait() | 
| Guido van Rossum | d8faa36 | 2007-04-27 19:54:29 +0000 | [diff] [blame] | 1053 |         ftp.close() | 
 | 1054 |  | 
 | 1055 |  | 
| R David Murray | 87632f1 | 2013-02-19 18:32:28 -0500 | [diff] [blame] | 1056 | class TestNetrcDeprecation(TestCase): | 
 | 1057 |  | 
 | 1058 |     def test_deprecation(self): | 
 | 1059 |         with support.temp_cwd(), support.EnvironmentVarGuard() as env: | 
 | 1060 |             env['HOME'] = os.getcwd() | 
 | 1061 |             open('.netrc', 'w').close() | 
 | 1062 |             with self.assertWarns(DeprecationWarning): | 
 | 1063 |                 ftplib.Netrc() | 
 | 1064 |  | 
 | 1065 |  | 
 | 1066 |  | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 1067 | def test_main(): | 
| Serhiy Storchaka | 4376763 | 2013-11-03 21:31:38 +0200 | [diff] [blame] | 1068 |     tests = [TestFTPClass, TestTimeouts, TestNetrcDeprecation, | 
 | 1069 |              TestIPv6Environment, | 
 | 1070 |              TestTLS_FTPClassMixin, TestTLS_FTPClass] | 
| Antoine Pitrou | f988cd0 | 2009-11-17 20:21:14 +0000 | [diff] [blame] | 1071 |  | 
| Benjamin Peterson | be17a11 | 2008-09-27 21:49:47 +0000 | [diff] [blame] | 1072 |     thread_info = support.threading_setup() | 
 | 1073 |     try: | 
 | 1074 |         support.run_unittest(*tests) | 
 | 1075 |     finally: | 
 | 1076 |         support.threading_cleanup(*thread_info) | 
 | 1077 |  | 
| Guido van Rossum | d8faa36 | 2007-04-27 19:54:29 +0000 | [diff] [blame] | 1078 |  | 
 | 1079 | if __name__ == '__main__': | 
 | 1080 |     test_main() |